      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:      03.11.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('ui/search_bar.js');
     12
     13 /**
     14  * @class
     15  *
     16  * M.ListView is the prototype of any list view. It is used to display static or dynamic
     17  * content as vertically aligned list items (M.ListItemView). A list view provides some
     18  * easy to use helper method, e.g. an out-of-the-box delete view for items.
     19  *
     20  * @extends M.View
     21  */
     22 M.ListView = M.View.extend(
     23 /** @scope M.ListView.prototype */ {
     24
     25     /**
     26      * The type of this object.
     27      *
     28      * @type String
     29      */
     30     type: 'M.ListView',
     31
     32     /**
     33      * Determines whether to remove all item if the list is updated or not.
     34      *
     35      * @type Boolean
     36      */
     37     removeItemsOnUpdate: YES,
     38
     39     /**
     40      * Determines whether to display the list as a divided list or not.
     41      *
     42      * @type Boolean
     43      */
     44     isDividedList: NO,
     45
     46     /**
     47      * If the list view is a divided list, this property can be used to customize the style
     48      * of the list's dividers.
     49      *
     50      * @type String
     51      */
     52     cssClassForDivider: null,
     53
     54     /**
     55      * Determines whether to display the the number of child items for each list item view.
     56      *
     57      * @type Boolean
     58      */
     59     isCountedList: NO,
     60
     61     /**
     62      * If the list view is a counted list, this property can be used to customize the style
     63      * of the list item's counter.
     64      *
     65      * @type String
     66      */
     67     cssClassForCounter: null,
     68
     69     /**
     70      * This property can be used to customize the style of the list view's split view. For example
     71      * the toggleRemove() of a list view uses the built-in split view functionality.
     72      *
     73      * @type String
     74      */
     75     cssClassForSplitView: null,
     76
     77     /**
     78      * The list view's items, respectively its child views.
     79      *
     80      * @type Array
     81      */
     82     items: null,
     83
     84     /**
     85      * States whether the list view is currently in edit mode or not. This is mainly used by the
     86      * built-in toggleRemove() functionality.
     87      *
     88      * @type Boolean
     89      */
     90     inEditMode: NO,
     91
     92     /**
     93      * This property contains all available options for the edit mode. For example the target and action
     94      * of the automatically rendered delete button can be specified using this property.
     95      *
     96      * @type Object
     97      */
     98     editOptions: null,
     99
    100     /**
    101      * Defines if the ListView is rendered with prefixed numbering for each item.
    102      *
    103      * @type Boolean
    104      */
    105     isNumberedList: NO,
    106
    107     /**
    108      * This property contains the list view's template view, the blueprint for every child view.
    109      *
    110      * @type M.ListItemView
    111      */
    112     listItemTemplateView: null,
    113
    114     /**
    115      * Determines whether to display the list view 'inset' or at full width.
    116      *
    117      * @type Boolean
    118      */
    119     isInsetList: NO,
    120
    121     /**
    122      * The list view's search bar.
    123      *
    124      * @type Object
    125       */
    126     searchBar: M.SearchBarView,
    127
    128     /**
    129      * Determines whether or not to display a search bar at the top of the list view.
    130      *
    131      * @type Boolean
    132      */
    133     hasSearchBar: NO,
    134
    135     /**
    136      * If the hasSearchBar property is set to YES, this property determines whether to use the built-in
    137      * simple search filters or not. If set to YES, the list is simply filtered on the fly according
    138      * to the entered search string. Only list items matching the entered search string will be visible.
    139      *
    140      * If a custom search behaviour is needed, this property must be set to NO.
    141      *
    142      * @type Boolean
    143      */
    144     usesDefaultSearchBehaviour: YES,
    145
    146     /**
    147      * An object containing target and action to be triggered if the search string changes.
    148      *
    149      * @type Object
    150      */
    151     onSearchStringDidChange: null,
    152
    153     /**
    154      * This method renders the empty list view either as an ordered or as an unordered list. It also applies
    155      * some styling, if the corresponding properties where set.
    156      *
    157      * @private
    158      * @returns {String} The list view's styling as html representation.
    159      */
    160     render: function() {
    161         if(this.hasSearchBar && !this.usesDefaultSearchBehaviour) {
    162             this.searchBar.isListViewSearchBar = YES;
    163             this.searchBar.listView = this;
    164             this.searchBar = M.SearchBarView.design(this.searchBar);
    165             this.html += this.searchBar.render();
    166         }
    167
    168         var listTagName = this.isNumberedList ? 'ol' : 'ul';
    169         this.html += '<' + listTagName + ' id="' + this.id + '" data-role="listview"' + this.style() + '></' + listTagName + '>';
    170
    171         return this.html;
    172     },
    173
    174     /**
    175      * This method adds a new list item to the list view by simply appending its html representation
    176      * to the list view inside the DOM. This method is based on jQuery's append().
    177      *
    178      * @param {String} item The html representation of a list item to be added.
    179      */
    180     addItem: function(item) {
    181         $('#' + this.id).append(item);
    182     },
    183
    184     /**
    185      * This method removes all of the list view's items by removing all of its content in the DOM. This
    186      * method is based on jQuery's empty().
    187      */
    188     removeAllItems: function() {
    189         $('#' + this.id).empty();
    190     },
    191
    192     /**
    193      * Updates the the list view by re-rendering all of its child views, respectively its item views. There
    194      * is no rendering done inside this method itself. It is more like the manager of the rendering process
    195      * and delegates the responsibility to renderListItemDivider() and renderListItemView() based on the
    196      * given list view configuration.
    197      *
    198      * @private
    199      */
    200     renderUpdate: function() {
    201
    202         /* Remove all list items if the removeItemsOnUpdate property is set to YES */
    203         if(this.removeItemsOnUpdate) {
    204             this.removeAllItems();
    205         }
    206
    207         /* Save this in variable that for later use within an other scope (e.g. _each()) */
    208         var that = this;
    209
    210         /* Get the list view's content as an object from the assigned content binding */
    211         var content = eval(this.contentBinding);
    212
    213         /* Get the list view's template view for each list item */
    214         var templateView = this.listItemTemplateView;
    215
    216         /* If there is an items property, re-assign this to content, otherwise iterate through content itself */
    217         if(this.items) {
    218             content = content[this.items];
    219         }
    220
    221         if(this.isDividedList) {
    222             _.each(content, function(items, divider) {
    223                 that.renderListItemDivider(divider);
    224                 that.renderListItemView(items, templateView);
    225             });
    226         } else {
    227             this.renderListItemView(content, templateView);
    228         }
    229
    230         /* Finally let the whole list look nice */
    231         this.themeUpdate();
    232     },
    233
    234     /**
    235      * Renders a list item divider based on a string given by its only parameter.
    236      *
    237      * @param {String} name The name of the list divider to be rendered.
    238      * @private
    239      */
    240     renderListItemDivider: function(name) {
    241         var obj = M.ListItemView.design({});
    242         obj.value = name;
    243         obj.isDivider = YES,
    244         this.addItem(obj.render());
    245         obj.theme();
    246     },
    247
    248     /**
    249      * This method renders list items based on the passed parameters.
    250      *
    251      * @param {Array} content The list items to be rendered.
    252      * @param {M.ListItemView} templateView The template for for each list item.
    253      * @private
    254      */
    255     renderListItemView: function(content, templateView) {
    256         /* Save this in variable that for later use within an other scope (e.g. _each()) */
    257         var that = this;
    258
    259         _.each(content, function(item) {
    260
    261             /* Create a new object for the current template view */
    262             var obj = templateView.design({});
    263
    264             /* If item is a model, assign the model's id to the view's modelId property */
    265             if(item.type === 'M.Model') {
    266                 obj.modelId = item.id;
    267             }
    268
    269             /* Get the child views as an array of strings */
    270             var childViewsArray = obj.childViews.split(' ');
    271
    272             /* If the item is a model, read the values from the 'record' property instead */
    273             var record = item.type === 'M.Model' ? item.record : item;
    274
    275             /* Iterate through all views defined in the template view */
    276             for(var i in childViewsArray) {
    277                 /* Create a new object for the current view */
    278                 obj[childViewsArray[i]] = obj[childViewsArray[i]].design({});
    279
    280                 var regexResult = null;
    281                 if(obj[childViewsArray[i]].computedValue) {
    282                     /* This regex looks for a variable inside the template view (<%= ... %>) ... */
    283                     regexResult = /^<%=\s+([.|_|-|$|�|a-zA-Z]+[0-9]*[.|_|-|$|�|a-zA-Z]*)\s*%>$/.exec(obj[childViewsArray[i]].computedValue.valuePattern);
    284                 } else {
    285                     regexResult = /^<%=\s+([.|_|-|$|�|a-zA-Z]+[0-9]*[.|_|-|$|�|a-zA-Z]*)\s*%>$/.exec(obj[childViewsArray[i]].valuePattern);
    286                 }
    287
    288                 /* ... if a match was found, the variable is replaced by the corresponding value inside the record */
    289                 if(regexResult) {
    290                     switch (obj[childViewsArray[i]].type) {
    291                         case 'M.LabelView':
    292                         case 'M.ButtonView':
    293                         case 'M.ImageView':
    294                             obj[childViewsArray[i]].value = record[regexResult[1]];
    295                             break;
    296                     }
    297                 }
    298             }
    299
    300             /* If edit mode is on, render a delete button */
    301             if(that.inEditMode) {
    302                 obj.inEditMode = that.inEditMode;
    303                 obj.deleteButton = obj.deleteButton.design({
    304                     modelId: item.id,
    305                     target: that.editOptions.target,
    306                     action: that.editOptions.action
    307                 });
    308             }
    309
    310             /* set the list view as 'parent' for the current list item view */
    311             obj.listView = that;
    312
    313             /* Add the current list view item to the list view ... */
    314             that.addItem(obj.render());
    315
    316             /* ... once it is in the DOM, make it look nice */
    317             for(var i in childViewsArray) {
    318                 obj[childViewsArray[i]].theme();
    319             }
    320         });
    321     },
    322
    323     /**
    324      * Triggers the rendering engine, jQuery mobile, to style the list view.
    325      *
    326      * @private
    327      */
    328     theme: function() {
    329         $('#' + this.id).listview();
    330         if(this.searchBar) {
    331             this.searchBar.theme();
    332         }
    333     },
    334
    335     /**
    336      * Triggers the rendering engine, jQuery mobile, to re-style the list view.
    337      *
    338      * @private
    339      */
    340     themeUpdate: function() {
    341         $('#' + this.id).listview('refresh');
    342     },
    343
    344     /**
    345      * This method activates the edit mode and forces the list view to re-render itself
    346      * and to display a remove button for every list view item.
    347      *
    348      * @param {Object} options The options for the remove button.
    349      */
    350     toggleRemove: function(options) {
    351         if(eval(this.contentBinding)) {
    352             this.inEditMode = !this.inEditMode;
    353             this.editOptions = options;
    354             this.renderUpdate();
    355         }
    356     },
    357
    358     /**
    359      * This method activates a list item by applying the default 'isActive' css style to its
    360      * DOM representation.
    361      *
    362      * @param {String} listItemId The id of the list item to be set active.
    363      */
    364     setActiveListItem: function(listItemId) {
    365         $('#' + this.id).find('li').each(function() {
    366             var listItem = M.ViewManager.getViewById($(this).attr('id'));
    367             listItem.removeCssClass('ui-btn-active');
    368         });
    369         M.ViewManager.getViewById(listItemId).addCssClass('ui-btn-active')
    370     },
    371
    372     /**
    373      * Applies some style-attributes to the list view.
    374      *
    375      * @private
    376      * @returns {String} The list's styling as html representation.
    377      */
    378     style: function() {
    379         var html = '';
    380         if(this.isDividedList && this.cssClassForDivider) {
    381             html += ' data-dividertheme="' + this.cssClassForDivider + '"';
    382         }
    383         if(this.isInsetList) {
    384             html += ' data-inset="true"';
    385         }
    386         if(this.isCountedList && this.cssClassForCounter) {
    387             html += ' data-counttheme="' + this.cssClassForCounter + '"';
    388         }
    389         if(this.cssClassForSplitView) {
    390             html += ' data-splittheme="' + this.cssClassForSplitView + '"';
    391         }
    392         if(this.hasSearchBar && this.usesDefaultSearchBehaviour) {
    393             html += ' data-filter="true"';
    394         }
    395         return html;
    396     }
    397
    398 });