~andreserl/maas/packaging_precise_rebase

« back to all changes in this revision

Viewing changes to debian/extras/jslibs/yui/datatable-core/datatable-core-debug.js

  • Committer: Andres Rodriguez
  • Date: 2013-03-20 18:12:30 UTC
  • mfrom: (145.2.22 precise.sru)
  • Revision ID: andreserl@ubuntu.com-20130320181230-6l5guc0nhlv2z4p7
Re-base againts latest quantal released branch towards SRU

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.5.1 (build 22)
 
3
Copyright 2012 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
6
*/
 
7
YUI.add('datatable-core', function(Y) {
 
8
 
 
9
/**
 
10
The core implementation of the `DataTable` and `DataTable.Base` Widgets.
 
11
 
 
12
@module datatable
 
13
@submodule datatable-core
 
14
@since 3.5.0
 
15
**/
 
16
 
 
17
var INVALID = Y.Attribute.INVALID_VALUE,
 
18
 
 
19
    Lang         = Y.Lang,
 
20
    isFunction   = Lang.isFunction,
 
21
    isObject     = Lang.isObject,
 
22
    isArray      = Lang.isArray,
 
23
    isString     = Lang.isString,
 
24
    isNumber     = Lang.isNumber,
 
25
    fromTemplate = Lang.sub,
 
26
 
 
27
    toArray = Y.Array,
 
28
 
 
29
    keys = Y.Object.keys,
 
30
 
 
31
    Table;
 
32
    
 
33
// TODO: add this to Y.Object
 
34
function flatten(o) {
 
35
    var flat = {},
 
36
        key;
 
37
 
 
38
    for (key in o) {
 
39
        // Not doing a hasOwnProperty check on purpose
 
40
        flat[key] = o[key];
 
41
    }
 
42
 
 
43
    return flat;
 
44
}
 
45
 
 
46
/**
 
47
_API docs for this extension are included in the DataTable class._
 
48
 
 
49
Class extension providing the core API and structure for the DataTable Widget.
 
50
 
 
51
Use this class extension with Widget or another Base-based superclass to create
 
52
the basic DataTable API and composing class structure.
 
53
 
 
54
Notable about this architecture is that rendering and UI event management for
 
55
the header, body, and footer of the table are deferred to configurable classes
 
56
in the `headerView`, `bodyView`, and `footerView` attributes.  In this extension
 
57
they have no default values, requiring implementers to supply their own classes
 
58
to render the table content.
 
59
 
 
60
@class DataTable.Core
 
61
@for DataTable
 
62
@since 3.5.0
 
63
**/
 
64
Table = Y.namespace('DataTable').Core = function () {};
 
65
 
 
66
Table.ATTRS = {
 
67
    /**
 
68
    Columns to include in the rendered table.
 
69
    
 
70
    If omitted, the attributes on the configured `recordType` or the first item
 
71
    in the `data` collection will be used as a source.
 
72
 
 
73
    This attribute takes an array of strings or objects (mixing the two is
 
74
    fine).  Each string or object is considered a column to be rendered.
 
75
    Strings are converted to objects, so `columns: ['first', 'last']` becomes
 
76
    `columns: [{ key: 'first' }, { key: 'last' }]`.
 
77
 
 
78
    DataTable.Core only concerns itself with a few properties of columns.
 
79
    All other properties are for use by the `headerView`, `bodyView`,
 
80
    `footerView`, and any class extensions or plugins on the final class or
 
81
    instance. See the descriptions of the view classes and feature class
 
82
    extensions and plugins for details on the specific properties they read or
 
83
    add to column definitions.
 
84
 
 
85
    The properties that are referenced or assigned are:
 
86
 
 
87
    * `key` - Used to identify the record field/attribute containing content for
 
88
      this column.  Also used to create a default Model if no `recordType` or
 
89
      `data` are provided during construction.  If `name` is not specified, this
 
90
      is assigned to the `_id` property (with added incrementer if the key is
 
91
      used by multiple columns).
 
92
    * `children` - Traversed to initialize nested column objects
 
93
    * `name` - Used in place of, or in addition to, the `key`.  Useful for
 
94
      columns that aren't bound to a field/attribute in the record data.  This
 
95
      is assigned to the `_id` property.
 
96
    * `id` - For backward compatibility.  Implementers can specify the id of
 
97
      the header cell.  This should be avoided, if possible, to avoid the
 
98
      potential for creating DOM elements with duplicate IDs.
 
99
    * `field` - For backward compatibility.  Implementers should use `name`.
 
100
    * `_id` - Assigned unique-within-this-instance id for a column.  By order
 
101
      of preference, assumes the value of `name`, `key`, `id`, or `_yuid`.
 
102
      This is used by the `bodyView` and `headerView` as well as feature module
 
103
      as a means to identify a specific column without ambiguity (such as
 
104
      multiple columns using the same `key`.
 
105
    * `_yuid` - Guid stamp assigned to the column object.
 
106
    * `_parent` - Assigned to all child columns, referencing their parent
 
107
      column.
 
108
 
 
109
    @attribute columns
 
110
    @type {Object[]|String[]}
 
111
    @default (from `recordType` ATTRS or first item in the `data`)
 
112
    @since 3.5.0
 
113
    **/
 
114
    columns: {
 
115
        // TODO: change to setter to coerce Columnset?
 
116
        validator: isArray,
 
117
        getter: '_getColumns'
 
118
    },
 
119
 
 
120
    /**
 
121
    Model subclass to use as the `model` for the ModelList stored in the `data`
 
122
    attribute.
 
123
 
 
124
    If not provided, it will try really hard to figure out what to use.  The
 
125
    following attempts will be made to set a default value:
 
126
    
 
127
    1. If the `data` attribute is set with a ModelList instance and its `model`
 
128
       property is set, that will be used.
 
129
    2. If the `data` attribute is set with a ModelList instance, and its
 
130
       `model` property is unset, but it is populated, the `ATTRS` of the
 
131
       `constructor of the first item will be used.
 
132
    3. If the `data` attribute is set with a non-empty array, a Model subclass
 
133
       will be generated using the keys of the first item as its `ATTRS` (see
 
134
       the `_createRecordClass` method).
 
135
    4. If the `columns` attribute is set, a Model subclass will be generated
 
136
       using the columns defined with a `key`. This is least desirable because
 
137
       columns can be duplicated or nested in a way that's not parsable.
 
138
    5. If neither `data` nor `columns` is set or populated, a change event
 
139
       subscriber will listen for the first to be changed and try all over
 
140
       again.
 
141
 
 
142
    @attribute recordType
 
143
    @type {Function}
 
144
    @default (see description)
 
145
    @since 3.5.0
 
146
    **/
 
147
    recordType: {
 
148
        setter: '_setRecordType',
 
149
        writeOnce: true
 
150
    },
 
151
 
 
152
    /**
 
153
    The collection of data records to display.  This attribute is a pass
 
154
    through to a `data` property, which is a ModelList instance.
 
155
 
 
156
    If this attribute is passed a ModelList or subclass, it will be assigned to
 
157
    the property directly.  If an array of objects is passed, a new ModelList
 
158
    will be created using the configured `recordType` as its `model` property
 
159
    and seeded with the array.
 
160
 
 
161
    Retrieving this attribute will return the ModelList stored in the `data`
 
162
    property.
 
163
 
 
164
    @attribute data
 
165
    @type {ModelList|Object[]}
 
166
    @default `new ModelList()`
 
167
    @since 3.5.0
 
168
    **/
 
169
    data: {
 
170
        value : [],
 
171
        setter: '_setData',
 
172
        getter: '_getData'
 
173
    },
 
174
 
 
175
    /**
 
176
    The class or object to use for rendering the `<thead>` and column headers
 
177
    for the table.  This attribute is responsible for populating the the
 
178
    instance's `head` property.
 
179
 
 
180
    If a class constructor (function) is passed, an instance of that clas will
 
181
    be created at `render()` time and assigned to `this.head`.  If an object is
 
182
    passed, `head` will be set immediately.
 
183
 
 
184
    Valid objects or classes will have a `render()` method, though it is
 
185
    recommended that they be subclasses of `Y.Base` or `Y.View`.  If the object
 
186
    or class supports events, its `addTarget()` method will be called to bubble
 
187
    its events to this instance.
 
188
 
 
189
    The core implementaion does not define a default `headerView`.  Classes
 
190
    built from this extension should define a default.
 
191
 
 
192
    @attribute headerView
 
193
    @type {Function|Object}
 
194
    @since 3.5.0
 
195
    **/
 
196
    headerView: {
 
197
        validator: '_validateView',
 
198
        writeOnce: true
 
199
    },
 
200
 
 
201
    /**
 
202
    The class or object to use for rendering the `<tfoot>` and any relevant
 
203
    content for it.  This attribute is responsible for populating the the
 
204
    instance's `foot` property.
 
205
 
 
206
    If a class constructor (function) is passed, an instance of that clas will
 
207
    be created at `render()` time and assigned to `this.foot`.  If an object is
 
208
    passed, `foot` will be set immediately.
 
209
 
 
210
    Valid objects or classes will have a `render()` method, though it is
 
211
    recommended that they be subclasses of `Y.Base` or `Y.View`.  If the object
 
212
    or class supports events, its `addTarget()` method will be called to bubble
 
213
    its events to this instance.
 
214
 
 
215
    The core implementaion does not define a default `footerView`.  Classes
 
216
    built from this extension should define a default if appropriate.
 
217
 
 
218
    @attribute footerView
 
219
    @type {Function|Object}
 
220
    @since 3.5.0
 
221
    **/
 
222
    footerView: {
 
223
        validator: '_validateView',
 
224
        writeOnce: true
 
225
    },
 
226
 
 
227
    /**
 
228
    The class or object to use for rendering the `<tbody>` or `<tbody>`s and
 
229
    all data row content for the table.  This attribute is responsible for
 
230
    populating the the instance's `body` property.
 
231
 
 
232
    If a class constructor (function) is passed, an instance of that clas will
 
233
    be created at `render()` time and assigned to `this.body`.  If an object is
 
234
    passed, `body` will be set immediately.
 
235
 
 
236
    Valid objects or classes will have a `render()` method, though it is
 
237
    recommended that they be subclasses of `Y.Base` or `Y.View`.  If the object
 
238
    or class supports events, its `addTarget()` method will be called to bubble
 
239
    its events to this instance.
 
240
 
 
241
    The core implementaion does not define a default `bodyView`.  Classes
 
242
    built from this extension should define a default.
 
243
 
 
244
    @attribute bodyView
 
245
    @type {Function|Object}
 
246
    @since 3.5.0
 
247
    **/
 
248
    bodyView: {
 
249
        validator: '_validateView',
 
250
        writeOnce: true
 
251
    },
 
252
 
 
253
    /**
 
254
    Content for the `<table summary="ATTRIBUTE VALUE HERE">`.  Values assigned
 
255
    to this attribute will be HTML escaped for security.
 
256
 
 
257
    @attribute summary
 
258
    @type {String}
 
259
    @default '' (empty string)
 
260
    @since 3.5.0
 
261
    **/
 
262
    summary: {
 
263
        value: '',
 
264
        // For paranoid reasons, the value is escaped on its way in because
 
265
        // rendering can be based on string concatenation.
 
266
        setter: Y.Escape.html
 
267
    },
 
268
 
 
269
    /**
 
270
    HTML content of an optional `<caption>` element to appear above the table.
 
271
    Leave this config unset or set to a falsy value to remove the caption.
 
272
 
 
273
    @attribute caption
 
274
    @type HTML
 
275
    @default '' (empty string)
 
276
    @since 3.5.0
 
277
    **/
 
278
    caption: {
 
279
        value: ''
 
280
    },
 
281
 
 
282
    /**
 
283
    Deprecated as of 3.5.0. Passes through to the `data` attribute.
 
284
 
 
285
    WARNING: `get('recordset')` will NOT return a Recordset instance as of
 
286
    3.5.0.  This is a break in backward compatibility.
 
287
 
 
288
    @attribute recordset
 
289
    @type {Object[]|Recordset}
 
290
    @deprecated Use the `data` attribute
 
291
    @since 3.5.0
 
292
    **/
 
293
    recordset: {
 
294
        setter: '_setRecordset',
 
295
        getter: '_getRecordset',
 
296
        lazyAdd: false
 
297
    },
 
298
 
 
299
    /**
 
300
    Deprecated as of 3.5.0. Passes through to the `columns` attribute.
 
301
 
 
302
    If a Columnset object is passed, its raw object and array column data will
 
303
    be extracted for use.
 
304
 
 
305
    WARNING: `get('columnset')` will NOT return a Columnset instance as of
 
306
    3.5.0.  This is a break in backward compatibility.
 
307
 
 
308
    @attribute columnset
 
309
    @type {Object[]|Columnset}
 
310
    @deprecated Use the `columns` attribute
 
311
    @since 3.5.0
 
312
    **/
 
313
    columnset: {
 
314
        setter: '_setColumnset',
 
315
        getter: '_getColumnset',
 
316
        lazyAdd: false
 
317
    }
 
318
};
 
319
 
 
320
Y.mix(Table.prototype, {
 
321
    // -- Instance properties -------------------------------------------------
 
322
 
 
323
    /**
 
324
    The HTML template used to create the caption Node if the `caption`
 
325
    attribute is set.
 
326
 
 
327
    @property CAPTION_TEMPLATE
 
328
    @type {HTML}
 
329
    @default '<caption class="{className}"/>'
 
330
    @since 3.5.0
 
331
    **/
 
332
    CAPTION_TEMPLATE: '<caption class="{className}"/>',
 
333
 
 
334
    /**
 
335
    The HTML template used to create the table Node.
 
336
 
 
337
    @property TABLE_TEMPLATE
 
338
    @type {HTML}
 
339
    @default '<table cellspacing="0" class="{className}"/>'
 
340
    @since 3.5.0
 
341
    **/
 
342
    TABLE_TEMPLATE  : '<table cellspacing="0" class="{className}"/>',
 
343
 
 
344
    /**
 
345
    HTML template used to create table's `<tbody>` if configured with a
 
346
    `bodyView`.
 
347
 
 
348
    @property TBODY_TEMPLATE
 
349
    @type {HTML}
 
350
    @default '<tbody class="{className}"/>'
 
351
    @since 3.5.0
 
352
    **/
 
353
    TBODY_TEMPLATE: '<tbody class="{className}"/>',
 
354
 
 
355
    /**
 
356
    Template used to create the table's `<tfoot>` if configured with a
 
357
    `footerView`.
 
358
 
 
359
    @property TFOOT_TEMPLATE
 
360
    @type {HTML}
 
361
    @default '<tfoot class="{className}"/>'
 
362
    @since 3.5.0
 
363
    **/
 
364
    TFOOT_TEMPLATE:
 
365
        '<tfoot class="{className}"/>',
 
366
 
 
367
    /**
 
368
    Template used to create the table's `<thead>` if configured with a
 
369
    `headerView`.
 
370
 
 
371
    @property THEAD_TEMPLATE
 
372
    @type {HTML}
 
373
    @default '<thead class="{className}"/>'
 
374
    @since 3.5.0
 
375
    **/
 
376
    THEAD_TEMPLATE:
 
377
        '<thead class="{className}"/>',
 
378
 
 
379
    /**
 
380
    The object or instance of the class assigned to `bodyView` that is
 
381
    responsible for rendering and managing the table's `<tbody>`(s) and its
 
382
    content.
 
383
 
 
384
    @property body
 
385
    @type {Object}
 
386
    @default undefined (initially unset)
 
387
    @since 3.5.0
 
388
    **/
 
389
    //body: null,
 
390
 
 
391
    /**
 
392
    The object or instance of the class assigned to `footerView` that is
 
393
    responsible for rendering and managing the table's `<tfoot>` and its
 
394
    content.
 
395
 
 
396
    @property foot
 
397
    @type {Object}
 
398
    @default undefined (initially unset)
 
399
    @since 3.5.0
 
400
    **/
 
401
    //foot: null,
 
402
 
 
403
    /**
 
404
    The object or instance of the class assigned to `headerView` that is
 
405
    responsible for rendering and managing the table's `<thead>` and its
 
406
    content.
 
407
 
 
408
    @property head
 
409
    @type {Object}
 
410
    @default undefined (initially unset)
 
411
    @since 3.5.0
 
412
    **/
 
413
    //head: null,
 
414
 
 
415
    /**
 
416
    The ModelList that manages the table's data.
 
417
 
 
418
    @property data
 
419
    @type {ModelList}
 
420
    @default undefined (initially unset)
 
421
    @since 3.5.0
 
422
    **/
 
423
    //data: null,
 
424
 
 
425
    // -- Public methods ------------------------------------------------------
 
426
    /**
 
427
    Pass through to `delegate()` called from the `contentBox`.
 
428
 
 
429
    @method delegate
 
430
    @param type {String} the event type to delegate
 
431
    @param fn {Function} the callback function to execute.  This function
 
432
                 will be provided the event object for the delegated event.
 
433
    @param spec {String|Function} a selector that must match the target of the
 
434
                 event or a function to test target and its parents for a match
 
435
    @param context {Object} optional argument that specifies what 'this' refers to
 
436
    @param args* {any} 0..n additional arguments to pass on to the callback
 
437
                 function.  These arguments will be added after the event object.
 
438
    @return {EventHandle} the detach handle
 
439
    @since 3.5.0
 
440
    **/
 
441
    delegate: function () {
 
442
        var contentBox = this.get('contentBox');
 
443
 
 
444
        return contentBox.delegate.apply(contentBox, arguments);
 
445
    },
 
446
 
 
447
    /**
 
448
    Returns the `<td>` Node from the given row and column index.  Alternately,
 
449
    the `seed` can be a Node.  If so, the nearest ancestor cell is returned.
 
450
    If the `seed` is a cell, it is returned.  If there is no cell at the given
 
451
    coordinates, `null` is returned.
 
452
 
 
453
    Optionally, include an offset array or string to return a cell near the
 
454
    cell identified by the `seed`.  The offset can be an array containing the
 
455
    number of rows to shift followed by the number of columns to shift, or one
 
456
    of "above", "below", "next", or "previous".
 
457
 
 
458
    <pre><code>// Previous cell in the previous row
 
459
    var cell = table.getCell(e.target, [-1, -1]);
 
460
 
 
461
    // Next cell
 
462
    var cell = table.getCell(e.target, 'next');
 
463
    var cell = table.getCell(e.taregt, [0, 1];</pre></code>
 
464
 
 
465
    This is actually just a pass through to the `bodyView` instance's method
 
466
    by the same name.
 
467
 
 
468
    @method getCell
 
469
    @param {Number[]|Node} seed Array of row and column indexes, or a Node that
 
470
        is either the cell itself or a descendant of one.
 
471
    @param {Number[]|String} [shift] Offset by which to identify the returned
 
472
        cell Node
 
473
    @return {Node}
 
474
    @since 3.5.0
 
475
    **/
 
476
    getCell: function (seed, shift) {
 
477
        return this.body && this.body.getCell &&
 
478
            this.body.getCell.apply(this.body, arguments);
 
479
    },
 
480
 
 
481
    /**
 
482
    Gets the column configuration object for the given key, name, or index.  For
 
483
    nested columns, `name` can be an array of indexes, each identifying the index
 
484
    of that column in the respective parent's "children" array.
 
485
 
 
486
    If you pass a column object, it will be returned.
 
487
 
 
488
    For columns with keys, you can also fetch the column with
 
489
    `instance.get('columns.foo')`.
 
490
 
 
491
    @method getColumn
 
492
    @param {String|Number|Number[]} name Key, "name", index, or index array to
 
493
                identify the column
 
494
    @return {Object} the column configuration object
 
495
    @since 3.5.0
 
496
    **/
 
497
    getColumn: function (name) {
 
498
        var col, columns, i, len, cols;
 
499
 
 
500
        if (isObject(name) && !isArray(name)) {
 
501
            // TODO: support getting a column from a DOM node - this will cross
 
502
            // the line into the View logic, so it should be relayed
 
503
 
 
504
            // Assume an object passed in is already a column def
 
505
            col = name;
 
506
        } else {
 
507
            col = this.get('columns.' + name);
 
508
        }
 
509
 
 
510
        if (col) {
 
511
            return col;
 
512
        }
 
513
 
 
514
        columns = this.get('columns');
 
515
 
 
516
        if (isNumber(name) || isArray(name)) {
 
517
            name = toArray(name);
 
518
            cols = columns;
 
519
 
 
520
            for (i = 0, len = name.length - 1; cols && i < len; ++i) {
 
521
                cols = cols[name[i]] && cols[name[i]].children;
 
522
            }
 
523
 
 
524
            return (cols && cols[name[i]]) || null;
 
525
        }
 
526
 
 
527
        return null;
 
528
    },
 
529
 
 
530
    /**
 
531
    Returns the Model associated to the record `id`, `clientId`, or index (not
 
532
    row index).  If none of those yield a Model from the `data` ModelList, the
 
533
    arguments will be passed to the `bodyView` instance's `getRecord` method
 
534
    if it has one.
 
535
 
 
536
    If no Model can be found, `null` is returned.
 
537
 
 
538
    @method getRecord
 
539
    @param {Number|String|Node} seed Record `id`, `clientId`, index, Node, or
 
540
        identifier for a row or child element
 
541
    @return {Model}
 
542
    @since 3.5.0
 
543
    **/
 
544
    getRecord: function (seed) {
 
545
        var record = this.data.getById(seed) || this.data.getByClientId(seed);
 
546
 
 
547
        if (!record) {
 
548
            if (isNumber(seed)) {
 
549
                record = this.data.item(seed);
 
550
            }
 
551
            
 
552
            if (!record && this.body && this.body.getRecord) {
 
553
                record = this.body.getRecord.apply(this.body, arguments);
 
554
            }
 
555
        }
 
556
 
 
557
        return record || null;
 
558
    },
 
559
 
 
560
    /**
 
561
    Returns the `<tr>` Node from the given row index, Model, or Model's
 
562
    `clientId`.  If the rows haven't been rendered yet, or if the row can't be
 
563
    found by the input, `null` is returned.
 
564
 
 
565
    This is actually just a pass through to the `bodyView` instance's method
 
566
    by the same name.
 
567
 
 
568
    @method getRow
 
569
    @param {Number|String|Model} id Row index, Model instance, or clientId
 
570
    @return {Node}
 
571
    @since 3.5.0
 
572
    **/
 
573
    getRow: function (id) {
 
574
        return this.body && this.body.getRow &&
 
575
            this.body.getRow.apply(this.body, arguments);
 
576
    },
 
577
 
 
578
    /**
 
579
    Updates the UI with the current attribute state.  Fires the `renderHeader`,
 
580
    `renderBody`, and `renderFooter` events;
 
581
 
 
582
    @method syncUI
 
583
    @since 3.5.0
 
584
    **/
 
585
    syncUI: function () {
 
586
        this._uiSetCaption(this.get('caption'));
 
587
        this._uiSetSummary(this.get('summary'));
 
588
 
 
589
        if (this.head) {
 
590
            this.fire('renderHeader', { view: this.head });
 
591
        }
 
592
        if (this.body) {
 
593
            this.fire('renderBody',   { view: this.body });
 
594
        }
 
595
        if (this.foot) {
 
596
            this.fire('renderFooter', { view: this.foot });
 
597
        }
 
598
    },
 
599
 
 
600
    // -- Protected and private properties and methods ------------------------
 
601
 
 
602
    /**
 
603
    Configuration object passed to the class constructor in `bodyView` during
 
604
    render.
 
605
 
 
606
    This property is set by the `_initViewConfig` method at instantiation.
 
607
 
 
608
    @property _bodyConfig
 
609
    @type {Object}
 
610
    @default undefined (initially unset)
 
611
    @protected
 
612
    @since 3.5.0
 
613
    **/
 
614
    //_bodyConfig: null,
 
615
 
 
616
    /**
 
617
    A map of column key to column configuration objects parsed from the
 
618
    `columns` attribute.
 
619
 
 
620
    @property _columnMap
 
621
    @type {Object}
 
622
    @default undefined (initially unset)
 
623
    @protected
 
624
    @since 3.5.0
 
625
    **/
 
626
    //_columnMap: null,
 
627
 
 
628
    /**
 
629
    Configuration object passed to the class constructor in `footerView` during
 
630
    render.
 
631
 
 
632
    This property is set by the `_initViewConfig` method at instantiation.
 
633
 
 
634
    @property _footerConfig
 
635
    @type {Object}
 
636
    @default undefined (initially unset)
 
637
    @protected
 
638
    @since 3.5.0
 
639
    **/
 
640
    //_footerConfig: null,
 
641
 
 
642
    /**
 
643
    Configuration object passed to the class constructor in `headerView` during
 
644
    render.
 
645
 
 
646
    This property is set by the `_initViewConfig` method at instantiation.
 
647
 
 
648
    @property _headerConfig
 
649
    @type {Object}
 
650
    @default undefined (initially unset)
 
651
    @protected
 
652
    @since 3.5.0
 
653
    **/
 
654
    //_headerConfig: null,
 
655
 
 
656
    /**
 
657
    The Node instance of the table containing the data rows.  This is set when
 
658
    the table is rendered.  It may also be set by progressive enhancement,
 
659
    though this extension does not provide the logic to parse from source.
 
660
 
 
661
    @property _tableNode
 
662
    @type {Node}
 
663
    @default undefined (initially unset)
 
664
    @protected
 
665
    @since 3.5.0
 
666
    **/
 
667
    //_tableNode: null,
 
668
 
 
669
    /**
 
670
    Configuration object used as the prototype of `_headerConfig`,
 
671
    `_bodyConfig`, and `_footerConfig`. Add properties to this object if you
 
672
    want them in all three of the other config objects.
 
673
 
 
674
    This property is set by the `_initViewConfig` method at instantiation.
 
675
 
 
676
    @property _viewConfig
 
677
    @type {Object}
 
678
    @default undefined (initially unset)
 
679
    @protected
 
680
    @since 3.5.0
 
681
    **/
 
682
    //_viewConfig: null,
 
683
 
 
684
    /**
 
685
    Updates the `_columnMap` property in response to changes in the `columns`
 
686
    attribute.
 
687
 
 
688
    @method _afterColumnsChange
 
689
    @param {EventFacade} e The `columnsChange` event object
 
690
    @protected
 
691
    @since 3.5.0
 
692
    **/
 
693
    _afterColumnsChange: function (e) {
 
694
        this._setColumnMap(e.newVal);
 
695
        this._setDisplayColumns(e.newVal);
 
696
    },
 
697
 
 
698
    /**
 
699
    Updates the `modelList` attributes of the rendered views in response to the
 
700
    `data` attribute being assigned a new ModelList.
 
701
 
 
702
    @method _afterDataChange
 
703
    @param {EventFacade} e the `dataChange` event
 
704
    @protected
 
705
    @since 3.5.0
 
706
    **/
 
707
    _afterDataChange: function (e) {
 
708
        var modelList = e.newVal;
 
709
 
 
710
        if (this.head) {
 
711
            this.head.set('modelList', modelList);
 
712
        }
 
713
        if (this.body) {
 
714
            this.body.set('modelList', modelList);
 
715
        }
 
716
        if (this.foot) {
 
717
            this.foot.set('modelList', modelList);
 
718
        }
 
719
    },
 
720
 
 
721
    /**
 
722
    Subscribes to attribute change events to update the UI.
 
723
 
 
724
    @method bindUI
 
725
    @protected
 
726
    @since 3.5.0
 
727
    **/
 
728
    bindUI: function () {
 
729
        // TODO: handle widget attribute changes
 
730
        this.after('dataChange', Y.bind('_afterDataChange', this));
 
731
    },
 
732
 
 
733
    /**
 
734
    Creates a Model subclass from an array of attribute names or an object of
 
735
    attribute definitions.  This is used to generate a class suitable to
 
736
    represent the data passed to the `data` attribute if no `recordType` is
 
737
    set.
 
738
 
 
739
    @method _createRecordClass
 
740
    @param {String[]|Object} attrs Names assigned to the Model subclass's
 
741
                `ATTRS` or its entire `ATTRS` definition object
 
742
    @return {Model}
 
743
    @protected
 
744
    @since 3.5.0
 
745
    **/
 
746
    _createRecordClass: function (attrs) {
 
747
        var ATTRS, i, len;
 
748
 
 
749
        if (isArray(attrs)) {
 
750
            ATTRS = {};
 
751
 
 
752
            for (i = 0, len = attrs.length; i < len; ++i) {
 
753
                ATTRS[attrs[i]] = {};
 
754
            }
 
755
        } else if (isObject(attrs)) {
 
756
            ATTRS = attrs;
 
757
        }
 
758
 
 
759
        return Y.Base.create('record', Y.Model, [], null, { ATTRS: ATTRS });
 
760
    },
 
761
 
 
762
    /**
 
763
    Creates the `<table>`.
 
764
 
 
765
    @method _createTable
 
766
    @return {Node} The `<table>` node
 
767
    @protected
 
768
    @since 3.5.0
 
769
    **/
 
770
    _createTable: function () {
 
771
        return Y.Node.create(fromTemplate(this.TABLE_TEMPLATE, {
 
772
            className: this.getClassName('table')
 
773
        })).empty();
 
774
    },
 
775
 
 
776
    /**
 
777
    Creates a `<tbody>` node from the `TBODY_TEMPLATE`.
 
778
 
 
779
    @method _createTBody
 
780
    @protected
 
781
    @since 3.5.0
 
782
    **/
 
783
    _createTBody: function () {
 
784
        return Y.Node.create(fromTemplate(this.TBODY_TEMPLATE, {
 
785
            className: this.getClassName('data')
 
786
        }));
 
787
    },
 
788
 
 
789
    /**
 
790
    Creates a `<tfoot>` node from the `TFOOT_TEMPLATE`.
 
791
 
 
792
    @method _createTFoot
 
793
    @protected
 
794
    @since 3.5.0
 
795
    **/
 
796
    _createTFoot: function () {
 
797
        return Y.Node.create(fromTemplate(this.TFOOT_TEMPLATE, {
 
798
            className: this.getClassName('footer')
 
799
        }));
 
800
    },
 
801
 
 
802
    /**
 
803
    Creates a `<thead>` node from the `THEAD_TEMPLATE`.
 
804
 
 
805
    @method _createTHead
 
806
    @protected
 
807
    @since 3.5.0
 
808
    **/
 
809
    _createTHead: function () {
 
810
        return Y.Node.create(fromTemplate(this.THEAD_TEMPLATE, {
 
811
            className: this.getClassName('columns')
 
812
        }));
 
813
    },
 
814
 
 
815
    /**
 
816
    Calls `render()` on the `bodyView` class instance.
 
817
 
 
818
    @method _defRenderBodyFn
 
819
    @param {EventFacade} e The renderBody event
 
820
    @protected
 
821
    @since 3.5.0
 
822
    **/
 
823
    _defRenderBodyFn: function (e) {
 
824
        e.view.render();
 
825
    },
 
826
 
 
827
    /**
 
828
    Calls `render()` on the `footerView` class instance.
 
829
 
 
830
    @method _defRenderFooterFn
 
831
    @param {EventFacade} e The renderFooter event
 
832
    @protected
 
833
    @since 3.5.0
 
834
    **/
 
835
    _defRenderFooterFn: function (e) {
 
836
        e.view.render();
 
837
    },
 
838
 
 
839
    /**
 
840
    Calls `render()` on the `headerView` class instance.
 
841
 
 
842
    @method _defRenderHeaderFn
 
843
    @param {EventFacade} e The renderHeader event
 
844
    @protected
 
845
    @since 3.5.0
 
846
    **/
 
847
    _defRenderHeaderFn: function (e) {
 
848
        e.view.render();
 
849
    },
 
850
 
 
851
    /**
 
852
    Renders the `<table>` and, if there are associated Views, the `<thead>`,
 
853
    `<tfoot>`, and `<tbody>` (empty until `syncUI`).
 
854
 
 
855
    Assigns the generated table nodes to the `_tableNode`, `_theadNode`,
 
856
    `_tfootNode`, and `_tbodyNode` properties.  Assigns the instantiated Views
 
857
    to the `head`, `foot`, and `body` properties.
 
858
 
 
859
 
 
860
    @method _defRenderTableFn
 
861
    @param {EventFacade} e The renderTable event
 
862
    @protected
 
863
    @since 3.5.0
 
864
    **/
 
865
    _defRenderTableFn: function (e) {
 
866
        var config;
 
867
 
 
868
        this._tableNode = this._createTable();
 
869
 
 
870
        if (e.headerView) {
 
871
            config = flatten(e.headerConfig || {});
 
872
            config.container = this._theadNode = this._createTHead();
 
873
 
 
874
            this.head = new e.headerView(config);
 
875
            this.head.addTarget(this);
 
876
 
 
877
            this._tableNode.insertBefore(this._theadNode,
 
878
                this._tableNode.one('> tfoot, > tbody'));
 
879
        }
 
880
 
 
881
        if (e.footerView) {
 
882
            config = flatten(e.footerConfig || {});
 
883
            config.container = this._tfootNode = this._createTFoot();
 
884
 
 
885
            this.foot = new e.footerView(config);
 
886
            this.foot.addTarget(this);
 
887
 
 
888
            this._tableNode.insertBefore(this._tfootNode,
 
889
                this._tableNode.one('> tbody'));
 
890
        }
 
891
 
 
892
        if (e.bodyView) {
 
893
            config = flatten(e.bodyConfig || {});
 
894
            config.container = this._tbodyNode = this._createTBody();
 
895
 
 
896
            this.body = new e.bodyView(config);
 
897
            this.body.addTarget(this);
 
898
 
 
899
            this._tableNode.append(this._tbodyNode);
 
900
        }
 
901
    },
 
902
 
 
903
    /**
 
904
    Contains column configuration objects for those columns believed to be intended for display in the `<tbody>`. Populated by `_setDisplayColumns`.
 
905
 
 
906
    @property _displayColumns
 
907
    @type {Object[]}
 
908
    @value undefined (initially not set)
 
909
    @protected
 
910
    @since 3.5.0
 
911
    **/
 
912
    //_displayColumns: null,
 
913
 
 
914
    /**
 
915
    The getter for the `columns` attribute.  Returns the array of column
 
916
    configuration objects if `instance.get('columns')` is called, or the
 
917
    specific column object if `instance.get('columns.columnKey')` is called.
 
918
 
 
919
    @method _getColumns
 
920
    @param {Object[]} columns The full array of column objects
 
921
    @param {String} name The attribute name requested
 
922
                         (e.g. 'columns' or 'columns.foo');
 
923
    @protected
 
924
    @since 3.5.0
 
925
    **/
 
926
    _getColumns: function (columns, name) {
 
927
        // Workaround for an attribute oddity (ticket #2529254)
 
928
        // getter is expected to return an object if get('columns.foo') is called.
 
929
        // Note 'columns.' is 8 characters
 
930
        return name.length > 8 ? this._columnMap : columns;
 
931
    },
 
932
 
 
933
    /**
 
934
    Relays the `get()` request for the deprecated `columnset` attribute to the
 
935
    `columns` attribute.
 
936
 
 
937
    THIS BREAKS BACKWARD COMPATIBILITY.  3.4.1 and prior implementations will
 
938
    expect a Columnset instance returned from `get('columnset')`.
 
939
 
 
940
    @method _getColumnset
 
941
    @param {Object} ignored The current value stored in the `columnset` state
 
942
    @param {String} name The attribute name requested
 
943
                         (e.g. 'columnset' or 'columnset.foo');
 
944
    @deprecated This will be removed with the `columnset` attribute in a future
 
945
                version.
 
946
    @protected
 
947
    @since 3.5.0
 
948
    **/
 
949
    _getColumnset: function (_, name) {
 
950
        return this.get(name.replace(/^columnset/, 'columns'));
 
951
    },
 
952
 
 
953
    /**
 
954
    The getter for the `data` attribute.  Returns the ModelList stored in the
 
955
    `data` property.  If the ModelList is not yet set, it returns the current
 
956
    raw data (presumably an empty array or `undefined`).
 
957
 
 
958
    @method _getData
 
959
    @param {Object[]|ModelList} val The current data stored in the attribute
 
960
    @protected
 
961
    @since 3.5.0
 
962
    **/
 
963
    _getData: function (val) {
 
964
        return this.data || val;
 
965
    },
 
966
 
 
967
    /**
 
968
    Initializes the `_columnMap` property from the configured `columns`
 
969
    attribute.  If `columns` is not set, but `recordType` is, it uses the
 
970
    `ATTRS` of that class.  If neither are set, it temporarily falls back to an
 
971
    empty array. `_initRecordType` will call back into this method if it finds
 
972
    the `columnMap` empty.
 
973
 
 
974
    @method _initColumns
 
975
    @protected
 
976
    @since 3.5.0
 
977
    **/
 
978
    _initColumns: function () {
 
979
        var columns    = this.get('columns'),
 
980
            recordType = this.get('recordType');
 
981
        
 
982
        // Default column definition from the configured recordType
 
983
        if (!columns) {
 
984
            // TODO: merge superclass attributes up to Model?
 
985
            columns = (recordType && recordType.ATTRS) ?
 
986
                      keys(recordType.ATTRS) : [];
 
987
 
 
988
            this.set('columns', columns, { silent: true });
 
989
        }
 
990
 
 
991
        this._setColumnMap(columns);
 
992
 
 
993
        this._setDisplayColumns(columns);
 
994
    },
 
995
 
 
996
    /**
 
997
    Initializes the instance's `data` property from the value of the `data`
 
998
    attribute.  If the attribute value is a ModelList, it is assigned directly
 
999
    to `this.data`.  If it is an array, a ModelList is created, its `model`
 
1000
    property is set to the configured `recordType` class, and it is seeded with
 
1001
    the array data.  This ModelList is then assigned to `this.data`.
 
1002
 
 
1003
    @method _initData
 
1004
    @protected
 
1005
    @since 3.5.0
 
1006
    **/
 
1007
    _initData: function () {
 
1008
        var data = this.get('data'),
 
1009
            recordType, values;
 
1010
 
 
1011
        if (isArray(data)) {
 
1012
            recordType = this.get('recordType');
 
1013
 
 
1014
            values = data;
 
1015
            data = new Y.ModelList();
 
1016
 
 
1017
            // _initRecordType is run before this, so recordType will be set
 
1018
            // if the data array had any records.  Otherwise, values is an
 
1019
            // empty array, so no need to call reset();
 
1020
            if (recordType) {
 
1021
                data.model = recordType;
 
1022
                data.reset(values, { silent: true });
 
1023
            }
 
1024
 
 
1025
            // Make sure the attribute state object contains the ModelList.
 
1026
            // TODO: maybe better would be to purge the attribute state value?
 
1027
            this.set('data', data, { silent: true });
 
1028
        }
 
1029
 
 
1030
        this.data = data;
 
1031
 
 
1032
        this.data.addTarget(this);
 
1033
    },
 
1034
 
 
1035
    /**
 
1036
    Publishes core events.
 
1037
 
 
1038
    @method _initEvents
 
1039
    @protected
 
1040
    @since 3.5.0
 
1041
    **/
 
1042
    _initEvents: function () {
 
1043
        this.publish({
 
1044
            // Y.bind used to allow late binding for method override support
 
1045
            renderTable : {
 
1046
                fireOnce: true,
 
1047
                defaultFn: Y.bind('_defRenderTableFn', this)
 
1048
            },
 
1049
            renderHeader: {
 
1050
                defaultFn: Y.bind('_defRenderHeaderFn', this)
 
1051
            },
 
1052
            renderBody  : {
 
1053
                defaultFn: Y.bind('_defRenderBodyFn', this)
 
1054
            },
 
1055
            renderFooter: {
 
1056
                defaultFn: Y.bind('_defRenderFooterFn', this)
 
1057
            }
 
1058
        });
 
1059
    },
 
1060
 
 
1061
    /**
 
1062
    Initializes the columns, `recordType` and data ModelList.
 
1063
 
 
1064
    @method initializer
 
1065
    @protected
 
1066
    @since 3.5.0
 
1067
    **/
 
1068
    initializer: function () {
 
1069
        this._initColumns();
 
1070
 
 
1071
        this._initRecordType();
 
1072
 
 
1073
        this._initData();
 
1074
 
 
1075
        this._initViewConfig();
 
1076
 
 
1077
        this._initEvents();
 
1078
 
 
1079
        this.after('columnsChange', this._afterColumnsChange);
 
1080
 
 
1081
        // FIXME: this needs to be added to Widget._buildCfg.custom
 
1082
        this._UI_ATTRS = {
 
1083
            BIND: this._UI_ATTRS.BIND.concat(['caption', 'summary']),
 
1084
            SYNC: this._UI_ATTRS.SYNC.concat(['caption', 'summary'])
 
1085
        };
 
1086
    },
 
1087
 
 
1088
    /**
 
1089
    If the `recordType` attribute is not set, this method attempts to set a
 
1090
    default value.
 
1091
 
 
1092
    It tries the following methods to determine a default:
 
1093
 
 
1094
    1. If the `data` attribute is set with a ModelList with a `model` property,
 
1095
       that class is used.
 
1096
    2. If the `data` attribute is set with a non-empty ModelList, the
 
1097
       `constructor` of the first item is used.
 
1098
    3. If the `data` attribute is set with a non-empty array and the first item
 
1099
       is a Base subclass, its constructor is used.
 
1100
    4. If the `data` attribute is set with a non-empty array a custom Model
 
1101
       subclass is generated using the keys of the first item as its `ATTRS`.
 
1102
    5. If the `_columnMap` property has keys, a custom Model subclass is
 
1103
       generated using those keys as its `ATTRS`.
 
1104
 
 
1105
    Of none of those are successful, it subscribes to the change events for
 
1106
    `columns`, `recordType`, and `data` to try again.
 
1107
 
 
1108
    If defaulting the `recordType` and the current `_columnMap` property is
 
1109
    empty, it will call `_initColumns`.
 
1110
 
 
1111
    @method _initRecordType
 
1112
    @protected
 
1113
    @since 3.5.0
 
1114
    **/
 
1115
    _initRecordType: function () {
 
1116
        var data, columns, recordType, handle, columnKeys;
 
1117
            
 
1118
        if (!this.get('recordType')) {
 
1119
            data    = this.get('data');
 
1120
            columns = this._columnMap;
 
1121
 
 
1122
            // Use the ModelList's specified Model class
 
1123
            if (data.model) {
 
1124
                recordType = data.model;
 
1125
 
 
1126
            // Or if not configured, use the construct of the first Model
 
1127
            } else if (data.size && data.size()) {
 
1128
                recordType = data.model = data.item(0).constructor;
 
1129
 
 
1130
            // Or if the data is an array, build a class from the first item
 
1131
            } else if (isArray(data) && data.length) {
 
1132
                recordType = (data[0].constructor.ATTRS) ?
 
1133
                    data[0].constructor :
 
1134
                    this._createRecordClass(keys(data[0]));
 
1135
 
 
1136
            // Or if the columns were defined, build a class from the keys
 
1137
            } else {
 
1138
                columnKeys = keys(columns);
 
1139
                
 
1140
                if (columnKeys.length) {
 
1141
                    recordType = this._createRecordClass(columnKeys);
 
1142
                }
 
1143
            }
 
1144
 
 
1145
            if (recordType) {
 
1146
                this.set('recordType', recordType, { silent: true });
 
1147
 
 
1148
                if (!columns || !columns.length) {
 
1149
                    this._initColumns();
 
1150
                }
 
1151
            } else {
 
1152
                // FIXME: Edge case race condition with
 
1153
                // new DT({ on/after: { <any of these changes> } }) OR
 
1154
                // new DT().on( <any of these changes> )
 
1155
                // where there's not enough info to assign this.data.model
 
1156
                // at construction. The on/constructor subscriptions will be
 
1157
                // executed before this subscription.
 
1158
                handle = this.after(
 
1159
                    ['columnsChange', 'recordTypeChange','dataChange'],
 
1160
                    function (e) {
 
1161
                        // manually batch detach rather than manage separate
 
1162
                        // subs in case the change was inadequate to populate
 
1163
                        // recordType. But subs must be detached because the
 
1164
                        // subscriber recurses to _initRecordType, which would
 
1165
                        // result in duplicate subs.
 
1166
                        handle.detach();
 
1167
 
 
1168
                        if (!this.data.model) {
 
1169
                            // FIXME: resubscribing if there's still not enough
 
1170
                            // info to populate recordType will place the new
 
1171
                            // subs later in the callback queue, opening the
 
1172
                            // race condition even more.
 
1173
                            this._initRecordType();
 
1174
 
 
1175
                            // If recordType isn't set yet, _initRecordType
 
1176
                            // will have recreated this subscription.
 
1177
                            this.data.model = this.get('recordType');
 
1178
                        }
 
1179
                    });
 
1180
            }
 
1181
        }
 
1182
    },
 
1183
 
 
1184
    /**
 
1185
    Initializes the `_viewConfig`, `_headerConfig`, `_bodyConfig`, and
 
1186
    `_footerConfig` properties with the configuration objects that will be
 
1187
    passed to the constructors of the `headerView`, `bodyView`, and
 
1188
    `footerView`.
 
1189
    
 
1190
    Extensions can add to the config objects to deliver custom parameters at
 
1191
    view instantiation.  `_viewConfig` is used as the prototype of the other
 
1192
    three config objects, so properties added here will be inherited by all
 
1193
    configs.
 
1194
 
 
1195
    @method _initViewConfig
 
1196
    @protected
 
1197
    @since 3.5.0
 
1198
    **/
 
1199
    _initViewConfig: function () {
 
1200
        this._viewConfig = {
 
1201
            source   : this,
 
1202
            cssPrefix: this._cssPrefix
 
1203
        };
 
1204
 
 
1205
        // Use prototypal inheritance to share common configs from _viewConfig
 
1206
        this._headerConfig = Y.Object(this._viewConfig);
 
1207
        this._bodyConfig   = Y.Object(this._viewConfig);
 
1208
        this._footerConfig = Y.Object(this._viewConfig);
 
1209
    },
 
1210
 
 
1211
    /**
 
1212
    Iterates the array of column configurations to capture all columns with a
 
1213
    `key` property.  Columns that are represented as strings will be replaced
 
1214
    with objects with the string assigned as the `key` property.  If a column
 
1215
    has a `children` property, it will be iterated, adding any nested column
 
1216
    keys to the returned map. There is no limit to the levels of nesting.
 
1217
 
 
1218
    All columns are assigned a `_yuid` stamp and `_id` property corresponding
 
1219
    to the column's configured `name` or `key` property with any spaces
 
1220
    replaced with dashes.  If the same `name` or `key` appears in multiple
 
1221
    columns, subsequent appearances will have their `_id` appended with an
 
1222
    incrementing number (e.g. if column "foo" is included in the `columns`
 
1223
    attribute twice, the first will get `_id` of "foo", and the second an `_id`
 
1224
    of "foo1").  Columns that are children of other columns will have the
 
1225
    `_parent` property added, assigned the column object to which they belong.
 
1226
 
 
1227
    The result is an object map with column keys as the property name and the
 
1228
    corresponding column object as the associated value.
 
1229
 
 
1230
    @method _parseColumns
 
1231
    @param {Object[]|String[]} columns The array of column names or
 
1232
                configuration objects to scan
 
1233
    @protected
 
1234
    @since 3.5.0
 
1235
    **/
 
1236
    _parseColumns: function (columns) {
 
1237
        var map  = {},
 
1238
            keys = {};
 
1239
        
 
1240
        function genId(name) {
 
1241
            // Sanitize the name for use in generated CSS classes.
 
1242
            // TODO: is there more to do for other uses of _id?
 
1243
            name = name.replace(/\s+/, '-');
 
1244
 
 
1245
            if (keys[name]) {
 
1246
                name += (keys[name]++);
 
1247
            } else {
 
1248
                keys[name] = 1;
 
1249
            }
 
1250
 
 
1251
            return name;
 
1252
        }
 
1253
 
 
1254
        function process(cols, parent) {
 
1255
            var i, len, col, key, yuid;
 
1256
 
 
1257
            for (i = 0, len = cols.length; i < len; ++i) {
 
1258
                col = cols[i];
 
1259
 
 
1260
                if (isString(col)) {
 
1261
                    // Update the array entry as well, so the attribute state array
 
1262
                    // contains the same objects.
 
1263
                    cols[i] = col = { key: col };
 
1264
                }
 
1265
 
 
1266
                yuid = Y.stamp(col);
 
1267
 
 
1268
                // For backward compatibility
 
1269
                if (!col.id) {
 
1270
                    // Implementers can shoot themselves in the foot by setting
 
1271
                    // this config property to a non-unique value
 
1272
                    col.id = yuid;
 
1273
                }
 
1274
                if (col.field) {
 
1275
                    // Field is now known as "name" to avoid confusion with data
 
1276
                    // fields or schema.resultFields
 
1277
                    col.name = col.field;
 
1278
                }
 
1279
 
 
1280
                if (parent) {
 
1281
                    col._parent = parent;
 
1282
                } else {
 
1283
                    delete col._parent;
 
1284
                }
 
1285
 
 
1286
                if (isArray(col.children)) {
 
1287
                    // Allow getColumn for parent columns if they have a name
 
1288
                    if (col.name) {
 
1289
                        map[genId(col.name)] = col;
 
1290
                    }
 
1291
 
 
1292
                    process(col.children, col);
 
1293
                } else {
 
1294
                    key = col.key;
 
1295
 
 
1296
                    // First in wins for multiple columns with the same key
 
1297
                    // because the first call to genId will return the same key,
 
1298
                    // which will then be overwritten by the subsequent
 
1299
                    // same-keyed column.  So table.getColumn(key) would return
 
1300
                    // the last same-keyed column.
 
1301
                    if (key && !map[key]) {
 
1302
                        map[key] = col;
 
1303
                    }
 
1304
 
 
1305
                    // Unique id based on the column's configured name or key,
 
1306
                    // falling back to the yuid.  Duplicates will have a counter
 
1307
                    // added to the end.
 
1308
                    col._id = genId(col.name || col.key || col.id);
 
1309
 
 
1310
                    //TODO: named columns can conflict with keyed columns
 
1311
                    map[col._id] = col;
 
1312
                }
 
1313
            }
 
1314
        }
 
1315
 
 
1316
        process(columns);
 
1317
 
 
1318
        return map;
 
1319
    },
 
1320
 
 
1321
    /**
 
1322
    Builds the table and attaches it to the DOM.  This requires the host class
 
1323
    to provide a `contentBox` attribute.  This is typically provided by Widget.
 
1324
 
 
1325
    @method renderUI
 
1326
    @protected
 
1327
    @since 3.5.0
 
1328
    **/
 
1329
    renderUI: function () {
 
1330
        var contentBox = this.get('contentBox'),
 
1331
            table;
 
1332
 
 
1333
        if (contentBox) {
 
1334
            // _viewConfig is the prototype for _headerConfig et al.
 
1335
            this._viewConfig.columns   = this.get('columns');
 
1336
            this._viewConfig.modelList = this.data;
 
1337
 
 
1338
            this.fire('renderTable', {
 
1339
                headerView  : this.get('headerView'),
 
1340
                headerConfig: this._headerConfig,
 
1341
 
 
1342
                bodyView    : this.get('bodyView'),
 
1343
                bodyConfig  : this._bodyConfig,
 
1344
 
 
1345
                footerView  : this.get('footerView'),
 
1346
                footerConfig: this._footerConfig
 
1347
            });
 
1348
 
 
1349
            table = this._tableNode;
 
1350
 
 
1351
            if (table) {
 
1352
                // off DOM or in an existing node attached to a different parentNode
 
1353
                if (!table.inDoc() || !table.ancestor().compareTo(contentBox)) {
 
1354
                    contentBox.append(table);
 
1355
                }
 
1356
            } else { Y.log('Problem rendering DataTable: table not created', 'warn', 'datatable'); // On the same line to allow builder to strip the else clause
 
1357
            }
 
1358
        } else { Y.log('Problem rendering DataTable: contentBox not found', 'warn', 'datatable'); // On the same line to allow builder to strip the else clause
 
1359
        }
 
1360
    },
 
1361
 
 
1362
    /**
 
1363
    Assigns the `_columnMap` property with the parsed results of the array of
 
1364
    column definitions passed.
 
1365
 
 
1366
    @method _setColumnMap
 
1367
    @param {Object[]|String[]} columns the raw column configuration objects or
 
1368
                                       key names
 
1369
    @protected
 
1370
    @since 3.5.0
 
1371
    **/
 
1372
    _setColumnMap: function (columns) {
 
1373
        this._columnMap = this._parseColumns(columns);
 
1374
    },
 
1375
 
 
1376
    /**
 
1377
    Relays attribute assignments of the deprecated `columnset` attribute to the
 
1378
    `columns` attribute.  If a Columnset is object is passed, its basic object
 
1379
    structure is mined.
 
1380
 
 
1381
    @method _setColumnset
 
1382
    @param {Array|Columnset} val The columnset value to relay
 
1383
    @deprecated This will be removed with the deprecated `columnset` attribute
 
1384
                in a later version.
 
1385
    @protected
 
1386
    @since 3.5.0
 
1387
    **/
 
1388
    _setColumnset: function (val) {
 
1389
        if (val && Y.Columnset && val instanceof Y.Columnset) {
 
1390
            val = val.get('definitions');
 
1391
        }
 
1392
 
 
1393
        this.set('columns', val);
 
1394
 
 
1395
        return isArray(val) ? val : INVALID;
 
1396
    },
 
1397
 
 
1398
    /**
 
1399
    Accepts an object with `each` and `getAttrs` (preferably a ModelList or
 
1400
    subclass) or an array of data objects.  If an array is passes, it will
 
1401
    create a ModelList to wrap the data.  In doing so, it will set the created
 
1402
    ModelList's `model` property to the class in the `recordType` attribute,
 
1403
    which will be defaulted if not yet set.
 
1404
 
 
1405
    If the `data` property is already set with a ModelList, passing an array as
 
1406
    the value will call the ModelList's `reset()` method with that array rather
 
1407
    than replacing the stored ModelList wholesale.
 
1408
 
 
1409
    Any non-ModelList-ish and non-array value is invalid.
 
1410
 
 
1411
    @method _setData
 
1412
    @protected
 
1413
    @since 3.5.0
 
1414
    **/
 
1415
    _setData: function (val) {
 
1416
        if (val === null) {
 
1417
            val = [];
 
1418
        }
 
1419
 
 
1420
        if (isArray(val)) {
 
1421
            if (this.data) {
 
1422
                if (!this.data.model && val.length) {
 
1423
                    // FIXME: this should happen only once, but this is a side
 
1424
                    // effect in the setter.  Bad form, but I need the model set
 
1425
                    // before calling reset()
 
1426
                    this.set('recordType', keys(val[0]));
 
1427
                }
 
1428
 
 
1429
                this.data.reset(val);
 
1430
 
 
1431
                // Return the instance ModelList to avoid storing unprocessed
 
1432
                // data in the state and their vivified Model representations in
 
1433
                // the instance's data property.  Decreases memory consumption.
 
1434
                val = this.data;
 
1435
            }
 
1436
            // else pass through the array data, but don't assign this.data
 
1437
            // Let the _initData process clean up.
 
1438
        } else if (val && val.each && val.getAttrs) {
 
1439
            this.data = val;
 
1440
            // TODO: return true to decrease memory footprint?
 
1441
        } else {
 
1442
            val = INVALID;
 
1443
        }
 
1444
 
 
1445
        return val;
 
1446
    },
 
1447
 
 
1448
    /**
 
1449
    Stores an array of columns intended for display in the `_displayColumns`
 
1450
    property.  This method assumes that if a column configuration object does
 
1451
    not have children, it is a display column.
 
1452
 
 
1453
    @method _setDisplayColumns
 
1454
    @param {Object[]} columns Column config array to extract display columns from
 
1455
    @protected
 
1456
    @since 3.5.0
 
1457
    **/
 
1458
    _setDisplayColumns: function (columns) {
 
1459
        function extract(cols) {
 
1460
            var display = [],
 
1461
                i, len, col;
 
1462
 
 
1463
            for (i = 0, len = cols.length; i < len; ++i) {
 
1464
                col = cols[i];
 
1465
 
 
1466
                if (col.children) {
 
1467
                    display.push.apply(display, extract(col.children));
 
1468
                } else {
 
1469
                    display.push(col);
 
1470
                }
 
1471
            }
 
1472
 
 
1473
            return display;
 
1474
        }
 
1475
 
 
1476
        this._displayColumns = extract(columns);
 
1477
    },
 
1478
 
 
1479
    /**
 
1480
    Relays the value assigned to the deprecated `recordset` attribute to the
 
1481
    `data` attribute.  If a Recordset instance is passed, the raw object data
 
1482
    will be culled from it.
 
1483
 
 
1484
    @method _setRecordset
 
1485
    @param {Object[]|Recordset} val The recordset value to relay
 
1486
    @deprecated This will be removed with the deprecated `recordset` attribute
 
1487
                in a later version.
 
1488
    @protected
 
1489
    @since 3.5.0
 
1490
    **/
 
1491
    _setRecordset: function (val) {
 
1492
        var data;
 
1493
 
 
1494
        if (val && Y.Recordset && val instanceof Y.Recordset) {
 
1495
            data = [];
 
1496
            val.each(function (record) {
 
1497
                data.push(record.get('data'));
 
1498
            });
 
1499
            val = data;
 
1500
        }
 
1501
 
 
1502
        this.set('data', val);
 
1503
 
 
1504
        return val;
 
1505
    },
 
1506
 
 
1507
    /**
 
1508
    Accepts a Base subclass (preferably a Model subclass). Alternately, it will
 
1509
    generate a custom Model subclass from an array of attribute names or an
 
1510
    object defining attributes and their respective configurations (it is
 
1511
    assigned as the `ATTRS` of the new class).
 
1512
 
 
1513
    Any other value is invalid.
 
1514
 
 
1515
    @method _setRecordType
 
1516
    @param {Function|String[]|Object} val The Model subclass, array of
 
1517
            attribute names, or the `ATTRS` definition for a custom model
 
1518
            subclass
 
1519
    @return {Function} A Base/Model subclass
 
1520
    @protected
 
1521
    @since 3.5.0
 
1522
    **/
 
1523
    _setRecordType: function (val) {
 
1524
        var modelClass;
 
1525
 
 
1526
        // Duck type based on known/likely consumed APIs
 
1527
        if (isFunction(val) && val.prototype.set && val.prototype.getAttrs) {
 
1528
            modelClass = val;
 
1529
        } else if (isObject(val)) {
 
1530
            modelClass = this._createRecordClass(val);
 
1531
        }
 
1532
 
 
1533
        return modelClass || INVALID;
 
1534
    },
 
1535
 
 
1536
    /**
 
1537
    Creates, removes, or updates the table's `<caption>` element per the input
 
1538
    value.  Empty values result in the caption being removed.
 
1539
 
 
1540
    @method _uiSetCaption
 
1541
    @param {HTML} htmlContent The content to populate the table caption
 
1542
    @protected
 
1543
    @since 3.5.0
 
1544
    **/
 
1545
    _uiSetCaption: function (htmlContent) {
 
1546
        var table   = this._tableNode,
 
1547
            caption = this._captionNode;
 
1548
 
 
1549
        if (htmlContent) {
 
1550
            if (!caption) {
 
1551
                this._captionNode = caption = Y.Node.create(
 
1552
                    fromTemplate(this.CAPTION_TEMPLATE, {
 
1553
                        className: this.getClassName('caption')
 
1554
                    }));
 
1555
 
 
1556
                table.prepend(this._captionNode);
 
1557
            }
 
1558
 
 
1559
            caption.setContent(htmlContent);
 
1560
 
 
1561
        } else if (caption) {
 
1562
            caption.remove(true);
 
1563
 
 
1564
            delete this._captionNode;
 
1565
        }
 
1566
    },
 
1567
 
 
1568
    /**
 
1569
    Updates the table's `summary` attribute with the input value.
 
1570
 
 
1571
    @method _uiSetSummary
 
1572
    @protected
 
1573
    @since 3.5.0
 
1574
    **/
 
1575
    _uiSetSummary: function (summary) {
 
1576
        if (summary) {
 
1577
            this._tableNode.setAttribute('summary', summary);
 
1578
        } else {
 
1579
            this._tableNode.removeAttribute('summary');
 
1580
        }
 
1581
    },
 
1582
 
 
1583
    /**
 
1584
    Sets the `boundingBox` and table width per the input value.
 
1585
 
 
1586
    @method _uiSetWidth
 
1587
    @param {Number|String} width The width to make the table
 
1588
    @protected
 
1589
    @since 3.5.0
 
1590
    **/
 
1591
    _uiSetWidth: function (width) {
 
1592
        var table = this._tableNode;
 
1593
 
 
1594
        if (isNumber(width)) {
 
1595
            // DEF_UNIT from Widget
 
1596
            width += this.DEF_UNIT;
 
1597
        }
 
1598
 
 
1599
        if (isString(width)) {
 
1600
            this._uiSetDim('width', width);
 
1601
 
 
1602
            // Table width needs to account for borders
 
1603
            table.setStyle('width', !width ? '' :
 
1604
                (this.get('boundingBox').get('offsetWidth') -
 
1605
                 (parseInt(table.getComputedStyle('borderLeftWidth'), 10)|0) -
 
1606
                 (parseInt(table.getComputedStyle('borderLeftWidth'), 10)|0)) +
 
1607
                 'px');
 
1608
 
 
1609
            table.setStyle('width', width);
 
1610
        }
 
1611
    },
 
1612
 
 
1613
    /**
 
1614
    Verifies the input value is a function with a `render` method on its
 
1615
    prototype.  `null` is also accepted to remove the default View.
 
1616
 
 
1617
    @method _validateView
 
1618
    @protected
 
1619
    @since 3.5.0
 
1620
    **/
 
1621
    _validateView: function (val) {
 
1622
        // TODO support View instances?
 
1623
        return val === null || (isFunction(val) && val.prototype.render);
 
1624
    }
 
1625
});
 
1626
 
 
1627
 
 
1628
}, '3.5.1' ,{requires:['escape','model-list','node-event-delegate']});