/ JavaScript

Model-View-Controller (MVC) in JavaScript

JavaScript is one of the most powerful languages. It supports wide range of the programming styles and techniques, but such flexibility comes with danger. It is relatively easy for the JavaScript project to become a clutter mess when the best practices are not followed or design patterns are applied incorrectly.

My goal for this article is to demonstrate how to apply the Model-View-Controller pattern while developing a simple JavaScript component. The component is the ListBox ("select" HTML tag) control with an editable list of items: the user should be able to select and remove items and add new items into the list.

I hope that this article may be a good reading for you by itself. But it would be much better if you consider to run the examples and play with them.

The Model-View-Controller pattern requires some description here. The name of the pattern consists of the names of its actors: Model - stores an application data; View - renders Model for a client; and Controller - updates Model by reacting on client's actions. Wikipedia defines parts of the Model-View-Controller architecture as follows:

  • Model - The domain-specific representation of the information on which the application operates. The model is another name for the domain layer. Domain
    logic adds meaning to raw data (e.g., calculating if today is the user's birthday, or the totals, taxes and shipping charges for shopping cart items).
  • View - Renders the model into a form suitable for interaction, typically a user interface element. MVC is often seen in web applications, where the view is the HTML page and the code which gathers dynamic data for the page.
  • Controller - Processes and responds to events, typically user actions, and invokes changes on the model and perhaps the view.

So let's design the main classes of the component in a way which reflect the parts of this design pattern.

The data of the component is just a list of items, in which one particular item can be selected and deleted. So, the model of the component is very simple - it consists of an array and a selected item index; and here it is:

/**
 * The Model. Model stores items and notifies
 * observers about changes.
 */
function ListModel(items) {
    this._items = items;
    this._selectedIndex = -1;

    this.itemAdded = new Event(this);
    this.itemRemoved = new Event(this);
    this.selectedIndexChanged = new Event(this);
}

ListModel.prototype = {
    getItems : function () {
        return [].concat(this._items);
    },

    addItem : function (item) {
        this._items.push(item);
        this.itemAdded.notify({ item : item });
    },

    removeItemAt : function (index) {
        var item;

        item = this._items[index];
        this._items.splice(index, 1);
        this.itemRemoved.notify({ item : item });
        if (index === this._selectedIndex) {
            this.setSelectedIndex(-1);
        }
    },

    getSelectedIndex : function () {
        return this._selectedIndex;
    },

    setSelectedIndex : function (index) {
        var previousIndex;

        previousIndex = this._selectedIndex;
        this._selectedIndex = index;
        this.selectedIndexChanged.notify({ previous : previousIndex });
    }
};

Event is a simple class for implementing the Observer pattern:

function Event(sender) {
    this._sender = sender;
    this._listeners = [];
}

Event.prototype = {
    attach : function (listener) {
        this._listeners.push(listener);
    },
    notify : function (args) {
        var index;

        for (index = 0; index < this._listeners.length; index += 1) {
            this._listeners[index](this._sender, args);
        }
    }
};

Before designing the View we need to fix the UI structure of the component. There are numerous alternatives of interface, but for the purpose of this article the most simple one will suit better. Let's keep the items in a Listbox control and add two buttons nearby: "plus" button for adding items and "minus" for removing selected item. ListBox will provide us with the low-level machinery for selecting an item and navigating. A View class is tightly bound to a Controller class, which "... handles the input event from the user interface, often via a registered handler or callback" (from wikipedia.org).

Here are the View and Controller classes:

/**
 * The View. View presents the model and provides
 * the UI events. The controller is attached to these
 * events to handle the user interaction.
 */
function ListView(model, elements) {
    this._model = model;
    this._elements = elements;

    this.listModified = new Event(this);
    this.addButtonClicked = new Event(this);
    this.delButtonClicked = new Event(this);

    var _this = this;

    // attach model listeners
    this._model.itemAdded.attach(function () {
        _this.rebuildList();
    });
    this._model.itemRemoved.attach(function () {
        _this.rebuildList();
    });

    // attach listeners to HTML controls
    this._elements.list.change(function (e) {
        _this.listModified.notify({ index : e.target.selectedIndex });
    });
    this._elements.addButton.click(function () {
        _this.addButtonClicked.notify();
    });
    this._elements.delButton.click(function () {
        _this.delButtonClicked.notify();
    });
}

ListView.prototype = {
    show : function () {
        this.rebuildList();
    },

    rebuildList : function () {
        var list, items, key;

        list = this._elements.list;
        list.html('');

        items = this._model.getItems();
        for (key in items) {
            if (items.hasOwnProperty(key)) {
                list.append($('<option>' + items[key] + '</option>'));
            }
        }
        this._model.setSelectedIndex(-1);
    }
};

/**
 * The Controller. Controller responds to user actions and
 * invokes changes on the model.
 */
function ListController(model, view) {
    this._model = model;
    this._view = view;

    var _this = this;

    this._view.listModified.attach(function (sender, args) {
        _this.updateSelected(args.index);
    });

    this._view.addButtonClicked.attach(function () {
        _this.addItem();
    });

    this._view.delButtonClicked.attach(function () {
        _this.delItem();
    });
}

ListController.prototype = {
    addItem : function () {
        var item = window.prompt('Add item:', '');
        if (item) {
            this._model.addItem(item);
        }
    },

    delItem : function () {
        var index;

        index = this._model.getSelectedIndex();
        if (index !== -1) {
            this._model.removeItemAt(this._model.getSelectedIndex());
        }
    },

    updateSelected : function (index) {
        this._model.setSelectedIndex(index);
    }
};

And of course, the Model, View, and Controller classes should be instantiated:

$(function () {
    var model = new ListModel(['PHP', 'JavaScript']),
        view = new ListView(model, {
            'list' : $('#list'), 
            'addButton' : $('#plusBtn'), 
            'delButton' : $('#minusBtn')
        }),
        controller = new ListController(model, view);
    
    view.show();
});
<select id="list" size="10"></select>
<button id="plusBtn">  +  </button>
<button id="minusBtn">  -  </button>

If you like the explanation of the MVC concept in this article, consider the extended version of it, with:

  • Client-side example in modern JavaScript with complete source code.
  • Server-side example with node.js, Express.js and WebSockers.
  • Suggested exercises.

Buy Now

Alex Netkachov

Alex Netkachov

Alex likes functional programming and algorithms. Apart from programming, his favourites are walking with his family in the parks and national trails and reading about universe and history.

Read More