[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 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._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 {String} 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 {String} 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 {String} 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 {String} 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 {String} 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 {String} 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|Boolean} 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.17.2', {"requires": ["datatable-base", "datatable-column-widths", "dom-screen"], "skinnable": true});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |