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