[ Index ]

PHP Cross Reference of Unnamed Project

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


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