Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('datatable-core', function (Y, NAME) {
2
 
3
/**
4
The core implementation of the `DataTable` and `DataTable.Base` Widgets.
5
 
6
@module datatable
7
@submodule datatable-core
8
@since 3.5.0
9
**/
10
 
11
var INVALID = Y.Attribute.INVALID_VALUE,
12
 
13
    Lang         = Y.Lang,
14
    isFunction   = Lang.isFunction,
15
    isObject     = Lang.isObject,
16
    isArray      = Lang.isArray,
17
    isString     = Lang.isString,
18
    isNumber     = Lang.isNumber,
19
 
20
    toArray = Y.Array,
21
 
22
    keys = Y.Object.keys,
23
 
24
    Table;
25
 
26
/**
27
_API docs for this extension are included in the DataTable class._
28
 
29
Class extension providing the core API and structure for the DataTable Widget.
30
 
31
Use this class extension with Widget or another Base-based superclass to create
32
the basic DataTable model API and composing class structure.
33
 
34
@class DataTable.Core
35
@for DataTable
36
@since 3.5.0
37
**/
38
Table = Y.namespace('DataTable').Core = function () {};
39
 
40
Table.ATTRS = {
41
    /**
42
    Columns to include in the rendered table.
43
 
44
    If omitted, the attributes on the configured `recordType` or the first item
45
    in the `data` collection will be used as a source.
46
 
47
    This attribute takes an array of strings or objects (mixing the two is
48
    fine).  Each string or object is considered a column to be rendered.
49
    Strings are converted to objects, so `columns: ['first', 'last']` becomes
50
    `columns: [{ key: 'first' }, { key: 'last' }]`.
51
 
52
    DataTable.Core only concerns itself with a few properties of columns.
53
    These properties are:
54
 
55
    * `key` - Used to identify the record field/attribute containing content for
56
      this column.  Also used to create a default Model if no `recordType` or
57
      `data` are provided during construction.  If `name` is not specified, this
58
      is assigned to the `_id` property (with added incrementer if the key is
59
      used by multiple columns).
60
    * `children` - Traversed to initialize nested column objects
61
    * `name` - Used in place of, or in addition to, the `key`.  Useful for
62
      columns that aren't bound to a field/attribute in the record data.  This
63
      is assigned to the `_id` property.
64
    * `id` - For backward compatibility.  Implementers can specify the id of
65
      the header cell.  This should be avoided, if possible, to avoid the
66
      potential for creating DOM elements with duplicate IDs.
67
    * `field` - For backward compatibility.  Implementers should use `name`.
68
    * `_id` - Assigned unique-within-this-instance id for a column.  By order
69
      of preference, assumes the value of `name`, `key`, `id`, or `_yuid`.
70
      This is used by the rendering views as well as feature module
71
      as a means to identify a specific column without ambiguity (such as
72
      multiple columns using the same `key`.
73
    * `_yuid` - Guid stamp assigned to the column object.
74
    * `_parent` - Assigned to all child columns, referencing their parent
75
      column.
76
 
77
    @attribute columns
78
    @type {Object[]|String[]}
79
    @default (from `recordType` ATTRS or first item in the `data`)
80
    @since 3.5.0
81
    **/
82
    columns: {
83
        // TODO: change to setter to clone input array/objects
84
        validator: isArray,
85
        setter: '_setColumns',
86
        getter: '_getColumns'
87
    },
88
 
89
    /**
90
    Model subclass to use as the `model` for the ModelList stored in the `data`
91
    attribute.
92
 
93
    If not provided, it will try really hard to figure out what to use.  The
94
    following attempts will be made to set a default value:
95
 
96
    1. If the `data` attribute is set with a ModelList instance and its `model`
97
       property is set, that will be used.
98
    2. If the `data` attribute is set with a ModelList instance, and its
99
       `model` property is unset, but it is populated, the `ATTRS` of the
100
       `constructor of the first item will be used.
101
    3. If the `data` attribute is set with a non-empty array, a Model subclass
102
       will be generated using the keys of the first item as its `ATTRS` (see
103
       the `_createRecordClass` method).
104
    4. If the `columns` attribute is set, a Model subclass will be generated
105
       using the columns defined with a `key`. This is least desirable because
106
       columns can be duplicated or nested in a way that's not parsable.
107
    5. If neither `data` nor `columns` is set or populated, a change event
108
       subscriber will listen for the first to be changed and try all over
109
       again.
110
 
111
    @attribute recordType
112
    @type {Function}
113
    @default (see description)
114
    @since 3.5.0
115
    **/
116
    recordType: {
117
        getter: '_getRecordType',
118
        setter: '_setRecordType'
119
    },
120
 
121
    /**
122
    The collection of data records to display.  This attribute is a pass
123
    through to a `data` property, which is a ModelList instance.
124
 
125
    If this attribute is passed a ModelList or subclass, it will be assigned to
126
    the property directly.  If an array of objects is passed, a new ModelList
127
    will be created using the configured `recordType` as its `model` property
128
    and seeded with the array.
129
 
130
    Retrieving this attribute will return the ModelList stored in the `data`
131
    property.
132
 
133
    @attribute data
134
    @type {ModelList|Object[]}
135
    @default `new ModelList()`
136
    @since 3.5.0
137
    **/
138
    data: {
139
        valueFn: '_initData',
140
        setter : '_setData',
141
        lazyAdd: false
142
    },
143
 
144
    /**
145
    Content for the `<table summary="ATTRIBUTE VALUE HERE">`.  Values assigned
146
    to this attribute will be HTML escaped for security.
147
 
148
    @attribute summary
149
    @type {String}
150
    @default '' (empty string)
151
    @since 3.5.0
152
    **/
153
    //summary: {},
154
 
155
    /**
156
    HTML content of an optional `<caption>` element to appear above the table.
157
    Leave this config unset or set to a falsy value to remove the caption.
158
 
159
    @attribute caption
160
    @type HTML
161
    @default '' (empty string)
162
    @since 3.5.0
163
    **/
164
    //caption: {},
165
 
166
    /**
167
    Deprecated as of 3.5.0. Passes through to the `data` attribute.
168
 
169
    WARNING: `get('recordset')` will NOT return a Recordset instance as of
170
    3.5.0.  This is a break in backward compatibility.
171
 
172
    @attribute recordset
173
    @type {Object[]|Recordset}
174
    @deprecated Use the `data` attribute
175
    @since 3.5.0
176
    **/
177
    recordset: {
178
        setter: '_setRecordset',
179
        getter: '_getRecordset',
180
        lazyAdd: false
181
    },
182
 
183
    /**
184
    Deprecated as of 3.5.0. Passes through to the `columns` attribute.
185
 
186
    WARNING: `get('columnset')` will NOT return a Columnset instance as of
187
    3.5.0.  This is a break in backward compatibility.
188
 
189
    @attribute columnset
190
    @type {Object[]}
191
    @deprecated Use the `columns` attribute
192
    @since 3.5.0
193
    **/
194
    columnset: {
195
        setter: '_setColumnset',
196
        getter: '_getColumnset',
197
        lazyAdd: false
198
    }
199
};
200
 
201
Y.mix(Table.prototype, {
202
    // -- Instance properties -------------------------------------------------
203
    /**
204
    The ModelList that manages the table's data.
205
 
206
    @property data
207
    @type {ModelList}
208
    @default undefined (initially unset)
209
    @since 3.5.0
210
    **/
211
    //data: null,
212
 
213
    // -- Public methods ------------------------------------------------------
214
 
215
    /**
216
    Gets the column configuration object for the given key, name, or index.  For
217
    nested columns, `name` can be an array of indexes, each identifying the index
218
    of that column in the respective parent's "children" array.
219
 
220
    If you pass a column object, it will be returned.
221
 
222
    For columns with keys, you can also fetch the column with
223
    `instance.get('columns.foo')`.
224
 
225
    @method getColumn
226
    @param {String|Number|Number[]} name Key, "name", index, or index array to
227
                identify the column
228
    @return {Object} the column configuration object
229
    @since 3.5.0
230
    **/
231
    getColumn: function (name) {
232
        var col, columns, i, len, cols;
233
 
234
        if (isObject(name) && !isArray(name)) {
235
            if (name && name._node) {
236
                col = this.body.getColumn(name);
237
            } else {
238
                col = name;
239
            }
240
        } else {
241
            col = this.get('columns.' + name);
242
        }
243
 
244
        if (col) {
245
            return col;
246
        }
247
 
248
        columns = this.get('columns');
249
 
250
        if (isNumber(name) || isArray(name)) {
251
            name = toArray(name);
252
            cols = columns;
253
 
254
            for (i = 0, len = name.length - 1; cols && i < len; ++i) {
255
                cols = cols[name[i]] && cols[name[i]].children;
256
            }
257
 
258
            return (cols && cols[name[i]]) || null;
259
        }
260
 
261
        return null;
262
    },
263
 
264
    /**
265
    Returns the Model associated to the record `id`, `clientId`, or index (not
266
    row index).  If none of those yield a Model from the `data` ModelList, the
267
    arguments will be passed to the `view` instance's `getRecord` method
268
    if it has one.
269
 
270
    If no Model can be found, `null` is returned.
271
 
272
    @method getRecord
273
    @param {Number|String|Node} seed Record `id`, `clientId`, index, Node, or
274
        identifier for a row or child element
275
    @return {Model}
276
    @since 3.5.0
277
    **/
278
    getRecord: function (seed) {
279
        var record = this.data.getById(seed) || this.data.getByClientId(seed);
280
 
281
        if (!record) {
282
            if (isNumber(seed)) {
283
                record = this.data.item(seed);
284
            }
285
 
286
            // TODO: this should be split out to base somehow
287
            if (!record && this.view && this.view.getRecord) {
288
                record = this.view.getRecord.apply(this.view, arguments);
289
            }
290
        }
291
 
292
        return record || null;
293
    },
294
 
295
    // -- Protected and private properties and methods ------------------------
296
 
297
    /**
298
    This tells `Y.Base` that it should create ad-hoc attributes for config
299
    properties passed to DataTable's constructor. This is useful for setting
300
    configurations on the DataTable that are intended for the rendering View(s).
301
 
302
    @property _allowAdHocAttrs
303
    @type Boolean
304
    @default true
305
    @protected
306
    @since 3.6.0
307
    **/
308
    _allowAdHocAttrs: true,
309
 
310
    /**
311
    A map of column key to column configuration objects parsed from the
312
    `columns` attribute.
313
 
314
    @property _columnMap
315
    @type {Object}
316
    @default undefined (initially unset)
317
    @protected
318
    @since 3.5.0
319
    **/
320
    //_columnMap: null,
321
 
322
    /**
323
    The Node instance of the table containing the data rows.  This is set when
324
    the table is rendered.  It may also be set by progressive enhancement,
325
    though this extension does not provide the logic to parse from source.
326
 
327
    @property _tableNode
328
    @type {Node}
329
    @default undefined (initially unset)
330
    @protected
331
    @since 3.5.0
332
    **/
333
    //_tableNode: null,
334
 
335
    /**
336
    Updates the `_columnMap` property in response to changes in the `columns`
337
    attribute.
338
 
339
    @method _afterColumnsChange
340
    @param {EventFacade} e The `columnsChange` event object
341
    @protected
342
    @since 3.5.0
343
    **/
344
    _afterColumnsChange: function (e) {
345
        this._setColumnMap(e.newVal);
346
    },
347
 
348
    /**
349
    Updates the `modelList` attributes of the rendered views in response to the
350
    `data` attribute being assigned a new ModelList.
351
 
352
    @method _afterDataChange
353
    @param {EventFacade} e the `dataChange` event
354
    @protected
355
    @since 3.5.0
356
    **/
357
    _afterDataChange: function (e) {
358
        var modelList = e.newVal;
359
 
360
        this.data = e.newVal;
361
 
362
        if (!this.get('columns') && modelList.size()) {
363
            // TODO: this will cause a re-render twice because the Views are
364
            // subscribed to columnsChange
365
            this._initColumns();
366
        }
367
    },
368
 
369
    /**
370
    Assigns to the new recordType as the model for the data ModelList
371
 
372
    @method _afterRecordTypeChange
373
    @param {EventFacade} e recordTypeChange event
374
    @protected
375
    @since 3.6.0
376
    **/
377
    _afterRecordTypeChange: function (e) {
378
        var data = this.data.toJSON();
379
 
380
        this.data.model = e.newVal;
381
 
382
        this.data.reset(data);
383
 
384
        if (!this.get('columns') && data) {
385
            if (data.length) {
386
                this._initColumns();
387
            } else {
388
                this.set('columns', keys(e.newVal.ATTRS));
389
            }
390
        }
391
    },
392
 
393
    /**
394
    Creates a Model subclass from an array of attribute names or an object of
395
    attribute definitions.  This is used to generate a class suitable to
396
    represent the data passed to the `data` attribute if no `recordType` is
397
    set.
398
 
399
    @method _createRecordClass
400
    @param {String[]|Object} attrs Names assigned to the Model subclass's
401
                `ATTRS` or its entire `ATTRS` definition object
402
    @return {Model}
403
    @protected
404
    @since 3.5.0
405
    **/
406
    _createRecordClass: function (attrs) {
407
        var ATTRS, i, len;
408
 
409
        if (isArray(attrs)) {
410
            ATTRS = {};
411
 
412
            for (i = 0, len = attrs.length; i < len; ++i) {
413
                ATTRS[attrs[i]] = {};
414
            }
415
        } else if (isObject(attrs)) {
416
            ATTRS = attrs;
417
        }
418
 
419
        return Y.Base.create('record', Y.Model, [], null, { ATTRS: ATTRS });
420
    },
421
 
422
    /**
423
    Tears down the instance.
424
 
425
    @method destructor
426
    @protected
427
    @since 3.6.0
428
    **/
429
    destructor: function () {
430
        new Y.EventHandle(Y.Object.values(this._eventHandles)).detach();
431
    },
432
 
433
    /**
434
    The getter for the `columns` attribute.  Returns the array of column
435
    configuration objects if `instance.get('columns')` is called, or the
436
    specific column object if `instance.get('columns.columnKey')` is called.
437
 
438
    @method _getColumns
439
    @param {Object[]} columns The full array of column objects
440
    @param {String} name The attribute name requested
441
                         (e.g. 'columns' or 'columns.foo');
442
    @protected
443
    @since 3.5.0
444
    **/
445
    _getColumns: function (columns, name) {
446
        // Workaround for an attribute oddity (ticket #2529254)
447
        // getter is expected to return an object if get('columns.foo') is called.
448
        // Note 'columns.' is 8 characters
449
        return name.length > 8 ? this._columnMap : columns;
450
    },
451
 
452
    /**
453
    Relays the `get()` request for the deprecated `columnset` attribute to the
454
    `columns` attribute.
455
 
456
    THIS BREAKS BACKWARD COMPATIBILITY.  3.4.1 and prior implementations will
457
    expect a Columnset instance returned from `get('columnset')`.
458
 
459
    @method _getColumnset
460
    @param {Object} ignored The current value stored in the `columnset` state
461
    @param {String} name The attribute name requested
462
                         (e.g. 'columnset' or 'columnset.foo');
463
    @deprecated This will be removed with the `columnset` attribute in a future
464
                version.
465
    @protected
466
    @since 3.5.0
467
    **/
468
    _getColumnset: function (_, name) {
469
        return this.get(name.replace(/^columnset/, 'columns'));
470
    },
471
 
472
    /**
473
    Returns the Model class of the instance's `data` attribute ModelList.  If
474
    not set, returns the explicitly configured value.
475
 
476
    @method _getRecordType
477
    @param {Model} val The currently configured value
478
    @return {Model}
479
    **/
480
    _getRecordType: function (val) {
481
        // Prefer the value stored in the attribute because the attribute
482
        // change event defaultFn sets e.newVal = this.get('recordType')
483
        // before notifying the after() subs.  But if this getter returns
484
        // this.data.model, then after() subs would get e.newVal === previous
485
        // model before _afterRecordTypeChange can set
486
        // this.data.model = e.newVal
487
        return val || (this.data && this.data.model);
488
    },
489
 
490
    /**
491
    Initializes the `_columnMap` property from the configured `columns`
492
    attribute.  If `columns` is not set, but there are records in the `data`
493
    ModelList, use
494
    `ATTRS` of that class.
495
 
496
    @method _initColumns
497
    @protected
498
    @since 3.5.0
499
    **/
500
    _initColumns: function () {
501
        var columns = this.get('columns') || [],
502
            item;
503
 
504
        // Default column definition from the configured recordType
505
        if (!columns.length && this.data.size()) {
506
            // TODO: merge superclass attributes up to Model?
507
            item = this.data.item(0);
508
 
509
            if (item.toJSON) {
510
                item = item.toJSON();
511
            }
512
 
513
            this.set('columns', keys(item));
514
        }
515
 
516
        this._setColumnMap(columns);
517
    },
518
 
519
    /**
520
    Sets up the change event subscriptions to maintain internal state.
521
 
522
    @method _initCoreEvents
523
    @protected
524
    @since 3.6.0
525
    **/
526
    _initCoreEvents: function () {
527
        this._eventHandles.coreAttrChanges = this.after({
528
            columnsChange   : Y.bind('_afterColumnsChange', this),
529
            recordTypeChange: Y.bind('_afterRecordTypeChange', this),
530
            dataChange      : Y.bind('_afterDataChange', this)
531
        });
532
    },
533
 
534
    /**
535
    Defaults the `data` attribute to an empty ModelList if not set during
536
    construction.  Uses the configured `recordType` for the ModelList's `model`
537
    proeprty if set.
538
 
539
    @method _initData
540
    @protected
541
    @return {ModelList}
542
    @since 3.6.0
543
    **/
544
    _initData: function () {
545
        var recordType = this.get('recordType'),
546
            // TODO: LazyModelList if recordType doesn't have complex ATTRS
547
            modelList = new Y.ModelList();
548
 
549
        if (recordType) {
550
            modelList.model = recordType;
551
        }
552
 
553
        return modelList;
554
    },
555
 
556
    /**
557
    Initializes the instance's `data` property from the value of the `data`
558
    attribute.  If the attribute value is a ModelList, it is assigned directly
559
    to `this.data`.  If it is an array, a ModelList is created, its `model`
560
    property is set to the configured `recordType` class, and it is seeded with
561
    the array data.  This ModelList is then assigned to `this.data`.
562
 
563
    @method _initDataProperty
564
    @param {Array|ModelList|ArrayList} data Collection of data to populate the
565
            DataTable
566
    @protected
567
    @since 3.6.0
568
    **/
569
    _initDataProperty: function (data) {
570
        var recordType;
571
 
572
        if (!this.data) {
573
            recordType = this.get('recordType');
574
 
575
            if (data && data.each && data.toJSON) {
576
                this.data = data;
577
 
578
                if (recordType) {
579
                    this.data.model = recordType;
580
                }
581
            } else {
582
                // TODO: customize the ModelList or read the ModelList class
583
                // from a configuration option?
584
                this.data = new Y.ModelList();
585
 
586
                if (recordType) {
587
                    this.data.model = recordType;
588
                }
589
            }
590
 
591
            // TODO: Replace this with an event relay for specific events.
592
            // Using bubbling causes subscription conflicts with the models'
593
            // aggregated change event and 'change' events from DOM elements
594
            // inside the table (via Widget UI event).
595
            this.data.addTarget(this);
596
        }
597
    },
598
 
599
    /**
600
    Initializes the columns, `recordType` and data ModelList.
601
 
602
    @method initializer
603
    @param {Object} config Configuration object passed to constructor
604
    @protected
605
    @since 3.5.0
606
    **/
607
    initializer: function (config) {
608
        var data       = config.data,
609
            columns    = config.columns,
610
            recordType;
611
 
612
        // Referencing config.data to allow _setData to be more stringent
613
        // about its behavior
614
        this._initDataProperty(data);
615
 
616
        // Default columns from recordType ATTRS if recordType is supplied at
617
        // construction.  If no recordType is supplied, but the data is
618
        // supplied as a non-empty array, use the keys of the first item
619
        // as the columns.
620
        if (!columns) {
621
            recordType = (config.recordType || config.data === this.data) &&
622
                            this.get('recordType');
623
 
624
            if (recordType) {
625
                columns = keys(recordType.ATTRS);
626
            } else if (isArray(data) && data.length) {
627
                columns = keys(data[0]);
628
            }
629
 
630
            if (columns) {
631
                this.set('columns', columns);
632
            }
633
        }
634
 
635
        this._initColumns();
636
 
637
        this._eventHandles = {};
638
 
639
        this._initCoreEvents();
640
    },
641
 
642
    /**
643
    Iterates the array of column configurations to capture all columns with a
644
    `key` property.  An map is built with column keys as the property name and
645
    the corresponding column object as the associated value.  This map is then
646
    assigned to the instance's `_columnMap` property.
647
 
648
    @method _setColumnMap
649
    @param {Object[]|String[]} columns The array of column config objects
650
    @protected
651
    @since 3.6.0
652
    **/
653
    _setColumnMap: function (columns) {
654
        var map = {};
655
 
656
        function process(cols) {
657
            var i, len, col, key;
658
 
659
            for (i = 0, len = cols.length; i < len; ++i) {
660
                col = cols[i];
661
                key = col.key;
662
 
663
                // First in wins for multiple columns with the same key
664
                // because the first call to genId (in _setColumns) will
665
                // return the same key, which will then be overwritten by the
666
                // subsequent same-keyed column.  So table.getColumn(key) would
667
                // return the last same-keyed column.
668
                if (key && !map[key]) {
669
                    map[key] = col;
670
                }
671
                //TODO: named columns can conflict with keyed columns
672
                map[col._id] = col;
673
 
674
                if (col.children) {
675
                    process(col.children);
676
                }
677
            }
678
        }
679
 
680
        process(columns);
681
 
682
        this._columnMap = map;
683
    },
684
 
685
    /**
686
    Translates string columns into objects with that string as the value of its
687
    `key` property.
688
 
689
    All columns are assigned a `_yuid` stamp and `_id` property corresponding
690
    to the column's configured `name` or `key` property with any spaces
691
    replaced with dashes.  If the same `name` or `key` appears in multiple
692
    columns, subsequent appearances will have their `_id` appended with an
693
    incrementing number (e.g. if column "foo" is included in the `columns`
694
    attribute twice, the first will get `_id` of "foo", and the second an `_id`
695
    of "foo1").  Columns that are children of other columns will have the
696
    `_parent` property added, assigned the column object to which they belong.
697
 
698
    @method _setColumns
699
    @param {null|Object[]|String[]} val Array of config objects or strings
700
    @return {null|Object[]}
701
    @protected
702
    **/
703
    _setColumns: function (val) {
704
        var keys = {},
705
            known = [],
706
            knownCopies = [],
707
            arrayIndex = Y.Array.indexOf;
708
 
709
        function copyObj(o) {
710
            var copy = {},
711
                key, val, i;
712
 
713
            known.push(o);
714
            knownCopies.push(copy);
715
 
716
            for (key in o) {
717
                if (o.hasOwnProperty(key)) {
718
                    val = o[key];
719
 
720
                    if (isArray(val)) {
721
                        copy[key] = val.slice();
722
                    } else if (isObject(val, true)) {
723
                        i = arrayIndex(known, val);
724
 
725
                        copy[key] = i === -1 ? copyObj(val) : knownCopies[i];
726
                    } else {
727
                        copy[key] = o[key];
728
                    }
729
                }
730
            }
731
 
732
            return copy;
733
        }
734
 
735
        function genId(name) {
736
            // Sanitize the name for use in generated CSS classes.
737
            // TODO: is there more to do for other uses of _id?
738
            name = name.replace(/\s+/, '-');
739
 
740
            if (keys[name]) {
741
                name += (keys[name]++);
742
            } else {
743
                keys[name] = 1;
744
            }
745
 
746
            return name;
747
        }
748
 
749
        function process(cols, parent) {
750
            var columns = [],
751
                i, len, col, yuid;
752
 
753
            for (i = 0, len = cols.length; i < len; ++i) {
754
                columns[i] = // chained assignment
755
                col = isString(cols[i]) ? { key: cols[i] } : copyObj(cols[i]);
756
 
757
                yuid = Y.stamp(col);
758
 
759
                // For backward compatibility
760
                if (!col.id) {
761
                    // Implementers can shoot themselves in the foot by setting
762
                    // this config property to a non-unique value
763
                    col.id = yuid;
764
                }
765
                if (col.field) {
766
                    // Field is now known as "name" to avoid confusion with data
767
                    // fields or schema.resultFields
768
                    col.name = col.field;
769
                }
770
 
771
                if (parent) {
772
                    col._parent = parent;
773
                } else {
774
                    delete col._parent;
775
                }
776
 
777
                // Unique id based on the column's configured name or key,
778
                // falling back to the yuid.  Duplicates will have a counter
779
                // added to the end.
780
                col._id = genId(col.name || col.key || col.id);
781
 
782
                if (isArray(col.children)) {
783
                    col.children = process(col.children, col);
784
                }
785
            }
786
 
787
            return columns;
788
        }
789
 
790
        return val && process(val);
791
    },
792
 
793
    /**
794
    Relays attribute assignments of the deprecated `columnset` attribute to the
795
    `columns` attribute.  If a Columnset is object is passed, its basic object
796
    structure is mined.
797
 
798
    @method _setColumnset
799
    @param {Array|Columnset} val The columnset value to relay
800
    @deprecated This will be removed with the deprecated `columnset` attribute
801
                in a later version.
802
    @protected
803
    @since 3.5.0
804
    **/
805
    _setColumnset: function (val) {
806
        this.set('columns', val);
807
 
808
        return isArray(val) ? val : INVALID;
809
    },
810
 
811
    /**
812
    Accepts an object with `each` and `getAttrs` (preferably a ModelList or
813
    subclass) or an array of data objects.  If an array is passes, it will
814
    create a ModelList to wrap the data.  In doing so, it will set the created
815
    ModelList's `model` property to the class in the `recordType` attribute,
816
    which will be defaulted if not yet set.
817
 
818
    If the `data` property is already set with a ModelList, passing an array as
819
    the value will call the ModelList's `reset()` method with that array rather
820
    than replacing the stored ModelList wholesale.
821
 
822
    Any non-ModelList-ish and non-array value is invalid.
823
 
824
    @method _setData
825
    @protected
826
    @since 3.5.0
827
    **/
828
    _setData: function (val) {
829
        if (val === null) {
830
            val = [];
831
        }
832
 
833
        if (isArray(val)) {
834
            this._initDataProperty();
835
 
836
            // silent to prevent subscribers to both reset and dataChange
837
            // from reacting to the change twice.
838
            // TODO: would it be better to return INVALID to silence the
839
            // dataChange event, or even allow both events?
840
            this.data.reset(val, { silent: true });
841
 
842
            // Return the instance ModelList to avoid storing unprocessed
843
            // data in the state and their vivified Model representations in
844
            // the instance's data property.  Decreases memory consumption.
845
            val = this.data;
846
        } else if (!val || !val.each || !val.toJSON) {
847
            // ModelList/ArrayList duck typing
848
            val = INVALID;
849
        }
850
 
851
        return val;
852
    },
853
 
854
    /**
855
    Relays the value assigned to the deprecated `recordset` attribute to the
856
    `data` attribute.  If a Recordset instance is passed, the raw object data
857
    will be culled from it.
858
 
859
    @method _setRecordset
860
    @param {Object[]|Recordset} val The recordset value to relay
861
    @deprecated This will be removed with the deprecated `recordset` attribute
862
                in a later version.
863
    @protected
864
    @since 3.5.0
865
    **/
866
    _setRecordset: function (val) {
867
        var data;
868
 
869
        if (val && Y.Recordset && val instanceof Y.Recordset) {
870
            data = [];
871
            val.each(function (record) {
872
                data.push(record.get('data'));
873
            });
874
            val = data;
875
        }
876
 
877
        this.set('data', val);
878
 
879
        return val;
880
    },
881
 
882
    /**
883
    Accepts a Base subclass (preferably a Model subclass). Alternately, it will
884
    generate a custom Model subclass from an array of attribute names or an
885
    object defining attributes and their respective configurations (it is
886
    assigned as the `ATTRS` of the new class).
887
 
888
    Any other value is invalid.
889
 
890
    @method _setRecordType
891
    @param {Function|String[]|Object} val The Model subclass, array of
892
            attribute names, or the `ATTRS` definition for a custom model
893
            subclass
894
    @return {Function} A Base/Model subclass
895
    @protected
896
    @since 3.5.0
897
    **/
898
    _setRecordType: function (val) {
899
        var modelClass;
900
 
901
        // Duck type based on known/likely consumed APIs
902
        if (isFunction(val) && val.prototype.toJSON && val.prototype.setAttrs) {
903
            modelClass = val;
904
        } else if (isObject(val)) {
905
            modelClass = this._createRecordClass(val);
906
        }
907
 
908
        return modelClass || INVALID;
909
    }
910
 
911
});
912
 
913
 
914
 
915
/**
916
_This is a documentation entry only_
917
 
918
Columns are described by object literals with a set of properties.
919
There is not an actual `DataTable.Column` class.
920
However, for the purpose of documenting it, this pseudo-class is declared here.
921
 
922
DataTables accept an array of column definitions in their [columns](DataTable.html#attr_columns)
923
attribute.  Each entry in this array is a column definition which may contain
924
any combination of the properties listed below.
925
 
926
There are no mandatory properties though a column will usually have a
927
[key](#property_key) property to reference the data it is supposed to show.
928
The [columns](DataTable.html#attr_columns) attribute can accept a plain string
929
in lieu of an object literal, which is the equivalent of an object with the
930
[key](#property_key) property set to that string.
931
 
932
@class DataTable.Column
933
*/
934
 
935
/**
936
Binds the column values to the named property in the [data](DataTable.html#attr_data).
937
 
938
Optional if [formatter](#property_formatter), [nodeFormatter](#property_nodeFormatter),
939
or [cellTemplate](#property_cellTemplate) is used to populate the content.
940
 
941
It should not be set if [children](#property_children) is set.
942
 
943
The value is used for the [\_id](#property__id) property unless the [name](#property_name)
944
property is also set.
945
 
946
    { key: 'username' }
947
 
948
The above column definition can be reduced to this:
949
 
950
    'username'
951
 
952
@property key
953
@type String
954
 */
955
/**
956
An identifier that can be used to locate a column via
957
[getColumn](DataTable.html#method_getColumn)
958
or style columns with class `yui3-datatable-col-NAME` after dropping characters
959
that are not valid for CSS class names.
960
 
961
It defaults to the [key](#property_key).
962
 
963
The value is used for the [\_id](#property__id) property.
964
 
965
    { name: 'fullname', formatter: ... }
966
 
967
@property name
968
@type String
969
*/
970
/**
971
An alias for [name](#property_name) for backward compatibility.
972
 
973
    { field: 'fullname', formatter: ... }
974
 
975
@property field
976
@type String
977
*/
978
/**
979
Overrides the default unique id assigned `<th id="HERE">`.
980
 
981
__Use this with caution__, since it can result in
982
duplicate ids in the DOM.
983
 
984
    {
985
        name: 'checkAll',
986
        id: 'check-all',
987
        label: ...
988
        formatter: ...
989
    }
990
 
991
@property id
992
@type String
993
 */
994
/**
995
HTML to populate the header `<th>` for the column.
996
It defaults to the value of the [key](#property_key) property or the text
997
`Column n` where _n_ is an ordinal number.
998
 
999
    { key: 'MfgvaPrtNum', label: 'Part Number' }
1000
 
1001
@property label
1002
@type {String}
1003
 */
1004
/**
1005
Used to create stacked headers.
1006
 
1007
Child columns may also contain `children`. There is no limit
1008
to the depth of nesting.
1009
 
1010
Columns configured with `children` are for display only and
1011
<strong>should not</strong> be configured with a [key](#property_key).
1012
Configurations relating to the display of data, such as
1013
[formatter](#property_formatter), [nodeFormatter](#property_nodeFormatter),
1014
[emptyCellValue](#property_emptyCellValue), etc. are ignored.
1015
 
1016
    { label: 'Name', children: [
1017
        { key: 'firstName', label: 'First`},
1018
        { key: 'lastName', label: 'Last`}
1019
    ]}
1020
 
1021
@property  children
1022
@type Array
1023
*/
1024
/**
1025
Assigns the value `<th abbr="HERE">`.
1026
 
1027
    {
1028
      key  : 'forecast',
1029
      label: '1yr Target Forecast',
1030
      abbr : 'Forecast'
1031
    }
1032
 
1033
@property abbr
1034
@type String
1035
 */
1036
/**
1037
Assigns the value `<th title="HERE">`.
1038
 
1039
    {
1040
      key  : 'forecast',
1041
      label: '1yr Target Forecast',
1042
      title: 'Target Forecast for the Next 12 Months'
1043
    }
1044
 
1045
@property title
1046
@type String
1047
 */
1048
/**
1049
Overrides the default [CELL_TEMPLATE](DataTable.HeaderView.html#property_CELL_TEMPLATE)
1050
used by `Y.DataTable.HeaderView` to render the header cell
1051
for this column.  This is necessary when more control is
1052
needed over the markup for the header itself, rather than
1053
its content.
1054
 
1055
Use the [label](#property_label) configuration if you don't need to
1056
customize the `<th>` iteself.
1057
 
1058
Implementers are strongly encouraged to preserve at least
1059
the `{id}` and `{_id}` placeholders in the custom value.
1060
 
1061
    {
1062
        headerTemplate:
1063
            '<th id="{id}" ' +
1064
                'title="Unread" ' +
1065
                'class="{className}" ' +
1066
                '{_id}>&#9679;</th>'
1067
    }
1068
 
1069
@property headerTemplate
1070
@type HTML
1071
 */
1072
/**
1073
Overrides the default [CELL_TEMPLATE](DataTable.BodyView.html#property_CELL_TEMPLATE)
1074
used by `Y.DataTable.BodyView` to render the data cells
1075
for this column.  This is necessary when more control is
1076
needed over the markup for the `<td>` itself, rather than
1077
its content.
1078
 
1079
    {
1080
        key: 'id',
1081
        cellTemplate:
1082
            '<td class="{className}">' +
1083
              '<input type="checkbox" ' +
1084
                     'id="{content}">' +
1085
            '</td>'
1086
    }
1087
 
1088
 
1089
@property cellTemplate
1090
@type String
1091
 */
1092
/**
1093
String or function used to translate the raw record data for each cell in a
1094
given column into a format better suited to display.
1095
 
1096
If it is a string, it will initially be assumed to be the name of one of the
1097
formatting functions in
1098
[Y.DataTable.BodyView.Formatters](DataTable.BodyView.Formatters.html).
1099
If one such formatting function exists, it will be used.
1100
 
1101
If no such named formatter is found, it will be assumed to be a template
1102
string and will be expanded.  The placeholders can contain the key to any
1103
field in the record or the placeholder `{value}` which represents the value
1104
of the current field.
1105
 
1106
If the value is a function, it will be assumed to be a formatting function.
1107
A formatting function receives a single argument, an object with the following properties:
1108
 
1109
* __value__ The raw value from the record Model to populate this cell.
1110
  Equivalent to `o.record.get(o.column.key)` or `o.data[o.column.key]`.
1111
* __data__ The Model data for this row in simple object format.
1112
* __record__ The Model for this row.
1113
* __column__ The column configuration object.
1114
* __className__ A string of class names to add `<td class="HERE">` in addition to
1115
  the column class and any classes in the column's className configuration.
1116
* __rowIndex__ The index of the current Model in the ModelList.
1117
  Typically correlates to the row index as well.
1118
* __rowClass__ A string of css classes to add `<tr class="HERE"><td....`
1119
  This is useful to avoid the need for nodeFormatters to add classes to the containing row.
1120
 
1121
The formatter function may return a string value that will be used for the cell
1122
contents or it may change the value of the `value`, `className` or `rowClass`
1123
properties which well then be used to format the cell.  If the value for the cell
1124
is returned in the `value` property of the input argument, no value should be returned.
1125
 
1126
    {
1127
        key: 'name',
1128
        formatter: 'link',  // named formatter
1129
        linkFrom: 'website' // extra column property for link formatter
1130
    },
1131
    {
1132
        key: 'cost',
1133
        formatter: '${value}' // formatter template string
1134
      //formatter: '${cost}'  // same result but less portable
1135
    },
1136
    {
1137
        name: 'Name',          // column does not have associated field value
1138
                               // thus, it uses name instead of key
1139
        formatter: '{firstName} {lastName}' // template references other fields
1140
    },
1141
    {
1142
        key: 'price',
1143
        formatter: function (o) { // function both returns a string to show
1144
            if (o.value > 3) {    // and a className to apply to the cell
1145
                o.className += 'expensive';
1146
            }
1147
 
1148
            return '$' + o.value.toFixed(2);
1149
        }
1150
    },
1151
@property formatter
1152
@type String || Function
1153
 
1154
*/
1155
/**
1156
Used to customize the content of the data cells for this column.
1157
 
1158
`nodeFormatter` is significantly slower than [formatter](#property_formatter)
1159
and should be avoided if possible. Unlike [formatter](#property_formatter),
1160
`nodeFormatter` has access to the `<td>` element and its ancestors.
1161
 
1162
The function provided is expected to fill in the `<td>` element itself.
1163
__Node formatters should return `false`__ except in certain conditions as described
1164
in the users guide.
1165
 
1166
The function receives a single object
1167
argument with the following properties:
1168
 
1169
* __td__	The `<td>` Node for this cell.
1170
* __cell__	If the cell `<td> contains an element with class `yui3-datatable-liner,
1171
  this will refer to that Node. Otherwise, it is equivalent to `td` (default behavior).
1172
* __value__	The raw value from the record Model to populate this cell.
1173
  Equivalent to `o.record.get(o.column.key)` or `o.data[o.column.key]`.
1174
* __data__	The Model data for this row in simple object format.
1175
* __record__	The Model for this row.
1176
* __column__	The column configuration object.
1177
* __rowIndex__	The index of the current Model in the ModelList.
1178
 _Typically_ correlates to the row index as well.
1179
 
1180
@example
1181
    nodeFormatter: function (o) {
1182
        if (o.value < o.data.quota) {
1183
            o.td.setAttribute('rowspan', 2);
1184
            o.td.setAttribute('data-term-id', this.record.get('id'));
1185
 
1186
            o.td.ancestor().insert(
1187
                '<tr><td colspan"3">' +
1188
                    '<button class="term">terminate</button>' +
1189
                '</td></tr>',
1190
                'after');
1191
        }
1192
 
1193
        o.cell.setHTML(o.value);
1194
        return false;
1195
    }
1196
 
1197
@property nodeFormatter
1198
@type Function
1199
*/
1200
/**
1201
Provides the default value to populate the cell if the data
1202
for that cell is `undefined`, `null`, or an empty string.
1203
 
1204
    {
1205
        key: 'price',
1206
        emptyCellValue: '???'
1207
    }
1208
 
1209
@property emptyCellValue
1210
@type {String} depending on the setting of allowHTML
1211
 */
1212
/**
1213
Skips the security step of HTML escaping the value for cells
1214
in this column.
1215
 
1216
This is also necessary if [emptyCellValue](#property_emptyCellValue)
1217
is set with an HTML string.
1218
`nodeFormatter`s ignore this configuration.  If using a
1219
`nodeFormatter`, it is recommended to use
1220
[Y.Escape.html()](Escape.html#method_html)
1221
on any user supplied content that is to be displayed.
1222
 
1223
    {
1224
        key: 'preview',
1225
        allowHTML: true
1226
    }
1227
 
1228
@property allowHTML
1229
@type Boolean
1230
*/
1231
/**
1232
A string of CSS classes that will be added to the `<td>`'s
1233
`class` attribute.
1234
 
1235
Note, all cells will automatically have a class in the
1236
form of "yui3-datatable-col-XXX" added to the `<td>`, where
1237
XXX is the column's configured `name`, `key`, or `id` (in
1238
that order of preference) sanitized from invalid characters.
1239
 
1240
    {
1241
        key: 'symbol',
1242
        className: 'no-hide'
1243
    }
1244
 
1245
@property className
1246
@type String
1247
*/
1248
/**
1249
(__read-only__) The unique identifier assigned
1250
to each column.  This is used for the `id` if not set, and
1251
the `_id` if none of [name](#property_name),
1252
[field](#property_field), [key](#property_key), or [id](#property_id) are
1253
set.
1254
 
1255
@property _yuid
1256
@type String
1257
@protected
1258
 */
1259
/**
1260
(__read-only__) A unique-to-this-instance name
1261
used extensively in the rendering process.  It is also used
1262
to create the column's classname, as the input name
1263
`table.getColumn(HERE)`, and in the column header's
1264
`<th data-yui3-col-id="HERE">`.
1265
 
1266
The value is populated by the first of [name](#property_name),
1267
[field](#property_field), [key](#property_key), [id](#property_id),
1268
 or [_yuid](#property__yuid) to have a value.  If that value
1269
has already been used (such as when multiple columns have
1270
the same `key`), an incrementer is added to the end.  For
1271
example, two columns with `key: "id"` will have `_id`s of
1272
"id" and "id2".  `table.getColumn("id")` will return the
1273
first column, and `table.getColumn("id2")` will return the
1274
second.
1275
 
1276
@property _id
1277
@type String
1278
@protected
1279
 */
1280
/**
1281
(__read-only__) Used by
1282
`Y.DataTable.HeaderView` when building stacked column
1283
headers.
1284
 
1285
@property _colspan
1286
@type Integer
1287
@protected
1288
 */
1289
/**
1290
(__read-only__) Used by
1291
`Y.DataTable.HeaderView` when building stacked column
1292
headers.
1293
 
1294
@property _rowspan
1295
@type Integer
1296
@protected
1297
 */
1298
/**
1299
(__read-only__) Assigned to all columns in a
1300
column's `children` collection.  References the parent
1301
column object.
1302
 
1303
@property _parent
1304
@type DataTable.Column
1305
@protected
1306
 */
1307
/**
1308
(__read-only__) Array of the `id`s of the
1309
column and all parent columns.  Used by
1310
`Y.DataTable.BodyView` to populate `<td headers="THIS">`
1311
when a cell references more than one header.
1312
 
1313
@property _headers
1314
@type Array
1315
@protected
1316
*/
1317
 
1318
 
1319
}, '3.18.1', {"requires": ["escape", "model-list", "node-event-delegate"]});