Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('datatable-table', function (Y, NAME) {
2
 
3
/**
4
View class responsible for rendering a `<table>` from provided data.  Used as
5
the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.
6
 
7
@module datatable
8
@submodule datatable-table
9
@since 3.6.0
10
**/
11
var toArray = Y.Array,
12
    YLang   = Y.Lang,
13
    fromTemplate = YLang.sub,
14
 
15
    isArray    = YLang.isArray,
16
    isFunction = YLang.isFunction;
17
 
18
/**
19
View class responsible for rendering a `<table>` from provided data.  Used as
20
the default `view` for `Y.DataTable.Base` and `Y.DataTable` classes.
21
 
22
 
23
 
24
@class TableView
25
@namespace DataTable
26
@extends View
27
@since 3.6.0
28
**/
29
Y.namespace('DataTable').TableView = Y.Base.create('table', Y.View, [], {
30
 
31
    /**
32
    The HTML template used to create the caption Node if the `caption`
33
    attribute is set.
34
 
35
    @property CAPTION_TEMPLATE
36
    @type {String}
37
    @default '<caption class="{className}"></caption>'
38
    @since 3.6.0
39
    **/
40
    CAPTION_TEMPLATE: '<caption class="{className}"></caption>',
41
 
42
    /**
43
    The HTML template used to create the table Node.
44
 
45
    @property TABLE_TEMPLATE
46
    @type {String}
47
    @default '<table cellspacing="0" class="{className}"></table>'
48
    @since 3.6.0
49
    **/
50
    TABLE_TEMPLATE  : '<table cellspacing="0" class="{className}"></table>',
51
 
52
    /**
53
    The object or instance of the class assigned to `bodyView` that is
54
    responsible for rendering and managing the table's `<tbody>`(s) and its
55
    content.
56
 
57
    @property body
58
    @type {Object}
59
    @default undefined (initially unset)
60
    @since 3.5.0
61
    **/
62
    //body: null,
63
 
64
    /**
65
    The object or instance of the class assigned to `footerView` that is
66
    responsible for rendering and managing the table's `<tfoot>` and its
67
    content.
68
 
69
    @property foot
70
    @type {Object}
71
    @default undefined (initially unset)
72
    @since 3.5.0
73
    **/
74
    //foot: null,
75
 
76
    /**
77
    The object or instance of the class assigned to `headerView` that is
78
    responsible for rendering and managing the table's `<thead>` and its
79
    content.
80
 
81
    @property head
82
    @type {Object}
83
    @default undefined (initially unset)
84
    @since 3.5.0
85
    **/
86
    //head: null,
87
 
88
    //-----------------------------------------------------------------------//
89
    // Public methods
90
    //-----------------------------------------------------------------------//
91
 
92
    /**
93
    Returns the `<td>` Node from the given row and column index.  Alternately,
94
    the `seed` can be a Node.  If so, the nearest ancestor cell is returned.
95
    If the `seed` is a cell, it is returned.  If there is no cell at the given
96
    coordinates, `null` is returned.
97
 
98
    Optionally, include an offset array or string to return a cell near the
99
    cell identified by the `seed`.  The offset can be an array containing the
100
    number of rows to shift followed by the number of columns to shift, or one
101
    of "above", "below", "next", or "previous".
102
 
103
    <pre><code>// Previous cell in the previous row
104
    var cell = table.getCell(e.target, [-1, -1]);
105
 
106
    // Next cell
107
    var cell = table.getCell(e.target, 'next');
108
    var cell = table.getCell(e.taregt, [0, 1];</pre></code>
109
 
110
    This is actually just a pass through to the `bodyView` instance's method
111
    by the same name.
112
 
113
    @method getCell
114
    @param {Number[]|Node} seed Array of row and column indexes, or a Node that
115
        is either the cell itself or a descendant of one.
116
    @param {Number[]|String} [shift] Offset by which to identify the returned
117
        cell Node
118
    @return {Node}
119
    @since 3.5.0
120
    **/
121
    getCell: function (/* seed, shift */) {
122
        return this.body && this.body.getCell &&
123
            this.body.getCell.apply(this.body, arguments);
124
    },
125
 
126
    /**
127
    Returns the generated CSS classname based on the input.  If the `host`
128
    attribute is configured, it will attempt to relay to its `getClassName`
129
    or use its static `NAME` property as a string base.
130
 
131
    If `host` is absent or has neither method nor `NAME`, a CSS classname
132
    will be generated using this class's `NAME`.
133
 
134
    @method getClassName
135
    @param {String} token* Any number of token strings to assemble the
136
        classname from.
137
    @return {String}
138
    @protected
139
    **/
140
    getClassName: function () {
141
        // TODO: add attr with setter for host?
142
        var host = this.host,
143
            NAME = (host && host.constructor.NAME) ||
144
                    this.constructor.NAME;
145
 
146
        if (host && host.getClassName) {
147
            return host.getClassName.apply(host, arguments);
148
        } else {
149
            return Y.ClassNameManager.getClassName
150
                .apply(Y.ClassNameManager,
151
                       [NAME].concat(toArray(arguments, 0, true)));
152
        }
153
    },
154
 
155
    /**
156
    Relays call to the `bodyView`'s `getRecord` method if it has one.
157
 
158
    @method getRecord
159
    @param {String|Node} seed Node or identifier for a row or child element
160
    @return {Model}
161
    @since 3.6.0
162
    **/
163
    getRecord: function () {
164
        return this.body && this.body.getRecord &&
165
            this.body.getRecord.apply(this.body, arguments);
166
    },
167
 
168
    /**
169
    Returns the `<tr>` Node from the given row index, Model, or Model's
170
    `clientId`.  If the rows haven't been rendered yet, or if the row can't be
171
    found by the input, `null` is returned.
172
 
173
    This is actually just a pass through to the `bodyView` instance's method
174
    by the same name.
175
 
176
    @method getRow
177
    @param {Number|String|Model} id Row index, Model instance, or clientId
178
    @return {Node}
179
    @since 3.5.0
180
    **/
181
    getRow: function (/* id */) {
182
        return this.body && this.body.getRow &&
183
            this.body.getRow.apply(this.body, arguments);
184
    },
185
 
186
 
187
    //-----------------------------------------------------------------------//
188
    // Protected and private methods
189
    //-----------------------------------------------------------------------//
190
    /**
191
    Updates the table's `summary` attribute.
192
 
193
    @method _afterSummaryChange
194
    @param {EventHandle} e The change event
195
    @protected
196
    @since 3.6.0
197
    **/
198
    _afterSummaryChange: function (e) {
199
        this._uiSetSummary(e.newVal);
200
    },
201
 
202
    /**
203
    Updates the table's `<caption>`.
204
 
205
    @method _afterCaptionChange
206
    @param {EventHandle} e The change event
207
    @protected
208
    @since 3.6.0
209
    **/
210
    _afterCaptionChange: function (e) {
211
        this._uiSetCaption(e.newVal);
212
    },
213
 
214
    /**
215
    Updates the table's width.
216
 
217
    @method _afterWidthChange
218
    @param {EventHandle} e The change event
219
    @protected
220
    @since 3.6.0
221
    **/
222
    _afterWidthChange: function (e) {
223
        this._uiSetWidth(e.newVal);
224
    },
225
 
226
    /**
227
    Attaches event subscriptions to relay attribute changes to the child Views.
228
 
229
    @method _bindUI
230
    @protected
231
    @since 3.6.0
232
    **/
233
    _bindUI: function () {
234
        var relay;
235
 
236
        if (!this._eventHandles) {
237
            relay = Y.bind('_relayAttrChange', this);
238
 
239
            this._eventHandles = this.after({
240
                columnsChange  : relay,
241
                modelListChange: relay,
242
                summaryChange  : Y.bind('_afterSummaryChange', this),
243
                captionChange  : Y.bind('_afterCaptionChange', this),
244
                widthChange    : Y.bind('_afterWidthChange', this)
245
            });
246
        }
247
    },
248
 
249
    /**
250
    Creates the `<table>`.
251
 
252
    @method _createTable
253
    @return {Node} The `<table>` node
254
    @protected
255
    @since 3.5.0
256
    **/
257
    _createTable: function () {
258
        return Y.Node.create(fromTemplate(this.TABLE_TEMPLATE, {
259
            className: this.getClassName('table')
260
        })).empty();
261
    },
262
 
263
    /**
264
    Calls `render()` on the `bodyView` class instance.
265
 
266
    @method _defRenderBodyFn
267
    @param {EventFacade} e The renderBody event
268
    @protected
269
    @since 3.5.0
270
    **/
271
    _defRenderBodyFn: function (e) {
272
        e.view.render();
273
    },
274
 
275
    /**
276
    Calls `render()` on the `footerView` class instance.
277
 
278
    @method _defRenderFooterFn
279
    @param {EventFacade} e The renderFooter event
280
    @protected
281
    @since 3.5.0
282
    **/
283
    _defRenderFooterFn: function (e) {
284
        e.view.render();
285
    },
286
 
287
    /**
288
    Calls `render()` on the `headerView` class instance.
289
 
290
    @method _defRenderHeaderFn
291
    @param {EventFacade} e The renderHeader event
292
    @protected
293
    @since 3.5.0
294
    **/
295
    _defRenderHeaderFn: function (e) {
296
        e.view.render();
297
    },
298
 
299
    /**
300
    Renders the `<table>` and, if there are associated Views, the `<thead>`,
301
    `<tfoot>`, and `<tbody>` (empty until `syncUI`).
302
 
303
    Assigns the generated table nodes to the `tableNode`, `_theadNode`,
304
    `_tfootNode`, and `_tbodyNode` properties.  Assigns the instantiated Views
305
    to the `head`, `foot`, and `body` properties.
306
 
307
 
308
    @method _defRenderTableFn
309
    @param {EventFacade} e The renderTable event
310
    @protected
311
    @since 3.5.0
312
    **/
313
    _defRenderTableFn: function (e) {
314
        var container = this.get('container'),
315
            attrs = this.getAttrs();
316
 
317
        if (!this.tableNode) {
318
            this.tableNode = this._createTable();
319
        }
320
 
321
        attrs.host  = this.get('host') || this;
322
        attrs.table = this;
323
        attrs.container = this.tableNode;
324
 
325
        this._uiSetCaption(this.get('caption'));
326
        this._uiSetSummary(this.get('summary'));
327
        this._uiSetWidth(this.get('width'));
328
 
329
        if (this.head || e.headerView) {
330
            if (!this.head) {
331
                this.head = new e.headerView(Y.merge(attrs, e.headerConfig));
332
            }
333
 
334
            this.fire('renderHeader', { view: this.head });
335
        }
336
 
337
        if (this.foot || e.footerView) {
338
            if (!this.foot) {
339
                this.foot = new e.footerView(Y.merge(attrs, e.footerConfig));
340
            }
341
 
342
            this.fire('renderFooter', { view: this.foot });
343
        }
344
 
345
        attrs.columns = this.displayColumns;
346
 
347
        if (this.body || e.bodyView) {
348
            if (!this.body) {
349
                this.body = new e.bodyView(Y.merge(attrs, e.bodyConfig));
350
            }
351
 
352
            this.fire('renderBody', { view: this.body });
353
        }
354
 
355
        if (!container.contains(this.tableNode)) {
356
            container.append(this.tableNode);
357
        }
358
 
359
        this._bindUI();
360
    },
361
 
362
    /**
363
    Cleans up state, destroys child views, etc.
364
 
365
    @method destructor
366
    @protected
367
    **/
368
    destructor: function () {
369
        if (this.head && this.head.destroy) {
370
            this.head.destroy();
371
        }
372
        delete this.head;
373
 
374
        if (this.foot && this.foot.destroy) {
375
            this.foot.destroy();
376
        }
377
        delete this.foot;
378
 
379
        if (this.body && this.body.destroy) {
380
            this.body.destroy();
381
        }
382
        delete this.body;
383
 
384
        if (this._eventHandles) {
385
            this._eventHandles.detach();
386
            delete this._eventHandles;
387
        }
388
 
389
        if (this.tableNode) {
390
            this.tableNode.remove().destroy(true);
391
        }
392
    },
393
 
394
    /**
395
    Processes the full column array, distilling the columns down to those that
396
    correspond to cell data columns.
397
 
398
    @method _extractDisplayColumns
399
    @protected
400
    **/
401
    _extractDisplayColumns: function () {
402
        var columns = this.get('columns'),
403
            displayColumns = [];
404
 
405
        function process(cols) {
406
            var i, len, col;
407
 
408
            for (i = 0, len = cols.length; i < len; ++i) {
409
                col = cols[i];
410
 
411
                if (isArray(col.children)) {
412
                    process(col.children);
413
                } else {
414
                    displayColumns.push(col);
415
                }
416
            }
417
        }
418
 
419
        if (columns) {
420
            process(columns);
421
        }
422
 
423
        /**
424
        Array of the columns that correspond to those with value cells in the
425
        data rows. Excludes colspan header columns (configured with `children`).
426
 
427
        @property displayColumns
428
        @type {Object[]}
429
        @since 3.6.0
430
        **/
431
        this.displayColumns = displayColumns;
432
    },
433
 
434
    /**
435
    Publishes core events.
436
 
437
    @method _initEvents
438
    @protected
439
    @since 3.5.0
440
    **/
441
    _initEvents: function () {
442
        this.publish({
443
            // Y.bind used to allow late binding for method override support
444
            renderTable : { defaultFn: Y.bind('_defRenderTableFn', this) },
445
            renderHeader: { defaultFn: Y.bind('_defRenderHeaderFn', this) },
446
            renderBody  : { defaultFn: Y.bind('_defRenderBodyFn', this) },
447
            renderFooter: { defaultFn: Y.bind('_defRenderFooterFn', this) }
448
        });
449
    },
450
 
451
    /**
452
    Constructor logic.
453
 
454
    @method intializer
455
    @param {Object} config Configuration object passed to the constructor
456
    @protected
457
    @since 3.6.0
458
    **/
459
    initializer: function (config) {
460
        this.host = config.host;
461
 
462
        this._initEvents();
463
 
464
        this._extractDisplayColumns();
465
 
466
        this.after('columnsChange', this._extractDisplayColumns, this);
467
    },
468
 
469
    /**
470
    Relays attribute changes to the child Views.
471
 
472
    @method _relayAttrChange
473
    @param {EventHandle} e The change event
474
    @protected
475
    @since 3.6.0
476
    **/
477
    _relayAttrChange: function (e) {
478
        var attr = e.attrName,
479
            val  = e.newVal;
480
 
481
        if (this.head) {
482
            this.head.set(attr, val);
483
        }
484
 
485
        if (this.foot) {
486
            this.foot.set(attr, val);
487
        }
488
 
489
        if (this.body) {
490
            if (attr === 'columns') {
491
                val = this.displayColumns;
492
            }
493
 
494
            this.body.set(attr, val);
495
        }
496
    },
497
 
498
    /**
499
    Creates the UI in the configured `container`.
500
 
501
    @method render
502
    @chainable
503
    **/
504
    render: function () {
505
        if (this.get('container')) {
506
            this.fire('renderTable', {
507
                headerView  : this.get('headerView'),
508
                headerConfig: this.get('headerConfig'),
509
 
510
                bodyView    : this.get('bodyView'),
511
                bodyConfig  : this.get('bodyConfig'),
512
 
513
                footerView  : this.get('footerView'),
514
                footerConfig: this.get('footerConfig')
515
            });
516
        }
517
 
518
        return this;
519
    },
520
 
521
    /**
522
    Creates, removes, or updates the table's `<caption>` element per the input
523
    value.  Empty values result in the caption being removed.
524
 
525
    @method _uiSetCaption
526
    @param {String} htmlContent The content to populate the table caption
527
    @protected
528
    @since 3.5.0
529
    **/
530
    _uiSetCaption: function (htmlContent) {
531
        var table   = this.tableNode,
532
            caption = this.captionNode;
533
 
534
        if (htmlContent) {
535
            if (!caption) {
536
                this.captionNode = caption = Y.Node.create(
537
                    fromTemplate(this.CAPTION_TEMPLATE, {
538
                        className: this.getClassName('caption')
539
                    }));
540
 
541
                table.prepend(this.captionNode);
542
            }
543
 
544
            caption.setHTML(htmlContent);
545
 
546
        } else if (caption) {
547
            caption.remove(true);
548
 
549
            delete this.captionNode;
550
        }
551
    },
552
 
553
    /**
554
    Updates the table's `summary` attribute with the input value.
555
 
556
    @method _uiSetSummary
557
    @protected
558
    @since 3.5.0
559
    **/
560
    _uiSetSummary: function (summary) {
561
        if (summary) {
562
            this.tableNode.setAttribute('summary', summary);
563
        } else {
564
            this.tableNode.removeAttribute('summary');
565
        }
566
    },
567
 
568
    /**
569
    Sets the `boundingBox` and table width per the input value.
570
 
571
    @method _uiSetWidth
572
    @param {Number|String} width The width to make the table
573
    @protected
574
    @since 3.5.0
575
    **/
576
    _uiSetWidth: function (width) {
577
        var table = this.tableNode;
578
 
579
        // Table width needs to account for borders
580
        table.setStyle('width', !width ? '' :
581
            (this.get('container').get('offsetWidth') -
582
             (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0) -
583
             (parseInt(table.getComputedStyle('borderLeftWidth'), 10)||0)) +
584
             'px');
585
 
586
        table.setStyle('width', width);
587
    },
588
 
589
    /**
590
    Ensures that the input is a View class or at least has a `render` method.
591
 
592
    @method _validateView
593
    @param {View|Function} val The View class
594
    @return {Boolean}
595
    @protected
596
    **/
597
    _validateView: function (val) {
598
        return isFunction(val) && val.prototype.render;
599
    }
600
}, {
601
    ATTRS: {
602
        /**
603
        Content for the `<table summary="ATTRIBUTE VALUE HERE">`.  Values
604
        assigned to this attribute will be HTML escaped for security.
605
 
606
        @attribute summary
607
        @type {String}
608
        @default '' (empty string)
609
        @since 3.5.0
610
        **/
611
        //summary: {},
612
 
613
        /**
614
        HTML content of an optional `<caption>` element to appear above the
615
        table.  Leave this config unset or set to a falsy value to remove the
616
        caption.
617
 
618
        @attribute caption
619
        @type HTML
620
        @default undefined (initially unset)
621
        @since 3.6.0
622
        **/
623
        //caption: {},
624
 
625
        /**
626
        Columns to include in the rendered table.
627
 
628
        This attribute takes an array of objects. Each object is considered a
629
        data column or header cell to be rendered.  How the objects are
630
        translated into markup is delegated to the `headerView`, `bodyView`,
631
        and `footerView`.
632
 
633
        The raw value is passed to the `headerView` and `footerView`.  The
634
        `bodyView` receives the instance's `displayColumns` array, which is
635
        parsed from the columns array.  If there are no nested columns (columns
636
        configured with a `children` array), the `displayColumns` is the same
637
        as the raw value.
638
 
639
        @attribute columns
640
        @type {Object[]}
641
        @since 3.6.0
642
        **/
643
        columns: {
644
            validator: isArray
645
        },
646
 
647
        /**
648
        Width of the table including borders.  This value requires units, so
649
        `200` is invalid, but `'200px'` is valid.  Setting the empty string
650
        (the default) will allow the browser to set the table width.
651
 
652
        @attribute width
653
        @type {String}
654
        @default ''
655
        @since 3.6.0
656
        **/
657
        width: {
658
            value: '',
659
            validator: YLang.isString
660
        },
661
 
662
        /**
663
        An instance of this class is used to render the contents of the
664
        `<thead>`&mdash;the column headers for the table.
665
 
666
        The instance of this View will be assigned to the instance's `head`
667
        property.
668
 
669
        It is not strictly necessary that the class function assigned here be
670
        a View subclass.  It must however have a `render()` method.
671
 
672
        @attribute headerView
673
        @type {Function|Object}
674
        @default Y.DataTable.HeaderView
675
        @since 3.6.0
676
        **/
677
        headerView: {
678
            value: Y.DataTable.HeaderView,
679
            validator: '_validateView'
680
        },
681
 
682
        /**
683
        Configuration overrides used when instantiating the `headerView`
684
        instance.
685
 
686
        @attribute headerConfig
687
        @type {Object}
688
        @since 3.6.0
689
        **/
690
        //headerConfig: {},
691
 
692
        /**
693
        An instance of this class is used to render the contents of the
694
        `<tfoot>` (if appropriate).
695
 
696
        The instance of this View will be assigned to the instance's `foot`
697
        property.
698
 
699
        It is not strictly necessary that the class function assigned here be
700
        a View subclass.  It must however have a `render()` method.
701
 
702
        @attribute footerView
703
        @type {Function|Object}
704
        @since 3.6.0
705
        **/
706
        footerView: {
707
            validator: '_validateView'
708
        },
709
 
710
        /**
711
        Configuration overrides used when instantiating the `footerView`
712
        instance.
713
 
714
        @attribute footerConfig
715
        @type {Object}
716
        @since 3.6.0
717
        **/
718
        //footerConfig: {},
719
 
720
        /**
721
        An instance of this class is used to render the contents of the table's
722
        `<tbody>`&mdash;the data cells in the table.
723
 
724
        The instance of this View will be assigned to the instance's `body`
725
        property.
726
 
727
        It is not strictly necessary that the class function assigned here be
728
        a View subclass.  It must however have a `render()` method.
729
 
730
        @attribute bodyView
731
        @type {Function|Object}
732
        @default Y.DataTable.BodyView
733
        @since 3.6.0
734
        **/
735
        bodyView: {
736
            value: Y.DataTable.BodyView,
737
            validator: '_validateView'
738
        }
739
 
740
        /**
741
        Configuration overrides used when instantiating the `bodyView`
742
        instance.
743
 
744
        @attribute bodyConfig
745
        @type {Object}
746
        @since 3.6.0
747
        **/
748
        //bodyConfig: {}
749
    }
750
});
751
 
752
 
753
 
754
 
755
}, '3.18.1', {"requires": ["datatable-core", "datatable-head", "datatable-body", "view", "classnamemanager"]});