Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('datatable-base', function (Y, NAME) {
2
 
3
/**
4
A Widget for displaying tabular data.  The base implementation of DataTable
5
provides the ability to dynamically generate an HTML table from a set of column
6
configurations and row data.
7
 
8
Two classes are included in the `datatable-base` module: `Y.DataTable` and
9
`Y.DataTable.Base`.
10
 
11
@module datatable
12
@submodule datatable-base
13
@main datatable
14
@since 3.5.0
15
**/
16
 
17
// DataTable API docs included before DataTable.Base to make yuidoc work
18
/**
19
A Widget for displaying tabular data.  Before feature modules are `use()`d,
20
this class is functionally equivalent to DataTable.Base.  However, feature
21
modules can modify this class in non-destructive ways, expanding the API and
22
functionality.
23
 
24
This is the primary DataTable class.  Out of the box, it provides the ability
25
to dynamically generate an HTML table from a set of column configurations and
26
row data.  But feature module inclusion can add table sorting, pagintaion,
27
highlighting, selection, and more.
28
 
29
<pre><code>
30
// The functionality of this table would require additional modules be use()d,
31
// but the feature APIs are aggregated onto Y.DataTable.
32
// (Snippet is for illustration. Not all features are available today.)
33
var table = new Y.DataTable({
34
    columns: [
35
        { type: 'checkbox', defaultChecked: true },
36
        { key: 'firstName', sortable: true, resizable: true },
37
        { key: 'lastName', sortable: true },
38
        { key: 'role', formatter: toRoleName }
39
    ],
40
    data: {
41
        source: 'http://myserver.com/service/json',
42
        type: 'json',
43
        schema: {
44
            resultListLocator: 'results.users',
45
            fields: [
46
                'username',
47
                'firstName',
48
                'lastName',
49
                { key: 'role', type: 'number' }
50
            ]
51
        }
52
    },
53
    recordType: UserModel,
54
    pagedData: {
55
        location: 'footer',
56
        pageSizes: [20, 50, 'all'],
57
        rowsPerPage: 20,
58
        pageLinks: 5
59
    },
60
    editable: true
61
});
62
</code></pre>
63
 
64
### Column Configuration
65
 
66
The column configurations are set in the form of an array of objects, where
67
each object corresponds to a column.  For columns populated directly from the
68
row data, a 'key' property is required to bind the column to that property or
69
attribute in the row data.
70
 
71
Not all columns need to relate to row data, nor do all properties or attributes
72
of the row data need to have a corresponding column.  However, only those
73
columns included in the `columns` configuration attribute will be rendered.
74
 
75
Other column configuration properties are supported by the configured
76
`view`, class as well as any features added by plugins or class extensions.
77
See the description of DataTable.TableView and its subviews
78
DataTable.HeaderView, DataTable.BodyView, and DataTable.FooterView (and other
79
DataTable feature classes) to see what column properties they support.
80
 
81
Some examples of column configurations would be:
82
 
83
<pre><code>
84
// Basic
85
var columns = [{ key: 'firstName' }, { key: 'lastName' }, { key: 'age' }];
86
 
87
// For columns without any additional configuration, strings can be used
88
var columns = ['firstName', 'lastName', 'age'];
89
 
90
// Multi-row column headers (see DataTable.HeaderView for details)
91
var columns = [
92
    {
93
        label: 'Name',
94
        children: [
95
            { key: 'firstName' },
96
            { key: 'lastName' }
97
        ]
98
    },
99
    'age' // mixing and matching objects and strings is ok
100
];
101
 
102
// Including columns that are not related 1:1 to row data fields/attributes
103
// (See DataTable.BodyView for details)
104
var columns = [
105
    {
106
        label: 'Name', // Needed for the column header
107
        formatter: function (o) {
108
            // Fill the column cells with data from firstName and lastName
109
            if (o.data.age > 55) {
110
                o.className += ' senior';
111
            }
112
            return o.data.lastName + ', ' + o.data.firstName;
113
        }
114
    },
115
    'age'
116
];
117
 
118
// Columns that include feature configurations (for illustration; not all
119
// features are available today).
120
var columns = [
121
    { type: 'checkbox', defaultChecked: true },
122
    { key: 'firstName', sortable: true, resizable: true, min-width: '300px' },
123
    { key: 'lastName', sortable: true, resizable: true, min-width: '300px' },
124
    { key: 'age', emptyCellValue: '<em>unknown</em>' }
125
];
126
</code></pre>
127
 
128
### Row Data Configuration
129
 
130
The `data` configuration attribute is responsible for housing the data objects
131
that will be rendered as rows.  You can provide this information in two ways by default:
132
 
133
1. An array of simple objects with key:value pairs
134
2. A ModelList of Base-based class instances (presumably Model subclass
135
   instances)
136
 
137
If an array of objects is passed, it will be translated into a ModelList filled
138
with instances of the class provided to the `recordType` attribute.  This
139
attribute can also create a custom Model subclass from an array of field names
140
or an object of attribute configurations.  If no `recordType` is provided, one
141
will be created for you from available information (see `_initRecordType`).
142
Providing either your own ModelList instance for `data`, or at least Model
143
class for `recordType`, is the best way to control client-server
144
synchronization when modifying data on the client side.
145
 
146
The ModelList instance that manages the table's data is available in the `data`
147
property on the DataTable instance.
148
 
149
 
150
### Rendering
151
 
152
Table rendering is a collaborative process between the DataTable and its
153
configured `view`. The DataTable creates an instance of the configured `view`
154
(DataTable.TableView by default), and calls its `render()` method.
155
DataTable.TableView, for instance, then creates the `<table>` and `<caption>`,
156
then delegates the rendering of the specific sections of the table to subviews,
157
which can be configured as `headerView`, `bodyView`, and `footerView`.
158
DataTable.TableView defaults the `headerView` to DataTable.HeaderView and the
159
`bodyView` to DataTable.BodyView, but leaves the `footerView` unassigned.
160
Setting any subview to `null` will result in that table section not being
161
rendered.
162
 
163
@class DataTable
164
@extends DataTable.Base
165
@since 3.5.0
166
**/
167
 
168
// DataTable API docs included before DataTable.Base to make yuidoc work
169
/**
170
The baseline implementation of a DataTable.  This class should be used
171
primarily as a superclass for a custom DataTable with a specific set of
172
features.  Because features can be composed onto `Y.DataTable`, custom
173
subclasses of DataTable.Base will remain unmodified when new feature modules
174
are loaded.
175
 
176
Example usage might look like this:
177
 
178
<pre><code>
179
// Custom subclass with only sorting and mutability added.  If other datatable
180
// feature modules are loaded, this class will not be affected.
181
var MyTableClass = Y.Base.create('table', Y.DataTable.Base,
182
                       [ Y.DataTable.Sortable, Y.DataTable.Mutable ]);
183
 
184
var table = new MyTableClass({
185
    columns: ['firstName', 'lastName', 'age'],
186
    data: [
187
        { firstName: 'Frank', lastName: 'Zappa', age: 71 },
188
        { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
189
        { firstName: 'Albert', lastName: 'Einstein', age: 132 },
190
        ...
191
    ]
192
});
193
 
194
table.render('#over-there');
195
 
196
// DataTable.Base can be instantiated if a featureless table is needed.
197
var table = new Y.DataTable.Base({
198
    columns: ['firstName', 'lastName', 'age'],
199
    data: [
200
        { firstName: 'Frank', lastName: 'Zappa', age: 71 },
201
        { firstName: 'Frank', lastName: 'Lloyd Wright', age: 144 },
202
        { firstName: 'Albert', lastName: 'Einstein', age: 132 },
203
        ...
204
    ]
205
});
206
 
207
table.render('#in-here');
208
</code></pre>
209
 
210
DataTable.Base is built from DataTable.Core, and sets the default `view`
211
to `Y.DataTable.TableView`.
212
 
213
@class Base
214
@extends Widget
215
@uses DataTable.Core
216
@namespace DataTable
217
@since 3.5.0
218
**/
219
Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core], {
220
 
221
    /**
222
    Pass through to `delegate()` called from the `contentBox`.
223
 
224
    @method delegate
225
    @param type {String} the event type to delegate
226
    @param fn {Function} the callback function to execute.  This function
227
                 will be provided the event object for the delegated event.
228
    @param spec {String|Function} a selector that must match the target of the
229
                 event or a function to test target and its parents for a match
230
    @param context {Object} optional argument that specifies what 'this' refers to
231
    @param args* {any} 0..n additional arguments to pass on to the callback
232
                 function.  These arguments will be added after the event object.
233
    @return {EventHandle} the detach handle
234
    @since 3.5.0
235
    **/
236
    delegate: function () {
237
        var contentBox = this.get('contentBox');
238
 
239
        return contentBox.delegate.apply(contentBox, arguments);
240
    },
241
 
242
    /**
243
    Destroys the table `View` if it's been created.
244
 
245
    @method destructor
246
    @protected
247
    @since 3.6.0
248
    **/
249
    destructor: function () {
250
        if (this.view) {
251
            this.view.destroy();
252
        }
253
    },
254
 
255
    /**
256
    Returns the `<td>` Node from the given row and column index.  Alternately,
257
    the `seed` can be a Node.  If so, the nearest ancestor cell is returned.
258
    If the `seed` is a cell, it is returned.  If there is no cell at the given
259
    coordinates, `null` is returned.
260
 
261
    Optionally, include an offset array or string to return a cell near the
262
    cell identified by the `seed`.  The offset can be an array containing the
263
    number of rows to shift followed by the number of columns to shift, or one
264
    of "above", "below", "next", or "previous".
265
 
266
    <pre><code>// Previous cell in the previous row
267
    var cell = table.getCell(e.target, [-1, -1]);
268
 
269
    // Next cell
270
    var cell = table.getCell(e.target, 'next');
271
    var cell = table.getCell(e.taregt, [0, 1];</pre></code>
272
 
273
    This is actually just a pass through to the `view` instance's method
274
    by the same name.
275
 
276
    @method getCell
277
    @param {Number[]|Node} seed Array of row and column indexes, or a Node that
278
        is either the cell itself or a descendant of one.
279
    @param {Number[]|String} [shift] Offset by which to identify the returned
280
        cell Node
281
    @return {Node}
282
    @since 3.5.0
283
    **/
284
    getCell: function (/* seed, shift */) {
285
        return this.view && this.view.getCell &&
286
            this.view.getCell.apply(this.view, arguments);
287
    },
288
 
289
    /**
290
    Returns the `<tr>` Node from the given row index, Model, or Model's
291
    `clientId`.  If the rows haven't been rendered yet, or if the row can't be
292
    found by the input, `null` is returned.
293
 
294
    This is actually just a pass through to the `view` instance's method
295
    by the same name.
296
 
297
    @method getRow
298
    @param {Number|String|Model} id Row index, Model instance, or clientId
299
    @return {Node}
300
    @since 3.5.0
301
    **/
302
    getRow: function (/* id */) {
303
        return this.view && this.view.getRow &&
304
            this.view.getRow.apply(this.view, arguments);
305
    },
306
 
307
    /**
308
    Updates the `_displayColumns` property.
309
 
310
    @method _afterDisplayColumnsChange
311
    @param {EventFacade} e The `columnsChange` event
312
    @protected
313
    @since 3.6.0
314
    **/
315
    // FIXME: This is a kludge for back compat with features that reference
316
    // _displayColumns.  They should be updated to TableView plugins.
317
    _afterDisplayColumnsChange: function (e) {
318
        this._extractDisplayColumns(e.newVal || []);
319
    },
320
 
321
    /**
322
    Attaches subscriptions to relay core change events to the view.
323
 
324
    @method bindUI
325
    @protected
326
    @since 3.6.0
327
    **/
328
    bindUI: function () {
329
        this._eventHandles.relayCoreChanges = this.after(
330
            ['columnsChange',
331
             'dataChange',
332
             'summaryChange',
333
             'captionChange',
334
             'widthChange'],
335
            Y.bind('_relayCoreAttrChange', this));
336
    },
337
 
338
    /**
339
    The default behavior of the `renderView` event.  Calls `render()` on the
340
    `View` instance on the event.
341
 
342
    @method _defRenderViewFn
343
    @param {EventFacade} e The `renderView` event
344
    @protected
345
    **/
346
    _defRenderViewFn: function (e) {
347
        e.view.render();
348
    },
349
 
350
    /**
351
    Processes the full column array, distilling the columns down to those that
352
    correspond to cell data columns.
353
 
354
    @method _extractDisplayColumns
355
    @param {Object[]} columns The full set of table columns
356
    @protected
357
    **/
358
    // FIXME: this is a kludge for back compat, duplicating logic in the
359
    // tableView
360
    _extractDisplayColumns: function (columns) {
361
        var displayColumns = [];
362
 
363
        function process(cols) {
364
            var i, len, col;
365
 
366
            for (i = 0, len = cols.length; i < len; ++i) {
367
                col = cols[i];
368
 
369
                if (Y.Lang.isArray(col.children)) {
370
                    process(col.children);
371
                } else {
372
                    displayColumns.push(col);
373
                }
374
            }
375
        }
376
 
377
        process(columns);
378
 
379
        /**
380
        Array of the columns that correspond to those with value cells in the
381
        data rows. Excludes colspan header columns (configured with `children`).
382
 
383
        @property _displayColumns
384
        @type {Object[]}
385
        @since 3.5.0
386
        **/
387
        this._displayColumns = displayColumns;
388
    },
389
 
390
    /**
391
    Sets up the instance's events.
392
 
393
    @method initializer
394
    @param {Object} [config] Configuration object passed at construction
395
    @protected
396
    @since 3.6.0
397
    **/
398
    initializer: function () {
399
        this.publish('renderView', {
400
            defaultFn: Y.bind('_defRenderViewFn', this)
401
        });
402
 
403
        // Have to use get('columns'), not config.columns because the setter
404
        // needs to transform string columns to objects.
405
        this._extractDisplayColumns(this.get('columns') || []);
406
 
407
        // FIXME: kludge for back compat of features that reference
408
        // _displayColumns on the instance.  They need to be updated to
409
        // TableView plugins, most likely.
410
        this.after('columnsChange', Y.bind('_afterDisplayColumnsChange', this));
411
    },
412
 
413
    /**
414
    Relays attribute changes to the instance's `view`.
415
 
416
    @method _relayCoreAttrChange
417
    @param {EventFacade} e The change event
418
    @protected
419
    @since 3.6.0
420
    **/
421
    _relayCoreAttrChange: function (e) {
422
        var attr = (e.attrName === 'data') ? 'modelList' : e.attrName;
423
 
424
        this.view.set(attr, e.newVal);
425
    },
426
 
427
    /**
428
    Instantiates the configured `view` class that will be responsible for
429
    setting up the View class.
430
 
431
    @method @renderUI
432
    @protected
433
    @since 3.6.0
434
    **/
435
    renderUI: function () {
436
        var self = this,
437
            View = this.get('view');
438
 
439
        if (View) {
440
            this.view = new View(
441
                Y.merge(
442
                    this.getAttrs(),
443
                    {
444
                        host     : this,
445
                        container: this.get('contentBox'),
446
                        modelList: this.data
447
                    },
448
                    this.get('viewConfig')));
449
 
450
            // For back compat, share the view instances and primary nodes
451
            // on this instance.
452
            // TODO: Remove this?
453
            if (!this._eventHandles.legacyFeatureProps) {
454
                this._eventHandles.legacyFeatureProps = this.view.after({
455
                    renderHeader: function (e) {
456
                        self.head = e.view;
457
                        self._theadNode = e.view.theadNode;
458
                        // TODO: clean up the repetition.
459
                        // This is here so that subscribers to renderHeader etc
460
                        // have access to this._tableNode from the DT instance
461
                        self._tableNode = e.view.get('container');
462
                    },
463
                    renderFooter: function (e) {
464
                        self.foot = e.view;
465
                        self._tfootNode = e.view.tfootNode;
466
                        self._tableNode = e.view.get('container');
467
                    },
468
                    renderBody: function (e) {
469
                        self.body = e.view;
470
                        self._tbodyNode = e.view.tbodyNode;
471
                        self._tableNode = e.view.get('container');
472
                    },
473
                    // FIXME: guarantee that the properties are available, even
474
                    // if the configured (or omitted) views don't create them
475
                    renderTable: function () {
476
                        var contentBox = this.get('container');
477
 
478
                        self._tableNode = this.tableNode ||
479
                            contentBox.one('.' + this.getClassName('table') +
480
                                           ', table');
481
 
482
                        // FIXME: _captionNode isn't available until after
483
                        // renderTable unless in the renderX subs I look for
484
                        // it under the container's parentNode (to account for
485
                        // scroll breaking out the caption table).
486
                        self._captionNode = this.captionNode ||
487
                            contentBox.one('caption');
488
 
489
                        if (!self._theadNode) {
490
                            self._theadNode = contentBox.one(
491
                                '.' + this.getClassName('columns') + ', thead');
492
                        }
493
 
494
                        if (!self._tbodyNode) {
495
                            self._tbodyNode = contentBox.one(
496
                                '.' + this.getClassName('data') + ', tbody');
497
                        }
498
 
499
                        if (!self._tfootNode) {
500
                            self._tfootNode = contentBox.one(
501
                                '.' + this.getClassName('footer') + ', tfoot');
502
                        }
503
                    }
504
                });
505
            }
506
 
507
            // To *somewhat* preserve table.on('renderHeader', fn) in the
508
            // form of table.on('table:renderHeader', fn), because I couldn't
509
            // figure out another option.
510
            this.view.addTarget(this);
511
        }
512
    },
513
 
514
    /**
515
    Fires the `renderView` event, delegating UI updates to the configured View.
516
 
517
    @method syncUI
518
    @since 3.5.0
519
    **/
520
    syncUI: function () {
521
        if (this.view) {
522
            this.fire('renderView', { view: this.view });
523
        }
524
    },
525
 
526
    /**
527
    Verifies the input value is a function with a `render` method on its
528
    prototype.  `null` is also accepted to remove the default View.
529
 
530
    @method _validateView
531
    @protected
532
    @since 3.5.0
533
    **/
534
    _validateView: function (val) {
535
        // TODO support View instances?
536
        return val === null || (Y.Lang.isFunction(val) && val.prototype.render);
537
    }
538
}, {
539
    ATTRS: {
540
        /**
541
        The View class used to render the `<table>` into the Widget's
542
        `contentBox`.  This View can handle the entire table rendering itself
543
        or delegate to other Views.
544
 
545
        It is not strictly necessary that the class function assigned here be
546
        a View subclass.  It must however have a `render()` method.
547
 
548
        When the DataTable is rendered, an instance of this View will be
549
        created and its `render()` method called.  The View instance will be
550
        assigned to the DataTable instance's `view` property.
551
 
552
        @attribute view
553
        @type {Function}
554
        @default Y.DataTable.TableView
555
        @since 3.6.0
556
        **/
557
        view: {
558
            value: Y.DataTable.TableView,
559
            validator: '_validateView'
560
        },
561
 
562
        /**
563
        Configuration object passed to the class constructor in `view`
564
        during render.
565
 
566
        @attribute viewConfig
567
        @type {Object}
568
        @default undefined (initially unset)
569
        @protected
570
        @since 3.6.0
571
        **/
572
        viewConfig: {}
573
 
574
        /**
575
        If the View class assigned to the DataTable's `view` attribute supports
576
        it, this class will be used for rendering the contents of the
577
        `<thead>`&mdash;the column headers for the table.
578
 
579
        Similar to `view`, the instance of this View will be assigned to the
580
        DataTable instance's `head` property.
581
 
582
        It is not strictly necessary that the class function assigned here be
583
        a View subclass.  It must however have a `render()` method.
584
 
585
        @attribute headerView
586
        @type {Function|Object}
587
        @default Y.DataTable.HeaderView
588
        @since 3.5.0
589
        **/
590
        /*
591
        headerView: {
592
            value: Y.DataTable.HeaderView,
593
            validator: '_validateView'
594
        },
595
        */
596
 
597
        /**
598
        Configuration object passed to the class constructor in `headerView`
599
        during render.
600
 
601
        @attribute headerConfig
602
        @type {Object}
603
        @default undefined (initially unset)
604
        @protected
605
        @since 3.6.0
606
        **/
607
        //headConfig: {},
608
 
609
        /**
610
        If the View class assigned to the DataTable's `view` attribute supports
611
        it, this class will be used for rendering the contents of the `<tfoot>`.
612
 
613
        Similar to `view`, the instance of this View will be assigned to the
614
        DataTable instance's `foot` property.
615
 
616
        It is not strictly necessary that the class function assigned here be
617
        a View subclass.  It must however have a `render()` method.
618
 
619
        @attribute footerView
620
        @type {Function|Object}
621
        @since 3.5.0
622
        **/
623
        /*
624
        footerView: {
625
            validator: '_validateView'
626
        },
627
        */
628
 
629
        /**
630
        Configuration object passed to the class constructor in `footerView`
631
        during render.
632
 
633
        @attribute footerConfig
634
        @type {Object}
635
        @default undefined (initially unset)
636
        @protected
637
        @since 3.6.0
638
        **/
639
        //footerConfig: {},
640
 
641
        /**
642
        If the View class assigned to the DataTable's `view` attribute supports
643
        it, this class will be used for rendering the contents of the `<tbody>`
644
        including all data rows.
645
 
646
        Similar to `view`, the instance of this View will be assigned to the
647
        DataTable instance's `body` property.
648
 
649
        It is not strictly necessary that the class function assigned here be
650
        a View subclass.  It must however have a `render()` method.
651
 
652
        @attribute bodyView
653
        @type {Function}
654
        @default Y.DataTable.BodyView
655
        @since 3.5.0
656
        **/
657
        /*
658
        bodyView: {
659
            value: Y.DataTable.BodyView,
660
            validator: '_validateView'
661
        },
662
        */
663
 
664
        /**
665
        Configuration object passed to the class constructor in `bodyView`
666
        during render.
667
 
668
        @attribute bodyConfig
669
        @type {Object}
670
        @default undefined (initially unset)
671
        @protected
672
        @since 3.6.0
673
        **/
674
        //bodyConfig: {}
675
    }
676
});
677
 
678
// The DataTable API docs are above DataTable.Base docs.
679
Y.DataTable = Y.mix(
680
    Y.Base.create('datatable', Y.DataTable.Base, []), // Create the class
681
    Y.DataTable); // Migrate static and namespaced classes
682
 
683
 
684
}, '3.18.1', {
685
    "requires": [
686
        "datatable-core",
687
        "datatable-table",
688
        "datatable-head",
689
        "datatable-body",
690
        "base-build",
691
        "widget"
692
    ],
693
    "skinnable": true
694
});