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