MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / datatable-scroll-deprecated / datatable-scroll-deprecated.js
blob3ad53f7552afa83fdf12aa1baf1f2dcfda7f6eb8
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('datatable-scroll-deprecated', function(Y) {
9 /**
10  * Extends DataTable base to enable x,y, and xy scrolling.
11  * @module datatable
12  * @submodule datatable-scroll
13  */
16 var YNode = Y.Node,
17     YLang = Y.Lang,
18     YUA = Y.UA,
19     YgetClassName = Y.ClassNameManager.getClassName,
20     DATATABLE = "datatable",
21     CLASS_HEADER = YgetClassName(DATATABLE, "hd"),
22     CLASS_BODY = YgetClassName(DATATABLE, "bd"),
23     CLASS_DATA = YgetClassName(DATATABLE, "data"),
24     CLASS_LINER = YgetClassName(DATATABLE, "liner"),
25     CLASS_SCROLLABLE = YgetClassName(DATATABLE, "scrollable"),
26     CONTAINER_HEADER = '<div class="'+CLASS_HEADER+'"></div>',
27     CONTAINER_BODY = '<div class="'+CLASS_BODY+'"></div>',
28     TEMPLATE_TABLE = '<table></table>',
29     scrollbarWidth = Y.cached(function () {
30         var testNode = Y.one('body').appendChild('<div style="position:absolute;visibility:hidden;overflow:scroll;width:20px;"><p style="height:1px"/></div>'),
31             width = testNode.get('offsetWidth') - testNode.get('clientWidth');
33         testNode.remove(true);
35         return width;
36     });
37     
38 /**
39  * Adds scrolling to DataTable.
40  * @class DataTableScroll
41  * @extends Plugin.Base
42  */
43 function DataTableScroll() {
44     DataTableScroll.superclass.constructor.apply(this, arguments);
47 Y.mix(DataTableScroll, {
48     NS: "scroll",
50     NAME: "dataTableScroll",
52     ATTRS: {
53     
54         /**
55         *  The width for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the x direction.
56         *
57         * @attribute width
58         * @public
59         * @type string
60         */
61         width: {
62             value: undefined,
63             writeOnce: "initOnly"
64         },
65         
66         /**
67         *  The height for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the y-direction.
68         *
69         * @attribute height
70         * @public
71         * @type string
72         */
73         height: {
74             value: undefined,
75             writeOnce: "initOnly"
76         },
77         
78         
79         /**
80         *  The scrolling direction for the table.
81         *
82         * @attribute scroll
83         * @private
84         * @type string
85         */
86         _scroll: {
87             //value: 'y',
88             valueFn: function() {
89                 var w = this.get('width'),
90                 h = this.get('height');
91                 
92                 if (w && h) {
93                     return 'xy';
94                 }
95                 else if (w) {
96                     return 'x';
97                 }
98                 else if (h) {
99                     return 'y';
100                 }
101                 else {
102                     return null;
103                 }
104             }
105         },
106         
107         
108         /**
109         *  The hexadecimal colour value to set on the top-right of the table if a scrollbar exists. 
110         *
111         * @attribute COLOR_COLUMNFILLER
112         * @public
113         * @type string
114         */
115         COLOR_COLUMNFILLER: {
116             value: '#f2f2f2',
117             validator: YLang.isString,
118             setter: function(param) {
119                 if (this._headerContainerNode) {
120                     this._headerContainerNode.setStyle('backgroundColor', param);
121                 }
122             }
123         }
124     }
127 Y.extend(DataTableScroll, Y.Plugin.Base, {
128     
129     /**
130     *  The table node created in datatable-base
131     *
132     * @property _parentTableNode
133     * @private
134     * @type {Node}
135     */
136     _parentTableNode: null,
137     
138     
139     /**
140     *  The THEAD node which resides within the table node created in datatable-base
141     *
142     * @property _parentTheadNode
143     * @private
144     * @type {Node}
145     */
146     _parentTheadNode: null,
147     
148     
149     /**
150     *  The TBODY node which resides within the table node created in datatable-base
151     *
152     * @property _parentTbodyNode
153     * @private
154     * @type {Node}
155     */
156     _parentTbodyNode: null,
157     
158     
159     /**
160     *  The TBODY Message node which resides within the table node created in datatable-base
161     *
162     * @property _parentMsgNode
163     * @private
164     * @type {Node}
165     */
166     _parentMsgNode: null,
167     
168     
169     /**
170     *  The contentBox specified for the datatable in datatable-base
171     *
172     * @property _parentContainer
173     * @private
174     * @type {Node}
175     */
176     _parentContainer: null,
177     
178     
179     /**
180     *  The DIV node that contains all the scrollable elements (a table with a tbody on it)
181     *
182     * @property _bodyContainerNode
183     * @private
184     * @type {Node}
185     */
186     _bodyContainerNode: null,
187     
188     
189     /**
190     *  The DIV node that contains a table with a THEAD in it (which syncs its horizontal scroll with the _bodyContainerNode above)
191     *
192     * @property _headerContainerNode
193     * @private
194     * @type {Node}
195     */
196     _headerContainerNode: null,
197     
198     
199     //--------------------------------------
200     //  Methods
201     //--------------------------------------
204     
205     initializer: function(config) {
206         var dt = this.get("host");
207         this._parentContainer = dt.get('contentBox');
208         this._parentContainer.addClass(CLASS_SCROLLABLE);
209         this._setUpNodes();
210     },
211     
212     /////////////////////////////////////////////////////////////////////////////
213     //
214     // Set up Table Nodes
215     //
216     /////////////////////////////////////////////////////////////////////////////
217     
218     /**
219     *  Set up methods to fire after host methods execute
220     *
221     * @method _setUpNodes
222     * @private
223     */          
224     _setUpNodes: function() {
225         
226         this.afterHostMethod("_addTableNode", this._setUpParentTableNode);
227         this.afterHostMethod("_addTheadNode", this._setUpParentTheadNode); 
228         this.afterHostMethod("_addTbodyNode", this._setUpParentTbodyNode);
229         this.afterHostMethod("_addMessageNode", this._setUpParentMessageNode);
230         //this.beforeHostMethod('renderUI', this._removeCaptionNode);
231         this.afterHostMethod("renderUI", this.renderUI);
232         this.afterHostMethod("bindUI", this.bindUI);
233         this.afterHostMethod("syncUI", this.syncUI);
234         
235         if (this.get('_scroll') !== 'x') {
236             this.afterHostMethod('_attachTheadThNode', this._attachTheadThNode);
237             this.afterHostMethod('_attachTbodyTdNode', this._attachTbodyTdNode);
238         }
239         
240     },
241         
242     /**
243     *  Stores the main &lt;table&gt; node provided by the host as a private property
244     *
245     * @method _setUpParentTableNode
246     * @private
247     */
248     _setUpParentTableNode: function() {
249         this._parentTableNode = this.get('host')._tableNode;
250     },
251     
252     
253     /**
254     *  Stores the main &lt;thead&gt; node provided by the host as a private property
255     *
256     * @method _setUpParentTheadNode
257     * @private
258     */
259     _setUpParentTheadNode: function() {
260         this._parentTheadNode = this.get('host')._theadNode;
261     },
262     
263     /**
264     *  Stores the main &lt;tbody&gt; node provided by the host as a private property
265     *
266     * @method _setUpParentTbodyNode
267     * @private
268     */
269     _setUpParentTbodyNode: function() {
270         this._parentTbodyNode = this.get('host')._tbodyNode;
271     },
272     
273     
274     /**
275     *  Stores the main &lt;tbody&gt; message node provided by the host as a private property
276     *
277     * @method _setUpParentMessageNode
278     * @private
279     */
280     _setUpParentMessageNode: function() {
281         this._parentMsgNode = this.get('host')._msgNode;
282     },
283     
284     /////////////////////////////////////////////////////////////////////////////
285     //
286     // Renderer
287     //
288     /////////////////////////////////////////////////////////////////////////////
289     
290     /**
291     *  Primary rendering method that takes the datatable rendered in
292     * the host, and splits it up into two separate &lt;divs&gt; each containing two 
293     * separate tables (one containing the head and one containing the body). 
294     * This method fires after renderUI is called on datatable-base.
295     * 
296     * @method renderUI
297     */
298     renderUI: function() {
299         //Y.Profiler.start('render');
300         this._createBodyContainer();
301         this._createHeaderContainer();
302         this._setContentBoxDimensions();
303         //Y.Profiler.stop('render');
304         //console.log(Y.Profiler.getReport("render"));
305     },
306     
307     /**
308     Binds event subscriptions to keep the state and UI in sync
310     @method bindUI
311     **/
312     bindUI: function () {
313         // FIXME: I don't know why the string bind, but I don't want to break
314         // stuff until I have time to rebuild it properly
315         this._bodyContainerNode.on('scroll', Y.bind("_onScroll", this));
317         this.afterHostEvent("recordsetChange", this.syncUI);
318         this.afterHostEvent("recordset:recordsChange", this.syncUI);
319     },
321     /**
322     *  Post rendering method that is responsible for creating a column
323     * filler, and performing width and scroll synchronization between the &lt;th&gt; 
324     * elements and the &lt;td&gt; elements.
325     * This method fires after syncUI is called on datatable-base
326     * 
327     * @method syncUI
328     * @public
329     */
330     syncUI: function() {
331         //Y.Profiler.start('sync');
332         this._removeCaptionNode();
333         this._syncWidths();
334         this._syncScroll();
335         //Y.Profiler.stop('sync');
336         //console.log(Y.Profiler.getReport("sync"));
337         
338     },
339     
340     /**
341     *  Remove the caption created in base. Scrolling datatables dont support captions.
342     * 
343     * @method _removeCaptionNode
344     * @private
345     */
346     _removeCaptionNode: function() {
347         this.get('host')._captionNode.remove();
348         //Y.DataTable.Base.prototype.createCaption = function(v) {/*do nothing*/};
349         //Y.DataTable.Base.prototype._uiSetCaption = function(v) {/*do nothing*/};
350     },
352     /**
353     *  Adjusts the width of the TH and the TDs to make sure that the two are in sync
354     * 
355     * Implementation Details: 
356     *   Compares the width of the TH liner div to the the width of the TD node.
357     *   The TD liner width is not actually used because the TD often stretches
358     *   past the liner if the parent DIV is very large. Measuring the TD width
359     *   is more accurate.
360     *   
361     *   Instead of measuring via .get('width'), 'clientWidth' is used, as it
362     *   returns a number, whereas 'width' returns a string, In IE6,
363     *   'clientWidth' is not supported, so 'offsetWidth' is used. 'offsetWidth'
364     *   is not as accurate on Chrome,FF as 'clientWidth' - thus the need for
365     *   the fork.
366     * 
367     * @method _syncWidths
368     * @private
369     */
370     _syncWidths: function() {
371         var headerTable = this._parentContainer.one('.' + CLASS_HEADER),
372             bodyTable   = this._parentContainer.one('.' + CLASS_BODY),
373             // nodelist of all the THs
374             headers     = headerTable.all('thead .' + CLASS_LINER),
375             // nodelist of the TDs in the first row
376             firstRow    = bodyTable.one('.' + CLASS_DATA + ' tr'),
377             cells       = firstRow && firstRow.all('.' + CLASS_LINER),
378             // FIXME: Code smell
379             widthProperty = (YUA.ie) ? 'offsetWidth' : 'clientWidth';
381             //stylesheet = new YStyleSheet('columnsSheet'),
382             //className;
383             
384         // If there are data rows, iterate each header and the cells of the
385         // first row comparing cell widths.  Assign the larger width to the
386         // narrower node (header or cell).
387         if (cells && cells.size()) {
388             headers.each(function (header, i) {
389                 var cell        = cells.item(i),
390                     headerWidth = header.get(widthProperty),
391                     cellWidth   = cell.get(widthProperty),
392                     width       = Math.max(headerWidth, cellWidth);
394                 width -= (parseInt(header.getComputedStyle('paddingLeft'),10)|0) +
395                          (parseInt(header.getComputedStyle('paddingRight'),10)|0);
396                 
397                 header.setStyle('width', width + 'px');
398                 cell.setStyle('width', width + 'px');
399             });
400         }
401             /*
402             // If browser is not IE - get the clientWidth of the Liner
403             // div and the TD.
404             // Note:  We are not getting the width of the TDLiner, we
405             // are getting the width of the actual cell.  Why? Because
406             // when the table is set to auto width, the cell will grow
407             // to try to fit the table in its container.  The liner
408             // could potentially be much smaller than the cell width.
409             // TODO: Explore if there is a better way using only LINERS
410             // widths - I don't think this should be a problem, given
411             // that the liner is a div, a block element and will
412             // expand to width.
413             if (!ie) {
414                 // TODO: this should actually be done with
415                 // getComputedStyle('width') but this messes up
416                 // columns. Explore this option.
417                 thWidth = thLiner.get('clientWidth');
418                 tdWidth = td.item(i).get('clientWidth');
419             } else {
420                 // IE wasn't recognizing clientWidths, so we are using
421                 // offsetWidths.
422                 // TODO: should use getComputedStyle('width') because
423                 // offsetWidth will screw up when padding is changed.
424                 // TODO: for some reason, using
425                 // tdLiner.get('clientWidth') doesn't work - why not?
426                 thWidth = thLiner.get('offsetWidth');
427                 tdWidth = td.item(i).get('offsetWidth');
428                 //thWidth = parseFloat(thLiner.getComputedStyle('width').split('px')[0]);
429                 //tdWidth = parseFloat(td.item(i).getComputedStyle('width').split('px')[0]);
430             }
431                                 
432             // expand the TH or the TD to match the wider
433             if (thWidth > tdWidth) {
434                 tdLiner.setStyle('width', (thWidth - 20 + 'px'));
435                 //thLiner.setStyle('width', (tdWidth - 20 + 'px'));
436                 //stylesheet.set(className,{'width': (thWidth - 20 + 'px')});
437             } else if (tdWidth > thWidth) {
438                 // if you don't set an explicit width here, when the width
439                 // is set in line 368, it will auto-shrink the widths of
440                 // the other cells (because they dont have an explicit
441                 // width)
442                 thLiner.setStyle('width', (tdWidth - 20 + 'px'));
443                 tdLiner.setStyle('width', (tdWidth - 20 + 'px'));
444                 //stylesheet.set(className,{'width': (tdWidth - 20 + 'px')});
445             }
446                 
447             //}
449         }
450         */
451         
452         //stylesheet.enable();
454     },
455     
456     /**
457     *  Adds the approriate width to the liner divs of the TH nodes before they are appended to DOM
458     *
459     * @method _attachTheadThNode
460     * @private
461     */
462     _attachTheadThNode: function(o) {
463         var width = o.column.get('width');
464         
465         if (width) {
466             o.th.one('.' + CLASS_LINER)
467                 .setStyles({
468                     width: width,
469                     overflow:'hidden'
470                 });
471         }
472     },
473     
474     /**
475     *  Adds the appropriate width to the liner divs of the TD nodes before they are appended to DOM
476     *
477     * @method _attachTbodyTdNode
478     * @private
479     */
480     _attachTbodyTdNode: function(o) {
481         var width = o.column.get('width');
482         
483         if (width) {
484             o.td.one('.' + CLASS_LINER)
485                 .setStyles({
486                     width: width,
487                     overflow: 'hidden'
488                 });
489         }
490     },
491     
492     /**
493     *  Creates the body DIV that contains all the data. 
494     *
495     * @method _createBodyContainer
496     * @private
497     */
498     _createBodyContainer: function() {
499         var bd = YNode.create(CONTAINER_BODY);
500             
501         this._bodyContainerNode = bd;       
502         this._setStylesForTbody();
503         
504         bd.appendChild(this._parentTableNode);
505         this._parentContainer.appendChild(bd);
506     },
507     
508     /**
509     *  Creates the DIV that contains a &lt;table&gt; with all the headers. 
510     *
511     * @method _createHeaderContainer
512     * @private
513     */
514     _createHeaderContainer: function() {
515         var hd = YNode.create(CONTAINER_HEADER),
516             tbl = YNode.create(TEMPLATE_TABLE);
517             
518         this._headerContainerNode = hd;
519         
520         //hd.setStyle('backgroundColor',this.get("COLOR_COLUMNFILLER"));
521         this._setStylesForThead();
522         tbl.appendChild(this._parentTheadNode);
523         hd.appendChild(tbl);
524         this._parentContainer.prepend(hd);
525         
526     },
527     
528     /**
529     *  Creates styles for the TBODY based on what type of table it is.
530     *
531     * @method _setStylesForTbody
532     * @private
533     */
534     _setStylesForTbody: function() {
535         var dir = this.get('_scroll'),
536             w = this.get('width') || "",
537             h = this.get('height') || "",
538             el = this._bodyContainerNode,
539             styles = {width:"", height:h};
540                 
541         if (dir === 'x') {
542             //X-Scrolling tables should not have a Y-Scrollbar so overflow-y is hidden. THe width on x-scrolling tables must be set by user.
543             styles.overflowY = 'hidden';
544             styles.width = w;
545         } else if (dir === 'y') {
546             //Y-Scrolling tables should not have a X-Scrollbar so overflow-x is hidden. The width isn't neccessary because it can be auto.
547             styles.overflowX = 'hidden';
548         } else if (dir === 'xy') {
549             styles.width = w;
550         } else {
551             //scrolling is set to 'null' - ie: width and height are not set. Don't have any type of scrolling.
552             styles.overflowX = 'hidden';
553             styles.overflowY = 'hidden';
554             styles.width = w;
555         }
556         
557         el.setStyles(styles);
558         return el;
559     },
560     
561     
562     /**
563     *  Creates styles for the THEAD based on what type of datatable it is.
564     *
565     * @method _setStylesForThead
566     * @private
567     */
568     _setStylesForThead: function() {
569         var w = this.get('width') || "",
570             el = this._headerContainerNode;
571         
572         //if (dir !== 'y') {
573         el.setStyles({'width': w, 'overflow': 'hidden'});
574         // }
575     },
576     
577     /**
578     *  Sets an auto width on the content box if it doesn't exist or if its a y-datatable.
579     *
580     * @method _setContentBoxDimensions
581     * @private
582     */
583     _setContentBoxDimensions: function() {
584         
585         if (this.get('_scroll') === 'y' || (!this.get('width'))) {
586             this._parentContainer.setStyle('width', 'auto');
587         }
588         
589     },
590     
591     /////////////////////////////////////////////////////////////////////////////
592     //
593     // Scroll Syncing
594     //
595     /////////////////////////////////////////////////////////////////////////////
596     
597     /**
598     *  Ensures that scrolling is synced across the two tables
599     *
600     * @method _onScroll
601     * @private
602     */
603     _onScroll: function() {
604         this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
605     },
606     
607     /**
608      *  Syncs padding around scrollable tables, including Column header right-padding
609      * and container width and height.
610      *
611      * @method _syncScroll
612      * @private 
613      */
614     _syncScroll : function() {
615         this._syncScrollX();
616         this._syncScrollY();
617         this._syncScrollOverhang();
618         if (YUA.opera) {
619             // Bug 1925874
620             this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
621             
622             if(!this.get("width")) {
623                 // Bug 1926125
624                 document.body.style += '';
625             }
626         }
627     },
628     
629     /**
630     *  Snaps container width for y-scrolling tables.
631     *
632     * @method _syncScrollY
633     * @private
634     */
635     _syncScrollY : function() {
636         var tBody = this._parentTbodyNode,
637             tBodyContainer = this._bodyContainerNode,
638             w;
639             // X-scrolling not enabled
640             if(!this.get("width")) {
641                 // Snap outer container width to content
642                 w = (tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) ?
643                 // but account for y-scrollbar since it is visible
644                     (tBody.get('parentNode').get('clientWidth') + scrollbarWidth()) + "px" :
645                     // no y-scrollbar, just borders
646                     (tBody.get('parentNode').get('clientWidth') + 2) + "px";
647                 this._parentContainer.setStyle('width', w);
648         }
649     },
650         
651     /**
652      *  Snaps container height for x-scrolling tables in IE. Syncs message TBODY width. 
653      * Taken from YUI2 ScrollingDataTable.js
654      *
655      * @method _syncScrollX
656      * @private
657      */
658     _syncScrollX: function() {
659         var tBody = this._parentTbodyNode,
660             tBodyContainer = this._bodyContainerNode,
661             w;
663         this._headerContainerNode.set('scrollLeft',
664             this._bodyContainerNode.get('scrollLeft'));
665         
666         if (!this.get('height') && (YUA.ie)) {
667             w = (tBodyContainer.get('scrollWidth') > tBodyContainer.get('offsetWidth')) ?
668                 (tBody.get('parentNode').get('offsetHeight') + scrollbarWidth()) + "px" : 
669                 tBody.get('parentNode').get('offsetHeight') + "px";
670             
671             tBodyContainer.setStyle('height', w);
672         }
673             
674         if (tBody.get('rows').size()) {
675             this._parentMsgNode.get('parentNode').setStyle('width', "");
676         } else {
677             this._parentMsgNode.get('parentNode').setStyle('width', this._parentTheadNode.get('parentNode').get('offsetWidth')+'px');
678         }
679             
680     },
681     
682     /**
683      *  Adds/removes Column header overhang as necesary.
684      * Taken from YUI2 ScrollingDataTable.js
685      *
686      * @method _syncScrollOverhang
687      * @private
688      */
689     _syncScrollOverhang: function() {
690         var tBodyContainer = this._bodyContainerNode,
691             padding = 1;
692         
693         //when its both x and y scrolling
694         if ((tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) || (tBodyContainer.get('scrollWidth') > tBodyContainer.get('clientWidth'))) {
695             padding = 18;
696         }
697         
698         this._setOverhangValue(padding);
699         
700         // After the widths have synced, there is a wrapping issue in the
701         // headerContainer in IE6. The header does not span the full length of
702         // the table (does not cover all of the y-scrollbar). By adding this
703         // line in when there is a y-scroll, the header will span correctly.
704         // TODO: this should not really occur on this.get('_scroll') === y - it
705         // should occur when scrollHeight > clientHeight, but clientHeight is
706         // not getting recognized in IE6?
707         if (YUA.ie !== 0 && this.get('_scroll') === 'y' && this._bodyContainerNode.get('scrollHeight') > this._bodyContainerNode.get('offsetHeight'))
708         {
709             this._headerContainerNode.setStyle('width', this._parentContainer.get('width'));
710         }
711     },
712     
713     
714     /**
715      *  Sets Column header overhang to given width.
716      * Taken from YUI2 ScrollingDataTable.js with minor modifications
717      *
718      * @method _setOverhangValue
719      * @param nBorderWidth {Number} Value of new border for overhang. 
720      * @private
721      */ 
722     _setOverhangValue: function(borderWidth) {
723         var host = this.get('host'),
724             cols = host.get('columnset').get('definitions'),
725             //lastHeaders = cols[cols.length-1] || [],
726             len = cols.length,
727             value = borderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"),
728             children = YNode.all('#'+this._parentContainer.get('id')+ ' .' + CLASS_HEADER + ' table thead th');
730         children.item(len-1).setStyle('borderRight', value);
731     }
732     
735 Y.namespace("Plugin").DataTableScroll = DataTableScroll;
738 }, '3.5.1' ,{requires:['datatable-base-deprecated','plugin']});