1 // ========================================================================== 2 // Project: The M-Project - Mobile HTML5 Application Framework 3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved. 4 // Creator: Dominik 5 // Date: 26.10.2010 6 // License: Dual licensed under the MIT or GPL Version 2 licenses. 7 // http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE 8 // http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE 9 // ========================================================================== 10 11 m_require('core/foundation/model.js'); 12 13 /** 14 * @class 15 * 16 * M.View defines the prototype for any view within The M-Project. It implements lots of basic 17 * properties and methods that are used in many derived views. M.View specifies a default 18 * behaviour for functionalities like rendering, theming, delegating updates etc. 19 * 20 * @extends M.Object 21 */ 22 M.View = M.Object.extend( 23 /** @scope M.View.prototype */ { 24 25 /** 26 * The type of this object. 27 * 28 * @type String 29 */ 30 type: 'M.View', 31 32 /** 33 * A boolean value to definitely recognize a view as a view, independent on its 34 * concrete type, e.g. M.ButtonView or M.LabelView. 35 * 36 * @type Boolean 37 */ 38 isView: YES, 39 40 /** 41 * The value property is a generic property for all values. Even if not all views 42 * really use it, e.g. the wrapper views like M.ButtonGroupView, most of it do. 43 * 44 * @property {String} 45 */ 46 value: null, 47 48 /** 49 * The path to a content that is bind to the view's value. 50 * 51 * @property {String} 52 */ 53 contentBinding: null, 54 55 /** 56 * An array specifying the view's children. 57 * 58 * @type Array 59 */ 60 childViews: null, 61 62 /** 63 * Indicates whether this view currently has the focus or not. 64 * 65 * @type Boolean 66 */ 67 hasFocus: NO, 68 69 /** 70 * The id of the view used for the html attribute id. Every view gets its own unique 71 * id during the rendering process. 72 */ 73 id: null, 74 75 /** 76 * Indicates whether the view should be displayed inline or not. This property isn't 77 * supported by all views, but e.g. by M.LabelView or M.ButtonView. 78 */ 79 isInline: NO, 80 81 /* 82 * Indicates whether the view is currently enabled or disabled. 83 */ 84 isEnabled: YES, 85 86 /** 87 * This property can be used to save a reference to the view's parent view. 88 * 89 * @param {Object} 90 */ 91 parentView: null, 92 93 /** 94 * If a view represents a model, e.g. within a list view, this property is used to save 95 * the model's id. So the view can be used to get to the record. 96 * 97 * @param {Object} 98 */ 99 modelId: null, 100 101 /** 102 * This property can be used to assign a css class to the view to get a custom styling. 103 * 104 * @type String 105 */ 106 cssClass: null, 107 108 /** 109 * This property can be used to assign a css style to the view. This allows you to 110 * create your custom styles inline. 111 * 112 * @type String 113 */ 114 cssStyle: null, 115 116 /** 117 * This property can be used to assign a css class to the view if an error occurs. The 118 * applying of this class is automatically triggered if the validation of the view 119 * goes wrong. This property is mainly used by input views, e.g. M.TextFieldView. 120 * 121 * @type String 122 */ 123 cssClassOnError: null, 124 125 /** 126 * This property can be used to assign a css class to the view on its initialization. This 127 * property is mainly used for input ui elements like text fields, that might have a initial 128 * value that should be rendered in a different style than the later value entered by the 129 * user. This property is mainly used by input views, e.g. M.TextFieldView. 130 * 131 * @type String 132 */ 133 cssClassOnInit: null, 134 135 /** 136 * This property is used internally to recursively build the pages html representation. 137 * It is once set within the render method and then eventually updated within the 138 * renderUpdate method. 139 * 140 * @type String 141 */ 142 html: '', 143 144 /** 145 * Determines whether an onChange event will trigger a defined action or not. 146 * This property is basically interesting for input ui elements, e.g. for 147 * text fields. 148 * 149 * @type Boolean 150 */ 151 triggerActionOnChange: NO, 152 153 /** 154 * Determines whether an onKeyUp event will trigger a defined action or not. 155 * This property is basically interesting for input ui elements, e.g. for 156 * text fields. 157 * 158 * @type Boolean 159 */ 160 triggerActionOnKeyUp: NO, 161 162 /** 163 * Determines whether an onKeyUp event with the enter button will trigger a defined 164 * action or not. This property is basically interesting for input ui elements, e.g. 165 * for text fields. 166 * 167 * @type Boolean 168 */ 169 triggerActionOnEnter: NO, 170 171 /** 172 * This method encapsulates the 'extend' method of M.Object for better reading of code syntax. 173 * It triggers the content binding for this view, 174 * gets an ID from and registers itself at the ViewManager. 175 * 176 * @param {Object} obj The mixed in object for the extend call. 177 */ 178 design: function(obj) { 179 var view = this.extend(obj); 180 if(view.contentBinding) { 181 view.attachToObservable(view.contentBinding); 182 } else if(view.computedValue && view.computedValue.contentBinding) { 183 view.attachToObservable(view.computedValue.contentBinding); 184 } 185 view.id = M.Application.viewManager.getNextId(); 186 M.Application.viewManager.register(view); 187 return view; 188 }, 189 190 /** 191 * This is the basic render method for any views. It does not specific rendering, it just calls 192 * renderChildViews method. Most views overwrite this method with a custom render behaviour. 193 * 194 * @private 195 * @returns {String} The list item view's html representation. 196 */ 197 render: function() { 198 this.renderChildViews(); 199 return this.html; 200 }, 201 202 /** 203 * @interface 204 * 205 * This method defines an interface method for updating an already rendered html representation 206 * of a view. This should be implemented with a specific behaviour for any view. 207 */ 208 renderUpdate: function() { 209 210 }, 211 212 /** 213 * Triggers render() on all children. This method defines a basic behaviour for rendering a view's 214 * child views. If a custom behaviour for a view is desired, the view has to overwrite this method. 215 * 216 * @private 217 */ 218 renderChildViews: function() { 219 if(this.childViews) { 220 var childViews = $.trim(this.childViews).split(' '); 221 for(var i in childViews) { 222 if(this.type === 'M.PageView' && this[childViews[i]].type === 'M.TabBarView') { 223 this.hasTabBarView = YES; 224 this.tabBarView = this[childViews[i]]; 225 } 226 this.html += this[childViews[i]].render(); 227 } 228 return this.html; 229 } 230 }, 231 232 /** 233 * Clears the html property of a view and triggers the same method on all of its 234 * child views. 235 */ 236 clearHtml: function() { 237 this.html = ''; 238 if(this.childViews) { 239 var childViews = $.trim(this.childViews).split(' '); 240 for(var i in childViews) { 241 this[childViews[i]].clearHtml(); 242 } 243 } 244 }, 245 246 /** 247 * If the view's computedValue property is set, compute the value. This allows you to 248 * apply a method to a dynamically set value. E.g. you can provide your value with an 249 * toUpperCase(). 250 */ 251 computeValue: function() { 252 if(this.computedValue) { 253 this.value = this.computedValue.operation(this.computedValue.valuePattern ? this.value : this.computedValue.value, this); 254 } 255 }, 256 257 /** 258 * This method is a basic implementation for theming a view. It simply calls the 259 * themeChildViews method. Most views overwrite this method with a custom theming 260 * behaviour. 261 */ 262 theme: function() { 263 this.themeChildViews(); 264 }, 265 266 /** 267 * This method triggers the theme method on all children. 268 */ 269 themeChildViews: function() { 270 if(this.childViews) { 271 var childViews = $.trim(this.childViews).split(' '); 272 for(var i in childViews) { 273 this[childViews[i]].theme(); 274 } 275 } 276 }, 277 278 /** 279 * The contentDidChange method is automatically called by the observable when the 280 * observable's state did change. It then updates the view's value property based 281 * on the specified content binding. 282 */ 283 contentDidChange: function(){ 284 var bindingPath = this.contentBinding ? this.contentBinding.split('.') : this.computedValue.contentBinding.split('.'); 285 if(bindingPath && bindingPath.length === 3 && !(this.hasFocus && this.type === 'M.TextFieldView')) { 286 if(this.contentBinding) { 287 this.value = eval(bindingPath[0])[bindingPath[1]][bindingPath[2]]; 288 } else { 289 this.computedValue.value = eval(bindingPath[0])[bindingPath[1]][bindingPath[2]]; 290 } 291 this.renderUpdate(); 292 this.delegateValueUpdate(); 293 } else if(bindingPath && bindingPath.length === 3) { 294 return; 295 } else { 296 M.Logger.log('bindingPath not valid', M.WARN); 297 } 298 }, 299 300 /** 301 * This method attaches the view to an observable to be later notified once the observable's 302 * state did change. 303 * 304 * @param {String} contentBinding The path to the observable property. 305 */ 306 attachToObservable: function(contentBinding) { 307 var bindingPath = contentBinding.split('.'); 308 if(bindingPath && bindingPath.length === 3 && eval(bindingPath[0]) && eval(bindingPath[0])[bindingPath[1]]) { 309 eval(bindingPath[0])[bindingPath[1]].observable.attach(this, bindingPath[2]); 310 this.isObserver = YES; 311 } else { 312 M.Logger.log('bindingPath not valid', M.WARN); 313 } 314 }, 315 316 /** 317 * @interface 318 * 319 * This method defines an interface method for setting the view's value from its DOM 320 * representation. This should be implemented with a specific behaviour for any view. 321 */ 322 setValueFromDOM: function() { 323 324 }, 325 326 /** 327 * This method delegates any value changes to a controller, if the 'contentBindingReverse' 328 * property is specified. 329 */ 330 delegateValueUpdate: function() { 331 /* delegate value updates to a binded controller, 332 but only if the view currently is the master */ 333 if(this.contentBindingReverse && this.hasFocus) { 334 var params = this.contentBindingReverse.split('.'); 335 var controller = eval(params[0]); 336 controller[params[1]].set(params[2], this.value); 337 } 338 }, 339 340 /** 341 * @interface 342 * 343 * This method defines an interface method for styling the view. This should be 344 * implemented with a specific behaviour for any view. 345 */ 346 style: function() { 347 348 }, 349 350 /** 351 * This method is called whenever the view got the focus and basically only sets 352 * the view's hasFocus property to YES. If a more complex behaviour is desired, 353 * a view has to overwrite this method. 354 */ 355 gotFocus: function() { 356 this.hasFocus = YES; 357 }, 358 359 /** 360 * This method is called whenever the view lost the focus and basically only sets 361 * the view's hasFocus property to NO. If a more complex behaviour is desired, 362 * a view has to overwrite this method. 363 */ 364 lostFocus: function() { 365 this.hasFocus = NO; 366 }, 367 368 /** 369 * This method secure the passed string. It is mainly used for securing input elements 370 * like M.TextFieldView but since it is part of M.View it can be used and called out 371 * of any view. 372 * 373 * So far we only replace '<' and '>' with their corresponding html entity. The functionality 374 * of this method will be extended in the future. If a more complex behaviour is desired, 375 * any view using this method has to overwrite it. 376 * 377 * @param {String} str The string to be secured. 378 * @returns {String} The secured string. 379 */ 380 secure: function(str) { 381 return str.replace(/</g, "<").replace(/>/g, ">"); 382 }, 383 384 /** 385 * This method parses a given string, replaces any new line, '\n', with a line break, '<br/>', 386 * and returns the modified string. This can be useful especially for input views, e.g. it is 387 * used in context with the M.TextFieldView. 388 * 389 * @param {String} str The string to be modified. 390 * @returns {String} The modified string. 391 */ 392 nl2br: function(str) { 393 if(str) { 394 if(typeof(str) !== 'string') { 395 str = String(str); 396 } 397 return str.replace(/\n/g, '<br />\n'); 398 } 399 return str; 400 }, 401 402 /** 403 * Adds a css class to the view's DOM representation. 404 * 405 * @param {String} cssClass The css class to be added. 406 */ 407 addCssClass: function(cssClass) { 408 $('#' + this.id).addClass(cssClass); 409 }, 410 411 /** 412 * Removes a css class to the view's DOM representation. 413 * 414 * @param {String} cssClass The css class to be added. 415 */ 416 removeCssClass: function(cssClass) { 417 $('#' + this.id).removeClass(cssClass); 418 }, 419 420 /** 421 * Adds or updates a css property to the view's DOM representation. 422 * 423 * @param {String} key The property's name. 424 * @param {String} value The property's value. 425 */ 426 setCssProperty: function(key, value) { 427 $('#' + this.id).css(key, value); 428 }, 429 430 /** 431 * Removes a css property from the view's DOM representation. 432 * 433 * @param {String} key The property's name. 434 */ 435 removeCssProperty: function(key) { 436 this.setCssProperty(key, ''); 437 } 438 439 });