Javascript - MVC

javascript

Articles

CanJS - Focuses on striking a balance between size, ease of use, safety, speed and flexibility.
javascriptmvc
Vue.js
AngularJS
React
Backbone.JS
EmberJS
BatmanJS
StapesJS
Maria
Serenade.js
Cappuccino
KnockoutJS
Enyo
http://cujojs.com/
http://duojs.org/
http://alt.js.org/
http://jsblocks.com/
https://flightjs.github.io/
https://ampersandjs.com/
http://marionettejs.com/
https://github.com/troopjs/

SammyJS
SpineJS
Cycle.js
Mojito
Min.js
Riot.js
PureMVC
SomaJS
PlastronJS
AgilityJS
AmplifyJS
Eyeballs.js
http://ariatemplates.com/
Component
http://www.socketstream.org/
https://www.firebase.com/

https://bitbucket.org/mckamey/duel/wiki/Home
KendoUI
rAppid.js
GWT
qatrix
ActiveJS
Epitome: an MVC* (MVP) framework for MooTools.
choco
ExtJS
Jamal

http://todomvc.com/

SprouteCore
TrimJunction

https://developers.google.com/closure/library/?csw=1
http://lhorie.github.io/mithril/

The Model:

var Events = {
    get: function(id) {
        return this.data[id];
    },
    del: function(id) {
        delete this.data[id];
        AjaxRequest.send('/events/delete/' + id);
    },
    create: function() { 
        ...
    },
    update: function() {
        ...
    },
    data: {
        '112': { 'name': 'Party time!', 'date': '2009-10-31' },
        '113': { 'name': 'Pressies!', 'date': '2009-12-25; {
    }
    metadata: {
        'name': { 'type': 'text', 'maxlength': 20 },
        'date': { 'type': 'date', 'between': ['2008-01-01','2009-01-01'] }
    }
}

The above code is a Model. It contains the CRUD methods, its data, and metadata.

The View:

In MVC, the View accepts data and determines how to render it. The View does not care about how to get the data or where the data comes from. It takes the data it gets.

View.EventsDialog = function(CalendarEvent) {
    var html = '<div><h2>{name}</h2>' +
            '<div class="date">{date}</div></div>';
    html = html.replace(/{[^}]*}/g, function(key) {
        return CalendarEvent[key.slice(1,-1)] || '';
    });
    var el = document.getElementById('eventshell');
    el[removed] = html;
}

Events.data = {
    '112': { 'name': 'Party time!', 'date': '2009-10-31' },
    '113': { 'name': 'Pressies!', 'date': '2009-12-25' }
}

View.EventsDialog(Events.data['112']); // update the rendering of item 112

The EventsDialog function expects a JSON object with name and date properties. The Events.data property stores the calendar events, and the call that sends a specific event to the events dialog.

The Events Dialog View can then be extended to include additional methods that enable interaction. In the following example, the Events Dialog View is given open and close methods. By doing this, we make the View self-aware and also provide points of contact that will allow a Controller to manage the View without need to know the inner details of the object.

View.EventsDialog.prototype.open = function() {
    document.getElementById('eventshell').style.display = 'block';
};

View.EventsDialog.prototype.close = function() {
    document.getElementById('eventshell').style.display = 'none';
};

var dialog = new View.EventDialog(eventObject);
dialog.open();
dialog.close();

Making Views aware of the data model and data retrieval method is an easy trap to fall into.

As a general rule, a View should not run its own methods. For example, a dialog should not open or close itself. Leave this to the Controller. If a user clicks on a Save button within a dialog, that event is passed to a Controller action. The action can then decide what the View should do. Maybe it closes the dialog. Maybe it tells the Views to display an "in-progress" indicator while the data saves. Once the data is saved, the Ajax completion event fire off another controller action, which tells the View to hide the indicator and close the dialog.

However, there are situations in which Views should handle their own events or run their own methods. For example, a View could have an input range with a slider that allows the user to pick the value. The View handles the logic for that slider interaction and how it displays the resulting value. The is no need for a Controller to manage that interaction.

The Controller:

A Controller is activated when an event occurs. That may be when the page loads or when a user initiates an action. An event handler is assigned to a controller method that will do the user's bidding.

Controllers.EventsEdit = function(event) {
    /* event is the javascript event, not our calendar event */
    // grab the event target id, which stores the id
    var id = event.target.id.replace(/[^d]/g,'');
    var dialog = new View.Dialog( Events.get(id) );
    dialog.open();
}

This pattern really comes in handy when data is used in various contexts. For example, say we're editing an event displayed on a calendar. We click the delete button, and new need to get rid of the dialog and the event on the calendar, and then delete the event from the server:

Controller.EventsDelete = function(event) {
    var id = event.target.id.replace(/[^d]/g, '');
    View.Calendar.remove(id);
    Events.del(id);
    dialog.close();
}

Controller actions becomes simpler and easier to understand. This is the key to building a maintainable application.

Form Validation:

The question is whether validation should be part of View or Model.

The Model determines whether the data is correct or not using a method. It is not concerned with how to present the summary view. It just needs to report which fields didn't make the grade.

var MyModel = {
    validate: function(data) {
        var invalidFields = [];
        for (var i = 0; i < data.length; i++) {
            if (this.metadata[data.key].required && !data.value) {
                invalidFields[invalidFields.length] = {
                    field: data.key,
                    message: data.key + ' is required.'
                }
            }
        }
        return invalidFields;
    },
    metadata: {
        'other': {required: true}
    }
}

To validate our data, we provide an array of key / value pairs. The key is the field name and the value is what the user entered into the field:

var data = [
    {'other': false}
];

var invalid = MyModel.validate(data);

Our invalid variable now contains a list of any fields that didn't validate. Now we'll pass the data to the View to display the errors on the page. This display will be a View, and will expect data to be passed to it from the Controller. The View will use that data to construct the error message that is shown to the user:

View.Message = function(messageData, type) {
    var el = document.getElementById('message');
    el.className = type;
    var message = '<h2>We have something to bring to your attention</h2>' +
        '<ul>';
    for (var i=0; i < messageData.length; i++) {
        message += '<li>' + messageData[i] + '</li>';
    }
    message += '</ul>';
    el[removed] = message;
}

View.Message.prototype.show = function() {
    ...
}

Our Model stores our data and can tell us whether the data validates. We have a View that we use to display error message. The last step is to validate the form when the user tries to submit it:

MyController.validateForm = function(event) {
    var data = [];
    data['other'] = document.getElementById('other').checked;
    var invalidFields = MyModel.validate(data);
    if (invalid.length) {
        event.preventDefault();
        // generating the view and show the message
        var message = new View.Message(invalidFields, 'error');
        message.show();
    }
}

addEvent(document.getElementById('myform'), 'submit', MyController.validateForm);
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License