[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/datatable-scroll/ -> datatable-scroll.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1