// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
/* global SELECTORS */
/**
* @module moodle-gradereport_grader-gradereporttable
* @submodule floatingheaders
*/
/**
* Provides floating headers to the grader report.
*
* See {{#crossLink "M.gradereport_grader.ReportTable"}}{{/crossLink}} for details.
*
* @namespace M.gradereport_grader
* @class FloatingHeaders
*/
var HEIGHT = 'height',
WIDTH = 'width',
OFFSETWIDTH = 'offsetWidth',
OFFSETHEIGHT = 'offsetHeight',
LOGNS = 'moodle-core-grade-report-grader';
CSS.FLOATING = 'floating';
function FloatingHeaders() {}
FloatingHeaders.ATTRS = {
};
FloatingHeaders.prototype = {
/**
* The height of the page header if a fixed position, floating header
* was found.
*
* @property pageHeaderHeight
* @type Number
* @default 0
* @protected
*/
pageHeaderHeight: 0,
/**
* A Node representing the container div.
*
* Positioning will be based on this element, which must have
* the CSS rule 'position: relative'.
*
* @property container
* @type Node
* @protected
*/
container: null,
/**
* A Node representing the header cell.
*
* @property headerCell
* @type Node
* @protected
*/
headerCell: null,
/**
* A Node representing the header row.
*
* @property headerRow
* @type Node
* @protected
*/
headerRow: null,
/**
* A Node representing the first cell which contains user name information.
*
* @property firstUserCell
* @type Node
* @protected
*/
firstUserCell: null,
/**
* A Node representing the first cell which does not contain a user header.
*
* @property firstNonUserCell
* @type Node
* @protected
*/
firstNonUserCell: null,
/**
* The position of the left of the first non-header cell in a row - the one after the email address.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
*
* @property firstNonUserCellLeft
* @type Number
* @protected
*/
firstNonUserCellLeft: 0,
/**
* The width of the first non-header cell in a row - the one after the email address.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
* This is only used for RTL calculations.
*
* @property firstNonUserCellWidth
* @type Number
* @protected
*/
firstNonUserCellWidth: 0,
/**
* A Node representing the original table footer row.
*
* @property tableFooterRow
* @type Node
* @protected
*/
tableFooterRow: null,
/**
* A Node representing the floating footer row in the grading table.
*
* @property footerRow
* @type Node
* @protected
*/
footerRow: null,
/**
* A Node representing the floating grade item header.
*
* @property gradeItemHeadingContainer
* @type Node
* @protected
*/
gradeItemHeadingContainer: null,
/**
* A Node representing the floating user header. This is the header with the Surname/First name
* sorting.
*
* @property userColumnHeader
* @type Node
* @protected
*/
userColumnHeader: null,
/**
* A Node representing the floating user column. This is the column containing all of the user
* names.
*
* @property userColumn
* @type Node
* @protected
*/
userColumn: null,
/**
* The position of the bottom of the first user cell.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
*
* @property firstUserCellBottom
* @type Number
* @protected
*/
firstUserCellBottom: 0,
/**
* The position of the left of the first user cell.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
*
* @property firstUserCellLeft
* @type Number
* @protected
*/
firstUserCellLeft: 0,
/**
* The width of the first user cell.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
* This is only used for RTL calculations.
*
* @property firstUserCellWidth
* @type Number
* @protected
*/
firstUserCellWidth: 0,
/**
* The width of the dock if it is visible.
*
* @property dockWidth
* @type Number
* @protected
*/
dockWidth: 0,
/**
* The position of the top of the final user cell.
* This is used when processing the scroll event as an optimisation. It must be updated when
* additional rows are loaded, or the window changes in some fashion.
*
* @property lastUserCellTop
* @type Number
* @protected
*/
lastUserCellTop: 0,
/**
* A list of Nodes representing the generic floating rows.
*
* @property floatingHeaderRow
* @type Node{}
* @protected
*/
floatingHeaderRow: null,
/**
* Array of EventHandles.
*
* @type EventHandle[]
* @property _eventHandles
* @protected
*/
_eventHandles: [],
/**
* Setup the grader report table.
*
* @method setupFloatingHeaders
* @chainable
*/
setupFloatingHeaders: function() {
// Grab references to commonly used Nodes.
this.firstUserCell = Y.one(SELECTORS.USERCELL);
this.container = Y.one(SELECTORS.GRADEPARENT);
this.firstNonUserCell = Y.one(SELECTORS.GRADECELL);
if (!this.firstUserCell) {
// No need for floating elements, there are no users.
return this;
}
if (M.cfg.behatsiterunning) {
// If the behat site is running we don't want floating elements.
return;
}
// Generate floating elements.
this._setupFloatingUserColumn();
this._setupFloatingUserHeader();
this._setupFloatingAssignmentHeaders();
this._setupFloatingAssignmentFooter();
// Setup generic floating left-aligned headers.
this.floatingHeaderRow = {};
// The 'Controls' row (shown in editing mode when certain options are set).
this._setupFloatingLeftHeaders('.controls .controls');
// The 'Range' row (shown in editing mode when certain options are set).
this._setupFloatingLeftHeaders('.range .range');
// The 'Overall Average' field.
this._setupFloatingLeftHeaders(SELECTORS.FOOTERTITLE);
// Additional setup for the footertitle.
this._setupFloatingAssignmentFooterTitle();
// Calculate the positions of edge cells. These are used for positioning of the floating headers.
// This must be called after the floating headers are setup, but before the scroll event handler is invoked.
this._calculateCellPositions();
// Setup the floating element initial positions by simulating scroll.
this._handleScrollEvent();
// Setup the event handlers.
this._setupEventHandlers();
// Listen for a resize event globally - other parts of the code not in this YUI wrapper may make changes to the
// fields which result in size changes.
Y.Global.on('moodle-gradereport_grader:resized', this._handleResizeEvent, this);
return this;
},
/**
* Calculate the positions of some cells. These values are used heavily
* in scroll event handling.
*
* @method _calculateCellPositions
* @protected
*/
_calculateCellPositions: function() {
// The header row shows the grade item headers and is floated to the top of the window.
this.headerRowTop = this.headerRow.getY();
// The footer row shows the grade averages and will be floated to the page bottom.
if (this.tableFooterRow) {
this.footerRowPosition = this.tableFooterRow.getY();
}
// Add the width of the dock if it is visible.
this.dockWidth = 0;
var dock = Y.one('.has_dock #dock');
if (dock) {
this.dockWidth = dock.get(OFFSETWIDTH);
}
var userCellList = Y.all(SELECTORS.USERCELL);
// The left of the user cells matches the left of the headerRow.
this.firstUserCellLeft = this.firstUserCell.getX();
this.firstUserCellWidth = this.firstUserCell.get(OFFSETWIDTH);
// The left of the user cells matches the left of the footer title.
this.firstNonUserCellLeft = this.firstNonUserCell.getX();
this.firstNonUserCellWidth = this.firstNonUserCell.get(OFFSETWIDTH);
if (userCellList.size() > 1) {
// Use the top of the second cell for the bottom of the first cell.
// This is used when scrolling to fix the footer to the top edge of the window.
var firstUserCell = userCellList.item(1);
this.firstUserCellBottom = firstUserCell.getY() + parseInt(firstUserCell.getComputedStyle(HEIGHT), 10);
// Use the top of the penultimate cell when scrolling the header.
// The header is the same size as the cells.
this.lastUserCellTop = userCellList.item(userCellList.size() - 2).getY();
} else {
var firstItem = userCellList.item(0);
// We can't use the top of the second row as there is only one row.
this.lastUserCellTop = firstItem.getY();
if (this.tableFooterRow) {
// The footer is present so we can use that.
this.firstUserCellBottom = this.footerRowPosition + parseInt(this.tableFooterRow.getComputedStyle(HEIGHT), 10);
} else {
// No other clues - calculate the top instead.
this.firstUserCellBottom = firstItem.getY() + firstItem.get('offsetHeight');
}
}
// Check whether a header is present and whether it is floating.
var header = Y.one('header');
this.pageHeaderHeight = 0;
if (header) {
if (header.getComputedStyle('position') === 'fixed') {
this.pageHeaderHeight = header.get(OFFSETHEIGHT);
} else {
var navbar = Y.one('.navbar');
if (navbar && navbar.getComputedStyle('position') === 'fixed') {
// If the navbar exists and isn't fixed, we need to offset the page header to accommodate for it.
this.pageHeaderHeight = navbar.get(OFFSETHEIGHT);
}
}
}
},
/**
* Get the relative XY of the node.
*
* @method _getRelativeXY
* @protected
* @param {Node} node The node to get the position of.
* @return {Array} Containing X and Y.
*/
_getRelativeXY: function(node) {
return this._getRelativeXYFromXY(node.getX(), node.getY());
},
/**
* Get the relative positioning from coordinates.
*
* This gives the position according to the parent of the table, which must
* be set as position: relative.
*
* @method _getRelativeXYFromXY
* @protected
* @param {Number} x X position.
* @param {Number} y Y position.
* @return {Array} Containing X and Y.
*/
_getRelativeXYFromXY: function(x, y) {
var parentXY = this.container.getXY();
return [x - parentXY[0], y - parentXY[1]];
},
/**
* Get the relative positioning of an elements from coordinates.
*
* @method _getRelativeXFromX
* @protected
* @param {Number} pos X position.
* @return {Number} relative X position.
*/
_getRelativeXFromX: function(pos) {
return this._getRelativeXYFromXY(pos, 0)[0];
},
/**
* Get the relative positioning of an elements from coordinates.
*
* @method _getRelativeYFromY
* @protected
* @param {Number} pos Y position.
* @return {Number} relative Y position.
*/
_getRelativeYFromY: function(pos) {
return this._getRelativeXYFromXY(0, pos)[1];
},
/**
* Return the size of the horizontal scrollbar.
*
* @method _getScrollBarHeight
* @protected
* @return {Number} Height of the scrollbar.
*/
_getScrollBarHeight: function() {
if (Y.UA.ie && Y.UA.ie >= 10) {
// IE has transparent scrollbars, which sometimes disappear... it's better to ignore them.
return 0;
} else if (Y.config.doc.body.scrollWidth > Y.config.doc.body.clientWidth) {
// The document can be horizontally scrolled.
return Y.DOM.getScrollbarWidth();
}
return 0;
},
/**
* Setup the main event listeners.
* These deal with things like window events.
*
* @method _setupEventHandlers
* @protected
*/
_setupEventHandlers: function() {
this._eventHandles.push(
// Listen for window scrolls, resizes, and rotation events.
Y.one(Y.config.win).on('scroll', this._handleScrollEvent, this),
Y.one(Y.config.win).on('resize', this._handleResizeEvent, this),
Y.one(Y.config.win).on('orientationchange', this._handleResizeEvent, this),
Y.Global.on('dock:shown', this._handleResizeEvent, this),
Y.Global.on('dock:hidden', this._handleResizeEvent, this)
);
},
/**
* Create and setup the floating column of user names.
*
* @method _setupFloatingUserColumn
* @protected
*/
_setupFloatingUserColumn: function() {
// Grab all cells in the user names column.
var userColumn = Y.all(SELECTORS.USERCELL),
// Create a floating table.
floatingUserColumn = Y.Node.create('
'),
// Get the XY for the floating element.
coordinates = this._getRelativeXY(this.firstUserCell);
// Generate the new fields.
userColumn.each(function(node) {
var height = node.getComputedStyle(HEIGHT);
// Nasty hack to account for Internet Explorer
if (Y.UA.ie !== 0) {
var allHeight = node.get('offsetHeight');
var marginHeight = parseInt(node.getComputedStyle('marginTop'), 10) +
parseInt(node.getComputedStyle('marginBottom'), 10);
var paddingHeight = parseInt(node.getComputedStyle('paddingTop'), 10) +
parseInt(node.getComputedStyle('paddingBottom'), 10);
var borderHeight = parseInt(node.getComputedStyle('borderTopWidth'), 10) +
parseInt(node.getComputedStyle('borderBottomWidth'), 10);
height = allHeight - marginHeight - paddingHeight - borderHeight;
}
// Create and configure the new container.
var containerNode = Y.Node.create('');
containerNode.set('innerHTML', node.get('innerHTML'))
.setAttribute('class', node.getAttribute('class'))
.setAttribute('data-uid', node.ancestor('tr').getData('uid'))
.setStyles({
height: height,
width: node.getComputedStyle(WIDTH)
});
// Add the new nodes to our floating table.
floatingUserColumn.appendChild(containerNode);
}, this);
// Style the floating user container.
floatingUserColumn.setStyles({
left: coordinates[0] + 'px',
position: 'absolute',
top: coordinates[1] + 'px'
});
// Append to the grader region.
this.graderRegion.append(floatingUserColumn);
// Store a reference to this for later - we use it in the event handlers.
this.userColumn = floatingUserColumn;
},
/**
* Create and setup the floating username header cell.
*
* @method _setupFloatingUserHeader
* @protected
*/
_setupFloatingUserHeader: function() {
// We make various references to the header cells. Store it for later.
this.headerRow = Y.one(SELECTORS.HEADERROW);
this.headerCell = Y.one(SELECTORS.STUDENTHEADER);
// Create the floating row and cell.
var floatingUserHeaderRow = Y.Node.create(''),
floatingUserHeaderCell = Y.Node.create(''),
nodepos = this._getRelativeXY(this.headerCell)[0],
coordinates = this._getRelativeXY(this.headerRow),
gradeHeadersOffset = coordinates[0];
// Append the content and style to the floating cell.
floatingUserHeaderCell
.set('innerHTML', this.headerCell.getHTML())
.setAttribute('class', this.headerCell.getAttribute('class'))
.setStyles({
// The header is larger than the user cells, so we take the user cell.
width: this.firstUserCell.getComputedStyle(WIDTH),
left: (nodepos - gradeHeadersOffset) + 'px'
});
// Style the floating row.
floatingUserHeaderRow
.setStyles({
left: coordinates[0] + 'px',
position: 'absolute',
top: coordinates[1] + 'px'
});
// Append the cell to the row, and finally to the region.
floatingUserHeaderRow.append(floatingUserHeaderCell);
this.graderRegion.append(floatingUserHeaderRow);
// Store a reference to this for later - we use it in the event handlers.
this.userColumnHeader = floatingUserHeaderRow;
},
/**
* Create and setup the floating grade item header row.
*
* @method _setupFloatingAssignmentHeaders
* @protected
*/
_setupFloatingAssignmentHeaders: function() {
this.headerRow = Y.one('#user-grades tr.heading');
var gradeHeaders = Y.all('#user-grades tr.heading .cell');
// Generate a floating headers
var floatingGradeHeaders = Y.Node.create('');
var coordinates = this._getRelativeXY(this.headerRow);
var floatingGradeHeadersWidth = 0;
var floatingGradeHeadersHeight = 0;
var gradeHeadersOffset = coordinates[0];
gradeHeaders.each(function(node) {
var nodepos = this._getRelativeXY(node)[0];
var newnode = Y.Node.create('');
newnode.append(node.getHTML())
.setAttribute('class', node.getAttribute('class'))
.setData('itemid', node.getData('itemid'))
.setStyles({
height: node.getComputedStyle(HEIGHT),
left: (nodepos - gradeHeadersOffset) + 'px',
position: 'absolute',
width: node.getComputedStyle(WIDTH)
});
// Sum up total widths - these are used in the container styles.
// Use the offsetHeight and Width here as this contains the
// padding, margin, and borders.
floatingGradeHeadersWidth += parseInt(node.get(OFFSETWIDTH), 10);
floatingGradeHeadersHeight = node.get(OFFSETHEIGHT);
// Append to our floating table.
floatingGradeHeaders.appendChild(newnode);
}, this);
// Position header table.
floatingGradeHeaders.setStyles({
height: floatingGradeHeadersHeight + 'px',
left: coordinates[0] + 'px',
position: 'absolute',
top: coordinates[1] + 'px',
width: floatingGradeHeadersWidth + 'px'
});
// Insert in place before the grader headers.
this.userColumnHeader.insert(floatingGradeHeaders, 'before');
// Store a reference to this for later - we use it in the event handlers.
this.gradeItemHeadingContainer = floatingGradeHeaders;
},
/**
* Create and setup the floating header row of grade item titles.
*
* @method _setupFloatingAssignmentFooter
* @protected
*/
_setupFloatingAssignmentFooter: function() {
this.tableFooterRow = Y.one('#user-grades .avg');
if (!this.tableFooterRow) {
Y.log('Averages footer not found - unable to float it.', 'warn', LOGNS);
return;
}
// Generate the sticky footer row.
var footerCells = this.tableFooterRow.all('.cell');
// Create a container.
var floatingGraderFooter = Y.Node.create('');
var footerWidth = 0;
var coordinates = this._getRelativeXY(this.tableFooterRow);
var footerRowOffset = coordinates[0];
var floatingGraderFooterHeight = 0;
// Copy cell content.
footerCells.each(function(node) {
var newnode = Y.Node.create('');
var nodepos = this._getRelativeXY(node)[0];
newnode.set('innerHTML', node.getHTML())
.setAttribute('class', node.getAttribute('class'))
.setStyles({
height: node.getComputedStyle(HEIGHT),
left: (nodepos - footerRowOffset) + 'px',
position: 'absolute',
width: node.getComputedStyle(WIDTH)
});
floatingGraderFooter.append(newnode);
floatingGraderFooterHeight = node.get(OFFSETHEIGHT);
footerWidth += parseInt(node.get(OFFSETWIDTH), 10);
}, this);
// Position the row.
floatingGraderFooter.setStyles({
position: 'absolute',
left: coordinates[0] + 'px',
bottom: '1px',
height: floatingGraderFooterHeight + 'px',
width: footerWidth + 'px'
});
// Append to the grader region.
this.graderRegion.append(floatingGraderFooter);
this.footerRow = floatingGraderFooter;
},
/**
* Create and setup the floating footer title cell.
*
* @method _setupFloatingAssignmentFooterTitle
* @protected
*/
_setupFloatingAssignmentFooterTitle: function() {
var floatingFooterRow = this.floatingHeaderRow[SELECTORS.FOOTERTITLE];
if (floatingFooterRow) {
// Style the floating row.
floatingFooterRow
.setStyles({
bottom: '1px'
});
}
},
/**
* Create and setup the floating left headers.
*
* @method _setupFloatingLeftHeaders
* @protected
*/
_setupFloatingLeftHeaders: function(headerSelector) {
// We make various references to the origin cell. Store it for later.
var origin = Y.one(headerSelector);
if (!origin) {
return;
}
// Create the floating row and cell.
var floatingRow = Y.Node.create(''),
floatingCell = Y.Node.create(''),
coordinates = this._getRelativeXY(origin),
width = this.firstUserCell.getComputedStyle(WIDTH),
height = origin.get(OFFSETHEIGHT);
// Append the content and style to the floating cell.
floatingCell
.set('innerHTML', origin.getHTML())
.setAttribute('class', origin.getAttribute('class'))
.setStyles({
// The header is larger than the user cells, so we take the user cell.
width: width
});
// Style the floating row.
floatingRow
.setStyles({
position: 'absolute',
top: coordinates[1] + 'px',
left: coordinates[0] + 'px',
height: height + 'px'
})
// Add all classes from the parent to the row
.addClass(origin.get('parentNode').get('className'));
// Append the cell to the row, and finally to the region.
floatingRow.append(floatingCell);
this.graderRegion.append(floatingRow);
// Store a reference to this for later - we use it in the event handlers.
this.floatingHeaderRow[headerSelector] = floatingRow;
},
/**
* Process a Scroll Event on the window.
*
* @method _handleScrollEvent
* @protected
*/
_handleScrollEvent: function() {
// Performance is important in this function as it is called frequently and in quick succesion.
// To prevent layout thrashing when the DOM is repeatedly updated and queried, updated and queried,
// updates must be batched.
// Next do all the calculations.
var gradeItemHeadingContainerStyles = {},
userColumnHeaderStyles = {},
userColumnStyles = {},
footerStyles = {},
coord = 0,
floatingUserTriggerPoint = 0, // The X position at which the floating should start.
floatingUserRelativePoint = 0, // The point to use when calculating the new position.
headerFloats = false,
userFloats = false,
footerFloats = false,
leftTitleFloats = false,
floatingHeaderStyles = {},
floatingFooterTitleStyles = {},
floatingFooterTitleRow = false;
// Header position.
gradeItemHeadingContainerStyles.left = this._getRelativeXFromX(this.headerRow.getX());
if (Y.config.win.pageYOffset + this.pageHeaderHeight > this.headerRowTop) {
headerFloats = true;
if (Y.config.win.pageYOffset + this.pageHeaderHeight < this.lastUserCellTop) {
coord = this._getRelativeYFromY(Y.config.win.pageYOffset + this.pageHeaderHeight);
gradeItemHeadingContainerStyles.top = coord + 'px';
userColumnHeaderStyles.top = coord + 'px';
} else {
coord = this._getRelativeYFromY(this.lastUserCellTop);
gradeItemHeadingContainerStyles.top = coord + 'px';
userColumnHeaderStyles.top = coord + 'px';
}
} else {
headerFloats = false;
coord = this._getRelativeYFromY(this.headerRowTop);
gradeItemHeadingContainerStyles.top = coord + 'px';
userColumnHeaderStyles.top = coord + 'px';
}
// User column position.
if (window.right_to_left()) {
floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset - this.dockWidth;
floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth;
userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth);
leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) <
(this.firstNonUserCellLeft + this.firstUserCellWidth);
} else {
floatingUserRelativePoint = Y.config.win.pageXOffset;
floatingUserTriggerPoint = floatingUserRelativePoint + this.dockWidth;
userFloats = floatingUserTriggerPoint > this.firstUserCellLeft;
leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth);
}
if (userFloats) {
coord = this._getRelativeXFromX(floatingUserRelativePoint);
userColumnStyles.left = coord + 'px';
userColumnHeaderStyles.left = coord + 'px';
} else {
coord = this._getRelativeXFromX(this.firstUserCellLeft);
userColumnStyles.left = coord + 'px';
userColumnHeaderStyles.left = coord + 'px';
}
// Update the miscellaneous left-only floats.
Y.Object.each(this.floatingHeaderRow, function(origin, key) {
floatingHeaderStyles[key] = {
left: userColumnStyles.left
};
}, this);
// Update footer.
if (this.footerRow) {
footerStyles.left = this._getRelativeXFromX(this.headerRow.getX());
// Determine whether the footer should now be shown as sticky.
var pageHeight = Y.config.win.innerHeight,
pageOffset = Y.config.win.pageYOffset,
bottomScrollPosition = pageHeight - this._getScrollBarHeight() + pageOffset,
footerRowHeight = parseInt(this.footerRow.getComputedStyle(HEIGHT), 10),
footerBottomPosition = footerRowHeight + this.footerRowPosition;
floatingFooterTitleStyles = floatingHeaderStyles[SELECTORS.FOOTERTITLE];
floatingFooterTitleRow = this.floatingHeaderRow[SELECTORS.FOOTERTITLE];
if (bottomScrollPosition < footerBottomPosition && bottomScrollPosition > this.firstUserCellBottom) {
// We have not scrolled below the footer, nor above the first row.
footerStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px';
footerFloats = true;
} else {
// The footer should not float any more.
footerStyles.bottom = '1px';
footerFloats = false;
}
if (floatingFooterTitleStyles) {
floatingFooterTitleStyles.bottom = footerStyles.bottom;
floatingFooterTitleStyles.top = null;
}
floatingHeaderStyles[SELECTORS.FOOTERTITLE] = floatingFooterTitleStyles;
}
// Apply the styles and mark elements as floating, or not.
if (this.gradeItemHeadingContainer) {
this.gradeItemHeadingContainer.setStyles(gradeItemHeadingContainerStyles);
if (headerFloats) {
this.gradeItemHeadingContainer.addClass(CSS.FLOATING);
} else {
this.gradeItemHeadingContainer.removeClass(CSS.FLOATING);
}
}
if (this.userColumnHeader) {
this.userColumnHeader.setStyles(userColumnHeaderStyles);
if (userFloats) {
this.userColumnHeader.addClass(CSS.FLOATING);
} else {
this.userColumnHeader.removeClass(CSS.FLOATING);
}
}
if (this.userColumn) {
this.userColumn.setStyles(userColumnStyles);
if (userFloats) {
this.userColumn.addClass(CSS.FLOATING);
} else {
this.userColumn.removeClass(CSS.FLOATING);
}
}
if (this.footerRow) {
this.footerRow.setStyles(footerStyles);
if (footerFloats) {
this.footerRow.addClass(CSS.FLOATING);
} else {
this.footerRow.removeClass(CSS.FLOATING);
}
}
// And apply the styles to the generic left headers.
Y.Object.each(floatingHeaderStyles, function(styles, key) {
if (this.floatingHeaderRow[key]) {
this.floatingHeaderRow[key].setStyles(styles);
}
}, this);
Y.Object.each(this.floatingHeaderRow, function(value, key) {
if (this.floatingHeaderRow[key]) {
if (leftTitleFloats) {
this.floatingHeaderRow[key].addClass(CSS.FLOATING);
} else {
this.floatingHeaderRow[key].removeClass(CSS.FLOATING);
}
}
}, this);
// The footer title has a more specific float setting.
if (floatingFooterTitleRow) {
if (leftTitleFloats) {
floatingFooterTitleRow.addClass(CSS.FLOATING);
} else {
floatingFooterTitleRow.removeClass(CSS.FLOATING);
}
}
},
/**
* Process a size change Event on the window.
*
* @method _handleResizeEvent
* @protected
*/
_handleResizeEvent: function() {
// Recalculate the position of the edge cells for scroll positioning.
this._calculateCellPositions();
// Simulate a scroll.
this._handleScrollEvent();
// Resize user cells.
var userWidth = this.firstUserCell.getComputedStyle(WIDTH);
var userCells = Y.all(SELECTORS.USERCELL);
this.userColumnHeader.one('.cell').setStyle('width', userWidth);
this.userColumn.all('.cell').each(function(cell, idx) {
var height = userCells.item(idx).getComputedStyle(HEIGHT);
// Nasty hack to account for Internet Explorer
if (Y.UA.ie !== 0) {
var node = userCells.item(idx);
var allHeight = node.getDOMNode ?
node.getDOMNode().getBoundingClientRect().height :
node.get('offsetHeight');
var marginHeight = parseInt(node.getComputedStyle('marginTop'), 10) +
parseInt(node.getComputedStyle('marginBottom'), 10);
var paddingHeight = parseInt(node.getComputedStyle('paddingTop'), 10) +
parseInt(node.getComputedStyle('paddingBottom'), 10);
var borderHeight = parseInt(node.getComputedStyle('borderTopWidth'), 10) +
parseInt(node.getComputedStyle('borderBottomWidth'), 10);
height = allHeight - marginHeight - paddingHeight - borderHeight;
}
cell.setStyles({
width: userWidth,
height: height
});
}, this);
// Resize headers & footers.
// This is an expensive operation, not expected to happen often.
var headers = this.gradeItemHeadingContainer.all('.cell');
var resizedcells = Y.all(SELECTORS.HEADERCELLS);
var headeroffsetleft = this.headerRow.getX();
var newcontainerwidth = 0;
resizedcells.each(function(cell, idx) {
var headercell = headers.item(idx);
newcontainerwidth += cell.get(OFFSETWIDTH);
var styles = {
width: cell.getComputedStyle(WIDTH),
left: cell.getX() - headeroffsetleft + 'px'
};
headercell.setStyles(styles);
});
if (this.footerRow) {
var footers = this.footerRow.all('.cell');
if (footers.size() !== 0) {
var resizedavgcells = Y.all(SELECTORS.FOOTERCELLS);
resizedavgcells.each(function(cell, idx) {
var footercell = footers.item(idx);
var styles = {
width: cell.getComputedStyle(WIDTH),
left: cell.getX() - headeroffsetleft + 'px'
};
footercell.setStyles(styles);
});
}
}
// Resize the title areas too.
Y.Object.each(this.floatingHeaderRow, function(row) {
row.one('div').setStyle('width', userWidth);
}, this);
this.gradeItemHeadingContainer.setStyle('width', newcontainerwidth);
}
};
Y.Base.mix(Y.M.gradereport_grader.ReportTable, [FloatingHeaders]);