mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 04:22:07 +02:00
MDL-42504 quiz autosave: alert users if connection lost.
When the auto-save fails, we alert the user that they may have lost their internet connection so that they don't do more work that they might just lose.
This commit is contained in:
parent
5e6da548d7
commit
52b612f08b
@ -60,3 +60,4 @@ if ($attemptobj->is_finished()) {
|
||||
|
||||
$attemptobj->process_auto_save($timenow);
|
||||
$transaction->allow_commit();
|
||||
echo 'OK';
|
||||
|
@ -185,6 +185,12 @@ $string['confirmserverdelete'] = 'Are you sure you want to remove the server <b>
|
||||
$string['confirmstartattemptlimit'] = 'Number of attempts allowed: {$a}. You are about to start a new attempt. Do you wish to proceed?';
|
||||
$string['confirmstartattempttimelimit'] = 'This quiz has a time limit and is limited to {$a} attempt(s). You are about to start a new attempt. Do you wish to proceed?';
|
||||
$string['confirmstarttimelimit'] = 'The quiz has a time limit. Are you sure that you wish to start?';
|
||||
$string['connectionok'] = 'Network connection restored. You may continue safely.';
|
||||
$string['connectionerror'] = 'Network connection lost. (Autosave failed).
|
||||
|
||||
Make a note of any responses entered on this page in the last few minutes, then try to re-connect.
|
||||
|
||||
Once connection has been re-established, your responses should be saved and this message will disappear.';
|
||||
$string['containercategorycreated'] = 'This category has been created to store all the original categories moved to site level due to the causes specified below.';
|
||||
$string['continueattemptquiz'] = 'Continue the last attempt';
|
||||
$string['continuepreview'] = 'Continue the last preview';
|
||||
|
@ -492,6 +492,8 @@ class mod_quiz_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('form');
|
||||
|
||||
$output .= $this->connection_warning();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
@ -1162,6 +1164,18 @@ class mod_quiz_renderer extends plugin_renderer_base {
|
||||
|
||||
return $this->heading($title, 3) . html_writer::tag('div', $graph, array('class' => 'graph'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the connection warning messages, which are initially hidden, and
|
||||
* only revealed by JavaScript if necessary.
|
||||
*/
|
||||
public function connection_warning() {
|
||||
$options = array('filter' => false, 'newlines' => false);
|
||||
$warning = format_text(get_string('connectionerror', 'quiz'), FORMAT_MARKDOWN, $options);
|
||||
$ok = format_text(get_string('connectionok', 'quiz'), FORMAT_MARKDOWN, $options);
|
||||
return html_writer::tag('div', $warning, array('id' => 'connection-error', 'style' => 'display: none;', 'role' => 'alert')) .
|
||||
html_writer::tag('div', $ok, array('id' => 'connection-ok', 'style' => 'display: none;', 'role' => 'alert'));
|
||||
}
|
||||
}
|
||||
|
||||
class mod_quiz_links_to_other_attempts implements renderable {
|
||||
|
@ -28,6 +28,28 @@ body.jsenabled .questionflagcheckbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-mod-quiz-attempt #connection-ok,
|
||||
#page-mod-quiz-attempt #connection-error {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 80%;
|
||||
left: 10%;
|
||||
color: #555;
|
||||
border-radius: 0 0 10px 10px;
|
||||
box-shadow: 5px 5px 20px 0 #666666;
|
||||
padding: 1em 1em 0;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#page-mod-quiz-attempt #connection-error {
|
||||
background-color: #fcc;
|
||||
}
|
||||
#page-mod-quiz-attempt #connection-ok {
|
||||
background-color: #cfb;
|
||||
width: 60%;
|
||||
left: 20%;
|
||||
}
|
||||
|
||||
/** Mod quiz attempt **/
|
||||
.generalbox#passwordbox {
|
||||
/* Should probably match .generalbox#intro above */
|
||||
|
@ -30,13 +30,17 @@ M.mod_quiz.autosave = {
|
||||
TINYMCE_DETECTION_DELAY: 500,
|
||||
TINYMCE_DETECTION_REPEATS: 20,
|
||||
WATCH_HIDDEN_DELAY: 1000,
|
||||
FAILURES_BEFORE_NOTIFY: 1,
|
||||
FIRST_SUCCESSFUL_SAVE: -1,
|
||||
|
||||
/** Selectors. */
|
||||
SELECTORS: {
|
||||
QUIZ_FORM: '#responseform',
|
||||
VALUE_CHANGE_ELEMENTS: 'input, textarea',
|
||||
CHANGE_ELEMENTS: 'input, select',
|
||||
HIDDEN_INPUTS: 'input[type=hidden]'
|
||||
HIDDEN_INPUTS: 'input[type=hidden]',
|
||||
CONNECTION_ERROR: '#connection-error',
|
||||
CONNECTION_OK: '#connection-ok'
|
||||
},
|
||||
|
||||
/** Script that handles the auto-saves. */
|
||||
@ -57,9 +61,13 @@ M.mod_quiz.autosave = {
|
||||
/** Y.io transaction for the save ajax request. */
|
||||
save_transaction: null,
|
||||
|
||||
/** @property Failed saves count. */
|
||||
savefailures: 0,
|
||||
|
||||
/** Properly bound key change handler. */
|
||||
editor_change_handler: null,
|
||||
|
||||
/** Record of the value of all the hidden fields, last time they were checked. */
|
||||
hidden_field_values: {},
|
||||
|
||||
/**
|
||||
@ -202,7 +210,10 @@ M.mod_quiz.autosave = {
|
||||
this.save_transaction = Y.io(this.AUTOSAVE_HANDLER, {
|
||||
method: 'POST',
|
||||
form: {id: this.form},
|
||||
on: {complete: this.save_done},
|
||||
on: {
|
||||
success: this.save_done,
|
||||
failure: this.save_failed
|
||||
},
|
||||
context: this
|
||||
});
|
||||
},
|
||||
@ -215,6 +226,29 @@ M.mod_quiz.autosave = {
|
||||
Y.log('Dirty after save.');
|
||||
this.start_save_timer();
|
||||
}
|
||||
|
||||
if (this.savefailures > 0) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).hide();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).show();
|
||||
this.savefailures = this.FIRST_SUCCESSFUL_SAVE;
|
||||
} else if (this.savefailures === this.FIRST_SUCCESSFUL_SAVE) {
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
this.savefailures = 0;
|
||||
}
|
||||
},
|
||||
|
||||
save_failed: function() {
|
||||
Y.log('Save failed.');
|
||||
this.save_transaction = null;
|
||||
|
||||
// We want to retry soon.
|
||||
this.start_save_timer();
|
||||
|
||||
this.savefailures = Math.max(1, this.savefailures + 1);
|
||||
if (this.savefailures === this.FAILURES_BEFORE_NOTIFY) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).show();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
}
|
||||
},
|
||||
|
||||
is_time_nearly_over: function() {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-mod_quiz-autosave",function(e,t){M.mod_quiz=M.mod_quiz||{},M.mod_quiz.autosave={TINYMCE_DETECTION_DELAY:500,TINYMCE_DETECTION_REPEATS:20,WATCH_HIDDEN_DELAY:1e3,SELECTORS:{QUIZ_FORM:"#responseform",VALUE_CHANGE_ELEMENTS:"input, textarea",CHANGE_ELEMENTS:"input, select",HIDDEN_INPUTS:"input[type=hidden]"},AUTOSAVE_HANDLER:M.cfg.wwwroot+"/mod/quiz/autosave.ajax.php",delay:12e4,form:null,dirty:!1,delay_timer:null,save_transaction:null,editor_change_handler:null,hidden_field_values:{},init:function(t){this.form=e.one(this.SELECTORS.QUIZ_FORM);if(!this.form)return;this.delay=t*1e3,this.form.delegate("valuechange",this.value_changed,this.SELECTORS.VALUE_CHANGE_ELEMENTS,this),this.form.delegate("change",this.value_changed,this.SELECTORS.CHANGE_ELEMENTS,this),this.form.on("submit",this.stop_autosaving,this),this.init_tinymce(this.TINYMCE_DETECTION_REPEATS),this.save_hidden_field_values(),this.watch_hidden_fields()},save_hidden_field_values:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name");if(!t)return;this.hidden_field_values[t]=e.get("value")},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),e.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name"),n=e.get("value");if(!t)return;if(!(t in this.hidden_field_values)||n!==this.hidden_field_values[t])this.hidden_field_values[t]=n,this.value_changed({target:e})},this)},init_tinymce:function(t){if(typeof tinyMCE=="undefined"){t>0&&e.later(this.TINYMCE_DETECTION_DELAY,this,this.init_tinymce,[t-1]);return}this.editor_change_handler=e.bind(this.editor_changed,this),tinyMCE.onAddEditor.add(e.bind(this.init_tinymce_editor,this))},init_tinymce_editor:function(e,t){t.onChange.add(this.editor_change_handler),t.onRedo.add(this.editor_change_handler),t.onUndo.add(this.editor_change_handler),t.onKeyDown.add(this.editor_change_handler)},value_changed:function(e){if(e.target.get("name")==="thispage"||e.target.get("name")==="scrollpos"||e.target.get("name").match(/_:flagged$/))return;this.start_save_timer_if_necessary()},editor_changed:function(e){this.start_save_timer_if_necessary()},start_save_timer_if_necessary:function(){this.dirty=!0;if(this.delay_timer||this.save_transaction)return;this.start_save_timer()},start_save_timer:function(){this.cancel_delay(),this.delay_timer=e.later(this.delay,this,this.save_changes)},cancel_delay:function(){this.delay_timer&&this.delay_timer!==!0&&this.delay_timer.cancel(),this.delay_timer=null},save_changes:function(){this.cancel_delay(),this.dirty=!1;if(this.is_time_nearly_over()){this.stop_autosaving();return}typeof tinyMCE!="undefined"&&tinyMCE.triggerSave(),this.save_transaction=e.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{complete:this.save_done},context:this})},save_done:function(){this.save_transaction=null,this.dirty&&this.start_save_timer()},is_time_nearly_over:function(){return M.mod_quiz.timer&&M.mod_quiz.timer.endtime&&(new Date).getTime()+2*this.delay>M.mod_quiz.timer.endtime},stop_autosaving:function(){this.cancel_delay(),this.delay_timer=!0,this.save_transaction&&this.save_transaction.abort()}}},"@VERSION@",{requires:["base","node","event","event-valuechange","node-event-delegate","io-form"]});
|
||||
YUI.add("moodle-mod_quiz-autosave",function(e,t){M.mod_quiz=M.mod_quiz||{},M.mod_quiz.autosave={TINYMCE_DETECTION_DELAY:500,TINYMCE_DETECTION_REPEATS:20,WATCH_HIDDEN_DELAY:1e3,FAILURES_BEFORE_NOTIFY:1,FIRST_SUCCESSFUL_SAVE:-1,SELECTORS:{QUIZ_FORM:"#responseform",VALUE_CHANGE_ELEMENTS:"input, textarea",CHANGE_ELEMENTS:"input, select",HIDDEN_INPUTS:"input[type=hidden]",CONNECTION_ERROR:"#connection-error",CONNECTION_OK:"#connection-ok"},AUTOSAVE_HANDLER:M.cfg.wwwroot+"/mod/quiz/autosave.ajax.php",delay:12e4,form:null,dirty:!1,delay_timer:null,save_transaction:null,savefailures:0,editor_change_handler:null,hidden_field_values:{},init:function(t){this.form=e.one(this.SELECTORS.QUIZ_FORM);if(!this.form)return;this.delay=t*1e3,this.form.delegate("valuechange",this.value_changed,this.SELECTORS.VALUE_CHANGE_ELEMENTS,this),this.form.delegate("change",this.value_changed,this.SELECTORS.CHANGE_ELEMENTS,this),this.form.on("submit",this.stop_autosaving,this),this.init_tinymce(this.TINYMCE_DETECTION_REPEATS),this.save_hidden_field_values(),this.watch_hidden_fields()},save_hidden_field_values:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name");if(!t)return;this.hidden_field_values[t]=e.get("value")},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),e.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(e){var t=e.get("name"),n=e.get("value");if(!t)return;if(!(t in this.hidden_field_values)||n!==this.hidden_field_values[t])this.hidden_field_values[t]=n,this.value_changed({target:e})},this)},init_tinymce:function(t){if(typeof tinyMCE=="undefined"){t>0&&e.later(this.TINYMCE_DETECTION_DELAY,this,this.init_tinymce,[t-1]);return}this.editor_change_handler=e.bind(this.editor_changed,this),tinyMCE.onAddEditor.add(e.bind(this.init_tinymce_editor,this))},init_tinymce_editor:function(e,t){t.onChange.add(this.editor_change_handler),t.onRedo.add(this.editor_change_handler),t.onUndo.add(this.editor_change_handler),t.onKeyDown.add(this.editor_change_handler)},value_changed:function(e){if(e.target.get("name")==="thispage"||e.target.get("name")==="scrollpos"||e.target.get("name").match(/_:flagged$/))return;this.start_save_timer_if_necessary()},editor_changed:function(e){this.start_save_timer_if_necessary()},start_save_timer_if_necessary:function(){this.dirty=!0;if(this.delay_timer||this.save_transaction)return;this.start_save_timer()},start_save_timer:function(){this.cancel_delay(),this.delay_timer=e.later(this.delay,this,this.save_changes)},cancel_delay:function(){this.delay_timer&&this.delay_timer!==!0&&this.delay_timer.cancel(),this.delay_timer=null},save_changes:function(){this.cancel_delay(),this.dirty=!1;if(this.is_time_nearly_over()){this.stop_autosaving();return}typeof tinyMCE!="undefined"&&tinyMCE.triggerSave(),this.save_transaction=e.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{success:this.save_done,failure:this.save_failed},context:this})},save_done:function(){this.save_transaction=null,this.dirty&&this.start_save_timer(),this.savefailures>0?(e.one(this.SELECTORS.CONNECTION_ERROR).hide(),e.one(this.SELECTORS.CONNECTION_OK).show(),this.savefailures=this.FIRST_SUCCESSFUL_SAVE):this.savefailures===this.FIRST_SUCCESSFUL_SAVE&&(e.one(this.SELECTORS.CONNECTION_OK).hide(),this.savefailures=0)},save_failed:function(){this.save_transaction=null,this.start_save_timer(),this.savefailures=Math.max(1,this.savefailures+1),this.savefailures===this.FAILURES_BEFORE_NOTIFY&&(e.one(this.SELECTORS.CONNECTION_ERROR).show(),e.one(this.SELECTORS.CONNECTION_OK).hide())},is_time_nearly_over:function(){return M.mod_quiz.timer&&M.mod_quiz.timer.endtime&&(new Date).getTime()+2*this.delay>M.mod_quiz.timer.endtime},stop_autosaving:function(){this.cancel_delay(),this.delay_timer=!0,this.save_transaction&&this.save_transaction.abort()}}},"@VERSION@",{requires:["base","node","event","event-valuechange","node-event-delegate","io-form"]});
|
||||
|
@ -30,13 +30,17 @@ M.mod_quiz.autosave = {
|
||||
TINYMCE_DETECTION_DELAY: 500,
|
||||
TINYMCE_DETECTION_REPEATS: 20,
|
||||
WATCH_HIDDEN_DELAY: 1000,
|
||||
FAILURES_BEFORE_NOTIFY: 1,
|
||||
FIRST_SUCCESSFUL_SAVE: -1,
|
||||
|
||||
/** Selectors. */
|
||||
SELECTORS: {
|
||||
QUIZ_FORM: '#responseform',
|
||||
VALUE_CHANGE_ELEMENTS: 'input, textarea',
|
||||
CHANGE_ELEMENTS: 'input, select',
|
||||
HIDDEN_INPUTS: 'input[type=hidden]'
|
||||
HIDDEN_INPUTS: 'input[type=hidden]',
|
||||
CONNECTION_ERROR: '#connection-error',
|
||||
CONNECTION_OK: '#connection-ok'
|
||||
},
|
||||
|
||||
/** Script that handles the auto-saves. */
|
||||
@ -57,9 +61,13 @@ M.mod_quiz.autosave = {
|
||||
/** Y.io transaction for the save ajax request. */
|
||||
save_transaction: null,
|
||||
|
||||
/** @property Failed saves count. */
|
||||
savefailures: 0,
|
||||
|
||||
/** Properly bound key change handler. */
|
||||
editor_change_handler: null,
|
||||
|
||||
/** Record of the value of all the hidden fields, last time they were checked. */
|
||||
hidden_field_values: {},
|
||||
|
||||
/**
|
||||
@ -194,7 +202,10 @@ M.mod_quiz.autosave = {
|
||||
this.save_transaction = Y.io(this.AUTOSAVE_HANDLER, {
|
||||
method: 'POST',
|
||||
form: {id: this.form},
|
||||
on: {complete: this.save_done},
|
||||
on: {
|
||||
success: this.save_done,
|
||||
failure: this.save_failed
|
||||
},
|
||||
context: this
|
||||
});
|
||||
},
|
||||
@ -205,6 +216,28 @@ M.mod_quiz.autosave = {
|
||||
if (this.dirty) {
|
||||
this.start_save_timer();
|
||||
}
|
||||
|
||||
if (this.savefailures > 0) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).hide();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).show();
|
||||
this.savefailures = this.FIRST_SUCCESSFUL_SAVE;
|
||||
} else if (this.savefailures === this.FIRST_SUCCESSFUL_SAVE) {
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
this.savefailures = 0;
|
||||
}
|
||||
},
|
||||
|
||||
save_failed: function() {
|
||||
this.save_transaction = null;
|
||||
|
||||
// We want to retry soon.
|
||||
this.start_save_timer();
|
||||
|
||||
this.savefailures = Math.max(1, this.savefailures + 1);
|
||||
if (this.savefailures === this.FAILURES_BEFORE_NOTIFY) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).show();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
}
|
||||
},
|
||||
|
||||
is_time_nearly_over: function() {
|
||||
|
38
mod/quiz/yui/src/autosave/js/autosave.js
vendored
38
mod/quiz/yui/src/autosave/js/autosave.js
vendored
@ -28,13 +28,17 @@ M.mod_quiz.autosave = {
|
||||
TINYMCE_DETECTION_DELAY: 500,
|
||||
TINYMCE_DETECTION_REPEATS: 20,
|
||||
WATCH_HIDDEN_DELAY: 1000,
|
||||
FAILURES_BEFORE_NOTIFY: 1,
|
||||
FIRST_SUCCESSFUL_SAVE: -1,
|
||||
|
||||
/** Selectors. */
|
||||
SELECTORS: {
|
||||
QUIZ_FORM: '#responseform',
|
||||
VALUE_CHANGE_ELEMENTS: 'input, textarea',
|
||||
CHANGE_ELEMENTS: 'input, select',
|
||||
HIDDEN_INPUTS: 'input[type=hidden]'
|
||||
HIDDEN_INPUTS: 'input[type=hidden]',
|
||||
CONNECTION_ERROR: '#connection-error',
|
||||
CONNECTION_OK: '#connection-ok'
|
||||
},
|
||||
|
||||
/** Script that handles the auto-saves. */
|
||||
@ -55,9 +59,13 @@ M.mod_quiz.autosave = {
|
||||
/** Y.io transaction for the save ajax request. */
|
||||
save_transaction: null,
|
||||
|
||||
/** @property Failed saves count. */
|
||||
savefailures: 0,
|
||||
|
||||
/** Properly bound key change handler. */
|
||||
editor_change_handler: null,
|
||||
|
||||
/** Record of the value of all the hidden fields, last time they were checked. */
|
||||
hidden_field_values: {},
|
||||
|
||||
/**
|
||||
@ -200,7 +208,10 @@ M.mod_quiz.autosave = {
|
||||
this.save_transaction = Y.io(this.AUTOSAVE_HANDLER, {
|
||||
method: 'POST',
|
||||
form: {id: this.form},
|
||||
on: {complete: this.save_done},
|
||||
on: {
|
||||
success: this.save_done,
|
||||
failure: this.save_failed
|
||||
},
|
||||
context: this
|
||||
});
|
||||
},
|
||||
@ -213,6 +224,29 @@ M.mod_quiz.autosave = {
|
||||
Y.log('Dirty after save.');
|
||||
this.start_save_timer();
|
||||
}
|
||||
|
||||
if (this.savefailures > 0) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).hide();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).show();
|
||||
this.savefailures = this.FIRST_SUCCESSFUL_SAVE;
|
||||
} else if (this.savefailures === this.FIRST_SUCCESSFUL_SAVE) {
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
this.savefailures = 0;
|
||||
}
|
||||
},
|
||||
|
||||
save_failed: function() {
|
||||
Y.log('Save failed.');
|
||||
this.save_transaction = null;
|
||||
|
||||
// We want to retry soon.
|
||||
this.start_save_timer();
|
||||
|
||||
this.savefailures = Math.max(1, this.savefailures + 1);
|
||||
if (this.savefailures === this.FAILURES_BEFORE_NOTIFY) {
|
||||
Y.one(this.SELECTORS.CONNECTION_ERROR).show();
|
||||
Y.one(this.SELECTORS.CONNECTION_OK).hide();
|
||||
}
|
||||
},
|
||||
|
||||
is_time_nearly_over: function() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user