You’ve got routes, script loading, OOP. Thoses are very low level tools. To allow you building a strong and understandable Rich Application, RESThub JS gives you a tiny MVC pattern.
A Controller is a autonomous component that will be attached to a DOM node (div, span) in order to display itself. It is statefull, so you can create many Controller instances, they will render themselves based on there own atributes.
Template loading is based on RequireJS text plugin and jQuery tmpl.
Controllers are Classes, so here is an example of declaration:
define([ 'text!home.html', lib/controller', 'lib/jqueryui/button' ], function(tmpl, Controller) {
return Controller.extend("MyHomeController", {
template : tmpl,
myName : "I'm called HomeController",
/**
* Constructor. Display template and creates widgets.
*/
init : function() {
var data = { user: $.storage.get(Constants.USER_KEY) };
// Calls the view rendering
this.render(data);
}
}
});
We saw in the class paragraph that init() method acts like a constructor. So during construction, the view will be rendered.
To instantiate a controller, we need to require it, and then to use it as a jQuery plugin:
$.route('#/home', function() {
require(['myhome'], function() {
$('#myDiv').my_home();
});
});
Select an existing DOM node (the one with id ‘myDiv’ in the example), and apply the desired controller on it.
The render method will display the current Controller based on the template and the data passed as parameters:
/**
* Renders current widget with the template specified in this.prototype.template.
*
* @param data Mandatory, the data to render. This can be any JavaScript type, including Array or Object.. For example an object {value : 'test'} passed as data arameter will be referenced as ${value} in the template
* @param options Optional map of user-defined key-value pairs. Extends the tmplItem data structure, available to the template during rendering
* @param el Optional, used to override the default element when the template will be rendered (by default this.element)
* @param tmpl Optional, used to override the default template (by default this.tmpl)
*/
render : function(data, options, el, tmpl);
Now we want to add some interactivity: let’s plug a jQuery-UI button widget:
init : function() {
// Calls the view rendering
this.render();
// Adds some widget
$('a', this.element)
.click($.proxy(this, '_buttonHandler'))
.button({
label: 'ClickMe'
})
By itself the button does not handle clicks (its a business-agnostic widget). So we bound a handler method to the click event of the DOM Node.
As you probably know, Javascript loses the ‘context’ (pointed by the this keyword) when asynchronous event occurs.
So when the user will click, the handler will not be related to your Controller !
Fortunately, the jQuery $.proxy() method corrects this issue.
And we could use the Controller’s inner attribute in the handler:
/**
* Handler of user click. Writes a console message.
*
* @param {Event} event Click event
*/
_buttonHandler: function(event) {
// Stops event propagation
event.preventDefault();
console.info('You clicked me: ' + this._myName);
return false;
}
What about memory management ?
As we bound a handler to the click event, we need to unbind this before the Controller deletion. Or we may create memory leaks.
That’s the purpose of the destroy() method:
/**
* Destroy function, invoked when the rendering is removed.
* May be overrited to add specific finalization code.
*/
destroy: function() {
// Unbind connected handlers
$('a', this.element).unbind();
// Calls overriden method
this._super();
}
Be careful ! You absolutely need to call the overriden method when overriding the destroy() method.
In complex RIA, Controllers massively use event (the pub-sub mechanism). To lighten usage of event handles, Controllers have a specific subscribe() method:
init : function() {
// Calls the view rendering
this.render();
this.subscribe(['event1', 'event2'], '_eventHandler');
The controller’s subscribe() method will automatically holds the generated handle, and performs automatically unsubscription in the destroy() method.
You can quickly register a single handler to multiple events.
And in bonus, you do not need to use $.proxy(), because it’s internally used.