1 /**
  2     Create and extend controllers
  3     @class
  4 */
  5 MBX.JsController = (function () {
  6     /**
  7         public methods of JsController
  8         @memberof MBX.JsController
  9         @namespace
 10     */
 11     var publicObj = {};
 12     
 13     /**
 14         used to cache instances of controllers and stop name collisions
 15         @memberof MBX.JsController
 16         @private
 17     */
 18     var controllerCache = {};
 19     
 20     
 21     /** @private */
 22     var jsElementClass = '.js_updateable';
 23     
 24     /**
 25         The instance that gets created from calling
 26         MBX.JsController.create(name, opts)
 27         @see MBX.JsController
 28         @name JsController
 29         @class
 30     */
 31     var JsController = function (name, opts) {
 32         opts = opts || {};
 33         if (!name) {
 34             throw new Error("A name must be specified");
 35         }
 36         if (controllerCache[name]) {
 37             throw new Error("A controller by that name exists");
 38         }
 39         
 40         this.controllerName = name;
 41         _(this).extend(opts);
 42         if (this.model) {
 43             this._subscribeToEvents();
 44         }
 45         controllerCache[name] = this;
 46         
 47         MBX.emit(publicObj.Event.newController, {
 48             object: this
 49         });
 50     };
 51 
 52     _(JsController.prototype).extend(EventEmitter.prototype);
 53     
 54     _(JsController.prototype).extend(
 55         /** @lends JsController */
 56         {
 57             
 58         active: true,
 59         
 60         /** reactivate event listening on this controller
 61         */
 62         activate: function () {
 63             this.active = true;
 64         },
 65         
 66         /** quiets all events on this controller, your callbacks will
 67             not get called
 68         */
 69         deactivate: function () {
 70             this.active = false;
 71         },   
 72         
 73         /**
 74             If you have passed in a function for onInstanceChange
 75             then this will pass the object and the key that changed to
 76             your function
 77             @requires MBX.JsModel
 78             @requires this.model
 79             @see JsModel#instance
 80             @example
 81               myController = MBX.JsController.create("myController", {
 82                   onInstanceChange: function (modelInstance, key) {
 83                       // where key will be the attribute (string)
 84                       // that has changed on the modelInstance (JsModel#instance)
 85                   }
 86               });
 87         */
 88         _onInstanceChange: function (evt) {
 89             if (this.onInstanceChange && this.active) {
 90                 this.onInstanceChange(evt.object, evt.key);
 91             }
 92         },
 93         
 94         /**
 95             @private
 96             @requires MBX.JsModel
 97             @requires this.model
 98         */
 99         _onInstanceCreate: function (evt) {            
100             if (this.onInstanceCreate && this.active) {
101                 this.onInstanceCreate(evt.object);
102             }     
103         },
104         
105         /**
106             @private
107             @requires MBX.JsModel
108             @requires this.model
109         */
110         _onInstanceDestroy: function (evt) {
111             if (this.onInstanceDestroy && this.active) {
112                 this.onInstanceDestroy(evt.object);
113             }
114         },
115         
116         /**
117             @private
118             @requires MBX.JsModel
119             @requires this.model
120         */
121         _onAttributeChange: function (evt) {
122             if (this.active && typeof this.onAttributeChange == 'function') {
123                 this.onAttributeChange(evt.key);
124             }
125         },
126         
127         /**
128             @private
129             @requires MBX.JsModel
130             @requires this.model
131             
132         */
133         _subscribeToEvents: function () {
134             var model = this.model;
135             if (!_(model).isArray()) {
136                model = [model];
137             }
138 
139             this.eventSubscriptions = [];
140 
141             _(model).each(_(function (model) {
142                 var changeEvent = model.Event.changeInstance;
143                 var newEvent = model.Event.newInstance;
144                 var destroyEvent = model.Event.destroyInstance;
145                 var attributeEvent = model.Event.changeAttribute;
146                 //var defer = this.looselyCoupled;
147 
148                 var onInstanceChange =  _(this._onInstanceChange).bind(this);
149                 var onInstanceCreate =  _(this._onInstanceCreate).bind(this);
150                 var onInstanceDestroy = _(this._onInstanceDestroy).bind(this);
151                 var onAttributeChange = _(this._onAttributeChange).bind(this);
152                 MBX.on(changeEvent, onInstanceChange);
153                 this.eventSubscriptions.push({name: changeEvent, handler: onInstanceChange});
154 
155                 MBX.on(newEvent, onInstanceCreate);
156                 this.eventSubscriptions.push({name: newEvent, handler: onInstanceCreate});
157 
158                 MBX.on(destroyEvent, onInstanceDestroy);
159                 this.eventSubscriptions.push({name: destroyEvent, handler: onInstanceDestroy});
160 
161                 MBX.on(attributeEvent, onAttributeChange);
162                 this.eventSubscriptions.push({name: attributeEvent, handler: onAttributeChange});
163 
164             }).bind(this));
165         },
166 
167         _unsubscribeToEvents: function () {
168             if (this.eventSubscriptions && this.eventSubscriptions[0]) {
169                 _(this.eventSubscriptions).each(function (obj) {
170                     MBX.removeListener(obj.name, obj.handler);
171                 });
172             }
173         }
174     });
175     
176     /**
177         This is mostly used internally and is fired on MBX everytime a controller is created
178         @memberOf MBX.JsController
179         @name MBX.JsController.Event
180     */
181     publicObj.Event = {
182         newController: "new_controller",
183         afterRender: "render_finished"
184     };
185     
186     /**
187         call extend() to add methods and/or attributes to ALL controllers
188         @param {Object} methsAndAttrs
189         @name MBX.JsController.extend
190         @function
191     */
192     publicObj.extend = function (methsAndAttrs) {
193         methsAndAttrs = methsAndAttrs || {};
194         _(JsController.prototype).extend(methsAndAttrs);
195     };
196 
197         /**
198         Destroy a controller and unsubscribe its event listeners
199         @param {String} name the name of the controller
200         @name MBX.JsController.destroyController
201         @function
202     */
203     publicObj.destroyController = function (name) {
204         if (controllerCache[name]) {
205             controllerCache[name]._unsubscribeToEvents();
206             delete controllerCache[name];
207         }
208     };
209     
210     /**
211         Controllers allow some decently powerful hooks. You can specify a model, and an
212         onInstanceChange, onInstanceDestroy, onInstanceCreate.
213         
214         If your controller listens to a model, but you are not dependent on real-time updates,
215         you can add the option "looselyCoupled: true" and all updates will be done with
216         setTimeout, which will be a performance enhancement.
217           
218         @name MBX.JsController.create
219         @param {String} name the name of the controller
220         @param {Object} opts used to extend the controller methods at instantiation
221         @see JsController
222         @function
223         @example
224           MBX.DesktopUploadController = MBX.JsController.create("DesktopUpload", {
225               looselyCoupled: false, // false is the default
226               ANewMethod: function (something) {
227                   return something;
228               }
229           })
230           MBX.DesktopUpload.ANewMethod("boo") == "boo";
231         @example
232           MBX.DesktopUploadController = MBX.JsController.create("DesktopUpload", {
233               model: MBX.DesktopUpload,
234               onInstanceCreate: function (instance) {
235                   alert(instance.get('greeting'));
236               }              
237           });
238           MBX.DesktopUpload.create({ greeting: 'hi' });  // will alert('hi');
239         @example
240           MBX.DesktopUploadController = MBX.JsController.create("DesktopUpload", {
241               model: MBX.DesktopUpload,
242               onInstanceChange: function (instance) {
243                   alert(instance.get('greeting'));
244               }              
245           });
246           var instance = MBX.DesktopUpload.create();
247           instance.set('greeting', 'hi');  // will alert('hi')
248     */
249     publicObj.create = function (name, opts) {
250         if (controllerCache[name]) {
251             throw new Error("A controller with the name of " + name + " is already in use");
252         }
253         var controller = new JsController(name, opts);
254         if (typeof controller.initialize == "function") {
255             controller.initialize();
256         }
257         return controller;
258     };
259     
260     return publicObj;
261 })();
262