[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/datatable-body/ -> datatable-body-debug.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1