[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/grade/report/grader/yui/src/gradereporttable/js/ -> floatingheaders.js (source)

   1  // This file is part of Moodle - http://moodle.org/
   2  //
   3  // Moodle is free software: you can redistribute it and/or modify
   4  // it under the terms of the GNU General Public License as published by
   5  // the Free Software Foundation, either version 3 of the License, or
   6  // (at your option) any later version.
   7  //
   8  // Moodle is distributed in the hope that it will be useful,
   9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11  // GNU General Public License for more details.
  12  //
  13  // You should have received a copy of the GNU General Public License
  14  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  15  
  16  /**
  17   * @module moodle-gradereport_grader-gradereporttable
  18   * @submodule floatingheaders
  19   */
  20  
  21  /**
  22   * Provides floating headers to the grader report.
  23   *
  24   * See {{#crossLink "M.gradereport_grader.ReportTable"}}{{/crossLink}} for details.
  25   *
  26   * @namespace M.gradereport_grader
  27   * @class FloatingHeaders
  28   */
  29  
  30  var HEIGHT = 'height',
  31      WIDTH = 'width',
  32      OFFSETWIDTH = 'offsetWidth',
  33      OFFSETHEIGHT = 'offsetHeight';
  34  
  35  CSS.FLOATING = 'floating';
  36  
  37  function FloatingHeaders() {}
  38  
  39  FloatingHeaders.ATTRS= {
  40  };
  41  
  42  FloatingHeaders.prototype = {
  43      /**
  44       * The height of the page header if a fixed position, floating header
  45       * was found.
  46       *
  47       * @property pageHeaderHeight
  48       * @type Number
  49       * @default 0
  50       * @protected
  51       */
  52      pageHeaderHeight: 0,
  53  
  54      /**
  55       * A Node representing the container div.
  56       *
  57       * Positioning will be based on this element, which must have
  58       * the CSS rule 'position: relative'.
  59       *
  60       * @property container
  61       * @type Node
  62       * @protected
  63       */
  64      container: null,
  65  
  66      /**
  67       * A Node representing the header cell.
  68       *
  69       * @property headerCell
  70       * @type Node
  71       * @protected
  72       */
  73      headerCell: null,
  74  
  75      /**
  76       * A Node representing the header row.
  77       *
  78       * @property headerRow
  79       * @type Node
  80       * @protected
  81       */
  82      headerRow: null,
  83  
  84      /**
  85       * A Node representing the first cell which contains user name information.
  86       *
  87       * @property firstUserCell
  88       * @type Node
  89       * @protected
  90       */
  91      firstUserCell: null,
  92  
  93      /**
  94       * A Node representing the first cell which does not contain a user header.
  95       *
  96       * @property firstNonUserCell
  97       * @type Node
  98       * @protected
  99       */
 100      firstNonUserCell: null,
 101  
 102      /**
 103       * The position of the left of the first non-header cell in a row - the one after the email address.
 104       * This is used when processing the scroll event as an optimisation. It must be updated when
 105       * additional rows are loaded, or the window changes in some fashion.
 106       *
 107       * @property firstNonUserCellLeft
 108       * @type Number
 109       * @protected
 110       */
 111      firstNonUserCellLeft: 0,
 112  
 113      /**
 114       * The width of the first non-header cell in a row - the one after the email address.
 115       * This is used when processing the scroll event as an optimisation. It must be updated when
 116       * additional rows are loaded, or the window changes in some fashion.
 117       * This is only used for RTL calculations.
 118       *
 119       * @property firstNonUserCellWidth
 120       * @type Number
 121       * @protected
 122       */
 123      firstNonUserCellWidth: 0,
 124  
 125      /**
 126       * A Node representing the original table footer row.
 127       *
 128       * @property tableFooterRow
 129       * @type Node
 130       * @protected
 131       */
 132      tableFooterRow: null,
 133  
 134      /**
 135       * A Node representing the floating footer row in the grading table.
 136       *
 137       * @property footerRow
 138       * @type Node
 139       * @protected
 140       */
 141      footerRow: null,
 142  
 143      /**
 144       * A Node representing the floating grade item header.
 145       *
 146       * @property gradeItemHeadingContainer
 147       * @type Node
 148       * @protected
 149       */
 150      gradeItemHeadingContainer: null,
 151  
 152      /**
 153       * A Node representing the floating user header. This is the header with the Surname/First name
 154       * sorting.
 155       *
 156       * @property userColumnHeader
 157       * @type Node
 158       * @protected
 159       */
 160      userColumnHeader: null,
 161  
 162      /**
 163       * A Node representing the floating user column. This is the column containing all of the user
 164       * names.
 165       *
 166       * @property userColumn
 167       * @type Node
 168       * @protected
 169       */
 170      userColumn: null,
 171  
 172      /**
 173       * The position of the bottom of the first user cell.
 174       * This is used when processing the scroll event as an optimisation. It must be updated when
 175       * additional rows are loaded, or the window changes in some fashion.
 176       *
 177       * @property firstUserCellBottom
 178       * @type Number
 179       * @protected
 180       */
 181      firstUserCellBottom: 0,
 182  
 183      /**
 184       * The position of the left of the first user cell.
 185       * This is used when processing the scroll event as an optimisation. It must be updated when
 186       * additional rows are loaded, or the window changes in some fashion.
 187       *
 188       * @property firstUserCellLeft
 189       * @type Number
 190       * @protected
 191       */
 192      firstUserCellLeft: 0,
 193  
 194      /**
 195       * The width of the first user cell.
 196       * This is used when processing the scroll event as an optimisation. It must be updated when
 197       * additional rows are loaded, or the window changes in some fashion.
 198       * This is only used for RTL calculations.
 199       *
 200       * @property firstUserCellWidth
 201       * @type Number
 202       * @protected
 203       */
 204      firstUserCellWidth: 0,
 205  
 206      /**
 207       * The width of the dock if it is visible.
 208       *
 209       * @property dockWidth
 210       * @type Number
 211       * @protected
 212       */
 213      dockWidth: 0,
 214  
 215      /**
 216       * The position of the top of the final user cell.
 217       * This is used when processing the scroll event as an optimisation. It must be updated when
 218       * additional rows are loaded, or the window changes in some fashion.
 219       *
 220       * @property lastUserCellTop
 221       * @type Number
 222       * @protected
 223       */
 224      lastUserCellTop: 0,
 225  
 226      /**
 227       * A list of Nodes representing the generic floating rows.
 228       *
 229       * @property floatingHeaderRow
 230       * @type Node{}
 231       * @protected
 232       */
 233      floatingHeaderRow: null,
 234  
 235      /**
 236       * Array of EventHandles.
 237       *
 238       * @type EventHandle[]
 239       * @property _eventHandles
 240       * @protected
 241       */
 242      _eventHandles: [],
 243  
 244      /**
 245       * Setup the grader report table.
 246       *
 247       * @method setupFloatingHeaders
 248       * @chainable
 249       */
 250      setupFloatingHeaders: function() {
 251          // Grab references to commonly used Nodes.
 252          this.firstUserCell = Y.one(SELECTORS.USERCELL);
 253          this.container = Y.one(SELECTORS.GRADEPARENT);
 254          this.firstNonUserCell = Y.one(SELECTORS.USERMAIL).next();
 255  
 256          if (!this.firstUserCell) {
 257              // No need for floating elements, there are no users.
 258              return this;
 259          }
 260  
 261          // Generate floating elements.
 262          this._setupFloatingUserColumn();
 263          this._setupFloatingUserHeader();
 264          this._setupFloatingAssignmentHeaders();
 265          this._setupFloatingAssignmentFooter();
 266  
 267          // Setup generic floating left-aligned headers.
 268          this.floatingHeaderRow = {};
 269  
 270          // The 'Controls' row (shown in editing mode when certain options are set).
 271          this._setupFloatingLeftHeaders('.controls .controls');
 272  
 273          // The 'Range' row (shown in editing mode when certain options are set).
 274          this._setupFloatingLeftHeaders('.range .range');
 275  
 276          // The 'Overall Average' field.
 277          this._setupFloatingLeftHeaders(SELECTORS.FOOTERTITLE);
 278  
 279          // Additional setup for the footertitle.
 280          this._setupFloatingAssignmentFooterTitle();
 281  
 282          // Calculate the positions of edge cells. These are used for positioning of the floating headers.
 283          // This must be called after the floating headers are setup, but before the scroll event handler is invoked.
 284          this._calculateCellPositions();
 285  
 286          // Setup the floating element initial positions by simulating scroll.
 287          this._handleScrollEvent();
 288  
 289          // Setup the event handlers.
 290          this._setupEventHandlers();
 291  
 292          // Listen for a resize event globally - other parts of the code not in this YUI wrapper may make changes to the
 293          // fields which result in size changes.
 294          Y.Global.on('moodle-gradereport_grader:resized', this._handleResizeEvent, this);
 295  
 296          return this;
 297      },
 298  
 299      /**
 300       * Calculate the positions of some cells. These values are used heavily
 301       * in scroll event handling.
 302       *
 303       * @method _calculateCellPositions
 304       * @protected
 305       */
 306      _calculateCellPositions: function() {
 307          // The header row shows the grade item headers and is floated to the top of the window.
 308          this.headerRowTop = this.headerRow.getY();
 309  
 310          // The footer row shows the grade averages and will be floated to the page bottom.
 311          if (this.tableFooterRow) {
 312              this.footerRowPosition = this.tableFooterRow.getY();
 313          }
 314  
 315          // Add the width of the dock if it is visible.
 316          this.dockWidth = 0;
 317          var dock = Y.one('.has_dock #dock');
 318          if (dock) {
 319              this.dockWidth = dock.get(OFFSETWIDTH);
 320          }
 321  
 322          var userCellList = Y.all(SELECTORS.USERCELL);
 323  
 324          // The left of the user cells matches the left of the headerRow.
 325          this.firstUserCellLeft = this.firstUserCell.getX();
 326          this.firstUserCellWidth = this.firstUserCell.get(OFFSETWIDTH);
 327  
 328          // The left of the user cells matches the left of the footer title.
 329          this.firstNonUserCellLeft = this.firstNonUserCell.getX();
 330          this.firstNonUserCellWidth = this.firstNonUserCell.get(OFFSETWIDTH);
 331  
 332          if (userCellList.size() > 1) {
 333              // Use the top of the second cell for the bottom of the first cell.
 334              // This is used when scrolling to fix the footer to the top edge of the window.
 335              var firstUserCell = userCellList.item(1);
 336              this.firstUserCellBottom = firstUserCell.getY() + parseInt(firstUserCell.getComputedStyle(HEIGHT), 10);
 337  
 338              // Use the top of the penultimate cell when scrolling the header.
 339              // The header is the same size as the cells.
 340              this.lastUserCellTop = userCellList.item(userCellList.size() - 2).getY();
 341          } else {
 342              var firstItem = userCellList.item(0);
 343              // We can't use the top of the second row as there is only one row.
 344              this.lastUserCellTop = firstItem.getY();
 345  
 346              if (this.tableFooterRow) {
 347                  // The footer is present so we can use that.
 348                  this.firstUserCellBottom = this.footerRowPosition + parseInt(this.tableFooterRow.getComputedStyle(HEIGHT), 10);
 349              } else {
 350                  // No other clues - calculate the top instead.
 351                  this.firstUserCellBottom = firstItem.getY() + firstItem.get('offsetHeight');
 352              }
 353          }
 354  
 355          // Check whether a header is present and whether it is floating.
 356          var header = Y.one('header');
 357          this.pageHeaderHeight = 0;
 358          if (header) {
 359              if (header.getComputedStyle('position') === 'fixed') {
 360                  this.pageHeaderHeight = header.get(OFFSETHEIGHT);
 361              } else {
 362                  var navbar = Y.one('.navbar');
 363  
 364                  if (navbar && navbar.getComputedStyle('position') === 'fixed') {
 365                      // If the navbar exists and isn't fixed, we need to offset the page header to accommodate for it.
 366                      this.pageHeaderHeight = navbar.get(OFFSETHEIGHT);
 367                  }
 368              }
 369          }
 370      },
 371  
 372      /**
 373       * Get the relative XY of the node.
 374       *
 375       * @method _getRelativeXY
 376       * @protected
 377       * @param {Node} node The node to get the position of.
 378       * @return {Array} Containing X and Y.
 379       */
 380      _getRelativeXY: function(node) {
 381          return this._getRelativeXYFromXY(node.getX(), node.getY());
 382      },
 383  
 384      /**
 385       * Get the relative positioning from coordinates.
 386       *
 387       * This gives the position according to the parent of the table, which must
 388       * be set as position: relative.
 389       *
 390       * @method _getRelativeXYFromXY
 391       * @protected
 392       * @param {Number} x X position.
 393       * @param {Number} y Y position.
 394       * @return {Array} Containing X and Y.
 395       */
 396      _getRelativeXYFromXY: function(x, y) {
 397          var parentXY = this.container.getXY();
 398          return [x - parentXY[0], y - parentXY[1]];
 399      },
 400  
 401      /**
 402       * Get the relative positioning of an elements from coordinates.
 403       *
 404       * @method _getRelativeXFromX
 405       * @protected
 406       * @param {Number} pos X position.
 407       * @return {Number} relative X position.
 408       */
 409      _getRelativeXFromX: function(pos) {
 410          return this._getRelativeXYFromXY(pos, 0)[0];
 411      },
 412  
 413      /**
 414       * Get the relative positioning of an elements from coordinates.
 415       *
 416       * @method _getRelativeYFromY
 417       * @protected
 418       * @param {Number} pos Y position.
 419       * @return {Number} relative Y position.
 420       */
 421      _getRelativeYFromY: function(pos) {
 422          return this._getRelativeXYFromXY(0, pos)[1];
 423      },
 424  
 425      /**
 426       * Return the size of the horizontal scrollbar.
 427       *
 428       * @method _getScrollBarHeight
 429       * @protected
 430       * @return {Number} Height of the scrollbar.
 431       */
 432      _getScrollBarHeight: function() {
 433          if (Y.UA.ie && Y.UA.ie >= 10) {
 434              // IE has transparent scrollbars, which sometimes disappear... it's better to ignore them.
 435              return 0;
 436          } else if (Y.config.doc.body.scrollWidth > Y.config.doc.body.clientWidth) {
 437              // The document can be horizontally scrolled.
 438              return Y.DOM.getScrollbarWidth();
 439          }
 440          return 0;
 441      },
 442  
 443      /**
 444       * Setup the main event listeners.
 445       * These deal with things like window events.
 446       *
 447       * @method _setupEventHandlers
 448       * @protected
 449       */
 450      _setupEventHandlers: function() {
 451          this._eventHandles.push(
 452              // Listen for window scrolls, resizes, and rotation events.
 453              Y.one(Y.config.win).on('scroll', this._handleScrollEvent, this),
 454              Y.one(Y.config.win).on('resize', this._handleResizeEvent, this),
 455              Y.one(Y.config.win).on('orientationchange', this._handleResizeEvent, this),
 456              Y.Global.on('dock:shown', this._handleResizeEvent, this),
 457              Y.Global.on('dock:hidden', this._handleResizeEvent, this)
 458          );
 459      },
 460  
 461      /**
 462       * Create and setup the floating column of user names.
 463       *
 464       * @method _setupFloatingUserColumn
 465       * @protected
 466       */
 467      _setupFloatingUserColumn: function() {
 468          // Grab all cells in the user names column.
 469          var userColumn = Y.all(SELECTORS.USERCELL),
 470  
 471          // Create a floating table.
 472              floatingUserColumn = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly"></div>'),
 473  
 474          // Get the XY for the floating element.
 475              coordinates = this._getRelativeXY(this.firstUserCell);
 476  
 477          // Generate the new fields.
 478          userColumn.each(function(node) {
 479              // Create and configure the new container.
 480              var containerNode = Y.Node.create('<div></div>');
 481              containerNode.set('innerHTML', node.get('innerHTML'))
 482                      .setAttribute('class', node.getAttribute('class'))
 483                      .setAttribute('data-uid', node.ancestor('tr').getData('uid'))
 484                      .setStyles({
 485                          height: node.getComputedStyle(HEIGHT),
 486                          width:  node.getComputedStyle(WIDTH)
 487                      });
 488  
 489              // Add the new nodes to our floating table.
 490              floatingUserColumn.appendChild(containerNode);
 491          }, this);
 492  
 493          // Style the floating user container.
 494          floatingUserColumn.setStyles({
 495              left:       coordinates[0] + 'px',
 496              position:   'absolute',
 497              top:        coordinates[1] + 'px'
 498          });
 499  
 500          // Append to the grader region.
 501          this.graderRegion.append(floatingUserColumn);
 502  
 503          // Store a reference to this for later - we use it in the event handlers.
 504          this.userColumn = floatingUserColumn;
 505      },
 506  
 507      /**
 508       * Create and setup the floating username header cell.
 509       *
 510       * @method _setupFloatingUserHeader
 511       * @protected
 512       */
 513      _setupFloatingUserHeader: function() {
 514          // We make various references to the header cells. Store it for later.
 515          this.headerRow = Y.one(SELECTORS.HEADERROW);
 516          this.headerCell = Y.one(SELECTORS.STUDENTHEADER);
 517  
 518          // Create the floating row and cell.
 519          var floatingUserHeaderRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly heading"></div>'),
 520              floatingUserHeaderCell = Y.Node.create('<div></div>'),
 521              nodepos = this._getRelativeXY(this.headerCell)[0],
 522              coordinates = this._getRelativeXY(this.headerRow),
 523              gradeHeadersOffset = coordinates[0];
 524  
 525          // Append the content and style to the floating cell.
 526          floatingUserHeaderCell
 527              .set('innerHTML', this.headerCell.getHTML())
 528              .setAttribute('class', this.headerCell.getAttribute('class'))
 529              .setStyles({
 530                  // The header is larger than the user cells, so we take the user cell.
 531                  width:      this.firstUserCell.getComputedStyle(WIDTH),
 532                  left:       (nodepos - gradeHeadersOffset) + 'px'
 533              });
 534  
 535          // Style the floating row.
 536          floatingUserHeaderRow
 537              .setStyles({
 538                  left:       coordinates[0] + 'px',
 539                  position:   'absolute',
 540                  top:        coordinates[1] + 'px'
 541              });
 542  
 543          // Append the cell to the row, and finally to the region.
 544          floatingUserHeaderRow.append(floatingUserHeaderCell);
 545          this.graderRegion.append(floatingUserHeaderRow);
 546  
 547          // Store a reference to this for later - we use it in the event handlers.
 548          this.userColumnHeader = floatingUserHeaderRow;
 549      },
 550  
 551      /**
 552       * Create and setup the floating grade item header row.
 553       *
 554       * @method _setupFloatingAssignmentHeaders
 555       * @protected
 556       */
 557      _setupFloatingAssignmentHeaders: function() {
 558          this.headerRow = Y.one('#user-grades tr.heading');
 559  
 560          var gradeHeaders = Y.all('#user-grades tr.heading .cell');
 561  
 562          // Generate a floating headers
 563          var floatingGradeHeaders = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater heading"></div>');
 564  
 565          var coordinates = this._getRelativeXY(this.headerRow);
 566  
 567          var floatingGradeHeadersWidth = 0;
 568          var floatingGradeHeadersHeight = 0;
 569          var gradeHeadersOffset = coordinates[0];
 570  
 571          gradeHeaders.each(function(node) {
 572              var nodepos = this._getRelativeXY(node)[0];
 573  
 574              var newnode = Y.Node.create('<div></div>');
 575              newnode.append(node.getHTML())
 576                  .setAttribute('class', node.getAttribute('class'))
 577                  .setData('itemid', node.getData('itemid'))
 578                  .setStyles({
 579                      height:     node.getComputedStyle(HEIGHT),
 580                      left:       (nodepos - gradeHeadersOffset) + 'px',
 581                      position:   'absolute',
 582                      width:      node.getComputedStyle(WIDTH)
 583                  });
 584  
 585              // Sum up total widths - these are used in the container styles.
 586              // Use the offsetHeight and Width here as this contains the
 587              // padding, margin, and borders.
 588              floatingGradeHeadersWidth += parseInt(node.get(OFFSETWIDTH), 10);
 589              floatingGradeHeadersHeight = node.get(OFFSETHEIGHT);
 590  
 591              // Append to our floating table.
 592              floatingGradeHeaders.appendChild(newnode);
 593          }, this);
 594  
 595          // Position header table.
 596          floatingGradeHeaders.setStyles({
 597              height:     floatingGradeHeadersHeight + 'px',
 598              left:       coordinates[0] + 'px',
 599              position:   'absolute',
 600              top:        coordinates[1] + 'px',
 601              width:      floatingGradeHeadersWidth + 'px'
 602          });
 603  
 604          // Insert in place before the grader headers.
 605          this.userColumnHeader.insert(floatingGradeHeaders, 'before');
 606  
 607          // Store a reference to this for later - we use it in the event handlers.
 608          this.gradeItemHeadingContainer = floatingGradeHeaders;
 609      },
 610  
 611      /**
 612       * Create and setup the floating header row of grade item titles.
 613       *
 614       * @method _setupFloatingAssignmentFooter
 615       * @protected
 616       */
 617      _setupFloatingAssignmentFooter: function() {
 618          this.tableFooterRow = Y.one('#user-grades .avg');
 619          if (!this.tableFooterRow) {
 620              Y.log('Averages footer not found - unable to float it.', 'warn', LOGNS);
 621              return;
 622          }
 623  
 624          // Generate the sticky footer row.
 625          var footerCells = this.tableFooterRow.all('.cell');
 626  
 627          // Create a container.
 628          var floatingGraderFooter = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater avg"></div>');
 629          var footerWidth = 0;
 630          var coordinates = this._getRelativeXY(this.tableFooterRow);
 631          var footerRowOffset = coordinates[0];
 632          var floatingGraderFooterHeight = 0;
 633  
 634          // Copy cell content.
 635          footerCells.each(function(node) {
 636              var newnode = Y.Node.create('<div></div>');
 637              var nodepos = this._getRelativeXY(node)[0];
 638              newnode.set('innerHTML', node.getHTML())
 639                  .setAttribute('class', node.getAttribute('class'))
 640                  .setStyles({
 641                      height:     node.getComputedStyle(HEIGHT),
 642                      left:       (nodepos - footerRowOffset) + 'px',
 643                      position:   'absolute',
 644                      width:      node.getComputedStyle(WIDTH)
 645                  });
 646  
 647              floatingGraderFooter.append(newnode);
 648              floatingGraderFooterHeight = node.get(OFFSETHEIGHT);
 649              footerWidth += parseInt(node.get(OFFSETWIDTH), 10);
 650          }, this);
 651  
 652          // Position the row.
 653          floatingGraderFooter.setStyles({
 654              position:   'absolute',
 655              left:       coordinates[0] + 'px',
 656              bottom:     '1px',
 657              height:     floatingGraderFooterHeight + 'px',
 658              width:      footerWidth + 'px'
 659          });
 660  
 661          // Append to the grader region.
 662          this.graderRegion.append(floatingGraderFooter);
 663  
 664          this.footerRow = floatingGraderFooter;
 665      },
 666  
 667      /**
 668       * Create and setup the floating footer title cell.
 669       *
 670       * @method _setupFloatingAssignmentFooterTitle
 671       * @protected
 672       */
 673      _setupFloatingAssignmentFooterTitle: function() {
 674          var floatingFooterRow = this.floatingHeaderRow[SELECTORS.FOOTERTITLE];
 675          if (floatingFooterRow) {
 676              // Style the floating row.
 677              floatingFooterRow
 678                  .setStyles({
 679                      bottom:     '1px'
 680                  });
 681          }
 682      },
 683  
 684      /**
 685       * Create and setup the floating left headers.
 686       *
 687       * @method _setupFloatingLeftHeaders
 688       * @protected
 689       */
 690      _setupFloatingLeftHeaders: function(headerSelector) {
 691          // We make various references to the origin cell. Store it for later.
 692          var origin = Y.one(headerSelector);
 693  
 694          if (!origin) {
 695              return;
 696          }
 697  
 698          // Create the floating row and cell.
 699          var floatingRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly"></div>'),
 700              floatingCell = Y.Node.create('<div></div>'),
 701              coordinates = this._getRelativeXY(origin),
 702              width = this.firstUserCell.getComputedStyle(WIDTH),
 703              height = origin.get(OFFSETHEIGHT);
 704  
 705          // Append the content and style to the floating cell.
 706          floatingCell
 707              .set('innerHTML', origin.getHTML())
 708              .setAttribute('class', origin.getAttribute('class'))
 709              .setStyles({
 710                  // The header is larger than the user cells, so we take the user cell.
 711                  width:      width
 712              });
 713  
 714          // Style the floating row.
 715          floatingRow
 716              .setStyles({
 717                  position:   'absolute',
 718                  top:        coordinates[1] + 'px',
 719                  left:       coordinates[0] + 'px',
 720                  height:     height + 'px'
 721              })
 722              // Add all classes from the parent to the row
 723              .addClass(origin.get('parentNode').get('className'));
 724  
 725          // Append the cell to the row, and finally to the region.
 726          floatingRow.append(floatingCell);
 727          this.graderRegion.append(floatingRow);
 728  
 729          // Store a reference to this for later - we use it in the event handlers.
 730          this.floatingHeaderRow[headerSelector] = floatingRow;
 731      },
 732  
 733      /**
 734       * Process a Scroll Event on the window.
 735       *
 736       * @method _handleScrollEvent
 737       * @protected
 738       */
 739      _handleScrollEvent: function() {
 740          // Performance is important in this function as it is called frequently and in quick succesion.
 741          // To prevent layout thrashing when the DOM is repeatedly updated and queried, updated and queried,
 742          // updates must be batched.
 743  
 744          // Next do all the calculations.
 745          var gradeItemHeadingContainerStyles = {},
 746              userColumnHeaderStyles = {},
 747              userColumnStyles = {},
 748              footerStyles = {},
 749              coord = 0,
 750              floatingUserTriggerPoint = 0,       // The X position at which the floating should start.
 751              floatingUserRelativePoint = 0,      // The point to use when calculating the new position.
 752              headerFloats = false,
 753              userFloats = false,
 754              footerFloats = false,
 755              leftTitleFloats = false,
 756              floatingHeaderStyles = {},
 757              floatingFooterTitleStyles = {},
 758              floatingFooterTitleRow = false;
 759  
 760          // Header position.
 761          gradeItemHeadingContainerStyles.left = this._getRelativeXFromX(this.headerRow.getX());
 762          if (Y.config.win.pageYOffset + this.pageHeaderHeight > this.headerRowTop) {
 763              headerFloats = true;
 764              if (Y.config.win.pageYOffset + this.pageHeaderHeight < this.lastUserCellTop) {
 765                  coord = this._getRelativeYFromY(Y.config.win.pageYOffset + this.pageHeaderHeight);
 766                  gradeItemHeadingContainerStyles.top = coord + 'px';
 767                  userColumnHeaderStyles.top = coord + 'px';
 768              } else {
 769                  coord = this._getRelativeYFromY(this.lastUserCellTop);
 770                  gradeItemHeadingContainerStyles.top = coord + 'px';
 771                  userColumnHeaderStyles.top = coord + 'px';
 772              }
 773          } else {
 774              headerFloats = false;
 775              coord = this._getRelativeYFromY(this.headerRowTop);
 776              gradeItemHeadingContainerStyles.top = coord + 'px';
 777              userColumnHeaderStyles.top = coord + 'px';
 778          }
 779  
 780          // User column position.
 781          if (right_to_left()) {
 782              floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset - this.dockWidth;
 783              floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth;
 784              userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth);
 785              leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) <
 786                                (this.firstNonUserCellLeft + this.firstUserCellWidth);
 787          } else {
 788              floatingUserRelativePoint = Y.config.win.pageXOffset;
 789              floatingUserTriggerPoint = floatingUserRelativePoint + this.dockWidth;
 790              userFloats = floatingUserTriggerPoint > this.firstUserCellLeft;
 791              leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth);
 792          }
 793  
 794          if (userFloats) {
 795              coord = this._getRelativeXFromX(floatingUserRelativePoint);
 796              userColumnStyles.left = coord + 'px';
 797              userColumnHeaderStyles.left = coord + 'px';
 798          } else {
 799              coord = this._getRelativeXFromX(this.firstUserCellLeft);
 800              userColumnStyles.left = coord + 'px';
 801              userColumnHeaderStyles.left = coord + 'px';
 802          }
 803  
 804          // Update the miscellaneous left-only floats.
 805          Y.Object.each(this.floatingHeaderRow, function(origin, key) {
 806              floatingHeaderStyles[key] = {
 807                  left: userColumnStyles.left
 808              };
 809          }, this);
 810  
 811          // Update footer.
 812          if (this.footerRow) {
 813              footerStyles.left = this._getRelativeXFromX(this.headerRow.getX());
 814  
 815              // Determine whether the footer should now be shown as sticky.
 816              var pageHeight = Y.config.win.innerHeight,
 817                  pageOffset = Y.config.win.pageYOffset,
 818                  bottomScrollPosition = pageHeight - this._getScrollBarHeight() + pageOffset,
 819                  footerRowHeight = parseInt(this.footerRow.getComputedStyle(HEIGHT), 10),
 820                  footerBottomPosition = footerRowHeight + this.footerRowPosition;
 821  
 822              floatingFooterTitleStyles = floatingHeaderStyles[SELECTORS.FOOTERTITLE];
 823              floatingFooterTitleRow = this.floatingHeaderRow[SELECTORS.FOOTERTITLE];
 824              if (bottomScrollPosition < footerBottomPosition && bottomScrollPosition > this.firstUserCellBottom) {
 825                  // We have not scrolled below the footer, nor above the first row.
 826                  footerStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px';
 827                  footerFloats = true;
 828              } else {
 829                  // The footer should not float any more.
 830                  footerStyles.bottom = '1px';
 831                  footerFloats = false;
 832              }
 833              if (floatingFooterTitleStyles) {
 834                  floatingFooterTitleStyles.bottom = footerStyles.bottom;
 835                  floatingFooterTitleStyles.top = null;
 836              }
 837              floatingHeaderStyles[SELECTORS.FOOTERTITLE] = floatingFooterTitleStyles;
 838          }
 839  
 840          // Apply the styles.
 841          this.gradeItemHeadingContainer.setStyles(gradeItemHeadingContainerStyles);
 842          this.userColumnHeader.setStyles(userColumnHeaderStyles);
 843          this.userColumn.setStyles(userColumnStyles);
 844          this.footerRow.setStyles(footerStyles);
 845  
 846          // And apply the styles to the generic left headers.
 847          Y.Object.each(floatingHeaderStyles, function(styles, key) {
 848              this.floatingHeaderRow[key].setStyles(styles);
 849          }, this);
 850  
 851          // Mark the elements as floating, or not.
 852          if (headerFloats) {
 853              this.gradeItemHeadingContainer.addClass(CSS.FLOATING);
 854          } else {
 855              this.gradeItemHeadingContainer.removeClass(CSS.FLOATING);
 856          }
 857  
 858          if (userFloats) {
 859              this.userColumnHeader.addClass(CSS.FLOATING);
 860              this.userColumn.addClass(CSS.FLOATING);
 861          } else {
 862              this.userColumnHeader.removeClass(CSS.FLOATING);
 863              this.userColumn.removeClass(CSS.FLOATING);
 864          }
 865  
 866          if (footerFloats) {
 867              this.footerRow.addClass(CSS.FLOATING);
 868          } else {
 869              this.footerRow.removeClass(CSS.FLOATING);
 870          }
 871  
 872          Y.Object.each(this.floatingHeaderRow, function(value, key) {
 873              if (leftTitleFloats) {
 874                  this.floatingHeaderRow[key].addClass(CSS.FLOATING);
 875              } else {
 876                  this.floatingHeaderRow[key].removeClass(CSS.FLOATING);
 877              }
 878          }, this);
 879  
 880          // The footer title has a more specific float setting.
 881          if (floatingFooterTitleRow) {
 882              if (leftTitleFloats) {
 883                  floatingFooterTitleRow.addClass(CSS.FLOATING);
 884              } else {
 885                  floatingFooterTitleRow.removeClass(CSS.FLOATING);
 886              }
 887          }
 888  
 889      },
 890  
 891      /**
 892       * Process a size change Event on the window.
 893       *
 894       * @method _handleResizeEvent
 895       * @protected
 896       */
 897      _handleResizeEvent: function() {
 898          // Recalculate the position of the edge cells for scroll positioning.
 899          this._calculateCellPositions();
 900  
 901          // Simulate a scroll.
 902          this._handleScrollEvent();
 903  
 904          // Resize user cells.
 905          var userWidth = this.firstUserCell.getComputedStyle(WIDTH);
 906          var userCells = Y.all(SELECTORS.USERCELL);
 907          this.userColumnHeader.one('.cell').setStyle('width', userWidth);
 908          this.userColumn.all('.cell').each(function(cell, idx) {
 909              cell.setStyles({
 910                  width: userWidth,
 911                  height: userCells.item(idx).getComputedStyle(HEIGHT)
 912              });
 913          }, this);
 914  
 915          // Resize headers & footers.
 916          // This is an expensive operation, not expected to happen often.
 917          var headers = this.gradeItemHeadingContainer.all('.cell');
 918          var resizedcells = Y.all(SELECTORS.HEADERCELLS);
 919  
 920          var headeroffsetleft = this.headerRow.getX();
 921          var newcontainerwidth = 0;
 922          resizedcells.each(function(cell, idx) {
 923              var headercell = headers.item(idx);
 924  
 925              newcontainerwidth += cell.get(OFFSETWIDTH);
 926              var styles = {
 927                  width: cell.getComputedStyle(WIDTH),
 928                  left: cell.getX() - headeroffsetleft + 'px'
 929              };
 930              headercell.setStyles(styles);
 931          });
 932  
 933          if (this.footerRow) {
 934              var footers = this.footerRow.all('.cell');
 935              if (footers.size() !== 0) {
 936                  var resizedavgcells = Y.all(SELECTORS.FOOTERCELLS);
 937  
 938                  resizedavgcells.each(function(cell, idx) {
 939                      var footercell = footers.item(idx);
 940                      var styles = {
 941                          width: cell.getComputedStyle(WIDTH),
 942                          left: cell.getX() - headeroffsetleft + 'px'
 943                      };
 944                      footercell.setStyles(styles);
 945                  });
 946              }
 947          }
 948  
 949          // Resize the title areas too.
 950          Y.Object.each(this.floatingHeaderRow, function(row) {
 951              row.one('div').setStyle('width', userWidth);
 952          }, this);
 953  
 954          this.gradeItemHeadingContainer.setStyle('width', newcontainerwidth);
 955      }
 956  
 957  };
 958  
 959  Y.Base.mix(Y.M.gradereport_grader.ReportTable, [FloatingHeaders]);


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1