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