2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('datatable-body', function (Y, NAME) {
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.
15
@submodule datatable-body
19
isArray = Lang.isArray,
20
isNumber = Lang.isNumber,
21
isString = Lang.isString,
22
fromTemplate = Lang.sub,
23
htmlEscape = Y.Escape.html,
27
valueRegExp = /\{value\}/g;
30
View class responsible for rendering the `<tbody>` section of a table. Used as
31
the default `bodyView` for `Y.DataTable.Base` and `Y.DataTable` classes.
33
Translates the provided `modelList` into a rendered `<tbody>` based on the data
34
in the constituent Models, altered or ammended by any special column
37
The `columns` configuration, passed to the constructor, determines which
38
columns will be rendered.
40
The rendering process involves constructing an HTML template for a complete row
41
of data, built by concatenating a customized copy of the instance's
42
`CELL_TEMPLATE` into the `ROW_TEMPLATE` once for each column. This template is
43
then populated with values from each Model in the `modelList`, aggregating a
44
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.
46
Supported properties of the column objects include:
48
* `key` - Used to link a column to an attribute in a Model.
49
* `name` - Used for columns that don't relate to an attribute in the Model
50
(`formatter` or `nodeFormatter` only) if the implementer wants a
51
predictable name to refer to in their CSS.
52
* `cellTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in this
54
* `formatter` - Used to customize or override the content value from the
55
Model. These do not have access to the cell or row Nodes and should
56
return string (HTML) content.
57
* `nodeFormatter` - Used to provide content for a cell as well as perform any
58
custom modifications on the cell or row Node that could not be performed by
59
`formatter`s. Should be used sparingly for better performance.
60
* `emptyCellValue` - String (HTML) value to use if the Model data for a
61
column, or the content generated by a `formatter`, is the empty string,
62
`null`, or `undefined`.
63
* `allowHTML` - Set to `true` if a column value, `formatter`, or
64
`emptyCellValue` can contain HTML. This defaults to `false` to protect
66
* `className` - Space delimited CSS classes to add to all `<td>`s in a column.
68
A column `formatter` can be:
70
* a function, as described below.
71
* a string which can be:
72
* the name of a pre-defined formatter function
73
which can be located in the `Y.DataTable.BodyView.Formatters` hash using the
74
value of the `formatter` property as the index.
75
* A template that can use the `{value}` placeholder to include the value
76
for the current cell or the name of any field in the underlaying model
77
also enclosed in curly braces. Any number and type of these placeholders
80
Column `formatter`s are passed an object (`o`) with the following properties:
82
* `value` - The current value of the column's associated attribute, if any.
83
* `data` - An object map of Model keys to their current values.
84
* `record` - The Model instance.
85
* `column` - The column configuration object for the current column.
86
* `className` - Initially empty string to allow `formatter`s to add CSS
87
classes to the cell's `<td>`.
88
* `rowIndex` - The zero-based row number.
89
* `rowClass` - Initially empty string to allow `formatter`s to add CSS
90
classes to the cell's containing row `<tr>`.
92
They may return a value or update `o.value` to assign specific HTML content. A
93
returned value has higher precedence.
95
Column `nodeFormatter`s are passed an object (`o`) with the following
98
* `value` - The current value of the column's associated attribute, if any.
99
* `td` - The `<td>` Node instance.
100
* `cell` - The `<div>` liner Node instance if present, otherwise, the `<td>`.
101
When adding content to the cell, prefer appending into this property.
102
* `data` - An object map of Model keys to their current values.
103
* `record` - The Model instance.
104
* `column` - The column configuration object for the current column.
105
* `rowIndex` - The zero-based row number.
107
They are expected to inject content into the cell's Node directly, including
108
any "empty" cell content. Each `nodeFormatter` will have access through the
109
Node API to all cells and rows in the `<tbody>`, but not to the `<table>`, as
110
it will not be attached yet.
112
If a `nodeFormatter` returns `false`, the `o.td` and `o.cell` Nodes will be
113
`destroy()`ed to remove them from the Node cache and free up memory. The DOM
114
elements will remain as will any content added to them. _It is highly
115
advisable to always return `false` from your `nodeFormatter`s_.
122
Y.namespace('DataTable').BodyView = Y.Base.create('tableBody', Y.View, [], {
123
// -- Instance properties -------------------------------------------------
126
HTML template used to create table cells.
128
@property CELL_TEMPLATE
130
@default '<td {headers} class="{className}">{content}</td>'
133
CELL_TEMPLATE: '<td {headers} class="{className}">{content}</td>',
136
CSS class applied to even rows. This is assigned at instantiation.
138
For DataTable, this will be `yui3-datatable-even`.
142
@default 'yui3-table-even'
148
CSS class applied to odd rows. This is assigned at instantiation.
150
When used by DataTable instances, this will be `yui3-datatable-odd`.
154
@default 'yui3-table-odd'
160
HTML template used to create table rows.
162
@property ROW_TEMPLATE
164
@default '<tr id="{rowId}" data-yui3-record="{clientId}" class="{rowClass}">{content}</tr>'
167
ROW_TEMPLATE : '<tr id="{rowId}" data-yui3-record="{clientId}" class="{rowClass}">{content}</tr>',
170
The object that serves as the source of truth for column and row data.
171
This property is assigned at instantiation from the `host` property of
172
the configuration object passed to the constructor.
176
@default (initially unset)
179
//TODO: should this be protected?
183
HTML templates used to create the `<tbody>` containing the table rows.
185
@property TBODY_TEMPLATE
187
@default '<tbody class="{className}">{content}</tbody>'
190
TBODY_TEMPLATE: '<tbody class="{className}"></tbody>',
192
// -- Public methods ------------------------------------------------------
195
Returns the `<td>` Node from the given row and column index. Alternately,
196
the `seed` can be a Node. If so, the nearest ancestor cell is returned.
197
If the `seed` is a cell, it is returned. If there is no cell at the given
198
coordinates, `null` is returned.
200
Optionally, include an offset array or string to return a cell near the
201
cell identified by the `seed`. The offset can be an array containing the
202
number of rows to shift followed by the number of columns to shift, or one
203
of "above", "below", "next", or "previous".
205
<pre><code>// Previous cell in the previous row
206
var cell = table.getCell(e.target, [-1, -1]);
209
var cell = table.getCell(e.target, 'next');
210
var cell = table.getCell(e.taregt, [0, 1];</pre></code>
213
@param {Number[]|Node} seed Array of row and column indexes, or a Node that
214
is either the cell itself or a descendant of one.
215
@param {Number[]|String} [shift] Offset by which to identify the returned
220
getCell: function (seed, shift) {
221
var tbody = this.tbodyNode,
222
row, cell, index, rowIndexOffset;
226
row = tbody.get('children').item(seed[0]);
227
cell = row && row.get('children').item(seed[1]);
228
} else if (Y.instanceOf(seed, Y.Node)) {
229
cell = seed.ancestor('.' + this.getClassName('cell'), true);
233
rowIndexOffset = tbody.get('firstChild.rowIndex');
234
if (isString(shift)) {
235
// TODO this should be a static object map
237
case 'above' : shift = [-1, 0]; break;
238
case 'below' : shift = [1, 0]; break;
239
case 'next' : shift = [0, 1]; break;
240
case 'previous': shift = [0, -1]; break;
244
if (isArray(shift)) {
245
index = cell.get('parentNode.rowIndex') +
246
shift[0] - rowIndexOffset;
247
row = tbody.get('children').item(index);
249
index = cell.get('cellIndex') + shift[1];
250
cell = row && row.get('children').item(index);
259
Returns the generated CSS classname based on the input. If the `host`
260
attribute is configured, it will attempt to relay to its `getClassName`
261
or use its static `NAME` property as a string base.
263
If `host` is absent or has neither method nor `NAME`, a CSS classname
264
will be generated using this class's `NAME`.
267
@param {String} token* Any number of token strings to assemble the
273
getClassName: function () {
274
var host = this.host,
277
if (host && host.getClassName) {
278
return host.getClassName.apply(host, arguments);
280
args = toArray(arguments);
281
args.unshift(this.constructor.NAME);
282
return Y.ClassNameManager.getClassName
283
.apply(Y.ClassNameManager, args);
288
Returns the Model associated to the row Node or id provided. Passing the
289
Node or id for a descendant of the row also works.
291
If no Model can be found, `null` is returned.
294
@param {String|Node} seed Row Node or `id`, or one for a descendant of a row
298
getRecord: function (seed) {
299
var modelList = this.get('modelList'),
300
tbody = this.tbodyNode,
305
if (isString(seed)) {
306
seed = tbody.one('#' + seed);
309
if (Y.instanceOf(seed, Y.Node)) {
310
row = seed.ancestor(function (node) {
311
return node.get('parentNode').compareTo(tbody);
315
modelList.getByClientId(row.getData('yui3-record'));
319
return record || null;
323
Returns the `<tr>` Node from the given row index, Model, or Model's
324
`clientId`. If the rows haven't been rendered yet, or if the row can't be
325
found by the input, `null` is returned.
328
@param {Number|String|Model} id Row index, Model instance, or clientId
332
getRow: function (id) {
333
var tbody = this.tbodyNode,
338
id = this._idMap[id.get ? id.get('clientId') : id] || id;
342
tbody.get('children').item(id) :
350
Creates the table's `<tbody>` content by assembling markup generated by
351
populating the `ROW\_TEMPLATE`, and `CELL\_TEMPLATE` templates with content
352
from the `columns` and `modelList` attributes.
354
The rendering process happens in three stages:
356
1. A row template is assembled from the `columns` attribute (see
357
`_createRowTemplate`)
359
2. An HTML string is built up by concatening the application of the data in
360
each Model in the `modelList` to the row template. For cells with
361
`formatter`s, the function is called to generate cell content. Cells
362
with `nodeFormatter`s are ignored. For all other cells, the data value
363
from the Model attribute for the given column key is used. The
364
accumulated row markup is then inserted into the container.
366
3. If any column is configured with a `nodeFormatter`, the `modelList` is
367
iterated again to apply the `nodeFormatter`s.
369
Supported properties of the column objects include:
371
* `key` - Used to link a column to an attribute in a Model.
372
* `name` - Used for columns that don't relate to an attribute in the Model
373
(`formatter` or `nodeFormatter` only) if the implementer wants a
374
predictable name to refer to in their CSS.
375
* `cellTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in
377
* `formatter` - Used to customize or override the content value from the
378
Model. These do not have access to the cell or row Nodes and should
379
return string (HTML) content.
380
* `nodeFormatter` - Used to provide content for a cell as well as perform
381
any custom modifications on the cell or row Node that could not be
382
performed by `formatter`s. Should be used sparingly for better
384
* `emptyCellValue` - String (HTML) value to use if the Model data for a
385
column, or the content generated by a `formatter`, is the empty string,
386
`null`, or `undefined`.
387
* `allowHTML` - Set to `true` if a column value, `formatter`, or
388
`emptyCellValue` can contain HTML. This defaults to `false` to protect
390
* `className` - Space delimited CSS classes to add to all `<td>`s in a
393
Column `formatter`s are passed an object (`o`) with the following
396
* `value` - The current value of the column's associated attribute, if
398
* `data` - An object map of Model keys to their current values.
399
* `record` - The Model instance.
400
* `column` - The column configuration object for the current column.
401
* `className` - Initially empty string to allow `formatter`s to add CSS
402
classes to the cell's `<td>`.
403
* `rowIndex` - The zero-based row number.
404
* `rowClass` - Initially empty string to allow `formatter`s to add CSS
405
classes to the cell's containing row `<tr>`.
407
They may return a value or update `o.value` to assign specific HTML
408
content. A returned value has higher precedence.
410
Column `nodeFormatter`s are passed an object (`o`) with the following
413
* `value` - The current value of the column's associated attribute, if
415
* `td` - The `<td>` Node instance.
416
* `cell` - The `<div>` liner Node instance if present, otherwise, the
417
`<td>`. When adding content to the cell, prefer appending into this
419
* `data` - An object map of Model keys to their current values.
420
* `record` - The Model instance.
421
* `column` - The column configuration object for the current column.
422
* `rowIndex` - The zero-based row number.
424
They are expected to inject content into the cell's Node directly, including
425
any "empty" cell content. Each `nodeFormatter` will have access through the
426
Node API to all cells and rows in the `<tbody>`, but not to the `<table>`,
427
as it will not be attached yet.
429
If a `nodeFormatter` returns `false`, the `o.td` and `o.cell` Nodes will be
430
`destroy()`ed to remove them from the Node cache and free up memory. The
431
DOM elements will remain as will any content added to them. _It is highly
432
advisable to always return `false` from your `nodeFormatter`s_.
435
@return {BodyView} The instance
439
render: function () {
440
var table = this.get('container'),
441
data = this.get('modelList'),
442
columns = this.get('columns'),
443
tbody = this.tbodyNode ||
444
(this.tbodyNode = this._createTBodyNode());
446
// Needed for mutation
447
this._createRowTemplate(columns);
450
tbody.setHTML(this._createDataHTML(columns));
452
this._applyNodeFormatters(tbody, columns);
455
if (tbody.get('parentNode') !== table) {
456
table.appendChild(tbody);
459
this._afterRenderCleanup();
466
// -- Protected and private methods ---------------------------------------
468
Handles changes in the source's columns attribute. Redraws the table data.
470
@method _afterColumnsChange
471
@param {EventFacade} e The `columnsChange` event object
475
// TODO: Preserve existing DOM
476
// This will involve parsing and comparing the old and new column configs
477
// and reacting to four types of changes:
478
// 1. formatter, nodeFormatter, emptyCellValue changes
479
// 2. column deletions
480
// 3. column additions
481
// 4. column moves (preserve cells)
482
_afterColumnsChange: function () {
487
Handles modelList changes, including additions, deletions, and updates.
489
Modifies the existing table DOM accordingly.
491
@method _afterDataChange
492
@param {EventFacade} e The `change` event from the ModelList
496
_afterDataChange: function () {
497
//var type = e.type.slice(e.type.lastIndexOf(':') + 1);
499
// TODO: Isolate changes
504
Handles replacement of the modelList.
506
Rerenders the `<tbody>` contents.
508
@method _afterModelListChange
509
@param {EventFacade} e The `modelListChange` event
513
_afterModelListChange: function () {
514
var handles = this._eventHandles;
516
if (handles.dataChange) {
517
handles.dataChange.detach();
518
delete handles.dataChange;
522
if (this.tbodyNode) {
528
Iterates the `modelList`, and calls any `nodeFormatter`s found in the
529
`columns` param on the appropriate cell Nodes in the `tbody`.
531
@method _applyNodeFormatters
532
@param {Node} tbody The `<tbody>` Node whose columns to update
533
@param {Object[]} columns The column configurations
537
_applyNodeFormatters: function (tbody, columns) {
538
var host = this.host,
539
data = this.get('modelList'),
541
linerQuery = '.' + this.getClassName('liner'),
544
// Only iterate the ModelList again if there are nodeFormatters
545
for (i = 0, len = columns.length; i < len; ++i) {
546
if (columns[i].nodeFormatter) {
551
if (data && formatters.length) {
552
rows = tbody.get('childNodes');
554
data.each(function (record, index) {
555
var formatterData = {
556
data : record.toJSON(),
560
row = rows.item(index),
561
i, len, col, key, cells, cell, keep;
565
cells = row.get('childNodes');
566
for (i = 0, len = formatters.length; i < len; ++i) {
567
cell = cells.item(formatters[i]);
570
col = formatterData.column = columns[formatters[i]];
571
key = col.key || col.id;
573
formatterData.value = record.get(key);
574
formatterData.td = cell;
575
formatterData.cell = cell.one(linerQuery) || cell;
577
keep = col.nodeFormatter.call(host,formatterData);
579
if (keep === false) {
580
// Remove from the Node cache to reduce
581
// memory footprint. This also purges events,
582
// which you shouldn't be scoping to a cell
583
// anyway. You've been warned. Incidentally,
584
// you should always return false. Just sayin.
595
Binds event subscriptions from the UI and the host (if assigned).
601
bindUI: function () {
602
var handles = this._eventHandles,
603
modelList = this.get('modelList'),
604
changeEvent = modelList.model.NAME + ':change';
606
if (!handles.columnsChange) {
607
handles.columnsChange = this.after('columnsChange',
608
bind('_afterColumnsChange', this));
611
if (modelList && !handles.dataChange) {
612
handles.dataChange = modelList.after(
613
['add', 'remove', 'reset', changeEvent],
614
bind('_afterDataChange', this));
619
Iterates the `modelList` and applies each Model to the `_rowTemplate`,
620
allowing any column `formatter` or `emptyCellValue` to override cell
621
content for the appropriate column. The aggregated HTML string is
624
@method _createDataHTML
625
@param {Object[]} columns The column configurations to customize the
626
generated cell content or class names
627
@return {HTML} The markup for all Models in the `modelList`, each applied
628
to the `_rowTemplate`
632
_createDataHTML: function (columns) {
633
var data = this.get('modelList'),
637
data.each(function (model, index) {
638
html += this._createRowHTML(model, index, columns);
646
Applies the data of a given Model, modified by any column formatters and
647
supplemented by other template values to the instance's `_rowTemplate` (see
648
`_createRowTemplate`). The generated string is then returned.
650
The data from Model's attributes is fetched by `toJSON` and this data
651
object is appended with other properties to supply values to {placeholders}
652
in the template. For a template generated from a Model with 'foo' and 'bar'
653
attributes, the data object would end up with the following properties
654
before being used to populate the `_rowTemplate`:
656
* `clientID` - From Model, used the assign the `<tr>`'s 'id' attribute.
657
* `foo` - The value to populate the 'foo' column cell content. This
658
value will be the value stored in the Model's `foo` attribute, or the
659
result of the column's `formatter` if assigned. If the value is '',
660
`null`, or `undefined`, and the column's `emptyCellValue` is assigned,
661
that value will be used.
662
* `bar` - Same for the 'bar' column cell content.
663
* `foo-className` - String of CSS classes to apply to the `<td>`.
664
* `bar-className` - Same.
665
* `rowClass` - String of CSS classes to apply to the `<tr>`. This
666
will be the odd/even class per the specified index plus any additional
667
classes assigned by column formatters (via `o.rowClass`).
669
Because this object is available to formatters, any additional properties
670
can be added to fill in custom {placeholders} in the `_rowTemplate`.
672
@method _createRowHTML
673
@param {Model} model The Model instance to apply to the row template
674
@param {Number} index The index the row will be appearing
675
@param {Object[]} columns The column configurations
676
@return {HTML} The markup for the provided Model, less any `nodeFormatter`s
680
_createRowHTML: function (model, index, columns) {
681
var data = model.toJSON(),
682
clientId = model.get('clientId'),
684
rowId : this._getRowId(clientId),
686
rowClass: (index % 2) ? this.CLASS_ODD : this.CLASS_EVEN
688
host = this.host || this,
689
i, len, col, token, value, formatterData;
691
for (i = 0, len = columns.length; i < len; ++i) {
693
value = data[col.key];
694
token = col._id || col.key;
696
values[token + '-className'] = '';
698
if (col._formatterFn) {
709
// Formatters can either return a value
710
value = col._formatterFn.call(host, formatterData);
712
// or update the value property of the data obj passed
713
if (value === undefined) {
714
value = formatterData.value;
717
values[token + '-className'] = formatterData.className;
718
values.rowClass += ' ' + formatterData.rowClass;
721
if (value === undefined || value === null || value === '') {
722
value = col.emptyCellValue || '';
725
values[token] = col.allowHTML ? value : htmlEscape(value);
727
values.rowClass = values.rowClass.replace(/\s+/g, ' ');
730
return fromTemplate(this._rowTemplate, values);
734
Creates a custom HTML template string for use in generating the markup for
735
individual table rows with {placeholder}s to capture data from the Models
736
in the `modelList` attribute or from column `formatter`s.
738
Assigns the `_rowTemplate` property.
740
@method _createRowTemplate
741
@param {Object[]} columns Array of column configuration objects
745
_createRowTemplate: function (columns) {
747
cellTemplate = this.CELL_TEMPLATE,
748
F = Y.DataTable.BodyView.Formatters,
749
i, len, col, key, token, headers, tokenValues, formatter;
751
for (i = 0, len = columns.length; i < len; ++i) {
754
token = col._id || key;
755
formatter = col.formatter;
756
// Only include headers if there are more than one
757
headers = (col._headers || []).length > 1 ?
758
'headers="' + col._headers.join(' ') + '"' : '';
761
content : '{' + token + '}',
763
className: this.getClassName('col', token) + ' ' +
764
(col.className || '') + ' ' +
765
this.getClassName('cell') +
766
' {' + token + '-className}'
769
if (Lang.isFunction(formatter)) {
770
col._formatterFn = formatter;
771
} else if (formatter in F) {
772
col._formatterFn = F[formatter].call(this.host || this, col);
774
tokenValues.content = formatter.replace(valueRegExp, tokenValues.content);
778
if (col.nodeFormatter) {
779
// Defer all node decoration to the formatter
780
tokenValues.content = '';
783
html += fromTemplate(col.cellTemplate || cellTemplate, tokenValues);
786
this._rowTemplate = fromTemplate(this.ROW_TEMPLATE, {
791
Cleans up temporary values created during rendering.
792
@method _afterRenderCleanup
795
_afterRenderCleanup: function () {
796
var columns = this.get('columns'),
797
i, len = columns.length;
799
for (i = 0;i < len; i+=1) {
800
delete columns[i]._formatterFn;
806
Creates the `<tbody>` node that will store the data rows.
808
@method _createTBodyNode
813
_createTBodyNode: function () {
814
return Y.Node.create(fromTemplate(this.TBODY_TEMPLATE, {
815
className: this.getClassName('data')
820
Destroys the instance.
826
destructor: function () {
827
(new Y.EventHandle(YObject.values(this._eventHandles))).detach();
831
Holds the event subscriptions needing to be detached when the instance is
834
@property _eventHandles
836
@default undefined (initially unset)
840
//_eventHandles: null,
843
Returns the row ID associated with a Model's clientId.
846
@param {String} clientId The Model clientId
850
_getRowId: function (clientId) {
851
return this._idMap[clientId] || (this._idMap[clientId] = Y.guid());
855
Map of Model clientIds to row ids.
864
Initializes the instance. Reads the following configuration properties in
865
addition to the instance attributes:
867
* `columns` - (REQUIRED) The initial column information
868
* `host` - The object to serve as source of truth for column info and
869
for generating class names
872
@param {Object} config Configuration data
876
initializer: function (config) {
877
this.host = config.host;
879
this._eventHandles = {
880
modelListChange: this.after('modelListChange',
881
bind('_afterModelListChange', this))
885
this.CLASS_ODD = this.getClassName('odd');
886
this.CLASS_EVEN = this.getClassName('even');
891
The HTML template used to create a full row of markup for a single Model in
892
the `modelList` plus any customizations defined in the column
895
@property _rowTemplate
897
@default (initially unset)
904
Hash of formatting functions for cell contents.
906
This property can be populated with a hash of formatting functions by the developer
907
or a set of pre-defined functions can be loaded via the `datatable-formatters` module.
909
See: [DataTable.BodyView.Formatters](./DataTable.BodyView.Formatters.html)
919
}, '3.10.3', {"requires": ["datatable-core", "view", "classnamemanager"]});