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