      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 });