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
                else {Y.log('Key of column matches existing key or name: ' + key, 'warn', NAME);}
672
                if (map[col._id]) {Y.log('Key of column matches existing key or name: ' + col._id, 'warn', NAME);}
673
                //TODO: named columns can conflict with keyed columns
674
                map[col._id] = col;
675
 
676
                if (col.children) {
677
                    process(col.children);
678
                }
679
            }
680
        }
681
 
682
        process(columns);
683
 
684
        this._columnMap = map;
685
    },
686
 
687
    /**
688
    Translates string columns into objects with that string as the value of its
689
    `key` property.
690
 
691
    All columns are assigned a `_yuid` stamp and `_id` property corresponding
692
    to the column's configured `name` or `key` property with any spaces
693
    replaced with dashes.  If the same `name` or `key` appears in multiple
694
    columns, subsequent appearances will have their `_id` appended with an
695
    incrementing number (e.g. if column "foo" is included in the `columns`
696
    attribute twice, the first will get `_id` of "foo", and the second an `_id`
697
    of "foo1").  Columns that are children of other columns will have the
698
    `_parent` property added, assigned the column object to which they belong.
699
 
700
    @method _setColumns
701
    @param {null|Object[]|String[]} val Array of config objects or strings
702
    @return {null|Object[]}
703
    @protected
704
    **/
705
    _setColumns: function (val) {
706
        var keys = {},
707
            known = [],
708
            knownCopies = [],
709
            arrayIndex = Y.Array.indexOf;
710
 
711
        function copyObj(o) {
712
            var copy = {},
713
                key, val, i;
714
 
715
            known.push(o);
716
            knownCopies.push(copy);
717
 
718
            for (key in o) {
719
                if (o.hasOwnProperty(key)) {
720
                    val = o[key];
721
 
722
                    if (isArray(val)) {
723
                        copy[key] = val.slice();
724
                    } else if (isObject(val, true)) {
725
                        i = arrayIndex(known, val);
726
 
727
                        copy[key] = i === -1 ? copyObj(val) : knownCopies[i];
728
                    } else {
729
                        copy[key] = o[key];
730
                    }
731
                }
732
            }
733
 
734
            return copy;
735
        }
736
 
737
        function genId(name) {
738
            // Sanitize the name for use in generated CSS classes.
739
            // TODO: is there more to do for other uses of _id?
740
            name = name.replace(/\s+/, '-');
741
 
742
            if (keys[name]) {
743
                name += (keys[name]++);
744
            } else {
745
                keys[name] = 1;
746
            }
747
 
748
            return name;
749
        }
750
 
751
        function process(cols, parent) {
752
            var columns = [],
753
                i, len, col, yuid;
754
 
755
            for (i = 0, len = cols.length; i < len; ++i) {
756
                columns[i] = // chained assignment
757
                col = isString(cols[i]) ? { key: cols[i] } : copyObj(cols[i]);
758
 
759
                yuid = Y.stamp(col);
760
 
761
                // For backward compatibility
762
                if (!col.id) {
763
                    // Implementers can shoot themselves in the foot by setting
764
                    // this config property to a non-unique value
765
                    col.id = yuid;
766
                }
767
                if (col.field) {
768
                    // Field is now known as "name" to avoid confusion with data
769
                    // fields or schema.resultFields
770
                    col.name = col.field;
771
                }
772
 
773
                if (parent) {
774
                    col._parent = parent;
775
                } else {
776
                    delete col._parent;
777
                }
778
 
779
                // Unique id based on the column's configured name or key,
780
                // falling back to the yuid.  Duplicates will have a counter
781
                // added to the end.
782
                col._id = genId(col.name || col.key || col.id);
783
 
784
                if (isArray(col.children)) {
785
                    col.children = process(col.children, col);
786
                }
787
            }
788
 
789
            return columns;
790
        }
791
 
792
        return val && process(val);
793
    },
794
 
795
    /**
796
    Relays attribute assignments of the deprecated `columnset` attribute to the
797
    `columns` attribute.  If a Columnset is object is passed, its basic object
798
    structure is mined.
799
 
800
    @method _setColumnset
801
    @param {Array|Columnset} val The columnset value to relay
802
    @deprecated This will be removed with the deprecated `columnset` attribute
803
                in a later version.
804
    @protected
805
    @since 3.5.0
806
    **/
807
    _setColumnset: function (val) {
808
        this.set('columns', val);
809
 
810
        return isArray(val) ? val : INVALID;
811
    },
812
 
813
    /**
814
    Accepts an object with `each` and `getAttrs` (preferably a ModelList or
815
    subclass) or an array of data objects.  If an array is passes, it will
816
    create a ModelList to wrap the data.  In doing so, it will set the created
817
    ModelList's `model` property to the class in the `recordType` attribute,
818
    which will be defaulted if not yet set.
819
 
820
    If the `data` property is already set with a ModelList, passing an array as
821
    the value will call the ModelList's `reset()` method with that array rather
822
    than replacing the stored ModelList wholesale.
823
 
824
    Any non-ModelList-ish and non-array value is invalid.
825
 
826
    @method _setData
827
    @protected
828
    @since 3.5.0
829
    **/
830
    _setData: function (val) {
831
        if (val === null) {
832
            val = [];
833
        }
834
 
835
        if (isArray(val)) {
836
            this._initDataProperty();
837
 
838
            // silent to prevent subscribers to both reset and dataChange
839
            // from reacting to the change twice.
840
            // TODO: would it be better to return INVALID to silence the
841
            // dataChange event, or even allow both events?
842
            this.data.reset(val, { silent: true });
843
 
844
            // Return the instance ModelList to avoid storing unprocessed
845
            // data in the state and their vivified Model representations in
846
            // the instance's data property.  Decreases memory consumption.
847
            val = this.data;
848
        } else if (!val || !val.each || !val.toJSON) {
849
            // ModelList/ArrayList duck typing
850
            val = INVALID;
851
        }
852
 
853
        return val;
854
    },
855
 
856
    /**
857
    Relays the value assigned to the deprecated `recordset` attribute to the
858
    `data` attribute.  If a Recordset instance is passed, the raw object data
859
    will be culled from it.
860
 
861
    @method _setRecordset
862
    @param {Object[]|Recordset} val The recordset value to relay
863
    @deprecated This will be removed with the deprecated `recordset` attribute
864
                in a later version.
865
    @protected
866
    @since 3.5.0
867
    **/
868
    _setRecordset: function (val) {
869
        var data;
870
 
871
        if (val && Y.Recordset && val instanceof Y.Recordset) {
872
            data = [];
873
            val.each(function (record) {
874
                data.push(record.get('data'));
875
            });
876
            val = data;
877
        }
878
 
879
        this.set('data', val);
880
 
881
        return val;
882
    },
883
 
884
    /**
885
    Accepts a Base subclass (preferably a Model subclass). Alternately, it will
886
    generate a custom Model subclass from an array of attribute names or an
887
    object defining attributes and their respective configurations (it is
888
    assigned as the `ATTRS` of the new class).
889
 
890
    Any other value is invalid.
891
 
892
    @method _setRecordType
893
    @param {Function|String[]|Object} val The Model subclass, array of
894
            attribute names, or the `ATTRS` definition for a custom model
895
            subclass
896
    @return {Function} A Base/Model subclass
897
    @protected
898
    @since 3.5.0
899
    **/
900
    _setRecordType: function (val) {
901
        var modelClass;
902
 
903
        // Duck type based on known/likely consumed APIs
904
        if (isFunction(val) && val.prototype.toJSON && val.prototype.setAttrs) {
905
            modelClass = val;
906
        } else if (isObject(val)) {
907
            modelClass = this._createRecordClass(val);
908
        }
909
 
910
        return modelClass || INVALID;
911
    }
912
 
913
});
914
 
915
 
916
 
917
/**
918
_This is a documentation entry only_
919
 
920
Columns are described by object literals with a set of properties.
921
There is not an actual `DataTable.Column` class.
922
However, for the purpose of documenting it, this pseudo-class is declared here.
923
 
924
DataTables accept an array of column definitions in their [columns](DataTable.html#attr_columns)
925
attribute.  Each entry in this array is a column definition which may contain
926
any combination of the properties listed below.
927
 
928
There are no mandatory properties though a column will usually have a
929
[key](#property_key) property to reference the data it is supposed to show.
930
The [columns](DataTable.html#attr_columns) attribute can accept a plain string
931
in lieu of an object literal, which is the equivalent of an object with the
932
[key](#property_key) property set to that string.
933
 
934
@class DataTable.Column
935
*/
936
 
937
/**
938
Binds the column values to the named property in the [data](DataTable.html#attr_data).
939
 
940
Optional if [formatter](#property_formatter), [nodeFormatter](#property_nodeFormatter),
941
or [cellTemplate](#property_cellTemplate) is used to populate the content.
942
 
943
It should not be set if [children](#property_children) is set.
944
 
945
The value is used for the [\_id](#property__id) property unless the [name](#property_name)
946
property is also set.
947
 
948
    { key: 'username' }
949
 
950
The above column definition can be reduced to this:
951
 
952
    'username'
953
 
954
@property key
955
@type String
956
 */
957
/**
958
An identifier that can be used to locate a column via
959
[getColumn](DataTable.html#method_getColumn)
960
or style columns with class `yui3-datatable-col-NAME` after dropping characters
961
that are not valid for CSS class names.
962
 
963
It defaults to the [key](#property_key).
964
 
965
The value is used for the [\_id](#property__id) property.
966
 
967
    { name: 'fullname', formatter: ... }
968
 
969
@property name
970
@type String
971
*/
972
/**
973
An alias for [name](#property_name) for backward compatibility.
974
 
975
    { field: 'fullname', formatter: ... }
976
 
977
@property field
978
@type String
979
*/
980
/**
981
Overrides the default unique id assigned `<th id="HERE">`.
982
 
983
__Use this with caution__, since it can result in
984
duplicate ids in the DOM.
985
 
986
    {
987
        name: 'checkAll',
988
        id: 'check-all',
989
        label: ...
990
        formatter: ...
991
    }
992
 
993
@property id
994
@type String
995
 */
996
/**
997
HTML to populate the header `<th>` for the column.
998
It defaults to the value of the [key](#property_key) property or the text
999
`Column n` where _n_ is an ordinal number.
1000
 
1001
    { key: 'MfgvaPrtNum', label: 'Part Number' }
1002
 
1003
@property label
1004
@type {String}
1005
 */
1006
/**
1007
Used to create stacked headers.
1008
 
1009
Child columns may also contain `children`. There is no limit
1010
to the depth of nesting.
1011
 
1012
Columns configured with `children` are for display only and
1013
<strong>should not</strong> be configured with a [key](#property_key).
1014
Configurations relating to the display of data, such as
1015
[formatter](#property_formatter), [nodeFormatter](#property_nodeFormatter),
1016
[emptyCellValue](#property_emptyCellValue), etc. are ignored.
1017
 
1018
    { label: 'Name', children: [
1019
        { key: 'firstName', label: 'First`},
1020
        { key: 'lastName', label: 'Last`}
1021
    ]}
1022
 
1023
@property  children
1024
@type Array
1025
*/
1026
/**
1027
Assigns the value `<th abbr="HERE">`.
1028
 
1029
    {
1030
      key  : 'forecast',
1031
      label: '1yr Target Forecast',
1032
      abbr : 'Forecast'
1033
    }
1034
 
1035
@property abbr
1036
@type String
1037
 */
1038
/**
1039
Assigns the value `<th title="HERE">`.
1040
 
1041
    {
1042
      key  : 'forecast',
1043
      label: '1yr Target Forecast',
1044
      title: 'Target Forecast for the Next 12 Months'
1045
    }
1046
 
1047
@property title
1048
@type String
1049
 */
1050
/**
1051
Overrides the default [CELL_TEMPLATE](DataTable.HeaderView.html#property_CELL_TEMPLATE)
1052
used by `Y.DataTable.HeaderView` to render the header cell
1053
for this column.  This is necessary when more control is
1054
needed over the markup for the header itself, rather than
1055
its content.
1056
 
1057
Use the [label](#property_label) configuration if you don't need to
1058
customize the `<th>` iteself.
1059
 
1060
Implementers are strongly encouraged to preserve at least
1061
the `{id}` and `{_id}` placeholders in the custom value.
1062
 
1063
    {
1064
        headerTemplate:
1065
            '<th id="{id}" ' +
1066
                'title="Unread" ' +
1067
                'class="{className}" ' +
1068
                '{_id}>&#9679;</th>'
1069
    }
1070
 
1071
@property headerTemplate
1072
@type HTML
1073
 */
1074
/**
1075
Overrides the default [CELL_TEMPLATE](DataTable.BodyView.html#property_CELL_TEMPLATE)
1076
used by `Y.DataTable.BodyView` to render the data cells
1077
for this column.  This is necessary when more control is
1078
needed over the markup for the `<td>` itself, rather than
1079
its content.
1080
 
1081
    {
1082
        key: 'id',
1083
        cellTemplate:
1084
            '<td class="{className}">' +
1085
              '<input type="checkbox" ' +
1086
                     'id="{content}">' +
1087
            '</td>'
1088
    }
1089
 
1090
 
1091
@property cellTemplate
1092
@type String
1093
 */
1094
/**
1095
String or function used to translate the raw record data for each cell in a
1096
given column into a format better suited to display.
1097
 
1098
If it is a string, it will initially be assumed to be the name of one of the
1099
formatting functions in
1100
[Y.DataTable.BodyView.Formatters](DataTable.BodyView.Formatters.html).
1101
If one such formatting function exists, it will be used.
1102
 
1103
If no such named formatter is found, it will be assumed to be a template
1104
string and will be expanded.  The placeholders can contain the key to any
1105
field in the record or the placeholder `{value}` which represents the value
1106
of the current field.
1107
 
1108
If the value is a function, it will be assumed to be a formatting function.
1109
A formatting function receives a single argument, an object with the following properties:
1110
 
1111
* __value__ The raw value from the record Model to populate this cell.
1112
  Equivalent to `o.record.get(o.column.key)` or `o.data[o.column.key]`.
1113
* __data__ The Model data for this row in simple object format.
1114
* __record__ The Model for this row.
1115
* __column__ The column configuration object.
1116
* __className__ A string of class names to add `<td class="HERE">` in addition to
1117
  the column class and any classes in the column's className configuration.
1118
* __rowIndex__ The index of the current Model in the ModelList.
1119
  Typically correlates to the row index as well.
1120
* __rowClass__ A string of css classes to add `<tr class="HERE"><td....`
1121
  This is useful to avoid the need for nodeFormatters to add classes to the containing row.
1122
 
1123
The formatter function may return a string value that will be used for the cell
1124
contents or it may change the value of the `value`, `className` or `rowClass`
1125
properties which well then be used to format the cell.  If the value for the cell
1126
is returned in the `value` property of the input argument, no value should be returned.
1127
 
1128
    {
1129
        key: 'name',
1130
        formatter: 'link',  // named formatter
1131
        linkFrom: 'website' // extra column property for link formatter
1132
    },
1133
    {
1134
        key: 'cost',
1135
        formatter: '${value}' // formatter template string
1136
      //formatter: '${cost}'  // same result but less portable
1137
    },
1138
    {
1139
        name: 'Name',          // column does not have associated field value
1140
                               // thus, it uses name instead of key
1141
        formatter: '{firstName} {lastName}' // template references other fields
1142
    },
1143
    {
1144
        key: 'price',
1145
        formatter: function (o) { // function both returns a string to show
1146
            if (o.value > 3) {    // and a className to apply to the cell
1147
                o.className += 'expensive';
1148
            }
1149
 
1150
            return '$' + o.value.toFixed(2);
1151
        }
1152
    },
1153
@property formatter
1154
@type String || Function
1155
 
1156
*/
1157
/**
1158
Used to customize the content of the data cells for this column.
1159
 
1160
`nodeFormatter` is significantly slower than [formatter](#property_formatter)
1161
and should be avoided if possible. Unlike [formatter](#property_formatter),
1162
`nodeFormatter` has access to the `<td>` element and its ancestors.
1163
 
1164
The function provided is expected to fill in the `<td>` element itself.
1165
__Node formatters should return `false`__ except in certain conditions as described
1166
in the users guide.
1167
 
1168
The function receives a single object
1169
argument with the following properties:
1170
 
1171
* __td__	The `<td>` Node for this cell.
1172
* __cell__	If the cell `<td> contains an element with class `yui3-datatable-liner,
1173
  this will refer to that Node. Otherwise, it is equivalent to `td` (default behavior).
1174
* __value__	The raw value from the record Model to populate this cell.
1175
  Equivalent to `o.record.get(o.column.key)` or `o.data[o.column.key]`.
1176
* __data__	The Model data for this row in simple object format.
1177
* __record__	The Model for this row.
1178
* __column__	The column configuration object.
1179
* __rowIndex__	The index of the current Model in the ModelList.
1180
 _Typically_ correlates to the row index as well.
1181
 
1182
@example
1183
    nodeFormatter: function (o) {
1184
        if (o.value < o.data.quota) {
1185
            o.td.setAttribute('rowspan', 2);
1186
            o.td.setAttribute('data-term-id', this.record.get('id'));
1187
 
1188
            o.td.ancestor().insert(
1189
                '<tr><td colspan"3">' +
1190
                    '<button class="term">terminate</button>' +
1191
                '</td></tr>',
1192
                'after');
1193
        }
1194
 
1195
        o.cell.setHTML(o.value);
1196
        return false;
1197
    }
1198
 
1199
@property nodeFormatter
1200
@type Function
1201
*/
1202
/**
1203
Provides the default value to populate the cell if the data
1204
for that cell is `undefined`, `null`, or an empty string.
1205
 
1206
    {
1207
        key: 'price',
1208
        emptyCellValue: '???'
1209
    }
1210
 
1211
@property emptyCellValue
1212
@type {String} depending on the setting of allowHTML
1213
 */
1214
/**
1215
Skips the security step of HTML escaping the value for cells
1216
in this column.
1217
 
1218
This is also necessary if [emptyCellValue](#property_emptyCellValue)
1219
is set with an HTML string.
1220
`nodeFormatter`s ignore this configuration.  If using a
1221
`nodeFormatter`, it is recommended to use
1222
[Y.Escape.html()](Escape.html#method_html)
1223
on any user supplied content that is to be displayed.
1224
 
1225
    {
1226
        key: 'preview',
1227
        allowHTML: true
1228
    }
1229
 
1230
@property allowHTML
1231
@type Boolean
1232
*/
1233
/**
1234
A string of CSS classes that will be added to the `<td>`'s
1235
`class` attribute.
1236
 
1237
Note, all cells will automatically have a class in the
1238
form of "yui3-datatable-col-XXX" added to the `<td>`, where
1239
XXX is the column's configured `name`, `key`, or `id` (in
1240
that order of preference) sanitized from invalid characters.
1241
 
1242
    {
1243
        key: 'symbol',
1244
        className: 'no-hide'
1245
    }
1246
 
1247
@property className
1248
@type String
1249
*/
1250
/**
1251
(__read-only__) The unique identifier assigned
1252
to each column.  This is used for the `id` if not set, and
1253
the `_id` if none of [name](#property_name),
1254
[field](#property_field), [key](#property_key), or [id](#property_id) are
1255
set.
1256
 
1257
@property _yuid
1258
@type String
1259
@protected
1260
 */
1261
/**
1262
(__read-only__) A unique-to-this-instance name
1263
used extensively in the rendering process.  It is also used
1264
to create the column's classname, as the input name
1265
`table.getColumn(HERE)`, and in the column header's
1266
`<th data-yui3-col-id="HERE">`.
1267
 
1268
The value is populated by the first of [name](#property_name),
1269
[field](#property_field), [key](#property_key), [id](#property_id),
1270
 or [_yuid](#property__yuid) to have a value.  If that value
1271
has already been used (such as when multiple columns have
1272
the same `key`), an incrementer is added to the end.  For
1273
example, two columns with `key: "id"` will have `_id`s of
1274
"id" and "id2".  `table.getColumn("id")` will return the
1275
first column, and `table.getColumn("id2")` will return the
1276
second.
1277
 
1278
@property _id
1279
@type String
1280
@protected
1281
 */
1282
/**
1283
(__read-only__) Used by
1284
`Y.DataTable.HeaderView` when building stacked column
1285
headers.
1286
 
1287
@property _colspan
1288
@type Integer
1289
@protected
1290
 */
1291
/**
1292
(__read-only__) Used by
1293
`Y.DataTable.HeaderView` when building stacked column
1294
headers.
1295
 
1296
@property _rowspan
1297
@type Integer
1298
@protected
1299
 */
1300
/**
1301
(__read-only__) Assigned to all columns in a
1302
column's `children` collection.  References the parent
1303
column object.
1304
 
1305
@property _parent
1306
@type DataTable.Column
1307
@protected
1308
 */
1309
/**
1310
(__read-only__) Array of the `id`s of the
1311
column and all parent columns.  Used by
1312
`Y.DataTable.BodyView` to populate `<td headers="THIS">`
1313
when a cell references more than one header.
1314
 
1315
@property _headers
1316
@type Array
1317
@protected
1318
*/
1319
 
1320
 
1321
}, '3.18.1', {"requires": ["escape", "model-list", "node-event-delegate"]});