[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/grade/report/grader/ -> module.js (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1