Gives your Modules the power to communicate with the Application.
The Sandbox instance is shared among all Modules and is the only way for them to communicate with the Application. It provides a set of functionality that needs knowledge of either all involved Module instances or the Application itself.
Thinking of Ajax and the module markup you may retrieve - How would you apply JavaScript functionality to these snippets? addModules provides you exactly that - the possibility to easily add modules dynamically.
/** * Adds (register and start) all modules in the given context scope. * * @method addModules * @param {jQuery|Zepto} $ctx the context. * @return {Array} a list containing the references of the registered modules. */ addModules: function($ctx) {
addModules is a very handy method that allows you to register and start new modules by simply passing a jQuery/Zepto object containing the module(s) DOM as the one and only parameter.
With ie. widget systems or lazy loading techniques in mind this turns into a very powerful utility.
This code could ie. be placed in the on phase of your widget chooser module.
// Make the module instance available for closures var self = this; // Bind the click event to add a new widget $('.addWidget', this.$ctx).on('click', function() { var modules = []; $.ajax({ url: '<url to get the markup of the widget(s)>', success: function(data) { // if needed: wrap the markup with a div (it is used as context parameter, therefore modules are only found if they are not the root element) var $data = $(data).wrap('<div></div>').parent(); // if needed: connect the widget(s) to the appropriate connector channels $('.mod', $data).data('connectors', ["1"]); // register and start the modules in $data modules = self.sandbox.addModules($data); // add them to the DOM $('.body').append($data); } }); return false; });
The snippet above registers and starts the found modules in $data. addModules returns an array containing the instances of the found Modules. Isn't that simple?
/** * Removes (stop and unregister) the modules by the given module instances. * * @method removeModules * @param {Array} modules a list containting the module instances to remove. */ removeModules: function(modules) {
removeModules is the opposite of addModules. As parameter you have to pass in an array containing the Module instances (ie. the return value of addModules or getModuleById).
The given modules are stopped and unregistered. In other words: everything gets cleaned up - all references to the Modules are being deleted and all attached data and events are being removed from the Module DOM. So after that, the modules are only plain markup again - without any functionality.
This is code could ie. be placed in the on phase of the widget.
// Make the module instance available for closures var self = this; // Bind the click event to remove the widget itself $('.removeWidget', this.$ctx).on('click', function() { // stop and unregister the module itself self.sandbox.removeModules([self]); // remove it from the DOM self.$ctx.remove(); return false; });
After running the above, it is like the widget never existed.
Notice: Removing all attached data and events from the module DOM is an expensive operation, because every single Node has to be checked! This expensive operation takes place in the stop method of the base Module (the one that all of your Modules inherit from). If, like in the above example, the module is getting removed from the DOM afterwards, this operation is not necessary because your library cleans up automatically.
In this case it would be better to overwrite the stop method in the widget. As a best practice we recommend you to overwrite the stop method in your Module and unbind your bound events and attached data manually.
In your module could do something like:
stop: function() { var $ctx = this.$ctx; // unbind the previously bound click event $('.removeWidget', $ctx).off('click'); // remove all data attributes from the module node $ctx.removeData(); }
This unbinds your previously bound click event. Furthermore, all TerrificJS specific data attributes get removed from the module node.
Normally connection channels are established by using the Connector Naming Conventions. However, in some cases a Module needs full control over its communication channels.
This code could ie. be placed in the on phase of your module.
// Make the module instance available for closures var self = this; // Click event on subscribe button $('.subscribe', this.$ctx).on('click', function() { // subscribe to channel 1 self.sandbox.subscribe(1, self); }); /// Click event on unsubscribe button $('.unsubscribe', this.$ctx).on('click', function() { // unsubscribe from channel 1 self.sandbox.unsubscribe(1, self); });
Connectors allow you to loosely couple your Modules. Although, in some rare cases you need a bit more flexibility and control. If so - getModuleById is your choice!
/** * Gets the appropriate module for the given id. * * @method getModuleById * @param {int} id the module id * @return {Module} the appropriate module */ getModuleById: function(id) {
In most cases your Modules will communicate with each other over Connector channels (or custom events if they better fit your needs). Using Connectors lets you couple your Modules loosely, which is great because your application will not break if one of the Modules does not exist on a page.
Despite the fact that getModuleById breaks with the loose coupling principle, it can be very useful in some cases where you need a bit more flexibility and control over your Modules
getModuleById makes it possible to access other Module instances directly without communicating over connectors.
Think of an application where one module (ie. modStatus) takes care of all states of the involved Modules. One solution for this problem could be:
This is perfectly fine and there is nothing wrong with this solution. But there are a few things to consider:
And now the solution with getModuleById. This solution does not need any Connectors, custom events or something similar. Instead you only have to make sure, that every Module implements a getStatus method that returns the current state. So in your observable Module you could write something like:
getStatus: function() { return this.state; }
All of the other work is done from the state Module itself. It loops over all Modules and polls their current state when needed.
collectStates: function() { var self = this, $ctx = this.$ctx, states = []; // loop through all modules on the page $('.mod', $ctx).each(function() { var $mod = $(this); var instance = self.sandbox.getModuleById($mod.data('id')); // check whether the module is observable if($.isFunction(instance.getStatus)) { // add the current state to the states array states[$mod.data('id')] = instance.getStatus(); } } return status; }
the Connector solution is not better or worse than the one using getModuleById – though personally i would prefer the former for this kind of problem – its simply different and both of them have their pros and cons.
This method simply allows you to access your config from within your Modules.
/** * Gets the application config. * * @method getConfig * @return {Object} the configuration object */ getConfig: function() {
Unsurprisingly, getConfigParam returns the corresponding config param for the given identifier.
/** * Gets an application config param. * * @method getConfigParam * @param {String} name the param name * @return {mixed} the appropriate configuration param */ getConfigParam: function(name) {
