1
/* YUI 3.9.1 (build 5852) Copyright 2013 Yahoo! Inc. http://yuilibrary.com/license/ */
2
YUI.add('datatable-scroll', function (Y, NAME) {
5
Adds the ability to make the table rows scrollable while preserving the header
8
@module datatable-scroll
13
isString = YLang.isString,
14
isNumber = YLang.isNumber,
15
isArray = YLang.isArray,
19
// Returns the numeric value portion of the computed style, defaulting to 0
20
function styleDim(node, style) {
21
return parseInt(node.getComputedStyle(style), 10) || 0;
25
_API docs for this extension are included in the DataTable class._
27
Adds the ability to make the table rows scrollable while preserving the header
30
There are two types of scrolling, horizontal (x) and vertical (y). Horizontal
31
scrolling is achieved by wrapping the entire table in a scrollable container.
32
Vertical scrolling is achieved by splitting the table headers and data into two
33
separate tables, the latter of which is wrapped in a vertically scrolling
34
container. In this case, column widths of header cells and data cells are kept
35
in sync programmatically.
37
Since the split table synchronization can be costly at runtime, the split is only
38
done if the data in the table stretches beyond the configured `height` value.
40
To activate or deactivate scrolling, set the `scrollable` attribute to one of
43
* `false` - (default) Scrolling is disabled.
44
* `true` or 'xy' - If `height` is set, vertical scrolling will be activated, if
45
`width` is set, horizontal scrolling will be activated.
46
* 'x' - Activate horizontal scrolling only. Requires the `width` attribute is
48
* 'y' - Activate vertical scrolling only. Requires the `height` attribute is
51
@class DataTable.Scrollable
55
Y.DataTable.Scrollable = Scrollable = function () {};
59
Activates or deactivates scrolling in the table. Acceptable values are:
61
* `false` - (default) Scrolling is disabled.
62
* `true` or 'xy' - If `height` is set, vertical scrolling will be
63
activated, if `width` is set, horizontal scrolling will be activated.
64
* 'x' - Activate horizontal scrolling only. Requires the `width` attribute
66
* 'y' - Activate vertical scrolling only. Requires the `height` attribute
70
@type {String|Boolean}
76
setter: '_setScrollable'
80
Y.mix(Scrollable.prototype, {
83
Scrolls a given row or cell into view if the table is scrolling. Pass the
84
`clientId` of a Model from the DataTable's `data` ModelList or its row
85
index to scroll to a row or a [row index, column index] array to scroll to
86
a cell. Alternately, to scroll to any element contained within the table's
87
scrolling areas, pass its ID, or the Node itself (though you could just as
88
well call `node.scrollIntoView()` yourself, but hey, whatever).
91
@param {String|Number|Number[]|Node} id A row clientId, row index, cell
92
coordinate array, id string, or Node
97
scrollTo: function (id) {
100
if (id && this._tbodyNode && (this._yScrollNode || this._xScrollNode)) {
102
target = this.getCell(id);
103
} else if (isNumber(id)) {
104
target = this.getRow(id);
105
} else if (isString(id)) {
106
target = this._tbodyNode.one('#' + id);
107
} else if (id instanceof Y.Node &&
108
// TODO: ancestor(yScrollNode, xScrollNode)
109
id.ancestor('.yui3-datatable') === this.get('boundingBox')) {
114
target.scrollIntoView();
121
//--------------------------------------------------------------------------
122
// Protected properties and methods
123
//--------------------------------------------------------------------------
126
Template for the `<table>` that is used to fix the caption in place when
127
the table is horizontally scrolling.
129
@property _CAPTION_TABLE_TEMPLATE
131
@value '<table class="{className}" role="presentation"></table>'
135
_CAPTION_TABLE_TEMPLATE: '<table class="{className}" role="presentation"></table>',
138
Template used to create sizable element liners around header content to
139
synchronize fixed header column widths.
141
@property _SCROLL_LINER_TEMPLATE
143
@value '<div class="{className}"></div>'
147
_SCROLL_LINER_TEMPLATE: '<div class="{className}"></div>',
150
Template for the virtual scrollbar needed in "y" and "xy" scrolling setups.
152
@property _SCROLLBAR_TEMPLATE
154
@value '<div class="{className}"><div></div></div>'
158
_SCROLLBAR_TEMPLATE: '<div class="{className}"><div></div></div>',
161
Template for the `<div>` that is used to contain the table when the table is
162
horizontally scrolling.
164
@property _X_SCROLLER_TEMPLATE
166
@value '<div class="{className}"></div>'
170
_X_SCROLLER_TEMPLATE: '<div class="{className}"></div>',
173
Template for the `<table>` used to contain the fixed column headers for
174
vertically scrolling tables.
176
@property _Y_SCROLL_HEADER_TEMPLATE
178
@value '<table cellspacing="0" role="presentation" aria-hidden="true" class="{className}"></table>'
182
_Y_SCROLL_HEADER_TEMPLATE: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>',
185
Template for the `<div>` that is used to contain the rows when the table is
186
vertically scrolling.
188
@property _Y_SCROLLER_TEMPLATE
190
@value '<div class="{className}"><div class="{scrollerClassName}"></div></div>'
194
_Y_SCROLLER_TEMPLATE: '<div class="{className}"><div class="{scrollerClassName}"></div></div>',
197
Adds padding to the last cells in the fixed header for vertically scrolling
198
tables. This padding is equal in width to the scrollbar, so can't be
199
relegated to a stylesheet.
201
@method _addScrollbarPadding
205
_addScrollbarPadding: function () {
206
var fixedHeader = this._yScrollHeader,
207
headerClass = '.' + this.getClassName('header'),
208
scrollbarWidth, rows, header, i, len;
211
scrollbarWidth = Y.DOM.getScrollbarWidth() + 'px';
212
rows = fixedHeader.all('tr');
214
for (i = 0, len = rows.size(); i < len; i += +header.get('rowSpan')) {
215
header = rows.item(i).all(headerClass).pop();
216
header.setStyle('paddingRight', scrollbarWidth);
222
Reacts to changes in the `scrollable` attribute by updating the `_xScroll`
223
and `_yScroll` properties and syncing the scrolling structure accordingly.
225
@method _afterScrollableChange
226
@param {EventFacade} e The relevant change event (ignored)
230
_afterScrollableChange: function () {
231
var scroller = this._xScrollNode;
233
if (this._xScroll && scroller) {
234
if (this._yScroll && !this._yScrollNode) {
235
scroller.setStyle('paddingRight',
236
Y.DOM.getScrollbarWidth() + 'px');
237
} else if (!this._yScroll && this._yScrollNode) {
238
scroller.setStyle('paddingRight', '');
242
this._syncScrollUI();
246
Reacts to changes in the `caption` attribute by adding, removing, or
247
syncing the caption table when the table is set to scroll.
249
@method _afterScrollCaptionChange
250
@param {EventFacade} e The relevant change event (ignored)
254
_afterScrollCaptionChange: function () {
255
if (this._xScroll || this._yScroll) {
256
this._syncScrollUI();
261
Reacts to changes in the `columns` attribute of vertically scrolling tables
262
by refreshing the fixed headers, scroll container, and virtual scrollbar
265
@method _afterScrollColumnsChange
266
@param {EventFacade} e The relevant change event (ignored)
270
_afterScrollColumnsChange: function () {
271
if (this._xScroll || this._yScroll) {
272
if (this._yScroll && this._yScrollHeader) {
273
this._syncScrollHeaders();
276
this._syncScrollUI();
281
Reacts to changes in vertically scrolling table's `data` ModelList by
282
synchronizing the fixed column header widths and virtual scrollbar height.
284
@method _afterScrollDataChange
285
@param {EventFacade} e The relevant change event (ignored)
289
_afterScrollDataChange: function () {
290
if (this._xScroll || this._yScroll) {
291
this._syncScrollUI();
296
Reacts to changes in the `height` attribute of vertically scrolling tables
297
by updating the height of the `<div>` wrapping the data table and the
298
virtual scrollbar. If `scrollable` was set to "y" or "xy" but lacking a
299
declared `height` until the received change, `_syncScrollUI` is called to
300
create the fixed headers etc.
302
@method _afterScrollHeightChange
303
@param {EventFacade} e The relevant change event (ignored)
307
_afterScrollHeightChange: function () {
309
this._syncScrollUI();
313
/* (not an API doc comment on purpose)
314
Reacts to the sort event (if the table is also sortable) by updating the
315
fixed header classes to match the data table's headers.
317
THIS IS A HACK that will be removed immediately after the 3.5.0 release.
318
If you're reading this and the current version is greater than 3.5.0, I
319
should be publicly scolded.
321
_afterScrollSort: function () {
322
var headers, headerClass;
324
if (this._yScroll && this._yScrollHeader) {
325
headerClass = '.' + this.getClassName('header');
326
headers = this._theadNode.all(headerClass);
328
this._yScrollHeader.all(headerClass).each(function (header, i) {
329
header.set('className', headers.item(i).get('className'));
335
Reacts to changes in the width of scrolling tables by expanding the width of
336
the `<div>` wrapping the data table for horizontally scrolling tables or
337
upding the position of the virtual scrollbar for vertically scrolling
340
@method _afterScrollWidthChange
341
@param {EventFacade} e The relevant change event (ignored)
345
_afterScrollWidthChange: function () {
346
if (this._xScroll || this._yScroll) {
347
this._syncScrollUI();
352
Binds virtual scrollbar interaction to the `_yScrollNode`'s `scrollTop` and
355
@method _bindScrollbar
359
_bindScrollbar: function () {
360
var scrollbar = this._scrollbarNode,
361
scroller = this._yScrollNode;
363
if (scrollbar && scroller && !this._scrollbarEventHandle) {
364
this._scrollbarEventHandle = new Y.Event.Handle([
365
scrollbar.on('scroll', this._syncScrollPosition, this),
366
scroller.on('scroll', this._syncScrollPosition, this)
372
Binds to the window resize event to update the vertical scrolling table
373
headers and wrapper `<div>` dimensions.
375
@method _bindScrollResize
379
_bindScrollResize: function () {
380
if (!this._scrollResizeHandle) {
381
// TODO: sync header widths and scrollbar position. If the height
382
// of the headers has changed, update the scrollbar dims as well.
383
this._scrollResizeHandle = Y.on('resize',
384
this._syncScrollUI, null, this);
389
Attaches internal subscriptions to keep the scrolling structure up to date
390
with changes in the table's `data`, `columns`, `caption`, or `height`. The
391
`width` is taken care of already.
393
This executes after the table's native `bindUI` method.
395
@method _bindScrollUI
399
_bindScrollUI: function () {
401
columnsChange: Y.bind('_afterScrollColumnsChange', this),
402
heightChange : Y.bind('_afterScrollHeightChange', this),
403
widthChange : Y.bind('_afterScrollWidthChange', this),
404
captionChange: Y.bind('_afterScrollCaptionChange', this),
405
scrollableChange: Y.bind('_afterScrollableChange', this),
406
// FIXME: this is a last minute hack to work around the fact that
407
// DT doesn't use a tableView to render table content that can be
408
// replaced with a scrolling table view. This must be removed asap!
409
sort : Y.bind('_afterScrollSort', this)
412
this.after(['dataChange', '*:add', '*:remove', '*:reset', '*:change'],
413
Y.bind('_afterScrollDataChange', this));
417
Clears the lock and timer used to manage synchronizing the scroll position
418
between the vertical scroll container and the virtual scrollbar.
420
@method _clearScrollLock
424
_clearScrollLock: function () {
425
if (this._scrollLock) {
426
this._scrollLock.cancel();
427
delete this._scrollLock;
432
Creates a virtual scrollbar from the `_SCROLLBAR_TEMPLATE`, assigning it to
433
the `_scrollbarNode` property.
435
@method _createScrollbar
436
@return {Node} The created Node
440
_createScrollbar: function () {
441
var scrollbar = this._scrollbarNode;
444
scrollbar = this._scrollbarNode = Y.Node.create(
445
Y.Lang.sub(this._SCROLLBAR_TEMPLATE, {
446
className: this.getClassName('scrollbar')
449
// IE 6-10 require the scrolled area to be visible (at least 1px)
450
// or they don't respond to clicking on the scrollbar rail or arrows
451
scrollbar.setStyle('width', (Y.DOM.getScrollbarWidth() + 1) + 'px');
458
Creates a separate table to contain the caption when the table is
459
configured to scroll vertically or horizontally.
461
@method _createScrollCaptionTable
462
@return {Node} The created Node
466
_createScrollCaptionTable: function () {
467
if (!this._captionTable) {
468
this._captionTable = Y.Node.create(
469
Y.Lang.sub(this._CAPTION_TABLE_TEMPLATE, {
470
className: this.getClassName('caption', 'table')
473
this._captionTable.empty();
476
return this._captionTable;
480
Populates the `_xScrollNode` property by creating the `<div>` Node described
481
by the `_X_SCROLLER_TEMPLATE`.
483
@method _createXScrollNode
484
@return {Node} The created Node
488
_createXScrollNode: function () {
489
if (!this._xScrollNode) {
490
this._xScrollNode = Y.Node.create(
491
Y.Lang.sub(this._X_SCROLLER_TEMPLATE, {
492
className: this.getClassName('x','scroller')
496
return this._xScrollNode;
500
Populates the `_yScrollHeader` property by creating the `<table>` Node
501
described by the `_Y_SCROLL_HEADER_TEMPLATE`.
503
@method _createYScrollHeader
504
@return {Node} The created Node
508
_createYScrollHeader: function () {
509
var fixedHeader = this._yScrollHeader;
512
fixedHeader = this._yScrollHeader = Y.Node.create(
513
Y.Lang.sub(this._Y_SCROLL_HEADER_TEMPLATE, {
514
className: this.getClassName('scroll','columns')
522
Populates the `_yScrollNode` property by creating the `<div>` Node described
523
by the `_Y_SCROLLER_TEMPLATE`.
525
@method _createYScrollNode
526
@return {Node} The created Node
530
_createYScrollNode: function () {
533
if (!this._yScrollNode) {
534
scrollerClass = this.getClassName('y', 'scroller');
536
this._yScrollContainer = Y.Node.create(
537
Y.Lang.sub(this._Y_SCROLLER_TEMPLATE, {
538
className: this.getClassName('y','scroller','container'),
539
scrollerClassName: scrollerClass
542
this._yScrollNode = this._yScrollContainer
543
.one('.' + scrollerClass);
546
return this._yScrollContainer;
550
Removes the nodes used to create horizontal and vertical scrolling and
551
rejoins the caption to the main table if needed.
553
@method _disableScrolling
557
_disableScrolling: function () {
558
this._removeScrollCaptionTable();
559
this._disableXScrolling();
560
this._disableYScrolling();
561
this._unbindScrollResize();
563
this._uiSetWidth(this.get('width'));
567
Removes the nodes used to allow horizontal scrolling.
569
@method _disableXScrolling
573
_disableXScrolling: function () {
574
this._removeXScrollNode();
578
Removes the nodes used to allow vertical scrolling.
580
@method _disableYScrolling
584
_disableYScrolling: function () {
585
this._removeYScrollHeader();
586
this._removeYScrollNode();
587
this._removeYScrollContainer();
588
this._removeScrollbar();
592
Cleans up external event subscriptions.
598
destructor: function () {
599
this._unbindScrollbar();
600
this._unbindScrollResize();
601
this._clearScrollLock();
605
Sets up event handlers and AOP advice methods to bind the DataTable's natural
606
behaviors with the scrolling APIs and state.
609
@param {Object} config The config object passed to the constructor (ignored)
613
initializer: function () {
614
this._setScrollProperties();
616
this.after(['scrollableChange', 'heightChange', 'widthChange'],
617
this._setScrollProperties);
619
this.after('renderView', Y.bind('_syncScrollUI', this));
621
Y.Do.after(this._bindScrollUI, this, 'bindUI');
625
Removes the table used to house the caption when the table is scrolling.
627
@method _removeScrollCaptionTable
631
_removeScrollCaptionTable: function () {
632
if (this._captionTable) {
633
if (this._captionNode) {
634
this._tableNode.prepend(this._captionNode);
637
this._captionTable.remove().destroy(true);
639
delete this._captionTable;
644
Removes the `<div>` wrapper used to contain the data table when the table
645
is horizontally scrolling.
647
@method _removeXScrollNode
651
_removeXScrollNode: function () {
652
var scroller = this._xScrollNode;
655
scroller.replace(scroller.get('childNodes').toFrag());
656
scroller.remove().destroy(true);
658
delete this._xScrollNode;
663
Removes the `<div>` wrapper used to contain the data table and fixed header
664
when the table is vertically scrolling.
666
@method _removeYScrollContainer
670
_removeYScrollContainer: function () {
671
var scroller = this._yScrollContainer;
674
scroller.replace(scroller.get('childNodes').toFrag());
675
scroller.remove().destroy(true);
677
delete this._yScrollContainer;
682
Removes the `<table>` used to contain the fixed column headers when the
683
table is vertically scrolling.
685
@method _removeYScrollHeader
689
_removeYScrollHeader: function () {
690
if (this._yScrollHeader) {
691
this._yScrollHeader.remove().destroy(true);
693
delete this._yScrollHeader;
698
Removes the `<div>` wrapper used to contain the data table when the table
699
is vertically scrolling.
701
@method _removeYScrollNode
705
_removeYScrollNode: function () {
706
var scroller = this._yScrollNode;
709
scroller.replace(scroller.get('childNodes').toFrag());
710
scroller.remove().destroy(true);
712
delete this._yScrollNode;
717
Removes the virtual scrollbar used by scrolling tables.
719
@method _removeScrollbar
723
_removeScrollbar: function () {
724
if (this._scrollbarNode) {
725
this._scrollbarNode.remove().destroy(true);
727
delete this._scrollbarNode;
729
if (this._scrollbarEventHandle) {
730
this._scrollbarEventHandle.detach();
732
delete this._scrollbarEventHandle;
737
Accepts (case insensitive) values "x", "y", "xy", `true`, and `false`.
738
`true` is translated to "xy" and upper case values are converted to lower
739
case. All other values are invalid.
741
@method _setScrollable
742
@param {String|Boolea} val Incoming value for the `scrollable` attribute
747
_setScrollable: function (val) {
753
val = val.toLowerCase();
756
return (val === false || val === 'y' || val === 'x' || val === 'xy') ?
758
Y.Attribute.INVALID_VALUE;
762
Assigns the `_xScroll` and `_yScroll` properties to true if an
763
appropriate value is set in the `scrollable` attribute and the `height`
764
and/or `width` is set.
766
@method _setScrollProperties
770
_setScrollProperties: function () {
771
var scrollable = this.get('scrollable') || '',
772
width = this.get('width'),
773
height = this.get('height');
775
this._xScroll = width && scrollable.indexOf('x') > -1;
776
this._yScroll = height && scrollable.indexOf('y') > -1;
780
Keeps the virtual scrollbar and the scrolling `<div>` wrapper around the
781
data table in vertically scrolling tables in sync.
783
@method _syncScrollPosition
784
@param {DOMEventFacade} e The scroll event
788
_syncScrollPosition: function (e) {
789
var scrollbar = this._scrollbarNode,
790
scroller = this._yScrollNode,
791
source = e.currentTarget,
794
if (scrollbar && scroller) {
795
if (this._scrollLock && this._scrollLock.source !== source) {
799
this._clearScrollLock();
800
this._scrollLock = Y.later(300, this, this._clearScrollLock);
801
this._scrollLock.source = source;
803
other = (source === scrollbar) ? scroller : scrollbar;
804
other.set('scrollTop', source.get('scrollTop'));
809
Splits the caption from the data `<table>` if the table is configured to
810
scroll. If not, rejoins the caption to the data `<table>` if it needs to
813
@method _syncScrollCaptionUI
817
_syncScrollCaptionUI: function () {
818
var caption = this._captionNode,
819
table = this._tableNode,
820
captionTable = this._captionTable,
824
id = caption.getAttribute('id');
827
captionTable = this._createScrollCaptionTable();
829
this.get('contentBox').prepend(captionTable);
832
if (!caption.get('parentNode').compareTo(captionTable)) {
833
captionTable.empty().insert(caption);
836
id = Y.stamp(caption);
837
caption.setAttribute('id', id);
840
table.setAttribute('aria-describedby', id);
842
} else if (captionTable) {
843
this._removeScrollCaptionTable();
848
Assigns widths to the fixed header columns to match the columns in the data
851
@method _syncScrollColumnWidths
855
_syncScrollColumnWidths: function () {
858
if (this._theadNode && this._yScrollHeader) {
859
// Capture dims and assign widths in two passes to avoid reflows for
860
// each access of clientWidth/getComputedStyle
861
this._theadNode.all('.' + this.getClassName('header'))
862
.each(function (header) {
864
// FIXME: IE returns the col.style.width from
865
// getComputedStyle even if the column has been
866
// compressed below that width, so it must use
867
// clientWidth. FF requires getComputedStyle because it
868
// uses fractional widths that round up to an overall
869
// cell/table width 1px greater than the data table's
870
// cell/table width, resulting in misaligned columns or
871
// fixed header bleed through. I can't think of a
872
// *reasonable* way to capture the correct width without
873
// a sniff. Math.min(cW - p, getCS(w)) was imperfect
874
// and punished all browsers, anyway.
875
(Y.UA.ie && Y.UA.ie < 8) ?
876
(header.get('clientWidth') -
877
styleDim(header, 'paddingLeft') -
878
styleDim(header, 'paddingRight')) + 'px' :
879
header.getComputedStyle('width'));
882
this._yScrollHeader.all('.' + this.getClassName('scroll', 'liner'))
883
.each(function (liner, i) {
884
liner.setStyle('width', widths[i]);
890
Creates matching headers in the fixed header table for vertically scrolling
891
tables and synchronizes the column widths.
893
@method _syncScrollHeaders
897
_syncScrollHeaders: function () {
898
var fixedHeader = this._yScrollHeader,
899
linerTemplate = this._SCROLL_LINER_TEMPLATE,
900
linerClass = this.getClassName('scroll', 'liner'),
901
headerClass = this.getClassName('header'),
902
headers = this._theadNode.all('.' + headerClass);
904
if (this._theadNode && fixedHeader) {
905
fixedHeader.empty().appendChild(
906
this._theadNode.cloneNode(true));
908
// Prevent duplicate IDs and assign ARIA attributes to hide
909
// from screen readers
910
fixedHeader.all('[id]').removeAttribute('id');
912
fixedHeader.all('.' + headerClass).each(function (header, i) {
913
var liner = Y.Node.create(Y.Lang.sub(linerTemplate, {
914
className: linerClass
916
refHeader = headers.item(i);
918
// Can't assign via skin css because sort (and potentially
919
// others) might override the padding values.
920
liner.setStyle('padding',
921
refHeader.getComputedStyle('paddingTop') + ' ' +
922
refHeader.getComputedStyle('paddingRight') + ' ' +
923
refHeader.getComputedStyle('paddingBottom') + ' ' +
924
refHeader.getComputedStyle('paddingLeft'));
926
liner.appendChild(header.get('childNodes').toFrag());
928
header.appendChild(liner);
931
this._syncScrollColumnWidths();
933
this._addScrollbarPadding();
938
Wraps the table for X and Y scrolling, if necessary, if the `scrollable`
939
attribute is set. Synchronizes dimensions and DOM placement of all
940
scrolling related nodes.
942
@method _syncScrollUI
946
_syncScrollUI: function () {
947
var x = this._xScroll,
949
xScroller = this._xScrollNode,
950
yScroller = this._yScrollNode,
951
scrollLeft = xScroller && xScroller.get('scrollLeft'),
952
scrollTop = yScroller && yScroller.get('scrollTop');
954
this._uiSetScrollable();
956
// TODO: Probably should split this up into syncX, syncY, and syncXY
958
if ((this.get('width') || '').slice(-1) === '%') {
959
this._bindScrollResize();
961
this._unbindScrollResize();
964
this._syncScrollCaptionUI();
966
this._disableScrolling();
969
if (this._yScrollHeader) {
970
this._yScrollHeader.setStyle('display', 'none');
975
this._disableYScrolling();
978
this._syncXScrollUI(y);
983
this._disableXScrolling();
986
this._syncYScrollUI(x);
989
// Restore scroll position
990
if (scrollLeft && this._xScrollNode) {
991
this._xScrollNode.set('scrollLeft', scrollLeft);
993
if (scrollTop && this._yScrollNode) {
994
this._yScrollNode.set('scrollTop', scrollTop);
999
Wraps the table in a scrolling `<div>` of the configured width for "x"
1002
@method _syncXScrollUI
1003
@param {Boolean} xy True if the table is configured with scrollable ="xy"
1007
_syncXScrollUI: function (xy) {
1008
var scroller = this._xScrollNode,
1009
yScroller = this._yScrollContainer,
1010
table = this._tableNode,
1011
width = this.get('width'),
1012
bbWidth = this.get('boundingBox').get('offsetWidth'),
1013
scrollbarWidth = Y.DOM.getScrollbarWidth(),
1014
borderWidth, tableWidth;
1017
scroller = this._createXScrollNode();
1019
// Not using table.wrap() because IE went all crazy, wrapping the
1020
// table in the last td in the table itself.
1021
(yScroller || table).replace(scroller).appendTo(scroller);
1024
// Can't use offsetHeight - clientHeight because IE6 returns
1025
// clientHeight of 0 intially.
1026
borderWidth = styleDim(scroller, 'borderLeftWidth') +
1027
styleDim(scroller, 'borderRightWidth');
1029
scroller.setStyle('width', '');
1030
this._uiSetDim('width', '');
1031
if (xy && this._yScrollContainer) {
1032
this._yScrollContainer.setStyle('width', '');
1035
// Lock the table's unconstrained width to avoid configured column
1036
// widths being ignored
1037
if (Y.UA.ie && Y.UA.ie < 8) {
1038
// Have to assign a style and trigger a reflow to allow the
1039
// subsequent clearing of width + reflow to expand the table to
1040
// natural width in IE 6
1041
table.setStyle('width', width);
1042
table.get('offsetWidth');
1044
table.setStyle('width', '');
1045
tableWidth = table.get('offsetWidth');
1046
table.setStyle('width', tableWidth + 'px');
1048
this._uiSetDim('width', width);
1050
// Can't use 100% width because the borders add additional width
1051
// TODO: Cache the border widths, though it won't prevent a reflow
1052
scroller.setStyle('width', (bbWidth - borderWidth) + 'px');
1054
// expand the table to fill the assigned width if it doesn't
1055
// already overflow the configured width
1056
if ((scroller.get('offsetWidth') - borderWidth) > tableWidth) {
1057
// Assumes the wrapped table doesn't have borders
1059
table.setStyle('width', (scroller.get('offsetWidth') -
1060
borderWidth - scrollbarWidth) + 'px');
1062
table.setStyle('width', '100%');
1068
Wraps the table in a scrolling `<div>` of the configured height (accounting
1069
for the caption if there is one) if "y" scrolling is enabled. Otherwise,
1070
unwraps the table if necessary.
1072
@method _syncYScrollUI
1073
@param {Boolean} xy True if the table is configured with scrollable = "xy"
1077
_syncYScrollUI: function (xy) {
1078
var yScroller = this._yScrollContainer,
1079
yScrollNode = this._yScrollNode,
1080
xScroller = this._xScrollNode,
1081
fixedHeader = this._yScrollHeader,
1082
scrollbar = this._scrollbarNode,
1083
table = this._tableNode,
1084
thead = this._theadNode,
1085
captionTable = this._captionTable,
1086
boundingBox = this.get('boundingBox'),
1087
contentBox = this.get('contentBox'),
1088
width = this.get('width'),
1089
height = boundingBox.get('offsetHeight'),
1090
scrollbarWidth = Y.DOM.getScrollbarWidth(),
1093
if (captionTable && !xy) {
1094
captionTable.setStyle('width', width || '100%');
1098
yScroller = this._createYScrollNode();
1100
yScrollNode = this._yScrollNode;
1102
table.replace(yScroller).appendTo(yScrollNode);
1105
outerScroller = xy ? xScroller : yScroller;
1108
table.setStyle('width', '');
1111
// Set the scroller height
1113
// Account for the horizontal scrollbar in the overall height
1114
height -= scrollbarWidth;
1117
yScrollNode.setStyle('height',
1118
(height - outerScroller.get('offsetTop') -
1119
// because IE6 is returning clientHeight 0 initially
1120
styleDim(outerScroller, 'borderTopWidth') -
1121
styleDim(outerScroller, 'borderBottomWidth')) + 'px');
1123
// Set the scroller width
1125
// For xy scrolling tables, the table should expand freely within
1127
yScroller.setStyle('width',
1128
(table.get('offsetWidth') + scrollbarWidth) + 'px');
1130
this._uiSetYScrollWidth(width);
1133
if (captionTable && !xy) {
1134
captionTable.setStyle('width', yScroller.get('offsetWidth') + 'px');
1137
// Allow headerless scrolling
1138
if (thead && !fixedHeader) {
1139
fixedHeader = this._createYScrollHeader();
1141
yScroller.prepend(fixedHeader);
1143
this._syncScrollHeaders();
1147
this._syncScrollColumnWidths();
1149
fixedHeader.setStyle('display', '');
1150
// This might need to come back if FF has issues
1151
//fixedHeader.setStyle('width', '100%');
1152
//(yScroller.get('clientWidth') + scrollbarWidth) + 'px');
1155
scrollbar = this._createScrollbar();
1157
this._bindScrollbar();
1159
contentBox.prepend(scrollbar);
1162
this._uiSetScrollbarHeight();
1163
this._uiSetScrollbarPosition(outerScroller);
1168
Assigns the appropriate class to the `boundingBox` to identify the DataTable
1169
as horizontally scrolling, vertically scrolling, or both (adds both classes).
1171
Classes added are "yui3-datatable-scrollable-x" or "...-y"
1173
@method _uiSetScrollable
1177
_uiSetScrollable: function () {
1178
this.get('boundingBox')
1179
.toggleClass(this.getClassName('scrollable','x'), this._xScroll)
1180
.toggleClass(this.getClassName('scrollable','y'), this._yScroll);
1184
Updates the virtual scrollbar's height to avoid overlapping with the fixed
1187
@method _uiSetScrollbarHeight
1191
_uiSetScrollbarHeight: function () {
1192
var scrollbar = this._scrollbarNode,
1193
scroller = this._yScrollNode,
1194
fixedHeader = this._yScrollHeader;
1196
if (scrollbar && scroller && fixedHeader) {
1197
scrollbar.get('firstChild').setStyle('height',
1198
this._tbodyNode.get('scrollHeight') + 'px');
1200
scrollbar.setStyle('height',
1201
(parseFloat(scroller.getComputedStyle('height')) -
1202
parseFloat(fixedHeader.getComputedStyle('height'))) + 'px');
1207
Updates the virtual scrollbar's placement to avoid overlapping the fixed
1208
headers or the data table.
1210
@method _uiSetScrollbarPosition
1211
@param {Node} scroller Reference node to position the scrollbar over
1215
_uiSetScrollbarPosition: function (scroller) {
1216
var scrollbar = this._scrollbarNode,
1217
fixedHeader = this._yScrollHeader;
1219
if (scrollbar && scroller && fixedHeader) {
1220
scrollbar.setStyles({
1221
// Using getCS instead of offsetHeight because FF uses
1222
// fractional values, but reports ints to offsetHeight, so
1223
// offsetHeight is unreliable. It is probably fine to use
1224
// offsetHeight in this case but this was left in place after
1225
// fixing an off-by-1px issue in FF 10- by fixing the caption
1226
// font style so FF picked it up.
1227
top: (parseFloat(fixedHeader.getComputedStyle('height')) +
1228
styleDim(scroller, 'borderTopWidth') +
1229
scroller.get('offsetTop')) + 'px',
1231
// Minus 1 because IE 6-10 require the scrolled area to be
1232
// visible by at least 1px or it won't respond to clicks on the
1233
// scrollbar rail or endcap arrows.
1234
left: (scroller.get('offsetWidth') -
1235
Y.DOM.getScrollbarWidth() - 1 -
1236
styleDim(scroller, 'borderRightWidth')) + 'px'
1242
Assigns the width of the `<div>` wrapping the data table in vertically
1245
If the table can't compress to the specified width, the container is
1246
expanded accordingly.
1248
@method _uiSetYScrollWidth
1249
@param {String} width The CSS width to attempt to set
1253
_uiSetYScrollWidth: function (width) {
1254
var scroller = this._yScrollContainer,
1255
table = this._tableNode,
1256
tableWidth, borderWidth, scrollerWidth, scrollbarWidth;
1258
if (scroller && table) {
1259
scrollbarWidth = Y.DOM.getScrollbarWidth();
1262
// Assumes no table border
1263
borderWidth = scroller.get('offsetWidth') -
1264
scroller.get('clientWidth') +
1265
scrollbarWidth; // added back at the end
1267
// The table's rendered width might be greater than the
1269
scroller.setStyle('width', width);
1271
// Have to subtract the border width from the configured width
1272
// because the scroller's width will need to be reduced by the
1273
// border width as well during the width reassignment below.
1274
scrollerWidth = scroller.get('clientWidth') - borderWidth;
1276
// Assumes no table borders
1277
table.setStyle('width', scrollerWidth + 'px');
1279
tableWidth = table.get('offsetWidth');
1281
// Expand the scroll node width if the table can't fit.
1282
// Otherwise, reassign the scroller a pixel width that
1283
// accounts for the borders.
1284
scroller.setStyle('width',
1285
(tableWidth + scrollbarWidth) + 'px');
1287
// Allow the table to expand naturally
1288
table.setStyle('width', '');
1289
scroller.setStyle('width', '');
1291
scroller.setStyle('width',
1292
(table.get('offsetWidth') + scrollbarWidth) + 'px');
1298
Detaches the scroll event subscriptions used to maintain scroll position
1299
parity between the scrollable `<div>` wrapper around the data table and the
1300
virtual scrollbar for vertically scrolling tables.
1302
@method _unbindScrollbar
1306
_unbindScrollbar: function () {
1307
if (this._scrollbarEventHandle) {
1308
this._scrollbarEventHandle.detach();
1313
Detaches the resize event subscription used to maintain column parity for
1314
vertically scrolling tables with percentage widths.
1316
@method _unbindScrollResize
1320
_unbindScrollResize: function () {
1321
if (this._scrollResizeHandle) {
1322
this._scrollResizeHandle.detach();
1323
delete this._scrollResizeHandle;
1328
Indicates horizontal table scrolling is enabled.
1332
@default undefined (not initially set)
1339
Indicates vertical table scrolling is enabled.
1343
@default undefined (not initially set)
1350
Fixed column header `<table>` Node for vertical scrolling tables.
1352
@property _yScrollHeader
1354
@default undefined (not initially set)
1358
//_yScrollHeader: null,
1361
Overflow Node used to contain the data rows in a vertically scrolling table.
1363
@property _yScrollNode
1365
@default undefined (not initially set)
1369
//_yScrollNode: null,
1372
Overflow Node used to contain the table headers and data in a horizontally
1375
@property _xScrollNode
1377
@default undefined (not initially set)
1381
//_xScrollNode: null
1384
Y.Base.mix(Y.DataTable, [Scrollable]);
1387
}, '3.9.1', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true});