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