1 |
efrain |
1 |
YUI.add('datatable-scroll', function (Y, NAME) {
|
|
|
2 |
|
|
|
3 |
/**
|
|
|
4 |
Adds the ability to make the table rows scrollable while preserving the header
|
|
|
5 |
placement.
|
|
|
6 |
|
|
|
7 |
@module datatable-scroll
|
|
|
8 |
@for DataTable
|
|
|
9 |
@since 3.5.0
|
|
|
10 |
**/
|
|
|
11 |
var YLang = Y.Lang,
|
|
|
12 |
isString = YLang.isString,
|
|
|
13 |
isNumber = YLang.isNumber,
|
|
|
14 |
isArray = YLang.isArray,
|
|
|
15 |
|
|
|
16 |
Scrollable;
|
|
|
17 |
|
|
|
18 |
// Returns the numeric value portion of the computed style, defaulting to 0
|
|
|
19 |
function styleDim(node, style) {
|
|
|
20 |
return parseInt(node.getComputedStyle(style), 10) || 0;
|
|
|
21 |
}
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
_API docs for this extension are included in the DataTable class._
|
|
|
25 |
|
|
|
26 |
Adds the ability to make the table rows scrollable while preserving the header
|
|
|
27 |
placement.
|
|
|
28 |
|
|
|
29 |
There are two types of scrolling, horizontal (x) and vertical (y). Horizontal
|
|
|
30 |
scrolling is achieved by wrapping the entire table in a scrollable container.
|
|
|
31 |
Vertical scrolling is achieved by splitting the table headers and data into two
|
|
|
32 |
separate tables, the latter of which is wrapped in a vertically scrolling
|
|
|
33 |
container. In this case, column widths of header cells and data cells are kept
|
|
|
34 |
in sync programmatically.
|
|
|
35 |
|
|
|
36 |
Since the split table synchronization can be costly at runtime, the split is only
|
|
|
37 |
done if the data in the table stretches beyond the configured `height` value.
|
|
|
38 |
|
|
|
39 |
To activate or deactivate scrolling, set the `scrollable` attribute to one of
|
|
|
40 |
the following values:
|
|
|
41 |
|
|
|
42 |
* `false` - (default) Scrolling is disabled.
|
|
|
43 |
* `true` or 'xy' - If `height` is set, vertical scrolling will be activated, if
|
|
|
44 |
`width` is set, horizontal scrolling will be activated.
|
|
|
45 |
* 'x' - Activate horizontal scrolling only. Requires the `width` attribute is
|
|
|
46 |
also set.
|
|
|
47 |
* 'y' - Activate vertical scrolling only. Requires the `height` attribute is
|
|
|
48 |
also set.
|
|
|
49 |
|
|
|
50 |
@class DataTable.Scrollable
|
|
|
51 |
@for DataTable
|
|
|
52 |
@since 3.5.0
|
|
|
53 |
**/
|
|
|
54 |
Y.DataTable.Scrollable = Scrollable = function () {};
|
|
|
55 |
|
|
|
56 |
Scrollable.ATTRS = {
|
|
|
57 |
/**
|
|
|
58 |
Activates or deactivates scrolling in the table. Acceptable values are:
|
|
|
59 |
|
|
|
60 |
* `false` - (default) Scrolling is disabled.
|
|
|
61 |
* `true` or 'xy' - If `height` is set, vertical scrolling will be
|
|
|
62 |
activated, if `width` is set, horizontal scrolling will be activated.
|
|
|
63 |
* 'x' - Activate horizontal scrolling only. Requires the `width` attribute
|
|
|
64 |
is also set.
|
|
|
65 |
* 'y' - Activate vertical scrolling only. Requires the `height` attribute
|
|
|
66 |
is also set.
|
|
|
67 |
|
|
|
68 |
@attribute scrollable
|
|
|
69 |
@type {String|Boolean}
|
|
|
70 |
@value false
|
|
|
71 |
@since 3.5.0
|
|
|
72 |
**/
|
|
|
73 |
scrollable: {
|
|
|
74 |
value: false,
|
|
|
75 |
setter: '_setScrollable'
|
|
|
76 |
}
|
|
|
77 |
};
|
|
|
78 |
|
|
|
79 |
Y.mix(Scrollable.prototype, {
|
|
|
80 |
|
|
|
81 |
/**
|
|
|
82 |
Scrolls a given row or cell into view if the table is scrolling. Pass the
|
|
|
83 |
`clientId` of a Model from the DataTable's `data` ModelList or its row
|
|
|
84 |
index to scroll to a row or a [row index, column index] array to scroll to
|
|
|
85 |
a cell. Alternately, to scroll to any element contained within the table's
|
|
|
86 |
scrolling areas, pass its ID, or the Node itself (though you could just as
|
|
|
87 |
well call `node.scrollIntoView()` yourself, but hey, whatever).
|
|
|
88 |
|
|
|
89 |
@method scrollTo
|
|
|
90 |
@param {String|Number|Number[]|Node} id A row clientId, row index, cell
|
|
|
91 |
coordinate array, id string, or Node
|
|
|
92 |
@return {DataTable}
|
|
|
93 |
@chainable
|
|
|
94 |
@since 3.5.0
|
|
|
95 |
**/
|
|
|
96 |
scrollTo: function (id) {
|
|
|
97 |
var target;
|
|
|
98 |
|
|
|
99 |
if (id && this._tbodyNode && (this._yScrollNode || this._xScrollNode)) {
|
|
|
100 |
if (isArray(id)) {
|
|
|
101 |
target = this.getCell(id);
|
|
|
102 |
} else if (isNumber(id)) {
|
|
|
103 |
target = this.getRow(id);
|
|
|
104 |
} else if (isString(id)) {
|
|
|
105 |
target = this._tbodyNode.one('#' + id);
|
|
|
106 |
} else if (id._node &&
|
|
|
107 |
// TODO: ancestor(yScrollNode, xScrollNode)
|
|
|
108 |
id.ancestor('.yui3-datatable') === this.get('boundingBox')) {
|
|
|
109 |
target = id;
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
if(target) {
|
|
|
113 |
target.scrollIntoView();
|
|
|
114 |
}
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
return this;
|
|
|
118 |
},
|
|
|
119 |
|
|
|
120 |
//--------------------------------------------------------------------------
|
|
|
121 |
// Protected properties and methods
|
|
|
122 |
//--------------------------------------------------------------------------
|
|
|
123 |
|
|
|
124 |
/**
|
|
|
125 |
Template for the `<table>` that is used to fix the caption in place when
|
|
|
126 |
the table is horizontally scrolling.
|
|
|
127 |
|
|
|
128 |
@property _CAPTION_TABLE_TEMPLATE
|
|
|
129 |
@type {String}
|
|
|
130 |
@value '<table class="{className}" role="presentation"></table>'
|
|
|
131 |
@protected
|
|
|
132 |
@since 3.5.0
|
|
|
133 |
**/
|
|
|
134 |
_CAPTION_TABLE_TEMPLATE: '<table class="{className}" role="presentation"></table>',
|
|
|
135 |
|
|
|
136 |
/**
|
|
|
137 |
Template used to create sizable element liners around header content to
|
|
|
138 |
synchronize fixed header column widths.
|
|
|
139 |
|
|
|
140 |
@property _SCROLL_LINER_TEMPLATE
|
|
|
141 |
@type {String}
|
|
|
142 |
@value '<div class="{className}"></div>'
|
|
|
143 |
@protected
|
|
|
144 |
@since 3.5.0
|
|
|
145 |
**/
|
|
|
146 |
_SCROLL_LINER_TEMPLATE: '<div class="{className}"></div>',
|
|
|
147 |
|
|
|
148 |
/**
|
|
|
149 |
Template for the virtual scrollbar needed in "y" and "xy" scrolling setups.
|
|
|
150 |
|
|
|
151 |
@property _SCROLLBAR_TEMPLATE
|
|
|
152 |
@type {String}
|
|
|
153 |
@value '<div class="{className}"><div></div></div>'
|
|
|
154 |
@protected
|
|
|
155 |
@since 3.5.0
|
|
|
156 |
**/
|
|
|
157 |
_SCROLLBAR_TEMPLATE: '<div class="{className}"><div></div></div>',
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
Template for the `<div>` that is used to contain the table when the table is
|
|
|
161 |
horizontally scrolling.
|
|
|
162 |
|
|
|
163 |
@property _X_SCROLLER_TEMPLATE
|
|
|
164 |
@type {String}
|
|
|
165 |
@value '<div class="{className}"></div>'
|
|
|
166 |
@protected
|
|
|
167 |
@since 3.5.0
|
|
|
168 |
**/
|
|
|
169 |
_X_SCROLLER_TEMPLATE: '<div class="{className}"></div>',
|
|
|
170 |
|
|
|
171 |
/**
|
|
|
172 |
Template for the `<table>` used to contain the fixed column headers for
|
|
|
173 |
vertically scrolling tables.
|
|
|
174 |
|
|
|
175 |
@property _Y_SCROLL_HEADER_TEMPLATE
|
|
|
176 |
@type {String}
|
|
|
177 |
@value '<table cellspacing="0" role="presentation" aria-hidden="true" class="{className}"></table>'
|
|
|
178 |
@protected
|
|
|
179 |
@since 3.5.0
|
|
|
180 |
**/
|
|
|
181 |
_Y_SCROLL_HEADER_TEMPLATE: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>',
|
|
|
182 |
|
|
|
183 |
/**
|
|
|
184 |
Template for the `<div>` that is used to contain the rows when the table is
|
|
|
185 |
vertically scrolling.
|
|
|
186 |
|
|
|
187 |
@property _Y_SCROLLER_TEMPLATE
|
|
|
188 |
@type {String}
|
|
|
189 |
@value '<div class="{className}"><div class="{scrollerClassName}"></div></div>'
|
|
|
190 |
@protected
|
|
|
191 |
@since 3.5.0
|
|
|
192 |
**/
|
|
|
193 |
_Y_SCROLLER_TEMPLATE: '<div class="{className}"><div class="{scrollerClassName}"></div></div>',
|
|
|
194 |
|
|
|
195 |
/**
|
|
|
196 |
Adds padding to the last cells in the fixed header for vertically scrolling
|
|
|
197 |
tables. This padding is equal in width to the scrollbar, so can't be
|
|
|
198 |
relegated to a stylesheet.
|
|
|
199 |
|
|
|
200 |
@method _addScrollbarPadding
|
|
|
201 |
@protected
|
|
|
202 |
@since 3.5.0
|
|
|
203 |
**/
|
|
|
204 |
_addScrollbarPadding: function () {
|
|
|
205 |
var fixedHeader = this._yScrollHeader,
|
|
|
206 |
headerClass = '.' + this.getClassName('header'),
|
|
|
207 |
scrollbarWidth, rows, header, i, len;
|
|
|
208 |
|
|
|
209 |
if (fixedHeader) {
|
|
|
210 |
scrollbarWidth = Y.DOM.getScrollbarWidth() + 'px';
|
|
|
211 |
rows = fixedHeader.all('tr');
|
|
|
212 |
|
|
|
213 |
for (i = 0, len = rows.size(); i < len; i += +header.get('rowSpan')) {
|
|
|
214 |
header = rows.item(i).all(headerClass).pop();
|
|
|
215 |
header.setStyle('paddingRight', scrollbarWidth);
|
|
|
216 |
}
|
|
|
217 |
}
|
|
|
218 |
},
|
|
|
219 |
|
|
|
220 |
/**
|
|
|
221 |
Reacts to changes in the `scrollable` attribute by updating the `_xScroll`
|
|
|
222 |
and `_yScroll` properties and syncing the scrolling structure accordingly.
|
|
|
223 |
|
|
|
224 |
@method _afterScrollableChange
|
|
|
225 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
226 |
@protected
|
|
|
227 |
@since 3.5.0
|
|
|
228 |
**/
|
|
|
229 |
_afterScrollableChange: function () {
|
|
|
230 |
var scroller = this._xScrollNode;
|
|
|
231 |
|
|
|
232 |
if (this._xScroll && scroller) {
|
|
|
233 |
if (this._yScroll && !this._yScrollNode) {
|
|
|
234 |
scroller.setStyle('paddingRight',
|
|
|
235 |
Y.DOM.getScrollbarWidth() + 'px');
|
|
|
236 |
} else if (!this._yScroll && this._yScrollNode) {
|
|
|
237 |
scroller.setStyle('paddingRight', '');
|
|
|
238 |
}
|
|
|
239 |
}
|
|
|
240 |
|
|
|
241 |
this._syncScrollUI();
|
|
|
242 |
},
|
|
|
243 |
|
|
|
244 |
/**
|
|
|
245 |
Reacts to changes in the `caption` attribute by adding, removing, or
|
|
|
246 |
syncing the caption table when the table is set to scroll.
|
|
|
247 |
|
|
|
248 |
@method _afterScrollCaptionChange
|
|
|
249 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
250 |
@protected
|
|
|
251 |
@since 3.5.0
|
|
|
252 |
**/
|
|
|
253 |
_afterScrollCaptionChange: function () {
|
|
|
254 |
if (this._xScroll || this._yScroll) {
|
|
|
255 |
this._syncScrollUI();
|
|
|
256 |
}
|
|
|
257 |
},
|
|
|
258 |
|
|
|
259 |
/**
|
|
|
260 |
Reacts to changes in the `columns` attribute of vertically scrolling tables
|
|
|
261 |
by refreshing the fixed headers, scroll container, and virtual scrollbar
|
|
|
262 |
position.
|
|
|
263 |
|
|
|
264 |
@method _afterScrollColumnsChange
|
|
|
265 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
266 |
@protected
|
|
|
267 |
@since 3.5.0
|
|
|
268 |
**/
|
|
|
269 |
_afterScrollColumnsChange: function () {
|
|
|
270 |
if (this._xScroll || this._yScroll) {
|
|
|
271 |
if (this._yScroll && this._yScrollHeader) {
|
|
|
272 |
this._syncScrollHeaders();
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
this._syncScrollUI();
|
|
|
276 |
}
|
|
|
277 |
},
|
|
|
278 |
|
|
|
279 |
/**
|
|
|
280 |
Reacts to changes in vertically scrolling table's `data` ModelList by
|
|
|
281 |
synchronizing the fixed column header widths and virtual scrollbar height.
|
|
|
282 |
|
|
|
283 |
@method _afterScrollDataChange
|
|
|
284 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
285 |
@protected
|
|
|
286 |
@since 3.5.0
|
|
|
287 |
**/
|
|
|
288 |
_afterScrollDataChange: function () {
|
|
|
289 |
if (this._xScroll || this._yScroll) {
|
|
|
290 |
this._syncScrollUI();
|
|
|
291 |
}
|
|
|
292 |
},
|
|
|
293 |
|
|
|
294 |
/**
|
|
|
295 |
Reacts to changes in the `height` attribute of vertically scrolling tables
|
|
|
296 |
by updating the height of the `<div>` wrapping the data table and the
|
|
|
297 |
virtual scrollbar. If `scrollable` was set to "y" or "xy" but lacking a
|
|
|
298 |
declared `height` until the received change, `_syncScrollUI` is called to
|
|
|
299 |
create the fixed headers etc.
|
|
|
300 |
|
|
|
301 |
@method _afterScrollHeightChange
|
|
|
302 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
303 |
@protected
|
|
|
304 |
@since 3.5.0
|
|
|
305 |
**/
|
|
|
306 |
_afterScrollHeightChange: function () {
|
|
|
307 |
if (this._yScroll) {
|
|
|
308 |
this._syncScrollUI();
|
|
|
309 |
}
|
|
|
310 |
},
|
|
|
311 |
|
|
|
312 |
/* (not an API doc comment on purpose)
|
|
|
313 |
Reacts to the sort event (if the table is also sortable) by updating the
|
|
|
314 |
fixed header classes to match the data table's headers.
|
|
|
315 |
|
|
|
316 |
THIS IS A HACK that will be removed immediately after the 3.5.0 release.
|
|
|
317 |
If you're reading this and the current version is greater than 3.5.0, I
|
|
|
318 |
should be publicly scolded.
|
|
|
319 |
*/
|
|
|
320 |
_afterScrollSort: function () {
|
|
|
321 |
var headers, headerClass;
|
|
|
322 |
|
|
|
323 |
if (this._yScroll && this._yScrollHeader) {
|
|
|
324 |
headerClass = '.' + this.getClassName('header');
|
|
|
325 |
headers = this._theadNode.all(headerClass);
|
|
|
326 |
|
|
|
327 |
this._yScrollHeader.all(headerClass).each(function (header, i) {
|
|
|
328 |
header.set('className', headers.item(i).get('className'));
|
|
|
329 |
});
|
|
|
330 |
}
|
|
|
331 |
},
|
|
|
332 |
|
|
|
333 |
/**
|
|
|
334 |
Reacts to changes in the width of scrolling tables by expanding the width of
|
|
|
335 |
the `<div>` wrapping the data table for horizontally scrolling tables or
|
|
|
336 |
upding the position of the virtual scrollbar for vertically scrolling
|
|
|
337 |
tables.
|
|
|
338 |
|
|
|
339 |
@method _afterScrollWidthChange
|
|
|
340 |
@param {EventFacade} e The relevant change event (ignored)
|
|
|
341 |
@protected
|
|
|
342 |
@since 3.5.0
|
|
|
343 |
**/
|
|
|
344 |
_afterScrollWidthChange: function () {
|
|
|
345 |
if (this._xScroll || this._yScroll) {
|
|
|
346 |
this._syncScrollUI();
|
|
|
347 |
}
|
|
|
348 |
},
|
|
|
349 |
|
|
|
350 |
/**
|
|
|
351 |
Binds virtual scrollbar interaction to the `_yScrollNode`'s `scrollTop` and
|
|
|
352 |
vice versa.
|
|
|
353 |
|
|
|
354 |
@method _bindScrollbar
|
|
|
355 |
@protected
|
|
|
356 |
@since 3.5.0
|
|
|
357 |
**/
|
|
|
358 |
_bindScrollbar: function () {
|
|
|
359 |
var scrollbar = this._scrollbarNode,
|
|
|
360 |
scroller = this._yScrollNode;
|
|
|
361 |
|
|
|
362 |
if (scrollbar && scroller && !this._scrollbarEventHandle) {
|
|
|
363 |
this._scrollbarEventHandle = new Y.Event.Handle([
|
|
|
364 |
scrollbar.on('scroll', this._syncScrollPosition, this),
|
|
|
365 |
scroller.on('scroll', this._syncScrollPosition, this)
|
|
|
366 |
]);
|
|
|
367 |
}
|
|
|
368 |
},
|
|
|
369 |
|
|
|
370 |
/**
|
|
|
371 |
Binds to the window resize event to update the vertical scrolling table
|
|
|
372 |
headers and wrapper `<div>` dimensions.
|
|
|
373 |
|
|
|
374 |
@method _bindScrollResize
|
|
|
375 |
@protected
|
|
|
376 |
@since 3.5.0
|
|
|
377 |
**/
|
|
|
378 |
_bindScrollResize: function () {
|
|
|
379 |
if (!this._scrollResizeHandle) {
|
|
|
380 |
// TODO: sync header widths and scrollbar position. If the height
|
|
|
381 |
// of the headers has changed, update the scrollbar dims as well.
|
|
|
382 |
this._scrollResizeHandle = Y.on('resize',
|
|
|
383 |
this._syncScrollUI, null, this);
|
|
|
384 |
}
|
|
|
385 |
},
|
|
|
386 |
|
|
|
387 |
/**
|
|
|
388 |
Attaches internal subscriptions to keep the scrolling structure up to date
|
|
|
389 |
with changes in the table's `data`, `columns`, `caption`, or `height`. The
|
|
|
390 |
`width` is taken care of already.
|
|
|
391 |
|
|
|
392 |
This executes after the table's native `bindUI` method.
|
|
|
393 |
|
|
|
394 |
@method _bindScrollUI
|
|
|
395 |
@protected
|
|
|
396 |
@since 3.5.0
|
|
|
397 |
**/
|
|
|
398 |
_bindScrollUI: function () {
|
|
|
399 |
this.after({
|
|
|
400 |
columnsChange: Y.bind('_afterScrollColumnsChange', this),
|
|
|
401 |
heightChange : Y.bind('_afterScrollHeightChange', this),
|
|
|
402 |
widthChange : Y.bind('_afterScrollWidthChange', this),
|
|
|
403 |
captionChange: Y.bind('_afterScrollCaptionChange', this),
|
|
|
404 |
scrollableChange: Y.bind('_afterScrollableChange', this),
|
|
|
405 |
// FIXME: this is a last minute hack to work around the fact that
|
|
|
406 |
// DT doesn't use a tableView to render table content that can be
|
|
|
407 |
// replaced with a scrolling table view. This must be removed asap!
|
|
|
408 |
sort : Y.bind('_afterScrollSort', this)
|
|
|
409 |
});
|
|
|
410 |
|
|
|
411 |
this.after(['dataChange', '*:add', '*:remove', '*:reset', '*:change'],
|
|
|
412 |
Y.bind('_afterScrollDataChange', this));
|
|
|
413 |
},
|
|
|
414 |
|
|
|
415 |
/**
|
|
|
416 |
Clears the lock and timer used to manage synchronizing the scroll position
|
|
|
417 |
between the vertical scroll container and the virtual scrollbar.
|
|
|
418 |
|
|
|
419 |
@method _clearScrollLock
|
|
|
420 |
@protected
|
|
|
421 |
@since 3.5.0
|
|
|
422 |
**/
|
|
|
423 |
_clearScrollLock: function () {
|
|
|
424 |
if (this._scrollLock) {
|
|
|
425 |
this._scrollLock.cancel();
|
|
|
426 |
delete this._scrollLock;
|
|
|
427 |
}
|
|
|
428 |
},
|
|
|
429 |
|
|
|
430 |
/**
|
|
|
431 |
Creates a virtual scrollbar from the `_SCROLLBAR_TEMPLATE`, assigning it to
|
|
|
432 |
the `_scrollbarNode` property.
|
|
|
433 |
|
|
|
434 |
@method _createScrollbar
|
|
|
435 |
@return {Node} The created Node
|
|
|
436 |
@protected
|
|
|
437 |
@since 3.5.0
|
|
|
438 |
**/
|
|
|
439 |
_createScrollbar: function () {
|
|
|
440 |
var scrollbar = this._scrollbarNode;
|
|
|
441 |
|
|
|
442 |
if (!scrollbar) {
|
|
|
443 |
scrollbar = this._scrollbarNode = Y.Node.create(
|
|
|
444 |
Y.Lang.sub(this._SCROLLBAR_TEMPLATE, {
|
|
|
445 |
className: this.getClassName('scrollbar')
|
|
|
446 |
}));
|
|
|
447 |
|
|
|
448 |
// IE 6-10 require the scrolled area to be visible (at least 1px)
|
|
|
449 |
// or they don't respond to clicking on the scrollbar rail or arrows
|
|
|
450 |
scrollbar.setStyle('width', (Y.DOM.getScrollbarWidth() + 1) + 'px');
|
|
|
451 |
}
|
|
|
452 |
|
|
|
453 |
return scrollbar;
|
|
|
454 |
},
|
|
|
455 |
|
|
|
456 |
/**
|
|
|
457 |
Creates a separate table to contain the caption when the table is
|
|
|
458 |
configured to scroll vertically or horizontally.
|
|
|
459 |
|
|
|
460 |
@method _createScrollCaptionTable
|
|
|
461 |
@return {Node} The created Node
|
|
|
462 |
@protected
|
|
|
463 |
@since 3.5.0
|
|
|
464 |
**/
|
|
|
465 |
_createScrollCaptionTable: function () {
|
|
|
466 |
if (!this._captionTable) {
|
|
|
467 |
this._captionTable = Y.Node.create(
|
|
|
468 |
Y.Lang.sub(this._CAPTION_TABLE_TEMPLATE, {
|
|
|
469 |
className: this.getClassName('caption', 'table')
|
|
|
470 |
}));
|
|
|
471 |
|
|
|
472 |
this._captionTable.empty();
|
|
|
473 |
}
|
|
|
474 |
|
|
|
475 |
return this._captionTable;
|
|
|
476 |
},
|
|
|
477 |
|
|
|
478 |
/**
|
|
|
479 |
Populates the `_xScrollNode` property by creating the `<div>` Node described
|
|
|
480 |
by the `_X_SCROLLER_TEMPLATE`.
|
|
|
481 |
|
|
|
482 |
@method _createXScrollNode
|
|
|
483 |
@return {Node} The created Node
|
|
|
484 |
@protected
|
|
|
485 |
@since 3.5.0
|
|
|
486 |
**/
|
|
|
487 |
_createXScrollNode: function () {
|
|
|
488 |
if (!this._xScrollNode) {
|
|
|
489 |
this._xScrollNode = Y.Node.create(
|
|
|
490 |
Y.Lang.sub(this._X_SCROLLER_TEMPLATE, {
|
|
|
491 |
className: this.getClassName('x','scroller')
|
|
|
492 |
}));
|
|
|
493 |
}
|
|
|
494 |
|
|
|
495 |
return this._xScrollNode;
|
|
|
496 |
},
|
|
|
497 |
|
|
|
498 |
/**
|
|
|
499 |
Populates the `_yScrollHeader` property by creating the `<table>` Node
|
|
|
500 |
described by the `_Y_SCROLL_HEADER_TEMPLATE`.
|
|
|
501 |
|
|
|
502 |
@method _createYScrollHeader
|
|
|
503 |
@return {Node} The created Node
|
|
|
504 |
@protected
|
|
|
505 |
@since 3.5.0
|
|
|
506 |
**/
|
|
|
507 |
_createYScrollHeader: function () {
|
|
|
508 |
var fixedHeader = this._yScrollHeader;
|
|
|
509 |
|
|
|
510 |
if (!fixedHeader) {
|
|
|
511 |
fixedHeader = this._yScrollHeader = Y.Node.create(
|
|
|
512 |
Y.Lang.sub(this._Y_SCROLL_HEADER_TEMPLATE, {
|
|
|
513 |
className: this.getClassName('scroll','columns')
|
|
|
514 |
}));
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
return fixedHeader;
|
|
|
518 |
},
|
|
|
519 |
|
|
|
520 |
/**
|
|
|
521 |
Populates the `_yScrollNode` property by creating the `<div>` Node described
|
|
|
522 |
by the `_Y_SCROLLER_TEMPLATE`.
|
|
|
523 |
|
|
|
524 |
@method _createYScrollNode
|
|
|
525 |
@return {Node} The created Node
|
|
|
526 |
@protected
|
|
|
527 |
@since 3.5.0
|
|
|
528 |
**/
|
|
|
529 |
_createYScrollNode: function () {
|
|
|
530 |
var scrollerClass;
|
|
|
531 |
|
|
|
532 |
if (!this._yScrollNode) {
|
|
|
533 |
scrollerClass = this.getClassName('y', 'scroller');
|
|
|
534 |
|
|
|
535 |
this._yScrollContainer = Y.Node.create(
|
|
|
536 |
Y.Lang.sub(this._Y_SCROLLER_TEMPLATE, {
|
|
|
537 |
className: this.getClassName('y','scroller','container'),
|
|
|
538 |
scrollerClassName: scrollerClass
|
|
|
539 |
}));
|
|
|
540 |
|
|
|
541 |
this._yScrollNode = this._yScrollContainer
|
|
|
542 |
.one('.' + scrollerClass);
|
|
|
543 |
}
|
|
|
544 |
|
|
|
545 |
return this._yScrollContainer;
|
|
|
546 |
},
|
|
|
547 |
|
|
|
548 |
/**
|
|
|
549 |
Removes the nodes used to create horizontal and vertical scrolling and
|
|
|
550 |
rejoins the caption to the main table if needed.
|
|
|
551 |
|
|
|
552 |
@method _disableScrolling
|
|
|
553 |
@protected
|
|
|
554 |
@since 3.5.0
|
|
|
555 |
**/
|
|
|
556 |
_disableScrolling: function () {
|
|
|
557 |
this._removeScrollCaptionTable();
|
|
|
558 |
this._disableXScrolling();
|
|
|
559 |
this._disableYScrolling();
|
|
|
560 |
this._unbindScrollResize();
|
|
|
561 |
|
|
|
562 |
this._uiSetWidth(this.get('width'));
|
|
|
563 |
},
|
|
|
564 |
|
|
|
565 |
/**
|
|
|
566 |
Removes the nodes used to allow horizontal scrolling.
|
|
|
567 |
|
|
|
568 |
@method _disableXScrolling
|
|
|
569 |
@protected
|
|
|
570 |
@since 3.5.0
|
|
|
571 |
**/
|
|
|
572 |
_disableXScrolling: function () {
|
|
|
573 |
this._removeXScrollNode();
|
|
|
574 |
},
|
|
|
575 |
|
|
|
576 |
/**
|
|
|
577 |
Removes the nodes used to allow vertical scrolling.
|
|
|
578 |
|
|
|
579 |
@method _disableYScrolling
|
|
|
580 |
@protected
|
|
|
581 |
@since 3.5.0
|
|
|
582 |
**/
|
|
|
583 |
_disableYScrolling: function () {
|
|
|
584 |
this._removeYScrollHeader();
|
|
|
585 |
this._removeYScrollNode();
|
|
|
586 |
this._removeYScrollContainer();
|
|
|
587 |
this._removeScrollbar();
|
|
|
588 |
},
|
|
|
589 |
|
|
|
590 |
/**
|
|
|
591 |
Cleans up external event subscriptions.
|
|
|
592 |
|
|
|
593 |
@method destructor
|
|
|
594 |
@protected
|
|
|
595 |
@since 3.5.0
|
|
|
596 |
**/
|
|
|
597 |
destructor: function () {
|
|
|
598 |
this._unbindScrollbar();
|
|
|
599 |
this._unbindScrollResize();
|
|
|
600 |
this._clearScrollLock();
|
|
|
601 |
},
|
|
|
602 |
|
|
|
603 |
/**
|
|
|
604 |
Sets up event handlers and AOP advice methods to bind the DataTable's natural
|
|
|
605 |
behaviors with the scrolling APIs and state.
|
|
|
606 |
|
|
|
607 |
@method initializer
|
|
|
608 |
@param {Object} config The config object passed to the constructor (ignored)
|
|
|
609 |
@protected
|
|
|
610 |
@since 3.5.0
|
|
|
611 |
**/
|
|
|
612 |
initializer: function () {
|
|
|
613 |
this._setScrollProperties();
|
|
|
614 |
|
|
|
615 |
this.after(['scrollableChange', 'heightChange', 'widthChange'],
|
|
|
616 |
this._setScrollProperties);
|
|
|
617 |
|
|
|
618 |
this.after('renderView', Y.bind('_syncScrollUI', this));
|
|
|
619 |
|
|
|
620 |
Y.Do.after(this._bindScrollUI, this, 'bindUI');
|
|
|
621 |
},
|
|
|
622 |
|
|
|
623 |
/**
|
|
|
624 |
Removes the table used to house the caption when the table is scrolling.
|
|
|
625 |
|
|
|
626 |
@method _removeScrollCaptionTable
|
|
|
627 |
@protected
|
|
|
628 |
@since 3.5.0
|
|
|
629 |
**/
|
|
|
630 |
_removeScrollCaptionTable: function () {
|
|
|
631 |
if (this._captionTable) {
|
|
|
632 |
if (this._captionNode) {
|
|
|
633 |
this._tableNode.prepend(this._captionNode);
|
|
|
634 |
}
|
|
|
635 |
|
|
|
636 |
this._captionTable.remove().destroy(true);
|
|
|
637 |
|
|
|
638 |
delete this._captionTable;
|
|
|
639 |
}
|
|
|
640 |
},
|
|
|
641 |
|
|
|
642 |
/**
|
|
|
643 |
Removes the `<div>` wrapper used to contain the data table when the table
|
|
|
644 |
is horizontally scrolling.
|
|
|
645 |
|
|
|
646 |
@method _removeXScrollNode
|
|
|
647 |
@protected
|
|
|
648 |
@since 3.5.0
|
|
|
649 |
**/
|
|
|
650 |
_removeXScrollNode: function () {
|
|
|
651 |
var scroller = this._xScrollNode;
|
|
|
652 |
|
|
|
653 |
if (scroller) {
|
|
|
654 |
scroller.replace(scroller.get('childNodes').toFrag());
|
|
|
655 |
scroller.remove().destroy(true);
|
|
|
656 |
|
|
|
657 |
delete this._xScrollNode;
|
|
|
658 |
}
|
|
|
659 |
},
|
|
|
660 |
|
|
|
661 |
/**
|
|
|
662 |
Removes the `<div>` wrapper used to contain the data table and fixed header
|
|
|
663 |
when the table is vertically scrolling.
|
|
|
664 |
|
|
|
665 |
@method _removeYScrollContainer
|
|
|
666 |
@protected
|
|
|
667 |
@since 3.5.0
|
|
|
668 |
**/
|
|
|
669 |
_removeYScrollContainer: function () {
|
|
|
670 |
var scroller = this._yScrollContainer;
|
|
|
671 |
|
|
|
672 |
if (scroller) {
|
|
|
673 |
scroller.replace(scroller.get('childNodes').toFrag());
|
|
|
674 |
scroller.remove().destroy(true);
|
|
|
675 |
|
|
|
676 |
delete this._yScrollContainer;
|
|
|
677 |
}
|
|
|
678 |
},
|
|
|
679 |
|
|
|
680 |
/**
|
|
|
681 |
Removes the `<table>` used to contain the fixed column headers when the
|
|
|
682 |
table is vertically scrolling.
|
|
|
683 |
|
|
|
684 |
@method _removeYScrollHeader
|
|
|
685 |
@protected
|
|
|
686 |
@since 3.5.0
|
|
|
687 |
**/
|
|
|
688 |
_removeYScrollHeader: function () {
|
|
|
689 |
if (this._yScrollHeader) {
|
|
|
690 |
this._yScrollHeader.remove().destroy(true);
|
|
|
691 |
|
|
|
692 |
delete this._yScrollHeader;
|
|
|
693 |
}
|
|
|
694 |
},
|
|
|
695 |
|
|
|
696 |
/**
|
|
|
697 |
Removes the `<div>` wrapper used to contain the data table when the table
|
|
|
698 |
is vertically scrolling.
|
|
|
699 |
|
|
|
700 |
@method _removeYScrollNode
|
|
|
701 |
@protected
|
|
|
702 |
@since 3.5.0
|
|
|
703 |
**/
|
|
|
704 |
_removeYScrollNode: function () {
|
|
|
705 |
var scroller = this._yScrollNode;
|
|
|
706 |
|
|
|
707 |
if (scroller) {
|
|
|
708 |
scroller.replace(scroller.get('childNodes').toFrag());
|
|
|
709 |
scroller.remove().destroy(true);
|
|
|
710 |
|
|
|
711 |
delete this._yScrollNode;
|
|
|
712 |
}
|
|
|
713 |
},
|
|
|
714 |
|
|
|
715 |
/**
|
|
|
716 |
Removes the virtual scrollbar used by scrolling tables.
|
|
|
717 |
|
|
|
718 |
@method _removeScrollbar
|
|
|
719 |
@protected
|
|
|
720 |
@since 3.5.0
|
|
|
721 |
**/
|
|
|
722 |
_removeScrollbar: function () {
|
|
|
723 |
if (this._scrollbarNode) {
|
|
|
724 |
this._scrollbarNode.remove().destroy(true);
|
|
|
725 |
|
|
|
726 |
delete this._scrollbarNode;
|
|
|
727 |
}
|
|
|
728 |
if (this._scrollbarEventHandle) {
|
|
|
729 |
this._scrollbarEventHandle.detach();
|
|
|
730 |
|
|
|
731 |
delete this._scrollbarEventHandle;
|
|
|
732 |
}
|
|
|
733 |
},
|
|
|
734 |
|
|
|
735 |
/**
|
|
|
736 |
Accepts (case insensitive) values "x", "y", "xy", `true`, and `false`.
|
|
|
737 |
`true` is translated to "xy" and upper case values are converted to lower
|
|
|
738 |
case. All other values are invalid.
|
|
|
739 |
|
|
|
740 |
@method _setScrollable
|
|
|
741 |
@param {String|Boolean} val Incoming value for the `scrollable` attribute
|
|
|
742 |
@return {String}
|
|
|
743 |
@protected
|
|
|
744 |
@since 3.5.0
|
|
|
745 |
**/
|
|
|
746 |
_setScrollable: function (val) {
|
|
|
747 |
if (val === true) {
|
|
|
748 |
val = 'xy';
|
|
|
749 |
}
|
|
|
750 |
|
|
|
751 |
if (isString(val)) {
|
|
|
752 |
val = val.toLowerCase();
|
|
|
753 |
}
|
|
|
754 |
|
|
|
755 |
return (val === false || val === 'y' || val === 'x' || val === 'xy') ?
|
|
|
756 |
val :
|
|
|
757 |
Y.Attribute.INVALID_VALUE;
|
|
|
758 |
},
|
|
|
759 |
|
|
|
760 |
/**
|
|
|
761 |
Assigns the `_xScroll` and `_yScroll` properties to true if an
|
|
|
762 |
appropriate value is set in the `scrollable` attribute and the `height`
|
|
|
763 |
and/or `width` is set.
|
|
|
764 |
|
|
|
765 |
@method _setScrollProperties
|
|
|
766 |
@protected
|
|
|
767 |
@since 3.5.0
|
|
|
768 |
**/
|
|
|
769 |
_setScrollProperties: function () {
|
|
|
770 |
var scrollable = this.get('scrollable') || '',
|
|
|
771 |
width = this.get('width'),
|
|
|
772 |
height = this.get('height');
|
|
|
773 |
|
|
|
774 |
this._xScroll = width && scrollable.indexOf('x') > -1;
|
|
|
775 |
this._yScroll = height && scrollable.indexOf('y') > -1;
|
|
|
776 |
},
|
|
|
777 |
|
|
|
778 |
/**
|
|
|
779 |
Keeps the virtual scrollbar and the scrolling `<div>` wrapper around the
|
|
|
780 |
data table in vertically scrolling tables in sync.
|
|
|
781 |
|
|
|
782 |
@method _syncScrollPosition
|
|
|
783 |
@param {DOMEventFacade} e The scroll event
|
|
|
784 |
@protected
|
|
|
785 |
@since 3.5.0
|
|
|
786 |
**/
|
|
|
787 |
_syncScrollPosition: function (e) {
|
|
|
788 |
var scrollbar = this._scrollbarNode,
|
|
|
789 |
scroller = this._yScrollNode,
|
|
|
790 |
source = e.currentTarget,
|
|
|
791 |
other;
|
|
|
792 |
|
|
|
793 |
if (scrollbar && scroller) {
|
|
|
794 |
if (this._scrollLock && this._scrollLock.source !== source) {
|
|
|
795 |
return;
|
|
|
796 |
}
|
|
|
797 |
|
|
|
798 |
this._clearScrollLock();
|
|
|
799 |
this._scrollLock = Y.later(300, this, this._clearScrollLock);
|
|
|
800 |
this._scrollLock.source = source;
|
|
|
801 |
|
|
|
802 |
other = (source === scrollbar) ? scroller : scrollbar;
|
|
|
803 |
other.set('scrollTop', source.get('scrollTop'));
|
|
|
804 |
}
|
|
|
805 |
},
|
|
|
806 |
|
|
|
807 |
/**
|
|
|
808 |
Splits the caption from the data `<table>` if the table is configured to
|
|
|
809 |
scroll. If not, rejoins the caption to the data `<table>` if it needs to
|
|
|
810 |
be.
|
|
|
811 |
|
|
|
812 |
@method _syncScrollCaptionUI
|
|
|
813 |
@protected
|
|
|
814 |
@since 3.5.0
|
|
|
815 |
**/
|
|
|
816 |
_syncScrollCaptionUI: function () {
|
|
|
817 |
var caption = this._captionNode,
|
|
|
818 |
table = this._tableNode,
|
|
|
819 |
captionTable = this._captionTable,
|
|
|
820 |
id;
|
|
|
821 |
|
|
|
822 |
if (caption) {
|
|
|
823 |
id = caption.getAttribute('id');
|
|
|
824 |
|
|
|
825 |
if (!captionTable) {
|
|
|
826 |
captionTable = this._createScrollCaptionTable();
|
|
|
827 |
|
|
|
828 |
this.get('contentBox').prepend(captionTable);
|
|
|
829 |
}
|
|
|
830 |
|
|
|
831 |
if (!caption.get('parentNode').compareTo(captionTable)) {
|
|
|
832 |
captionTable.empty().insert(caption);
|
|
|
833 |
|
|
|
834 |
if (!id) {
|
|
|
835 |
id = Y.stamp(caption);
|
|
|
836 |
caption.setAttribute('id', id);
|
|
|
837 |
}
|
|
|
838 |
|
|
|
839 |
table.setAttribute('aria-describedby', id);
|
|
|
840 |
}
|
|
|
841 |
} else if (captionTable) {
|
|
|
842 |
this._removeScrollCaptionTable();
|
|
|
843 |
}
|
|
|
844 |
},
|
|
|
845 |
|
|
|
846 |
/**
|
|
|
847 |
Assigns widths to the fixed header columns to match the columns in the data
|
|
|
848 |
table.
|
|
|
849 |
|
|
|
850 |
@method _syncScrollColumnWidths
|
|
|
851 |
@protected
|
|
|
852 |
@since 3.5.0
|
|
|
853 |
**/
|
|
|
854 |
_syncScrollColumnWidths: function () {
|
|
|
855 |
var widths = [];
|
|
|
856 |
|
|
|
857 |
if (this._theadNode && this._yScrollHeader) {
|
|
|
858 |
// Capture dims and assign widths in two passes to avoid reflows for
|
|
|
859 |
// each access of clientWidth/getComputedStyle
|
|
|
860 |
this._theadNode.all('.' + this.getClassName('header'))
|
|
|
861 |
.each(function (header) {
|
|
|
862 |
widths.push(
|
|
|
863 |
// FIXME: IE returns the col.style.width from
|
|
|
864 |
// getComputedStyle even if the column has been
|
|
|
865 |
// compressed below that width, so it must use
|
|
|
866 |
// clientWidth. FF requires getComputedStyle because it
|
|
|
867 |
// uses fractional widths that round up to an overall
|
|
|
868 |
// cell/table width 1px greater than the data table's
|
|
|
869 |
// cell/table width, resulting in misaligned columns or
|
|
|
870 |
// fixed header bleed through. I can't think of a
|
|
|
871 |
// *reasonable* way to capture the correct width without
|
|
|
872 |
// a sniff. Math.min(cW - p, getCS(w)) was imperfect
|
|
|
873 |
// and punished all browsers, anyway.
|
|
|
874 |
(Y.UA.ie && Y.UA.ie < 8) ?
|
|
|
875 |
(header.get('clientWidth') -
|
|
|
876 |
styleDim(header, 'paddingLeft') -
|
|
|
877 |
styleDim(header, 'paddingRight')) + 'px' :
|
|
|
878 |
header.getComputedStyle('width'));
|
|
|
879 |
});
|
|
|
880 |
|
|
|
881 |
this._yScrollHeader.all('.' + this.getClassName('scroll', 'liner'))
|
|
|
882 |
.each(function (liner, i) {
|
|
|
883 |
liner.setStyle('width', widths[i]);
|
|
|
884 |
});
|
|
|
885 |
}
|
|
|
886 |
},
|
|
|
887 |
|
|
|
888 |
/**
|
|
|
889 |
Creates matching headers in the fixed header table for vertically scrolling
|
|
|
890 |
tables and synchronizes the column widths.
|
|
|
891 |
|
|
|
892 |
@method _syncScrollHeaders
|
|
|
893 |
@protected
|
|
|
894 |
@since 3.5.0
|
|
|
895 |
**/
|
|
|
896 |
_syncScrollHeaders: function () {
|
|
|
897 |
var fixedHeader = this._yScrollHeader,
|
|
|
898 |
linerTemplate = this._SCROLL_LINER_TEMPLATE,
|
|
|
899 |
linerClass = this.getClassName('scroll', 'liner'),
|
|
|
900 |
headerClass = this.getClassName('header'),
|
|
|
901 |
headers = this._theadNode.all('.' + headerClass);
|
|
|
902 |
|
|
|
903 |
if (this._theadNode && fixedHeader) {
|
|
|
904 |
fixedHeader.empty().appendChild(
|
|
|
905 |
this._theadNode.cloneNode(true));
|
|
|
906 |
|
|
|
907 |
// Prevent duplicate IDs and assign ARIA attributes to hide
|
|
|
908 |
// from screen readers
|
|
|
909 |
fixedHeader.all('[id]').removeAttribute('id');
|
|
|
910 |
|
|
|
911 |
fixedHeader.all('.' + headerClass).each(function (header, i) {
|
|
|
912 |
var liner = Y.Node.create(Y.Lang.sub(linerTemplate, {
|
|
|
913 |
className: linerClass
|
|
|
914 |
})),
|
|
|
915 |
refHeader = headers.item(i);
|
|
|
916 |
|
|
|
917 |
// Can't assign via skin css because sort (and potentially
|
|
|
918 |
// others) might override the padding values.
|
|
|
919 |
liner.setStyle('padding',
|
|
|
920 |
refHeader.getComputedStyle('paddingTop') + ' ' +
|
|
|
921 |
refHeader.getComputedStyle('paddingRight') + ' ' +
|
|
|
922 |
refHeader.getComputedStyle('paddingBottom') + ' ' +
|
|
|
923 |
refHeader.getComputedStyle('paddingLeft'));
|
|
|
924 |
|
|
|
925 |
liner.appendChild(header.get('childNodes').toFrag());
|
|
|
926 |
|
|
|
927 |
header.appendChild(liner);
|
|
|
928 |
}, this);
|
|
|
929 |
|
|
|
930 |
this._syncScrollColumnWidths();
|
|
|
931 |
|
|
|
932 |
this._addScrollbarPadding();
|
|
|
933 |
}
|
|
|
934 |
},
|
|
|
935 |
|
|
|
936 |
/**
|
|
|
937 |
Wraps the table for X and Y scrolling, if necessary, if the `scrollable`
|
|
|
938 |
attribute is set. Synchronizes dimensions and DOM placement of all
|
|
|
939 |
scrolling related nodes.
|
|
|
940 |
|
|
|
941 |
@method _syncScrollUI
|
|
|
942 |
@protected
|
|
|
943 |
@since 3.5.0
|
|
|
944 |
**/
|
|
|
945 |
_syncScrollUI: function () {
|
|
|
946 |
var x = this._xScroll,
|
|
|
947 |
y = this._yScroll,
|
|
|
948 |
xScroller = this._xScrollNode,
|
|
|
949 |
yScroller = this._yScrollNode,
|
|
|
950 |
scrollLeft = xScroller && xScroller.get('scrollLeft'),
|
|
|
951 |
scrollTop = yScroller && yScroller.get('scrollTop');
|
|
|
952 |
|
|
|
953 |
this._uiSetScrollable();
|
|
|
954 |
|
|
|
955 |
// TODO: Probably should split this up into syncX, syncY, and syncXY
|
|
|
956 |
if (x || y) {
|
|
|
957 |
if ((this.get('width') || '').slice(-1) === '%') {
|
|
|
958 |
this._bindScrollResize();
|
|
|
959 |
} else {
|
|
|
960 |
this._unbindScrollResize();
|
|
|
961 |
}
|
|
|
962 |
|
|
|
963 |
this._syncScrollCaptionUI();
|
|
|
964 |
} else {
|
|
|
965 |
this._disableScrolling();
|
|
|
966 |
}
|
|
|
967 |
|
|
|
968 |
if (this._yScrollHeader) {
|
|
|
969 |
this._yScrollHeader.setStyle('display', 'none');
|
|
|
970 |
}
|
|
|
971 |
|
|
|
972 |
if (x) {
|
|
|
973 |
if (!y) {
|
|
|
974 |
this._disableYScrolling();
|
|
|
975 |
}
|
|
|
976 |
|
|
|
977 |
this._syncXScrollUI(y);
|
|
|
978 |
}
|
|
|
979 |
|
|
|
980 |
if (y) {
|
|
|
981 |
if (!x) {
|
|
|
982 |
this._disableXScrolling();
|
|
|
983 |
}
|
|
|
984 |
|
|
|
985 |
this._syncYScrollUI(x);
|
|
|
986 |
}
|
|
|
987 |
|
|
|
988 |
// Restore scroll position
|
|
|
989 |
if (scrollLeft && this._xScrollNode) {
|
|
|
990 |
this._xScrollNode.set('scrollLeft', scrollLeft);
|
|
|
991 |
}
|
|
|
992 |
if (scrollTop && this._yScrollNode) {
|
|
|
993 |
this._yScrollNode.set('scrollTop', scrollTop);
|
|
|
994 |
}
|
|
|
995 |
},
|
|
|
996 |
|
|
|
997 |
/**
|
|
|
998 |
Wraps the table in a scrolling `<div>` of the configured width for "x"
|
|
|
999 |
scrolling.
|
|
|
1000 |
|
|
|
1001 |
@method _syncXScrollUI
|
|
|
1002 |
@param {Boolean} xy True if the table is configured with scrollable ="xy"
|
|
|
1003 |
@protected
|
|
|
1004 |
@since 3.5.0
|
|
|
1005 |
**/
|
|
|
1006 |
_syncXScrollUI: function (xy) {
|
|
|
1007 |
var scroller = this._xScrollNode,
|
|
|
1008 |
yScroller = this._yScrollContainer,
|
|
|
1009 |
table = this._tableNode,
|
|
|
1010 |
width = this.get('width'),
|
|
|
1011 |
bbWidth = this.get('boundingBox').get('offsetWidth'),
|
|
|
1012 |
scrollbarWidth = Y.DOM.getScrollbarWidth(),
|
|
|
1013 |
borderWidth, tableWidth;
|
|
|
1014 |
|
|
|
1015 |
if (!scroller) {
|
|
|
1016 |
scroller = this._createXScrollNode();
|
|
|
1017 |
|
|
|
1018 |
// Not using table.wrap() because IE went all crazy, wrapping the
|
|
|
1019 |
// table in the last td in the table itself.
|
|
|
1020 |
(yScroller || table).replace(scroller).appendTo(scroller);
|
|
|
1021 |
}
|
|
|
1022 |
|
|
|
1023 |
// Can't use offsetHeight - clientHeight because IE6 returns
|
|
|
1024 |
// clientHeight of 0 intially.
|
|
|
1025 |
borderWidth = styleDim(scroller, 'borderLeftWidth') +
|
|
|
1026 |
styleDim(scroller, 'borderRightWidth');
|
|
|
1027 |
|
|
|
1028 |
scroller.setStyle('width', '');
|
|
|
1029 |
this._uiSetDim('width', '');
|
|
|
1030 |
if (xy && this._yScrollContainer) {
|
|
|
1031 |
this._yScrollContainer.setStyle('width', '');
|
|
|
1032 |
}
|
|
|
1033 |
|
|
|
1034 |
// Lock the table's unconstrained width to avoid configured column
|
|
|
1035 |
// widths being ignored
|
|
|
1036 |
if (Y.UA.ie && Y.UA.ie < 8) {
|
|
|
1037 |
// Have to assign a style and trigger a reflow to allow the
|
|
|
1038 |
// subsequent clearing of width + reflow to expand the table to
|
|
|
1039 |
// natural width in IE 6
|
|
|
1040 |
table.setStyle('width', width);
|
|
|
1041 |
table.get('offsetWidth');
|
|
|
1042 |
}
|
|
|
1043 |
table.setStyle('width', '');
|
|
|
1044 |
tableWidth = table.get('offsetWidth');
|
|
|
1045 |
table.setStyle('width', tableWidth + 'px');
|
|
|
1046 |
|
|
|
1047 |
this._uiSetDim('width', width);
|
|
|
1048 |
|
|
|
1049 |
// Can't use 100% width because the borders add additional width
|
|
|
1050 |
// TODO: Cache the border widths, though it won't prevent a reflow
|
|
|
1051 |
scroller.setStyle('width', (bbWidth - borderWidth) + 'px');
|
|
|
1052 |
|
|
|
1053 |
// expand the table to fill the assigned width if it doesn't
|
|
|
1054 |
// already overflow the configured width
|
|
|
1055 |
if ((scroller.get('offsetWidth') - borderWidth) > tableWidth) {
|
|
|
1056 |
// Assumes the wrapped table doesn't have borders
|
|
|
1057 |
if (xy) {
|
|
|
1058 |
table.setStyle('width', (scroller.get('offsetWidth') -
|
|
|
1059 |
borderWidth - scrollbarWidth) + 'px');
|
|
|
1060 |
} else {
|
|
|
1061 |
table.setStyle('width', '100%');
|
|
|
1062 |
}
|
|
|
1063 |
}
|
|
|
1064 |
},
|
|
|
1065 |
|
|
|
1066 |
/**
|
|
|
1067 |
Wraps the table in a scrolling `<div>` of the configured height (accounting
|
|
|
1068 |
for the caption if there is one) if "y" scrolling is enabled. Otherwise,
|
|
|
1069 |
unwraps the table if necessary.
|
|
|
1070 |
|
|
|
1071 |
@method _syncYScrollUI
|
|
|
1072 |
@param {Boolean} xy True if the table is configured with scrollable = "xy"
|
|
|
1073 |
@protected
|
|
|
1074 |
@since 3.5.0
|
|
|
1075 |
**/
|
|
|
1076 |
_syncYScrollUI: function (xy) {
|
|
|
1077 |
var yScroller = this._yScrollContainer,
|
|
|
1078 |
yScrollNode = this._yScrollNode,
|
|
|
1079 |
xScroller = this._xScrollNode,
|
|
|
1080 |
fixedHeader = this._yScrollHeader,
|
|
|
1081 |
scrollbar = this._scrollbarNode,
|
|
|
1082 |
table = this._tableNode,
|
|
|
1083 |
thead = this._theadNode,
|
|
|
1084 |
captionTable = this._captionTable,
|
|
|
1085 |
boundingBox = this.get('boundingBox'),
|
|
|
1086 |
contentBox = this.get('contentBox'),
|
|
|
1087 |
width = this.get('width'),
|
|
|
1088 |
height = boundingBox.get('offsetHeight'),
|
|
|
1089 |
scrollbarWidth = Y.DOM.getScrollbarWidth(),
|
|
|
1090 |
outerScroller;
|
|
|
1091 |
|
|
|
1092 |
if (captionTable && !xy) {
|
|
|
1093 |
captionTable.setStyle('width', width || '100%');
|
|
|
1094 |
}
|
|
|
1095 |
|
|
|
1096 |
if (!yScroller) {
|
|
|
1097 |
yScroller = this._createYScrollNode();
|
|
|
1098 |
|
|
|
1099 |
yScrollNode = this._yScrollNode;
|
|
|
1100 |
|
|
|
1101 |
table.replace(yScroller).appendTo(yScrollNode);
|
|
|
1102 |
}
|
|
|
1103 |
|
|
|
1104 |
outerScroller = xy ? xScroller : yScroller;
|
|
|
1105 |
|
|
|
1106 |
if (!xy) {
|
|
|
1107 |
table.setStyle('width', '');
|
|
|
1108 |
}
|
|
|
1109 |
|
|
|
1110 |
// Set the scroller height
|
|
|
1111 |
if (xy) {
|
|
|
1112 |
// Account for the horizontal scrollbar in the overall height
|
|
|
1113 |
height -= scrollbarWidth;
|
|
|
1114 |
}
|
|
|
1115 |
|
|
|
1116 |
yScrollNode.setStyle('height',
|
|
|
1117 |
(height - outerScroller.get('offsetTop') -
|
|
|
1118 |
// because IE6 is returning clientHeight 0 initially
|
|
|
1119 |
styleDim(outerScroller, 'borderTopWidth') -
|
|
|
1120 |
styleDim(outerScroller, 'borderBottomWidth')) + 'px');
|
|
|
1121 |
|
|
|
1122 |
// Set the scroller width
|
|
|
1123 |
if (xy) {
|
|
|
1124 |
// For xy scrolling tables, the table should expand freely within
|
|
|
1125 |
// the x scroller
|
|
|
1126 |
yScroller.setStyle('width',
|
|
|
1127 |
(table.get('offsetWidth') + scrollbarWidth) + 'px');
|
|
|
1128 |
} else {
|
|
|
1129 |
this._uiSetYScrollWidth(width);
|
|
|
1130 |
}
|
|
|
1131 |
|
|
|
1132 |
if (captionTable && !xy) {
|
|
|
1133 |
captionTable.setStyle('width', yScroller.get('offsetWidth') + 'px');
|
|
|
1134 |
}
|
|
|
1135 |
|
|
|
1136 |
// Allow headerless scrolling
|
|
|
1137 |
if (thead && !fixedHeader) {
|
|
|
1138 |
fixedHeader = this._createYScrollHeader();
|
|
|
1139 |
|
|
|
1140 |
yScroller.prepend(fixedHeader);
|
|
|
1141 |
|
|
|
1142 |
this._syncScrollHeaders();
|
|
|
1143 |
}
|
|
|
1144 |
|
|
|
1145 |
if (fixedHeader) {
|
|
|
1146 |
this._syncScrollColumnWidths();
|
|
|
1147 |
|
|
|
1148 |
fixedHeader.setStyle('display', '');
|
|
|
1149 |
// This might need to come back if FF has issues
|
|
|
1150 |
//fixedHeader.setStyle('width', '100%');
|
|
|
1151 |
//(yScroller.get('clientWidth') + scrollbarWidth) + 'px');
|
|
|
1152 |
|
|
|
1153 |
if (!scrollbar) {
|
|
|
1154 |
scrollbar = this._createScrollbar();
|
|
|
1155 |
|
|
|
1156 |
this._bindScrollbar();
|
|
|
1157 |
|
|
|
1158 |
contentBox.prepend(scrollbar);
|
|
|
1159 |
}
|
|
|
1160 |
|
|
|
1161 |
this._uiSetScrollbarHeight();
|
|
|
1162 |
this._uiSetScrollbarPosition(outerScroller);
|
|
|
1163 |
}
|
|
|
1164 |
},
|
|
|
1165 |
|
|
|
1166 |
/**
|
|
|
1167 |
Assigns the appropriate class to the `boundingBox` to identify the DataTable
|
|
|
1168 |
as horizontally scrolling, vertically scrolling, or both (adds both classes).
|
|
|
1169 |
|
|
|
1170 |
Classes added are "yui3-datatable-scrollable-x" or "...-y"
|
|
|
1171 |
|
|
|
1172 |
@method _uiSetScrollable
|
|
|
1173 |
@protected
|
|
|
1174 |
@since 3.5.0
|
|
|
1175 |
**/
|
|
|
1176 |
_uiSetScrollable: function () {
|
|
|
1177 |
this.get('boundingBox')
|
|
|
1178 |
.toggleClass(this.getClassName('scrollable','x'), this._xScroll)
|
|
|
1179 |
.toggleClass(this.getClassName('scrollable','y'), this._yScroll);
|
|
|
1180 |
},
|
|
|
1181 |
|
|
|
1182 |
/**
|
|
|
1183 |
Updates the virtual scrollbar's height to avoid overlapping with the fixed
|
|
|
1184 |
headers.
|
|
|
1185 |
|
|
|
1186 |
@method _uiSetScrollbarHeight
|
|
|
1187 |
@protected
|
|
|
1188 |
@since 3.5.0
|
|
|
1189 |
**/
|
|
|
1190 |
_uiSetScrollbarHeight: function () {
|
|
|
1191 |
var scrollbar = this._scrollbarNode,
|
|
|
1192 |
scroller = this._yScrollNode,
|
|
|
1193 |
fixedHeader = this._yScrollHeader;
|
|
|
1194 |
|
|
|
1195 |
if (scrollbar && scroller && fixedHeader) {
|
|
|
1196 |
scrollbar.get('firstChild').setStyle('height',
|
|
|
1197 |
this._tbodyNode.get('scrollHeight') + 'px');
|
|
|
1198 |
|
|
|
1199 |
scrollbar.setStyle('height',
|
|
|
1200 |
(parseFloat(scroller.getComputedStyle('height')) -
|
|
|
1201 |
parseFloat(fixedHeader.getComputedStyle('height'))) + 'px');
|
|
|
1202 |
}
|
|
|
1203 |
},
|
|
|
1204 |
|
|
|
1205 |
/**
|
|
|
1206 |
Updates the virtual scrollbar's placement to avoid overlapping the fixed
|
|
|
1207 |
headers or the data table.
|
|
|
1208 |
|
|
|
1209 |
@method _uiSetScrollbarPosition
|
|
|
1210 |
@param {Node} scroller Reference node to position the scrollbar over
|
|
|
1211 |
@protected
|
|
|
1212 |
@since 3.5.0
|
|
|
1213 |
**/
|
|
|
1214 |
_uiSetScrollbarPosition: function (scroller) {
|
|
|
1215 |
var scrollbar = this._scrollbarNode,
|
|
|
1216 |
fixedHeader = this._yScrollHeader;
|
|
|
1217 |
|
|
|
1218 |
if (scrollbar && scroller && fixedHeader) {
|
|
|
1219 |
scrollbar.setStyles({
|
|
|
1220 |
// Using getCS instead of offsetHeight because FF uses
|
|
|
1221 |
// fractional values, but reports ints to offsetHeight, so
|
|
|
1222 |
// offsetHeight is unreliable. It is probably fine to use
|
|
|
1223 |
// offsetHeight in this case but this was left in place after
|
|
|
1224 |
// fixing an off-by-1px issue in FF 10- by fixing the caption
|
|
|
1225 |
// font style so FF picked it up.
|
|
|
1226 |
top: (parseFloat(fixedHeader.getComputedStyle('height')) +
|
|
|
1227 |
styleDim(scroller, 'borderTopWidth') +
|
|
|
1228 |
scroller.get('offsetTop')) + 'px',
|
|
|
1229 |
|
|
|
1230 |
// Minus 1 because IE 6-10 require the scrolled area to be
|
|
|
1231 |
// visible by at least 1px or it won't respond to clicks on the
|
|
|
1232 |
// scrollbar rail or endcap arrows.
|
|
|
1233 |
left: (scroller.get('offsetWidth') -
|
|
|
1234 |
Y.DOM.getScrollbarWidth() - 1 -
|
|
|
1235 |
styleDim(scroller, 'borderRightWidth')) + 'px'
|
|
|
1236 |
});
|
|
|
1237 |
}
|
|
|
1238 |
},
|
|
|
1239 |
|
|
|
1240 |
/**
|
|
|
1241 |
Assigns the width of the `<div>` wrapping the data table in vertically
|
|
|
1242 |
scrolling tables.
|
|
|
1243 |
|
|
|
1244 |
If the table can't compress to the specified width, the container is
|
|
|
1245 |
expanded accordingly.
|
|
|
1246 |
|
|
|
1247 |
@method _uiSetYScrollWidth
|
|
|
1248 |
@param {String} width The CSS width to attempt to set
|
|
|
1249 |
@protected
|
|
|
1250 |
@since 3.5.0
|
|
|
1251 |
**/
|
|
|
1252 |
_uiSetYScrollWidth: function (width) {
|
|
|
1253 |
var scroller = this._yScrollContainer,
|
|
|
1254 |
table = this._tableNode,
|
|
|
1255 |
tableWidth, borderWidth, scrollerWidth, scrollbarWidth;
|
|
|
1256 |
|
|
|
1257 |
if (scroller && table) {
|
|
|
1258 |
scrollbarWidth = Y.DOM.getScrollbarWidth();
|
|
|
1259 |
|
|
|
1260 |
if (width) {
|
|
|
1261 |
// Assumes no table border
|
|
|
1262 |
borderWidth = scroller.get('offsetWidth') -
|
|
|
1263 |
scroller.get('clientWidth') +
|
|
|
1264 |
scrollbarWidth; // added back at the end
|
|
|
1265 |
|
|
|
1266 |
// The table's rendered width might be greater than the
|
|
|
1267 |
// configured width
|
|
|
1268 |
scroller.setStyle('width', width);
|
|
|
1269 |
|
|
|
1270 |
// Have to subtract the border width from the configured width
|
|
|
1271 |
// because the scroller's width will need to be reduced by the
|
|
|
1272 |
// border width as well during the width reassignment below.
|
|
|
1273 |
scrollerWidth = scroller.get('clientWidth') - borderWidth;
|
|
|
1274 |
|
|
|
1275 |
// Assumes no table borders
|
|
|
1276 |
table.setStyle('width', scrollerWidth + 'px');
|
|
|
1277 |
|
|
|
1278 |
tableWidth = table.get('offsetWidth');
|
|
|
1279 |
|
|
|
1280 |
// Expand the scroll node width if the table can't fit.
|
|
|
1281 |
// Otherwise, reassign the scroller a pixel width that
|
|
|
1282 |
// accounts for the borders.
|
|
|
1283 |
scroller.setStyle('width',
|
|
|
1284 |
(tableWidth + scrollbarWidth) + 'px');
|
|
|
1285 |
} else {
|
|
|
1286 |
// Allow the table to expand naturally
|
|
|
1287 |
table.setStyle('width', '');
|
|
|
1288 |
scroller.setStyle('width', '');
|
|
|
1289 |
|
|
|
1290 |
scroller.setStyle('width',
|
|
|
1291 |
(table.get('offsetWidth') + scrollbarWidth) + 'px');
|
|
|
1292 |
}
|
|
|
1293 |
}
|
|
|
1294 |
},
|
|
|
1295 |
|
|
|
1296 |
/**
|
|
|
1297 |
Detaches the scroll event subscriptions used to maintain scroll position
|
|
|
1298 |
parity between the scrollable `<div>` wrapper around the data table and the
|
|
|
1299 |
virtual scrollbar for vertically scrolling tables.
|
|
|
1300 |
|
|
|
1301 |
@method _unbindScrollbar
|
|
|
1302 |
@protected
|
|
|
1303 |
@since 3.5.0
|
|
|
1304 |
**/
|
|
|
1305 |
_unbindScrollbar: function () {
|
|
|
1306 |
if (this._scrollbarEventHandle) {
|
|
|
1307 |
this._scrollbarEventHandle.detach();
|
|
|
1308 |
}
|
|
|
1309 |
},
|
|
|
1310 |
|
|
|
1311 |
/**
|
|
|
1312 |
Detaches the resize event subscription used to maintain column parity for
|
|
|
1313 |
vertically scrolling tables with percentage widths.
|
|
|
1314 |
|
|
|
1315 |
@method _unbindScrollResize
|
|
|
1316 |
@protected
|
|
|
1317 |
@since 3.5.0
|
|
|
1318 |
**/
|
|
|
1319 |
_unbindScrollResize: function () {
|
|
|
1320 |
if (this._scrollResizeHandle) {
|
|
|
1321 |
this._scrollResizeHandle.detach();
|
|
|
1322 |
delete this._scrollResizeHandle;
|
|
|
1323 |
}
|
|
|
1324 |
}
|
|
|
1325 |
|
|
|
1326 |
/**
|
|
|
1327 |
Indicates horizontal table scrolling is enabled.
|
|
|
1328 |
|
|
|
1329 |
@property _xScroll
|
|
|
1330 |
@type {Boolean}
|
|
|
1331 |
@default undefined (not initially set)
|
|
|
1332 |
@private
|
|
|
1333 |
@since 3.5.0
|
|
|
1334 |
**/
|
|
|
1335 |
//_xScroll: null,
|
|
|
1336 |
|
|
|
1337 |
/**
|
|
|
1338 |
Indicates vertical table scrolling is enabled.
|
|
|
1339 |
|
|
|
1340 |
@property _yScroll
|
|
|
1341 |
@type {Boolean}
|
|
|
1342 |
@default undefined (not initially set)
|
|
|
1343 |
@private
|
|
|
1344 |
@since 3.5.0
|
|
|
1345 |
**/
|
|
|
1346 |
//_yScroll: null,
|
|
|
1347 |
|
|
|
1348 |
/**
|
|
|
1349 |
Fixed column header `<table>` Node for vertical scrolling tables.
|
|
|
1350 |
|
|
|
1351 |
@property _yScrollHeader
|
|
|
1352 |
@type {Node}
|
|
|
1353 |
@default undefined (not initially set)
|
|
|
1354 |
@protected
|
|
|
1355 |
@since 3.5.0
|
|
|
1356 |
**/
|
|
|
1357 |
//_yScrollHeader: null,
|
|
|
1358 |
|
|
|
1359 |
/**
|
|
|
1360 |
Overflow Node used to contain the data rows in a vertically scrolling table.
|
|
|
1361 |
|
|
|
1362 |
@property _yScrollNode
|
|
|
1363 |
@type {Node}
|
|
|
1364 |
@default undefined (not initially set)
|
|
|
1365 |
@protected
|
|
|
1366 |
@since 3.5.0
|
|
|
1367 |
**/
|
|
|
1368 |
//_yScrollNode: null,
|
|
|
1369 |
|
|
|
1370 |
/**
|
|
|
1371 |
Overflow Node used to contain the table headers and data in a horizontally
|
|
|
1372 |
scrolling table.
|
|
|
1373 |
|
|
|
1374 |
@property _xScrollNode
|
|
|
1375 |
@type {Node}
|
|
|
1376 |
@default undefined (not initially set)
|
|
|
1377 |
@protected
|
|
|
1378 |
@since 3.5.0
|
|
|
1379 |
**/
|
|
|
1380 |
//_xScrollNode: null
|
|
|
1381 |
}, true);
|
|
|
1382 |
|
|
|
1383 |
Y.Base.mix(Y.DataTable, [Scrollable]);
|
|
|
1384 |
|
|
|
1385 |
|
|
|
1386 |
}, '3.18.1', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true});
|