[ 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 return; 198 } 199 200 this.delay = delay * 1000; 201 202 this.form.delegate('valuechange', this.value_changed, this.SELECTORS.VALUE_CHANGE_ELEMENTS, this); 203 this.form.delegate('change', this.value_changed, this.SELECTORS.CHANGE_ELEMENTS, this); 204 this.form.on('submit', this.stop_autosaving, this); 205 206 this.init_tinymce(this.TINYMCE_DETECTION_REPEATS); 207 208 this.save_hidden_field_values(); 209 this.watch_hidden_fields(); 210 }, 211 212 save_hidden_field_values: function() { 213 this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(hidden) { 214 var name = hidden.get('name'); 215 if (!name) { 216 return; 217 } 218 this.hidden_field_values[name] = hidden.get('value'); 219 }, this); 220 }, 221 222 watch_hidden_fields: function() { 223 this.detect_hidden_field_changes(); 224 Y.later(this.WATCH_HIDDEN_DELAY, this, this.watch_hidden_fields); 225 }, 226 227 detect_hidden_field_changes: function() { 228 this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(hidden) { 229 var name = hidden.get('name'), 230 value = hidden.get('value'); 231 if (!name) { 232 return; 233 } 234 if (!(name in this.hidden_field_values) || value !== this.hidden_field_values[name]) { 235 this.hidden_field_values[name] = value; 236 this.value_changed({target: hidden}); 237 } 238 }, this); 239 }, 240 241 /** 242 * Initialise watching of TinyMCE specifically. 243 * 244 * Because TinyMCE might load slowly, and after us, we need to keep 245 * trying, until we detect TinyMCE is there, or enough time has passed. 246 * This is based on the TINYMCE_DETECTION_DELAY and 247 * TINYMCE_DETECTION_REPEATS properties. 248 * 249 * 250 * @method init_tinymce 251 * @param {Number} repeatcount The number of attempts made so far. 252 */ 253 init_tinymce: function(repeatcount) { 254 if (typeof window.tinyMCE === 'undefined') { 255 if (repeatcount > 0) { 256 Y.later(this.TINYMCE_DETECTION_DELAY, this, this.init_tinymce, [repeatcount - 1]); 257 } else { 258 } 259 return; 260 } 261 262 this.editor_change_handler = Y.bind(this.editor_changed, this); 263 window.tinyMCE.onAddEditor.add(Y.bind(this.init_tinymce_editor, this)); 264 }, 265 266 /** 267 * Initialise watching of a specific TinyMCE editor. 268 * 269 * @method init_tinymce_editor 270 * @param {EventFacade} e 271 * @param {Object} editor The TinyMCE editor object 272 */ 273 init_tinymce_editor: function(e, editor) { 274 editor.onChange.add(this.editor_change_handler); 275 editor.onRedo.add(this.editor_change_handler); 276 editor.onUndo.add(this.editor_change_handler); 277 editor.onKeyDown.add(this.editor_change_handler); 278 }, 279 280 value_changed: function(e) { 281 var name = e.target.getAttribute('name'); 282 if (name === 'thispage' || name === 'scrollpos' || (name && name.match(/_:flagged$/))) { 283 return; // Not interesting. 284 } 285 286 // Fallback to the ID when the name is not present (in the case of content editable). 287 name = name || '#' + e.target.getAttribute('id'); 288 this.start_save_timer_if_necessary(); 289 }, 290 291 editor_changed: function(editor) { 292 this.start_save_timer_if_necessary(); 293 }, 294 295 start_save_timer_if_necessary: function() { 296 this.dirty = true; 297 298 if (this.delay_timer || this.save_transaction) { 299 // Already counting down or daving. 300 return; 301 } 302 303 this.start_save_timer(); 304 }, 305 306 start_save_timer: function() { 307 this.cancel_delay(); 308 this.delay_timer = Y.later(this.delay, this, this.save_changes); 309 }, 310 311 cancel_delay: function() { 312 if (this.delay_timer && this.delay_timer !== true) { 313 this.delay_timer.cancel(); 314 } 315 this.delay_timer = null; 316 }, 317 318 save_changes: function() { 319 this.cancel_delay(); 320 this.dirty = false; 321 322 if (this.is_time_nearly_over()) { 323 this.stop_autosaving(); 324 return; 325 } 326 327 if (typeof window.tinyMCE !== 'undefined') { 328 window.tinyMCE.triggerSave(); 329 } 330 this.save_transaction = Y.io(this.AUTOSAVE_HANDLER, { 331 method: 'POST', 332 form: {id: this.form}, 333 on: { 334 success: this.save_done, 335 failure: this.save_failed 336 }, 337 context: this 338 }); 339 }, 340 341 save_done: function(transactionid, response) { 342 if (response.responseText !== 'OK') { 343 // Because IIS is useless, Moodle can't send proper HTTP response 344 // codes, so we have to detect failures manually. 345 this.save_failed(transactionid, response); 346 return; 347 } 348 349 this.save_transaction = null; 350 351 if (this.dirty) { 352 this.start_save_timer(); 353 } 354 355 if (this.savefailures > 0) { 356 Y.one(this.SELECTORS.CONNECTION_ERROR).hide(); 357 Y.one(this.SELECTORS.CONNECTION_OK).show(); 358 this.savefailures = this.FIRST_SUCCESSFUL_SAVE; 359 } else if (this.savefailures === this.FIRST_SUCCESSFUL_SAVE) { 360 Y.one(this.SELECTORS.CONNECTION_OK).hide(); 361 this.savefailures = 0; 362 } 363 }, 364 365 save_failed: function() { 366 this.save_transaction = null; 367 368 // We want to retry soon. 369 this.start_save_timer(); 370 371 this.savefailures = Math.max(1, this.savefailures + 1); 372 if (this.savefailures === this.FAILURES_BEFORE_NOTIFY) { 373 Y.one(this.SELECTORS.CONNECTION_ERROR).show(); 374 Y.one(this.SELECTORS.CONNECTION_OK).hide(); 375 } 376 }, 377 378 is_time_nearly_over: function() { 379 return M.mod_quiz.timer && M.mod_quiz.timer.endtime && 380 (new Date().getTime() + 2 * this.delay) > M.mod_quiz.timer.endtime; 381 }, 382 383 stop_autosaving: function() { 384 this.cancel_delay(); 385 this.delay_timer = true; 386 if (this.save_transaction) { 387 this.save_transaction.abort(); 388 } 389 } 390 }; 391 392 393 }, '@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 |