[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/quiz/yui/src/autosave/js/ -> autosave.js (source)

   1  // This file is part of Moodle - http://moodle.org/
   2  //
   3  // Moodle is free software: you can redistribute it and/or modify
   4  // it under the terms of the GNU General Public License as published by
   5  // the Free Software Foundation, either version 3 of the License, or
   6  // (at your option) any later version.
   7  //
   8  // Moodle is distributed in the hope that it will be useful,
   9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11  // GNU General Public License for more details.
  12  //
  13  // You should have received a copy of the GNU General Public License
  14  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  15  
  16  
  17  /**
  18   * Auto-save functionality for during quiz attempts.
  19   *
  20   * @module moodle-mod_quiz-autosave
  21   */
  22  
  23  /**
  24   * Auto-save functionality for during quiz attempts.
  25   *
  26   * @class M.mod_quiz.autosave
  27   */
  28  
  29  M.mod_quiz = M.mod_quiz || {};
  30  M.mod_quiz.autosave = {
  31      /**
  32       * The amount of time (in milliseconds) to wait between TinyMCE detections.
  33       *
  34       * @property TINYMCE_DETECTION_DELAY
  35       * @type Number
  36       * @default 500
  37       * @private
  38       */
  39      TINYMCE_DETECTION_DELAY:  500,
  40  
  41      /**
  42       * The number of times to try redetecting TinyMCE.
  43       *
  44       * @property TINYMCE_DETECTION_REPEATS
  45       * @type Number
  46       * @default 20
  47       * @private
  48       */
  49      TINYMCE_DETECTION_REPEATS: 20,
  50  
  51      /**
  52       * The delay (in milliseconds) between checking hidden input fields.
  53       *
  54       * @property WATCH_HIDDEN_DELAY
  55       * @type Number
  56       * @default 1000
  57       * @private
  58       */
  59      WATCH_HIDDEN_DELAY:      1000,
  60  
  61      /**
  62       * The number of failures to ignore before notifying the user.
  63       *
  64       * @property FAILURES_BEFORE_NOTIFY
  65       * @type Number
  66       * @default 1
  67       * @private
  68       */
  69      FAILURES_BEFORE_NOTIFY:     1,
  70  
  71      /**
  72       * The value to use when resetting the successful save counter.
  73       *
  74       * @property FIRST_SUCCESSFUL_SAVE
  75       * @static
  76       * @type Number
  77       * @default -1
  78       * @private
  79       */
  80      FIRST_SUCCESSFUL_SAVE:     -1,
  81  
  82      /**
  83       * The selectors used throughout this class.
  84       *
  85       * @property SELECTORS
  86       * @private
  87       * @type Object
  88       * @static
  89       */
  90      SELECTORS: {
  91          QUIZ_FORM:             '#responseform',
  92          VALUE_CHANGE_ELEMENTS: 'input, textarea, [contenteditable="true"]',
  93          CHANGE_ELEMENTS:       'input, select',
  94          HIDDEN_INPUTS:         'input[type=hidden]',
  95          CONNECTION_ERROR:      '#connection-error',
  96          CONNECTION_OK:         '#connection-ok'
  97      },
  98  
  99      /**
 100       * The script which handles the autosaves.
 101       *
 102       * @property AUTOSAVE_HANDLER
 103       * @type String
 104       * @default M.cfg.wwwroot + '/mod/quiz/autosave.ajax.php'
 105       * @private
 106       */
 107      AUTOSAVE_HANDLER: M.cfg.wwwroot + '/mod/quiz/autosave.ajax.php',
 108  
 109      /**
 110       * The delay (in milliseconds) between a change being made, and it being auto-saved.
 111       *
 112       * @property delay
 113       * @type Number
 114       * @default 120000
 115       * @private
 116       */
 117      delay: 120000,
 118  
 119      /**
 120       * A Node reference to the form we are monitoring.
 121       *
 122       * @property form
 123       * @type Node
 124       * @default null
 125       */
 126      form: null,
 127  
 128      /**
 129       * Whether the form has been modified since the last save started.
 130       *
 131       * @property dirty
 132       * @type boolean
 133       * @default false
 134       */
 135      dirty: false,
 136  
 137      /**
 138       * Timer object for the delay between form modifaction and the save starting.
 139       *
 140       * @property delay_timer
 141       * @type Object
 142       * @default null
 143       * @private
 144       */
 145      delay_timer: null,
 146  
 147      /**
 148       * Y.io transaction for the save ajax request.
 149       *
 150       * @property save_transaction
 151       * @type object
 152       * @default null
 153       */
 154      save_transaction: null,
 155  
 156      /**
 157       * Failed saves count.
 158       *
 159       * @property savefailures
 160       * @type Number
 161       * @default 0
 162       * @private
 163       */
 164      savefailures: 0,
 165  
 166      /**
 167       * Properly bound key change handler.
 168       *
 169       * @property editor_change_handler
 170       * @type EventHandle
 171       * @default null
 172       * @private
 173       */
 174      editor_change_handler: null,
 175  
 176      /**
 177       * Record of the value of all the hidden fields, last time they were checked.
 178       *
 179       * @property hidden_field_values
 180       * @type Object
 181       * @default {}
 182       */
 183      hidden_field_values: {},
 184  
 185      /**
 186       * Initialise the autosave code.
 187       *
 188       * @method init
 189       * @param {Number} delay the delay, in seconds, between a change being detected, and
 190       * a save happening.
 191       */
 192      init: function(delay) {
 193          this.form = Y.one(this.SELECTORS.QUIZ_FORM);
 194          if (!this.form) {
 195              Y.log('No response form found. Why did you try to set up autosave?', 'debug', 'moodle-mod_quiz-autosave');
 196              return;
 197          }
 198  
 199          this.delay = delay * 1000;
 200  
 201          this.form.delegate('valuechange', this.value_changed, this.SELECTORS.VALUE_CHANGE_ELEMENTS, this);
 202          this.form.delegate('change', this.value_changed, this.SELECTORS.CHANGE_ELEMENTS, this);
 203          this.form.on('submit', this.stop_autosaving, this);
 204  
 205          this.init_tinymce(this.TINYMCE_DETECTION_REPEATS);
 206  
 207          this.save_hidden_field_values();
 208          this.watch_hidden_fields();
 209      },
 210  
 211      save_hidden_field_values: function() {
 212          this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(hidden) {
 213              var name = hidden.get('name');
 214              if (!name) {
 215                  return;
 216              }
 217              this.hidden_field_values[name] = hidden.get('value');
 218          }, this);
 219      },
 220  
 221      watch_hidden_fields: function() {
 222          this.detect_hidden_field_changes();
 223          Y.later(this.WATCH_HIDDEN_DELAY, this, this.watch_hidden_fields);
 224      },
 225  
 226      detect_hidden_field_changes: function() {
 227          this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(hidden) {
 228              var name = hidden.get('name'),
 229                  value = hidden.get('value');
 230              if (!name) {
 231                  return;
 232              }
 233              if (!(name in this.hidden_field_values) || value !== this.hidden_field_values[name]) {
 234                  this.hidden_field_values[name] = value;
 235                  this.value_changed({target: hidden});
 236              }
 237          }, this);
 238      },
 239  
 240      /**
 241       * Initialise watching of TinyMCE specifically.
 242       *
 243       * Because TinyMCE might load slowly, and after us, we need to keep
 244       * trying, until we detect TinyMCE is there, or enough time has passed.
 245       * This is based on the TINYMCE_DETECTION_DELAY and
 246       * TINYMCE_DETECTION_REPEATS properties.
 247       *
 248       *
 249       * @method init_tinymce
 250       * @param {Number} repeatcount The number of attempts made so far.
 251       */
 252      init_tinymce: function(repeatcount) {
 253          if (typeof window.tinyMCE === 'undefined') {
 254              if (repeatcount > 0) {
 255                  Y.later(this.TINYMCE_DETECTION_DELAY, this, this.init_tinymce, [repeatcount - 1]);
 256              } else {
 257                  Y.log('Gave up looking for TinyMCE.', 'debug', 'moodle-mod_quiz-autosave');
 258              }
 259              return;
 260          }
 261  
 262          Y.log('Found TinyMCE.', 'debug', 'moodle-mod_quiz-autosave');
 263          this.editor_change_handler = Y.bind(this.editor_changed, this);
 264          window.tinyMCE.onAddEditor.add(Y.bind(this.init_tinymce_editor, this));
 265      },
 266  
 267      /**
 268       * Initialise watching of a specific TinyMCE editor.
 269       *
 270       * @method init_tinymce_editor
 271       * @param {EventFacade} e
 272       * @param {Object} editor The TinyMCE editor object
 273       */
 274      init_tinymce_editor: function(e, editor) {
 275          Y.log('Found TinyMCE editor ' + editor.id + '.', 'debug', 'moodle-mod_quiz-autosave');
 276          editor.onChange.add(this.editor_change_handler);
 277          editor.onRedo.add(this.editor_change_handler);
 278          editor.onUndo.add(this.editor_change_handler);
 279          editor.onKeyDown.add(this.editor_change_handler);
 280      },
 281  
 282      value_changed: function(e) {
 283          var name = e.target.getAttribute('name');
 284          if (name === 'thispage' || name === 'scrollpos' || (name && name.match(/_:flagged$/))) {
 285              return; // Not interesting.
 286          }
 287  
 288          // Fallback to the ID when the name is not present (in the case of content editable).
 289          name = name || '#' + e.target.getAttribute('id');
 290          Y.log('Detected a value change in element ' + name + '.', 'debug', 'moodle-mod_quiz-autosave');
 291          this.start_save_timer_if_necessary();
 292      },
 293  
 294      editor_changed: function(editor) {
 295          Y.log('Detected a value change in editor ' + editor.id + '.', 'debug', 'moodle-mod_quiz-autosave');
 296          this.start_save_timer_if_necessary();
 297      },
 298  
 299      start_save_timer_if_necessary: function() {
 300          this.dirty = true;
 301  
 302          if (this.delay_timer || this.save_transaction) {
 303              // Already counting down or daving.
 304              return;
 305          }
 306  
 307          this.start_save_timer();
 308      },
 309  
 310      start_save_timer: function() {
 311          this.cancel_delay();
 312          this.delay_timer = Y.later(this.delay, this, this.save_changes);
 313      },
 314  
 315      cancel_delay: function() {
 316          if (this.delay_timer && this.delay_timer !== true) {
 317              this.delay_timer.cancel();
 318          }
 319          this.delay_timer = null;
 320      },
 321  
 322      save_changes: function() {
 323          this.cancel_delay();
 324          this.dirty = false;
 325  
 326          if (this.is_time_nearly_over()) {
 327              Y.log('No more saving, time is nearly over.', 'debug', 'moodle-mod_quiz-autosave');
 328              this.stop_autosaving();
 329              return;
 330          }
 331  
 332          Y.log('Doing a save.', 'debug', 'moodle-mod_quiz-autosave');
 333          if (typeof window.tinyMCE !== 'undefined') {
 334              window.tinyMCE.triggerSave();
 335          }
 336          this.save_transaction = Y.io(this.AUTOSAVE_HANDLER, {
 337              method:  'POST',
 338              form:    {id: this.form},
 339              on:      {
 340                  success: this.save_done,
 341                  failure: this.save_failed
 342              },
 343              context: this
 344          });
 345      },
 346  
 347      save_done: function(transactionid, response) {
 348          if (response.responseText !== 'OK') {
 349              // Because IIS is useless, Moodle can't send proper HTTP response
 350              // codes, so we have to detect failures manually.
 351              this.save_failed(transactionid, response);
 352              return;
 353          }
 354  
 355          Y.log('Save completed.', 'debug', 'moodle-mod_quiz-autosave');
 356          this.save_transaction = null;
 357  
 358          if (this.dirty) {
 359              Y.log('Dirty after save.', 'debug', 'moodle-mod_quiz-autosave');
 360              this.start_save_timer();
 361          }
 362  
 363          if (this.savefailures > 0) {
 364              Y.one(this.SELECTORS.CONNECTION_ERROR).hide();
 365              Y.one(this.SELECTORS.CONNECTION_OK).show();
 366              this.savefailures = this.FIRST_SUCCESSFUL_SAVE;
 367          } else if (this.savefailures === this.FIRST_SUCCESSFUL_SAVE) {
 368              Y.one(this.SELECTORS.CONNECTION_OK).hide();
 369              this.savefailures = 0;
 370          }
 371      },
 372  
 373      save_failed: function() {
 374          Y.log('Save failed.', 'debug', 'moodle-mod_quiz-autosave');
 375          this.save_transaction = null;
 376  
 377          // We want to retry soon.
 378          this.start_save_timer();
 379  
 380          this.savefailures = Math.max(1, this.savefailures + 1);
 381          if (this.savefailures === this.FAILURES_BEFORE_NOTIFY) {
 382              Y.one(this.SELECTORS.CONNECTION_ERROR).show();
 383              Y.one(this.SELECTORS.CONNECTION_OK).hide();
 384          }
 385      },
 386  
 387      is_time_nearly_over: function() {
 388          return M.mod_quiz.timer && M.mod_quiz.timer.endtime &&
 389                  (new Date().getTime() + 2 * this.delay) > M.mod_quiz.timer.endtime;
 390      },
 391  
 392      stop_autosaving: function() {
 393          this.cancel_delay();
 394          this.delay_timer = true;
 395          if (this.save_transaction) {
 396              this.save_transaction.abort();
 397          }
 398      }
 399  };


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1