[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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]);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |