~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/yuilib/3.13.0/datatable-scroll/datatable-scroll.js

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.13.0 (build 508226d)
 
3
Copyright 2013 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
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.13.0', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true});