Model-View-Controller (MVC) with JavaScript

The article describes an implementation of Model-View-Controller software design pattern in JavaScript. Created classes conform to Dojo toolkit class building concepts: dojo.lang.declare creates classes and dojo.event.connect supports low coupling of MVC.

I like programming with JavaScript, because it is the most flexible language in the world. With the JavaScript language developers can create applications either in procedural or object-oriented style. It supports almost all programming styles and techniques that I know. I’ve seen procedural, object-oriented, aspect-oriented JavaScript code snippets. A determined developer can even use functional programming techniques in JavaScript applications.

My goal for this article is to write a simple JavaScript component that can show a power of the language. The component is a kind of the HTML ListBox (“select” HTML tag) control with an editable list of items: the user can select item and remove it or add new items into the list. I will use Dojo toolkit class building functions [link] to create three classes that implement the Model-View-Controller design pattern. These classes will use Dojo’s event system for subscribing on DHTML events and for notifying the View about changes in the Model.

I hope, this article can be just a good reading for you, but it would be much better if you consider running the example and adapting it to you needs. I believe you have everything to create and run JavaScript programs: brains, hands, text editor (notepad), and Internet browser (IE or Firefox). You only need to download and unpack the Dojo toolkit to successfully run the example on your computer. Dojo manual and Dojo book are good places for finding answers to questions about the toolkit, which may arise while reading.

The Model-View-Controller pattern, which I’m about to use in the code, requires some description here. As you may know, the name of the pattern is based on the names of its main parts: Model, which stores an application data model; View, which renders Model for an appropriate representation; and Controller, which updates Model. Wikipedia defines typical components 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.

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

dojo.lang.declare('ListModel', null, {

	initializer : function (items) {
		this._items = items;
		this._selected = -1;
	},

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

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

	removeItemAt : function (index) {
		this._items.splice(index, 1);
	},

	removeSelectedItem : function () {
		if (-1 != this._selected) {
			this._items.splice(this._selected, 1);
		}
	},

	setSelected : function (index) {
		this._selected = index;
	}

});

The class declaration begins with dojo.lang.declare method, which defines a new class. Dojo book has a lot of information on how to use this method, but it contains wrong code in examples. You can see dojo.declare in the manual and this causes an error in the latest dojo version. First and second arguments of the function specify class name and superclass (or predecessor). A third argument of the dojo.lang.declare defines methods of the new class. The methods or our particular class supports retrieving list of items, adding an item to the list, selecting an item, and removing currently selected item. So, the Model part looks ready to be used by a View class.

The View class requires defining controls for interacting with. There are numerous alternatives of interface for the task, but I prefer a most simple one. I want my items to be in a Listbox control and two buttons below it: “plus” button for adding items and “minus” for removing selected item. The support for selecting an item is provided by Listbox’s native functionality. A View class is tightly bound with a Controller class, which “... handles the input event from the user interface, often via a registered handler or callback.” (from wikipedia.org). So, the View registers callback handlers on model changing methods and bounds controller methods with the UI controls. The classes use aspect-oriented JavaScript features: as you can see, the View class registers callback handler on a method of another class (the Model) without modifying it.

So, here are the View and Controller classes:

dojo.lang.declare('ListView', null, {

	initializer : function (model, controller, elements) {
		this._model = model;
		this._controller = controller;
		this._elements = elements;
		with (dojo.event) {
			connect(this._model, 'addItem', this, 'rebuildList');
			connect(this._model, 'removeSelectedItem', this, 'rebuildList');
			connect(this._elements.list, 'onchange', 
				this._controller, 'updateSelected');
		}
	},

	show : function () {
		this.rebuildList();
		var e = this._elements;
		dojo.event.connect(e.addButton, 'onclick', this._controller, 'addItem');
		dojo.event.connect(e.delButton, 'onclick', this._controller, 'delItem');
	},

	rebuildList : function () {
		var list = this._elements.list;
		while (list.lastChild) {
			list.removeChild(list.lastChild);
		}
		var items = this._model.getItems();
		for (var key in items) {
			list.appendChild(document.createElement('option')
				).appendChild(document.createTextNode(items[key]));
		}
		this._model.setSelected(-1);
	}

});

dojo.lang.declare('ListController', null, {

	initializer : function (model) {
		this._model = model;
	},

	addItem : function (e) {
		var item = prompt('Add item:', '');
		if (item) {
			this._model.addItem(item);
		}
		e.preventDefault();
	},

	delItem : function (e) {
		this._model.removeSelectedItem();
		e.preventDefault();
	},

	updateSelected : function (e) {
		this._model.setSelected(e.target.selectedIndex);
	}

});

And of course, the Model, View, and Controller classes should be instantiated. The sample, which you can review on this page, uses the following code to instantiate and configure the classes:

<script type="text/javascript">
dojo.addOnLoad(function () {
	var model = new ListModel(['PHP', 'JavaScript']);
	var view = new ListView(model, new ListController(model),
		{
			'list' : dojo.byId('list'), 
			'addButton' : dojo.byId('plusBtn'), 
			'delButton' : dojo.byId('minusBtn')
		}
	);
	view.show();
});
</script>
<select id="list" size="10" style="width: 15em"></select><br/>
<button id="plusBtn">  +  </button>
<button id="minusBtn">  –  </button>

Test it. The advanced example demonstrates how to add functions that change order of items in the list.

Code in this article was tested with Dojo Toolkit 0.4.3.