<ahref="architecture-examples/somajs/index.html"data-source="http://somajs.github.com/somajs/"data-content="soma.js is a javascript model-view-controller (MVC) framework that is meant to help developers to write loosely-coupled applications to increase scalability and maintainability.">soma.js</a>
soma.js is a javascript model-view-controller (MVC) framework that is meant to help developers to write loosely-coupled applications to increase scalability and maintainability.
The main idea behind the MVC pattern is to separate the data (model), the user interface (view) and the logic of the application (controller). They must be independent and should not know about each other in order to increase the scalability of the application.
soma.js is providing tools to make the three parts "talk" to each other, keeping the view and the model free of framework code, using only native events that can be dispatched from either the framework or the DOM itself.
## Credits
Created by [Romuald Quantin](http://www.soundstep.com) using [soma.js](http://somajs.github.com/somajs)
(function(){soma={};soma.version="1.0.2";soma.type="native";if(!Function.prototype.bind){Function.prototype.bind=functionc(f){varg=this;if(typeofg!="function"){thrownewError("Error, you must bind a function.")}vard=Array.prototype.slice.call(arguments,1);vare=function(){if(thisinstanceofe){varj=function(){};j.prototype=g.prototype;vari=newj;varh=g.apply(i,d.concat(Array.prototype.slice.call(arguments)));if(Object(h)===h){returnh}returni}else{returng.apply(f,d.concat(Array.prototype.slice.call(arguments)))}};returne}}soma.applyProperties=function(d,f){for(vareinf){d[e]=f[e]}};soma.inherit=function(g,f){vard;if(f&&f.hasOwnProperty("constructor")){d=f.constructor}else{d=function(){returng.apply(this,arguments)}}soma.applyProperties(d.prototype,g.prototype);vare=function(){};e.prototype=g.prototype;d.prototype=newe();if(f){soma.applyProperties(d.prototype,f,g.prototype)}d.prototype.constructor=d;d.parent=g.prototype;d.extend=function(h){returnsoma.inherit(d,h)};returnd};soma.extend=function(d){returnsoma.inherit(function(){},d)};varb=soma.extend({createPlugin:function(){returnthis.instance.createPlugin.apply(this.instance,arguments)},dispatchEvent:function(){this.instance.dispatchEvent.apply(this.instance,arguments)},addEventListener:function(){this.instance.addEventListener.apply(this.instance,arguments)},removeEventListener:function(){this.instance.removeEventListener.apply(this.instance,arguments)},hasEventListener:function(){returnthis.instance.hasEventListener.apply(this.instance,arguments)},hasCommand:function(d){returnthis.instance.hasCommand(d)},getCommand:function(d){returnthis.instance.getCommand(d)},getCommands:function(){returnthis.instance.getCommands()},addCommand:function(e,d){this.instance.controller.addCommand(e,d)},removeCommand:function(d){this.instance.controller.removeCommand(d)},hasWire:function(d){returnthis.instance.hasWire(d)},getWire:function(d){returnthis.instance.getWire(d)},addWire:function(e,d){returnthis.instance.addWire(e,d)},removeWire:function(d){this.instance.removeWire(d)},hasModel:function(d){returnthis.instance.hasModel(d)},getModel:function(d){returnthis.instance.getModel(d)},addModel:function(d,e){returnthis.instance.addModel(d,e)},removeModel:function(d){this.instance.removeModel(d)},getSequencer:function(d){return!!this.instance.controller?this.instance.controller.getSequencer(d):null},stopSequencerWithEvent:function(d){return!!this.instance.controller?this.instance.controller.stopSequencerWithEvent(d):null},stopSequencer:function(d){if(this.instance.controller){returnthis.instance.controller.stopSequencer(d)}},stopAllSequencers:function(){if(this.instance.controller){this.instance.controller.stopAllSequencers()}},isPartOfASequence:function(d){return!!this.instance.controller?this.instance.controller.isPartOfASequence(d):false},getLastSequencer:function(){return!!this.instance.controller?this.instance.controller.getLastSequencer():null},getRunningSequencers:function(){return!!this.instance.controller?this.instance.controller.getRunningSequencers():null},hasView:function(d){returnthis.instance.hasView(d)},getView:function(d){returnthis.instance.getView(d)},addView:function(e,d){returnthis.instance.addView(e,d)},removeView:function(d){this.instance.removeView(d)}});soma.AutoBind={blackList:["initialize","parent","constructor","$constructor","addEventListener","removeEventListener"],autobind:function(){if(this.wasAutoBound){return}varg=this;vare=g.AutoBindPattern;varf="([lL]istener|[hH]andler|[cB]allback)$";if(!e){e=f}else{e=f+"|"+e}for(varding){if(typeofg[d]=="function"){if(this._autobindIsBlacklisted(d)){continue}if(!d.match(e)){continue}g[d]=g[d].bind(g)}}},_autobindIsBlacklisted:function(d){varf=this.blackList;for(vare=0;e<f.length;e++){if(f[e]==d){returntrue}}returnfalse}};soma.Command=b.extend({instance:null,registerInstance:function(d){this.instance=d},execute:function(d){},toString:function(){return"[soma.Command]"}});vara=soma.extend({event:null,sequenceId:null,constructor:function(d){this.event=d}});soma.SequenceCommand=soma.Command.extend({commands:null,currentCommand:null,id:null,constructor:function(d){if(d==null){thrownewError("SequenceCommand Children expect an unique id as constructor arg")}this.commands=[];this.id=d;soma.Command.call(this)},registerInstance:function(d){this.instance=d;this.initializeSubCommands()},initializeSubCommands:function(){thrownewError("Subclasses of SequenceCommand must implement initializeSubCommands()")},addSubCommand:function(d){vare=newa(d);this.commands.push(e);this.instance.controller.registerSequencedCommand(this,e)},execute:function(d){if(this.commands==null||this.commands.length===0){return}this.currentCommand=this.commands.shift();if(this.hasCommand(this.currentCommand.event.type)){this.dispatchEvent(this.currentCommand.event)}},executeNextCommand:function(){if(this.commands==null){return}this.instance.controller.unregisterSequencedCommand(this,this.currentCommand.event.type);if(this.commands.length>0){this.execute(this.commands[0].event)}else{this.commands=null;this.currentCommand=null}},getLength:function(){if(this.commands==null){return-1}returnthis.commands.length},stop:function(){this.commands=null;this.commands=null;this.currentCommand=null;returnthis.instance.controller.unregisterSequencer(this)},getCurrentCommand:function(){returnthis.currentCommand},getCommands:function(){returnthis.commands},toString:function(){return"[soma.SequenceCommand]"}});soma.ParallelCommand=soma.Command.extend({commands:null,constructor:function(){this.commands=[]},registerInstance:function(d){this.instance=d;this.initializeSubCommands()},initializeSubCommands:function(){thrownewError("Subclasses of ParallelCommand must implement initializeSubCommands()")},addSubCommand:function(d){this.commands.push(d)},execute:function(){while(this.commands.length>0){vard=this.commands.shift();if(this.hasCommand(d.type)){this.dispatchEvent(d)}}this.commands=null},getLength:function(){returnthis.commands!=null?this.commands.length:-1},getCommands:function(){returnthis.commands},toString:function(){return"[soma.ParallelCommand]"}});soma.Wire=b.extend({name:null,instance:null,constructor:function(d){this.name=d},registerInstance:function(d){this.instance=d},init:function(){},dispose:function(){},getName:function(){returnthis.name},setName:function(d){this.name=d},toString:function(){return"[soma.Wire]"}});soma.applyProperties(soma.Wire.prototype,soma.AutoBind);soma.IDisposable=soma.extend({dispose:function(){}});soma.SomaController=soma.extend({instance:null,constructor:function(d){this.boundInstance=this.instanceHandler.bind(this);this.boundDomtree=this.domTreeHandler.bind(this);this.commands={};this.sequencers={};this.sequencersInfo={};this.lastEvent=null;this.lastSequencer=null;this.instance=d},addInterceptor:function(d){if(!soma){thrownewError("soma package has been overwritten by local variable")}if(this.instance.body.addEventListener){this.instance.body.addEventListener(d,this.boundDomtree,true)}this.instance.addEventListener(d,this.boundInstance,-Number.MAX_VALUE)},removeInterceptor:function(d){if(this.instance.body.removeEventListener){this.instance.body.removeEventListener(d,this.boundDomtree,true)}this.instance.removeEventListener(d,this.boundInstance)},executeCommand:function(f){vard=f.type;if(this.hasCommand(d)){varg=newthis.commands[d]();g.registerInstance(this.instance);g.execute(f)}},registerSequencedCommand:function(d,f){if(!(finstanceofa)){thrownewError("capsulate sequence commands in SequenceCommandProxy objects!")}vare=this.sequencersInfo;if(e[d.id]==null||this.sequencers[d.id]==null){this.lastSequencer=d;e[d.id]=[];this.sequencers[d.id]=d}f.sequenceId=d.id;e[d.id].push(f)},unregisterSequencedCommand:function(e,g){if(typeofg!="string"){thrownewError("Controller::unregisterSequencedCommand() expects commandName to be of type String, given:"+g)}varh=this.sequencersInfo;if(h[e.id]!=null&&h[e.id]!=undefined){vard=h[e.id].length;for(varf=0;f<d;f++){if(h[e.id][f].event.type==g){h[e.id][f]=null;h[e.id].splice(f,1);if(h[e.id].length==0){h[e.id]=null;deleteh[e.id]}break}}}},unregisterSequencer:function(e){varg=this.sequencers;if(g[e.id]!=null&&g[e.id]!=undefined){g[e.id]=null;deleteg[e.id];g=this.sequencersInfo;if(g[e.id]!=null){vard=g[e.id].length;for(varf=0;f<d;f++){g[e.id][f]=null}g[e.id]=null;deleteg[e.id];returntrue}}returnfalse},hasCommand:function(d){returnthis.commands[d]!=null},getCommand:function(d){if(this.hasCommand(d)){returnthis.commands[d]}returnnull},getCommands:function(){vare=[];vard=this.commands;for(varfind){e.push(f)}returne},addCommand:function(d,e){if(this.hasCommand(d)){thrownewError("Error in "+this+' Command "'+d+'" already registered.')}this.commands[d]=e;this.addInterceptor(d)},removeCommand:function(d){if(!this.hasCommand(d)){return}this.commands[d]=null;deletethis.commands[d];this.removeInterceptor(d)},getSequencer:function(j){varg=this.sequencersInfo;for(varhing){vard=g[h].length;for(varf=0;f<d;f++){if(g[h][f]&&g[h][f].event.type===j.type){vare=this.sequencers[g[h][f].sequenceId];return!!e?e:null}}}returnnull},stopSequencerWithEvent:function(j){varg=this.sequencersInfo;for(varhing){vard=g[h].length;for(varf=0;f<d;f++){if(g[h][f].event.type===j.type){try{this.sequencers[g[h][f].sequenceId].stop()}catch(k){returnfalse}returntrue}}}returnfalse},stopSequencer:function(d){if(d==null){returnfalse}d.stop();returntrue},stopAllSequencers:function(){vare=this.sequencers;varg=this.sequencersInfo;for(varfine){if(g[f]==null){continue}vard=g[f].length;g[f]=null;deleteg[f];e[f].stop();e[f]=null;deletee[f]}},isPartOfASequence:function(d){return(this.getSequencer(d)!=null)},getRunningSequencers:function(){vard=[];vare=this.sequencers;for(varfine){d.push(e[f])}returnd},getLastSequencer:function(){returnthis.lastSequencer},dispose:function(){for(vareinthis.commands){this.removeCommand(e)}for(vardinthis.sequencers){this.sequencers[d]=null;deletethis.sequencers[d]}this.commands=null;this.sequencers=null;this.lastEvent=null;this.lastSequencer=null},domTreeHandler:function(f){if(f.bubbles&&this.hasCommand(f.type)&&!f.isCloned){if(f.stopPropagation){f.stopPropagation()}else{f.cancelBubble=true}vard=f.clone();this.lastEvent=d;this.instance.dispatchEvent(d);if(!d.isDefaultPrevented()){this.executeCommand(f)}this.lastEvent=null}},instanceHandler:function(d){if(d.bubbles&&this.hasCommand(d.type)){if(this.lastEvent!=d){if(!d.isDefaultPrevented()){this.executeCommand(d)}}}this.lastEvent=null}});soma.SomaViews=soma.extend({views:null,autoBound:false,instance:null,constructor:function(d){this.views={};this.instance=d},hasView:function(d){returnthis.views[d]!=null},addView:function(e,d){if(this.hasView(e)){thrownewError('View "'+e+'" already exists')}if(document.attachEvent){d.instance=this.instance}if(!this.autoBound){soma.applyProperties(soma.View.prototype,soma.AutoBind);this.autoBound=true}if(d.shouldAutobind){d.autobind()}this.views[e]=d;if(d.init!=null){d.init()}returnd},getView:function(d){if(this.hasView(d)){returnthis.views[d]}returnnull},getViews:function(){vare={};for(vardinthis.views){e[d]=this.views[d]}returne},removeView:function(d){if(!this.hasView(d)){return}if(this.views[d]["dispose"]!=null){this.views[d].dispose()}this.views[d]=null;deletethis.views[d]},dispose:function(){for(vardinthis.views){this.removeView(d)}this.views=null;this.instance=null}});soma.EventDispatcher=soma.extend({listeners:null,constructor:function(){this.listeners=[]},addEventListener:function(e,f,d){if(!this.listeners||!e||!f){thrownewError("Error in EventDispatcher (addEventListener), one of the parameters is null or undefined.")}if(isNaN(d)){d=0}this.listeners.push({type:e,listener:f,priority:d,scope:this})},removeEventListener:function(f,h){if(!this.listeners){returnfalse}if(!f||!h){thrownewError("Error in EventDispatcher (removeEventListener), one of the parameters is null or undefined.")}vare=0;vard=this.listeners.length;for(e=d-1;e>-1;e--){varg=this.listeners[e];if(g.type==f&&g.listener==h){this.listeners.splice(e,1)}}},hasEventListener:function(f){if(!this.listeners){returnfalse}if(!f){thrownewError("Error in EventDispatcher (hasEventListener), one of the parameters is null or undefined.")}vare=0;vard=this.listeners.length;for(;e<d;++e){varg=this.listeners[e];if(g.type==f){returntrue}}returnfalse},dispatchEvent:function(f){if(!this.listeners||!f){thrownewError("Error in EventDispatcher (dispatchEvent), one of the parameters is null or undefined.")}vare=[];vard;for(d=0;d<this.listeners.length;d++){varg=this.listeners[d];if(g.type==f.type){e.push(g)}}e.sort(function(i,h){returnh.priority-i.priority});for(d=0;d<e.length;d++){e[d].listener.apply((f.srcElement)?f.srcElement:f.currentTarget,[f])}},getListeners:function(){returnthis.listeners.slice()},toString:function(){return"[soma.EventDispatcher]"},dispose:function(){this.listeners=null}});soma.Application=soma.EventDispatcher.extend({body:null,models:null,controller:null,wires:null,views:null,constructor:function(){soma.EventDispatcher.call(this);this.body=document.body;if(!this.body){thrownewError("soma requires body of type Element")}this.controller=newsoma.SomaController(this);this.models=newsoma.SomaModels(this);this.wires=newsoma.SomaWires(this);this.views=newsoma.SomaViews(this);this.init();this.registerModels();this.registerViews();this.registerCommands();this.registerWires();this.start()},createPlugin:function(){if(arguments.length==0||!arguments[0]){thrownewError("Error creating a plugin, plugin class is missing.")}varf=arguments[0];arguments[0]=this;vard=[null];for(vare=0;e<arguments.length;e++){d.push(arguments[e])}returnnew(Function.prototype.bind.apply(f,d))},hasCommand:function(d){return(!this.controller)?false:this.controller.hasCommand(d)},getCommand:function(d){return(!this.controller)?null:this.controller.getCommand(d)},getCommands:function(){return(!this.controller)?null:this.controller.getCommands()},addCommand:function(d,e){this.controller.addCommand(d,e)},removeCommand:function(d){this.controller.removeCommand(d)},hasWire:function(d){return(!this.wires)?false:this.wires.hasWire(d)},getWire:function(d){return(!this.wires)?null:this.wires.getWire(d)},getWires:function(){return(!this.wires)?null:this.wires.getWires()},addWire:function(e,d){returnthis.wires.addWire(e,d)},removeWire:function(d){this.wires.removeWire(d)},hasModel:function(d){return(!this.models)?false:this.models.hasModel(d)},getModel:function(d){return(!this.models)?null:this.models.getModel(d)},getModels:function(){return(!this.models)?null:this.models.getModels()},addModel:function(d,e){returnthis.models.addModel(d,e)},removeModel:function(d){this.models.removeModel(d)},hasView:function(d){return(!this.views)?false:this.views.hasView(d)},getView:function(d){return(!this.views)?null:this.views.getView(d)},getViews:function(){return(!this.views)?null:this.views.getViews()},addView:function(e,d){returnthis.views.addView(e,d)},removeView:function(d){this.views.removeView(d)},getSequencer:function(d){return!!this.controller?this.controller.getSequencer(d):null},isPartOfASequence:function(d){return(this.getSequencer(d)!=null)},stopSequencerWithEvent:function(d){return!!this.controller?this.controller.stopSequencerWithEvent(d):false},stopSequencer:function(d){return!!this.controller?this.controller.stopSequencer(d):false},stopAllSequencers:function(){if(this.controller){this.controller.stopAllSequencers()}},getRunningSequencers:function(){return!!this.controller?this.controller.getRunningSequencers():null},getLastSequencer:function(){return!!this.controller?this.controller.getLastSequencer():null},dispose:function(){if(this.models){this.models.dispose();this.models=null}if(this.views){this.views.dispose();this.views=null}if(this.controller){this.controller.dispose();this.controller=null}if(this.wires){this.wires.dispose();this.wires=null}this.body=null;soma.EventDispatcher.prototype.dispose.call(this)},toString:function(){return"[soma.Application]"},init:function(){},registerModels:function(){},registerViews:function(){},registerCommands:function(){},registerWires:function(){},start:function(){}});soma.SomaModels=soma.extend({models:null,instance:null,constructor:function(d){this.models={};this.instance=d},hasModel:function(d){returnthis.models[d]!=null},getModel:function(d){if(this.hasModel(d)){returnthis.models[d]}returnnull},getModels:function(){varf={};vare=this.models;for(vardine){f[d]=e[d]}returnf},addModel:function(d,e){if(this.hasModel(d)){thrownewError('Model "'+d+'" already exists')}this.models[d]=e;if(!e.dispatcher){e.dispatcher=this.instance}e.init();returne},removeModel:function(d){if(!this.hasModel(d)){return}this.models[d].dispose();this.models[d]=null;deletethis.models[d]},dispose:function(){for(vardinthis.models){this.removeModel(d)}this.models=null;this.instance=null}});soma.Model=soma.extend({name:null,data:null,dispatcher:null,constructor:function(d,f,e){this.data=f;this.dispatcher=e;if(d!=null){this.name=d}},init:function(){},dispose:function(){},dispatchEvent:function(){if(this.dispatcher){this.dispatcher.dispatchEvent.apply(this.dispatcher,arguments)}},addEventListener:function(){if(this.dispatcher){this.dispatcher.addEventListener.apply(this.dispatcher,arguments)}},removeEventListener:function(){if(this.dispatcher){this.dispatcher.addEventListener.apply(this.dispatcher,arguments)}},getName:function(){returnthis.name},setName:function(d){this.name=d},toString:function(){return"[soma.Model]"}});soma.View=soma.extend({instance:null,domElement:null,constructor:function(f){vare;if(f!=undefined){if(f.nodeType){e=f}else{thrownewError("domElement has to be a DOM-ELement")}}else{e=document.body}this.domElement=e},dispatchEvent:function(d){if(this.domElement.dispatchEvent){this.domElement.dispatchEvent(d)}else{if(this.instance){this.instance.dispatchEvent(d)}}},addEventListener:function(){if(this.domElement.addEventListener){this.domElement.addEventListener.apply(this.domElement,arguments)}else{if(this.instance){this.instance.addEventListener.apply(this.instance,arguments)}}},removeEventListener:function(){if(this.domElement.addEventListener){this.domElement.removeEventListener.apply(this.domElement,arguments)}else{if(this.instance){this.instance.removeEventListener.apply(this.instance,arguments)}}},init:function(){},dispose:function(){},toString:function(){return"[soma.View]"}});soma.SomaWires=soma.extend({wires:null,instance:null,constructor:function(d){this.wires={};this.instance=d},hasWire:function(d){returnthis.wires[d]!=null},addWire:function(e,d){if(this.hasWire(e)){thrownewError('Wire "'+e+'" already exists')}if(d.shouldAutobind){d.autobind()}this.wires[e]=d;d.registerInstance(this.instance);d.init();returnd},getWire:function(d){if(this.hasWire(d)){returnthis.wires[d]}returnnull},getWires:function(){vare={};for(vardinthis.wires){e[d]=this.wires[d]}returne},removeWire:function(d){if(!this.hasWire(d)){return}this.wires[d].dispose();this.wires[d]=null;deletethis.wires[d]},dispose:function(){for(vardinthis.wires){this.removeWire(d)}this.wires=null;this.instance=null}});soma.Mediator=soma.Wire.extend({viewComponent:null,constructor:function(d){soma.Wire.call(this,d);this.viewComponent=viewComponent},dispose:function(){this.viewComponent=null},toString:function(){return"[soma.Mediator]"}});soma.Event=soma.extend({constructor:function(g,i,f,d){varh=soma.Event.createGenericEvent(g,f,d);if(i!=null&&i!=undefined){h.params=i}h.isCloned=false;h.clone=this.clone.bind(h);h.isIE9=this.isIE9;h.isDefaultPrevented=this.isDefaultPrevented;if(this.isIE9()||!h.preventDefault||(h.getDefaultPrevented==undefined&&h.defaultPrevented==undefined)){h.preventDefault=this.preventDefault.bind(h)}if(this.isIE9()){h.IE9PreventDefault=false}returnh},clone:function(){vard=soma.Event.createGenericEvent(this.type,this.bubbles,this.cancelable);d.params=this.params;d.isCloned=true;d.clone=this.clone;d.isDefaultPrevented=this.isDefaultPrevented;d.isIE9=this.isIE9;if(this.isIE9()){d.IE9PreventDefault=this.IE9PreventDefault}returnd},preventDefault:function(){if(!this.cancelable){returnfalse}this.defaultPrevented=true;if(this.isIE9()){this.IE9PreventDefault=true}this.returnValue=false;returnthis},isDefaultPrevented:function(){if(!this.cancelable){returnfalse}if(this.isIE9()){returnthis.IE9PreventDefault}if(this.defaultPrevented!=undefined){returnthis.defaultPrevented}else{if(this.getDefaultPrevented!=undefined){returnthis.getDefaultPrevented()}}returnfalse},isIE9:function(){returndocument.body.style.scrollbar3dLightColor!=undefined&&document.body.style.opacity!=undefined},toString:function(){return"[soma.Event]"}});soma.Event.createGenericEvent=function(g,f,d){varh;f=f!==undefined?f:true;if(document.createEvent){h=document.createEvent("Event");h.initEvent(g,f,!!d)}else{h=document.createEventObject();h.type=g;h.bubbles=!!f;h.cancelable=!!d}returnh};soma.IResponder=soma.extend({fault:function(d){},result:function(d){}})})();