BForms GroupEditor component is designed to provide rich functionality, cross-browser, cross-device and internationalization support for your group editting needs.

The feature set includes drag & drop functionality to add items to groups, AJAX-enabled editing of the group items, creation and filtering of group items. The UI is touch friendly and HTML5 enhanced featuring edit in place.

On the client side, the GroupEditor is deployed as an AMD jQuery widget that supports theming and templates. The styling is done using bootstrap v3 CSS grid system.


Models

The first step in the GroupEditor creation is the model definition. You can divide your entities in multiple categories represented in the UI as tabs and groups.

It is not required to split your entities in multiple tabs but this gives you more flexibility. You can set associations between specific tabs and groups. This way you constrain the items in one tab to only be dragable to a specific group.

Firstly we need a view model for the group editor. The view model consists of one or more GroupEditor models. You can have multiple GroupEditors on the same page.

Group Editor View Model

public class GroupEditorViewModel
{
    public GroupEditorModel Editor { get; set; }
}

The GroupEditorModel is a collection of tabs and groups. The tabs will contain the dragable items while the groups will represent the dropable targets.

You can have as many tabs and groups as you like.

Group Editor Model

public class GroupEditorModel
{
    [BsEditorTab(Name = "Pages", Id = MenuItemTypes.Page, Selected = true)]
    public BsEditorTabModel<SampleGroupRowModel, MenuItemSearchModel, PageNewModel> Tab1 { get; set; }

    [BsEditorTab(Name = "Custom Links", Id = MenuItemTypes.CustomLink, Selected = false)]
    public BsEditorTabModel<SampleGroupRowModel, MenuItemSearchModel, CustomLinkNewModel> Tab2 { get; set; }

    [BsEditorTab(Name = "Categories", Id = MenuItemTypes.Category, Selected = false)]
    public BsEditorTabModel<SampleGroupRowModel, MenuItemSearchModel, CategoryNewModel> Tab3 { get; set; }

    [BsEditorGroup(Id = MenuTypes.PublicMenu)]
    public BsEditorGroupModel<SampleGroupRowModel> Group1 { get; set; }

    [BsEditorGroup(Id = MenuTypes.UsersMenu)]
    public BsEditorGroupModel<SampleGroupRowModel> Group2 { get; set; }

    [BsEditorGroup(Id = MenuTypes.AdminMenu)]
    public BsEditorGroupModel<SampleGroupRowModel> Group3 { get; set; }
}

Example

As you may have noticed in the above code example that there are some custom attributes declared in the GroupEditorModel model. The BsEditorTab attribute allows you to set specific properties for the tabs, and BsEditorGroup attribute allows you to set specific properties for the groups.


BsEditorTab Attribute

Options

Name

type string
default none

Set the tab display name.

Id

type object
default none

Set the tab unique id.

Selected

type bool
default false

Specify if the tab is selected on the first page load.

Editable

type bool
default false

Set the tab as editable.


BsEditorGroup Attribute

Options

Id

type object
default none

Set the group unique id.


For each tab you can have QuickSearch field, AdvancedSearch form and NewItem form. Of course you can also add custom forms.

Let's see how the Search and New models look.

Tab Search Model

public class MenuItemSearchModel
{
    public MenuItemSearchModel()
    {
        Visibility = new BsSelectList<MenuItemVisibility?>();
        Visibility.ItemsFromEnum(typeof(MenuItemVisibility));
    }

    [Display(Name = "DisplayName", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string DisplayName { get; set; }

    [Display(Name = "Link", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string Link { get; set; }

    [BsControl(BsControlType.RadioButtonList)]
    [Display(Name = "Visibility", ResourceType = typeof(Resource))]
    public BsSelectList<MenuItemVisibility?> Visibility { get; set; }
}

Example

New Tab Item Model

public class MenuItemNewModel
{
    public MenuItemNewModel()
    {
        Visibility = new BsSelectList<MenuItemVisibility>();
        Visibility.ItemsFromEnum(typeof(MenuItemVisibility));
        Visibility.SelectedValues = MenuItemVisibility.Any;

        Icon = new BsSelectList<Glyphicon?>();
        Icon.ItemsFromEnum(typeof(Glyphicon));
    }

    [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(Resource))]
    [BsControl(BsControlType.RadioButtonList)]
    [Display(Name = "Visibility", ResourceType = typeof(Resource))]
    public BsSelectList<MenuItemVisibility> Visibility { get; set; }

    [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(Resource))]
    [Display(Name = "DisplayNameLocal", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string DisplayNameLocal { get; set; }

    [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(Resource))]
    [Display(Name = "DisplayNameInternational", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string DisplayNameInternational { get; set; }

    [Required(ErrorMessageResourceName = "RequiredField", ErrorMessageResourceType = typeof(Resource))]
    [Display(Name = "Link", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string Link { get; set; }

    [Display(Name = "Icon", Prompt = "PromptIcon", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.DropDownList)]
    public BsSelectList<Glyphicon?> Icon { get; set; }

    public virtual MenuItemTypes MenuItemType { get; set; }
}

Example


You also need a GroupRowModel to create the tabs and groups models. The GroupRowModel must inherit from BsEditorGroupItemModel

Here is an example implementation:

Group Row Model

public class SampleGroupRowModel : BsEditorGroupItemModel<SampleRowFormModel>
{
    public int Id { get; set; }
    public string DisplayNameLocal { get; set; }
    public string DisplayNameInternational { get; set; }
    public string Permissions { get; set; }
    public string Link { get; set; }
    public Glyphicon? Icon { get; set; }

    public override object GetUniqueID()
    {
        return this.Id;
    }
}

Example


Once you have these models you can create the BsEditorTabModel and the BsEditorGroupModel for the GroupEditorModel.

The BsEditorTabModel has three overloaded contructors. One takes only the GroupRowModel. Use it if your tab has no Search or New forms.

The second constructor takes also the Search model. This means that the tab will implement AdvancedSearch functionality.

The last one takes all three models BsEditorTabModel<TRow, TSearch, TNew>. This tab will also implement the New item form.

Even if the models exists you can decide later in the view if you want to render these buttons and forms in the tab header.

The group items can also be edited. For this you need to create also a RowFormModel. This model should include all the fields you want to edit.

Group Form Model

public class SampleRowFormModel
{
    [Display(Name = "DisplayNameLocal", ResourceType = typeof(Resource))]
    [Required]
    [BsControl(BsControlType.TextBox)]
    public string DisplayNameLocal { get; set; }

	[Display(Name = "DisplayNameInternational", ResourceType = typeof(Resource))]
    [Required]
    [BsControl(BsControlType.TextBox)]
    public string DisplayNameInternational { get; set; }

    [Display(Name = "Link", Prompt = "PromptLink", ResourceType = typeof(Resource))]
    [BsControl(BsControlType.TextBox)]
    public string Link { get; set; }
}

Example


GroupEditor helper

In order to render a group editor, use the Html.BsGroupEditorFor() html helper extension.

@Html.BsGroupEditorFor(x => x.Editor);

This helper returns a BsEditorHtmlBuilder used to build and configure the html of the GroupEditor groups and tabs.


BsEditorHtmlBuilder

Methods

SaveUrl(string saveUrl)

params:

  • saveUrl

    type string
    default none
    description Save Url Action path.

Sets the Save Action Path for the GroupEditor.

Example

builder.SaveUrl(Url.Action("Save"));

IgnoreAjaxRequest(bool ignoreAjaxRequest)

params:

  • ignoreAjaxRequest

    type bool
    default none
    description Sets the ignoreAjaxRequest property for the GroupEditor.

Sets the ignoreAjaxRequest property for the GroupEditor.

Example

builder.IgnoreAjaxRequest(false);

ConfigureTabs(Action<BsEditorTabConfigurator<TModel>> configurator)

params:

  • configurator

    type Action<BsEditorTabConfigurator<TModel>>
    default none
    description tab configurator (template, connectsWith, bulkMove, quickSearch)

Using this method, you can customize the editor Tabs as you like by using the provided methods.

Example

builder.ConfigureTabs(cfg =>
{
    var tab1 = cfg.For(x => x.Tab1)
        //.Editable()
        .Template(x => x.Grid, "_TabItem")
        .Template(x => x.Search, "~/Views/Home/_Search.cshtml")
        .Template(x => x.New, "~/Views/Home/_New.cshtml")
        .ConnectsWith(MenuTypes.PublicMenu, MenuTypes.UsersMenu, MenuTypes.AdminMenu);

    var tab2 = cfg.For(x => x.Tab2)
        .Template(x => x.Grid, "_TabItem")
        //.Template(x => x.Search, "~/Views/Home/_Search.cshtml")
        //.Template(x => x.New, "~/Views/Home/_New.cshtml")
        .ConnectsWith(MenuTypes.PublicMenu);

    var tab3 = cfg.For(x => x.Tab3)
        .Template(x => x.Grid, "_TabItem")
        //.Template(x => x.Search, "~/Views/Home/_Search.cshtml")
        //.Template(x => x.New, "~/Views/Home/_New.cshtml")
        .ConnectsWith(MenuTypes.UsersMenu);


    tab1.BulkMove = false;
    tab1.Toolbar.QuickSearch = true;
    tab2.Toolbar.QuickSearch = true;
    tab3.Toolbar.QuickSearch = true;

    cfg.Title = "Menu Items";

})

Tab Configurator

BsEditorTabConfigurator

Properties

Title

type string
default none
description display title string

Specify the tabs group title displayed above the tabs.

Example

builder.ConfigureTabs(cfg =>
{
	cfg.Title = "Menu Items";
})

Methods

For<TEditor>

params:

  • BsEditorTabModel

    type BsEditorTabModel
    default none
    description Set tab attributes like Editable, Selected, PagerSettings, displayName for the new panel.

Sets the tab properties if any is provided. Returns a BsEditorTabBuilder.

Example

builder.ConfigureTabs(cfg =>
{
    var tab1 = cfg.For(x => x.Tab1);
})

BsEditorTabBuilder

Methods

Template<TValue>(Expression<Func<TModel, TValue>> expression, string template)

params:

  • expression

    type Expression
    default none
    description Used to select the Grid, the New Form or the AdvancedSearch Form.

  • template

    type string
    default none
    description Used to set the partial view for the selected part.

Sets the template property for the GridItem, New Form and AdvancedSearch Form by specifying the partial view for each.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1)
            .Template(x => x.Grid, "_TabItem")
            .Template(x => x.Search, "~/Views/Home/_Search.cshtml")
            .Template(x => x.New, "~/Views/Home/_New.cshtml");
})

Editable()

Specifies if the tab is editable or not.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1).Editable();
})

Selected(bool selected)

params:

  • selected

    type bool
    default none
    description Sets the selected property for the tab.

Specifies if the tab is selected or not on the widget initialization.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1).Selected(true);

	var tab2 = cfg.For(x => x.Tab2).Selected(false);
})

PagerSettings(BsPagerSettings pagerSettings)

params:

  • pagerSettings

    type BsPagerSettings
    default none
    description Sets the properties for the grid pager.

Sets the pager settings for the grid, such as DefaultPageSize, ShowPrevNextButtons, ShowFirstLastButtons, Template.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab3).PagerSettings(new BsPagerSettings {DefaultPageSize = 5, ShowFirstLastButtons = true, ShowPrevNextButtons = false})
})

DisplayName(string name)

params:

  • name

    type string
    default none
    description Sets the display name for the tab.

Sets the display name for the tab.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1).DisplayName("Tab One");
})

Id(object uid)

params:

  • uid

    type object
    default none
    description Sets the unique id for the tab.

Sets the unique id for the tab.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1).Id(TabTypesEnum.Pages);
})

ConnectsWith(params object[] ids)

params:

  • uid

    type object[]
    default none
    description Sets the groups that the tab is allowed to connect with.

Sets the collection of group ids that the tab is allowed to connect with. If a group is not in this collection then the respective group will not be a drop target for the tab group items.

Example

builder.ConfigureTabs(cfg =>
{
	var tab1 = cfg.For(x => x.Tab1)
            .ConnectsWith(MenuTypes.PublicMenu, MenuTypes.UsersMenu, MenuTypes.AdminMenu);
})

Group Configurator

ConfigureGroups(Action<BsEditorGroupConfigurator<TModel>> configurator)

params:

  • configurator

    type Action<BsEditorGroupConfigurator<TModel>>
    default none
    description group configurator (template, connectsWith, bulkMove, quickSearch)

Using this method, you can customize the editor Groups as you like by using the provided methods.

Example

builder.ConfigureGroups(cfg =>
{
    cfg.For(x => x.Group1)
        .DisplayText("Adauga la Public Menu")
        .DisplayName("Public Menu (FrontEnd)")
        .Template(x => x.Items, "_GroupItem");

    cfg.For(x => x.Group2)
        .DisplayText("Adauga la Users Menu")
        .DisplayName("Users Menu (Authentication Required)")
        .Template(x => x.Items, "_GroupItem");

    cfg.For(x => x.Group3)
        .DisplayText("Adauga la Admin Menu")
        .DisplayName("Admin Menu (BackEnd)")
        .Template(x => x.Items, "_GroupItem");

    cfg.Title = "Menus";
})

BsEditorGroupConfigurator

Properties

Title

type string
default none
description display title string

Specify the groups title displayed above the groups section.

Example

builder.ConfigureGroups(cfg =>
{
	cfg.Title = "My Groups";
})

Methods

For<TEditor>

params:

  • BsEditorGroupModel

    type BsEditorGroupModel
    default none
    description Set group attributes like Id, DisplayText, DisplayName for the group.

Sets the group properties if any is provided. Returns a BsEditorGroupBuilder.

Example

builder.ConfigureGroups(cfg =>
{
    var tab1 = cfg.For(x => x.Group1);
})

FormTemplate(MvcHtmlString template)

params:

  • template

    type MvcHtmlString
    default none
    description Set group editable form partial view.

Sets the editable form partial view for the group collection. This form is not required.

Example

builder.ConfigureGroups(cfg =>
{
 	cfg.FormTemplate(Html.Partial("_GroupEditorForm", Model.Editor2.Form));
})

BsEditorGroupBuilder

Methods

Template<TValue>(Expression<Func<TModel, TValue>> expression, string template)

params:

  • expression

    type Expression
    default none
    description Used to select the Group Items.

  • template

    type string
    default none
    description Used to set the partial view for the selected part.

Sets the template property for the GroupItem or GroupForm by specifying the partial view for each.

Example

builder.ConfigureGroups(cfg =>
{
	var group1 = cfg.For(x => x.Group1)
                .Template(x => x.Items, "_GroupItem")
                .Template(x => x.Form, "_RowForm");
})

DisplayText(string text)

params:

  • text

    type string
    default none
    description Sets the display text for the group.

Sets the display text for the group dropable area.

Example

builder.ConfigureGroups(cfg =>
{
	var group1 = cfg.For(x => x.Group1).DisplayText("Add to Group One");
})

DisplayName(string name)

params:

  • name

    type string
    default none
    description Sets the display name for the group.

Sets the display name for the group.

Example

builder.ConfigureGroups(cfg =>
{
	var group1 = cfg.For(x => x.Group1).DisplayName("Group One");
})

Id(object uid)

params:

  • uid

    type object
    default none
    description Sets the unique id for the group.

Sets the unique id for the group.

Example

builder.ConfigureGroups(cfg =>
{
	var group1 = cfg.For(x => x.Group1).Id(GroupTypesEnum.MainMenu);
})


Repository

There are no constraints on how you should build your repository specific to the GroupEditor.

However, because the GroupEditor uses the Grid and the Toolbar controls you have to make changes to your repository to accommodate these controls.

Please read and understand the requirements described in the Grid documentation under the Repository section.



Javascript

First step is to require bforms.groupEditor.js aka bforms-groupEditor (defined in RequireJS.json).

require([
        'bforms-groupEditor'    
], function () { }


Now that the script is loaded we can apply the groupEditor widget on the element.

$('#myGroupEditor').bsGroupEditor({
    getTabUrl: this.options.getTabUrl,
    buildDragHelper: function (model, tabId, connectsWith) {
        return $('<div class="col-lg-6 col-md-6 bs-itemContent" style="z-index:999"><span>' + model.DisplayNameInternational + '</span></div>');
    },
    buildGroupItem: $.proxy(function (model, group, tabId, objId) {
        return $('<span>' + model.DisplayNameInternational + '</span>');
    }, this),
    validateMove: function (model, tabId, $group) {
        if (model.Permissions == "PublicArea" && ($group.data('groupid') == 2 || $group.data('groupid') == 3)) return false;
    },
    onSaveSuccess: $.proxy(function () {
    }, this),
    initEditorForm: $.proxy(function ($form, uid, tabModel) {
        if (uid == "1.Search") {
           this._initSearchForm($form, uid);
        } else if (uid == "1.New") {
           this._initAddForm($form, uid);
        }
    }, this),
    validation: {
        required: {
            unobtrusive: true,
            message: "Please add at least one item."
        }
    }
});

On creation you can set different properties and options by providing a JSON object in the constructor as a parameter. For further configuration see the list of options below:


Options

getTabUrl

type string
default none

Used to set the url to the getTabUrl Action. Usualy you get this url from the options object send from the controller via requireJs this.options.getTabUrl.

buildDragHelper

type Function
default none

A function that takes as parameters the model, the tabId and the connectsWith array and returns a jquery Html fragment used to construct the dragged element.

buildGroupItem

type Function
default none

A function that takes as parameters the model, the $group, the tabId and the objId and returns a jquery Html fragment used to construct the newly added group item.

validateMove

type Function
default none

A function that takes as parameters the model, the tabId and the $group jquery object and returns false if a custom defined condition fails. If the function returns false the dragged item won't be added to the group. You don't want to return true from this function. If you return true any other conditions won't be tested, for example if the item already exists in the group.

onSaveSuccess

type Function
default none

A function to be called if the Save ajax call is successful.

validation

type Object
default none

Object to set validation options. For example: required: {unobtrusive: true, message: "Please add at least one item."}

initEditorForm

type Function
default none

A function called to initialize the groupEditor forms such as the Search form and the New Form on the tabs.