Building a Dynamic Application Menu with Durandal.js, Knockout, and Bootstrap (Pt. 1)

Categories: JavaScript

Tags: Bootstrap, Durandal, Knockoutjs, KoLite

I’m going to do a longer series here about how to create a dynamic menu bar system with Durandal, Knockout, and Twitter Bootstrap.  This menu bar is going to emulate the traditional desktop application menu bar you find in apps (like the File, Edit, or View menus, for example).  The special thing here is that it will be 100% dynamic.  This will allow interesting scenarios such as dynamically altering the application menu when the application is in a different mode or allow something like plug-ins to alter the menu structure adding new commands.

We will use the following libraries/frameworks to perform this task:

  • We’ll use Bootstrap to style the menus and get basic drop down menu support.  The menus in Bootstrap look very pretty and work very well using just one .js file for behavior and a little bit of markup.
  • We’ll use Durandal to structure the application and to take advantage of the composition engine it has.  I assume you know how to get a basic Durandal project up and running so I’m not going to spend a lot of time discussing how Durandal works.
  • We’ll use Knockout to do all of the data binding.  Our menu items will have observable properties in them so that menus will dynamically change when you change things about them in code.
  • We’ll also make use of KoLite by John Papa which provides a simple KO extension (the command) to abstract the idea of a UI command.  A single menu item will wrap a ko.command().  If a command does not allow execution, the corresponding menu item(s) will not allow it and will appear disabled.  Also, when clicking on a menu item, the command will execute.  This will all happen through data binding and will not

For this first part, let’s build the JavaScript object model for the menu system.  A Menu object (defined in ui/menu.js in my project) represents one menu on a menu bar (such as a File menu).  It will contain zero or more MenuItems which are the individual menu selections in the menu.  There is also a special menu item that acts as a divider or separator.  These dividers draw thin lines between menu items to help visually group commands.  They are not clickable.

Here’s the code for the MenuItem class (defined in ui/menuItem.js as a Durandal module):

define(function (require) {
    var MenuItem = function (itemText, command, items) {
        this.text = ko.observable(itemText);
        this.command = command || ko.command({ execute: function () { } });
        this.items = ko.observableArray(items || []);
        this.hasSubMenu = ko.computed(function () {
            return this.items().length > 0;
        }, this);
    };

    MenuItem.prototype.addMenuItem = function (menuItem, position) {
        if (position) {
            this.items.splice(position, 0, menuItem);
        }
        else {
            this.items.push(menuItem);
        }
    };

    MenuItem.prototype.addDivider = function (position) {
        var item = { divider: true };
        if (position) {
            this.items.splice(position, 0, item);
        }
        else {
            this.items.push(item);
        }
    };

    return MenuItem;
});

 

A menu item takes a menu item text (the text to appear in the menu), an optional KoLite command, and an optional set of child sub-items.  The sub-items are used for when the menu item is actually a menu within a menu and will be rendered with a an arrow to the right of the menu item using Bootstrap.

A Menu class also exists as a top-level container for MenuItems.  Think of this as the File menu or Edit menu.  It is defined in ui/menu.js as a Durandal module:

define(function (require) {
    var MenuItem = require('ui/menuItem');

    var Menu = function (text, items) {
        this.text = ko.observable(text);
        this.items = ko.observableArray(items || []);
    };

    Menu.prototype.addMenuItem = function (menuItem, position) {
        if (position) {
            this.items.splice(position, 0, menuItem);
        }
        else {
            this.items.push(menuItem);
        }

        return menuItem;
    };

    return Menu;
});

 

This class takes the text of the menu item (“File”) and the collection of MenuItems to add to the menu.  You can call addMenuItem() to add a menu item after the initial creation of the menu.  The position parameter will add the menu in the specified position.  If you don’t specify a position, it will be added to the end of the menu.

In the next part of the series, we’ll look at the HTML and KO data-bindings that will render the menus.

No Comments