public static void AddModelCollection(this ICmsConfig cmsConfig) { var config = cmsConfig.AddCollection <ModelEntity, ModelRepository>( Constants.ModelMakerAdminCollectionAlias, "Database", "MagentaPink10", "Models", x => { }); config.SetTreeView(x => x.Name); config.SetListView(view => { view.AddDefaultButton(DefaultButtonType.New); view.AddRow(row => { row.AddField(x => x.Name); row.AddField(x => x.PluralName).SetName("Plural name"); row.AddDefaultButton(DefaultButtonType.Edit); }); }); config.SetNodeEditor(editor => { editor.AddSection(section => { section.AddDefaultButton(DefaultButtonType.Up); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Delete); section.AddField(x => x.Name); section.AddField(x => x.PluralName) .SetName("Plural name"); section.AddField(x => x.IconColor) .SetDetails(new MarkupString("See Shared Colors at <a href=\"https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/shared\">https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/shared</a>.")) .SetType(EditorType.Dropdown) .SetDataCollection <EnumDataProvider <Color> >(); section.AddField(x => x.Icon) .SetDetails(new MarkupString("See Fabric Core icons at <a href=\"https://developer.microsoft.com/en-us/fluentui#/styles/web/icons#available-icons\">https://developer.microsoft.com/en-us/fluentui#/styles/web/icons#available-icons</a>.")); section.AddField(x => x.Alias) .SetName("Collection alias") .SetType(EditorType.Readonly) .VisibleWhen((m, s) => s == EntityState.IsExisting); section.AddField(x => x.Output) .SetDescription("Code will be generated for these items") .SetType(EditorType.EnumFlagPicker) .SetDataCollection <EnumDataProvider <OutputItem> >(); section.AddSubCollectionList("modelmaker::property"); }); }); }
public PageSetupResolver( ICmsConfig cmsConfig, ISetupResolver <ITypeRegistration, CustomTypeRegistrationConfig> typeRegistrationSetupResolver) { _cmsConfig = cmsConfig; _typeRegistrationSetupResolver = typeRegistrationSetupResolver; }
// CRUD editor using a mapped repository public static void AddMappedCollection(this ICmsConfig config) { config.AddCollection <MappedEntity, MappedInMemoryRepository <MappedEntity, DatabaseEntity> >("mapped", icon: "git-compare", "Mapped entities", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) // adding a data view builder allows you to have multiple tabs in the list editor, each with a different // query associated with it .SetDataViewBuilder <DatabaseEntityDataViewBuilder>() .SetListEditor(editor => { editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor .AddSection(section => { // since the repository is mapped, the OrderBy query send to the IQueryable of the repository // is based on DatabaseEntity, and not MappedEntity. because of this, the overload of SetOrderByExpression // is used by which you can specify the type of entity section.AddField(x => x.Name) .SetOrderByExpression <DatabaseEntity, string?>(x => x.Name); section.AddField(x => x.Description) .SetOrderByExpression <DatabaseEntity, string?>(x => x.Description); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); }); }); }); }
// CRUD editor with support for one-to-many relation + validation public static void AddCountryCollection(this ICmsConfig config) { config.AddCollection <Country>("country", "Countries", collection => { collection .SetTreeView(x => x.Name) .SetRepository <JsonRepository <Country> >() .SetListView(view => { view.AddDefaultButton(DefaultButtonType.New); view .AddRow(row => { row.AddField(p => p.Id.ToString()).SetName("ID"); row.AddField(p => p.Name); row.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor .AddDefaultButton(DefaultButtonType.SaveExisting) .AddDefaultButton(DefaultButtonType.SaveNew); editor.AddSection(section => { section.AddField(x => x.Name); // this property contains a list of people it is related to // you can see it as a ICollection<TRelated> property in EF Core // because this property has an RelationValidationAttribute, the CMS first checks if the // selected values are valid, before allowing the user to save this entity. // in this example, the user may only select up to two People per Country section.AddField(x => x.People) // a multi-select is a list of checkboxes .SetType(EditorType.MultiSelect) // this binds the PersonCollection to this collection // the CMS must know what the foreign entity and key is, you need to specify it .SetCollectionRelation <Person, int>( // this selects which values are used as selected values people => people.Select(p => p.Id), // alias of the related collection "person", // this callback allows you to specify how the multi-select should look like relation => { relation // when the user selects an element, the value that is used as Id is used // to set the value of the property .SetElementIdProperty(x => x.Id) .SetElementDisplayProperties(x => x.Name, x => x.Email); }); }); }); }); }
// CURD editor with validation attributes, custom editor and custom button panes public static void AddUserCollection(this ICmsConfig config) { config.AddCollection <User>("user", "Users", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) .SetRepository <JsonRepository <User> >() .SetListEditor(editor => { editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); // this pane button opens a sidepane displaying the ResetAllPane Razor component. // this component must inherit BaseSidePane and allows for more complex flows and confirmations. editor.AddPaneButton(typeof(ResetAllPane), "Reset all passwords", "trash"); // custom buttons are also allowed: // editor.AddCustomButton<TActionHandler>(typeof(ButtonType)); // they must reference a BaseButton derived Razor component, as well as a ActionHandler which handles // the click from the user. editor .AddSection(section => { section.AddField(x => x.Name); section.AddField(x => x.StartDate).SetType(EditorType.Date); // this field uses a custom editor, which must inherit BaseEditor section.AddField(x => x.Password).SetType(typeof(PasswordEditor)); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); }); }); }); }
/// <summary> /// This method adds the Model Maker plugin and collection to RapidCMS. /// </summary> /// <param name="cmsConfig"></param> /// <returns></returns> public static ICmsConfig AddModelMakerPlugin(this ICmsConfig cmsConfig) { cmsConfig.AddModelMakerPluginCore(); cmsConfig.AddModelCollection(); return(cmsConfig); }
public static void AddActiveCollection(this ICmsConfig config) { config.AddCollection <Counter, BaseRepository <Counter> >("active", icon: "contacts", "Active Counter", collection => { collection .SetTreeView(x => x.CurrentCount.ToString()) .ConfigureByConvention(CollectionConvention.ListViewNodeView); }); }
public TreeElementsSetupResolver(ICmsConfig cmsConfig, ISetupResolver <IEnumerable <TreeElementSetup>, IEnumerable <ITreeElementConfig> > treeElementResolver, ISetupResolver <IEnumerable <TreeElementSetup>, IPlugin> pluginTreeElementResolver, IEnumerable <IPlugin> plugins) { _cmsConfig = cmsConfig; _treeElementResolver = treeElementResolver; _pluginTreeElementResolver = pluginTreeElementResolver; _plugins = plugins; }
public CmsRepositoryTypeResolver(ICmsConfig cmsConfig, IServiceProvider serviceProvider) { using var repositoryResolvingScope = serviceProvider.CreateScope(); var types = cmsConfig.RepositoryTypes.Distinct(); _repositoryTypes = types.ToDictionary( type => AliasHelper.GetRepositoryAlias(repositoryResolvingScope.ServiceProvider.GetRequiredService(type).GetType())); _originallyRegisterdRepositoriesToAlias = _repositoryTypes.ToDictionary(x => x.Value, x => x.Key); }
public CollectionSetupResolver(ICmsConfig cmsConfig, ISetupResolver <IEnumerable <ITreeElementSetup>, IEnumerable <ITreeElementConfig> > treeElementResolver, ISetupResolver <IEntityVariantSetup, EntityVariantConfig> entityVariantResolver, ISetupResolver <TreeViewSetup, TreeViewConfig> treeViewResolver, ISetupResolver <ListSetup, ListConfig> listResolver, ISetupResolver <NodeSetup, NodeConfig> nodeResolver) { _treeElementResolver = treeElementResolver; _entityVariantResolver = entityVariantResolver; _treeViewResolver = treeViewResolver; _listResolver = listResolver; _nodeResolver = nodeResolver; Initialize(cmsConfig); }
// CRUD editor for simple POCO based on conventions public static void AddConventionCollection(this ICmsConfig config) { config.AddCollection <ConventionalPerson, JsonRepository <ConventionalPerson> >("person-convention", "People (by convention)", collection => { collection.SetTreeView(x => x.Name); // The convention system resolves the configuration based on the [Display]-attributes placed on the properties of the model of this collection. // It uses the EditorTypeHelper.TryFindDefaultEditorType to resolve the best matching editor for the property. // // - The ListEditor will display a list editor with columns for every column with a [Display] attribute and use its Name and Description // for displaying the name and description. // // - The ListView+NodeEditor will display a list view with columns for each column with a [Display] attribute with a defined ShortName. // The corresponding node editor will display an editor with fields for each the properties that sport a [Display] attribute, and uses // the Name and Description of said attribute. // // - The ListView will only display a readonly list view without edit options. collection.ConfigureByConvention(CollectionConvention.ListViewNodeEditor); }); }
public CollectionSetupResolver(ICmsConfig cmsConfig, ISetupResolver <IEnumerable <TreeElementSetup>, IEnumerable <ITreeElementConfig> > treeElementResolver, ISetupResolver <EntityVariantSetup, EntityVariantConfig> entityVariantResolver, ISetupResolver <TreeViewSetup, TreeViewConfig> treeViewResolver, ISetupResolver <ElementSetup, ElementConfig> elementResolver, ISetupResolver <ListSetup, ListConfig> listResolver, ISetupResolver <NodeSetup, NodeConfig> nodeResolver, IRepositoryTypeResolver repositoryTypeResolver, IEnumerable <IPlugin> plugins) { _cmsConfig = cmsConfig; _treeElementResolver = treeElementResolver; _entityVariantResolver = entityVariantResolver; _treeViewResolver = treeViewResolver; _elementResolver = elementResolver; _listResolver = listResolver; _nodeResolver = nodeResolver; _repositoryTypeResolver = repositoryTypeResolver; _plugins = plugins; Initialize(); }
private void Initialize(ICmsConfig cmsConfig) { MapCollections(cmsConfig.CollectionsAndPages.SelectNotNull(x => x as CollectionConfig)); void MapCollections(IEnumerable <CollectionConfig> collections) { foreach (var collection in collections.Where(col => !col.Recursive)) { if (!_collectionMap.TryAdd(collection.Alias, collection)) { throw new InvalidOperationException($"Duplicate collection alias '{collection.Alias}' not allowed."); } var subCollections = collection.CollectionsAndPages.SelectNotNull(x => x as CollectionConfig); if (subCollections.Any()) { MapCollections(subCollections); } } } }
// CRUD editor for simple POCO based on conventions public static void AddConventionCollection(this ICmsConfig config) { config.AddCollection <ConventionalPerson, BaseRepository <ConventionalPerson> >("person-convention", "Settings", "Green20", "People (by convention)", collection => { collection.SetTreeView(EntityVisibilty.Visible, x => x.Name); // The convention system resolves the configuration based on the [Display]-attributes placed on the properties of the model of this collection. // It uses the EditorTypeHelper.TryFindDefaultEditorType to resolve the best matching editor for the property. // // - The ListEditor will display a list editor with columns for every column with a [Display] attribute and use its Name and Description // for displaying the name and description. // // - The ListView+NodeEditor will display a list view with columns for each column with a [Display] attribute with a defined ShortName. // The corresponding node editor will display an editor with fields for each the properties that sport a [Display] attribute, and uses // the Name and Description of said attribute. // // - The ListView will only display a readonly list view without edit options. collection.ConfigureByConvention(CollectionConvention.ListViewNodeEditor); // There are three combo's that should be used: // - collection.SetTreeView(EntityVisibilty.Visible, x => x.Property); // - collection.ConfigureByConvention(CollectionConvention.ListViewNodeEditor); // There are three combo's that should be used: // - collection.SetTreeView(EntityVisibilty.Hidden, x => x.Property); // - collection.ConfigureByConvention(CollectionConvention.ListView); // There are three combo's that should be used: // - collection.SetTreeView(EntityVisibilty.Visible, x => x.Property); // - collection.ConfigureByConvention(CollectionConvention.ListViewNodeView); // There are three combo's that should be used: // - collection.SetTreeView(EntityVisibilty.Hidden, x => x.Property); // - collection.ConfigureByConvention(CollectionConvention.ListEditor); // Sub collections are added to the node view / node editor when there are collections. collection.AddSelfAsRecursiveCollection(); }); }
// CRUD editor with validation attributes, custom editor and custom button panes public static void AddUserCollection(this ICmsConfig config) { // the CMS users https://ionicons.com/, so use the name of any Ion Icon as icon for a collection config.AddCollection <User, BaseRepository <User> >("user", "UserFollowed", "BlueMagenta30", "Users", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) .SetListEditor(editor => { editor.AllowReordering(true); // you can control the number of entities on a single page editor.SetPageSize(20); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); // this pane button opens a sidepane displaying the ResetAllPane Razor component. // this component must inherit BaseSidePane and allows for more complex flows and confirmations. editor.AddPaneButton(typeof(ResetAllPane), "Reset all passwords", "LockSolid"); // custom buttons are also allowed: // editor.AddCustomButton<TActionHandler>(typeof(ButtonType)); // they must reference a BaseButton derived Razor component, as well as a ActionHandler which handles // the click from the user. editor .AddSection(section => { section.AddField(x => x.Name); // this field uses a custom editor, which must inherit BaseEditor section.AddField(x => x.Password).SetType(typeof(PasswordEditor)); // even though some properties on User are required, saving a User with only its Name set is allowed // since this editor cannot touch those required properties. // if all displayed properties on a model are valid, the whole model is considered valid, as the user // will be unable to make the model valid otherwise. section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor .AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddField(x => x.Name); section.AddField(x => x.StartDate).SetType(EditorType.Date); // this field uses a custom editor, which must inherit BaseEditor section.AddField(x => x.Password).SetType(typeof(PasswordEditor)); // some default editors (like FileUploadEditor) require custom components, so they must be added using their full classname // NoPreview is a default component indicating this upload editor has no preview of the file // the file upload handler is different between ServerSide and WebAssembly, so only its interface is added here // and via DI the correct handler is resolved. it's also allowed to reference the correct handler here (for example: Base64TextFileUploadHandler) // to allow for per input configuration // AND dependency injection in Blazor has trouble resolving generic types (like ApiFileUploadHandler<Base64TextFileUploadHandler>) so it's better // to reference simple interfaces or types section.AddField(x => x.FileBase64).SetType(typeof(FileUploadEditor <ITextUploadHandler, NoPreview>)) .SetName("User file"); // ImagePreview is a custom component derived from BasePreview to display the uploaded image section.AddField(x => x.ProfilePictureBase64).SetType(typeof(FileUploadEditor <IImageUploadHandler, ImagePreview>)) .SetName("User picture"); section.AddField(x => x.Integer).SetType(EditorType.Numeric); section.AddField(x => x.Double).SetType(EditorType.Numeric); }); }); }); }
// CRUD editor with validation attributes, custom editor and custom button panes public static void AddUserCollection(this ICmsConfig config) { // the CMS users https://ionicons.com/, so use the name of any Ion Icon as icon for a collection config.AddCollection <User, JsonRepository <User> >("user", icon: "contacts", "Users", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) .SetListEditor(editor => { // you can control the number of entities on a single page editor.SetPageSize(2); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); // this pane button opens a sidepane displaying the ResetAllPane Razor component. // this component must inherit BaseSidePane and allows for more complex flows and confirmations. editor.AddPaneButton(typeof(ResetAllPane), "Reset all passwords", "trash"); // custom buttons are also allowed: // editor.AddCustomButton<TActionHandler>(typeof(ButtonType)); // they must reference a BaseButton derived Razor component, as well as a ActionHandler which handles // the click from the user. editor .AddSection(section => { section.AddField(x => x.Name); // this field uses a custom editor, which must inherit BaseEditor section.AddField(x => x.Password).SetType(typeof(PasswordEditor)); // even though some properties on User are required, saving a User with only its Name set is allowed // since this editor cannot touch those required properties. // if all displayed properties on a model are valid, the whole model is considered valid, as the user // will be unable to make the model valid otherwise. section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor .AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddField(x => x.Name); section.AddField(x => x.StartDate).SetType(EditorType.Date); // this field uses a custom editor, which must inherit BaseEditor section.AddField(x => x.Password).SetType(typeof(PasswordEditor)); // some default editors (like FileUploadEditor) require custom components, so they must be added using their full classname // NoPreview is a default component indicating this upload editor has no preview of the file section.AddField(x => x.FileBase64).SetType(typeof(FileUploadEditor <Base64TextFileUploadHandler, NoPreview>)) .SetName("User file"); // ImagePreview is a custom component derived from BasePreview to display the uploaded image section.AddField(x => x.ProfilePictureBase64).SetType(typeof(FileUploadEditor <Base64ImageUploadHandler, ImagePreview>)) .SetName("User picture"); section.AddField(x => x.Integer).SetType(EditorType.Numeric); section.AddField(x => x.Double).SetType(EditorType.Numeric); }); }); }); }
// CRUD editor with support for one-to-many relation + validation public static void AddCountryCollection(this ICmsConfig config) { config.AddCollection <Country>("country", "Countries", collection => { collection .SetTreeView(x => x.Name) .SetRepository <JsonRepository <Country> >() .SetListView(view => { view.AddDefaultButton(DefaultButtonType.New); view.SetPageSize(2); view .AddRow(row => { row.AddField(p => p.Id.ToString()) .SetName("ID").SetType(DisplayType.Pre); // by specifying SetOrderExpression you can allow the user to sort the data on that column // giving it a default order will cause this column to be ordered when the user opens the view row.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); // multiple columns can be ordered at once, and the OrderBys stack from left to right, // so the Countries in this example will always be first ordered by Name, then by Metadata.Continent row.AddField(p => p.Metadata.Continent) .SetOrderByExpression(p => p.Metadata.Continent); row.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor // the Up button allows users to get one level up (based upon the tree) .AddDefaultButton(DefaultButtonType.Up, "Back to list", "list") .AddDefaultButton(DefaultButtonType.SaveExisting) .AddDefaultButton(DefaultButtonType.SaveNew); editor.AddSection(section => { section.AddField(x => x.Name); section.AddField(x => x.Metadata.Continent); // this property contains a list of people it is related to // you can see it as a ICollection<TRelated> property in EF Core // because this property has an RelationValidationAttribute, the CMS first checks if the // selected values are valid, before allowing the user to save this entity. // in this example, the user may only select up to two People per Country section.AddField(x => x.People) // a multi-select is a list of checkboxes .SetType(EditorType.MultiSelect) // this binds the PersonCollection to this collection // the CMS must know what the foreign entity and key is, you need to specify it .SetCollectionRelation <Person, int>( // this selects which values are used as selected values people => people.Select(p => p.Id), // alias of the related collection "person", // this callback allows you to specify how the multi-select should look like relation => { relation // when the user selects an element, the value that is used as Id is used // to set the value of the property .SetElementIdProperty(x => x.Id) .SetElementDisplayProperties(x => x.Name, x => x.Email); }); }); }); }); }
// CRUD editor with support for one-to-many relation + validation public static void AddCountryCollection(this ICmsConfig config) { config.AddCollection <Country, BaseRepository <Country> >("country", "Nav2DMapView", "Blue10", "Countries", collection => { // this collection uses a custom validator next to the data annotation validation collection.AddEntityValidator <CountryValidator>( new CountryValidator.Config { ForbiddenContinentName = "fdsafdsa", ForbiddenCountryName = "fdsa" }); collection // Set showEntities to true to have this collection to fold open on default .SetTreeView(x => x.Name, showEntitiesOnStartup: false) .SetListView(view => { view.AddDefaultButton(DefaultButtonType.New); view.SetPageSize(10); view .AddRow(row => { row.AddField(p => p.Id.ToString()) .SetName("ID").SetType(DisplayType.Pre); // by specifying SetOrderExpression you can allow the user to sort the data on that column // giving it a default order will cause this column to be ordered when the user opens the view row.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); // multiple columns can be ordered at once, and the OrderBys stack from left to right, // so the Countries in this example will always be first ordered by Name, then by Metadata.Continent row.AddField(p => p.Metadata.Continent) .SetOrderByExpression(p => p.Metadata.Continent); row.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor // the Up button allows users to get one level up (based upon the tree) .AddDefaultButton(DefaultButtonType.Up, "Back to list", "Back") .AddDefaultButton(DefaultButtonType.SaveExisting) .AddDefaultButton(DefaultButtonType.SaveNew) .AddDefaultButton(DefaultButtonType.Delete) // using navigation buttons can help the user to other pages quickly .AddNavigationButton <NavigateToPersonHandler>("Create new person", "FollowUser", isVisible: (country, state) => state == EntityState.IsExisting); editor.AddSection(section => { section.AddField(x => x.Name); section.AddField(x => x.Metadata.Continent); section.AddField(x => x.Metadata.Tag) .SetType(EditorType.Dropdown) // the FixedOptionsDataProvider allows for adding hard-coded options, without requiring a Enum .SetDataCollection(new FixedOptionsDataProvider( new[] { "Tag A", "Tag B", "Tab C" })); // this property contains a list of people it is related to // you can see it as a ICollection<TRelated> property in EF Core // because this property has an RelationValidationAttribute, the CMS first checks if the // selected values are valid, before allowing the user to save this entity. // in this example, the user may only select up to two People per Country section.AddField(x => x.People) // a multi-select is a list of checkboxes .SetType(EditorType.MultiSelect) // this binds the PersonCollection to this collection // the CMS must know what the foreign entity and key is, you need to specify it .SetCollectionRelation <Person, int>( // this selects which values are used as selected values people => people.Select(p => p.Id), // alias of the related collection "person", // this callback allows you to specify how the multi-select should look like relation => { relation // when the user selects an element, the value that is used as Id is used // to set the value of the property .SetElementIdProperty(x => x.Id) // multiple display properties can be used to display the dropdown, even // with nested properties .SetElementDisplayProperties(x => x.Name, x => x.Details.Email); }); }); editor.AddSection(section => { section.SetLabel("Related people"); section.AddRelatedCollectionList <Person, BaseRepository <Person> >(relation => { relation.SetListEditor(editor => { editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.Add); editor.AddDefaultButton(DefaultButtonType.New); editor.AddSection(row => { row.AddField(x => x.Name); row.AddDefaultButton(DefaultButtonType.Pick); row.AddDefaultButton(DefaultButtonType.Remove); row.AddDefaultButton(DefaultButtonType.SaveExisting); row.AddDefaultButton(DefaultButtonType.SaveNew); row.AddDefaultButton(DefaultButtonType.Delete); }); }); }); }); }); }); }
public static void AddTagCollection(this ICmsConfig config) { config.AddCollection <TagGroup, JsonRepository <TagGroup> >("taggroup", "Tag groups", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) .SetListEditor(editor => { editor.SetListType(ListType.Block); editor.SetSearchBarVisibility(false); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor .AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddField(x => x.Name); section.AddField(x => x.DefaultTagId).SetName("Default tag") .SetType(EditorType.Dropdown) .SetCollectionRelation <Tag, JsonRepository <Tag> >(config => { // this allows for configuring which property of the entity will make up the id for the element, and that value // is set to FavouriteChildId when the user selects an element config.SetElementIdProperty(x => x.Id); // because a single label is often not enough, you can add more properties to make the labels for each option config.SetElementDisplayProperties(x => x.Name, x => $"{x.Id} - {x.Name}"); // sets the entity that is currently edited as parent for the repository to get elements for this field config.SetEntityAsParent(); // the data in this dropdown will refresh automatically when the repository on which the data // is based flags that the data has been updated }); // it is possible to view or edit an subcollection from the parent // when adding a subcollection in an Editor will have to be a ListEditor while // adding a subcollection in a View will be a ListView. // the entity of this editor will be passed in as IParent in the repository of the // sub collection, making it possible to access the parents properties in the childrens repository section.AddSubCollectionList <Tag, JsonRepository <Tag> >(config => { config.SetListEditor(editor => { editor.SetSearchBarVisibility(false); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor.AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); // add custom buttons and action handlers using the following handler section.AddCustomButton <RandomNameActionHandler>(typeof(CustomButton), "Create name", "add-circle"); section.AddField(x => x.Name); }); }); }); // if you want to reuse a collection in multiple views, you can also reference it by alias // if you comment out the AddSubCollectionList above this comment, and enable the AddSubCollectionList below, // the editor will work identical, but now the collection with alias "tag" can be used for multiple things // section.AddSubCollectionList("tag"); }); }); // any collection can be added as subcollection, even collections based upon totally difference repositories // this lets you mix repositories which are based upon totally different databases easily collection .AddSubCollection <Tag, JsonRepository <Tag> >("tag", "Tags", subCollection => { subCollection .SetListEditor(editor => { editor.SetListType(ListType.Table); editor.SetSearchBarVisibility(false); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor.AddDefaultButton(DefaultButtonType.SaveExisting); editor.AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); // add custom buttons and action handlers using the following handler section.AddCustomButton <RandomNameActionHandler>(typeof(CustomButton), "Create name", "add-circle"); section.AddField(x => x.Name); }); }); }); }); }
public static void AddTagCollection(this ICmsConfig config) { config.AddCollection <TagGroup>("taggroup", "Tag groups", collection => { collection .SetTreeView(EntityVisibilty.Hidden, x => x.Name) .SetRepository <JsonRepository <TagGroup> >() .SetListEditor(ListType.Block, editor => { editor.SetSearchBarVisibility(false); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor .AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddField(x => x.Name); // it is possible to view or edit an subcollection from the parent // when adding a subcollection in an Editor will render the ListEditor while // adding a subcollection in a View will render the ListView. // the entity of this editor will be passed in as IParent in the repository of the // sub collection, making it possible to access the parents properties in the childrens repository section.AddSubCollectionList <Tag>("tag"); }); }); // any collection can be added as subcollection, even collections based upon totally difference repositories // this lets you mix repositories which are based upon totally different databases easily collection .AddCollection <Tag>("tag", "Tags", subCollection => { subCollection .SetRepository <JsonRepository <Tag> >() .SetListEditor(ListType.Table, editor => { editor.SetSearchBarVisibility(false); editor.AddDefaultButton(DefaultButtonType.Return); editor.AddDefaultButton(DefaultButtonType.New); editor.AddSection(section => { section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); // add custom buttons and action handlers using the following handler section.AddCustomButton <RandomNameActionHandler>(typeof(CustomButton), "Create name", "add-circle"); section.AddField(x => x.Name); }); }); }); }); }
// CRUD editor for simple POCO with recursive sub collections public static void AddPersonCollection(this ICmsConfig config) { config.AddCollection <Person>("person", "People", collection => { collection .SetTreeView(x => x.Name) // this repository handles all the CRUD for this collection .SetRepository <JsonRepository <Person> >() // a list view is a table that displays a row (or multiple rows) of info per entity .SetListView(view => { // if new entities can be made using the CMS, include a New button so users can insert new entities view.AddDefaultButton(DefaultButtonType.New); // multiple rows can be added to display even more data // only the first row will be used to generate headers view.AddRow(row => { // views always require you to specify strings, so the Id (an int) must be .ToString-ed. // since this messes up the name of the column, you can set it to a nice name row.AddField(p => p.Id.ToString()).SetName("ID"); row.AddField(p => p.Name); // if an entity can be edited, include an Edit button so users can start editing entities // the edit button in a list will always direct the user to the NodeEditor row.AddDefaultButton(DefaultButtonType.Edit); }); }) // a list editor is similair to a list view, but every column contains an editor so multiple entities can be edited in one go // a list editor takes precedence over a list view, so when navigating to the Person collection, this view will be displayed .SetListEditor(editor => { // adding Up to the button bar allows the user to get to the level above the current page (base upon the tree) // this button will be hidden automatically when the user is at the root editor.AddDefaultButton(DefaultButtonType.Up); // in a list editor a New allows the user to add entities, within the list editor editor.AddDefaultButton(DefaultButtonType.New); editor.AddDefaultButton(DefaultButtonType.Return); // a list editor can be in the shape of a table, or a set of blocks, so these sections are either rows or blocks editor.AddSection(row => { // these fields will be the editors row.AddField(p => p.Id).SetType(EditorType.Readonly); row.AddField(p => p.Name); // the SaveExisting button is only displayed when an entity is edited row.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); // the SaveNew button is only displayed when an entity is inserted row.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); // the View button always directs the user to the NodeView row.AddDefaultButton(DefaultButtonType.View); // the Edit button always directs the user to the NodeEdit row.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { // adding Up to the button bar allows the user to get to the level above the current page (base upon the tree) editor.AddDefaultButton(DefaultButtonType.Up); // just as in the ListEditor, SaveExisting only shows when the user is editing an existing entity, // and the SaveNew only when inserting an entity editor.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); editor.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); // if an entity can be deleted, add a button for it, so the user can delete the entity editor.AddDefaultButton(DefaultButtonType.Delete); // an node editor can have multiple sections, which are displayed as seperte blocks editor.AddSection(section => { // the DisableWhen expression is evaluated everytime any property of the entity is updated // so this allows you to make response forms which show or hide parts based upon the entity and its state section.AddField(x => x.Id).DisableWhen((person, state) => true); // it is allowed to use DisplayType fields in Editors, so some readonly data can easily be displayed section.AddField(x => x.Name).SetType(DisplayType.Label); section.AddField(x => x.Email); }); // you can even have sections specifically for an entity type. // if the repository can return multiple types of entities (all derived from a shared base type), // sections can be made specifically for a type editor.AddSection <Person>(section => { // sections can have labels to make complex forms more understandable section.SetLabel("Biography"); // sections can be hidden using VisibleWhen, based upon the entity or the state of that entity // so users won't be confronted with editors that do not apply section.VisibleWhen((person, state) => state == EntityState.IsExisting); // there are many types of editors available, and even custom types can be used section.AddField(x => x.Bio).SetType(EditorType.TextArea); }); editor.AddSection(section => { // relations with other entities, collections and repositories are first-class in RapidCMS // so this field will allow the user to select an entity that is one level deeper in the person-tree section.AddField(x => x.FavouriteChildId) .SetName("Favourite child") .SetType(EditorType.Select) .SetCollectionRelation <Person>("person", config => { // this allows for configuring which property of the entity will make up the id for the element, and that value // is set to FavouriteChildId when the user selects an element config.SetElementIdProperty(x => x.Id); // because a single label is often not enough, you can add more properties to make the labels for each option config.SetElementDisplayProperties(x => x.Name, x => $"{x.Id} - {x.Email}"); // sets the entity that is currently edited as parent for the repository to get elements for this field config.SetEntityAsParent(); // any level from the tree can be picked: // Favouite sibling: config.SetRepositoryParent(x => x); // Favouite parent: config.SetRepositoryParent(x => x.Parent); // Favouite grand parent: config.SetRepositoryParent(x => x.Parent != null ? x.Parent.Parent : default); // ?. is not yet supported in expressions.. }); }); }) .SetNodeView(view => { view.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); view.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); view.AddDefaultButton(DefaultButtonType.Delete); view.AddSection(section => { // views also allow for customization of how the data should be displayed // you can use the availablep DisplayType's, or create your own Razor components (must be derived from BaseDisplay) section.AddField(x => x.Id.ToString()).SetName("ID").SetType(DisplayType.Pre); section.AddField(x => x.Name).SetType(DisplayType.Label); section.AddField(x => x.Email).SetType(typeof(EmailDisplay)); }); view.AddSection(section => { section.AddField(x => x.Bio); }); }); collection.AddSelfAsRecursiveCollection(); }); }
// CRUD editor for simple POCO with recursive sub collections public static void AddPersonCollection(this ICmsConfig config) { config.AddCollection <Person, BaseRepository <Person> >("person", "Contact", "Red20", "People", collection => { collection .SetTreeView(x => x.Name) .SetElementConfiguration( x => x.Id, x => x.Name, x => x.Details != null ? x.Details.Email : "") // this repository handles all the CRUD for this collection // a list view is a table that displays a row (or multiple rows) of info per entity .SetListView(view => { // if new entities can be made using the CMS, include a New button so users can insert new entities view.AddDefaultButton(DefaultButtonType.New); // multiple rows can be added to display even more data // only the first row will be used to generate headers view.AddRow(row => { // views always require you to specify strings, so the Id (an int) must be .ToString-ed. // since this messes up the name of the column, you can set it to a nice name row.AddField(p => p.Id.ToString()).SetName("ID"); row.AddField(p => p.Name); // if an entity can be edited, include an Edit button so users can start editing entities // the edit button in a list will always direct the user to the NodeEditor row.AddDefaultButton(DefaultButtonType.Edit); }); }) // a list editor is similar to a list view, but every column contains an editor so multiple entities can be edited in one go // a list editor takes precedence over a list view, so when navigating to the Person collection, this view will be displayed .SetListEditor(editor => { editor.SetPageSize(20); // adding Up to the button bar allows the user to get to the level above the current page (base upon the tree) // this button will be hidden automatically when the user is at the root editor.AddDefaultButton(DefaultButtonType.Up); // in a list editor a New allows the user to add entities, within the list editor editor.AddDefaultButton(DefaultButtonType.New); editor.AddDefaultButton(DefaultButtonType.Return); // adding a SaveExisting button to the ListEditor allows the user to bulk-save the entire list // (only modified entities are touched) editor.AddDefaultButton(DefaultButtonType.SaveExisting); // allowing reordering so the user can shuffle the entities around and save them in a new order. // the Repository must implement ReorderAsync editor.AllowReordering(true); // a list editor can be in the shape of a table, or a set of blocks, so these sections are either rows or blocks editor.AddSection(row => { // these fields will be the editors row.AddField(p => p.Id).SetType(EditorType.Readonly); row.AddField(p => p.Name); // the SaveExisting button is only displayed when an entity is edited row.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); // the SaveNew button is only displayed when an entity is inserted row.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); // the View button always directs the user to the NodeView row.AddDefaultButton(DefaultButtonType.View); // the Edit button always directs the user to the NodeEdit row.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { // adding Up to the button bar allows the user to get to the level above the current page (base upon the tree) editor.AddDefaultButton(DefaultButtonType.Up); // just as in the ListEditor, SaveExisting only shows when the user is editing an existing entity, // and the SaveNew only when inserting an entity editor.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); editor.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); // if an entity can be deleted, add a button for it, so the user can delete the entity editor.AddDefaultButton(DefaultButtonType.Delete); // an node editor can have multiple sections, which are displayed as separate blocks editor.AddSection(section => { // the DisableWhen expression is evaluated every time any property of the entity is updated // so this allows you to make response forms which show or hide parts based upon the entity and its state section.AddField(x => x.Id).DisableWhen((person, state) => true); // it is allowed to use DisplayType fields in Editors, so some read only data can easily be displayed section.AddField(x => x.Name); //.SetType(DisplayType.Label); // if properties are in nested objects (like owned entities in EF), the editors support those as well // flagging such property with [ValidateObject] will have the nested object validated as well section.AddField(x => x.Details.Email) // by default, all the properties will be combined to form the Name of the property (DetailsEmail in this example) // so using SetName this can be set to something more user friendly .SetName("Email") .SetDetails(new MarkupString("<p>An email adress looks like <code>[email protected]</code>.</p>")); }); // you can even have sections specifically for an entity type. // if the repository can return multiple types of entities (all derived from a shared base type), // sections can be made specifically for a type editor.AddSection <Person>(section => { // sections can have labels to make complex forms more understandable section.SetLabel("Biography"); // sections can be hidden using VisibleWhen, based upon the entity or the state of that entity // so users won't be confronted with editors that do not apply section.VisibleWhen((person, state) => state == EntityState.IsExisting); // there are many types of editors available, and even custom types can be used section.AddField(x => x.Details.Bio).SetType(EditorType.TextArea).SetName("Bio"); }); editor.AddSection(section => { // relations with other entities, collections and repositories are first-class in RapidCMS // so this field will allow the user to select an entity that is one level deeper in the person-tree section.AddField(x => x.FavouriteChildId) .SetName("Favorite child") .SetType(EditorType.Select) .VisibleWhen((person, state) => state == EntityState.IsExisting) .SetCollectionRelation <Person>("person", config => { // this allows for configuring which property of the entity will make up the id for the element, and that value // is set to FavouriteChildId when the user selects an element config.SetElementIdProperty(x => x.Id); // because a single label is often not enough, you can add more properties to make the labels for each option config.SetElementDisplayProperties(x => x.Name, x => $"{x.Id} - {x.Details.Email}"); // sets the entity that is currently edited as parent for the repository to get elements for this field config.SetEntityAsParent(); // any level from the tree can be picked: // Favorite sibling: config.SetRepositoryParent(x => x); // Favorite parent: config.SetRepositoryParent(x => x.Parent); // Favorite grand parent: config.SetRepositoryParent(x => x.Parent != null ? x.Parent.Parent : default); // ?. is not yet supported in expressions.. }); }); // add the sub collection as element to the node editor editor.AddSection(section => { section.AddSubCollectionList("person"); }); }) .SetNodeView(view => { view.AddDefaultButton(DefaultButtonType.SaveExisting, isPrimary: true); view.AddDefaultButton(DefaultButtonType.SaveNew, isPrimary: true); view.AddDefaultButton(DefaultButtonType.Delete); view.AddSection(section => { // views also allow for customization of how the data should be displayed // you can use the available DisplayType's, or create your own Razor components (must be derived from BaseDisplay) section.AddField(x => x.Id.ToString()).SetName("ID").SetType(DisplayType.Pre); section.AddField(x => x.Name).SetType(DisplayType.Label); section.AddField(x => x.Details.Email).SetType(typeof(EmailDisplay)); }); view.AddSection(section => { section.AddField(x => x.Details.Bio); }); }); collection.AddSelfAsRecursiveCollection(); // if the regular node editor of an entity is getting too cluttered, or if you want to move some of the functionality // to a separate page, a detail page can be quite useful. // a detail page is always a NodeEditor, and is only visible on existing entity. // the repository it fetches its data from can have its own entity type, but the ID that is used to query for the data // is the same as the entity the page is part of. // it is also possible to create a navigation button to navigate to a details page, by building a INavigationHandler that // calls NavigationRequest.NavigateToDetails (see NavigateToPersonHandler) collection.AddDetailPage <Details, BaseRepository <Details> >("person-details", "Settings", "Red20", "Details", config => { config.AddDefaultButton(DefaultButtonType.SaveExisting, "Save"); config.AddSection(section => { section.AddField(x => x.Title); section.AddField(x => x.History); // when a model has a sub class which has a few simple properties, it's possible to group those properties // into a single editor using the ModelEditor type. this editor uses the same rules as the convention based // approach (see ConventionCollection), but groups the editors together of this property. // // to validate the properties of the nested object, the Nested property must be attributed with [ValidateObjectAsProperty] // to instruct the data annotation validator to validate NestedDetails and use that validation as validation result // for Nested. section.AddField(x => x.Nested) .SetType(EditorType.ModelEditor); }); }); }); }
public TreeElementsSetupResolver(ICmsConfig cmsConfig, ISetupResolver <IEnumerable <ITreeElementSetup>, IEnumerable <ITreeElementConfig> > treeElementResolver) { _cmsConfig = cmsConfig; _treeElementResolver = treeElementResolver; }
// CRUD editor with support for public static void AddEntityVariantCollection(this ICmsConfig config) { config.AddCollection <EntityVariantBase, BaseRepository <EntityVariantBase> >("variants", "ProductVariant", "OrangeYellow20", "Entity Variants", collection => { collection // Set showEntities to true to have this collection to fold open on default .SetTreeView(x => x.Name, showEntitiesOnStartup: true) // entity variants must be based of the same base type, but can introduce form elements for specialised cases .AddEntityVariant <EntityVariantA>("Variant A", "a") .AddEntityVariant <EntityVariantB>("Variant B", "b") .AddEntityVariant <EntityVariantC>("Variant C", "c") .SetListEditor(view => { view.AddDefaultButton(DefaultButtonType.New, label: "New {0}"); view.AddDefaultButton(DefaultButtonType.Return); view.SetColumnVisibility(EmptyVariantColumnVisibility.Visible); view.SetPageSize(10); view .AddSection(section => { section.AddField(p => p.Id); section.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); }) .AddSection <EntityVariantA>(section => { section.AddField(p => p.Id); section.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); section.AddField(p => p.NameA1); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Edit); }); view .AddSection <EntityVariantB>(section => { section.AddField(p => p.Id); section.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); section.AddField(p => p.NameB1); section.AddField(p => p.NameB2); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Edit); }); view .AddSection <EntityVariantC>(section => { section.AddField(p => p.Id); section.AddField(p => p.Name) .SetOrderByExpression(p => p.Name, OrderByType.Ascending); section.AddField(p => p.NameC1); section.AddField(p => p.NameC2); section.AddField(p => p.NameC3); section.AddDefaultButton(DefaultButtonType.SaveExisting); section.AddDefaultButton(DefaultButtonType.SaveNew); section.AddDefaultButton(DefaultButtonType.Edit); }); }) .SetNodeEditor(editor => { editor.AddDefaultButton(DefaultButtonType.Up); editor.AddDefaultButton(DefaultButtonType.SaveNew); editor.AddDefaultButton(DefaultButtonType.SaveExisting); editor.AddSection(generic => { generic.SetLabel("Generics"); generic.AddField(x => x.Id).SetType(DisplayType.Pre); generic.AddField(x => x.Name); }); editor.AddSection <EntityVariantA>(a => { a.SetLabel("Variant A specifics"); a.AddField(x => x.NameA1); }); editor.AddSection <EntityVariantB>(b => { b.SetLabel("Variant B specifics"); b.AddField(x => x.NameB1); b.AddField(x => x.NameB2); }); editor.AddSection <EntityVariantC>(c => { c.SetLabel("Variant C specifics"); c.AddField(x => x.NameC1); c.AddField(x => x.NameC2); c.AddField(x => x.NameC3); }); }); }); }
/// <summary> /// This method adds the Model Maker plugin to RapidCMS without the Model collection. /// /// Use this when the code runs in production. /// </summary> /// <param name="cmsConfig"></param> /// <returns></returns> public static ICmsConfig AddModelMakerPluginCore(this ICmsConfig cmsConfig) { cmsConfig.AddPlugin <ModelMakerPlugin>(); return(cmsConfig); }