2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('datatable-scroll', function (Y, NAME) {
11 Adds the ability to make the table rows scrollable while preserving the header
14 @module datatable-scroll
19 isString = YLang.isString,
20 isNumber = YLang.isNumber,
21 isArray = YLang.isArray,
25 // Returns the numeric value portion of the computed style, defaulting to 0
26 function styleDim(node, style) {
27 return parseInt(node.getComputedStyle(style), 10) || 0;
31 _API docs for this extension are included in the DataTable class._
33 Adds the ability to make the table rows scrollable while preserving the header
36 There are two types of scrolling, horizontal (x) and vertical (y). Horizontal
37 scrolling is achieved by wrapping the entire table in a scrollable container.
38 Vertical scrolling is achieved by splitting the table headers and data into two
39 separate tables, the latter of which is wrapped in a vertically scrolling
40 container. In this case, column widths of header cells and data cells are kept
41 in sync programmatically.
43 Since the split table synchronization can be costly at runtime, the split is only
44 done if the data in the table stretches beyond the configured `height` value.
46 To activate or deactivate scrolling, set the `scrollable` attribute to one of
49 * `false` - (default) Scrolling is disabled.
50 * `true` or 'xy' - If `height` is set, vertical scrolling will be activated, if
51 `width` is set, horizontal scrolling will be activated.
52 * 'x' - Activate horizontal scrolling only. Requires the `width` attribute is
54 * 'y' - Activate vertical scrolling only. Requires the `height` attribute is
57 @class DataTable.Scrollable
61 Y.DataTable.Scrollable = Scrollable = function () {};
65 Activates or deactivates scrolling in the table. Acceptable values are:
67 * `false` - (default) Scrolling is disabled.
68 * `true` or 'xy' - If `height` is set, vertical scrolling will be
69 activated, if `width` is set, horizontal scrolling will be activated.
70 * 'x' - Activate horizontal scrolling only. Requires the `width` attribute
72 * 'y' - Activate vertical scrolling only. Requires the `height` attribute
76 @type {String|Boolean}
82 setter: '_setScrollable'
86 Y.mix(Scrollable.prototype, {
89 Scrolls a given row or cell into view if the table is scrolling. Pass the
90 `clientId` of a Model from the DataTable's `data` ModelList or its row
91 index to scroll to a row or a [row index, column index] array to scroll to
92 a cell. Alternately, to scroll to any element contained within the table's
93 scrolling areas, pass its ID, or the Node itself (though you could just as
94 well call `node.scrollIntoView()` yourself, but hey, whatever).
97 @param {String|Number|Number[]|Node} id A row clientId, row index, cell
98 coordinate array, id string, or Node
103 scrollTo: function (id) {
106 if (id && this._tbodyNode && (this._yScrollNode || this._xScrollNode)) {
108 target = this.getCell(id);
109 } else if (isNumber(id)) {
110 target = this.getRow(id);
111 } else if (isString(id)) {
112 target = this._tbodyNode.one('#' + id);
113 } else if (id instanceof Y.Node &&
114 // TODO: ancestor(yScrollNode, xScrollNode)
115 id.ancestor('.yui3-datatable') === this.get('boundingBox')) {
120 target.scrollIntoView();
127 //--------------------------------------------------------------------------
128 // Protected properties and methods
129 //--------------------------------------------------------------------------
132 Template for the `<table>` that is used to fix the caption in place when
133 the table is horizontally scrolling.
135 @property _CAPTION_TABLE_TEMPLATE
137 @value '<table class="{className}" role="presentation"></table>'
141 _CAPTION_TABLE_TEMPLATE: '<table class="{className}" role="presentation"></table>',
144 Template used to create sizable element liners around header content to
145 synchronize fixed header column widths.
147 @property _SCROLL_LINER_TEMPLATE
149 @value '<div class="{className}"></div>'
153 _SCROLL_LINER_TEMPLATE: '<div class="{className}"></div>',
156 Template for the virtual scrollbar needed in "y" and "xy" scrolling setups.
158 @property _SCROLLBAR_TEMPLATE
160 @value '<div class="{className}"><div></div></div>'
164 _SCROLLBAR_TEMPLATE: '<div class="{className}"><div></div></div>',
167 Template for the `<div>` that is used to contain the table when the table is
168 horizontally scrolling.
170 @property _X_SCROLLER_TEMPLATE
172 @value '<div class="{className}"></div>'
176 _X_SCROLLER_TEMPLATE: '<div class="{className}"></div>',
179 Template for the `<table>` used to contain the fixed column headers for
180 vertically scrolling tables.
182 @property _Y_SCROLL_HEADER_TEMPLATE
184 @value '<table cellspacing="0" role="presentation" aria-hidden="true" class="{className}"></table>'
188 _Y_SCROLL_HEADER_TEMPLATE: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>',
191 Template for the `<div>` that is used to contain the rows when the table is
192 vertically scrolling.
194 @property _Y_SCROLLER_TEMPLATE
196 @value '<div class="{className}"><div class="{scrollerClassName}"></div></div>'
200 _Y_SCROLLER_TEMPLATE: '<div class="{className}"><div class="{scrollerClassName}"></div></div>',
203 Adds padding to the last cells in the fixed header for vertically scrolling
204 tables. This padding is equal in width to the scrollbar, so can't be
205 relegated to a stylesheet.
207 @method _addScrollbarPadding
211 _addScrollbarPadding: function () {
212 var fixedHeader = this._yScrollHeader,
213 headerClass = '.' + this.getClassName('header'),
214 scrollbarWidth, rows, header, i, len;
217 scrollbarWidth = Y.DOM.getScrollbarWidth() + 'px';
218 rows = fixedHeader.all('tr');
220 for (i = 0, len = rows.size(); i < len; i += +header.get('rowSpan')) {
221 header = rows.item(i).all(headerClass).pop();
222 header.setStyle('paddingRight', scrollbarWidth);
228 Reacts to changes in the `scrollable` attribute by updating the `_xScroll`
229 and `_yScroll` properties and syncing the scrolling structure accordingly.
231 @method _afterScrollableChange
232 @param {EventFacade} e The relevant change event (ignored)
236 _afterScrollableChange: function () {
237 var scroller = this._xScrollNode;
239 if (this._xScroll && scroller) {
240 if (this._yScroll && !this._yScrollNode) {
241 scroller.setStyle('paddingRight',
242 Y.DOM.getScrollbarWidth() + 'px');
243 } else if (!this._yScroll && this._yScrollNode) {
244 scroller.setStyle('paddingRight', '');
248 this._syncScrollUI();
252 Reacts to changes in the `caption` attribute by adding, removing, or
253 syncing the caption table when the table is set to scroll.
255 @method _afterScrollCaptionChange
256 @param {EventFacade} e The relevant change event (ignored)
260 _afterScrollCaptionChange: function () {
261 if (this._xScroll || this._yScroll) {
262 this._syncScrollUI();
267 Reacts to changes in the `columns` attribute of vertically scrolling tables
268 by refreshing the fixed headers, scroll container, and virtual scrollbar
271 @method _afterScrollColumnsChange
272 @param {EventFacade} e The relevant change event (ignored)
276 _afterScrollColumnsChange: function () {
277 if (this._xScroll || this._yScroll) {
278 if (this._yScroll && this._yScrollHeader) {
279 this._syncScrollHeaders();
282 this._syncScrollUI();
287 Reacts to changes in vertically scrolling table's `data` ModelList by
288 synchronizing the fixed column header widths and virtual scrollbar height.
290 @method _afterScrollDataChange
291 @param {EventFacade} e The relevant change event (ignored)
295 _afterScrollDataChange: function () {
296 if (this._xScroll || this._yScroll) {
297 this._syncScrollUI();
302 Reacts to changes in the `height` attribute of vertically scrolling tables
303 by updating the height of the `<div>` wrapping the data table and the
304 virtual scrollbar. If `scrollable` was set to "y" or "xy" but lacking a
305 declared `height` until the received change, `_syncScrollUI` is called to
306 create the fixed headers etc.
308 @method _afterScrollHeightChange
309 @param {EventFacade} e The relevant change event (ignored)
313 _afterScrollHeightChange: function () {
315 this._syncScrollUI();
319 /* (not an API doc comment on purpose)
320 Reacts to the sort event (if the table is also sortable) by updating the
321 fixed header classes to match the data table's headers.
323 THIS IS A HACK that will be removed immediately after the 3.5.0 release.
324 If you're reading this and the current version is greater than 3.5.0, I
325 should be publicly scolded.
327 _afterScrollSort: function () {
328 var headers, headerClass;
330 if (this._yScroll && this._yScrollHeader) {
331 headerClass = '.' + this.getClassName('header');
332 headers = this._theadNode.all(headerClass);
334 this._yScrollHeader.all(headerClass).each(function (header, i) {
335 header.set('className', headers.item(i).get('className'));
341 Reacts to changes in the width of scrolling tables by expanding the width of
342 the `<div>` wrapping the data table for horizontally scrolling tables or
343 upding the position of the virtual scrollbar for vertically scrolling
346 @method _afterScrollWidthChange
347 @param {EventFacade} e The relevant change event (ignored)
351 _afterScrollWidthChange: function () {
352 if (this._xScroll || this._yScroll) {
353 this._syncScrollUI();
358 Binds virtual scrollbar interaction to the `_yScrollNode`'s `scrollTop` and
361 @method _bindScrollbar
365 _bindScrollbar: function () {
366 var scrollbar = this._scrollbarNode,
367 scroller = this._yScrollNode;
369 if (scrollbar && scroller && !this._scrollbarEventHandle) {
370 this._scrollbarEventHandle = new Y.Event.Handle([
371 scrollbar.on('scroll', this._syncScrollPosition, this),
372 scroller.on('scroll', this._syncScrollPosition, this)
378 Binds to the window resize event to update the vertical scrolling table
379 headers and wrapper `<div>` dimensions.
381 @method _bindScrollResize
385 _bindScrollResize: function () {
386 if (!this._scrollResizeHandle) {
387 // TODO: sync header widths and scrollbar position. If the height
388 // of the headers has changed, update the scrollbar dims as well.
389 this._scrollResizeHandle = Y.on('resize',
390 this._syncScrollUI, null, this);
395 Attaches internal subscriptions to keep the scrolling structure up to date
396 with changes in the table's `data`, `columns`, `caption`, or `height`. The
397 `width` is taken care of already.
399 This executes after the table's native `bindUI` method.
401 @method _bindScrollUI
405 _bindScrollUI: function () {
407 columnsChange: Y.bind('_afterScrollColumnsChange', this),
408 heightChange : Y.bind('_afterScrollHeightChange', this),
409 widthChange : Y.bind('_afterScrollWidthChange', this),
410 captionChange: Y.bind('_afterScrollCaptionChange', this),
411 scrollableChange: Y.bind('_afterScrollableChange', this),
412 // FIXME: this is a last minute hack to work around the fact that
413 // DT doesn't use a tableView to render table content that can be
414 // replaced with a scrolling table view. This must be removed asap!
415 sort : Y.bind('_afterScrollSort', this)
418 this.after(['dataChange', '*:add', '*:remove', '*:reset', '*:change'],
419 Y.bind('_afterScrollDataChange', this));
423 Clears the lock and timer used to manage synchronizing the scroll position
424 between the vertical scroll container and the virtual scrollbar.
426 @method _clearScrollLock
430 _clearScrollLock: function () {
431 if (this._scrollLock) {
432 this._scrollLock.cancel();
433 delete this._scrollLock;
438 Creates a virtual scrollbar from the `_SCROLLBAR_TEMPLATE`, assigning it to
439 the `_scrollbarNode` property.
441 @method _createScrollbar
442 @return {Node} The created Node
446 _createScrollbar: function () {
447 var scrollbar = this._scrollbarNode;
450 scrollbar = this._scrollbarNode = Y.Node.create(
451 Y.Lang.sub(this._SCROLLBAR_TEMPLATE, {
452 className: this.getClassName('scrollbar')
455 // IE 6-10 require the scrolled area to be visible (at least 1px)
456 // or they don't respond to clicking on the scrollbar rail or arrows
457 scrollbar.setStyle('width', (Y.DOM.getScrollbarWidth() + 1) + 'px');
464 Creates a separate table to contain the caption when the table is
465 configured to scroll vertically or horizontally.
467 @method _createScrollCaptionTable
468 @return {Node} The created Node
472 _createScrollCaptionTable: function () {
473 if (!this._captionTable) {
474 this._captionTable = Y.Node.create(
475 Y.Lang.sub(this._CAPTION_TABLE_TEMPLATE, {
476 className: this.getClassName('caption', 'table')
479 this._captionTable.empty();
482 return this._captionTable;
486 Populates the `_xScrollNode` property by creating the `<div>` Node described
487 by the `_X_SCROLLER_TEMPLATE`.
489 @method _createXScrollNode
490 @return {Node} The created Node
494 _createXScrollNode: function () {
495 if (!this._xScrollNode) {
496 this._xScrollNode = Y.Node.create(
497 Y.Lang.sub(this._X_SCROLLER_TEMPLATE, {
498 className: this.getClassName('x','scroller')
502 return this._xScrollNode;
506 Populates the `_yScrollHeader` property by creating the `<table>` Node
507 described by the `_Y_SCROLL_HEADER_TEMPLATE`.
509 @method _createYScrollHeader
510 @return {Node} The created Node
514 _createYScrollHeader: function () {
515 var fixedHeader = this._yScrollHeader;
518 fixedHeader = this._yScrollHeader = Y.Node.create(
519 Y.Lang.sub(this._Y_SCROLL_HEADER_TEMPLATE, {
520 className: this.getClassName('scroll','columns')
528 Populates the `_yScrollNode` property by creating the `<div>` Node described
529 by the `_Y_SCROLLER_TEMPLATE`.
531 @method _createYScrollNode
532 @return {Node} The created Node
536 _createYScrollNode: function () {
539 if (!this._yScrollNode) {
540 scrollerClass = this.getClassName('y', 'scroller');
542 this._yScrollContainer = Y.Node.create(
543 Y.Lang.sub(this._Y_SCROLLER_TEMPLATE, {
544 className: this.getClassName('y','scroller','container'),
545 scrollerClassName: scrollerClass
548 this._yScrollNode = this._yScrollContainer
549 .one('.' + scrollerClass);
552 return this._yScrollContainer;
556 Removes the nodes used to create horizontal and vertical scrolling and
557 rejoins the caption to the main table if needed.
559 @method _disableScrolling
563 _disableScrolling: function () {
564 this._removeScrollCaptionTable();
565 this._disableXScrolling();
566 this._disableYScrolling();
567 this._unbindScrollResize();
569 this._uiSetWidth(this.get('width'));
573 Removes the nodes used to allow horizontal scrolling.
575 @method _disableXScrolling
579 _disableXScrolling: function () {
580 this._removeXScrollNode();
584 Removes the nodes used to allow vertical scrolling.
586 @method _disableYScrolling
590 _disableYScrolling: function () {
591 this._removeYScrollHeader();
592 this._removeYScrollNode();
593 this._removeYScrollContainer();
594 this._removeScrollbar();
598 Cleans up external event subscriptions.
604 destructor: function () {
605 this._unbindScrollbar();
606 this._unbindScrollResize();
607 this._clearScrollLock();
611 Sets up event handlers and AOP advice methods to bind the DataTable's natural
612 behaviors with the scrolling APIs and state.
615 @param {Object} config The config object passed to the constructor (ignored)
619 initializer: function () {
620 this._setScrollProperties();
622 this.after(['scrollableChange', 'heightChange', 'widthChange'],
623 this._setScrollProperties);
625 this.after('renderView', Y.bind('_syncScrollUI', this));
627 Y.Do.after(this._bindScrollUI, this, 'bindUI');
631 Removes the table used to house the caption when the table is scrolling.
633 @method _removeScrollCaptionTable
637 _removeScrollCaptionTable: function () {
638 if (this._captionTable) {
639 if (this._captionNode) {
640 this._tableNode.prepend(this._captionNode);
643 this._captionTable.remove().destroy(true);
645 delete this._captionTable;
650 Removes the `<div>` wrapper used to contain the data table when the table
651 is horizontally scrolling.
653 @method _removeXScrollNode
657 _removeXScrollNode: function () {
658 var scroller = this._xScrollNode;
661 scroller.replace(scroller.get('childNodes').toFrag());
662 scroller.remove().destroy(true);
664 delete this._xScrollNode;
669 Removes the `<div>` wrapper used to contain the data table and fixed header
670 when the table is vertically scrolling.
672 @method _removeYScrollContainer
676 _removeYScrollContainer: function () {
677 var scroller = this._yScrollContainer;
680 scroller.replace(scroller.get('childNodes').toFrag());
681 scroller.remove().destroy(true);
683 delete this._yScrollContainer;
688 Removes the `<table>` used to contain the fixed column headers when the
689 table is vertically scrolling.
691 @method _removeYScrollHeader
695 _removeYScrollHeader: function () {
696 if (this._yScrollHeader) {
697 this._yScrollHeader.remove().destroy(true);
699 delete this._yScrollHeader;
704 Removes the `<div>` wrapper used to contain the data table when the table
705 is vertically scrolling.
707 @method _removeYScrollNode
711 _removeYScrollNode: function () {
712 var scroller = this._yScrollNode;
715 scroller.replace(scroller.get('childNodes').toFrag());
716 scroller.remove().destroy(true);
718 delete this._yScrollNode;
723 Removes the virtual scrollbar used by scrolling tables.
725 @method _removeScrollbar
729 _removeScrollbar: function () {
730 if (this._scrollbarNode) {
731 this._scrollbarNode.remove().destroy(true);
733 delete this._scrollbarNode;
735 if (this._scrollbarEventHandle) {
736 this._scrollbarEventHandle.detach();
738 delete this._scrollbarEventHandle;
743 Accepts (case insensitive) values "x", "y", "xy", `true`, and `false`.
744 `true` is translated to "xy" and upper case values are converted to lower
745 case. All other values are invalid.
747 @method _setScrollable
748 @param {String|Boolea} val Incoming value for the `scrollable` attribute
753 _setScrollable: function (val) {
759 val = val.toLowerCase();
762 return (val === false || val === 'y' || val === 'x' || val === 'xy') ?
764 Y.Attribute.INVALID_VALUE;
768 Assigns the `_xScroll` and `_yScroll` properties to true if an
769 appropriate value is set in the `scrollable` attribute and the `height`
770 and/or `width` is set.
772 @method _setScrollProperties
776 _setScrollProperties: function () {
777 var scrollable = this.get('scrollable') || '',
778 width = this.get('width'),
779 height = this.get('height');
781 this._xScroll = width && scrollable.indexOf('x') > -1;
782 this._yScroll = height && scrollable.indexOf('y') > -1;
786 Keeps the virtual scrollbar and the scrolling `<div>` wrapper around the
787 data table in vertically scrolling tables in sync.
789 @method _syncScrollPosition
790 @param {DOMEventFacade} e The scroll event
794 _syncScrollPosition: function (e) {
795 var scrollbar = this._scrollbarNode,
796 scroller = this._yScrollNode,
797 source = e.currentTarget,
800 if (scrollbar && scroller) {
801 if (this._scrollLock && this._scrollLock.source !== source) {
805 this._clearScrollLock();
806 this._scrollLock = Y.later(300, this, this._clearScrollLock);
807 this._scrollLock.source = source;
809 other = (source === scrollbar) ? scroller : scrollbar;
810 other.set('scrollTop', source.get('scrollTop'));
815 Splits the caption from the data `<table>` if the table is configured to
816 scroll. If not, rejoins the caption to the data `<table>` if it needs to
819 @method _syncScrollCaptionUI
823 _syncScrollCaptionUI: function () {
824 var caption = this._captionNode,
825 table = this._tableNode,
826 captionTable = this._captionTable,
830 id = caption.getAttribute('id');
833 captionTable = this._createScrollCaptionTable();
835 this.get('contentBox').prepend(captionTable);
838 if (!caption.get('parentNode').compareTo(captionTable)) {
839 captionTable.empty().insert(caption);
842 id = Y.stamp(caption);
843 caption.setAttribute('id', id);
846 table.setAttribute('aria-describedby', id);
848 } else if (captionTable) {
849 this._removeScrollCaptionTable();
854 Assigns widths to the fixed header columns to match the columns in the data
857 @method _syncScrollColumnWidths
861 _syncScrollColumnWidths: function () {
864 if (this._theadNode && this._yScrollHeader) {
865 // Capture dims and assign widths in two passes to avoid reflows for
866 // each access of clientWidth/getComputedStyle
867 this._theadNode.all('.' + this.getClassName('header'))
868 .each(function (header) {
870 // FIXME: IE returns the col.style.width from
871 // getComputedStyle even if the column has been
872 // compressed below that width, so it must use
873 // clientWidth. FF requires getComputedStyle because it
874 // uses fractional widths that round up to an overall
875 // cell/table width 1px greater than the data table's
876 // cell/table width, resulting in misaligned columns or
877 // fixed header bleed through. I can't think of a
878 // *reasonable* way to capture the correct width without
879 // a sniff. Math.min(cW - p, getCS(w)) was imperfect
880 // and punished all browsers, anyway.
881 (Y.UA.ie && Y.UA.ie < 8) ?
882 (header.get('clientWidth') -
883 styleDim(header, 'paddingLeft') -
884 styleDim(header, 'paddingRight')) + 'px' :
885 header.getComputedStyle('width'));
888 this._yScrollHeader.all('.' + this.getClassName('scroll', 'liner'))
889 .each(function (liner, i) {
890 liner.setStyle('width', widths[i]);
896 Creates matching headers in the fixed header table for vertically scrolling
897 tables and synchronizes the column widths.
899 @method _syncScrollHeaders
903 _syncScrollHeaders: function () {
904 var fixedHeader = this._yScrollHeader,
905 linerTemplate = this._SCROLL_LINER_TEMPLATE,
906 linerClass = this.getClassName('scroll', 'liner'),
907 headerClass = this.getClassName('header'),
908 headers = this._theadNode.all('.' + headerClass);
910 if (this._theadNode && fixedHeader) {
911 fixedHeader.empty().appendChild(
912 this._theadNode.cloneNode(true));
914 // Prevent duplicate IDs and assign ARIA attributes to hide
915 // from screen readers
916 fixedHeader.all('[id]').removeAttribute('id');
918 fixedHeader.all('.' + headerClass).each(function (header, i) {
919 var liner = Y.Node.create(Y.Lang.sub(linerTemplate, {
920 className: linerClass
922 refHeader = headers.item(i);
924 // Can't assign via skin css because sort (and potentially
925 // others) might override the padding values.
926 liner.setStyle('padding',
927 refHeader.getComputedStyle('paddingTop') + ' ' +
928 refHeader.getComputedStyle('paddingRight') + ' ' +
929 refHeader.getComputedStyle('paddingBottom') + ' ' +
930 refHeader.getComputedStyle('paddingLeft'));
932 liner.appendChild(header.get('childNodes').toFrag());
934 header.appendChild(liner);
937 this._syncScrollColumnWidths();
939 this._addScrollbarPadding();
944 Wraps the table for X and Y scrolling, if necessary, if the `scrollable`
945 attribute is set. Synchronizes dimensions and DOM placement of all
946 scrolling related nodes.
948 @method _syncScrollUI
952 _syncScrollUI: function () {
953 var x = this._xScroll,
955 xScroller = this._xScrollNode,
956 yScroller = this._yScrollNode,
957 scrollLeft = xScroller && xScroller.get('scrollLeft'),
958 scrollTop = yScroller && yScroller.get('scrollTop');
960 this._uiSetScrollable();
962 // TODO: Probably should split this up into syncX, syncY, and syncXY
964 if ((this.get('width') || '').slice(-1) === '%') {
965 this._bindScrollResize();
967 this._unbindScrollResize();
970 this._syncScrollCaptionUI();
972 this._disableScrolling();
975 if (this._yScrollHeader) {
976 this._yScrollHeader.setStyle('display', 'none');
981 this._disableYScrolling();
984 this._syncXScrollUI(y);
989 this._disableXScrolling();
992 this._syncYScrollUI(x);
995 // Restore scroll position
996 if (scrollLeft && this._xScrollNode) {
997 this._xScrollNode.set('scrollLeft', scrollLeft);
999 if (scrollTop && this._yScrollNode) {
1000 this._yScrollNode.set('scrollTop', scrollTop);
1005 Wraps the table in a scrolling `<div>` of the configured width for "x"
1008 @method _syncXScrollUI
1009 @param {Boolean} xy True if the table is configured with scrollable ="xy"
1013 _syncXScrollUI: function (xy) {
1014 var scroller = this._xScrollNode,
1015 yScroller = this._yScrollContainer,
1016 table = this._tableNode,
1017 width = this.get('width'),
1018 bbWidth = this.get('boundingBox').get('offsetWidth'),
1019 scrollbarWidth = Y.DOM.getScrollbarWidth(),
1020 borderWidth, tableWidth;
1023 scroller = this._createXScrollNode();
1025 // Not using table.wrap() because IE went all crazy, wrapping the
1026 // table in the last td in the table itself.
1027 (yScroller || table).replace(scroller).appendTo(scroller);
1030 // Can't use offsetHeight - clientHeight because IE6 returns
1031 // clientHeight of 0 intially.
1032 borderWidth = styleDim(scroller, 'borderLeftWidth') +
1033 styleDim(scroller, 'borderRightWidth');
1035 scroller.setStyle('width', '');
1036 this._uiSetDim('width', '');
1037 if (xy && this._yScrollContainer) {
1038 this._yScrollContainer.setStyle('width', '');
1041 // Lock the table's unconstrained width to avoid configured column
1042 // widths being ignored
1043 if (Y.UA.ie && Y.UA.ie < 8) {
1044 // Have to assign a style and trigger a reflow to allow the
1045 // subsequent clearing of width + reflow to expand the table to
1046 // natural width in IE 6
1047 table.setStyle('width', width);
1048 table.get('offsetWidth');
1050 table.setStyle('width', '');
1051 tableWidth = table.get('offsetWidth');
1052 table.setStyle('width', tableWidth + 'px');
1054 this._uiSetDim('width', width);
1056 // Can't use 100% width because the borders add additional width
1057 // TODO: Cache the border widths, though it won't prevent a reflow
1058 scroller.setStyle('width', (bbWidth - borderWidth) + 'px');
1060 // expand the table to fill the assigned width if it doesn't
1061 // already overflow the configured width
1062 if ((scroller.get('offsetWidth') - borderWidth) > tableWidth) {
1063 // Assumes the wrapped table doesn't have borders
1065 table.setStyle('width', (scroller.get('offsetWidth') -
1066 borderWidth - scrollbarWidth) + 'px');
1068 table.setStyle('width', '100%');
1074 Wraps the table in a scrolling `<div>` of the configured height (accounting
1075 for the caption if there is one) if "y" scrolling is enabled. Otherwise,
1076 unwraps the table if necessary.
1078 @method _syncYScrollUI
1079 @param {Boolean} xy True if the table is configured with scrollable = "xy"
1083 _syncYScrollUI: function (xy) {
1084 var yScroller = this._yScrollContainer,
1085 yScrollNode = this._yScrollNode,
1086 xScroller = this._xScrollNode,
1087 fixedHeader = this._yScrollHeader,
1088 scrollbar = this._scrollbarNode,
1089 table = this._tableNode,
1090 thead = this._theadNode,
1091 captionTable = this._captionTable,
1092 boundingBox = this.get('boundingBox'),
1093 contentBox = this.get('contentBox'),
1094 width = this.get('width'),
1095 height = boundingBox.get('offsetHeight'),
1096 scrollbarWidth = Y.DOM.getScrollbarWidth(),
1099 if (captionTable && !xy) {
1100 captionTable.setStyle('width', width || '100%');
1104 yScroller = this._createYScrollNode();
1106 yScrollNode = this._yScrollNode;
1108 table.replace(yScroller).appendTo(yScrollNode);
1111 outerScroller = xy ? xScroller : yScroller;
1114 table.setStyle('width', '');
1117 // Set the scroller height
1119 // Account for the horizontal scrollbar in the overall height
1120 height -= scrollbarWidth;
1123 yScrollNode.setStyle('height',
1124 (height - outerScroller.get('offsetTop') -
1125 // because IE6 is returning clientHeight 0 initially
1126 styleDim(outerScroller, 'borderTopWidth') -
1127 styleDim(outerScroller, 'borderBottomWidth')) + 'px');
1129 // Set the scroller width
1131 // For xy scrolling tables, the table should expand freely within
1133 yScroller.setStyle('width',
1134 (table.get('offsetWidth') + scrollbarWidth) + 'px');
1136 this._uiSetYScrollWidth(width);
1139 if (captionTable && !xy) {
1140 captionTable.setStyle('width', yScroller.get('offsetWidth') + 'px');
1143 // Allow headerless scrolling
1144 if (thead && !fixedHeader) {
1145 fixedHeader = this._createYScrollHeader();
1147 yScroller.prepend(fixedHeader);
1149 this._syncScrollHeaders();
1153 this._syncScrollColumnWidths();
1155 fixedHeader.setStyle('display', '');
1156 // This might need to come back if FF has issues
1157 //fixedHeader.setStyle('width', '100%');
1158 //(yScroller.get('clientWidth') + scrollbarWidth) + 'px');
1161 scrollbar = this._createScrollbar();
1163 this._bindScrollbar();
1165 contentBox.prepend(scrollbar);
1168 this._uiSetScrollbarHeight();
1169 this._uiSetScrollbarPosition(outerScroller);
1174 Assigns the appropriate class to the `boundingBox` to identify the DataTable
1175 as horizontally scrolling, vertically scrolling, or both (adds both classes).
1177 Classes added are "yui3-datatable-scrollable-x" or "...-y"
1179 @method _uiSetScrollable
1183 _uiSetScrollable: function () {
1184 this.get('boundingBox')
1185 .toggleClass(this.getClassName('scrollable','x'), this._xScroll)
1186 .toggleClass(this.getClassName('scrollable','y'), this._yScroll);
1190 Updates the virtual scrollbar's height to avoid overlapping with the fixed
1193 @method _uiSetScrollbarHeight
1197 _uiSetScrollbarHeight: function () {
1198 var scrollbar = this._scrollbarNode,
1199 scroller = this._yScrollNode,
1200 fixedHeader = this._yScrollHeader;
1202 if (scrollbar && scroller && fixedHeader) {
1203 scrollbar.get('firstChild').setStyle('height',
1204 this._tbodyNode.get('scrollHeight') + 'px');
1206 scrollbar.setStyle('height',
1207 (parseFloat(scroller.getComputedStyle('height')) -
1208 parseFloat(fixedHeader.getComputedStyle('height'))) + 'px');
1213 Updates the virtual scrollbar's placement to avoid overlapping the fixed
1214 headers or the data table.
1216 @method _uiSetScrollbarPosition
1217 @param {Node} scroller Reference node to position the scrollbar over
1221 _uiSetScrollbarPosition: function (scroller) {
1222 var scrollbar = this._scrollbarNode,
1223 fixedHeader = this._yScrollHeader;
1225 if (scrollbar && scroller && fixedHeader) {
1226 scrollbar.setStyles({
1227 // Using getCS instead of offsetHeight because FF uses
1228 // fractional values, but reports ints to offsetHeight, so
1229 // offsetHeight is unreliable. It is probably fine to use
1230 // offsetHeight in this case but this was left in place after
1231 // fixing an off-by-1px issue in FF 10- by fixing the caption
1232 // font style so FF picked it up.
1233 top: (parseFloat(fixedHeader.getComputedStyle('height')) +
1234 styleDim(scroller, 'borderTopWidth') +
1235 scroller.get('offsetTop')) + 'px',
1237 // Minus 1 because IE 6-10 require the scrolled area to be
1238 // visible by at least 1px or it won't respond to clicks on the
1239 // scrollbar rail or endcap arrows.
1240 left: (scroller.get('offsetWidth') -
1241 Y.DOM.getScrollbarWidth() - 1 -
1242 styleDim(scroller, 'borderRightWidth')) + 'px'
1248 Assigns the width of the `<div>` wrapping the data table in vertically
1251 If the table can't compress to the specified width, the container is
1252 expanded accordingly.
1254 @method _uiSetYScrollWidth
1255 @param {String} width The CSS width to attempt to set
1259 _uiSetYScrollWidth: function (width) {
1260 var scroller = this._yScrollContainer,
1261 table = this._tableNode,
1262 tableWidth, borderWidth, scrollerWidth, scrollbarWidth;
1264 if (scroller && table) {
1265 scrollbarWidth = Y.DOM.getScrollbarWidth();
1268 // Assumes no table border
1269 borderWidth = scroller.get('offsetWidth') -
1270 scroller.get('clientWidth') +
1271 scrollbarWidth; // added back at the end
1273 // The table's rendered width might be greater than the
1275 scroller.setStyle('width', width);
1277 // Have to subtract the border width from the configured width
1278 // because the scroller's width will need to be reduced by the
1279 // border width as well during the width reassignment below.
1280 scrollerWidth = scroller.get('clientWidth') - borderWidth;
1282 // Assumes no table borders
1283 table.setStyle('width', scrollerWidth + 'px');
1285 tableWidth = table.get('offsetWidth');
1287 // Expand the scroll node width if the table can't fit.
1288 // Otherwise, reassign the scroller a pixel width that
1289 // accounts for the borders.
1290 scroller.setStyle('width',
1291 (tableWidth + scrollbarWidth) + 'px');
1293 // Allow the table to expand naturally
1294 table.setStyle('width', '');
1295 scroller.setStyle('width', '');
1297 scroller.setStyle('width',
1298 (table.get('offsetWidth') + scrollbarWidth) + 'px');
1304 Detaches the scroll event subscriptions used to maintain scroll position
1305 parity between the scrollable `<div>` wrapper around the data table and the
1306 virtual scrollbar for vertically scrolling tables.
1308 @method _unbindScrollbar
1312 _unbindScrollbar: function () {
1313 if (this._scrollbarEventHandle) {
1314 this._scrollbarEventHandle.detach();
1319 Detaches the resize event subscription used to maintain column parity for
1320 vertically scrolling tables with percentage widths.
1322 @method _unbindScrollResize
1326 _unbindScrollResize: function () {
1327 if (this._scrollResizeHandle) {
1328 this._scrollResizeHandle.detach();
1329 delete this._scrollResizeHandle;
1334 Indicates horizontal table scrolling is enabled.
1338 @default undefined (not initially set)
1345 Indicates vertical table scrolling is enabled.
1349 @default undefined (not initially set)
1356 Fixed column header `<table>` Node for vertical scrolling tables.
1358 @property _yScrollHeader
1360 @default undefined (not initially set)
1364 //_yScrollHeader: null,
1367 Overflow Node used to contain the data rows in a vertically scrolling table.
1369 @property _yScrollNode
1371 @default undefined (not initially set)
1375 //_yScrollNode: null,
1378 Overflow Node used to contain the table headers and data in a horizontally
1381 @property _xScrollNode
1383 @default undefined (not initially set)
1387 //_xScrollNode: null
1390 Y.Base.mix(Y.DataTable, [Scrollable]);
1393 }, '3.13.0', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true});