[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |