1 if (!("MBX" in window)) { 2 /** @namespace 3 @ignore 4 */ 5 MBX = {}; 6 } 7 8 /** this is the view part of the MVC framework 9 handle updates and creations and deletes of registered 10 objects and classes 11 @namespace 12 */ 13 MBX.JsView = (function () { 14 /** 15 @memberof MBX.JsView 16 @namespace 17 */ 18 var self = {}; 19 20 var jsElementClass = '.js_updateable'; 21 22 /** 23 Grab give a model - get a css... pass in "." as the second arg to give it css-style return 24 @param {JsModel} model the model you want the css for 25 @param {String} prePend any string you want prepended to the return (to avoid "." + MBX.JsView.modelCSS(model)) 26 @returns {String} a css class for a model 27 @name MBX.JsView.modelCSS 28 @function 29 */ 30 self.modelCSS = function (model, prePend) { 31 prePend = prePend || ""; 32 return prePend + model.modelName.toLowerCase(); 33 }; 34 35 /** given an instance of a model, give the recommended instance class 36 @param {JsModel#instance} instance the JsModel instance 37 @param {String} prePend any string you want prepended to the return (to avoid "." + MBX.JsView.modelCSS(model)) 38 @returns {String} a long-ish css class for a specific instance 39 @name MBX.JsView.cssForInstance 40 @function 41 */ 42 self.cssForInstance = function (instance, prePend) { 43 prePend = prePend || ""; 44 return prePend + self.modelCSS(instance.parentClass) + "_" + instance.primaryKey().toLowerCase().gsub(/[^\w\-]/, "_"); 45 }; 46 47 /** @class A single view instance 48 @constructor 49 @name JsView 50 @example 51 MBX.JsView.create({ 52 model: MBX.DesktopUpload, 53 onCreate: function (upload) { 54 //create the upload 55 }, 56 onChange: function (upload) { 57 // any upload changes 58 }, 59 onDestroy: function (upload) { 60 // handle destroys 61 } 62 }); 63 */ 64 var View = function (opts) { 65 opts = opts || {}; 66 Object.extend(this, opts); 67 68 if (this.model) { 69 this._subscribeToEvents(); 70 } 71 72 if (typeof this.initialize == 'function') { 73 this.initialize(); 74 } 75 }; 76 77 View.prototype = /** @lends JsView */{ 78 active: true, 79 80 /** reactivate event listening on this view 81 */ 82 activate: function () { 83 this.active = true; 84 }, 85 86 /** quiets all events on this view, your callbacks will 87 not get called 88 */ 89 deactivate: function () { 90 this.active = false; 91 }, 92 /** helper function to spit out divs 93 @param {String} className the classes you want to add to the div 94 @param {Object} opts (optional) any opts to pass to new Element 95 @returns div DOM element 96 */ 97 div: function (className, opts) { 98 opts = opts || {}; 99 Object.extend(opts, { className: className }); 100 return new Element('div', opts); 101 }, 102 103 /** helper function to spit out imageTag 104 @param {String} src the url for the image 105 @param {Object} opts (optional) any opts to pass to new Element 106 @returns img DOM element 107 */ 108 imageTag: function (src, opts) { 109 opts = opts || {}; 110 Object.extend(opts, { src: src }); 111 return new Element("img", opts); 112 }, 113 114 /** Anytime an instance changes on the model you are observing, 115 JsView will fire a function with the object and the key 116 @params {JsModel#instance} object the instance that changed 117 @params {String} key the key that changed 118 */ 119 _onInstanceChange: function (evt) { 120 if (this.active && typeof this.onInstanceChange == 'function') { 121 this.onInstanceChange(evt.object, evt.key); 122 } 123 }, 124 125 _onInstanceCreate: function (evt) { 126 if (this.active && typeof this.onInstanceCreate == 'function') { 127 this.onInstanceCreate(evt.object); 128 } 129 }, 130 131 _onInstanceDestroy: function (evt) { 132 if (this.active && typeof this.onInstanceDestroy == 'function') { 133 this.onInstanceDestroy(evt.object); 134 } 135 }, 136 137 _onAttributeChange: function (evt) { 138 if (this.active && typeof this.onAttributeChange == 'function') { 139 this.onAttributeChange(evt.key); 140 } 141 }, 142 143 /** subscribe to change, create, destroy events 144 We assume you don't need your UI elements to update at the expense of user interaction 145 hence the defer: true 146 */ 147 _subscribeToEvents: function () { 148 var changeEvent = this.model.Event.changeInstance; 149 var newEvent = this.model.Event.newInstance; 150 var destroyEvent = this.model.Event.destroyInstance; 151 var attributeEvent = this.model.Event.changeAttribute; 152 var defer = this.looselyCoupled; 153 154 this.eventSubscriptions = []; 155 this.eventSubscriptions.push(MBX.EventHandler.subscribe(MBX, changeEvent, this._onInstanceChange.bind(this), {defer: defer})); 156 this.eventSubscriptions.push(MBX.EventHandler.subscribe(MBX, newEvent, this._onInstanceCreate.bind(this), {defer: defer})); 157 this.eventSubscriptions.push(MBX.EventHandler.subscribe(MBX, destroyEvent, this._onInstanceDestroy.bind(this), {defer: defer})); 158 this.eventSubscriptions.push(MBX.EventHandler.subscribe(MBX, attributeEvent, this._onAttributeChange.bind(this), {defer: defer})); 159 }, 160 161 /** return the CSS class for the model associated with this view 162 @param {String} prePend any string to prepend to the model 163 @see MBX.JsView.modelCSS 164 */ 165 modelCSS: function (prePend) { 166 return MBX.JsView.modelCSS(this.model, prePend); 167 }, 168 169 /** given an instance of a model, give the recommended instance class 170 @see MBX.JsView.cssForInstance 171 */ 172 cssForInstance: function (instance, prePend) { 173 return MBX.JsView.cssForInstance(instance, prePend); 174 }, 175 176 /** given an instance, return the relevent elements 177 @param {JsModel#instance} instance the instance you want to find elements 178 @returns {Array} an array of extended dom elements that match the relevent class names 179 */ 180 elementsFromInstance: function (instance) { 181 return $$(this.modelCSS(".") + " " + cssForInstance(".")); 182 }, 183 184 /** return all the various collections of dom elements that have the appropriate classes 185 @returns {Array} array of extended dom elements that match collection CSS 186 */ 187 domCollections: function () { 188 var findString = [jsElementClass, this.modelCSS("."), '.collection'].join(''); 189 return $$(findString); 190 } 191 192 }; 193 194 /** create a new view handler... specify a model and some 195 functions and some great magic happens. 196 197 If your view listens to a model, but you are not dependent on real-time updates, 198 you can add the option "looselyCoupled: true" and all updates will be done with 199 setTimeout, which will be a performance enhancement. 200 201 @name MBX.JsView.create 202 @function 203 @param {Object} opts the various options specified for a view 204 @example 205 MBX.JsView.create({ 206 model: MBX.DesktopUpload, 207 looselyCoupled: false, // false is the default 208 onCreate: function (upload) { 209 //create the upload 210 }, 211 onChange: function (upload) { 212 // any upload changes 213 }, 214 onDestroy: function (upload) { 215 // handle destroys 216 } 217 }); 218 */ 219 self.create = function (opts) { 220 return new View(opts); 221 }; 222 223 /** 224 call extend() to add methods and/or attributes to ALL views 225 @param {Object} methsAndAttrs 226 @name MBX.JsView.extend 227 @function 228 */ 229 self.extend = function (methsAndAttrs) { 230 methsAndAttrs = methsAndAttrs || {}; 231 Object.extend(View.prototype, methsAndAttrs); 232 }; 233 234 /** Added to prototype elements when a view is created 235 allows you to observe particular keys of a model object 236 @name MBX.JsView.updatesOn 237 @function 238 @param {JsModel#instance} obj the model instance to watch 239 @param {String} key the key to watch 240 @param {Object} opts handler or preProcess functions to execute 241 @returns {DOM element} the dom element that was called (for chaining) 242 */ 243 self.updatesOn = function (element, obj, key, opts) { 244 if (!obj || !key) { 245 throw new Error("You must specify a key or an object with updatesOn"); 246 } 247 element = $(element); 248 opts = opts || {}; 249 250 var changeHandler = function (evt) { 251 var content = obj.get(key); 252 if (opts.preProcess) { 253 content = opts.preProcess(obj, content, key); 254 } 255 if (opts.handler) { 256 opts.handler(obj, element, content); 257 } else { 258 element.update(content); 259 } 260 }; 261 262 var sub = MBX.EventHandler.subscribe(obj, key + "_changed", changeHandler); 263 264 element.__JsViewSubscriptions = element.__JsViewSubscriptions || []; 265 element.__JsViewSubscriptions.push(sub); 266 267 obj.__JsViewSubscriptions = obj.__JsViewSubscriptions || []; 268 obj.__JsViewSubscriptions.push(sub); 269 270 if (opts.updateNow) { 271 changeHandler(); 272 } 273 274 return element; 275 }; 276 277 /** stop updating this instance, all event handlers are removed 278 Preferred method of accessing this function is through extended elements 279 @name MBX.JsView.stopUpdating 280 @function 281 @param {DOM Element} element the element to stop observing - this will usually be filled for you by prototype 282 @returns {DOM Element} element for chaining 283 @example 284 $("el").stopUpdating(); 285 */ 286 self.stopUpdating = function (element) { 287 element = $(element); 288 if (element.__JsViewSubscriptions) { 289 while (element.__JsViewSubscriptions.length > 0) { 290 MBX.EventHandler.unsubscribe(element.__JsViewSubscriptions.pop()); 291 } 292 } 293 return element; 294 }; 295 296 /** attach a JsModel#instance to this DOM element 297 Preferred method of accessing this function is through extended elements 298 @name MBX.JsView.assignInstance 299 @function 300 @example 301 $("el").assignInstance(instance); 302 $("el").getInstance(); // == instance 303 */ 304 self.assignInstance = function (element, instance) { 305 element = $(element); 306 element.__JsViewMvcInstance = instance; 307 element.addClassName(MBX.JsView.modelCSS(instance.parentClass)); 308 element.addClassName(MBX.JsView.cssForInstance(instance)); 309 return element; 310 }; 311 312 /** fetch a JsModel#instance from this DOM element 313 Preferred method of accessing this function is through extended elements 314 @name MBX.JsView.getInstance 315 @function 316 @example 317 $("el").assignInstance(instance); 318 $("el").getInstance(); // == instance 319 */ 320 self.getInstance = function (element, instance) { 321 return element.__JsViewMvcInstance; 322 }; 323 324 Element.addMethods({ 325 updatesOn: self.updatesOn, 326 stopUpdating: self.stopUpdating, 327 assignInstance: self.assignInstance, 328 getInstance: self.getInstance 329 }); 330 331 return self; 332 })(); 333