3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('datatable-scroll-deprecated', function(Y) {
10 * Extends DataTable base to enable x,y, and xy scrolling.
12 * @submodule datatable-scroll
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);
39 * Adds scrolling to DataTable.
40 * @class DataTableScroll
41 * @extends Plugin.Base
43 function DataTableScroll() {
44 DataTableScroll.superclass.constructor.apply(this, arguments);
47 Y.mix(DataTableScroll, {
50 NAME: "dataTableScroll",
55 * The width for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the x direction.
67 * The height for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the y-direction.
80 * The scrolling direction for the table.
89 var w = this.get('width'),
90 h = this.get('height');
109 * The hexadecimal colour value to set on the top-right of the table if a scrollbar exists.
111 * @attribute COLOR_COLUMNFILLER
115 COLOR_COLUMNFILLER: {
117 validator: YLang.isString,
118 setter: function(param) {
119 if (this._headerContainerNode) {
120 this._headerContainerNode.setStyle('backgroundColor', param);
127 Y.extend(DataTableScroll, Y.Plugin.Base, {
130 * The table node created in datatable-base
132 * @property _parentTableNode
136 _parentTableNode: null,
140 * The THEAD node which resides within the table node created in datatable-base
142 * @property _parentTheadNode
146 _parentTheadNode: null,
150 * The TBODY node which resides within the table node created in datatable-base
152 * @property _parentTbodyNode
156 _parentTbodyNode: null,
160 * The TBODY Message node which resides within the table node created in datatable-base
162 * @property _parentMsgNode
166 _parentMsgNode: null,
170 * The contentBox specified for the datatable in datatable-base
172 * @property _parentContainer
176 _parentContainer: null,
180 * The DIV node that contains all the scrollable elements (a table with a tbody on it)
182 * @property _bodyContainerNode
186 _bodyContainerNode: null,
190 * The DIV node that contains a table with a THEAD in it (which syncs its horizontal scroll with the _bodyContainerNode above)
192 * @property _headerContainerNode
196 _headerContainerNode: null,
199 //--------------------------------------
201 //--------------------------------------
205 initializer: function(config) {
206 var dt = this.get("host");
207 this._parentContainer = dt.get('contentBox');
208 this._parentContainer.addClass(CLASS_SCROLLABLE);
212 /////////////////////////////////////////////////////////////////////////////
214 // Set up Table Nodes
216 /////////////////////////////////////////////////////////////////////////////
219 * Set up methods to fire after host methods execute
221 * @method _setUpNodes
224 _setUpNodes: function() {
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);
235 if (this.get('_scroll') !== 'x') {
236 this.afterHostMethod('_attachTheadThNode', this._attachTheadThNode);
237 this.afterHostMethod('_attachTbodyTdNode', this._attachTbodyTdNode);
243 * Stores the main <table> node provided by the host as a private property
245 * @method _setUpParentTableNode
248 _setUpParentTableNode: function() {
249 this._parentTableNode = this.get('host')._tableNode;
254 * Stores the main <thead> node provided by the host as a private property
256 * @method _setUpParentTheadNode
259 _setUpParentTheadNode: function() {
260 this._parentTheadNode = this.get('host')._theadNode;
264 * Stores the main <tbody> node provided by the host as a private property
266 * @method _setUpParentTbodyNode
269 _setUpParentTbodyNode: function() {
270 this._parentTbodyNode = this.get('host')._tbodyNode;
275 * Stores the main <tbody> message node provided by the host as a private property
277 * @method _setUpParentMessageNode
280 _setUpParentMessageNode: function() {
281 this._parentMsgNode = this.get('host')._msgNode;
284 /////////////////////////////////////////////////////////////////////////////
288 /////////////////////////////////////////////////////////////////////////////
291 * Primary rendering method that takes the datatable rendered in
292 * the host, and splits it up into two separate <divs> 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.
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"));
308 Binds event subscriptions to keep the state and UI in sync
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);
322 * Post rendering method that is responsible for creating a column
323 * filler, and performing width and scroll synchronization between the <th>
324 * elements and the <td> elements.
325 * This method fires after syncUI is called on datatable-base
331 //Y.Profiler.start('sync');
332 this._removeCaptionNode();
335 //Y.Profiler.stop('sync');
336 //console.log(Y.Profiler.getReport("sync"));
341 * Remove the caption created in base. Scrolling datatables dont support captions.
343 * @method _removeCaptionNode
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*/};
353 * Adjusts the width of the TH and the TDs to make sure that the two are in sync
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
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
367 * @method _syncWidths
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),
379 widthProperty = (YUA.ie) ? 'offsetWidth' : 'clientWidth';
381 //stylesheet = new YStyleSheet('columnsSheet'),
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);
397 header.setStyle('width', width + 'px');
398 cell.setStyle('width', width + 'px');
402 // If browser is not IE - get the clientWidth of the Liner
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
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');
420 // IE wasn't recognizing clientWidths, so we are using
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]);
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
442 thLiner.setStyle('width', (tdWidth - 20 + 'px'));
443 tdLiner.setStyle('width', (tdWidth - 20 + 'px'));
444 //stylesheet.set(className,{'width': (tdWidth - 20 + 'px')});
452 //stylesheet.enable();
457 * Adds the approriate width to the liner divs of the TH nodes before they are appended to DOM
459 * @method _attachTheadThNode
462 _attachTheadThNode: function(o) {
463 var width = o.column.get('width');
466 o.th.one('.' + CLASS_LINER)
475 * Adds the appropriate width to the liner divs of the TD nodes before they are appended to DOM
477 * @method _attachTbodyTdNode
480 _attachTbodyTdNode: function(o) {
481 var width = o.column.get('width');
484 o.td.one('.' + CLASS_LINER)
493 * Creates the body DIV that contains all the data.
495 * @method _createBodyContainer
498 _createBodyContainer: function() {
499 var bd = YNode.create(CONTAINER_BODY);
501 this._bodyContainerNode = bd;
502 this._setStylesForTbody();
504 bd.appendChild(this._parentTableNode);
505 this._parentContainer.appendChild(bd);
509 * Creates the DIV that contains a <table> with all the headers.
511 * @method _createHeaderContainer
514 _createHeaderContainer: function() {
515 var hd = YNode.create(CONTAINER_HEADER),
516 tbl = YNode.create(TEMPLATE_TABLE);
518 this._headerContainerNode = hd;
520 //hd.setStyle('backgroundColor',this.get("COLOR_COLUMNFILLER"));
521 this._setStylesForThead();
522 tbl.appendChild(this._parentTheadNode);
524 this._parentContainer.prepend(hd);
529 * Creates styles for the TBODY based on what type of table it is.
531 * @method _setStylesForTbody
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};
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';
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') {
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';
557 el.setStyles(styles);
563 * Creates styles for the THEAD based on what type of datatable it is.
565 * @method _setStylesForThead
568 _setStylesForThead: function() {
569 var w = this.get('width') || "",
570 el = this._headerContainerNode;
573 el.setStyles({'width': w, 'overflow': 'hidden'});
578 * Sets an auto width on the content box if it doesn't exist or if its a y-datatable.
580 * @method _setContentBoxDimensions
583 _setContentBoxDimensions: function() {
585 if (this.get('_scroll') === 'y' || (!this.get('width'))) {
586 this._parentContainer.setStyle('width', 'auto');
591 /////////////////////////////////////////////////////////////////////////////
595 /////////////////////////////////////////////////////////////////////////////
598 * Ensures that scrolling is synced across the two tables
603 _onScroll: function() {
604 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
608 * Syncs padding around scrollable tables, including Column header right-padding
609 * and container width and height.
611 * @method _syncScroll
614 _syncScroll : function() {
617 this._syncScrollOverhang();
620 this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
622 if(!this.get("width")) {
624 document.body.style += '';
630 * Snaps container width for y-scrolling tables.
632 * @method _syncScrollY
635 _syncScrollY : function() {
636 var tBody = this._parentTbodyNode,
637 tBodyContainer = this._bodyContainerNode,
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);
652 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
653 * Taken from YUI2 ScrollingDataTable.js
655 * @method _syncScrollX
658 _syncScrollX: function() {
659 var tBody = this._parentTbodyNode,
660 tBodyContainer = this._bodyContainerNode,
663 this._headerContainerNode.set('scrollLeft',
664 this._bodyContainerNode.get('scrollLeft'));
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";
671 tBodyContainer.setStyle('height', w);
674 if (tBody.get('rows').size()) {
675 this._parentMsgNode.get('parentNode').setStyle('width', "");
677 this._parentMsgNode.get('parentNode').setStyle('width', this._parentTheadNode.get('parentNode').get('offsetWidth')+'px');
683 * Adds/removes Column header overhang as necesary.
684 * Taken from YUI2 ScrollingDataTable.js
686 * @method _syncScrollOverhang
689 _syncScrollOverhang: function() {
690 var tBodyContainer = this._bodyContainerNode,
693 //when its both x and y scrolling
694 if ((tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) || (tBodyContainer.get('scrollWidth') > tBodyContainer.get('clientWidth'))) {
698 this._setOverhangValue(padding);
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'))
709 this._headerContainerNode.setStyle('width', this._parentContainer.get('width'));
715 * Sets Column header overhang to given width.
716 * Taken from YUI2 ScrollingDataTable.js with minor modifications
718 * @method _setOverhangValue
719 * @param nBorderWidth {Number} Value of new border for overhang.
722 _setOverhangValue: function(borderWidth) {
723 var host = this.get('host'),
724 cols = host.get('columnset').get('definitions'),
725 //lastHeaders = cols[cols.length-1] || [],
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);
735 Y.namespace("Plugin").DataTableScroll = DataTableScroll;
738 }, '3.5.1' ,{requires:['datatable-base-deprecated','plugin']});