[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 /** 2 * Grader report namespace 3 */ 4 M.gradereport_grader = { 5 /** 6 * @namespace M.gradereport_grader 7 * @param {Object} reports A collection of classes used by the grader report module 8 */ 9 classes : {}, 10 /** 11 * Instantiates a new grader report 12 * 13 * @function 14 * @param {YUI} Y 15 * @param {Object} cfg A configuration object 16 * @param {Array} An array of items in the report 17 * @param {Array} An array of users on the report 18 * @param {Array} An array of feedback objects 19 * @param {Array} An array of student grades 20 */ 21 init_report : function(Y, cfg, items, users, feedback, grades) { 22 // Create the actual report 23 new this.classes.report(Y, cfg, items, users, feedback, grades); 24 } 25 }; 26 27 /** 28 * Initialises the JavaScript for the gradebook grader report 29 * 30 * The functions fall into 3 groups: 31 * M.gradereport_grader.classes.ajax Used when editing is off and fields are dynamically added and removed 32 * M.gradereport_grader.classes.existingfield Used when editing is on meaning all fields are already displayed 33 * M.gradereport_grader.classes.report Common to both of the above 34 * 35 * @class report 36 * @constructor 37 * @this {M.gradereport_grader} 38 * @param {YUI} Y 39 * @param {Object} cfg Configuration variables 40 * @param {Array} items An array containing grade items 41 * @param {Array} users An array containing user information 42 * @param {Array} feedback An array containing feedback information 43 */ 44 M.gradereport_grader.classes.report = function(Y, cfg, items, users, feedback, grades) { 45 this.Y = Y; 46 this.isediting = (cfg.isediting); 47 this.ajaxenabled = (cfg.ajaxenabled); 48 this.items = items; 49 this.users = users; 50 this.feedback = feedback; 51 this.table = Y.one('#user-grades'); 52 this.grades = grades; 53 54 // If ajax is enabled then initialise the ajax component 55 if (this.ajaxenabled) { 56 this.ajax = new M.gradereport_grader.classes.ajax(this, cfg); 57 } 58 }; 59 /** 60 * Extend the report class with the following methods and properties 61 */ 62 M.gradereport_grader.classes.report.prototype.table = null; // YUI Node for the reports main table 63 M.gradereport_grader.classes.report.prototype.items = []; // Array containing grade items 64 M.gradereport_grader.classes.report.prototype.users = []; // Array containing user information 65 M.gradereport_grader.classes.report.prototype.feedback = []; // Array containing feedback items 66 M.gradereport_grader.classes.report.prototype.ajaxenabled = false; // True is AJAX is enabled for the report 67 M.gradereport_grader.classes.report.prototype.ajax = null; // An instance of the ajax class or null 68 /** 69 * Builds an object containing information at the relevant cell given either 70 * the cell to get information for or an array containing userid and itemid 71 * 72 * @function 73 * @this {M.gradereport_grader} 74 * @param {Y.Node|Array} arg Either a YUI Node instance or an array containing 75 * the userid and itemid to reference 76 * @return {Object} 77 */ 78 M.gradereport_grader.classes.report.prototype.get_cell_info = function(arg) { 79 80 var userid= null; 81 var itemid = null; 82 var feedback = ''; // Don't default feedback to null or string comparisons become error prone 83 var cell = null; 84 var i = null; 85 86 if (arg instanceof this.Y.Node) { 87 if (arg.get('nodeName').toUpperCase() !== 'TD') { 88 arg = arg.ancestor('td.cell'); 89 } 90 var regexp = /^u(\d+)i(\d+)$/; 91 var parts = regexp.exec(arg.getAttribute('id')); 92 userid = parts[1]; 93 itemid = parts[2]; 94 cell = arg; 95 } else { 96 userid = arg[0]; 97 itemid = arg[1]; 98 cell = this.Y.one('#u'+userid+'i'+itemid); 99 } 100 101 if (!cell) { 102 return null; 103 } 104 105 return { 106 id : cell.getAttribute('id'), 107 userid : userid, 108 username : this.users[userid], 109 itemid : itemid, 110 itemname : this.items[itemid].name, 111 itemtype : this.items[itemid].type, 112 itemscale : this.items[itemid].scale, 113 itemdp : this.items[itemid].decimals, 114 cell : cell 115 }; 116 }; 117 /** 118 * Updates or creates the feedback JS structure for the given user/item 119 * 120 * @function 121 * @this {M.gradereport_grader} 122 * @param {Int} userid 123 * @param {Int} itemid 124 * @param {String} newfeedback 125 * @return {Bool} 126 */ 127 M.gradereport_grader.classes.report.prototype.update_feedback = function(userid, itemid, newfeedback) { 128 for (var i in this.feedback) { 129 if (this.feedback[i].user == userid && this.feedback[i].item == itemid) { 130 this.feedback[i].content = newfeedback; 131 return true; 132 } 133 } 134 this.feedback.push({user:userid,item:itemid,content:newfeedback}); 135 return true; 136 }; 137 /** 138 * Initialises the AJAX component of this report 139 * @class ajax 140 * @constructor 141 * @this {M.gradereport_grader.ajax} 142 * @param {M.gradereport_grader.classes.report} report 143 * @param {Object} cfg 144 */ 145 M.gradereport_grader.classes.ajax = function(report, cfg) { 146 this.report = report; 147 this.courseid = cfg.courseid || null; 148 this.feedbacktrunclength = cfg.feedbacktrunclength || null; 149 this.studentsperpage = cfg.studentsperpage || null; 150 this.showquickfeedback = cfg.showquickfeedback || false; 151 this.scales = cfg.scales || null; 152 this.existingfields = []; 153 154 if (!report.isediting) { 155 report.table.all('.clickable').on('click', this.make_editable, this); 156 } else { 157 for (var userid in report.users) { 158 if (!this.existingfields[userid]) { 159 this.existingfields[userid] = []; 160 } 161 for (var itemid in report.items) { 162 this.existingfields[userid][itemid] = new M.gradereport_grader.classes.existingfield(this, userid, itemid); 163 } 164 } 165 // Disable the Update button as we're saving using ajax. 166 submitbutton = this.report.Y.one('#gradersubmit'); 167 submitbutton.set('disabled', true); 168 } 169 }; 170 /** 171 * Extend the ajax class with the following methods and properties 172 */ 173 M.gradereport_grader.classes.ajax.prototype.report = null; // A reference to the report class this object will use 174 M.gradereport_grader.classes.ajax.prototype.courseid = null; // The id for the course being viewed 175 M.gradereport_grader.classes.ajax.prototype.feedbacktrunclength = null; // The length to truncate feedback to 176 M.gradereport_grader.classes.ajax.prototype.studentsperpage = null; // The number of students shown per page 177 M.gradereport_grader.classes.ajax.prototype.showquickfeedback = null; // True if feedback editing should be shown 178 M.gradereport_grader.classes.ajax.prototype.current = null; // The field being currently editing 179 M.gradereport_grader.classes.ajax.prototype.pendingsubmissions = []; // Array containing pending IO transactions 180 M.gradereport_grader.classes.ajax.prototype.scales = []; // An array of scales used in this report 181 /** 182 * Makes a cell editable 183 * @function 184 * @this {M.gradereport_grader.classes.ajax} 185 */ 186 M.gradereport_grader.classes.ajax.prototype.make_editable = function(e) { 187 var node = e; 188 if (e.halt) { 189 e.halt(); 190 node = e.target; 191 } 192 if (node.get('nodeName').toUpperCase() !== 'TD') { 193 node = node.ancestor('td'); 194 } 195 this.report.Y.detach('click', this.make_editable, node); 196 197 if (this.current) { 198 // Current is already set! 199 this.process_editable_field(node); 200 return; 201 } 202 203 // Sort out the field type 204 var fieldtype = 'text'; 205 if (node.hasClass('grade_type_scale')) { 206 fieldtype = 'scale'; 207 } 208 // Create the appropriate field widget 209 switch (fieldtype) { 210 case 'scale': 211 this.current = new M.gradereport_grader.classes.scalefield(this.report, node); 212 break; 213 case 'text': 214 default: 215 this.current = new M.gradereport_grader.classes.textfield(this.report, node); 216 break; 217 } 218 this.current.replace().attach_key_events(); 219 220 // Fire the global resized event for the gradereport_grader to update the table row/column sizes. 221 Y.Global.fire('moodle-gradereport_grader:resized'); 222 }; 223 /** 224 * Callback function for the user pressing the enter key on an editable field 225 * 226 * @function 227 * @this {M.gradereport_grader.classes.ajax} 228 * @param {Event} e 229 */ 230 M.gradereport_grader.classes.ajax.prototype.keypress_enter = function(e) { 231 this.process_editable_field(null); 232 }; 233 /** 234 * Callback function for the user pressing Tab or Shift+Tab 235 * 236 * @function 237 * @this {M.gradereport_grader.classes.ajax} 238 * @param {Event} e 239 * @param {Bool} ignoreshift If true and shift is pressed then don't exec 240 */ 241 M.gradereport_grader.classes.ajax.prototype.keypress_tab = function(e, ignoreshift) { 242 var next = null; 243 if (e.shiftKey) { 244 if (ignoreshift) { 245 return; 246 } 247 next = this.get_above_cell(); 248 } else { 249 next = this.get_below_cell(); 250 } 251 this.process_editable_field(next); 252 }; 253 /** 254 * Callback function for the user pressing an CTRL + an arrow key 255 * 256 * @function 257 * @this {M.gradereport_grader.classes.ajax} 258 */ 259 M.gradereport_grader.classes.ajax.prototype.keypress_arrows = function(e) { 260 e.preventDefault(); 261 var next = null; 262 switch (e.keyCode) { 263 case 37: // Left 264 next = this.get_prev_cell(); 265 break; 266 case 38: // Up 267 next = this.get_above_cell(); 268 break; 269 case 39: // Right 270 next = this.get_next_cell(); 271 break; 272 case 40: // Down 273 next = this.get_below_cell(); 274 break; 275 } 276 this.process_editable_field(next); 277 }; 278 /** 279 * Processes an editable field an does what ever is required to update it 280 * 281 * @function 282 * @this {M.gradereport_grader.classes.ajax} 283 * @param {Y.Node|null} next The next node to make editable (chaining) 284 */ 285 M.gradereport_grader.classes.ajax.prototype.process_editable_field = function(next) { 286 if (this.current.has_changed()) { 287 var properties = this.report.get_cell_info(this.current.node); 288 var values = this.current.commit(); 289 this.current.revert(); 290 this.submit(properties, values); 291 } else { 292 this.current.revert(); 293 } 294 this.current = null; 295 if (next) { 296 this.make_editable(next, null); 297 } 298 299 // Fire the global resized event for the gradereport_grader to update the table row/column sizes. 300 Y.Global.fire('moodle-gradereport_grader:resized'); 301 }; 302 /** 303 * Gets the next cell that is editable (right) 304 * @function 305 * @this {M.gradereport_grader.classes.ajax} 306 * @param {Y.Node} cell 307 * @return {Y.Node} 308 */ 309 M.gradereport_grader.classes.ajax.prototype.get_next_cell = function(cell) { 310 var n = cell || this.current.node; 311 var next = n.next('td'); 312 var tr = null; 313 if (!next && (tr = n.ancestor('tr').next('tr'))) { 314 next = tr.all('.grade').item(0); 315 } 316 if (!next) { 317 next = this.current.node; 318 } 319 return next; 320 }; 321 /** 322 * Gets the previous cell that is editable (left) 323 * @function 324 * @this {M.gradereport_grader.classes.ajax} 325 * @param {Y.Node} cell 326 * @return {Y.Node} 327 */ 328 M.gradereport_grader.classes.ajax.prototype.get_prev_cell = function(cell) { 329 var n = cell || this.current.node; 330 var next = n.previous('.grade'); 331 var tr = null; 332 if (!next && (tr = n.ancestor('tr').previous('tr'))) { 333 var cells = tr.all('.grade'); 334 next = cells.item(cells.size()-1); 335 } 336 if (!next) { 337 next = this.current.node; 338 } 339 return next; 340 }; 341 /** 342 * Gets the cell above if it is editable (up) 343 * @function 344 * @this {M.gradereport_grader.classes.ajax} 345 * @param {Y.Node} cell 346 * @return {Y.Node} 347 */ 348 M.gradereport_grader.classes.ajax.prototype.get_above_cell = function(cell) { 349 var n = cell || this.current.node; 350 var tr = n.ancestor('tr').previous('tr'); 351 var next = null; 352 if (tr) { 353 var column = 0; 354 var ntemp = n; 355 while (ntemp = ntemp.previous('td.cell')) { 356 column++; 357 } 358 next = tr.all('td.cell').item(column); 359 } 360 if (!next) { 361 next = this.current.node; 362 } 363 return next; 364 }; 365 /** 366 * Gets the cell below if it is editable (down) 367 * @function 368 * @this {M.gradereport_grader.classes.ajax} 369 * @param {Y.Node} cell 370 * @return {Y.Node} 371 */ 372 M.gradereport_grader.classes.ajax.prototype.get_below_cell = function(cell) { 373 var n = cell || this.current.node; 374 var tr = n.ancestor('tr').next('tr'); 375 var next = null; 376 if (tr && !tr.hasClass('avg')) { 377 var column = 0; 378 var ntemp = n; 379 while (ntemp = ntemp.previous('td.cell')) { 380 column++; 381 } 382 next = tr.all('td.cell').item(column); 383 } 384 // next will be null when we get to the bottom of a column 385 return next; 386 }; 387 /** 388 * Submits changes for update 389 * 390 * @function 391 * @this {M.gradereport_grader.classes.ajax} 392 * @param {Object} properties Properties of the cell being edited 393 * @param {Object} values Object containing old + new values 394 */ 395 M.gradereport_grader.classes.ajax.prototype.submit = function(properties, values) { 396 // Stop the IO queue so we can add to it 397 this.report.Y.io.queue.stop(); 398 // If the grade has changed add an IO transaction to update it to the queue 399 if (values.grade !== values.oldgrade) { 400 this.pendingsubmissions.push({transaction:this.report.Y.io.queue(M.cfg.wwwroot+'/grade/report/grader/ajax_callbacks.php', { 401 method : 'POST', 402 data : 'id='+this.courseid+'&userid='+properties.userid+'&itemid='+properties.itemid+'&action=update&newvalue='+values.grade+'&type='+properties.itemtype+'&sesskey='+M.cfg.sesskey, 403 on : { 404 complete : this.submission_outcome 405 }, 406 context : this, 407 arguments : { 408 properties : properties, 409 values : values, 410 type : 'grade' 411 } 412 }),complete:false,outcome:null}); 413 } 414 // If feedback is editable and has changed add to the IO queue for it 415 if (values.editablefeedback && values.feedback !== values.oldfeedback) { 416 this.pendingsubmissions.push({transaction:this.report.Y.io.queue(M.cfg.wwwroot+'/grade/report/grader/ajax_callbacks.php', { 417 method : 'POST', 418 data : 'id='+this.courseid+'&userid='+properties.userid+'&itemid='+properties.itemid+'&action=update&newvalue='+values.feedback+'&type=feedback&sesskey='+M.cfg.sesskey, 419 on : { 420 complete : this.submission_outcome 421 }, 422 context : this, 423 arguments : { 424 properties : properties, 425 values : values, 426 type : 'feedback' 427 } 428 }),complete:false,outcome:null}); 429 } 430 // Process the IO queue 431 this.report.Y.io.queue.start(); 432 }; 433 /** 434 * Callback function for IO transaction completions 435 * 436 * Uses a synchronous queue to ensure we maintain some sort of order 437 * 438 * @function 439 * @this {M.gradereport_grader.classes.ajax} 440 * @param {Int} tid Transaction ID 441 * @param {Object} outcome 442 * @param {Mixed} args 443 */ 444 M.gradereport_grader.classes.ajax.prototype.submission_outcome = function(tid, outcome, args) { 445 // Parse the response as JSON 446 try { 447 outcome = this.report.Y.JSON.parse(outcome.responseText); 448 } catch(e) { 449 var message = M.str.gradereport_grader.ajaxfailedupdate; 450 message = message.replace(/\[1\]/, args.type); 451 message = message.replace(/\[2\]/, this.report.users[args.properties.userid]); 452 453 this.display_submission_error(message, args.properties.cell); 454 return; 455 } 456 457 // Quick reference for the grader report 458 var i = null; 459 // Check the outcome 460 if (outcome.result == 'success') { 461 // Iterate through each row in the result object 462 for (i in outcome.row) { 463 if (outcome.row[i] && outcome.row[i].userid && outcome.row[i].itemid) { 464 // alias it, we use it quite a bit 465 var r = outcome.row[i]; 466 // Get the cell referred to by this result object 467 var info = this.report.get_cell_info([r.userid, r.itemid]); 468 if (!info) { 469 continue; 470 } 471 // Calculate the final grade for the cell 472 var finalgrade = ''; 473 if (!r.finalgrade) { 474 if (this.report.isediting) { 475 // In edit mode don't put hyphens in the grade text boxes 476 finalgrade = ''; 477 } else { 478 // In non-edit mode put a hyphen in the grade cell 479 finalgrade = '-'; 480 } 481 } else { 482 if (r.scale) { 483 finalgrade = this.scales[r.scale][parseFloat(r.finalgrade)-1]; 484 } else { 485 finalgrade = parseFloat(r.finalgrade).toFixed(info.itemdp); 486 } 487 } 488 if (this.report.isediting) { 489 if (args.properties.itemtype == 'scale') { 490 info.cell.one('#grade_'+r.userid+'_'+r.itemid).all('options').each(function(option){ 491 if (option.get('value') == finalgrade) { 492 option.setAttribute('selected', 'selected'); 493 } else { 494 option.removeAttribute('selected'); 495 } 496 }); 497 } else { 498 info.cell.one('#grade_'+r.userid+'_'+r.itemid).set('value', finalgrade); 499 } 500 } else { 501 // If there is no currently editing field or if this cell is not being currently edited 502 if (!this.current || info.cell.get('id') != this.current.node.get('id')) { 503 // Update the value 504 info.cell.one('.gradevalue').set('innerHTML',finalgrade); 505 } else if (this.current && info.cell.get('id') == this.current.node.get('id')) { 506 // If we are here the grade value of the cell currently being edited has changed !!!!!!!!! 507 // If the user has not actually changed the old value yet we will automatically correct it 508 // otherwise we will prompt the user to choose to use their value or the new value! 509 if (!this.current.has_changed() || confirm(M.str.gradereport_grader.ajaxfieldchanged)) { 510 this.current.set_grade(finalgrade); 511 this.current.grade.set('value', finalgrade); 512 } 513 } 514 } 515 } 516 } 517 // Flag the changed cell as overridden by ajax 518 args.properties.cell.addClass('ajaxoverridden'); 519 } else { 520 var p = args.properties; 521 if (args.type == 'grade') { 522 var oldgrade = args.values.oldgrade; 523 p.cell.one('.gradevalue').set('innerHTML',oldgrade); 524 } else if (args.type == 'feedback') { 525 this.report.update_feedback(p.userid, p.itemid, args.values.oldfeedback); 526 } 527 this.display_submission_error(outcome.message, p.cell); 528 } 529 // Check if all IO transactions in the queue are complete yet 530 var allcomplete = true; 531 for (i in this.pendingsubmissions) { 532 if (this.pendingsubmissions[i]) { 533 if (this.pendingsubmissions[i].transaction.id == tid) { 534 this.pendingsubmissions[i].complete = true; 535 this.pendingsubmissions[i].outcome = outcome; 536 this.report.Y.io.queue.remove(this.pendingsubmissions[i].transaction); 537 } 538 if (!this.pendingsubmissions[i].complete) { 539 allcomplete = false; 540 } 541 } 542 } 543 if (allcomplete) { 544 this.pendingsubmissions = []; 545 } 546 }; 547 /** 548 * Displays a submission error within a overlay on the cell that failed update 549 * 550 * @function 551 * @this {M.gradereport_grader.classes.ajax} 552 * @param {String} message 553 * @param {Y.Node} cell 554 */ 555 M.gradereport_grader.classes.ajax.prototype.display_submission_error = function(message, cell) { 556 var erroroverlay = new this.report.Y.Overlay({ 557 headerContent : '<div><strong class="error">'+M.str.gradereport_grader.ajaxerror+'</strong> <em>'+M.str.gradereport_grader.ajaxclicktoclose+'</em></div>', 558 bodyContent : message, 559 visible : false, 560 zIndex : 3 561 }); 562 erroroverlay.set('xy', [cell.getX()+10,cell.getY()+10]); 563 erroroverlay.render(this.report.table.ancestor('div')); 564 erroroverlay.show(); 565 erroroverlay.get('boundingBox').on('click', function(){ 566 this.get('boundingBox').setStyle('visibility', 'hidden'); 567 this.hide(); 568 this.destroy(); 569 }, erroroverlay); 570 erroroverlay.get('boundingBox').setStyle('visibility', 'visible'); 571 }; 572 /** 573 * A class for existing fields 574 * This class is used only when the user is in editing mode 575 * 576 * @class existingfield 577 * @constructor 578 * @param {M.gradereport_grader.classes.report} report 579 * @param {Int} userid 580 * @param {Int} itemid 581 */ 582 M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) { 583 this.report = ajax.report; 584 this.userid = userid; 585 this.itemid = itemid; 586 this.editfeedback = ajax.showquickfeedback; 587 this.grade = this.report.Y.one('#grade_'+userid+'_'+itemid); 588 589 if (this.report.grades) { 590 for (var i = 0; i < this.report.grades.length; i++) { 591 if (this.report.grades[i]['user']==this.userid && this.report.grades[i]['item']==this.itemid) { 592 this.oldgrade = this.report.grades[i]['grade']; 593 } 594 } 595 } 596 597 if (!this.oldgrade) { 598 // Assigning an empty string makes determining whether the grade has been changed easier 599 // This value is never sent to the server 600 this.oldgrade = ''; 601 } 602 603 // On blur save any changes in the grade field 604 this.grade.on('blur', this.submit, this); 605 606 // Check if feedback is enabled 607 if (this.editfeedback) { 608 // Get the feedback fields 609 this.feedback = this.report.Y.one('#feedback_'+userid+'_'+itemid); 610 611 for(var i = 0; i < this.report.feedback.length; i++) { 612 if (this.report.feedback[i]['user']==this.userid && this.report.feedback[i]['item']==this.itemid) { 613 this.oldfeedback = this.report.feedback[i]['content']; 614 } 615 } 616 617 if(!this.oldfeedback) { 618 // Assigning an empty string makes determining whether the feedback has been changed easier 619 // This value is never sent to the server 620 this.oldfeedback = ''; 621 } 622 623 // On blur save any changes in the feedback field 624 this.feedback.on('blur', this.submit, this); 625 626 // Override the default tab movements when moving between cells 627 this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this)); // Handle Shift+Tab 628 this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true)); // Handle Tab 629 this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this)); // Handle the Enter key being pressed 630 this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys 631 632 // Override the default tab movements for fields in the same cell 633 this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();this.grade.focus();}, this.feedback, 'press:9+shift', this)); 634 this.keyevents.push(this.report.Y.on('key', function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();}, this.grade, 'press:9', this)); 635 } else { 636 this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this)); // Handle Tab and Shift+Tab 637 } 638 this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this)); // Handle the Enter key being pressed 639 this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys 640 }; 641 /** 642 * Attach the required properties and methods to the existing field class 643 * via prototyping 644 */ 645 M.gradereport_grader.classes.existingfield.prototype.userid = null; 646 M.gradereport_grader.classes.existingfield.prototype.itemid = null; 647 M.gradereport_grader.classes.existingfield.prototype.editfeedback = false; 648 M.gradereport_grader.classes.existingfield.prototype.grade = null; 649 M.gradereport_grader.classes.existingfield.prototype.oldgrade = null; 650 M.gradereport_grader.classes.existingfield.prototype.keyevents = []; 651 /** 652 * Handles saving of changed on keypress 653 * 654 * @function 655 * @this {M.gradereport_grader.classes.existingfield} 656 * @param {Event} e 657 */ 658 M.gradereport_grader.classes.existingfield.prototype.keypress_enter = function(e) { 659 e.preventDefault(); 660 this.submit(); 661 }; 662 /** 663 * Handles setting the correct focus if the user presses tab 664 * 665 * @function 666 * @this {M.gradereport_grader.classes.existingfield} 667 * @param {Event} e 668 * @param {Bool} ignoreshift 669 */ 670 M.gradereport_grader.classes.existingfield.prototype.keypress_tab = function(e, ignoreshift) { 671 e.preventDefault(); 672 var next = null; 673 if (e.shiftKey) { 674 if (ignoreshift) { 675 return; 676 } 677 next = this.report.ajax.get_above_cell(this.grade.ancestor('td')); 678 } else { 679 next = this.report.ajax.get_below_cell(this.grade.ancestor('td')); 680 } 681 this.move_focus(next); 682 }; 683 /** 684 * Handles setting the correct focus when the user presses CTRL+arrow keys 685 * 686 * @function 687 * @this {M.gradereport_grader.classes.existingfield} 688 * @param {Event} e 689 */ 690 M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(e) { 691 var next = null; 692 switch (e.keyCode) { 693 case 37: // Left 694 next = this.report.ajax.get_prev_cell(this.grade.ancestor('td')); 695 break; 696 case 38: // Up 697 next = this.report.ajax.get_above_cell(this.grade.ancestor('td')); 698 break; 699 case 39: // Right 700 next = this.report.ajax.get_next_cell(this.grade.ancestor('td')); 701 break; 702 case 40: // Down 703 next = this.report.ajax.get_below_cell(this.grade.ancestor('td')); 704 break; 705 } 706 this.move_focus(next); 707 }; 708 /** 709 * Move the focus to the node 710 * @function 711 * @this {M.gradereport_grader.classes.existingfield} 712 * @param {Y.Node} node 713 */ 714 M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node) { 715 if (node) { 716 var properties = this.report.get_cell_info(node); 717 switch(properties.itemtype) { 718 case 'scale': 719 properties.cell.one('select.select').focus(); 720 break; 721 case 'value': 722 default: 723 properties.cell.one('input.text').focus(); 724 break; 725 } 726 } 727 }; 728 /** 729 * Checks if the values for the field have changed 730 * 731 * @function 732 * @this {M.gradereport_grader.classes.existingfield} 733 * @return {Bool} 734 */ 735 M.gradereport_grader.classes.existingfield.prototype.has_changed = function() { 736 if (this.editfeedback) { 737 return (this.grade.get('value') !== this.oldgrade || this.feedback.get('value') !== this.oldfeedback); 738 } 739 return (this.grade.get('value') !== this.oldgrade); 740 }; 741 /** 742 * Submits any changes and then updates the fields accordingly 743 * 744 * @function 745 * @this {M.gradereport_grader.classes.existingfield} 746 */ 747 M.gradereport_grader.classes.existingfield.prototype.submit = function() { 748 if (!this.has_changed()) { 749 return; 750 } 751 752 var properties = this.report.get_cell_info([this.userid,this.itemid]); 753 var values = (function(f){ 754 var feedback, oldfeedback = null; 755 if (f.editfeedback) { 756 feedback = f.feedback.get('value'); 757 oldfeedback = f.oldfeedback; 758 } 759 return { 760 editablefeedback : f.editfeedback, 761 grade : f.grade.get('value'), 762 oldgrade : f.oldgrade, 763 feedback : feedback, 764 oldfeedback : oldfeedback 765 }; 766 })(this); 767 768 this.oldgrade = values.grade; 769 if (values.editablefeedback && values.feedback != values.oldfeedback) { 770 this.report.update_feedback(this.userid, this.itemid, values.feedback); 771 this.oldfeedback = values.feedback; 772 } 773 774 this.report.ajax.submit(properties, values); 775 }; 776 777 /** 778 * Textfield class 779 * This classes gets used in conjunction with the report running with AJAX enabled 780 * and is used to manage a cell that has a grade requiring a textfield for input 781 * 782 * @class textfield 783 * @constructor 784 * @this {M.gradereport_grader.classes.textfield} 785 * @param {M.gradereport_grader.classes.report} report 786 * @param {Y.Node} node 787 */ 788 M.gradereport_grader.classes.textfield = function(report, node) { 789 this.report = report; 790 this.node = node; 791 this.gradespan = node.one('.gradevalue'); 792 this.inputdiv = this.report.Y.Node.create('<div></div>'); 793 this.editfeedback = this.report.ajax.showquickfeedback; 794 this.grade = this.report.Y.Node.create('<input type="text" class="text" value="" />'); 795 this.gradetype = 'value'; 796 this.inputdiv.append(this.grade); 797 if (this.report.ajax.showquickfeedback) { 798 this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />'); 799 this.inputdiv.append(this.feedback); 800 } 801 }; 802 /** 803 * Extend the textfield class with the following methods and properties 804 */ 805 M.gradereport_grader.classes.textfield.prototype.keyevents = []; 806 M.gradereport_grader.classes.textfield.prototype.editable = false; 807 M.gradereport_grader.classes.textfield.prototype.gradetype = null; 808 M.gradereport_grader.classes.textfield.prototype.grade = null; 809 M.gradereport_grader.classes.textfield.prototype.report = null; 810 M.gradereport_grader.classes.textfield.prototype.node = null; 811 M.gradereport_grader.classes.textfield.prototype.gradespam = null; 812 M.gradereport_grader.classes.textfield.prototype.inputdiv = null; 813 M.gradereport_grader.classes.textfield.prototype.editfeedback = false; 814 /** 815 * Replaces the cell contents with the controls to enable editing 816 * 817 * @function 818 * @this {M.gradereport_grader.classes.textfield} 819 * @return {M.gradereport_grader.classes.textfield} 820 */ 821 M.gradereport_grader.classes.textfield.prototype.replace = function() { 822 this.set_grade(this.get_grade()); 823 if (this.editfeedback) { 824 this.set_feedback(this.get_feedback()); 825 } 826 this.node.replaceChild(this.inputdiv, this.gradespan); 827 this.grade.focus(); 828 this.editable = true; 829 return this; 830 }; 831 /** 832 * Commits the changes within a cell and returns a result object of new + old values 833 * @function 834 * @this {M.gradereport_grader.classes.textfield} 835 * @return {Object} 836 */ 837 M.gradereport_grader.classes.textfield.prototype.commit = function() { 838 // Produce an anonymous result object contianing all values 839 var result = (function(field){ 840 field.editable = false; 841 var oldgrade = field.get_grade(); 842 if (oldgrade == '-') { 843 oldgrade = ''; 844 } 845 var feedback = null; 846 var oldfeedback = null; 847 if (field.editfeedback) { 848 oldfeedback = field.get_feedback(); 849 } 850 field.editable = true; 851 if (field.editfeedback) { 852 feedback = field.get_feedback(); 853 } 854 return { 855 gradetype : field.gradetype, 856 editablefeedback : field.editfeedback, 857 grade : field.get_grade(), 858 oldgrade : oldgrade, 859 feedback : feedback, 860 oldfeedback : oldfeedback 861 }; 862 })(this); 863 // Set the changes in stone 864 this.set_grade(result.grade); 865 if (this.editfeedback) { 866 this.set_feedback(result.feedback); 867 } 868 // Return the result object 869 return result; 870 }; 871 /** 872 * Reverts a cell back to its static contents 873 * @function 874 * @this {M.gradereport_grader.classes.textfield} 875 */ 876 M.gradereport_grader.classes.textfield.prototype.revert = function() { 877 this.node.replaceChild(this.gradespan, this.inputdiv); 878 for (var i in this.keyevents) { 879 if (this.keyevents[i]) { 880 this.keyevents[i].detach(); 881 } 882 } 883 this.keyevents = []; 884 this.node.on('click', this.report.ajax.make_editable, this.report.ajax); 885 }; 886 /** 887 * Gets the grade for current cell 888 * 889 * @function 890 * @this {M.gradereport_grader.classes.textfield} 891 * @return {Mixed} 892 */ 893 M.gradereport_grader.classes.textfield.prototype.get_grade = function() { 894 if (this.editable) { 895 return this.grade.get('value'); 896 } 897 return this.gradespan.get('innerHTML'); 898 }; 899 /** 900 * Sets the grade for the current cell 901 * @function 902 * @this {M.gradereport_grader.classes.textfield} 903 * @param {Mixed} value 904 */ 905 M.gradereport_grader.classes.textfield.prototype.set_grade = function(value) { 906 if (!this.editable) { 907 if (value == '-') { 908 value = ''; 909 } 910 this.grade.set('value', value); 911 } else { 912 if (value == '') { 913 value = '-'; 914 } 915 this.gradespan.set('innerHTML', value); 916 } 917 }; 918 /** 919 * Gets the feedback for the current cell 920 * @function 921 * @this {M.gradereport_grader.classes.textfield} 922 * @return {String} 923 */ 924 M.gradereport_grader.classes.textfield.prototype.get_feedback = function() { 925 if (this.editable) { 926 return this.feedback.get('value'); 927 } 928 var properties = this.report.get_cell_info(this.node); 929 if (properties) { 930 return properties.feedback || ''; 931 } 932 return ''; 933 }; 934 /** 935 * Sets the feedback for the current cell 936 * @function 937 * @this {M.gradereport_grader.classes.textfield} 938 * @param {Mixed} value 939 */ 940 M.gradereport_grader.classes.textfield.prototype.set_feedback = function(value) { 941 if (!this.editable) { 942 this.feedback.set('value', value); 943 } else { 944 var properties = this.report.get_cell_info(this.node); 945 this.report.update_feedback(properties.userid, properties.itemid, value); 946 } 947 }; 948 /** 949 * Checks if the current cell has changed at all 950 * @function 951 * @this {M.gradereport_grader.classes.textfield} 952 * @return {Bool} 953 */ 954 M.gradereport_grader.classes.textfield.prototype.has_changed = function() { 955 // If its not editable it has not changed 956 if (!this.editable) { 957 return false; 958 } 959 // If feedback is being edited then it has changed if either grade or feedback have changed 960 if (this.editfeedback) { 961 var properties = this.report.get_cell_info(this.node); 962 if (this.get_feedback() != properties.feedback) { 963 return true; 964 } 965 } 966 return (this.get_grade() != this.gradespan.get('innerHTML')); 967 }; 968 /** 969 * Attaches the key listeners for the editable fields and stored the event references 970 * against the textfield 971 * 972 * @function 973 * @this {M.gradereport_grader.classes.textfield} 974 */ 975 M.gradereport_grader.classes.textfield.prototype.attach_key_events = function() { 976 var a = this.report.ajax; 977 // Setup the default key events for tab and enter 978 if (this.editfeedback) { 979 this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a)); // Handle Shift+Tab 980 this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true)); // Handle Tab 981 this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a)); // Handle the Enter key being pressed 982 } else { 983 this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a)); // Handle Tab and Shift+Tab 984 } 985 this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a)); // Handle the Enter key being pressed 986 // Setup the arrow key events 987 this.keyevents.push(this.report.Y.on('key', a.keypress_arrows, this.grade.ancestor('td'), 'down:37,38,39,40+ctrl', a)); // Handle CTRL + arrow keys 988 // Prevent the default key action on all fields for arrow keys on all key events! 989 // Note: this still does not work in FF!!!!! 990 this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl')); 991 this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'press:37,38,39,40+ctrl')); 992 this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'up:37,38,39,40+ctrl')); 993 }; 994 995 /** 996 * An editable scale field 997 * 998 * @class scalefield 999 * @constructor 1000 * @inherits M.gradereport_grader.classes.textfield 1001 * @base M.gradereport_grader.classes.textfield 1002 * @this {M.gradereport_grader.classes.scalefield} 1003 * @param {M.gradereport_grader.classes.report} report 1004 * @param {Y.Node} node 1005 */ 1006 M.gradereport_grader.classes.scalefield = function(report, node) { 1007 this.report = report; 1008 this.node = node; 1009 this.gradespan = node.one('.gradevalue'); 1010 this.inputdiv = this.report.Y.Node.create('<div></div>'); 1011 this.editfeedback = this.report.ajax.showquickfeedback; 1012 this.grade = this.report.Y.Node.create('<select type="text" class="text" /><option value="-1">'+M.str.gradereport_grader.ajaxchoosescale+'</option></select>'); 1013 this.gradetype = 'scale'; 1014 this.inputdiv.append(this.grade); 1015 if (this.editfeedback) { 1016 this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />'); 1017 this.inputdiv.append(this.feedback); 1018 } 1019 var properties = this.report.get_cell_info(node); 1020 this.scale = this.report.ajax.scales[properties.itemscale]; 1021 for (var i in this.scale) { 1022 if (this.scale[i]) { 1023 this.grade.append(this.report.Y.Node.create('<option value="'+(parseFloat(i)+1)+'">'+this.scale[i]+'</option>')); 1024 } 1025 } 1026 }; 1027 /** 1028 * Override + extend the scalefield class with the following properties 1029 * and methods 1030 */ 1031 /** 1032 * @property {Array} scale 1033 */ 1034 M.gradereport_grader.classes.scalefield.prototype.scale = []; 1035 /** 1036 * Extend the scalefield with the functions from the textfield 1037 */ 1038 /** 1039 * Overrides the get_grade function so that it can pick up the value from the 1040 * scales select box 1041 * 1042 * @function 1043 * @this {M.gradereport_grader.classes.scalefield} 1044 * @return {Int} the scale id 1045 */ 1046 M.gradereport_grader.classes.scalefield.prototype.get_grade = function(){ 1047 if (this.editable) { 1048 // Return the scale value 1049 return this.grade.all('option').item(this.grade.get('selectedIndex')).get('value'); 1050 } else { 1051 // Return the scale values id 1052 var value = this.gradespan.get('innerHTML'); 1053 for (var i in this.scale) { 1054 if (this.scale[i] == value) { 1055 return parseFloat(i)+1; 1056 } 1057 } 1058 return -1; 1059 } 1060 }; 1061 /** 1062 * Overrides the set_grade function of textfield so that it can set the scale 1063 * within the scale select box 1064 * 1065 * @function 1066 * @this {M.gradereport_grader.classes.scalefield} 1067 * @param {String} value 1068 */ 1069 M.gradereport_grader.classes.scalefield.prototype.set_grade = function(value) { 1070 if (!this.editable) { 1071 if (value == '-') { 1072 value = '-1'; 1073 } 1074 this.grade.all('option').each(function(node){ 1075 if (node.get('value') == value) { 1076 node.set('selected', true); 1077 } 1078 }); 1079 } else { 1080 if (value == '' || value == '-1') { 1081 value = '-'; 1082 } else { 1083 value = this.scale[parseFloat(value)-1]; 1084 } 1085 this.gradespan.set('innerHTML', value); 1086 } 1087 }; 1088 /** 1089 * Checks if the current cell has changed at all 1090 * @function 1091 * @this {M.gradereport_grader.classes.scalefield} 1092 * @return {Bool} 1093 */ 1094 M.gradereport_grader.classes.scalefield.prototype.has_changed = function() { 1095 if (!this.editable) { 1096 return false; 1097 } 1098 var gradef = this.get_grade(); 1099 this.editable = false; 1100 var gradec = this.get_grade(); 1101 this.editable = true; 1102 if (this.editfeedback) { 1103 var properties = this.report.get_cell_info(this.node); 1104 var feedback = properties.feedback || ''; 1105 return (gradef != gradec || this.get_feedback() != feedback); 1106 } 1107 return (gradef != gradec); 1108 }; 1109 1110 /** 1111 * Manually extend the scalefield class with the properties and methods of the 1112 * textfield class that have not been defined 1113 */ 1114 for (var i in M.gradereport_grader.classes.textfield.prototype) { 1115 if (!M.gradereport_grader.classes.scalefield.prototype[i]) { 1116 M.gradereport_grader.classes.scalefield.prototype[i] = M.gradereport_grader.classes.textfield.prototype[i]; 1117 } 1118 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |