Developing Javascript MVC Application

update: full source code on github: https://github.com/serdary/JavaScript-MVC-NoteBook-App
demo: notebook js mvc application

Recently I needed to create a basic javascript web application that is written with Model-View-Controller Architecture.

There are some good JS tools (MVC-MVVM) that lots of startups and bigger companies use, such as knockout, javascriptmvc.

But if you don’t need something that complex, or you just want to write your own js mvc structure app from scratch, unfortunately, there isn’t lots of resource about this, so I created a mini notebook javascript MVC app.

It is not using any external libraries, such as jquery. It is purely written with javascript and I tried to avoid any non-crossbrowser built-in functions, like Array.indexOf. So I suppose it should work on most browsers.

Notebook App has around 400 LoC, so I’m not gonna add whole app here, but you can check from github. Please feel free to add your comments/corrections.

NoteBook Javascript MVC App

I’m not gonna explain MVC architecture here in detail, but here is some important points;
view should know nothing about the models and model should be about the data.
If you need to fetch data from server or insert/change data on server, this should be done in models.
And when a model changes, it should notify all the related view(s) about the changes.
Observer Pattern can be used to provide this functionality.

EventObserver

I created below EventObserver class to provide this notification system, flow is generally as follows:
– when the model is created, it adds events that are related.
– view attaches observers to an event.
– when a model changed (a method may be triggered on model etc.), it notifies the attached observers for the event, with optional data parameter.

var EventObserver = function(events) {
	var _eventObservers = {};
	// adds an event if it is not added before
	var addEvent = function(event) { };

	// notifies the attached observers to the event with data param
	var notify = function(event, data) { };

	// attaches an observer to the event if it is not already attached
	var attach = function(event, observer) { };

	// public methods for notifying and attaching observers
	return {
		notifyObservers: function(event, data) {
			notify(event, data);
		},
		attachObserver: function(event, observer) {
			attach(event, observer);
		}
	};
}

Models

I created 2 models for notebook app.

First one is Note Model. Class has _id and _content properties, which is very basic.
It has a private setter for id and some public methods such as save(), remove(), toString() and getters.
I have added 2 dummy methods (saveDummy() and removeDummy()) for testing purposes. In a real production app, save and remove methods will probably make an ajax call to server and methods will change the object state according to the operation result on server.
But in these dummy methods, it simply makes the value of _id -1 after removeDummy() called and assigns an incremental value to _id when saveDummy() method called.

// note model
var Note = function(content) {
	// private vars
	var _id;
	var _content = content;

	// setter
	var setId = function(id) {
		_id = id;
	};

	return {
		// getters

		// saves the current note
		save: function() {
			// all model validations go here
			if (_content === undefined || _content === '') {
				return false;
			}
			/*
			Make an ajax call to server, save the note and get id, i.e.:
			$.ajax({
				url: 'note/create',
				... });
			*/
		},

		// removes the note, returns the result
		remove: function() { },

		// used for testing the app without tiding an actual db,
		// identifiers are set by externally from the list
		saveDummy: function(dummyAutoId) {
		},

		// removes the note, returns the result
		removeDummy: function() {
		},
		toString: function() { }
	};
};

Second model is NoteList Model which holds a list of notes and an eventObservers object.
NoteList object provides the functionality to add / remove new notes and attach observers to the list events.

var NoteList = function() {
	var _list = [];
	// add the events which will be used to attach and notify observers
	var _eventObservers = EventObserver(['noteAdd', 'noteRemove']);
	return {
		// getter for the list
		getList: function() { },

		// adds a note to the list
		addNote: function(note) { },

		// removes a note from list
		removeNote: function(note) { },

		// used to attach an observer to an event
		attachObserver: function(event, observer) { },

		toString: function() { },

		// adds dummy notes to the list -- used to test the listing feature
		addDummyNotes: function(limit) { }
	};
};

Ok now lets create some views and a controller for the app.
I created 2 views for the app, a list view class and an add note view class.

Views

addEventListener functions are used to call the controller methods when a remove note link is clicked or a new note form is submitted. See the usage for submit form button below,

	// submit button for the add form
	var addButton = document.createElement('input');
	addButton.type = 'submit';
	addButton.value = 'add note';
	addButton.addEventListener('click', function(e) {
		e.preventDefault(); // prevent the link element's default behavior.
		noteController.handleAddEvent(textboxElement.value, noteList);
	}, false);
	// the last false parameter is about an event bubbling cross browser issue.
	//No need to detail right now.

List View has 3 major methods,
displayAllNotes: displays all the notes in the list. Called when the app is initialized.

updateListAfterRemoving: updates the note list on browser when user triggered remove a note event.
This observer is attached for noteRemove event when the view is created. So when noteRemoved event is triggered by the user, controller calls the appropriate method (NoteList.removeNote in this case), and the method notifies this function by using EventObserver object.

updateListAfterAdding: updates the note list when user submits a new note.
Same as updateListAfterRemoving, this observer method is called when the model changed.

var NoteListView = function(noteList, noteController) {
	// empties list wrapper and displays all of the user's notes.
	// if user has no notes, then displays a warning message
	var displayAllNotes = function() {
		var listWrapper = document.getElementById('list_wrapper');
		for (var i = 0; i < list.length; i++) {
			listWrapper.appendChild(createNoteElement(list[i]));
		}
	};

	// called from EventObserver class when user removed a note from the list
	// gets data from model, removes the note if it is removed successfully
	// adds a feedback message to browser
	var updateListAfterRemoving = function(data) { };

	// called from EventObserver class when user added a note to the list
	// gets data from model, adds the note to the end of the list, if it is added successfully
	// adds a feedback message to browser
	var updateListAfterAdding = function(data) { };

	// attach add and remove observers for the related events
	noteList.attachObserver('noteAdd', updateListAfterAdding);
	noteList.attachObserver('noteRemove', updateListAfterRemoving);

	return {
		// a public method to display the whole note list
		displayList: function() {
			displayAllNotes();
		}
	};
};

Note Add View has just 1 method which displays the “add a new note form”.
Method is called when the app is initialized.

var NoteAddView = function(noteList, noteController) {
	return {
		// displays the add form
		displayForm: function() {
		}
	};
};

Controller

Note Controller is the main controller for this basic app.
It has 2 handlers for addEvent and removeEvent and a displayNoteList public method to display all notes when the app starts.

var NoteController = function() {
	return {
		// handles the add event when a new form is submitted
		// creates a new note instnace and add to the notelist if it is valid
		handleAddEvent: function(content, noteList) {
			var note = Note(content);
			noteList.addNote(note);
		},
		// handles the remove event when user click the remove button
		// gets the note as param, remove it from the list
		handleRemoveEvent: function(note, noteList) {
			noteList.removeNote(note);
		},
		// triggered from webpage. Creates an empty list, shows add form and the note list to user.
		displayNoteList: function() {
			var noteList = NoteList();
			var noteAddView = NoteAddView(noteList, this);
			noteAddView.displayForm();
			var noteListView = NoteListView(noteList, this);
			noteListView.displayList();
		}
	};
};

A basic html markup for this app might be as follows.
It creates a new NoteController object and displays all the notes.

<html>
<head>
  <title>Javascript MVC Notebook</title>
  <script type="text/javascript" src="notebook.js"></script>
</head>
<body>
	<div id="wrapper">
		Javascript MVC Application - NoteBook
		<div id="message"></div>
		<div id="new_note_wrapper"></div>
		<div id="list_wrapper"></div>
	</div>
	<script type="text/javascript">
		var controller = NoteController();
		controller.displayNoteList();
	</script>
</body>
</html>

2 great blog posts, you should also check them out:
MVC Architecture for JavaScript Applications
Javascript mvc

That’s it!
If you have any questions, fixes or suggestions, please add your comments.
Thanks for reading!

 

Tags: , , ,

Comments: 2

Leave a reply »

 
  • rafa

    Hello Serdar,

    I really liked your example, I was precisely looking for a simple and transparent MVC implementation to structure an existing app. I will be taking a closer look at your code very soon.

    Do you have any comments or experiences unit testing each layer?

    Thanks!

     
     
     
    • Hi Rafa,
      I don’t have a lot of experience on JS unit testing but I’ve played with jsunit and busterjs, and I can recommend you taking a look at busterjs.org

       
  • Leave a Reply
     
    Your gravatar
    Your Name