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