|
525
|
1 |
/* |
|
|
2 |
YUI 3.10.3 (build 2fb5187) |
|
|
3 |
Copyright 2013 Yahoo! Inc. All rights reserved. |
|
|
4 |
Licensed under the BSD License. |
|
|
5 |
http://yuilibrary.com/license/ |
|
|
6 |
*/ |
|
|
7 |
|
|
|
8 |
YUI.add('datatable-scroll', function (Y, NAME) { |
|
|
9 |
|
|
|
10 |
/** |
|
|
11 |
Adds the ability to make the table rows scrollable while preserving the header |
|
|
12 |
placement. |
|
|
13 |
|
|
|
14 |
@module datatable-scroll |
|
|
15 |
@for DataTable |
|
|
16 |
@since 3.5.0 |
|
|
17 |
**/ |
|
|
18 |
var YLang = Y.Lang, |
|
|
19 |
isString = YLang.isString, |
|
|
20 |
isNumber = YLang.isNumber, |
|
|
21 |
isArray = YLang.isArray, |
|
|
22 |
|
|
|
23 |
Scrollable; |
|
|
24 |
|
|
|
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; |
|
|
28 |
} |
|
|
29 |
|
|
|
30 |
/** |
|
|
31 |
_API docs for this extension are included in the DataTable class._ |
|
|
32 |
|
|
|
33 |
Adds the ability to make the table rows scrollable while preserving the header |
|
|
34 |
placement. |
|
|
35 |
|
|
|
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. |
|
|
42 |
|
|
|
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. |
|
|
45 |
|
|
|
46 |
To activate or deactivate scrolling, set the `scrollable` attribute to one of |
|
|
47 |
the following values: |
|
|
48 |
|
|
|
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 |
|
|
53 |
also set. |
|
|
54 |
* 'y' - Activate vertical scrolling only. Requires the `height` attribute is |
|
|
55 |
also set. |
|
|
56 |
|
|
|
57 |
@class DataTable.Scrollable |
|
|
58 |
@for DataTable |
|
|
59 |
@since 3.5.0 |
|
|
60 |
**/ |
|
|
61 |
Y.DataTable.Scrollable = Scrollable = function () {}; |
|
|
62 |
|
|
|
63 |
Scrollable.ATTRS = { |
|
|
64 |
/** |
|
|
65 |
Activates or deactivates scrolling in the table. Acceptable values are: |
|
|
66 |
|
|
|
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 |
|
|
71 |
is also set. |
|
|
72 |
* 'y' - Activate vertical scrolling only. Requires the `height` attribute |
|
|
73 |
is also set. |
|
|
74 |
|
|
|
75 |
@attribute scrollable |
|
|
76 |
@type {String|Boolean} |
|
|
77 |
@value false |
|
|
78 |
@since 3.5.0 |
|
|
79 |
**/ |
|
|
80 |
scrollable: { |
|
|
81 |
value: false, |
|
|
82 |
setter: '_setScrollable' |
|
|
83 |
} |
|
|
84 |
}; |
|
|
85 |
|
|
|
86 |
Y.mix(Scrollable.prototype, { |
|
|
87 |
|
|
|
88 |
/** |
|
|
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). |
|
|
95 |
|
|
|
96 |
@method scrollTo |
|
|
97 |
@param {String|Number|Number[]|Node} id A row clientId, row index, cell |
|
|
98 |
coordinate array, id string, or Node |
|
|
99 |
@return {DataTable} |
|
|
100 |
@chainable |
|
|
101 |
@since 3.5.0 |
|
|
102 |
**/ |
|
|
103 |
scrollTo: function (id) { |
|
|
104 |
var target; |
|
|
105 |
|
|
|
106 |
if (id && this._tbodyNode && (this._yScrollNode || this._xScrollNode)) { |
|
|
107 |
if (isArray(id)) { |
|
|
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')) { |
|
|
116 |
target = id; |
|
|
117 |
} |
|
|
118 |
|
|
|
119 |
if(target) { |
|
|
120 |
target.scrollIntoView(); |
|
|
121 |
} |
|
|
122 |
} |
|
|
123 |
|
|
|
124 |
return this; |
|
|
125 |
}, |
|
|
126 |
|
|
|
127 |
//-------------------------------------------------------------------------- |
|
|
128 |
// Protected properties and methods |
|
|
129 |
//-------------------------------------------------------------------------- |
|
|
130 |
|
|
|
131 |
/** |
|
|
132 |
Template for the `<table>` that is used to fix the caption in place when |
|
|
133 |
the table is horizontally scrolling. |
|
|
134 |
|
|
|
135 |
@property _CAPTION_TABLE_TEMPLATE |
|
|
136 |
@type {HTML} |
|
|
137 |
@value '<table class="{className}" role="presentation"></table>' |
|
|
138 |
@protected |
|
|
139 |
@since 3.5.0 |
|
|
140 |
**/ |
|
|
141 |
_CAPTION_TABLE_TEMPLATE: '<table class="{className}" role="presentation"></table>', |
|
|
142 |
|
|
|
143 |
/** |
|
|
144 |
Template used to create sizable element liners around header content to |
|
|
145 |
synchronize fixed header column widths. |
|
|
146 |
|
|
|
147 |
@property _SCROLL_LINER_TEMPLATE |
|
|
148 |
@type {HTML} |
|
|
149 |
@value '<div class="{className}"></div>' |
|
|
150 |
@protected |
|
|
151 |
@since 3.5.0 |
|
|
152 |
**/ |
|
|
153 |
_SCROLL_LINER_TEMPLATE: '<div class="{className}"></div>', |
|
|
154 |
|
|
|
155 |
/** |
|
|
156 |
Template for the virtual scrollbar needed in "y" and "xy" scrolling setups. |
|
|
157 |
|
|
|
158 |
@property _SCROLLBAR_TEMPLATE |
|
|
159 |
@type {HTML} |
|
|
160 |
@value '<div class="{className}"><div></div></div>' |
|
|
161 |
@protected |
|
|
162 |
@since 3.5.0 |
|
|
163 |
**/ |
|
|
164 |
_SCROLLBAR_TEMPLATE: '<div class="{className}"><div></div></div>', |
|
|
165 |
|
|
|
166 |
/** |
|
|
167 |
Template for the `<div>` that is used to contain the table when the table is |
|
|
168 |
horizontally scrolling. |
|
|
169 |
|
|
|
170 |
@property _X_SCROLLER_TEMPLATE |
|
|
171 |
@type {HTML} |
|
|
172 |
@value '<div class="{className}"></div>' |
|
|
173 |
@protected |
|
|
174 |
@since 3.5.0 |
|
|
175 |
**/ |
|
|
176 |
_X_SCROLLER_TEMPLATE: '<div class="{className}"></div>', |
|
|
177 |
|
|
|
178 |
/** |
|
|
179 |
Template for the `<table>` used to contain the fixed column headers for |
|
|
180 |
vertically scrolling tables. |
|
|
181 |
|
|
|
182 |
@property _Y_SCROLL_HEADER_TEMPLATE |
|
|
183 |
@type {HTML} |
|
|
184 |
@value '<table cellspacing="0" role="presentation" aria-hidden="true" class="{className}"></table>' |
|
|
185 |
@protected |
|
|
186 |
@since 3.5.0 |
|
|
187 |
**/ |
|
|
188 |
_Y_SCROLL_HEADER_TEMPLATE: '<table cellspacing="0" aria-hidden="true" class="{className}"></table>', |
|
|
189 |
|
|
|
190 |
/** |
|
|
191 |
Template for the `<div>` that is used to contain the rows when the table is |
|
|
192 |
vertically scrolling. |
|
|
193 |
|
|
|
194 |
@property _Y_SCROLLER_TEMPLATE |
|
|
195 |
@type {HTML} |
|
|
196 |
@value '<div class="{className}"><div class="{scrollerClassName}"></div></div>' |
|
|
197 |
@protected |
|
|
198 |
@since 3.5.0 |
|
|
199 |
**/ |
|
|
200 |
_Y_SCROLLER_TEMPLATE: '<div class="{className}"><div class="{scrollerClassName}"></div></div>', |
|
|
201 |
|
|
|
202 |
/** |
|
|
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. |
|
|
206 |
|
|
|
207 |
@method _addScrollbarPadding |
|
|
208 |
@protected |
|
|
209 |
@since 3.5.0 |
|
|
210 |
**/ |
|
|
211 |
_addScrollbarPadding: function () { |
|
|
212 |
var fixedHeader = this._yScrollHeader, |
|
|
213 |
headerClass = '.' + this.getClassName('header'), |
|
|
214 |
scrollbarWidth, rows, header, i, len; |
|
|
215 |
|
|
|
216 |
if (fixedHeader) { |
|
|
217 |
scrollbarWidth = Y.DOM.getScrollbarWidth() + 'px'; |
|
|
218 |
rows = fixedHeader.all('tr'); |
|
|
219 |
|
|
|
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); |
|
|
223 |
} |
|
|
224 |
} |
|
|
225 |
}, |
|
|
226 |
|
|
|
227 |
/** |
|
|
228 |
Reacts to changes in the `scrollable` attribute by updating the `_xScroll` |
|
|
229 |
and `_yScroll` properties and syncing the scrolling structure accordingly. |
|
|
230 |
|
|
|
231 |
@method _afterScrollableChange |
|
|
232 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
233 |
@protected |
|
|
234 |
@since 3.5.0 |
|
|
235 |
**/ |
|
|
236 |
_afterScrollableChange: function () { |
|
|
237 |
var scroller = this._xScrollNode; |
|
|
238 |
|
|
|
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', ''); |
|
|
245 |
} |
|
|
246 |
} |
|
|
247 |
|
|
|
248 |
this._syncScrollUI(); |
|
|
249 |
}, |
|
|
250 |
|
|
|
251 |
/** |
|
|
252 |
Reacts to changes in the `caption` attribute by adding, removing, or |
|
|
253 |
syncing the caption table when the table is set to scroll. |
|
|
254 |
|
|
|
255 |
@method _afterScrollCaptionChange |
|
|
256 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
257 |
@protected |
|
|
258 |
@since 3.5.0 |
|
|
259 |
**/ |
|
|
260 |
_afterScrollCaptionChange: function () { |
|
|
261 |
if (this._xScroll || this._yScroll) { |
|
|
262 |
this._syncScrollUI(); |
|
|
263 |
} |
|
|
264 |
}, |
|
|
265 |
|
|
|
266 |
/** |
|
|
267 |
Reacts to changes in the `columns` attribute of vertically scrolling tables |
|
|
268 |
by refreshing the fixed headers, scroll container, and virtual scrollbar |
|
|
269 |
position. |
|
|
270 |
|
|
|
271 |
@method _afterScrollColumnsChange |
|
|
272 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
273 |
@protected |
|
|
274 |
@since 3.5.0 |
|
|
275 |
**/ |
|
|
276 |
_afterScrollColumnsChange: function () { |
|
|
277 |
if (this._xScroll || this._yScroll) { |
|
|
278 |
if (this._yScroll && this._yScrollHeader) { |
|
|
279 |
this._syncScrollHeaders(); |
|
|
280 |
} |
|
|
281 |
|
|
|
282 |
this._syncScrollUI(); |
|
|
283 |
} |
|
|
284 |
}, |
|
|
285 |
|
|
|
286 |
/** |
|
|
287 |
Reacts to changes in vertically scrolling table's `data` ModelList by |
|
|
288 |
synchronizing the fixed column header widths and virtual scrollbar height. |
|
|
289 |
|
|
|
290 |
@method _afterScrollDataChange |
|
|
291 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
292 |
@protected |
|
|
293 |
@since 3.5.0 |
|
|
294 |
**/ |
|
|
295 |
_afterScrollDataChange: function () { |
|
|
296 |
if (this._xScroll || this._yScroll) { |
|
|
297 |
this._syncScrollUI(); |
|
|
298 |
} |
|
|
299 |
}, |
|
|
300 |
|
|
|
301 |
/** |
|
|
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. |
|
|
307 |
|
|
|
308 |
@method _afterScrollHeightChange |
|
|
309 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
310 |
@protected |
|
|
311 |
@since 3.5.0 |
|
|
312 |
**/ |
|
|
313 |
_afterScrollHeightChange: function () { |
|
|
314 |
if (this._yScroll) { |
|
|
315 |
this._syncScrollUI(); |
|
|
316 |
} |
|
|
317 |
}, |
|
|
318 |
|
|
|
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. |
|
|
322 |
|
|
|
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. |
|
|
326 |
*/ |
|
|
327 |
_afterScrollSort: function () { |
|
|
328 |
var headers, headerClass; |
|
|
329 |
|
|
|
330 |
if (this._yScroll && this._yScrollHeader) { |
|
|
331 |
headerClass = '.' + this.getClassName('header'); |
|
|
332 |
headers = this._theadNode.all(headerClass); |
|
|
333 |
|
|
|
334 |
this._yScrollHeader.all(headerClass).each(function (header, i) { |
|
|
335 |
header.set('className', headers.item(i).get('className')); |
|
|
336 |
}); |
|
|
337 |
} |
|
|
338 |
}, |
|
|
339 |
|
|
|
340 |
/** |
|
|
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 |
|
|
344 |
tables. |
|
|
345 |
|
|
|
346 |
@method _afterScrollWidthChange |
|
|
347 |
@param {EventFacade} e The relevant change event (ignored) |
|
|
348 |
@protected |
|
|
349 |
@since 3.5.0 |
|
|
350 |
**/ |
|
|
351 |
_afterScrollWidthChange: function () { |
|
|
352 |
if (this._xScroll || this._yScroll) { |
|
|
353 |
this._syncScrollUI(); |
|
|
354 |
} |
|
|
355 |
}, |
|
|
356 |
|
|
|
357 |
/** |
|
|
358 |
Binds virtual scrollbar interaction to the `_yScrollNode`'s `scrollTop` and |
|
|
359 |
vice versa. |
|
|
360 |
|
|
|
361 |
@method _bindScrollbar |
|
|
362 |
@protected |
|
|
363 |
@since 3.5.0 |
|
|
364 |
**/ |
|
|
365 |
_bindScrollbar: function () { |
|
|
366 |
var scrollbar = this._scrollbarNode, |
|
|
367 |
scroller = this._yScrollNode; |
|
|
368 |
|
|
|
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) |
|
|
373 |
]); |
|
|
374 |
} |
|
|
375 |
}, |
|
|
376 |
|
|
|
377 |
/** |
|
|
378 |
Binds to the window resize event to update the vertical scrolling table |
|
|
379 |
headers and wrapper `<div>` dimensions. |
|
|
380 |
|
|
|
381 |
@method _bindScrollResize |
|
|
382 |
@protected |
|
|
383 |
@since 3.5.0 |
|
|
384 |
**/ |
|
|
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); |
|
|
391 |
} |
|
|
392 |
}, |
|
|
393 |
|
|
|
394 |
/** |
|
|
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. |
|
|
398 |
|
|
|
399 |
This executes after the table's native `bindUI` method. |
|
|
400 |
|
|
|
401 |
@method _bindScrollUI |
|
|
402 |
@protected |
|
|
403 |
@since 3.5.0 |
|
|
404 |
**/ |
|
|
405 |
_bindScrollUI: function () { |
|
|
406 |
this.after({ |
|
|
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) |
|
|
416 |
}); |
|
|
417 |
|
|
|
418 |
this.after(['dataChange', '*:add', '*:remove', '*:reset', '*:change'], |
|
|
419 |
Y.bind('_afterScrollDataChange', this)); |
|
|
420 |
}, |
|
|
421 |
|
|
|
422 |
/** |
|
|
423 |
Clears the lock and timer used to manage synchronizing the scroll position |
|
|
424 |
between the vertical scroll container and the virtual scrollbar. |
|
|
425 |
|
|
|
426 |
@method _clearScrollLock |
|
|
427 |
@protected |
|
|
428 |
@since 3.5.0 |
|
|
429 |
**/ |
|
|
430 |
_clearScrollLock: function () { |
|
|
431 |
if (this._scrollLock) { |
|
|
432 |
this._scrollLock.cancel(); |
|
|
433 |
delete this._scrollLock; |
|
|
434 |
} |
|
|
435 |
}, |
|
|
436 |
|
|
|
437 |
/** |
|
|
438 |
Creates a virtual scrollbar from the `_SCROLLBAR_TEMPLATE`, assigning it to |
|
|
439 |
the `_scrollbarNode` property. |
|
|
440 |
|
|
|
441 |
@method _createScrollbar |
|
|
442 |
@return {Node} The created Node |
|
|
443 |
@protected |
|
|
444 |
@since 3.5.0 |
|
|
445 |
**/ |
|
|
446 |
_createScrollbar: function () { |
|
|
447 |
var scrollbar = this._scrollbarNode; |
|
|
448 |
|
|
|
449 |
if (!scrollbar) { |
|
|
450 |
scrollbar = this._scrollbarNode = Y.Node.create( |
|
|
451 |
Y.Lang.sub(this._SCROLLBAR_TEMPLATE, { |
|
|
452 |
className: this.getClassName('scrollbar') |
|
|
453 |
})); |
|
|
454 |
|
|
|
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'); |
|
|
458 |
} |
|
|
459 |
|
|
|
460 |
return scrollbar; |
|
|
461 |
}, |
|
|
462 |
|
|
|
463 |
/** |
|
|
464 |
Creates a separate table to contain the caption when the table is |
|
|
465 |
configured to scroll vertically or horizontally. |
|
|
466 |
|
|
|
467 |
@method _createScrollCaptionTable |
|
|
468 |
@return {Node} The created Node |
|
|
469 |
@protected |
|
|
470 |
@since 3.5.0 |
|
|
471 |
**/ |
|
|
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') |
|
|
477 |
})); |
|
|
478 |
|
|
|
479 |
this._captionTable.empty(); |
|
|
480 |
} |
|
|
481 |
|
|
|
482 |
return this._captionTable; |
|
|
483 |
}, |
|
|
484 |
|
|
|
485 |
/** |
|
|
486 |
Populates the `_xScrollNode` property by creating the `<div>` Node described |
|
|
487 |
by the `_X_SCROLLER_TEMPLATE`. |
|
|
488 |
|
|
|
489 |
@method _createXScrollNode |
|
|
490 |
@return {Node} The created Node |
|
|
491 |
@protected |
|
|
492 |
@since 3.5.0 |
|
|
493 |
**/ |
|
|
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') |
|
|
499 |
})); |
|
|
500 |
} |
|
|
501 |
|
|
|
502 |
return this._xScrollNode; |
|
|
503 |
}, |
|
|
504 |
|
|
|
505 |
/** |
|
|
506 |
Populates the `_yScrollHeader` property by creating the `<table>` Node |
|
|
507 |
described by the `_Y_SCROLL_HEADER_TEMPLATE`. |
|
|
508 |
|
|
|
509 |
@method _createYScrollHeader |
|
|
510 |
@return {Node} The created Node |
|
|
511 |
@protected |
|
|
512 |
@since 3.5.0 |
|
|
513 |
**/ |
|
|
514 |
_createYScrollHeader: function () { |
|
|
515 |
var fixedHeader = this._yScrollHeader; |
|
|
516 |
|
|
|
517 |
if (!fixedHeader) { |
|
|
518 |
fixedHeader = this._yScrollHeader = Y.Node.create( |
|
|
519 |
Y.Lang.sub(this._Y_SCROLL_HEADER_TEMPLATE, { |
|
|
520 |
className: this.getClassName('scroll','columns') |
|
|
521 |
})); |
|
|
522 |
} |
|
|
523 |
|
|
|
524 |
return fixedHeader; |
|
|
525 |
}, |
|
|
526 |
|
|
|
527 |
/** |
|
|
528 |
Populates the `_yScrollNode` property by creating the `<div>` Node described |
|
|
529 |
by the `_Y_SCROLLER_TEMPLATE`. |
|
|
530 |
|
|
|
531 |
@method _createYScrollNode |
|
|
532 |
@return {Node} The created Node |
|
|
533 |
@protected |
|
|
534 |
@since 3.5.0 |
|
|
535 |
**/ |
|
|
536 |
_createYScrollNode: function () { |
|
|
537 |
var scrollerClass; |
|
|
538 |
|
|
|
539 |
if (!this._yScrollNode) { |
|
|
540 |
scrollerClass = this.getClassName('y', 'scroller'); |
|
|
541 |
|
|
|
542 |
this._yScrollContainer = Y.Node.create( |
|
|
543 |
Y.Lang.sub(this._Y_SCROLLER_TEMPLATE, { |
|
|
544 |
className: this.getClassName('y','scroller','container'), |
|
|
545 |
scrollerClassName: scrollerClass |
|
|
546 |
})); |
|
|
547 |
|
|
|
548 |
this._yScrollNode = this._yScrollContainer |
|
|
549 |
.one('.' + scrollerClass); |
|
|
550 |
} |
|
|
551 |
|
|
|
552 |
return this._yScrollContainer; |
|
|
553 |
}, |
|
|
554 |
|
|
|
555 |
/** |
|
|
556 |
Removes the nodes used to create horizontal and vertical scrolling and |
|
|
557 |
rejoins the caption to the main table if needed. |
|
|
558 |
|
|
|
559 |
@method _disableScrolling |
|
|
560 |
@protected |
|
|
561 |
@since 3.5.0 |
|
|
562 |
**/ |
|
|
563 |
_disableScrolling: function () { |
|
|
564 |
this._removeScrollCaptionTable(); |
|
|
565 |
this._disableXScrolling(); |
|
|
566 |
this._disableYScrolling(); |
|
|
567 |
this._unbindScrollResize(); |
|
|
568 |
|
|
|
569 |
this._uiSetWidth(this.get('width')); |
|
|
570 |
}, |
|
|
571 |
|
|
|
572 |
/** |
|
|
573 |
Removes the nodes used to allow horizontal scrolling. |
|
|
574 |
|
|
|
575 |
@method _disableXScrolling |
|
|
576 |
@protected |
|
|
577 |
@since 3.5.0 |
|
|
578 |
**/ |
|
|
579 |
_disableXScrolling: function () { |
|
|
580 |
this._removeXScrollNode(); |
|
|
581 |
}, |
|
|
582 |
|
|
|
583 |
/** |
|
|
584 |
Removes the nodes used to allow vertical scrolling. |
|
|
585 |
|
|
|
586 |
@method _disableYScrolling |
|
|
587 |
@protected |
|
|
588 |
@since 3.5.0 |
|
|
589 |
**/ |
|
|
590 |
_disableYScrolling: function () { |
|
|
591 |
this._removeYScrollHeader(); |
|
|
592 |
this._removeYScrollNode(); |
|
|
593 |
this._removeYScrollContainer(); |
|
|
594 |
this._removeScrollbar(); |
|
|
595 |
}, |
|
|
596 |
|
|
|
597 |
/** |
|
|
598 |
Cleans up external event subscriptions. |
|
|
599 |
|
|
|
600 |
@method destructor |
|
|
601 |
@protected |
|
|
602 |
@since 3.5.0 |
|
|
603 |
**/ |
|
|
604 |
destructor: function () { |
|
|
605 |
this._unbindScrollbar(); |
|
|
606 |
this._unbindScrollResize(); |
|
|
607 |
this._clearScrollLock(); |
|
|
608 |
}, |
|
|
609 |
|
|
|
610 |
/** |
|
|
611 |
Sets up event handlers and AOP advice methods to bind the DataTable's natural |
|
|
612 |
behaviors with the scrolling APIs and state. |
|
|
613 |
|
|
|
614 |
@method initializer |
|
|
615 |
@param {Object} config The config object passed to the constructor (ignored) |
|
|
616 |
@protected |
|
|
617 |
@since 3.5.0 |
|
|
618 |
**/ |
|
|
619 |
initializer: function () { |
|
|
620 |
this._setScrollProperties(); |
|
|
621 |
|
|
|
622 |
this.after(['scrollableChange', 'heightChange', 'widthChange'], |
|
|
623 |
this._setScrollProperties); |
|
|
624 |
|
|
|
625 |
this.after('renderView', Y.bind('_syncScrollUI', this)); |
|
|
626 |
|
|
|
627 |
Y.Do.after(this._bindScrollUI, this, 'bindUI'); |
|
|
628 |
}, |
|
|
629 |
|
|
|
630 |
/** |
|
|
631 |
Removes the table used to house the caption when the table is scrolling. |
|
|
632 |
|
|
|
633 |
@method _removeScrollCaptionTable |
|
|
634 |
@protected |
|
|
635 |
@since 3.5.0 |
|
|
636 |
**/ |
|
|
637 |
_removeScrollCaptionTable: function () { |
|
|
638 |
if (this._captionTable) { |
|
|
639 |
if (this._captionNode) { |
|
|
640 |
this._tableNode.prepend(this._captionNode); |
|
|
641 |
} |
|
|
642 |
|
|
|
643 |
this._captionTable.remove().destroy(true); |
|
|
644 |
|
|
|
645 |
delete this._captionTable; |
|
|
646 |
} |
|
|
647 |
}, |
|
|
648 |
|
|
|
649 |
/** |
|
|
650 |
Removes the `<div>` wrapper used to contain the data table when the table |
|
|
651 |
is horizontally scrolling. |
|
|
652 |
|
|
|
653 |
@method _removeXScrollNode |
|
|
654 |
@protected |
|
|
655 |
@since 3.5.0 |
|
|
656 |
**/ |
|
|
657 |
_removeXScrollNode: function () { |
|
|
658 |
var scroller = this._xScrollNode; |
|
|
659 |
|
|
|
660 |
if (scroller) { |
|
|
661 |
scroller.replace(scroller.get('childNodes').toFrag()); |
|
|
662 |
scroller.remove().destroy(true); |
|
|
663 |
|
|
|
664 |
delete this._xScrollNode; |
|
|
665 |
} |
|
|
666 |
}, |
|
|
667 |
|
|
|
668 |
/** |
|
|
669 |
Removes the `<div>` wrapper used to contain the data table and fixed header |
|
|
670 |
when the table is vertically scrolling. |
|
|
671 |
|
|
|
672 |
@method _removeYScrollContainer |
|
|
673 |
@protected |
|
|
674 |
@since 3.5.0 |
|
|
675 |
**/ |
|
|
676 |
_removeYScrollContainer: function () { |
|
|
677 |
var scroller = this._yScrollContainer; |
|
|
678 |
|
|
|
679 |
if (scroller) { |
|
|
680 |
scroller.replace(scroller.get('childNodes').toFrag()); |
|
|
681 |
scroller.remove().destroy(true); |
|
|
682 |
|
|
|
683 |
delete this._yScrollContainer; |
|
|
684 |
} |
|
|
685 |
}, |
|
|
686 |
|
|
|
687 |
/** |
|
|
688 |
Removes the `<table>` used to contain the fixed column headers when the |
|
|
689 |
table is vertically scrolling. |
|
|
690 |
|
|
|
691 |
@method _removeYScrollHeader |
|
|
692 |
@protected |
|
|
693 |
@since 3.5.0 |
|
|
694 |
**/ |
|
|
695 |
_removeYScrollHeader: function () { |
|
|
696 |
if (this._yScrollHeader) { |
|
|
697 |
this._yScrollHeader.remove().destroy(true); |
|
|
698 |
|
|
|
699 |
delete this._yScrollHeader; |
|
|
700 |
} |
|
|
701 |
}, |
|
|
702 |
|
|
|
703 |
/** |
|
|
704 |
Removes the `<div>` wrapper used to contain the data table when the table |
|
|
705 |
is vertically scrolling. |
|
|
706 |
|
|
|
707 |
@method _removeYScrollNode |
|
|
708 |
@protected |
|
|
709 |
@since 3.5.0 |
|
|
710 |
**/ |
|
|
711 |
_removeYScrollNode: function () { |
|
|
712 |
var scroller = this._yScrollNode; |
|
|
713 |
|
|
|
714 |
if (scroller) { |
|
|
715 |
scroller.replace(scroller.get('childNodes').toFrag()); |
|
|
716 |
scroller.remove().destroy(true); |
|
|
717 |
|
|
|
718 |
delete this._yScrollNode; |
|
|
719 |
} |
|
|
720 |
}, |
|
|
721 |
|
|
|
722 |
/** |
|
|
723 |
Removes the virtual scrollbar used by scrolling tables. |
|
|
724 |
|
|
|
725 |
@method _removeScrollbar |
|
|
726 |
@protected |
|
|
727 |
@since 3.5.0 |
|
|
728 |
**/ |
|
|
729 |
_removeScrollbar: function () { |
|
|
730 |
if (this._scrollbarNode) { |
|
|
731 |
this._scrollbarNode.remove().destroy(true); |
|
|
732 |
|
|
|
733 |
delete this._scrollbarNode; |
|
|
734 |
} |
|
|
735 |
if (this._scrollbarEventHandle) { |
|
|
736 |
this._scrollbarEventHandle.detach(); |
|
|
737 |
|
|
|
738 |
delete this._scrollbarEventHandle; |
|
|
739 |
} |
|
|
740 |
}, |
|
|
741 |
|
|
|
742 |
/** |
|
|
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. |
|
|
746 |
|
|
|
747 |
@method _setScrollable |
|
|
748 |
@param {String|Boolea} val Incoming value for the `scrollable` attribute |
|
|
749 |
@return {String} |
|
|
750 |
@protected |
|
|
751 |
@since 3.5.0 |
|
|
752 |
**/ |
|
|
753 |
_setScrollable: function (val) { |
|
|
754 |
if (val === true) { |
|
|
755 |
val = 'xy'; |
|
|
756 |
} |
|
|
757 |
|
|
|
758 |
if (isString(val)) { |
|
|
759 |
val = val.toLowerCase(); |
|
|
760 |
} |
|
|
761 |
|
|
|
762 |
return (val === false || val === 'y' || val === 'x' || val === 'xy') ? |
|
|
763 |
val : |
|
|
764 |
Y.Attribute.INVALID_VALUE; |
|
|
765 |
}, |
|
|
766 |
|
|
|
767 |
/** |
|
|
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. |
|
|
771 |
|
|
|
772 |
@method _setScrollProperties |
|
|
773 |
@protected |
|
|
774 |
@since 3.5.0 |
|
|
775 |
**/ |
|
|
776 |
_setScrollProperties: function () { |
|
|
777 |
var scrollable = this.get('scrollable') || '', |
|
|
778 |
width = this.get('width'), |
|
|
779 |
height = this.get('height'); |
|
|
780 |
|
|
|
781 |
this._xScroll = width && scrollable.indexOf('x') > -1; |
|
|
782 |
this._yScroll = height && scrollable.indexOf('y') > -1; |
|
|
783 |
}, |
|
|
784 |
|
|
|
785 |
/** |
|
|
786 |
Keeps the virtual scrollbar and the scrolling `<div>` wrapper around the |
|
|
787 |
data table in vertically scrolling tables in sync. |
|
|
788 |
|
|
|
789 |
@method _syncScrollPosition |
|
|
790 |
@param {DOMEventFacade} e The scroll event |
|
|
791 |
@protected |
|
|
792 |
@since 3.5.0 |
|
|
793 |
**/ |
|
|
794 |
_syncScrollPosition: function (e) { |
|
|
795 |
var scrollbar = this._scrollbarNode, |
|
|
796 |
scroller = this._yScrollNode, |
|
|
797 |
source = e.currentTarget, |
|
|
798 |
other; |
|
|
799 |
|
|
|
800 |
if (scrollbar && scroller) { |
|
|
801 |
if (this._scrollLock && this._scrollLock.source !== source) { |
|
|
802 |
return; |
|
|
803 |
} |
|
|
804 |
|
|
|
805 |
this._clearScrollLock(); |
|
|
806 |
this._scrollLock = Y.later(300, this, this._clearScrollLock); |
|
|
807 |
this._scrollLock.source = source; |
|
|
808 |
|
|
|
809 |
other = (source === scrollbar) ? scroller : scrollbar; |
|
|
810 |
other.set('scrollTop', source.get('scrollTop')); |
|
|
811 |
} |
|
|
812 |
}, |
|
|
813 |
|
|
|
814 |
/** |
|
|
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 |
|
|
817 |
be. |
|
|
818 |
|
|
|
819 |
@method _syncScrollCaptionUI |
|
|
820 |
@protected |
|
|
821 |
@since 3.5.0 |
|
|
822 |
**/ |
|
|
823 |
_syncScrollCaptionUI: function () { |
|
|
824 |
var caption = this._captionNode, |
|
|
825 |
table = this._tableNode, |
|
|
826 |
captionTable = this._captionTable, |
|
|
827 |
id; |
|
|
828 |
|
|
|
829 |
if (caption) { |
|
|
830 |
id = caption.getAttribute('id'); |
|
|
831 |
|
|
|
832 |
if (!captionTable) { |
|
|
833 |
captionTable = this._createScrollCaptionTable(); |
|
|
834 |
|
|
|
835 |
this.get('contentBox').prepend(captionTable); |
|
|
836 |
} |
|
|
837 |
|
|
|
838 |
if (!caption.get('parentNode').compareTo(captionTable)) { |
|
|
839 |
captionTable.empty().insert(caption); |
|
|
840 |
|
|
|
841 |
if (!id) { |
|
|
842 |
id = Y.stamp(caption); |
|
|
843 |
caption.setAttribute('id', id); |
|
|
844 |
} |
|
|
845 |
|
|
|
846 |
table.setAttribute('aria-describedby', id); |
|
|
847 |
} |
|
|
848 |
} else if (captionTable) { |
|
|
849 |
this._removeScrollCaptionTable(); |
|
|
850 |
} |
|
|
851 |
}, |
|
|
852 |
|
|
|
853 |
/** |
|
|
854 |
Assigns widths to the fixed header columns to match the columns in the data |
|
|
855 |
table. |
|
|
856 |
|
|
|
857 |
@method _syncScrollColumnWidths |
|
|
858 |
@protected |
|
|
859 |
@since 3.5.0 |
|
|
860 |
**/ |
|
|
861 |
_syncScrollColumnWidths: function () { |
|
|
862 |
var widths = []; |
|
|
863 |
|
|
|
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) { |
|
|
869 |
widths.push( |
|
|
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')); |
|
|
886 |
}); |
|
|
887 |
|
|
|
888 |
this._yScrollHeader.all('.' + this.getClassName('scroll', 'liner')) |
|
|
889 |
.each(function (liner, i) { |
|
|
890 |
liner.setStyle('width', widths[i]); |
|
|
891 |
}); |
|
|
892 |
} |
|
|
893 |
}, |
|
|
894 |
|
|
|
895 |
/** |
|
|
896 |
Creates matching headers in the fixed header table for vertically scrolling |
|
|
897 |
tables and synchronizes the column widths. |
|
|
898 |
|
|
|
899 |
@method _syncScrollHeaders |
|
|
900 |
@protected |
|
|
901 |
@since 3.5.0 |
|
|
902 |
**/ |
|
|
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); |
|
|
909 |
|
|
|
910 |
if (this._theadNode && fixedHeader) { |
|
|
911 |
fixedHeader.empty().appendChild( |
|
|
912 |
this._theadNode.cloneNode(true)); |
|
|
913 |
|
|
|
914 |
// Prevent duplicate IDs and assign ARIA attributes to hide |
|
|
915 |
// from screen readers |
|
|
916 |
fixedHeader.all('[id]').removeAttribute('id'); |
|
|
917 |
|
|
|
918 |
fixedHeader.all('.' + headerClass).each(function (header, i) { |
|
|
919 |
var liner = Y.Node.create(Y.Lang.sub(linerTemplate, { |
|
|
920 |
className: linerClass |
|
|
921 |
})), |
|
|
922 |
refHeader = headers.item(i); |
|
|
923 |
|
|
|
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')); |
|
|
931 |
|
|
|
932 |
liner.appendChild(header.get('childNodes').toFrag()); |
|
|
933 |
|
|
|
934 |
header.appendChild(liner); |
|
|
935 |
}, this); |
|
|
936 |
|
|
|
937 |
this._syncScrollColumnWidths(); |
|
|
938 |
|
|
|
939 |
this._addScrollbarPadding(); |
|
|
940 |
} |
|
|
941 |
}, |
|
|
942 |
|
|
|
943 |
/** |
|
|
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. |
|
|
947 |
|
|
|
948 |
@method _syncScrollUI |
|
|
949 |
@protected |
|
|
950 |
@since 3.5.0 |
|
|
951 |
**/ |
|
|
952 |
_syncScrollUI: function () { |
|
|
953 |
var x = this._xScroll, |
|
|
954 |
y = this._yScroll, |
|
|
955 |
xScroller = this._xScrollNode, |
|
|
956 |
yScroller = this._yScrollNode, |
|
|
957 |
scrollLeft = xScroller && xScroller.get('scrollLeft'), |
|
|
958 |
scrollTop = yScroller && yScroller.get('scrollTop'); |
|
|
959 |
|
|
|
960 |
this._uiSetScrollable(); |
|
|
961 |
|
|
|
962 |
// TODO: Probably should split this up into syncX, syncY, and syncXY |
|
|
963 |
if (x || y) { |
|
|
964 |
if ((this.get('width') || '').slice(-1) === '%') { |
|
|
965 |
this._bindScrollResize(); |
|
|
966 |
} else { |
|
|
967 |
this._unbindScrollResize(); |
|
|
968 |
} |
|
|
969 |
|
|
|
970 |
this._syncScrollCaptionUI(); |
|
|
971 |
} else { |
|
|
972 |
this._disableScrolling(); |
|
|
973 |
} |
|
|
974 |
|
|
|
975 |
if (this._yScrollHeader) { |
|
|
976 |
this._yScrollHeader.setStyle('display', 'none'); |
|
|
977 |
} |
|
|
978 |
|
|
|
979 |
if (x) { |
|
|
980 |
if (!y) { |
|
|
981 |
this._disableYScrolling(); |
|
|
982 |
} |
|
|
983 |
|
|
|
984 |
this._syncXScrollUI(y); |
|
|
985 |
} |
|
|
986 |
|
|
|
987 |
if (y) { |
|
|
988 |
if (!x) { |
|
|
989 |
this._disableXScrolling(); |
|
|
990 |
} |
|
|
991 |
|
|
|
992 |
this._syncYScrollUI(x); |
|
|
993 |
} |
|
|
994 |
|
|
|
995 |
// Restore scroll position |
|
|
996 |
if (scrollLeft && this._xScrollNode) { |
|
|
997 |
this._xScrollNode.set('scrollLeft', scrollLeft); |
|
|
998 |
} |
|
|
999 |
if (scrollTop && this._yScrollNode) { |
|
|
1000 |
this._yScrollNode.set('scrollTop', scrollTop); |
|
|
1001 |
} |
|
|
1002 |
}, |
|
|
1003 |
|
|
|
1004 |
/** |
|
|
1005 |
Wraps the table in a scrolling `<div>` of the configured width for "x" |
|
|
1006 |
scrolling. |
|
|
1007 |
|
|
|
1008 |
@method _syncXScrollUI |
|
|
1009 |
@param {Boolean} xy True if the table is configured with scrollable ="xy" |
|
|
1010 |
@protected |
|
|
1011 |
@since 3.5.0 |
|
|
1012 |
**/ |
|
|
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; |
|
|
1021 |
|
|
|
1022 |
if (!scroller) { |
|
|
1023 |
scroller = this._createXScrollNode(); |
|
|
1024 |
|
|
|
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); |
|
|
1028 |
} |
|
|
1029 |
|
|
|
1030 |
// Can't use offsetHeight - clientHeight because IE6 returns |
|
|
1031 |
// clientHeight of 0 intially. |
|
|
1032 |
borderWidth = styleDim(scroller, 'borderLeftWidth') + |
|
|
1033 |
styleDim(scroller, 'borderRightWidth'); |
|
|
1034 |
|
|
|
1035 |
scroller.setStyle('width', ''); |
|
|
1036 |
this._uiSetDim('width', ''); |
|
|
1037 |
if (xy && this._yScrollContainer) { |
|
|
1038 |
this._yScrollContainer.setStyle('width', ''); |
|
|
1039 |
} |
|
|
1040 |
|
|
|
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'); |
|
|
1049 |
} |
|
|
1050 |
table.setStyle('width', ''); |
|
|
1051 |
tableWidth = table.get('offsetWidth'); |
|
|
1052 |
table.setStyle('width', tableWidth + 'px'); |
|
|
1053 |
|
|
|
1054 |
this._uiSetDim('width', width); |
|
|
1055 |
|
|
|
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'); |
|
|
1059 |
|
|
|
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 |
|
|
1064 |
if (xy) { |
|
|
1065 |
table.setStyle('width', (scroller.get('offsetWidth') - |
|
|
1066 |
borderWidth - scrollbarWidth) + 'px'); |
|
|
1067 |
} else { |
|
|
1068 |
table.setStyle('width', '100%'); |
|
|
1069 |
} |
|
|
1070 |
} |
|
|
1071 |
}, |
|
|
1072 |
|
|
|
1073 |
/** |
|
|
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. |
|
|
1077 |
|
|
|
1078 |
@method _syncYScrollUI |
|
|
1079 |
@param {Boolean} xy True if the table is configured with scrollable = "xy" |
|
|
1080 |
@protected |
|
|
1081 |
@since 3.5.0 |
|
|
1082 |
**/ |
|
|
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(), |
|
|
1097 |
outerScroller; |
|
|
1098 |
|
|
|
1099 |
if (captionTable && !xy) { |
|
|
1100 |
captionTable.setStyle('width', width || '100%'); |
|
|
1101 |
} |
|
|
1102 |
|
|
|
1103 |
if (!yScroller) { |
|
|
1104 |
yScroller = this._createYScrollNode(); |
|
|
1105 |
|
|
|
1106 |
yScrollNode = this._yScrollNode; |
|
|
1107 |
|
|
|
1108 |
table.replace(yScroller).appendTo(yScrollNode); |
|
|
1109 |
} |
|
|
1110 |
|
|
|
1111 |
outerScroller = xy ? xScroller : yScroller; |
|
|
1112 |
|
|
|
1113 |
if (!xy) { |
|
|
1114 |
table.setStyle('width', ''); |
|
|
1115 |
} |
|
|
1116 |
|
|
|
1117 |
// Set the scroller height |
|
|
1118 |
if (xy) { |
|
|
1119 |
// Account for the horizontal scrollbar in the overall height |
|
|
1120 |
height -= scrollbarWidth; |
|
|
1121 |
} |
|
|
1122 |
|
|
|
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'); |
|
|
1128 |
|
|
|
1129 |
// Set the scroller width |
|
|
1130 |
if (xy) { |
|
|
1131 |
// For xy scrolling tables, the table should expand freely within |
|
|
1132 |
// the x scroller |
|
|
1133 |
yScroller.setStyle('width', |
|
|
1134 |
(table.get('offsetWidth') + scrollbarWidth) + 'px'); |
|
|
1135 |
} else { |
|
|
1136 |
this._uiSetYScrollWidth(width); |
|
|
1137 |
} |
|
|
1138 |
|
|
|
1139 |
if (captionTable && !xy) { |
|
|
1140 |
captionTable.setStyle('width', yScroller.get('offsetWidth') + 'px'); |
|
|
1141 |
} |
|
|
1142 |
|
|
|
1143 |
// Allow headerless scrolling |
|
|
1144 |
if (thead && !fixedHeader) { |
|
|
1145 |
fixedHeader = this._createYScrollHeader(); |
|
|
1146 |
|
|
|
1147 |
yScroller.prepend(fixedHeader); |
|
|
1148 |
|
|
|
1149 |
this._syncScrollHeaders(); |
|
|
1150 |
} |
|
|
1151 |
|
|
|
1152 |
if (fixedHeader) { |
|
|
1153 |
this._syncScrollColumnWidths(); |
|
|
1154 |
|
|
|
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'); |
|
|
1159 |
|
|
|
1160 |
if (!scrollbar) { |
|
|
1161 |
scrollbar = this._createScrollbar(); |
|
|
1162 |
|
|
|
1163 |
this._bindScrollbar(); |
|
|
1164 |
|
|
|
1165 |
contentBox.prepend(scrollbar); |
|
|
1166 |
} |
|
|
1167 |
|
|
|
1168 |
this._uiSetScrollbarHeight(); |
|
|
1169 |
this._uiSetScrollbarPosition(outerScroller); |
|
|
1170 |
} |
|
|
1171 |
}, |
|
|
1172 |
|
|
|
1173 |
/** |
|
|
1174 |
Assigns the appropriate class to the `boundingBox` to identify the DataTable |
|
|
1175 |
as horizontally scrolling, vertically scrolling, or both (adds both classes). |
|
|
1176 |
|
|
|
1177 |
Classes added are "yui3-datatable-scrollable-x" or "...-y" |
|
|
1178 |
|
|
|
1179 |
@method _uiSetScrollable |
|
|
1180 |
@protected |
|
|
1181 |
@since 3.5.0 |
|
|
1182 |
**/ |
|
|
1183 |
_uiSetScrollable: function () { |
|
|
1184 |
this.get('boundingBox') |
|
|
1185 |
.toggleClass(this.getClassName('scrollable','x'), this._xScroll) |
|
|
1186 |
.toggleClass(this.getClassName('scrollable','y'), this._yScroll); |
|
|
1187 |
}, |
|
|
1188 |
|
|
|
1189 |
/** |
|
|
1190 |
Updates the virtual scrollbar's height to avoid overlapping with the fixed |
|
|
1191 |
headers. |
|
|
1192 |
|
|
|
1193 |
@method _uiSetScrollbarHeight |
|
|
1194 |
@protected |
|
|
1195 |
@since 3.5.0 |
|
|
1196 |
**/ |
|
|
1197 |
_uiSetScrollbarHeight: function () { |
|
|
1198 |
var scrollbar = this._scrollbarNode, |
|
|
1199 |
scroller = this._yScrollNode, |
|
|
1200 |
fixedHeader = this._yScrollHeader; |
|
|
1201 |
|
|
|
1202 |
if (scrollbar && scroller && fixedHeader) { |
|
|
1203 |
scrollbar.get('firstChild').setStyle('height', |
|
|
1204 |
this._tbodyNode.get('scrollHeight') + 'px'); |
|
|
1205 |
|
|
|
1206 |
scrollbar.setStyle('height', |
|
|
1207 |
(parseFloat(scroller.getComputedStyle('height')) - |
|
|
1208 |
parseFloat(fixedHeader.getComputedStyle('height'))) + 'px'); |
|
|
1209 |
} |
|
|
1210 |
}, |
|
|
1211 |
|
|
|
1212 |
/** |
|
|
1213 |
Updates the virtual scrollbar's placement to avoid overlapping the fixed |
|
|
1214 |
headers or the data table. |
|
|
1215 |
|
|
|
1216 |
@method _uiSetScrollbarPosition |
|
|
1217 |
@param {Node} scroller Reference node to position the scrollbar over |
|
|
1218 |
@protected |
|
|
1219 |
@since 3.5.0 |
|
|
1220 |
**/ |
|
|
1221 |
_uiSetScrollbarPosition: function (scroller) { |
|
|
1222 |
var scrollbar = this._scrollbarNode, |
|
|
1223 |
fixedHeader = this._yScrollHeader; |
|
|
1224 |
|
|
|
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', |
|
|
1236 |
|
|
|
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' |
|
|
1243 |
}); |
|
|
1244 |
} |
|
|
1245 |
}, |
|
|
1246 |
|
|
|
1247 |
/** |
|
|
1248 |
Assigns the width of the `<div>` wrapping the data table in vertically |
|
|
1249 |
scrolling tables. |
|
|
1250 |
|
|
|
1251 |
If the table can't compress to the specified width, the container is |
|
|
1252 |
expanded accordingly. |
|
|
1253 |
|
|
|
1254 |
@method _uiSetYScrollWidth |
|
|
1255 |
@param {String} width The CSS width to attempt to set |
|
|
1256 |
@protected |
|
|
1257 |
@since 3.5.0 |
|
|
1258 |
**/ |
|
|
1259 |
_uiSetYScrollWidth: function (width) { |
|
|
1260 |
var scroller = this._yScrollContainer, |
|
|
1261 |
table = this._tableNode, |
|
|
1262 |
tableWidth, borderWidth, scrollerWidth, scrollbarWidth; |
|
|
1263 |
|
|
|
1264 |
if (scroller && table) { |
|
|
1265 |
scrollbarWidth = Y.DOM.getScrollbarWidth(); |
|
|
1266 |
|
|
|
1267 |
if (width) { |
|
|
1268 |
// Assumes no table border |
|
|
1269 |
borderWidth = scroller.get('offsetWidth') - |
|
|
1270 |
scroller.get('clientWidth') + |
|
|
1271 |
scrollbarWidth; // added back at the end |
|
|
1272 |
|
|
|
1273 |
// The table's rendered width might be greater than the |
|
|
1274 |
// configured width |
|
|
1275 |
scroller.setStyle('width', width); |
|
|
1276 |
|
|
|
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; |
|
|
1281 |
|
|
|
1282 |
// Assumes no table borders |
|
|
1283 |
table.setStyle('width', scrollerWidth + 'px'); |
|
|
1284 |
|
|
|
1285 |
tableWidth = table.get('offsetWidth'); |
|
|
1286 |
|
|
|
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'); |
|
|
1292 |
} else { |
|
|
1293 |
// Allow the table to expand naturally |
|
|
1294 |
table.setStyle('width', ''); |
|
|
1295 |
scroller.setStyle('width', ''); |
|
|
1296 |
|
|
|
1297 |
scroller.setStyle('width', |
|
|
1298 |
(table.get('offsetWidth') + scrollbarWidth) + 'px'); |
|
|
1299 |
} |
|
|
1300 |
} |
|
|
1301 |
}, |
|
|
1302 |
|
|
|
1303 |
/** |
|
|
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. |
|
|
1307 |
|
|
|
1308 |
@method _unbindScrollbar |
|
|
1309 |
@protected |
|
|
1310 |
@since 3.5.0 |
|
|
1311 |
**/ |
|
|
1312 |
_unbindScrollbar: function () { |
|
|
1313 |
if (this._scrollbarEventHandle) { |
|
|
1314 |
this._scrollbarEventHandle.detach(); |
|
|
1315 |
} |
|
|
1316 |
}, |
|
|
1317 |
|
|
|
1318 |
/** |
|
|
1319 |
Detaches the resize event subscription used to maintain column parity for |
|
|
1320 |
vertically scrolling tables with percentage widths. |
|
|
1321 |
|
|
|
1322 |
@method _unbindScrollResize |
|
|
1323 |
@protected |
|
|
1324 |
@since 3.5.0 |
|
|
1325 |
**/ |
|
|
1326 |
_unbindScrollResize: function () { |
|
|
1327 |
if (this._scrollResizeHandle) { |
|
|
1328 |
this._scrollResizeHandle.detach(); |
|
|
1329 |
delete this._scrollResizeHandle; |
|
|
1330 |
} |
|
|
1331 |
} |
|
|
1332 |
|
|
|
1333 |
/** |
|
|
1334 |
Indicates horizontal table scrolling is enabled. |
|
|
1335 |
|
|
|
1336 |
@property _xScroll |
|
|
1337 |
@type {Boolean} |
|
|
1338 |
@default undefined (not initially set) |
|
|
1339 |
@private |
|
|
1340 |
@since 3.5.0 |
|
|
1341 |
**/ |
|
|
1342 |
//_xScroll: null, |
|
|
1343 |
|
|
|
1344 |
/** |
|
|
1345 |
Indicates vertical table scrolling is enabled. |
|
|
1346 |
|
|
|
1347 |
@property _yScroll |
|
|
1348 |
@type {Boolean} |
|
|
1349 |
@default undefined (not initially set) |
|
|
1350 |
@private |
|
|
1351 |
@since 3.5.0 |
|
|
1352 |
**/ |
|
|
1353 |
//_yScroll: null, |
|
|
1354 |
|
|
|
1355 |
/** |
|
|
1356 |
Fixed column header `<table>` Node for vertical scrolling tables. |
|
|
1357 |
|
|
|
1358 |
@property _yScrollHeader |
|
|
1359 |
@type {Node} |
|
|
1360 |
@default undefined (not initially set) |
|
|
1361 |
@protected |
|
|
1362 |
@since 3.5.0 |
|
|
1363 |
**/ |
|
|
1364 |
//_yScrollHeader: null, |
|
|
1365 |
|
|
|
1366 |
/** |
|
|
1367 |
Overflow Node used to contain the data rows in a vertically scrolling table. |
|
|
1368 |
|
|
|
1369 |
@property _yScrollNode |
|
|
1370 |
@type {Node} |
|
|
1371 |
@default undefined (not initially set) |
|
|
1372 |
@protected |
|
|
1373 |
@since 3.5.0 |
|
|
1374 |
**/ |
|
|
1375 |
//_yScrollNode: null, |
|
|
1376 |
|
|
|
1377 |
/** |
|
|
1378 |
Overflow Node used to contain the table headers and data in a horizontally |
|
|
1379 |
scrolling table. |
|
|
1380 |
|
|
|
1381 |
@property _xScrollNode |
|
|
1382 |
@type {Node} |
|
|
1383 |
@default undefined (not initially set) |
|
|
1384 |
@protected |
|
|
1385 |
@since 3.5.0 |
|
|
1386 |
**/ |
|
|
1387 |
//_xScrollNode: null |
|
|
1388 |
}, true); |
|
|
1389 |
|
|
|
1390 |
Y.Base.mix(Y.DataTable, [Scrollable]); |
|
|
1391 |
|
|
|
1392 |
|
|
|
1393 |
}, '3.10.3', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true}); |