Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('datatable-head', function (Y, NAME) {
2
 
3
/**
4
View class responsible for rendering the `<thead>` section of a table. Used as
5
the default `headerView` for `Y.DataTable.Base` and `Y.DataTable` classes.
6
 
7
@module datatable
8
@submodule datatable-head
9
@since 3.5.0
10
**/
11
var Lang = Y.Lang,
12
    fromTemplate = Lang.sub,
13
    isArray = Lang.isArray,
14
    toArray = Y.Array;
15
 
16
/**
17
View class responsible for rendering the `<thead>` section of a table. Used as
18
the default `headerView` for `Y.DataTable.Base` and `Y.DataTable` classes.
19
 
20
Translates the provided array of column configuration objects into a rendered
21
`<thead>` based on the data in those objects.
22
 
23
 
24
The structure of the column data is expected to be a single array of objects,
25
where each object corresponds to a `<th>`.  Those objects may contain a
26
`children` property containing a similarly structured array to indicate the
27
nested cells should be grouped under the parent column's colspan in a separate
28
row of header cells. E.g.
29
 
30
<pre><code>
31
new Y.DataTable.HeaderView({
32
  container: tableNode,
33
  columns: [
34
    { key: 'id' }, // no nesting
35
    { key: 'name', children: [
36
      { key: 'firstName', label: 'First' },
37
      { key: 'lastName',  label: 'Last' } ] }
38
  ]
39
}).render();
40
</code></pre>
41
 
42
This would translate to the following visualization:
43
 
44
<pre>
45
---------------------
46
|    |     name     |
47
|    |---------------
48
| id | First | Last |
49
---------------------
50
</pre>
51
 
52
Supported properties of the column objects include:
53
 
54
  * `label`     - The HTML content of the header cell.
55
  * `key`       - If `label` is not specified, the `key` is used for content.
56
  * `children`  - Array of columns to appear below this column in the next
57
                  row.
58
  * `headerTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells in this
59
    column only.
60
  * `abbr`      - The content of the 'abbr' attribute of the `<th>`
61
  * `title`     - The content of the 'title' attribute of the `<th>`
62
  * `className` - Adds this string of CSS classes to the column header
63
 
64
Through the life of instantiation and rendering, the column objects will have
65
the following properties added to them:
66
 
67
  * `id`       - (Defaulted by DataTable) The id to assign the rendered column
68
  * `_colspan` - To supply the `<th>` attribute
69
  * `_rowspan` - To supply the `<th>` attribute
70
  * `_parent`  - (Added by DataTable) If the column is a child of another
71
    column, this points to its parent column
72
 
73
The column object is also used to provide values for {placeholder} tokens in the
74
instance's `CELL_TEMPLATE`, so you can modify the template and include other
75
column object properties to populate them.
76
 
77
@class HeaderView
78
@namespace DataTable
79
@extends View
80
@since 3.5.0
81
**/
82
Y.namespace('DataTable').HeaderView = Y.Base.create('tableHeader', Y.View, [], {
83
    // -- Instance properties -------------------------------------------------
84
 
85
    /**
86
    Template used to create the table's header cell markup.  Override this to
87
    customize how header cell markup is created.
88
 
89
    @property CELL_TEMPLATE
90
    @type {String}
91
    @default '<th id="{id}" colspan="{_colspan}" rowspan="{_rowspan}" class="{className}" scope="col" {_id}{abbr}{title}>{content}</th>'
92
    @since 3.5.0
93
    **/
94
    CELL_TEMPLATE:
95
        '<th id="{id}" colspan="{_colspan}" rowspan="{_rowspan}" class="{className}" scope="col" {_id}{abbr}{title}>{content}</th>',
96
 
97
    /**
98
    The data representation of the header rows to render.  This is assigned by
99
    parsing the `columns` configuration array, and is used by the render()
100
    method.
101
 
102
    @property columns
103
    @type {Array[]}
104
    @default (initially unset)
105
    @since 3.5.0
106
    **/
107
    //TODO: should this be protected?
108
    //columns: null,
109
 
110
    /**
111
    Template used to create the table's header row markup.  Override this to
112
    customize the row markup.
113
 
114
    @property ROW_TEMPLATE
115
    @type {String}
116
    @default '<tr>{content}</tr>'
117
    @since 3.5.0
118
    **/
119
    ROW_TEMPLATE:
120
        '<tr>{content}</tr>',
121
 
122
    /**
123
    The object that serves as the source of truth for column and row data.
124
    This property is assigned at instantiation from the `source` property of
125
    the configuration object passed to the constructor.
126
 
127
    @property source
128
    @type {Object}
129
    @default (initially unset)
130
    @since 3.5.0
131
    **/
132
    //TODO: should this be protected?
133
    //source: null,
134
 
135
    /**
136
    HTML templates used to create the `<thead>` containing the table headers.
137
 
138
    @property THEAD_TEMPLATE
139
    @type {String}
140
    @default '<thead class="{className}">{content}</thead>'
141
    @since 3.6.0
142
    **/
143
    THEAD_TEMPLATE: '<thead class="{className}"></thead>',
144
 
145
    // -- Public methods ------------------------------------------------------
146
 
147
    /**
148
    Returns the generated CSS classname based on the input.  If the `host`
149
    attribute is configured, it will attempt to relay to its `getClassName`
150
    or use its static `NAME` property as a string base.
151
 
152
    If `host` is absent or has neither method nor `NAME`, a CSS classname
153
    will be generated using this class's `NAME`.
154
 
155
    @method getClassName
156
    @param {String} token* Any number of token strings to assemble the
157
        classname from.
158
    @return {String}
159
    @protected
160
    **/
161
    getClassName: function () {
162
        // TODO: add attribute with setter? to host to use property this.host
163
        // for performance
164
        var host = this.host,
165
            NAME = (host && host.constructor.NAME) ||
166
                    this.constructor.NAME;
167
 
168
        if (host && host.getClassName) {
169
            return host.getClassName.apply(host, arguments);
170
        } else {
171
            return Y.ClassNameManager.getClassName
172
                .apply(Y.ClassNameManager,
173
                       [NAME].concat(toArray(arguments, 0, true)));
174
        }
175
    },
176
 
177
    /**
178
    Creates the `<thead>` Node content by assembling markup generated by
179
    populating the `ROW_TEMPLATE` and `CELL_TEMPLATE` templates with content
180
    from the `columns` property.
181
 
182
    @method render
183
    @chainable
184
    @since 3.5.0
185
    **/
186
    render: function () {
187
        var table    = this.get('container'),
188
            thead    = this.theadNode ||
189
                        (this.theadNode = this._createTHeadNode()),
190
            columns  = this.columns,
191
            defaults = {
192
                _colspan: 1,
193
                _rowspan: 1,
194
                abbr: '',
195
                title: ''
196
            },
197
            i, len, j, jlen, col, html, content, values;
198
 
199
        if (thead && columns) {
200
            html = '';
201
 
202
            if (columns.length) {
203
                for (i = 0, len = columns.length; i < len; ++i) {
204
                    content = '';
205
 
206
                    for (j = 0, jlen = columns[i].length; j < jlen; ++j) {
207
                        col = columns[i][j];
208
                        values = Y.merge(
209
                            defaults,
210
                            col, {
211
                                className: this.getClassName('header'),
212
                                content  : col.label || col.key ||
213
                                           ("Column " + (j + 1))
214
                            }
215
                        );
216
 
217
                        values._id = col._id ?
218
                            ' data-yui3-col-id="' + col._id + '"' : '';
219
 
220
                        if (col.abbr) {
221
                            values.abbr = ' abbr="' + col.abbr + '"';
222
                        }
223
 
224
                        if (col.title) {
225
                            values.title = ' title="' + col.title + '"';
226
                        }
227
 
228
                        if (col.className) {
229
                            values.className += ' ' + col.className;
230
                        }
231
 
232
                        if (col._first) {
233
                            values.className += ' ' + this.getClassName('first', 'header');
234
                        }
235
 
236
                        if (col._id) {
237
                            values.className +=
238
                                ' ' + this.getClassName('col', col._id);
239
                        }
240
 
241
                        content += fromTemplate(
242
                            col.headerTemplate || this.CELL_TEMPLATE, values);
243
                    }
244
 
245
                    html += fromTemplate(this.ROW_TEMPLATE, {
246
                        content: content
247
                    });
248
                }
249
            }
250
 
251
            thead.setHTML(html);
252
 
253
            if (thead.get('parentNode') !== table) {
254
                table.insertBefore(thead, table.one('tfoot, tbody'));
255
            }
256
        }
257
 
258
        this.bindUI();
259
 
260
        return this;
261
    },
262
 
263
    // -- Protected and private properties and methods ------------------------
264
 
265
    /**
266
    Handles changes in the source's columns attribute.  Redraws the headers.
267
 
268
    @method _afterColumnsChange
269
    @param {EventFacade} e The `columnsChange` event object
270
    @protected
271
    @since 3.5.0
272
    **/
273
    _afterColumnsChange: function (e) {
274
        this.columns = this._parseColumns(e.newVal);
275
 
276
        this.render();
277
    },
278
 
279
    /**
280
    Binds event subscriptions from the UI and the source (if assigned).
281
 
282
    @method bindUI
283
    @protected
284
    @since 3.5.0
285
    **/
286
    bindUI: function () {
287
        if (!this._eventHandles.columnsChange) {
288
            // TODO: How best to decouple this?
289
            this._eventHandles.columnsChange =
290
                this.after('columnsChange',
291
                    Y.bind('_afterColumnsChange', this));
292
        }
293
    },
294
 
295
    /**
296
    Creates the `<thead>` node that will store the header rows and cells.
297
 
298
    @method _createTHeadNode
299
    @return {Node}
300
    @protected
301
    @since 3.6.0
302
    **/
303
    _createTHeadNode: function () {
304
        return Y.Node.create(fromTemplate(this.THEAD_TEMPLATE, {
305
            className: this.getClassName('columns')
306
        }));
307
    },
308
 
309
    /**
310
    Destroys the instance.
311
 
312
    @method destructor
313
    @protected
314
    @since 3.5.0
315
    **/
316
    destructor: function () {
317
        (new Y.EventHandle(Y.Object.values(this._eventHandles))).detach();
318
    },
319
 
320
    /**
321
    Holds the event subscriptions needing to be detached when the instance is
322
    `destroy()`ed.
323
 
324
    @property _eventHandles
325
    @type {Object}
326
    @default undefined (initially unset)
327
    @protected
328
    @since 3.5.0
329
    **/
330
    //_eventHandles: null,
331
 
332
    /**
333
    Initializes the instance. Reads the following configuration properties:
334
 
335
      * `columns` - (REQUIRED) The initial column information
336
      * `host`    - The object to serve as source of truth for column info
337
 
338
    @method initializer
339
    @param {Object} config Configuration data
340
    @protected
341
    @since 3.5.0
342
    **/
343
    initializer: function (config) {
344
        this.host  = config.host;
345
        this.columns = this._parseColumns(config.columns);
346
 
347
        this._eventHandles = [];
348
    },
349
 
350
    /**
351
    Translate the input column format into a structure useful for rendering a
352
    `<thead>`, rows, and cells.  The structure of the input is expected to be a
353
    single array of objects, where each object corresponds to a `<th>`.  Those
354
    objects may contain a `children` property containing a similarly structured
355
    array to indicate the nested cells should be grouped under the parent
356
    column's colspan in a separate row of header cells. E.g.
357
 
358
    <pre><code>
359
    [
360
      { key: 'id' }, // no nesting
361
      { key: 'name', children: [
362
        { key: 'firstName', label: 'First' },
363
        { key: 'lastName',  label: 'Last' } ] }
364
    ]
365
    </code></pre>
366
 
367
    would indicate two header rows with the first column 'id' being assigned a
368
    `rowspan` of `2`, the 'name' column appearing in the first row with a
369
    `colspan` of `2`, and the 'firstName' and 'lastName' columns appearing in
370
    the second row, below the 'name' column.
371
 
372
    <pre>
373
    ---------------------
374
    |    |     name     |
375
    |    |---------------
376
    | id | First | Last |
377
    ---------------------
378
    </pre>
379
 
380
    Supported properties of the column objects include:
381
 
382
      * `label`    - The HTML content of the header cell.
383
      * `key`      - If `label` is not specified, the `key` is used for content.
384
      * `children` - Array of columns to appear below this column in the next
385
                     row.
386
      * `abbr`     - The content of the 'abbr' attribute of the `<th>`
387
      * `title`    - The content of the 'title' attribute of the `<th>`
388
      * `headerTemplate` - Overrides the instance's `CELL_TEMPLATE` for cells
389
        in this column only.
390
 
391
    The output structure is basically a simulation of the `<thead>` structure
392
    with arrays for rows and objects for cells.  Column objects have the
393
    following properties added to them:
394
 
395
      * `id`       - (Defaulted by DataTable) The id to assign the rendered
396
                     column
397
      * `_colspan` - Per the `<th>` attribute
398
      * `_rowspan` - Per the `<th>` attribute
399
      * `_parent`  - (Added by DataTable) If the column is a child of another
400
        column, this points to its parent column
401
 
402
    The column object is also used to provide values for {placeholder}
403
    replacement in the `CELL_TEMPLATE`, so you can modify the template and
404
    include other column object properties to populate them.
405
 
406
    @method _parseColumns
407
    @param {Object[]} data Array of column object data
408
    @return {Array[]} An array of arrays corresponding to the header row
409
            structure to render
410
    @protected
411
    @since 3.5.0
412
    **/
413
    _parseColumns: function (data) {
414
        var columns = [],
415
            stack = [],
416
            rowSpan = 1,
417
            entry, row, col, children, parent, i, len, j;
418
 
419
        if (isArray(data) && data.length) {
420
            // don't modify the input array
421
            data = data.slice();
422
 
423
            // First pass, assign colspans and calculate row count for
424
            // non-nested headers' rowspan
425
            stack.push([data, -1]);
426
 
427
            while (stack.length) {
428
                entry = stack[stack.length - 1];
429
                row   = entry[0];
430
                i     = entry[1] + 1;
431
 
432
                for (len = row.length; i < len; ++i) {
433
                    row[i] = col = Y.merge(row[i]);
434
                    children = col.children;
435
 
436
                    Y.stamp(col);
437
 
438
                    if (!col.id) {
439
                        col.id = Y.guid();
440
                    }
441
 
442
                    if (isArray(children) && children.length) {
443
                        stack.push([children, -1]);
444
                        entry[1] = i;
445
 
446
                        rowSpan = Math.max(rowSpan, stack.length);
447
 
448
                        // break to let the while loop process the children
449
                        break;
450
                    } else {
451
                        col._colspan = 1;
452
                    }
453
                }
454
 
455
                if (i >= len) {
456
                    // All columns in this row are processed
457
                    if (stack.length > 1) {
458
                        entry  = stack[stack.length - 2];
459
                        parent = entry[0][entry[1]];
460
 
461
                        parent._colspan = 0;
462
 
463
                        for (i = 0, len = row.length; i < len; ++i) {
464
                            // Can't use .length because in 3+ rows, colspan
465
                            // needs to aggregate the colspans of children
466
                            row[i]._parent   = parent;
467
                            parent._colspan += row[i]._colspan;
468
                        }
469
                    }
470
                    stack.pop();
471
                }
472
            }
473
 
474
            // Second pass, build row arrays and assign rowspan
475
            for (i = 0; i < rowSpan; ++i) {
476
                columns.push([]);
477
            }
478
 
479
            stack.push([data, -1]);
480
 
481
            while (stack.length) {
482
                entry = stack[stack.length - 1];
483
                row   = entry[0];
484
                i     = entry[1] + 1;
485
 
486
                for (len = row.length; i < len; ++i) {
487
                    col = row[i];
488
                    children = col.children;
489
 
490
                    columns[stack.length - 1].push(col);
491
 
492
                    entry[1] = i;
493
 
494
                    // collect the IDs of parent cols
495
                    col._headers = [col.id];
496
 
497
                    for (j = stack.length - 2; j >= 0; --j) {
498
                        parent = stack[j][0][stack[j][1]];
499
 
500
                        col._headers.unshift(parent.id);
501
                    }
502
 
503
                    if (children && children.length) {
504
                        // parent cells must assume rowspan 1 (long story)
505
 
506
                        // break to let the while loop process the children
507
                        stack.push([children, -1]);
508
                        break;
509
                    } else {
510
                        col._rowspan = rowSpan - stack.length + 1;
511
                    }
512
                }
513
 
514
                if (i >= len) {
515
                    // All columns in this row are processed
516
                    stack.pop();
517
                }
518
            }
519
        }
520
 
521
        for (i = 0, len = columns.length; i < len; i += col._rowspan) {
522
            col = columns[i][0];
523
 
524
            col._first = true;
525
        }
526
 
527
        return columns;
528
    }
529
});
530
 
531
 
532
}, '3.18.1', {"requires": ["datatable-core", "view", "classnamemanager"]});