[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('datatable-body', function (Y, NAME) { 9 10 /** 11 View class responsible for rendering the `<tbody>` section of a table. Used as 12 the default `bodyView` for `Y.DataTable.Base` and `Y.DataTable` classes. 13 14 @module datatable 15 @submodule datatable-body 16 @since 3.5.0 17 **/ 18 var Lang = Y.Lang, 19 isArray = Lang.isArray, 20 isNumber = Lang.isNumber, 21 isString = Lang.isString, 22 fromTemplate = Lang.sub, 23 htmlEscape = Y.Escape.html, 24 toArray = Y.Array, 25 bind = Y.bind, 26 YObject = Y.Object, 27 valueRegExp = /\{value\}/g, 28 EV_CONTENT_UPDATE = 'contentUpdate', 29 30 shiftMap = { 31 above: [-1, 0], 32 below: [1, 0], 33 next: [0, 1], 34 prev: [0, -1], 35 previous: [0, -1] 36 }; 37 38 /** 39 View class responsible for rendering the `<tbody>` section of a table. Used as 40 the default `bodyView` for `Y.DataTable.Base` and `Y.DataTable` classes. 41 42 Translates the provided `modelList` into a rendered `<tbody>` based on the data 43 in the constituent Models, altered or amended by any special column 44 configurations. 45 46 The `columns` configuration, passed to the constructor, determines which 47 columns will be rendered. 48 49 The rendering process involves constructing an HTML template for a complete row 50 of data, built by concatenating a customized copy of the instance's 51 `CELL_TEMPLATE` into the `ROW_TEMPLATE` once for each column. This template is 52 then populated with values from each Model in the `modelList`, aggregating a 53 complete HTML string of all row and column data. A `<tbody>` Node is then created from the markup and any column `nodeFormatter`s are applied. 54 55 Supported properties of the column objects include: 56 57 * `key` - Used to link a column to an attribute in a Model. 58 * `name` - Used for columns that don't relate to an attribute in the Model 59 (`formatter` or `nodeFormatter` only) if the implementer wants a 60 predictable name to refer to in their CSS. 61 * `cellTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in this 62 column only. 63 * `formatter` - Used to customize or override the content value from the 64 Model. These do not have access to the cell or row Nodes and should 65 return string (HTML) content. 66 * `nodeFormatter` - Used to provide content for a cell as well as perform any 67 custom modifications on the cell or row Node that could not be performed by 68 `formatter`s. Should be used sparingly for better performance. 69 * `emptyCellValue` - String (HTML) value to use if the Model data for a 70 column, or the content generated by a `formatter`, is the empty string, 71 `null`, or `undefined`. 72 * `allowHTML` - Set to `true` if a column value, `formatter`, or 73 `emptyCellValue` can contain HTML. This defaults to `false` to protect 74 against XSS. 75 * `className` - Space delimited CSS classes to add to all `<td>`s in a column. 76 77 A column `formatter` can be: 78 79 * a function, as described below. 80 * a string which can be: 81 * the name of a pre-defined formatter function 82 which can be located in the `Y.DataTable.BodyView.Formatters` hash using the 83 value of the `formatter` property as the index. 84 * A template that can use the `{value}` placeholder to include the value 85 for the current cell or the name of any field in the underlaying model 86 also enclosed in curly braces. Any number and type of these placeholders 87 can be used. 88 89 Column `formatter`s are passed an object (`o`) with the following properties: 90 91 * `value` - The current value of the column's associated attribute, if any. 92 * `data` - An object map of Model keys to their current values. 93 * `record` - The Model instance. 94 * `column` - The column configuration object for the current column. 95 * `className` - Initially empty string to allow `formatter`s to add CSS 96 classes to the cell's `<td>`. 97 * `rowIndex` - The zero-based row number. 98 * `rowClass` - Initially empty string to allow `formatter`s to add CSS 99 classes to the cell's containing row `<tr>`. 100 101 They may return a value or update `o.value` to assign specific HTML content. A 102 returned value has higher precedence. 103 104 Column `nodeFormatter`s are passed an object (`o`) with the following 105 properties: 106 107 * `value` - The current value of the column's associated attribute, if any. 108 * `td` - The `<td>` Node instance. 109 * `cell` - The `<div>` liner Node instance if present, otherwise, the `<td>`. 110 When adding content to the cell, prefer appending into this property. 111 * `data` - An object map of Model keys to their current values. 112 * `record` - The Model instance. 113 * `column` - The column configuration object for the current column. 114 * `rowIndex` - The zero-based row number. 115 116 They are expected to inject content into the cell's Node directly, including 117 any "empty" cell content. Each `nodeFormatter` will have access through the 118 Node API to all cells and rows in the `<tbody>`, but not to the `<table>`, as 119 it will not be attached yet. 120 121 If a `nodeFormatter` returns `false`, the `o.td` and `o.cell` Nodes will be 122 `destroy()`ed to remove them from the Node cache and free up memory. The DOM 123 elements will remain as will any content added to them. _It is highly 124 advisable to always return `false` from your `nodeFormatter`s_. 125 126 @class BodyView 127 @namespace DataTable 128 @extends View 129 @since 3.5.0 130 **/ 131 Y.namespace('DataTable').BodyView = Y.Base.create('tableBody', Y.View, [], { 132 // -- Instance properties ------------------------------------------------- 133 134 /** 135 HTML template used to create table cells. 136 137 @property CELL_TEMPLATE 138 @type {String} 139 @default '<td {headers} class="{className}">{content}</td>' 140 @since 3.5.0 141 **/ 142 CELL_TEMPLATE: '<td {headers} class="{className}">{content}</td>', 143 144 /** 145 CSS class applied to even rows. This is assigned at instantiation. 146 147 For DataTable, this will be `yui3-datatable-even`. 148 149 @property CLASS_EVEN 150 @type {String} 151 @default 'yui3-table-even' 152 @since 3.5.0 153 **/ 154 //CLASS_EVEN: null 155 156 /** 157 CSS class applied to odd rows. This is assigned at instantiation. 158 159 When used by DataTable instances, this will be `yui3-datatable-odd`. 160 161 @property CLASS_ODD 162 @type {String} 163 @default 'yui3-table-odd' 164 @since 3.5.0 165 **/ 166 //CLASS_ODD: null 167 168 /** 169 HTML template used to create table rows. 170 171 @property ROW_TEMPLATE 172 @type {String} 173 @default '<tr id="{rowId}" data-yui3-record="{clientId}" class="{rowClass}">{content}</tr>' 174 @since 3.5.0 175 **/ 176 ROW_TEMPLATE : '<tr id="{rowId}" data-yui3-record="{clientId}" class="{rowClass}">{content}</tr>', 177 178 /** 179 The object that serves as the source of truth for column and row data. 180 This property is assigned at instantiation from the `host` property of 181 the configuration object passed to the constructor. 182 183 @property host 184 @type {Object} 185 @default (initially unset) 186 @since 3.5.0 187 **/ 188 //TODO: should this be protected? 189 //host: null, 190 191 /** 192 HTML templates used to create the `<tbody>` containing the table rows. 193 194 @property TBODY_TEMPLATE 195 @type {String} 196 @default '<tbody class="{className}">{content}</tbody>' 197 @since 3.6.0 198 **/ 199 TBODY_TEMPLATE: '<tbody class="{className}"></tbody>', 200 201 // -- Public methods ------------------------------------------------------ 202 203 /** 204 Returns the `<td>` Node from the given row and column index. Alternately, 205 the `seed` can be a Node. If so, the nearest ancestor cell is returned. 206 If the `seed` is a cell, it is returned. If there is no cell at the given 207 coordinates, `null` is returned. 208 209 Optionally, include an offset array or string to return a cell near the 210 cell identified by the `seed`. The offset can be an array containing the 211 number of rows to shift followed by the number of columns to shift, or one 212 of "above", "below", "next", or "previous". 213 214 <pre><code>// Previous cell in the previous row 215 var cell = table.getCell(e.target, [-1, -1]); 216 217 // Next cell 218 var cell = table.getCell(e.target, 'next'); 219 var cell = table.getCell(e.target, [0, 1];</pre></code> 220 221 @method getCell 222 @param {Number[]|Node} seed Array of row and column indexes, or a Node that 223 is either the cell itself or a descendant of one. 224 @param {Number[]|String} [shift] Offset by which to identify the returned 225 cell Node 226 @return {Node} 227 @since 3.5.0 228 **/ 229 getCell: function (seed, shift) { 230 var tbody = this.tbodyNode, 231 row, cell, index, rowIndexOffset; 232 233 if (seed && tbody) { 234 if (isArray(seed)) { 235 row = tbody.get('children').item(seed[0]); 236 cell = row && row.get('children').item(seed[1]); 237 } else if (seed._node) { 238 cell = seed.ancestor('.' + this.getClassName('cell'), true); 239 } 240 241 if (cell && shift) { 242 rowIndexOffset = tbody.get('firstChild.rowIndex'); 243 if (isString(shift)) { 244 if (!shiftMap[shift]) { 245 Y.error('Unrecognized shift: ' + shift, null, 'datatable-body'); 246 } 247 shift = shiftMap[shift]; 248 } 249 250 if (isArray(shift)) { 251 index = cell.get('parentNode.rowIndex') + 252 shift[0] - rowIndexOffset; 253 row = tbody.get('children').item(index); 254 255 index = cell.get('cellIndex') + shift[1]; 256 cell = row && row.get('children').item(index); 257 } 258 } 259 } 260 261 return cell || null; 262 }, 263 264 /** 265 Returns the generated CSS classname based on the input. If the `host` 266 attribute is configured, it will attempt to relay to its `getClassName` 267 or use its static `NAME` property as a string base. 268 269 If `host` is absent or has neither method nor `NAME`, a CSS classname 270 will be generated using this class's `NAME`. 271 272 @method getClassName 273 @param {String} token* Any number of token strings to assemble the 274 classname from. 275 @return {String} 276 @protected 277 @since 3.5.0 278 **/ 279 getClassName: function () { 280 var host = this.host, 281 args; 282 283 if (host && host.getClassName) { 284 return host.getClassName.apply(host, arguments); 285 } else { 286 args = toArray(arguments); 287 args.unshift(this.constructor.NAME); 288 return Y.ClassNameManager.getClassName 289 .apply(Y.ClassNameManager, args); 290 } 291 }, 292 293 /** 294 Returns the Model associated to the row Node or id provided. Passing the 295 Node or id for a descendant of the row also works. 296 297 If no Model can be found, `null` is returned. 298 299 @method getRecord 300 @param {String|Node} seed Row Node or `id`, or one for a descendant of a row 301 @return {Model} 302 @since 3.5.0 303 **/ 304 getRecord: function (seed) { 305 var modelList = this.get('modelList'), 306 tbody = this.tbodyNode, 307 row = null, 308 record; 309 310 if (tbody) { 311 if (isString(seed)) { 312 seed = tbody.one('#' + seed); 313 } 314 315 if (seed && seed._node) { 316 row = seed.ancestor(function (node) { 317 return node.get('parentNode').compareTo(tbody); 318 }, true); 319 320 record = row && 321 modelList.getByClientId(row.getData('yui3-record')); 322 } 323 } 324 325 return record || null; 326 }, 327 328 /** 329 Returns the `<tr>` Node from the given row index, Model, or Model's 330 `clientId`. If the rows haven't been rendered yet, or if the row can't be 331 found by the input, `null` is returned. 332 333 @method getRow 334 @param {Number|String|Model} id Row index, Model instance, or clientId 335 @return {Node} 336 @since 3.5.0 337 **/ 338 getRow: function (id) { 339 var tbody = this.tbodyNode, 340 row = null; 341 342 if (tbody) { 343 if (id) { 344 id = this._idMap[id.get ? id.get('clientId') : id] || id; 345 } 346 347 row = isNumber(id) ? 348 tbody.get('children').item(id) : 349 tbody.one('#' + id); 350 } 351 352 return row; 353 }, 354 355 /** 356 Creates the table's `<tbody>` content by assembling markup generated by 357 populating the `ROW\_TEMPLATE`, and `CELL\_TEMPLATE` templates with content 358 from the `columns` and `modelList` attributes. 359 360 The rendering process happens in three stages: 361 362 1. A row template is assembled from the `columns` attribute (see 363 `_createRowTemplate`) 364 365 2. An HTML string is built up by concatenating the application of the data in 366 each Model in the `modelList` to the row template. For cells with 367 `formatter`s, the function is called to generate cell content. Cells 368 with `nodeFormatter`s are ignored. For all other cells, the data value 369 from the Model attribute for the given column key is used. The 370 accumulated row markup is then inserted into the container. 371 372 3. If any column is configured with a `nodeFormatter`, the `modelList` is 373 iterated again to apply the `nodeFormatter`s. 374 375 Supported properties of the column objects include: 376 377 * `key` - Used to link a column to an attribute in a Model. 378 * `name` - Used for columns that don't relate to an attribute in the Model 379 (`formatter` or `nodeFormatter` only) if the implementer wants a 380 predictable name to refer to in their CSS. 381 * `cellTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in 382 this column only. 383 * `formatter` - Used to customize or override the content value from the 384 Model. These do not have access to the cell or row Nodes and should 385 return string (HTML) content. 386 * `nodeFormatter` - Used to provide content for a cell as well as perform 387 any custom modifications on the cell or row Node that could not be 388 performed by `formatter`s. Should be used sparingly for better 389 performance. 390 * `emptyCellValue` - String (HTML) value to use if the Model data for a 391 column, or the content generated by a `formatter`, is the empty string, 392 `null`, or `undefined`. 393 * `allowHTML` - Set to `true` if a column value, `formatter`, or 394 `emptyCellValue` can contain HTML. This defaults to `false` to protect 395 against XSS. 396 * `className` - Space delimited CSS classes to add to all `<td>`s in a 397 column. 398 399 Column `formatter`s are passed an object (`o`) with the following 400 properties: 401 402 * `value` - The current value of the column's associated attribute, if 403 any. 404 * `data` - An object map of Model keys to their current values. 405 * `record` - The Model instance. 406 * `column` - The column configuration object for the current column. 407 * `className` - Initially empty string to allow `formatter`s to add CSS 408 classes to the cell's `<td>`. 409 * `rowIndex` - The zero-based row number. 410 * `rowClass` - Initially empty string to allow `formatter`s to add CSS 411 classes to the cell's containing row `<tr>`. 412 413 They may return a value or update `o.value` to assign specific HTML 414 content. A returned value has higher precedence. 415 416 Column `nodeFormatter`s are passed an object (`o`) with the following 417 properties: 418 419 * `value` - The current value of the column's associated attribute, if 420 any. 421 * `td` - The `<td>` Node instance. 422 * `cell` - The `<div>` liner Node instance if present, otherwise, the 423 `<td>`. When adding content to the cell, prefer appending into this 424 property. 425 * `data` - An object map of Model keys to their current values. 426 * `record` - The Model instance. 427 * `column` - The column configuration object for the current column. 428 * `rowIndex` - The zero-based row number. 429 430 They are expected to inject content into the cell's Node directly, including 431 any "empty" cell content. Each `nodeFormatter` will have access through the 432 Node API to all cells and rows in the `<tbody>`, but not to the `<table>`, 433 as it will not be attached yet. 434 435 If a `nodeFormatter` returns `false`, the `o.td` and `o.cell` Nodes will be 436 `destroy()`ed to remove them from the Node cache and free up memory. The 437 DOM elements will remain as will any content added to them. _It is highly 438 advisable to always return `false` from your `nodeFormatter`s_. 439 440 @method render 441 @chainable 442 @since 3.5.0 443 **/ 444 render: function () { 445 var table = this.get('container'), 446 data = this.get('modelList'), 447 displayCols = this.get('columns'), 448 tbody = this.tbodyNode || 449 (this.tbodyNode = this._createTBodyNode()); 450 451 // Needed for mutation 452 this._createRowTemplate(displayCols); 453 454 if (data) { 455 tbody.setHTML(this._createDataHTML(displayCols)); 456 457 this._applyNodeFormatters(tbody, displayCols); 458 } 459 460 if (tbody.get('parentNode') !== table) { 461 table.appendChild(tbody); 462 } 463 464 this.bindUI(); 465 466 return this; 467 }, 468 469 /** 470 Refreshes the provided row against the provided model and the Array of 471 columns to be updated. 472 473 @method refreshRow 474 @param {Node} row 475 @param {Model} model Y.Model representation of the row 476 @param {String[]} colKeys Array of column keys 477 478 @chainable 479 */ 480 refreshRow: function (row, model, colKeys) { 481 var col, 482 cell, 483 len = colKeys.length, 484 i; 485 486 for (i = 0; i < len; i++) { 487 col = this.getColumn(colKeys[i]); 488 489 if (col !== null) { 490 cell = row.one('.' + this.getClassName('col', col._id || col.key)); 491 this.refreshCell(cell, model); 492 } 493 } 494 495 return this; 496 }, 497 498 /** 499 Refreshes the given cell with the provided model data and the provided 500 column configuration. 501 502 Uses the provided column formatter if aviable. 503 504 @method refreshCell 505 @param {Node} cell Y.Node pointer to the cell element to be updated 506 @param {Model} [model] Y.Model representation of the row 507 @param {Object} [col] Column configuration object for the cell 508 509 @chainable 510 */ 511 refreshCell: function (cell, model, col) { 512 var content, 513 formatterFn, 514 formatterData, 515 data = model.toJSON(); 516 517 cell = this.getCell(cell); 518 /* jshint -W030 */ 519 model || (model = this.getRecord(cell)); 520 col || (col = this.getColumn(cell)); 521 /* jshint +W030 */ 522 523 if (col.nodeFormatter) { 524 formatterData = { 525 cell: cell.one('.' + this.getClassName('liner')) || cell, 526 column: col, 527 data: data, 528 record: model, 529 rowIndex: this._getRowIndex(cell.ancestor('tr')), 530 td: cell, 531 value: data[col.key] 532 }; 533 534 keep = col.nodeFormatter.call(host,formatterData); 535 536 if (keep === false) { 537 // Remove from the Node cache to reduce 538 // memory footprint. This also purges events, 539 // which you shouldn't be scoping to a cell 540 // anyway. You've been warned. Incidentally, 541 // you should always return false. Just sayin. 542 cell.destroy(true); 543 } 544 545 } else if (col.formatter) { 546 if (!col._formatterFn) { 547 col = this._setColumnsFormatterFn([col])[0]; 548 } 549 550 formatterFn = col._formatterFn || null; 551 552 if (formatterFn) { 553 formatterData = { 554 value : data[col.key], 555 data : data, 556 column : col, 557 record : model, 558 className: '', 559 rowClass : '', 560 rowIndex : this._getRowIndex(cell.ancestor('tr')) 561 }; 562 563 // Formatters can either return a value ... 564 content = formatterFn.call(this.get('host'), formatterData); 565 566 // ... or update the value property of the data obj passed 567 if (content === undefined) { 568 content = formatterData.value; 569 } 570 } 571 572 if (content === undefined || content === null || content === '') { 573 content = col.emptyCellValue || ''; 574 } 575 576 } else { 577 content = data[col.key] || col.emptyCellValue || ''; 578 } 579 580 cell.setHTML(col.allowHTML ? content : Y.Escape.html(content)); 581 582 return this; 583 }, 584 585 /** 586 Returns column data from this.get('columns'). If a Y.Node is provided as 587 the key, will try to determine the key from the classname 588 @method getColumn 589 @param {String|Node} name 590 @return {Object} Returns column configuration 591 */ 592 getColumn: function (name) { 593 if (name && name._node) { 594 // get column name from node 595 name = name.get('className').match( 596 new RegExp( this.getClassName('col') +'-([^ ]*)' ) 597 )[1]; 598 } 599 600 if (this.host) { 601 return this.host._columnMap[name] || null; 602 } 603 var displayCols = this.get('columns'), 604 col = null; 605 606 Y.Array.some(displayCols, function (_col) { 607 if ((_col._id || _col.key) === name) { 608 col = _col; 609 return true; 610 } 611 }); 612 613 return col; 614 }, 615 616 // -- Protected and private methods --------------------------------------- 617 /** 618 Handles changes in the source's columns attribute. Redraws the table data. 619 620 @method _afterColumnsChange 621 @param {EventFacade} e The `columnsChange` event object 622 @protected 623 @since 3.5.0 624 **/ 625 // TODO: Preserve existing DOM 626 // This will involve parsing and comparing the old and new column configs 627 // and reacting to four types of changes: 628 // 1. formatter, nodeFormatter, emptyCellValue changes 629 // 2. column deletions 630 // 3. column additions 631 // 4. column moves (preserve cells) 632 _afterColumnsChange: function () { 633 this.render(); 634 }, 635 636 /** 637 Handles modelList changes, including additions, deletions, and updates. 638 639 Modifies the existing table DOM accordingly. 640 641 @method _afterDataChange 642 @param {EventFacade} e The `change` event from the ModelList 643 @protected 644 @since 3.5.0 645 **/ 646 _afterDataChange: function (e) { 647 var type = (e.type.match(/:(add|change|remove)$/) || [])[1], 648 index = e.index, 649 displayCols = this.get('columns'), 650 col, 651 changed = e.changed && Y.Object.keys(e.changed), 652 key, 653 row, 654 i, 655 len; 656 657 for (i = 0, len = displayCols.length; i < len; i++ ) { 658 col = displayCols[i]; 659 660 // since nodeFormatters typcially make changes outside of it's 661 // cell, we need to see if there are any columns that have a 662 // nodeFormatter and if so, we need to do a full render() of the 663 // tbody 664 if (col.hasOwnProperty('nodeFormatter')) { 665 this.render(); 666 this.fire(EV_CONTENT_UPDATE); 667 return; 668 } 669 } 670 671 // TODO: if multiple rows are being added/remove/swapped, can we avoid the restriping? 672 switch (type) { 673 case 'change': 674 for (i = 0, len = displayCols.length; i < len; i++) { 675 col = displayCols[i]; 676 key = col.key; 677 if (col.formatter && !e.changed[key]) { 678 changed.push(key); 679 } 680 } 681 this.refreshRow(this.getRow(e.target), e.target, changed); 682 break; 683 case 'add': 684 // we need to make sure we don't have an index larger than the data we have 685 index = Math.min(index, this.get('modelList').size() - 1); 686 687 // updates the columns with formatter functions 688 this._setColumnsFormatterFn(displayCols); 689 row = Y.Node.create(this._createRowHTML(e.model, index, displayCols)); 690 this.tbodyNode.insert(row, index); 691 this._restripe(index); 692 break; 693 case 'remove': 694 this.getRow(index).remove(true); 695 // we removed a row, so we need to back up our index to stripe 696 this._restripe(index - 1); 697 break; 698 default: 699 this.render(); 700 } 701 702 // Event fired to tell users when we are done updating after the data 703 // was changed 704 this.fire(EV_CONTENT_UPDATE); 705 }, 706 707 /** 708 Toggles the odd/even classname of the row after the given index. This method 709 is used to update rows after a row is inserted into or removed from the table. 710 Note this event is delayed so the table is only restriped once when multiple 711 rows are updated at one time. 712 713 @protected 714 @method _restripe 715 @param {Number} [index] Index of row to start restriping after 716 @since 3.11.0 717 */ 718 _restripe: function (index) { 719 var task = this._restripeTask, 720 self; 721 722 // index|0 to force int, avoid NaN. Math.max() to avoid neg indexes. 723 index = Math.max((index|0), 0); 724 725 if (!task) { 726 self = this; 727 728 this._restripeTask = { 729 timer: setTimeout(function () { 730 // Check for self existence before continuing 731 if (!self || self.get('destroy') || !self.tbodyNode || !self.tbodyNode.inDoc()) { 732 self._restripeTask = null; 733 return; 734 } 735 736 var odd = [self.CLASS_ODD, self.CLASS_EVEN], 737 even = [self.CLASS_EVEN, self.CLASS_ODD], 738 index = self._restripeTask.index; 739 740 self.tbodyNode.get('childNodes') 741 .slice(index) 742 .each(function (row, i) { // TODO: each vs batch 743 row.replaceClass.apply(row, (index + i) % 2 ? even : odd); 744 }); 745 746 self._restripeTask = null; 747 }, 0), 748 749 index: index 750 }; 751 } else { 752 task.index = Math.min(task.index, index); 753 } 754 755 }, 756 757 /** 758 Handles replacement of the modelList. 759 760 Rerenders the `<tbody>` contents. 761 762 @method _afterModelListChange 763 @param {EventFacade} e The `modelListChange` event 764 @protected 765 @since 3.6.0 766 **/ 767 _afterModelListChange: function () { 768 var handles = this._eventHandles; 769 770 if (handles.dataChange) { 771 handles.dataChange.detach(); 772 delete handles.dataChange; 773 this.bindUI(); 774 } 775 776 if (this.tbodyNode) { 777 this.render(); 778 } 779 }, 780 781 /** 782 Iterates the `modelList`, and calls any `nodeFormatter`s found in the 783 `columns` param on the appropriate cell Nodes in the `tbody`. 784 785 @method _applyNodeFormatters 786 @param {Node} tbody The `<tbody>` Node whose columns to update 787 @param {Object[]} displayCols The column configurations 788 @protected 789 @since 3.5.0 790 **/ 791 _applyNodeFormatters: function (tbody, displayCols) { 792 var host = this.host || this, 793 data = this.get('modelList'), 794 formatters = [], 795 linerQuery = '.' + this.getClassName('liner'), 796 rows, i, len; 797 798 // Only iterate the ModelList again if there are nodeFormatters 799 for (i = 0, len = displayCols.length; i < len; ++i) { 800 if (displayCols[i].nodeFormatter) { 801 formatters.push(i); 802 } 803 } 804 805 if (data && formatters.length) { 806 rows = tbody.get('childNodes'); 807 808 data.each(function (record, index) { 809 var formatterData = { 810 data : record.toJSON(), 811 record : record, 812 rowIndex : index 813 }, 814 row = rows.item(index), 815 i, len, col, key, cells, cell, keep; 816 817 818 if (row) { 819 cells = row.get('childNodes'); 820 for (i = 0, len = formatters.length; i < len; ++i) { 821 cell = cells.item(formatters[i]); 822 823 if (cell) { 824 col = formatterData.column = displayCols[formatters[i]]; 825 key = col.key || col.id; 826 827 formatterData.value = record.get(key); 828 formatterData.td = cell; 829 formatterData.cell = cell.one(linerQuery) || cell; 830 831 keep = col.nodeFormatter.call(host,formatterData); 832 833 if (keep === false) { 834 // Remove from the Node cache to reduce 835 // memory footprint. This also purges events, 836 // which you shouldn't be scoping to a cell 837 // anyway. You've been warned. Incidentally, 838 // you should always return false. Just sayin. 839 cell.destroy(true); 840 } 841 } 842 } 843 } 844 }); 845 } 846 }, 847 848 /** 849 Binds event subscriptions from the UI and the host (if assigned). 850 851 @method bindUI 852 @protected 853 @since 3.5.0 854 **/ 855 bindUI: function () { 856 var handles = this._eventHandles, 857 modelList = this.get('modelList'), 858 changeEvent = modelList.model.NAME + ':change'; 859 860 if (!handles.columnsChange) { 861 handles.columnsChange = this.after('columnsChange', 862 bind('_afterColumnsChange', this)); 863 } 864 865 if (modelList && !handles.dataChange) { 866 handles.dataChange = modelList.after( 867 ['add', 'remove', 'reset', changeEvent], 868 bind('_afterDataChange', this)); 869 } 870 }, 871 872 /** 873 Iterates the `modelList` and applies each Model to the `_rowTemplate`, 874 allowing any column `formatter` or `emptyCellValue` to override cell 875 content for the appropriate column. The aggregated HTML string is 876 returned. 877 878 @method _createDataHTML 879 @param {Object[]} displayCols The column configurations to customize the 880 generated cell content or class names 881 @return {String} The markup for all Models in the `modelList`, each applied 882 to the `_rowTemplate` 883 @protected 884 @since 3.5.0 885 **/ 886 _createDataHTML: function (displayCols) { 887 var data = this.get('modelList'), 888 html = ''; 889 890 if (data) { 891 data.each(function (model, index) { 892 html += this._createRowHTML(model, index, displayCols); 893 }, this); 894 } 895 896 return html; 897 }, 898 899 /** 900 Applies the data of a given Model, modified by any column formatters and 901 supplemented by other template values to the instance's `_rowTemplate` (see 902 `_createRowTemplate`). The generated string is then returned. 903 904 The data from Model's attributes is fetched by `toJSON` and this data 905 object is appended with other properties to supply values to {placeholders} 906 in the template. For a template generated from a Model with 'foo' and 'bar' 907 attributes, the data object would end up with the following properties 908 before being used to populate the `_rowTemplate`: 909 910 * `clientID` - From Model, used the assign the `<tr>`'s 'id' attribute. 911 * `foo` - The value to populate the 'foo' column cell content. This 912 value will be the value stored in the Model's `foo` attribute, or the 913 result of the column's `formatter` if assigned. If the value is '', 914 `null`, or `undefined`, and the column's `emptyCellValue` is assigned, 915 that value will be used. 916 * `bar` - Same for the 'bar' column cell content. 917 * `foo-className` - String of CSS classes to apply to the `<td>`. 918 * `bar-className` - Same. 919 * `rowClass` - String of CSS classes to apply to the `<tr>`. This 920 will be the odd/even class per the specified index plus any additional 921 classes assigned by column formatters (via `o.rowClass`). 922 923 Because this object is available to formatters, any additional properties 924 can be added to fill in custom {placeholders} in the `_rowTemplate`. 925 926 @method _createRowHTML 927 @param {Model} model The Model instance to apply to the row template 928 @param {Number} index The index the row will be appearing 929 @param {Object[]} displayCols The column configurations 930 @return {String} The markup for the provided Model, less any `nodeFormatter`s 931 @protected 932 @since 3.5.0 933 **/ 934 _createRowHTML: function (model, index, displayCols) { 935 var data = model.toJSON(), 936 clientId = model.get('clientId'), 937 values = { 938 rowId : this._getRowId(clientId), 939 clientId: clientId, 940 rowClass: (index % 2) ? this.CLASS_ODD : this.CLASS_EVEN 941 }, 942 host = this.host || this, 943 i, len, col, token, value, formatterData; 944 945 for (i = 0, len = displayCols.length; i < len; ++i) { 946 col = displayCols[i]; 947 value = data[col.key]; 948 token = col._id || col.key; 949 950 values[token + '-className'] = ''; 951 952 if (col._formatterFn) { 953 formatterData = { 954 value : value, 955 data : data, 956 column : col, 957 record : model, 958 className: '', 959 rowClass : '', 960 rowIndex : index 961 }; 962 963 // Formatters can either return a value 964 value = col._formatterFn.call(host, formatterData); 965 966 // or update the value property of the data obj passed 967 if (value === undefined) { 968 value = formatterData.value; 969 } 970 971 values[token + '-className'] = formatterData.className; 972 values.rowClass += ' ' + formatterData.rowClass; 973 } 974 975 // if the token missing OR is the value a legit value 976 if (!values.hasOwnProperty(token) || data.hasOwnProperty(col.key)) { 977 if (value === undefined || value === null || value === '') { 978 value = col.emptyCellValue || ''; 979 } 980 981 values[token] = col.allowHTML ? value : htmlEscape(value); 982 } 983 } 984 985 // replace consecutive whitespace with a single space 986 values.rowClass = values.rowClass.replace(/\s+/g, ' '); 987 988 return fromTemplate(this._rowTemplate, values); 989 }, 990 991 /** 992 Locates the row within the tbodyNode and returns the found index, or Null 993 if it is not found in the tbodyNode 994 @param {Node} row 995 @return {Number} Index of row in tbodyNode 996 */ 997 _getRowIndex: function (row) { 998 var tbody = this.tbodyNode, 999 index = 1; 1000 1001 if (tbody && row) { 1002 1003 //if row is not in the tbody, return 1004 if (row.ancestor('tbody') !== tbody) { 1005 return null; 1006 } 1007 1008 // increment until we no longer have a previous node 1009 /*jshint boss: true*/ 1010 while (row = row.previous()) { // NOTE: assignment 1011 /*jshint boss: false*/ 1012 index++; 1013 } 1014 } 1015 1016 return index; 1017 }, 1018 1019 /** 1020 Creates a custom HTML template string for use in generating the markup for 1021 individual table rows with {placeholder}s to capture data from the Models 1022 in the `modelList` attribute or from column `formatter`s. 1023 1024 Assigns the `_rowTemplate` property. 1025 1026 @method _createRowTemplate 1027 @param {Object[]} displayCols Array of column configuration objects 1028 @protected 1029 @since 3.5.0 1030 **/ 1031 _createRowTemplate: function (displayCols) { 1032 var html = '', 1033 cellTemplate = this.CELL_TEMPLATE, 1034 i, len, col, key, token, headers, tokenValues, formatter; 1035 1036 this._setColumnsFormatterFn(displayCols); 1037 1038 for (i = 0, len = displayCols.length; i < len; ++i) { 1039 col = displayCols[i]; 1040 key = col.key; 1041 token = col._id || key; 1042 formatter = col._formatterFn; 1043 // Only include headers if there are more than one 1044 headers = (col._headers || []).length > 1 ? 1045 'headers="' + col._headers.join(' ') + '"' : ''; 1046 1047 tokenValues = { 1048 content : '{' + token + '}', 1049 headers : headers, 1050 className: this.getClassName('col', token) + ' ' + 1051 (col.className || '') + ' ' + 1052 this.getClassName('cell') + 1053 ' {' + token + '-className}' 1054 }; 1055 if (!formatter && col.formatter) { 1056 tokenValues.content = col.formatter.replace(valueRegExp, tokenValues.content); 1057 } 1058 1059 if (col.nodeFormatter) { 1060 // Defer all node decoration to the formatter 1061 tokenValues.content = ''; 1062 } 1063 1064 html += fromTemplate(col.cellTemplate || cellTemplate, tokenValues); 1065 } 1066 1067 this._rowTemplate = fromTemplate(this.ROW_TEMPLATE, { 1068 content: html 1069 }); 1070 }, 1071 1072 /** 1073 Parses the columns array and defines the column's _formatterFn if there 1074 is a formatter available on the column 1075 @protected 1076 @method _setColumnsFormatterFn 1077 @param {Object[]} displayCols Array of column configuration objects 1078 1079 @return {Object[]} Returns modified displayCols configuration Array 1080 */ 1081 _setColumnsFormatterFn: function (displayCols) { 1082 var Formatters = Y.DataTable.BodyView.Formatters, 1083 formatter, 1084 col, 1085 i, 1086 len; 1087 1088 for (i = 0, len = displayCols.length; i < len; i++) { 1089 col = displayCols[i]; 1090 formatter = col.formatter; 1091 1092 if (!col._formatterFn && formatter) { 1093 if (Lang.isFunction(formatter)) { 1094 col._formatterFn = formatter; 1095 } else if (formatter in Formatters) { 1096 col._formatterFn = Formatters[formatter].call(this.host || this, col); 1097 } 1098 } 1099 } 1100 1101 return displayCols; 1102 }, 1103 1104 /** 1105 Creates the `<tbody>` node that will store the data rows. 1106 1107 @method _createTBodyNode 1108 @return {Node} 1109 @protected 1110 @since 3.6.0 1111 **/ 1112 _createTBodyNode: function () { 1113 return Y.Node.create(fromTemplate(this.TBODY_TEMPLATE, { 1114 className: this.getClassName('data') 1115 })); 1116 }, 1117 1118 /** 1119 Destroys the instance. 1120 1121 @method destructor 1122 @protected 1123 @since 3.5.0 1124 **/ 1125 destructor: function () { 1126 (new Y.EventHandle(YObject.values(this._eventHandles))).detach(); 1127 }, 1128 1129 /** 1130 Holds the event subscriptions needing to be detached when the instance is 1131 `destroy()`ed. 1132 1133 @property _eventHandles 1134 @type {Object} 1135 @default undefined (initially unset) 1136 @protected 1137 @since 3.5.0 1138 **/ 1139 //_eventHandles: null, 1140 1141 /** 1142 Returns the row ID associated with a Model's clientId. 1143 1144 @method _getRowId 1145 @param {String} clientId The Model clientId 1146 @return {String} 1147 @protected 1148 **/ 1149 _getRowId: function (clientId) { 1150 return this._idMap[clientId] || (this._idMap[clientId] = Y.guid()); 1151 }, 1152 1153 /** 1154 Map of Model clientIds to row ids. 1155 1156 @property _idMap 1157 @type {Object} 1158 @protected 1159 **/ 1160 //_idMap, 1161 1162 /** 1163 Initializes the instance. Reads the following configuration properties in 1164 addition to the instance attributes: 1165 1166 * `columns` - (REQUIRED) The initial column information 1167 * `host` - The object to serve as source of truth for column info and 1168 for generating class names 1169 1170 @method initializer 1171 @param {Object} config Configuration data 1172 @protected 1173 @since 3.5.0 1174 **/ 1175 initializer: function (config) { 1176 this.host = config.host; 1177 1178 this._eventHandles = { 1179 modelListChange: this.after('modelListChange', 1180 bind('_afterModelListChange', this)) 1181 }; 1182 this._idMap = {}; 1183 1184 this.CLASS_ODD = this.getClassName('odd'); 1185 this.CLASS_EVEN = this.getClassName('even'); 1186 1187 } 1188 1189 /** 1190 The HTML template used to create a full row of markup for a single Model in 1191 the `modelList` plus any customizations defined in the column 1192 configurations. 1193 1194 @property _rowTemplate 1195 @type {String} 1196 @default (initially unset) 1197 @protected 1198 @since 3.5.0 1199 **/ 1200 //_rowTemplate: null 1201 },{ 1202 /** 1203 Hash of formatting functions for cell contents. 1204 1205 This property can be populated with a hash of formatting functions by the developer 1206 or a set of pre-defined functions can be loaded via the `datatable-formatters` module. 1207 1208 See: [DataTable.BodyView.Formatters](./DataTable.BodyView.Formatters.html) 1209 @property Formatters 1210 @type Object 1211 @since 3.8.0 1212 @static 1213 **/ 1214 Formatters: {} 1215 }); 1216 1217 1218 }, '3.17.2', {"requires": ["datatable-core", "view", "classnamemanager"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |