From 33bc816e8963f974d93b788afdebd7c41277c728 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 3 Sep 2014 14:40:15 +0800 Subject: [PATCH] MDL-47028 gradereport_grader: Add floating headers for other cells. This issue is a part of the MDL-46658 Task. This issue is a part of the MDL-25544 Epic. --- grade/report/grader/styles.css | 3 + ...adereport_grader-gradereporttable-debug.js | 194 +++++++++++++----- ...gradereport_grader-gradereporttable-min.js | 4 +- ...dle-gradereport_grader-gradereporttable.js | 194 +++++++++++++----- .../gradereporttable/js/floatingheaders.js | 192 ++++++++++++----- .../gradereporttable/js/gradereporttable.js | 2 +- 6 files changed, 431 insertions(+), 158 deletions(-) diff --git a/grade/report/grader/styles.css b/grade/report/grader/styles.css index e86fbd9492d..f04ac8a6060 100644 --- a/grade/report/grader/styles.css +++ b/grade/report/grader/styles.css @@ -123,6 +123,7 @@ /** * The footer's header. */ +.gradeparent .range .header, .gradeparent .avg .header { font-weight: bold; } @@ -179,6 +180,8 @@ .dir-rtl .gradeparent .excludedfloater { float: right; } + +.path-grade-report .gradeparent .floater .controls.cell, .gradeparent .controls { background-color: #f3ead8; } diff --git a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js index cba0b4a7cc3..cae61a9fec1 100644 --- a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js +++ b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js @@ -30,7 +30,7 @@ YUI.add('moodle-gradereport_grader-gradereporttable', function (Y, NAME) { */ var SELECTORS = { - FOOTERTITLE: '#user-grades .avg .header', + FOOTERTITLE: '.avg .header', FOOTERCELLS: '#user-grades .avg .cell', FOOTERROW: '#user-grades .avg', GRADECELL: 'td.grade', @@ -248,11 +248,22 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstNonUserCellLeft - * @type Node + * @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. @@ -301,22 +312,13 @@ FloatingHeaders.prototype = { */ userColumn: null, - /** - * A Node representing the floating footer title header. This is the header with the "Overage Average" title. - * - * @property floatingFooterTitleRow - * @type Node - * @protected - */ - floatingFooterTitleRow: 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 Node + * @type Number * @protected */ firstUserCellBottom: 0, @@ -327,22 +329,43 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstUserCellLeft - * @type Node + * @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 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 Node + * @type Number * @protected */ lastUserCellTop: 0, + /** + * A list of Nodes representing the generic floating rows. + * + * @property floatingHeaderRow + * @type Node{} + * @protected + */ + floatingHeaderRow: null, + /** * Array of EventHandles. * @@ -374,6 +397,20 @@ FloatingHeaders.prototype = { 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. @@ -409,9 +446,11 @@ FloatingHeaders.prototype = { // 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. @@ -746,41 +785,63 @@ FloatingHeaders.prototype = { * @protected */ _setupFloatingAssignmentFooterTitle: function() { - // We make various references to the header cells. Store it for later. - this.footerCell = Y.one(SELECTORS.FOOTERTITLE); + 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 floatingFooterRow = Y.Node.create(''), - floatingFooterCell = Y.Node.create('
'), - coordinates = this._getRelativeXY(this.headerRow), + var floatingRow = Y.Node.create(''), + floatingCell = Y.Node.create('
'), + coordinates = this._getRelativeXY(origin), width = this.firstUserCell.getComputedStyle(WIDTH), - height = this.footerCell.get(OFFSETHEIGHT); + height = origin.get(OFFSETHEIGHT); // Append the content and style to the floating cell. - floatingFooterCell - .set('innerHTML', this.footerCell.getHTML()) - .setAttribute('class', this.footerCell.getAttribute('class')) + 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. - floatingFooterRow + floatingRow .setStyles({ position: 'absolute', + top: coordinates[1] + 'px', left: coordinates[0] + 'px', - bottom: '1px', - height: height + 'px', - width: width - }); + 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. - floatingFooterRow.append(floatingFooterCell); - this.graderRegion.append(floatingFooterRow); + floatingRow.append(floatingCell); + this.graderRegion.append(floatingRow); // Store a reference to this for later - we use it in the event handlers. - this.floatingFooterTitleRow = floatingFooterRow; + this.floatingHeaderRow[headerSelector] = floatingRow; }, /** @@ -799,15 +860,16 @@ FloatingHeaders.prototype = { userColumnHeaderStyles = {}, userColumnStyles = {}, footerStyles = {}, - footerTitleStyles = {}, coord = 0, - userCellWidth = 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, - footerTitleFloats = false; + leftTitleFloats = false, + floatingHeaderStyles = {}, + floatingFooterTitleStyles = {}, + floatingFooterTitleRow = false; // Header position. gradeItemHeadingContainerStyles.left = this._getRelativeXFromX(this.headerRow.getX()); @@ -830,30 +892,36 @@ FloatingHeaders.prototype = { } // User column position. - userCellWidth = this.firstUserCell.get(OFFSETWIDTH); if (right_to_left()) { floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset; - floatingUserRelativePoint = floatingUserTriggerPoint - userCellWidth; - userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + userCellWidth); - footerTitleFloats = floatingUserTriggerPoint < (this.firstNonUserCellLeft + userCellWidth); + floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth; + userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth); + leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) < + (this.firstNonUserCellLeft + this.firstUserCellWidth); } else { floatingUserTriggerPoint = Y.config.win.pageXOffset; floatingUserRelativePoint = floatingUserTriggerPoint; userFloats = floatingUserTriggerPoint > this.firstUserCellLeft; - footerTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - userCellWidth); + leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth); } if (userFloats) { coord = this._getRelativeXFromX(floatingUserRelativePoint); userColumnStyles.left = coord + 'px'; userColumnHeaderStyles.left = coord + 'px'; - footerTitleStyles.left = userColumnStyles.left; } 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()); @@ -865,18 +933,22 @@ FloatingHeaders.prototype = { 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 = footerTitleStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; + footerStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; footerFloats = true; } else { // The footer should not float any more. footerStyles.bottom = '1px'; footerFloats = false; - - // The footer title may float if the page scrolls right. - footerTitleStyles.bottom = '1px'; } + if (floatingFooterTitleStyles) { + floatingFooterTitleStyles.bottom = footerStyles.bottom; + floatingFooterTitleStyles.top = null; + } + floatingHeaderStyles[SELECTORS.FOOTERTITLE] = floatingFooterTitleStyles; } // Apply the styles. @@ -884,7 +956,11 @@ FloatingHeaders.prototype = { this.userColumnHeader.setStyles(userColumnHeaderStyles); this.userColumn.setStyles(userColumnStyles); this.footerRow.setStyles(footerStyles); - this.floatingFooterTitleRow.setStyles(footerTitleStyles); + + // And apply the styles to the generic left headers. + Y.Object.each(floatingHeaderStyles, function(styles, key) { + this.floatingHeaderRow[key].setStyles(styles); + }, this); // Mark the elements as floating, or not. if (headerFloats) { @@ -907,11 +983,23 @@ FloatingHeaders.prototype = { this.footerRow.removeClass(CSS.FLOATING); } - if (this.footerRow && footerTitleFloats) { - this.floatingFooterTitleRow.addClass(CSS.FLOATING); - } else { - this.floatingFooterTitleRow.removeClass(CSS.FLOATING); + Y.Object.each(this.floatingHeaderRow, function(value, 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); + } } + }, /** @@ -969,12 +1057,14 @@ FloatingHeaders.prototype = { }; footercell.setStyles(styles); }); - - // Resize the title area too. - this.floatingFooterTitleRow.one('div').setStyle('width', userWidth); } } + // Resize the title areas too. + Y.Object.each(this.floatingHeaderRow, function(row) { + row.one('div').setStyle('width', userWidth); + }, this); + this.gradeItemHeadingContainer.setStyle('width', newcontainerwidth); } diff --git a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js index aa46149b8be..ba240407c44 100644 --- a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js +++ b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js @@ -1,2 +1,2 @@ -YUI.add("moodle-gradereport_grader-gradereporttable",function(e,t){function i(){i.superclass.constructor.apply(this,arguments)}function f(){}var n={FOOTERTITLE:"#user-grades .avg .header",FOOTERCELLS:"#user-grades .avg .cell",FOOTERROW:"#user-grades .avg",GRADECELL:"td.grade",GRADERTABLE:".gradeparent table",GRADEPARENT:".gradeparent",HEADERCELLS:"#user-grades .heading .cell",HEADERCELL:".gradebook-header-cell",HEADERROW:"#user-grades tr.heading",STUDENTHEADER:"#studentheader",USERCELL:"#user-grades .user.cell",USERMAIL:"#user-grades .useremail"},r={OVERRIDDEN:"overridden",TOOLTIPACTIVE:"tooltipactive"};e.extend(i,e.Base,{_eventHandles:[],graderTable:null,initializer:function(){this.graderRegion=e.one(n.GRADEPARENT),this.graderTable=e.one(n.GRADERTABLE),this.setupFloatingHeaders()},getGradeUserName:function(e){var t=e.ancestor("tr"),n=t.one("th.user .username");return n?n.get("text"):""},getGradeItemName:function(t){var n=e.one("th.item[data-itemid='"+t.getData("itemid")+"']");return n?n.get("text"):""},getGradeFeedback:function(e){return e.getData("feedback")}}),e.namespace("M.gradereport_grader").ReportTable=i,e.namespace("M.gradereport_grader").init=function(t){return new e.M.gradereport_grader.ReportTable(t)};var s="height",o="width",u="offsetWidth",a="offsetHeight";r.FLOATING="floating",f.ATTRS={},f.prototype={pageHeaderHeight:0,container:null,headerCell:null,headerRow:null,firstUserCell:null,firstNonUserCell:null,firstNonUserCellLeft:0,tableFooterRow:null,footerRow:null,gradeItemHeadingContainer:null,userColumnHeader:null,userColumn:null,floatingFooterTitleRow:null,firstUserCellBottom:0,firstUserCellLeft:0,lastUserCellTop:0,_eventHandles:[],setupFloatingHeaders:function(){return this.firstUserCell=e.one(n.USERCELL),this.container=e.one(n.GRADEPARENT),this.firstNonUserCell=e.one(n.USERMAIL).next(),this.firstUserCell?(this._setupFloatingUserColumn(),this._setupFloatingUserHeader(),this._setupFloatingAssignmentHeaders(),this._setupFloatingAssignmentFooter(),this._setupFloatingAssignmentFooterTitle(),this._calculateCellPositions(),this._handleScrollEvent(),this._setupEventHandlers(),this):this},_calculateCellPositions:function(){this.headerRowTop=this.headerRow.getY(),this.tableFooterRow&&(this.footerRowPosition=this.tableFooterRow.getY());var t=e.all(n.USERCELL);this.firstUserCellLeft=this.firstUserCell.getX(),this.firstNonUserCellLeft=this.firstNonUserCell.getX();if(t.size()>1){var r=t.item(1);this.firstUserCellBottom=r.getY()+parseInt(r.getComputedStyle(s),10),this.lastUserCellTop=t.item(t.size()-2).getY()}else{var i=t.item(0);this.lastUserCellTop=i.getY(),this.tableFooterRow?this.firstUserCellBottom=this.footerRowPosition+parseInt(this.tableFooterRow.getComputedStyle(s),10):this.firstUserCellBottom=i.getY()+i.get("offsetHeight")}var o=e.one("header");this.pageHeaderHeight=0,o&&o.getComputedStyle("position")==="fixed"&&(this.pageHeaderHeight=o.get(a))},_getRelativeXY:function(e){return this._getRelativeXYFromXY(e.getX(),e.getY())},_getRelativeXYFromXY:function(e,t){var n=this.container.getXY();return[e-n[0],t-n[1]]},_getRelativeXFromX:function(e){return this._getRelativeXYFromXY(e,0)[0]},_getRelativeYFromY:function(e){return this._getRelativeXYFromXY(0,e)[1]},_getScrollBarHeight:function(){return e.UA.ie&&e.UA.ie>=10?0:e.config.doc.body.scrollWidth>e.config.doc.body.clientWidth?e.DOM.getScrollbarWidth():0},_setupEventHandlers:function(){this._eventHandles.push(e.one(e.config.win).on("scroll",this._handleScrollEvent,this),e.one(e.config.win).on("resize",this._handleResizeEvent,this),e.one(e.config.win).on("orientationchange",this._handleResizeEvent,this))},_setupFloatingUserColumn:function(){var t=e.all(n.USERCELL),r=e.Node.create(''),i=this._getRelativeXY(this.firstUserCell);t.each(function(t){var n=e.Node.create("
");n.set("innerHTML",t.get("innerHTML")).setAttribute("class",t.getAttribute("class")).setAttribute("data-uid",t.ancestor("tr").getData("uid")).setStyles({height:t.getComputedStyle(s),width:t.getComputedStyle(o)}),r.appendChild(n)},this),r.setStyles({left:i[0]+"px",position:"absolute",top:i[1]+"px"}),this.graderRegion.append(r),this.userColumn=r},_setupFloatingUserHeader:function(){this.headerRow=e.one(n.HEADERROW),this.headerCell=e.one(n.STUDENTHEADER);var t=e.Node.create(''),r=e.Node.create("
"),i=this._getRelativeXY(this.headerCell)[0],s=this._getRelativeXY(this.headerRow),u=s[0];r.set("innerHTML",this.headerCell.getHTML()).setAttribute("class",this.headerCell.getAttribute("class")).setStyles({width:this.firstUserCell.getComputedStyle(o),left:i-u+"px"}),t.setStyles({left:s[0]+"px",position:"absolute",top:s[1]+"px"}),t.append(r),this.graderRegion.append(t),this.userColumnHeader=t},_setupFloatingAssignmentHeaders:function(){this.headerRow=e.one("#user-grades tr.heading");var t=e.all("#user-grades tr.heading .cell"),n=e.Node.create(''),r=this._getRelativeXY(this.headerRow),i=0,f=0,l=r[0];t.each(function(t){var r=this._getRelativeXY(t)[0],c=e.Node.create("
");c.append(t.getHTML()).setAttribute("class",t.getAttribute("class")).setData("itemid",t.getData("itemid")).setStyles({height:t.getComputedStyle(s),left:r-l+"px",position:"absolute",width:t.getComputedStyle(o)}),i+=parseInt(t.get(u),10),f=t.get(a),n.appendChild(c)},this),n.setStyles({height:f+"px",left:r[0]+"px",position:"absolute",top:r[1]+"px",width:i+"px"}),this.userColumnHeader.insert(n,"before"),this.gradeItemHeadingContainer=n},_setupFloatingAssignmentFooter:function(){this.tableFooterRow=e.one("#user-grades .avg");if(!this.tableFooterRow)return;var t=this.tableFooterRow.all(".cell"),n=e.Node.create(''),r=0,i=this._getRelativeXY(this.tableFooterRow),f=i[0],l=0;t.each(function(t){var i=e.Node.create("
" -),c=this._getRelativeXY(t)[0];i.set("innerHTML",t.getHTML()).setAttribute("class",t.getAttribute("class")).setStyles({height:t.getComputedStyle(s),left:c-f+"px",position:"absolute",width:t.getComputedStyle(o)}),n.append(i),l=t.get(a),r+=parseInt(t.get(u),10)},this),n.setStyles({position:"absolute",left:i[0]+"px",bottom:"1px",height:l+"px",width:r+"px"}),this.graderRegion.append(n),this.footerRow=n},_setupFloatingAssignmentFooterTitle:function(){this.footerCell=e.one(n.FOOTERTITLE);var t=e.Node.create(''),r=e.Node.create("
"),i=this._getRelativeXY(this.headerRow),s=this.firstUserCell.getComputedStyle(o),u=this.footerCell.get(a);r.set("innerHTML",this.footerCell.getHTML()).setAttribute("class",this.footerCell.getAttribute("class")).setStyles({width:s}),t.setStyles({position:"absolute",left:i[0]+"px",bottom:"1px",height:u+"px",width:s}),t.append(r),this.graderRegion.append(t),this.floatingFooterTitleRow=t},_handleScrollEvent:function(){var t={},n={},i={},o={},a={},f=0,l=0,c=0,h=0,p=!1,d=!1,v=!1,m=!1;t.left=this._getRelativeXFromX(this.headerRow.getX()),e.config.win.pageYOffset+this.pageHeaderHeight>this.headerRowTop?(p=!0,e.config.win.pageYOffset+this.pageHeaderHeightthis.firstUserCellLeft,m=c>this.firstNonUserCellLeft-l),d?(f=this._getRelativeXFromX(h),i.left=f+"px",n.left=f+"px",a.left=i.left):(f=this._getRelativeXFromX(this.firstUserCellLeft),i.left=f+"px",n.left=f+"px");if(this.footerRow){o.left=this._getRelativeXFromX(this.headerRow.getX());var g=e.config.win.innerHeight,y=e.config.win.pageYOffset,b=g-this._getScrollBarHeight()+y,w=parseInt(this.footerRow.getComputedStyle(s),10),E=w+this.footerRowPosition;bthis.firstUserCellBottom?(o.bottom=a.bottom=Math.ceil(E-b)+"px",v=!0):(o.bottom="1px",v=!1,a.bottom="1px")}this.gradeItemHeadingContainer.setStyles(t),this.userColumnHeader.setStyles(n),this.userColumn.setStyles(i),this.footerRow.setStyles(o),this.floatingFooterTitleRow.setStyles(a),p?this.gradeItemHeadingContainer.addClass(r.FLOATING):this.gradeItemHeadingContainer.removeClass(r.FLOATING),d?(this.userColumnHeader.addClass(r.FLOATING),this.userColumn.addClass(r.FLOATING)):(this.userColumnHeader.removeClass(r.FLOATING),this.userColumn.removeClass(r.FLOATING)),v?this.footerRow.addClass(r.FLOATING):this.footerRow.removeClass(r.FLOATING),this.footerRow&&m?this.floatingFooterTitleRow.addClass(r.FLOATING):this.floatingFooterTitleRow.removeClass(r.FLOATING)},_handleResizeEvent:function(){this._calculateCellPositions(),this._handleScrollEvent();var t=this.firstUserCell.getComputedStyle(o),r=e.all(n.USERCELL);this.userColumnHeader.one(".cell").setStyle("width",t),this.userColumn.all(".cell").each(function(e,n){e.setStyles({width:t,height:r.item(n).getComputedStyle(s)})},this);var i=this.gradeItemHeadingContainer.all(".cell"),a=e.all(n.HEADERCELLS),f=this.headerRow.getX(),l=0;a.each(function(e,t){var n=i.item(t);l+=e.get(u);var r={width:e.getComputedStyle(o),left:e.getX()-f+"px"};n.setStyles(r)});if(this.footerRow){var c=this.footerRow.all(".cell");if(c.size()!==0){var h=e.all(n.FOOTERCELLS);h.each(function(e,t){var n=c.item(t),r={width:e.getComputedStyle(o),left:e.getX()-f+"px"};n.setStyles(r)}),this.floatingFooterTitleRow.one("div").setStyle("width",t)}}this.gradeItemHeadingContainer.setStyle("width",l)}},e.Base.mix(e.M.gradereport_grader.ReportTable,[f])},"@VERSION@",{requires:["base","node","event","handlebars","overlay","event-hover"]}); +YUI.add("moodle-gradereport_grader-gradereporttable",function(e,t){function i(){i.superclass.constructor.apply(this,arguments)}function f(){}var n={FOOTERTITLE:".avg .header",FOOTERCELLS:"#user-grades .avg .cell",FOOTERROW:"#user-grades .avg",GRADECELL:"td.grade",GRADERTABLE:".gradeparent table",GRADEPARENT:".gradeparent",HEADERCELLS:"#user-grades .heading .cell",HEADERCELL:".gradebook-header-cell",HEADERROW:"#user-grades tr.heading",STUDENTHEADER:"#studentheader",USERCELL:"#user-grades .user.cell",USERMAIL:"#user-grades .useremail"},r={OVERRIDDEN:"overridden",TOOLTIPACTIVE:"tooltipactive"};e.extend(i,e.Base,{_eventHandles:[],graderTable:null,initializer:function(){this.graderRegion=e.one(n.GRADEPARENT),this.graderTable=e.one(n.GRADERTABLE),this.setupFloatingHeaders()},getGradeUserName:function(e){var t=e.ancestor("tr"),n=t.one("th.user .username");return n?n.get("text"):""},getGradeItemName:function(t){var n=e.one("th.item[data-itemid='"+t.getData("itemid")+"']");return n?n.get("text"):""},getGradeFeedback:function(e){return e.getData("feedback")}}),e.namespace("M.gradereport_grader").ReportTable=i,e.namespace("M.gradereport_grader").init=function(t){return new e.M.gradereport_grader.ReportTable(t)};var s="height",o="width",u="offsetWidth",a="offsetHeight";r.FLOATING="floating",f.ATTRS={},f.prototype={pageHeaderHeight:0,container:null,headerCell:null,headerRow:null,firstUserCell:null,firstNonUserCell:null,firstNonUserCellLeft:0,firstNonUserCellWidth:0,tableFooterRow:null,footerRow:null,gradeItemHeadingContainer:null,userColumnHeader:null,userColumn:null,firstUserCellBottom:0,firstUserCellLeft:0,firstUserCellWidth:0,lastUserCellTop:0,floatingHeaderRow:null,_eventHandles:[],setupFloatingHeaders:function(){return this.firstUserCell=e.one(n.USERCELL),this.container=e.one(n.GRADEPARENT),this.firstNonUserCell=e.one(n.USERMAIL).next(),this.firstUserCell?(this._setupFloatingUserColumn(),this._setupFloatingUserHeader(),this._setupFloatingAssignmentHeaders(),this._setupFloatingAssignmentFooter(),this.floatingHeaderRow={},this._setupFloatingLeftHeaders(".controls .controls"),this._setupFloatingLeftHeaders(".range .range"),this._setupFloatingLeftHeaders(n.FOOTERTITLE),this._setupFloatingAssignmentFooterTitle(),this._calculateCellPositions(),this._handleScrollEvent(),this._setupEventHandlers(),this):this},_calculateCellPositions:function(){this.headerRowTop=this.headerRow.getY(),this.tableFooterRow&&(this.footerRowPosition=this.tableFooterRow.getY());var t=e.all(n.USERCELL);this.firstUserCellLeft=this.firstUserCell.getX(),this.firstUserCellWidth=this.firstUserCell.get(u),this.firstNonUserCellLeft=this.firstNonUserCell.getX(),this.firstNonUserCellWidth=this.firstNonUserCell.get(u);if(t.size()>1){var r=t.item(1);this.firstUserCellBottom=r.getY()+parseInt(r.getComputedStyle(s),10),this.lastUserCellTop=t.item(t.size()-2).getY()}else{var i=t.item(0);this.lastUserCellTop=i.getY(),this.tableFooterRow?this.firstUserCellBottom=this.footerRowPosition+parseInt(this.tableFooterRow.getComputedStyle(s),10):this.firstUserCellBottom=i.getY()+i.get("offsetHeight")}var o=e.one("header");this.pageHeaderHeight=0,o&&o.getComputedStyle("position")==="fixed"&&(this.pageHeaderHeight=o.get(a))},_getRelativeXY:function(e){return this._getRelativeXYFromXY(e.getX(),e.getY())},_getRelativeXYFromXY:function(e,t){var n=this.container.getXY();return[e-n[0],t-n[1]]},_getRelativeXFromX:function(e){return this._getRelativeXYFromXY(e,0)[0]},_getRelativeYFromY:function(e){return this._getRelativeXYFromXY(0,e)[1]},_getScrollBarHeight:function(){return e.UA.ie&&e.UA.ie>=10?0:e.config.doc.body.scrollWidth>e.config.doc.body.clientWidth?e.DOM.getScrollbarWidth():0},_setupEventHandlers:function(){this._eventHandles.push(e.one(e.config.win).on("scroll",this._handleScrollEvent,this),e.one(e.config.win).on("resize",this._handleResizeEvent,this),e.one(e.config.win).on("orientationchange",this._handleResizeEvent,this))},_setupFloatingUserColumn:function(){var t=e.all(n.USERCELL),r=e.Node.create(''),i=this._getRelativeXY(this.firstUserCell);t.each(function(t){var n=e.Node.create("
");n.set("innerHTML",t.get("innerHTML")).setAttribute("class",t.getAttribute("class")).setAttribute("data-uid",t.ancestor("tr").getData("uid")).setStyles({height:t.getComputedStyle(s),width:t.getComputedStyle(o)}),r.appendChild(n)},this),r.setStyles({left:i[0]+"px",position:"absolute",top:i[1]+"px"}),this.graderRegion.append(r),this.userColumn=r},_setupFloatingUserHeader:function(){this.headerRow=e.one(n.HEADERROW),this.headerCell=e.one(n.STUDENTHEADER);var t=e.Node.create(''),r=e.Node.create("
"),i=this._getRelativeXY(this.headerCell)[0],s=this._getRelativeXY(this.headerRow),u=s[0];r.set("innerHTML",this.headerCell.getHTML()).setAttribute("class",this.headerCell.getAttribute("class")).setStyles({width:this.firstUserCell.getComputedStyle(o),left:i-u+"px"}),t.setStyles({left:s[0]+"px",position:"absolute",top:s[1]+"px"}),t.append(r),this.graderRegion.append(t),this.userColumnHeader=t},_setupFloatingAssignmentHeaders:function(){this.headerRow=e.one("#user-grades tr.heading");var t=e.all("#user-grades tr.heading .cell"),n=e.Node.create(''),r=this._getRelativeXY(this.headerRow),i=0,f=0,l=r[0];t.each(function(t){var r=this._getRelativeXY(t)[0],c=e.Node.create("
");c.append(t.getHTML()).setAttribute("class",t.getAttribute("class")).setData("itemid",t.getData("itemid")).setStyles({height:t.getComputedStyle(s),left:r-l+"px",position:"absolute",width:t.getComputedStyle(o)}),i+=parseInt(t.get(u),10),f=t.get(a),n.appendChild(c)},this),n.setStyles({height:f+"px",left:r[0]+"px",position:"absolute",top:r[1]+"px",width:i+"px"}),this.userColumnHeader.insert(n,"before"),this.gradeItemHeadingContainer=n},_setupFloatingAssignmentFooter:function(){this. +tableFooterRow=e.one("#user-grades .avg");if(!this.tableFooterRow)return;var t=this.tableFooterRow.all(".cell"),n=e.Node.create(''),r=0,i=this._getRelativeXY(this.tableFooterRow),f=i[0],l=0;t.each(function(t){var i=e.Node.create("
"),c=this._getRelativeXY(t)[0];i.set("innerHTML",t.getHTML()).setAttribute("class",t.getAttribute("class")).setStyles({height:t.getComputedStyle(s),left:c-f+"px",position:"absolute",width:t.getComputedStyle(o)}),n.append(i),l=t.get(a),r+=parseInt(t.get(u),10)},this),n.setStyles({position:"absolute",left:i[0]+"px",bottom:"1px",height:l+"px",width:r+"px"}),this.graderRegion.append(n),this.footerRow=n},_setupFloatingAssignmentFooterTitle:function(){var e=this.floatingHeaderRow[n.FOOTERTITLE];e&&e.setStyles({bottom:"1px"})},_setupFloatingLeftHeaders:function(t){var n=e.one(t);if(!n)return;var r=e.Node.create(''),i=e.Node.create("
"),s=this._getRelativeXY(n),u=this.firstUserCell.getComputedStyle(o),f=n.get(a);i.set("innerHTML",n.getHTML()).setAttribute("class",n.getAttribute("class")).setStyles({width:u}),r.setStyles({position:"absolute",top:s[1]+"px",left:s[0]+"px",height:f+"px"}).addClass(n.get("parentNode").get("className")),r.append(i),this.graderRegion.append(r),this.floatingHeaderRow[t]=r},_handleScrollEvent:function(){var t={},i={},o={},u={},a=0,f=0,l=0,c=!1,h=!1,p=!1,d=!1,v={},m={},g=!1;t.left=this._getRelativeXFromX(this.headerRow.getX()),e.config.win.pageYOffset+this.pageHeaderHeight>this.headerRowTop?(c=!0,e.config.win.pageYOffset+this.pageHeaderHeightthis.firstUserCellLeft,d=f>this.firstNonUserCellLeft-this.firstUserCellWidth),h?(a=this._getRelativeXFromX(l),o.left=a+"px",i.left=a+"px"):(a=this._getRelativeXFromX(this.firstUserCellLeft),o.left=a+"px",i.left=a+"px"),e.Object.each(this.floatingHeaderRow,function(e,t){v[t]={left:o.left}},this);if(this.footerRow){u.left=this._getRelativeXFromX(this.headerRow.getX());var y=e.config.win.innerHeight,b=e.config.win.pageYOffset,w=y-this._getScrollBarHeight()+b,E=parseInt(this.footerRow.getComputedStyle(s),10),S=E+this.footerRowPosition;m=v[n.FOOTERTITLE],g=this.floatingHeaderRow[n.FOOTERTITLE],wthis.firstUserCellBottom?(u.bottom=Math.ceil(S-w)+"px",p=!0):(u.bottom="1px",p=!1),m&&(m.bottom=u.bottom,m.top=null),v[n.FOOTERTITLE]=m}this.gradeItemHeadingContainer.setStyles(t),this.userColumnHeader.setStyles(i),this.userColumn.setStyles(o),this.footerRow.setStyles(u),e.Object.each(v,function(e,t){this.floatingHeaderRow[t].setStyles(e)},this),c?this.gradeItemHeadingContainer.addClass(r.FLOATING):this.gradeItemHeadingContainer.removeClass(r.FLOATING),h?(this.userColumnHeader.addClass(r.FLOATING),this.userColumn.addClass(r.FLOATING)):(this.userColumnHeader.removeClass(r.FLOATING),this.userColumn.removeClass(r.FLOATING)),p?this.footerRow.addClass(r.FLOATING):this.footerRow.removeClass(r.FLOATING),e.Object.each(this.floatingHeaderRow,function(e,t){d?this.floatingHeaderRow[t].addClass(r.FLOATING):this.floatingHeaderRow[t].removeClass(r.FLOATING)},this),g&&(d?g.addClass(r.FLOATING):g.removeClass(r.FLOATING))},_handleResizeEvent:function(){this._calculateCellPositions(),this._handleScrollEvent();var t=this.firstUserCell.getComputedStyle(o),r=e.all(n.USERCELL);this.userColumnHeader.one(".cell").setStyle("width",t),this.userColumn.all(".cell").each(function(e,n){e.setStyles({width:t,height:r.item(n).getComputedStyle(s)})},this);var i=this.gradeItemHeadingContainer.all(".cell"),a=e.all(n.HEADERCELLS),f=this.headerRow.getX(),l=0;a.each(function(e,t){var n=i.item(t);l+=e.get(u);var r={width:e.getComputedStyle(o),left:e.getX()-f+"px"};n.setStyles(r)});if(this.footerRow){var c=this.footerRow.all(".cell");if(c.size()!==0){var h=e.all(n.FOOTERCELLS);h.each(function(e,t){var n=c.item(t),r={width:e.getComputedStyle(o),left:e.getX()-f+"px"};n.setStyles(r)})}}e.Object.each(this.floatingHeaderRow,function(e){e.one("div").setStyle("width",t)},this),this.gradeItemHeadingContainer.setStyle("width",l)}},e.Base.mix(e.M.gradereport_grader.ReportTable,[f])},"@VERSION@",{requires:["base","node","event","handlebars","overlay","event-hover"]}); diff --git a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js index 218cf1b095a..9ea1d94c0e1 100644 --- a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js +++ b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js @@ -30,7 +30,7 @@ YUI.add('moodle-gradereport_grader-gradereporttable', function (Y, NAME) { */ var SELECTORS = { - FOOTERTITLE: '#user-grades .avg .header', + FOOTERTITLE: '.avg .header', FOOTERCELLS: '#user-grades .avg .cell', FOOTERROW: '#user-grades .avg', GRADECELL: 'td.grade', @@ -248,11 +248,22 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstNonUserCellLeft - * @type Node + * @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. @@ -301,22 +312,13 @@ FloatingHeaders.prototype = { */ userColumn: null, - /** - * A Node representing the floating footer title header. This is the header with the "Overage Average" title. - * - * @property floatingFooterTitleRow - * @type Node - * @protected - */ - floatingFooterTitleRow: 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 Node + * @type Number * @protected */ firstUserCellBottom: 0, @@ -327,22 +329,43 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstUserCellLeft - * @type Node + * @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 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 Node + * @type Number * @protected */ lastUserCellTop: 0, + /** + * A list of Nodes representing the generic floating rows. + * + * @property floatingHeaderRow + * @type Node{} + * @protected + */ + floatingHeaderRow: null, + /** * Array of EventHandles. * @@ -374,6 +397,20 @@ FloatingHeaders.prototype = { 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. @@ -409,9 +446,11 @@ FloatingHeaders.prototype = { // 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. @@ -745,41 +784,63 @@ FloatingHeaders.prototype = { * @protected */ _setupFloatingAssignmentFooterTitle: function() { - // We make various references to the header cells. Store it for later. - this.footerCell = Y.one(SELECTORS.FOOTERTITLE); + 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 floatingFooterRow = Y.Node.create(''), - floatingFooterCell = Y.Node.create('
'), - coordinates = this._getRelativeXY(this.headerRow), + var floatingRow = Y.Node.create(''), + floatingCell = Y.Node.create('
'), + coordinates = this._getRelativeXY(origin), width = this.firstUserCell.getComputedStyle(WIDTH), - height = this.footerCell.get(OFFSETHEIGHT); + height = origin.get(OFFSETHEIGHT); // Append the content and style to the floating cell. - floatingFooterCell - .set('innerHTML', this.footerCell.getHTML()) - .setAttribute('class', this.footerCell.getAttribute('class')) + 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. - floatingFooterRow + floatingRow .setStyles({ position: 'absolute', + top: coordinates[1] + 'px', left: coordinates[0] + 'px', - bottom: '1px', - height: height + 'px', - width: width - }); + 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. - floatingFooterRow.append(floatingFooterCell); - this.graderRegion.append(floatingFooterRow); + floatingRow.append(floatingCell); + this.graderRegion.append(floatingRow); // Store a reference to this for later - we use it in the event handlers. - this.floatingFooterTitleRow = floatingFooterRow; + this.floatingHeaderRow[headerSelector] = floatingRow; }, /** @@ -798,15 +859,16 @@ FloatingHeaders.prototype = { userColumnHeaderStyles = {}, userColumnStyles = {}, footerStyles = {}, - footerTitleStyles = {}, coord = 0, - userCellWidth = 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, - footerTitleFloats = false; + leftTitleFloats = false, + floatingHeaderStyles = {}, + floatingFooterTitleStyles = {}, + floatingFooterTitleRow = false; // Header position. gradeItemHeadingContainerStyles.left = this._getRelativeXFromX(this.headerRow.getX()); @@ -829,30 +891,36 @@ FloatingHeaders.prototype = { } // User column position. - userCellWidth = this.firstUserCell.get(OFFSETWIDTH); if (right_to_left()) { floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset; - floatingUserRelativePoint = floatingUserTriggerPoint - userCellWidth; - userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + userCellWidth); - footerTitleFloats = floatingUserTriggerPoint < (this.firstNonUserCellLeft + userCellWidth); + floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth; + userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth); + leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) < + (this.firstNonUserCellLeft + this.firstUserCellWidth); } else { floatingUserTriggerPoint = Y.config.win.pageXOffset; floatingUserRelativePoint = floatingUserTriggerPoint; userFloats = floatingUserTriggerPoint > this.firstUserCellLeft; - footerTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - userCellWidth); + leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth); } if (userFloats) { coord = this._getRelativeXFromX(floatingUserRelativePoint); userColumnStyles.left = coord + 'px'; userColumnHeaderStyles.left = coord + 'px'; - footerTitleStyles.left = userColumnStyles.left; } 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()); @@ -864,18 +932,22 @@ FloatingHeaders.prototype = { 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 = footerTitleStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; + footerStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; footerFloats = true; } else { // The footer should not float any more. footerStyles.bottom = '1px'; footerFloats = false; - - // The footer title may float if the page scrolls right. - footerTitleStyles.bottom = '1px'; } + if (floatingFooterTitleStyles) { + floatingFooterTitleStyles.bottom = footerStyles.bottom; + floatingFooterTitleStyles.top = null; + } + floatingHeaderStyles[SELECTORS.FOOTERTITLE] = floatingFooterTitleStyles; } // Apply the styles. @@ -883,7 +955,11 @@ FloatingHeaders.prototype = { this.userColumnHeader.setStyles(userColumnHeaderStyles); this.userColumn.setStyles(userColumnStyles); this.footerRow.setStyles(footerStyles); - this.floatingFooterTitleRow.setStyles(footerTitleStyles); + + // And apply the styles to the generic left headers. + Y.Object.each(floatingHeaderStyles, function(styles, key) { + this.floatingHeaderRow[key].setStyles(styles); + }, this); // Mark the elements as floating, or not. if (headerFloats) { @@ -906,11 +982,23 @@ FloatingHeaders.prototype = { this.footerRow.removeClass(CSS.FLOATING); } - if (this.footerRow && footerTitleFloats) { - this.floatingFooterTitleRow.addClass(CSS.FLOATING); - } else { - this.floatingFooterTitleRow.removeClass(CSS.FLOATING); + Y.Object.each(this.floatingHeaderRow, function(value, 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); + } } + }, /** @@ -968,12 +1056,14 @@ FloatingHeaders.prototype = { }; footercell.setStyles(styles); }); - - // Resize the title area too. - this.floatingFooterTitleRow.one('div').setStyle('width', userWidth); } } + // Resize the title areas too. + Y.Object.each(this.floatingHeaderRow, function(row) { + row.one('div').setStyle('width', userWidth); + }, this); + this.gradeItemHeadingContainer.setStyle('width', newcontainerwidth); } diff --git a/grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js b/grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js index 832db8925fe..85c93c6748b 100644 --- a/grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js +++ b/grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js @@ -105,11 +105,22 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstNonUserCellLeft - * @type Node + * @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. @@ -158,22 +169,13 @@ FloatingHeaders.prototype = { */ userColumn: null, - /** - * A Node representing the floating footer title header. This is the header with the "Overage Average" title. - * - * @property floatingFooterTitleRow - * @type Node - * @protected - */ - floatingFooterTitleRow: 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 Node + * @type Number * @protected */ firstUserCellBottom: 0, @@ -184,22 +186,43 @@ FloatingHeaders.prototype = { * additional rows are loaded, or the window changes in some fashion. * * @property firstUserCellLeft - * @type Node + * @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 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 Node + * @type Number * @protected */ lastUserCellTop: 0, + /** + * A list of Nodes representing the generic floating rows. + * + * @property floatingHeaderRow + * @type Node{} + * @protected + */ + floatingHeaderRow: null, + /** * Array of EventHandles. * @@ -231,6 +254,20 @@ FloatingHeaders.prototype = { 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. @@ -266,9 +303,11 @@ FloatingHeaders.prototype = { // 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. @@ -603,41 +642,63 @@ FloatingHeaders.prototype = { * @protected */ _setupFloatingAssignmentFooterTitle: function() { - // We make various references to the header cells. Store it for later. - this.footerCell = Y.one(SELECTORS.FOOTERTITLE); + 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 floatingFooterRow = Y.Node.create(''), - floatingFooterCell = Y.Node.create('
'), - coordinates = this._getRelativeXY(this.headerRow), + var floatingRow = Y.Node.create(''), + floatingCell = Y.Node.create('
'), + coordinates = this._getRelativeXY(origin), width = this.firstUserCell.getComputedStyle(WIDTH), - height = this.footerCell.get(OFFSETHEIGHT); + height = origin.get(OFFSETHEIGHT); // Append the content and style to the floating cell. - floatingFooterCell - .set('innerHTML', this.footerCell.getHTML()) - .setAttribute('class', this.footerCell.getAttribute('class')) + 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. - floatingFooterRow + floatingRow .setStyles({ position: 'absolute', + top: coordinates[1] + 'px', left: coordinates[0] + 'px', - bottom: '1px', - height: height + 'px', - width: width - }); + 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. - floatingFooterRow.append(floatingFooterCell); - this.graderRegion.append(floatingFooterRow); + floatingRow.append(floatingCell); + this.graderRegion.append(floatingRow); // Store a reference to this for later - we use it in the event handlers. - this.floatingFooterTitleRow = floatingFooterRow; + this.floatingHeaderRow[headerSelector] = floatingRow; }, /** @@ -656,15 +717,16 @@ FloatingHeaders.prototype = { userColumnHeaderStyles = {}, userColumnStyles = {}, footerStyles = {}, - footerTitleStyles = {}, coord = 0, - userCellWidth = 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, - footerTitleFloats = false; + leftTitleFloats = false, + floatingHeaderStyles = {}, + floatingFooterTitleStyles = {}, + floatingFooterTitleRow = false; // Header position. gradeItemHeadingContainerStyles.left = this._getRelativeXFromX(this.headerRow.getX()); @@ -687,30 +749,36 @@ FloatingHeaders.prototype = { } // User column position. - userCellWidth = this.firstUserCell.get(OFFSETWIDTH); if (right_to_left()) { floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset; - floatingUserRelativePoint = floatingUserTriggerPoint - userCellWidth; - userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + userCellWidth); - footerTitleFloats = floatingUserTriggerPoint < (this.firstNonUserCellLeft + userCellWidth); + floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth; + userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth); + leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) < + (this.firstNonUserCellLeft + this.firstUserCellWidth); } else { floatingUserTriggerPoint = Y.config.win.pageXOffset; floatingUserRelativePoint = floatingUserTriggerPoint; userFloats = floatingUserTriggerPoint > this.firstUserCellLeft; - footerTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - userCellWidth); + leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth); } if (userFloats) { coord = this._getRelativeXFromX(floatingUserRelativePoint); userColumnStyles.left = coord + 'px'; userColumnHeaderStyles.left = coord + 'px'; - footerTitleStyles.left = userColumnStyles.left; } 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()); @@ -722,18 +790,22 @@ FloatingHeaders.prototype = { 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 = footerTitleStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; + footerStyles.bottom = Math.ceil(footerBottomPosition - bottomScrollPosition) + 'px'; footerFloats = true; } else { // The footer should not float any more. footerStyles.bottom = '1px'; footerFloats = false; - - // The footer title may float if the page scrolls right. - footerTitleStyles.bottom = '1px'; } + if (floatingFooterTitleStyles) { + floatingFooterTitleStyles.bottom = footerStyles.bottom; + floatingFooterTitleStyles.top = null; + } + floatingHeaderStyles[SELECTORS.FOOTERTITLE] = floatingFooterTitleStyles; } // Apply the styles. @@ -741,7 +813,11 @@ FloatingHeaders.prototype = { this.userColumnHeader.setStyles(userColumnHeaderStyles); this.userColumn.setStyles(userColumnStyles); this.footerRow.setStyles(footerStyles); - this.floatingFooterTitleRow.setStyles(footerTitleStyles); + + // And apply the styles to the generic left headers. + Y.Object.each(floatingHeaderStyles, function(styles, key) { + this.floatingHeaderRow[key].setStyles(styles); + }, this); // Mark the elements as floating, or not. if (headerFloats) { @@ -764,11 +840,23 @@ FloatingHeaders.prototype = { this.footerRow.removeClass(CSS.FLOATING); } - if (this.footerRow && footerTitleFloats) { - this.floatingFooterTitleRow.addClass(CSS.FLOATING); - } else { - this.floatingFooterTitleRow.removeClass(CSS.FLOATING); + Y.Object.each(this.floatingHeaderRow, function(value, 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); + } } + }, /** @@ -826,12 +914,14 @@ FloatingHeaders.prototype = { }; footercell.setStyles(styles); }); - - // Resize the title area too. - this.floatingFooterTitleRow.one('div').setStyle('width', userWidth); } } + // Resize the title areas too. + Y.Object.each(this.floatingHeaderRow, function(row) { + row.one('div').setStyle('width', userWidth); + }, this); + this.gradeItemHeadingContainer.setStyle('width', newcontainerwidth); } diff --git a/grade/report/grader/yui/src/gradereporttable/js/gradereporttable.js b/grade/report/grader/yui/src/gradereporttable/js/gradereporttable.js index cb2035f0580..bbdbd8fba10 100644 --- a/grade/report/grader/yui/src/gradereporttable/js/gradereporttable.js +++ b/grade/report/grader/yui/src/gradereporttable/js/gradereporttable.js @@ -28,7 +28,7 @@ */ var SELECTORS = { - FOOTERTITLE: '#user-grades .avg .header', + FOOTERTITLE: '.avg .header', FOOTERCELLS: '#user-grades .avg .cell', FOOTERROW: '#user-grades .avg', GRADECELL: 'td.grade',