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});