BForms Toolbar component in conjuction with Grid component are designed to provide rich functionality, cross-browser, cross-device and internationalization support for tabular data providing full CRUD and search operations over complex datasets.

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

Toolbar helper

In order to render a toolbar you will need to use the Html.ToolbarFor() helper extension. The basic setup requirements for this to render something looks like this:

@Html.BsToolbarFor(x => x.Toolbar)


As you would expect the above code does nothing but render an empty toolbar. It doesn't really make sense to use it this way, so in order to make it truly functional we have to set some more properties. This can be achieved in 2 ways: by decorating your model property with attributes (BsToolbarAttribute and DisplayAttribute) - limited, not all properties can be set this way - or by setting them in your .cshtml file - all properties can be set and those that are set here overrides those set in the declarative way.

Display name

The name that describes the entity that the toolbar will try to manipulate. It can be set both ways.

Declarative - Decorate your property model with DisplayAttribute that comes with MVC

[Display(Name = "Contributors", ResourceType = typeof(Resource))]


Razor

@(Html.BsToolbarFor(x => x.Toolbar).DisplayName("Dev Team"))


Toolbar Theme

You can easily change the look of your toolbar by setting the theme. It actually just changes the colour of your component, but it is a simple and elegant feature that helps you to change basic styling with no effort at all. There are three ways in which you can change the theme.

Global - You can set the theme globally in the Global.asax file, inside the Application_Start() method.

protected void Application_Start()
{
    ....

    BForms.Utilities.BsUIManager.Theme(BsTheme.Orange);
}


Declarative - Decorate your property model with BsToolbarAttribute and set the Theme property to the theme of your desire. It will override the global theme.

[BsToolbar(Theme=BsTheme.Black)]

Razor - Use SetTheme() to set the theme inside the view. It will override the theme attribute on the model.

@(Html.BsToolbarFor(x => x.Toolbar)
    .DisplayName("Dev Team")
    .SetTheme(BsTheme.Purple)

Razor (Get Global) - Use Html.GetTheme() method to get the theme set in the Global.asax if any. If none is set inside Global.asax it will return the default one (Turqoise).

@(Html.BsToolbarFor(x => x.Toolbar)
    .DisplayName("Dev Team")
    .SetTheme(Html.GetTheme())

As you can see above, there are multiple places where you can set the theme. The order of precedence is (from the least important to the most important) Global.asax > Model > View.

The default theme is Turqoise and you can choose from Black, Blue, Orange, Green and Purple. See it live here by clicking the arrow in the right and choosing another theme.


Controls

Now you must say OK it can be blue, it can be black, it can have a name but it still has no buttons to interact with and you are right. It's time to add some controls. Be careful! By adding controls on the servers side doesn't mean that the user can instantly interact with them. For that you must do a little client side coding; more of that here.

Out of the box BsToolbar comes with 4 functionalities: quick search, advanced search, add and order. Sadly this part is for Razor only.

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        ca.Add(BsToolbarActionType.Add);

        ca.Add<BsToolbarQuickSearch>();
        
        ca.Add(BsToolbarActionType.AdvancedSearch);

        ca.Add(BsToolbarActionType.Order)
    })

There are 3 types of controls: actions, tabs and custom. Above we have three examples of tabs (Add, AdvancedSearch and Order - well they are not entirely tabs yet, will discuss it later) and a custom one(QuickSearch)

If you want to render actions or tabs you have 2 choices: pick a default one from BsToolbarActionType

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        ca.Add(BsToolbarActionType.Add);
    })

which will automatically render the action or tab button, or add a new one

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        ca.Add("btn-new");
    })

and customize it however you like. The level of customization between default and new ones is the same, the only difference being that the default ones can be used out of the box.


You can also use a button of type ActionLink that will redirect the user to the specified controller action.

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        ca.Add("").Action(Url.Action("YourAction", "YourController"));
    })

If you want a submenu you can use AddButtonGroup() to create a button group. After you create it, you can use this group to add default or custom buttons to it.

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        var groupButton = ca.AddButtonGroup();

        groupButton.Add(BsToolbarActionType.Order);        
        groupButton.AddActionLink().Action(Url.Action("YourAction", "YourController"));
    })

There are a series of helper extensions that you can apply to your buttons to change their properties. You can use Text() to change the text of your button, DisplayName() to change the display text of your group, GlyphIcon() to add an icon to your button or group, Tab() to set the Tab that will be opened on button click, or Placeholder() to change the placeholder text of your textbox (applies to QuickSearch).

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        var groupButton = ca.AddButtonGroup().DisplayName("Options").GlyphIcon(Glyphicon.Cog);

        groupButton.AddActionLink()
            .Text("User profile")
            .GlyphIcon(Glyphicon.User)
            .Action(Url.Action("Index", "UserProfile"));

        ca.Add<BsToolbarQuickSearch>()
          .Placeholder("Search");
        
        ca.Add(BsToolbarActionType.AdvancedSearch)
          .Tab(x => Html.BsPartialPrefixed(y => y.Search, "Toolbar/_Search", x));
    })

Custom controls (C#)

As stated above you can add custom controls to your toolbar and BsForms comes with a built-in one - BsToolbarQuickSearch. To use it simply add

@(Html.BsToolbarFor(x => x.Toolbar)
    .ConfigureActions(ca =>
    {
        ca.Add<BsToolbarQuickSearch>();
    })

You can add as many custom controls as you like, but as the name says, it's custom so you have to build it yourself. Luckily this is a fairly easy task to do. Let's take a look at the quick search example:

// Step 1: Inherit from BaseComponent
/// <summary>
/// Grid toolbar inline search component
/// </summary>
public class BsToolbarQuickSearch : BsBaseComponent
{
    internal string placeholder = "search";

    public BsToolbarQuickSearch() 
    {
        this.renderer = new BsToolbarQuickSearchRenderer(this);
    }

    // Step 2: pass viewContext to BaseComponent - 
    // used for writing the output html
    public BsToolbarQuickSearch(ViewContext viewContext)
        : base(viewContext) 
    {
        this.renderer = new BsToolbarQuickSearchRenderer(this);
    }

    // Step 3: Add customization. In this case we can 
    // set the quick search input placeholder
    /// <summary>
    /// Set input placeholder, default is "search"
    /// </summary>
    public BsToolbarQuickSearch Placeholder(string placeholder)
    {
        this.placeholder = placeholder;
        //return this for fluent api
        return this;
    }
}

Now that you have your page set up (html wise) it's time to add some interactions.

Javascript

The first step is to require toolbar.js

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


Now that the script is loaded we can apply the toolbar widget on our element

$('#toolbar').bsToolbar({
    uniqueName: 'usersToolbar',
    subscribers: [$('#grid')]
});

Options

uniqueName

type string
default DOM element id

Used to uniquely identify the toolbar. It is not required as long as the toolbar has the id attribute set.

subscribers

type Array
default empty

The toolbar by itself has no meaning. That's why for it to be functional you must provide a list of subscribers. These will be notified when an action is being made.

autoInitControls

type boolean
default true

When toolbar.js is loaded, the scripts for the default toolbar controls (quick search, advanced search and add) are also loaded. If autoInitControls = true they are automatically initialized if you added them when you configured the toolbar controls on the server side. Also if you call your own plugins you can set them to automatically initialize too. Read more about this here.

reset

type function
default resets advanced and quick search

Supply a handler if there is anything you would like to reset in your toolbar

controls

type Array
default empty

Add your custom controls that you don't want to automatically be initialized.


Custom controls (js)

Earlier I've showed you how to implement a custom control on the server side. Again we must provide some interaction for the user, so we have to write a plugin similar to the default ones.

Below you will find the implementation of the quick search plugin.

define('bforms-toolbar-quickSearch', [
    'jquery'
], function () {

    // plugin constructor
    var QuickSearch = function ($toolbar, options) {

        // set an unique name
        this.name = 'quickSearch';

        // set the type of the plugin: tab or custom
        this.type = 'custom';

        // set $toolbar container
        // required if your plugin has to communicate with toolbar
        // subscribers or other toolbar controls
        this.$toolbar = $toolbar;

        // merge options
        this.options = $.extend(true, {}, this._defaultOptions, options);

    };

    // plugin default options
    // will be extended by user options
    QuickSearch.prototype._defaultOptions = {
        // control selector, all plugins must have this as an option
        selector: '.bs-quick_search',
        // wheather the search is triggered while you type or on enter
        instant: true,
        // search timeout interval if it is set to instant
        timeout: 250
    };

    // plugin init
    // it will automatically be called
    QuickSearch.prototype.init = function () {

        // keep toolbar widget refrence as we need it later when 
        this.widget = this.$toolbar.data('bformsBsToolbar');

        // add handlers
        this.$toolbar.on('keyup',
                    this.options.selector + ' .bs-text',
                    $.proxy(this._evOnQuickSearchKeyup, this));

    };

    // event handler
    QuickSearch.prototype._evOnQuickSearchKeyup = function (e) {

        var $me = $(e.currentTarget);
        var val = $me.val().trim();

        if (val.length == 0 && $me.data('empty')) {
            return;
        }

        var advancedSearch = this.widget.getControl('advancedSearch');
        if (advancedSearch != null && advancedSearch.$element.hasClass('selected')) {
            advancedSearch.$element.trigger('click');
        }

        if (val.length == 0) {
            $me.data('empty', true);
        } else {
            $me.data('empty', false);
        }

        if (this.options.instant) {
            window.clearTimeout(this.quickSearchTimeout);
            this.quickSearchTimeout = window.setTimeout($.proxy(function () {
                this._search(val);
            }, this), this.options.timeout);
        } else if (e.which == 13 || e.keyCode == 13) {
            this._search(val);
        }

    };

    // search trigger
    QuickSearch.prototype._search = function (quickSearch) {

        // notify grid subscribers that a search was made
        for (var i = 0; i < this.widget.subscribers.length; i++) {
            this.widget.subscribers[i].bsGrid('search', quickSearch, true);
        }

    };

    // export module
    return QuickSearch;

});

Adding controls manually

In order to stop your controls to be automatically added to your toolbar you have to set autoInitControls to false. Then you can add your controls on toolbar initialization or after.

// on init
this.$toolbar.bsToolbar({
    uniqueName: 'usersToolbar',
    subscribers: [this.$grid],
    autoInitControls: false,
    //initialize default controls manually
    controls: [
        $.bforms.toolbar.defaults.advancedsearch,
        $.bforms.toolbar.controls.yourCustomControl
    ]
});

// after init
this.$toolbar.bsToolbar('controls', [$.bforms.toolbar.controls.yourCustomControl]);

Customizing controls

Advanced search and add controls manipulate the toolbar subscribers by submiting a form. By using their corresponding plugins you can customize the form functionality.

Add control

By default the add form has two buttons: save and reset. The save button saves the new entity and adds it to the grid as the first element. The reset button simply resets all form fields.

var addOptions = {
    // button name. When you want to customize a form button
    // functionality the name is the key based on which the 
    // options will be merged
    name: 'save',
    // button selector that the handler will attach to
    selector: '.js-btn-save',
    // validate form. In this case we validate the form because
    // adding an entity might have some conditions to meet
    validate: true,
    // parse form and send parsed data to handler
    parse: true,
    // button handler
    handler: $.proxy(this._evOnAdd, this)
};

var resetOptions = {
    // button name. When you want to customize a form button
    // functionality the name is the key based on which the 
    // options will be merged
    name: 'reset',
    // button selector that the handler will attach to
    selector: '.js-btn-reset',
    // validate form. In this case we don't want to validate
    // the form because all user input will be reset
    validate: false,
    // parse form and send parsed data to handler. We don't parse
    // the form because we've just reseted it
    parse: false,
    // button handler
    handler: $.proxy(this._evOnReset, this)
};

Advanced search control

By default the advanced search form has two buttons: search and reset. The search button triggers filter grid by sending the parsed form data. The reset button does the same but before parsing the form we reset it. Even though we reset the form we still need to parse it in order to filter the grid in case the form has some default fields values.

var searchOptions = {
	// button name. When you want to customize a form button
	// functionality the name is the key based on which the 
	// options will be merged
	name: 'search',
	// button selector that the handler will attach to
	selector: '.js-btn-search',
	// validate form. We don't validate data on search
	validate: false,
	// parse form and send parsed data to handler
	parse: true,
    // button handler
	handler: $.proxy(this._evOnSearch, this)
};

var resetOptions = {
	// button name. When you want to customize a form button
	// functionality the name is the key based on which the 
	// options will be merged
	name: 'reset',
	// button selector that the handler will attach to
	selector: '.js-btn-reset',
	// validate form. In this case we don't want to validate
	// the form because all user input will be reset
	validate: false,
	// parse form and send parsed data to handler. We parse the data
    // in case some fields have default values
	parse: true,
	// button handler
	handler: $.proxy(this._evOnReset, this)
};

You can customize the buttons behaviour of the plugins described above by following the next steps:

// Step 1: get advanced search plugin from toolbar defaults namespace
var advancedsearch = new $.bforms.toolbar.defaults.advancedsearch(this.$toolbar);

// Step 2: update button settings
advancedsearch.setcontrol('search', {
    handler: $.proxy(function () {
        console.log('custom');
        var widget = $('#toolbar').data('bformsBsToolbar');
        for (var i = 0; i < widget.subscribers.length; i++) {
            widget.subscribers[i].bsGrid('search', data);
        }
    }, this)
});

// Step 3: add control to toolbar
this.$toolbar.bsToolbar('controls', [advancedSearch]);