diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php
index e6b87e0f395..7c367f0542f 100644
--- a/mod/quiz/attempt.php
+++ b/mod/quiz/attempt.php
@@ -100,6 +100,8 @@ if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) {
// Set up auto-save if required.
$autosaveperiod = get_config('quiz', 'autosaveperiod');
if ($autosaveperiod) {
+ $PAGE->requires->string_for_js('strftimedatetimeshortaccurate', 'langconfig');
+ $PAGE->requires->string_for_js('lastautosave', 'quiz');
$PAGE->requires->yui_module('moodle-mod_quiz-autosave',
'M.mod_quiz.autosave.init', [$autosaveperiod]);
}
diff --git a/mod/quiz/classes/output/navigation_panel_attempt.php b/mod/quiz/classes/output/navigation_panel_attempt.php
index 0461179f55f..1a5cbd86f42 100644
--- a/mod/quiz/classes/output/navigation_panel_attempt.php
+++ b/mod/quiz/classes/output/navigation_panel_attempt.php
@@ -48,8 +48,15 @@ class navigation_panel_attempt extends navigation_panel_base {
// Don't link from the summary page to itself.
return '';
}
- return html_writer::link($this->attemptobj->summary_url(),
+
+ // We create a hidden div with an information message in order for the student
+ // to known when their answers have been auto-saved.
+ $html = html_writer::div(get_string('lastautosave', 'quiz', '-'), 'autosave_info', ['hidden' => 'hidden']);
+
+ $html .= html_writer::link($this->attemptobj->summary_url(),
get_string('endtest', 'quiz'), ['class' => 'endtestlink aalink']) .
$this->render_restart_preview_link($output);
+
+ return $html;
}
}
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index 916da8f8dd7..b18d7349bed 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -503,6 +503,7 @@ $string['invalidsource'] = 'The source is not accepted as valid.';
$string['invalidsourcetype'] = 'Invalid source type.';
$string['invalidstateid'] = 'Invalid state id';
$string['lastanswer'] = 'Your last answer was';
+$string['lastautosave'] = 'Last saved: {$a}';
$string['layout'] = 'Layout';
$string['layoutasshown'] = 'Page layout as shown.';
$string['layoutasshownwithpages'] = 'Page layout as shown. (Automatic new page every {$a} questions.)';
diff --git a/mod/quiz/styles.css b/mod/quiz/styles.css
index 9215c18a45f..a42298c8fca 100644
--- a/mod/quiz/styles.css
+++ b/mod/quiz/styles.css
@@ -188,6 +188,10 @@
margin: 0.5em 0;
}
+.path-mod-quiz .autosave_info {
+ font-size: small;
+}
+
.path-mod-quiz .othernav a,
.path-mod-quiz .othernav input {
display: block;
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js
index cdaeebbf2a1..fedf920a4df 100644
--- a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js
+++ b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js
@@ -387,6 +387,8 @@ M.mod_quiz.autosave = {
M.mod_quiz.timer.updateEndTime(autosavedata.timeleft);
}
+ this.update_saved_time_display();
+
Y.log('Save completed.', 'debug', 'moodle-mod_quiz-autosave');
this.save_transaction = null;
@@ -419,6 +421,21 @@ M.mod_quiz.autosave = {
}
},
+ /**
+ * Inform the user that their answers have been saved.
+ *
+ * @method update_saved_time_display
+ */
+ update_saved_time_display: function() {
+ // We fetch the current language's preferred time format from the language pack.
+ var timeFormat = M.util.get_string('strftimedatetimeshortaccurate', 'langconfig');
+ var message = M.util.get_string('lastautosave', 'quiz', Y.Date.format(new Date(), {'format': timeFormat}));
+
+ var infoDiv = Y.one('#mod_quiz_navblock .othernav .autosave_info');
+ infoDiv.set('text', message);
+ infoDiv.show();
+ },
+
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;
@@ -434,4 +451,14 @@ M.mod_quiz.autosave = {
};
-}, '@VERSION@', {"requires": ["base", "node", "event", "event-valuechange", "node-event-delegate", "io-form"]});
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "event-valuechange",
+ "node-event-delegate",
+ "io-form",
+ "datatype-date-format"
+ ]
+});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
index 698e1b024c6..7ebe6833a77 100644
--- a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
+++ b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
@@ -1 +1 @@
-YUI.add("moodle-mod_quiz-autosave",function(n,e){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, [contenteditable="true"]',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(e){this.form=n.one(this.SELECTORS.QUIZ_FORM),this.form&&(this.delay=1e3*e,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),require(["core_form/events"],function(e){window.addEventListener(e.eventTypes.uploadChanged,this.value_changed.bind(this))}.bind(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");t&&(this.hidden_field_values[t]=e.get("value"))},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),n.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"),i=e.get("value");t&&(t in this.hidden_field_values&&i===this.hidden_field_values[t]||(this.hidden_field_values[t]=i,this.value_changed({target:e})))},this)},init_tinymce:function(e){var t;"undefined"!=typeof window.tinyMCE?(this.editor_change_handler=n.bind(this.editor_changed,this),window.tinyMCE.onAddEditor?window.tinyMCE.onAddEditor.add(n.bind(this.init_tinymce_editor,this)):window.tinyMCE.on&&(t=this.start_save_timer_if_necessary.bind(this),window.tinyMCE.on("AddEditor",function(e){e.editor.on("Change Undo Redo keydown",t)}),window.tinyMCE.get().forEach(function(e){e.on("Change Undo Redo keydown",t)}))):0M.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"]});
\ No newline at end of file
+YUI.add("moodle-mod_quiz-autosave",function(n,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, [contenteditable="true"]',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=n.one(this.SELECTORS.QUIZ_FORM),this.form&&(this.delay=1e3*t,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),require(["core_form/events"],function(t){window.addEventListener(t.eventTypes.uploadChanged,this.value_changed.bind(this))}.bind(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(t){var e=t.get("name");e&&(this.hidden_field_values[e]=t.get("value"))},this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),n.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each(function(t){var e=t.get("name"),i=t.get("value");e&&(e in this.hidden_field_values&&i===this.hidden_field_values[e]||(this.hidden_field_values[e]=i,this.value_changed({target:t})))},this)},init_tinymce:function(t){var e;"undefined"!=typeof window.tinyMCE?(this.editor_change_handler=n.bind(this.editor_changed,this),window.tinyMCE.onAddEditor?window.tinyMCE.onAddEditor.add(n.bind(this.init_tinymce_editor,this)):window.tinyMCE.on&&(e=this.start_save_timer_if_necessary.bind(this),window.tinyMCE.on("AddEditor",function(t){t.editor.on("Change Undo Redo keydown",e)}),window.tinyMCE.get().forEach(function(t){t.on("Change Undo Redo keydown",e)}))):0M.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","datatype-date-format"]});
\ No newline at end of file
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js
index 992461006f3..82ddfea0710 100644
--- a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js
+++ b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js
@@ -378,6 +378,8 @@ M.mod_quiz.autosave = {
M.mod_quiz.timer.updateEndTime(autosavedata.timeleft);
}
+ this.update_saved_time_display();
+
this.save_transaction = null;
if (this.dirty) {
@@ -407,6 +409,21 @@ M.mod_quiz.autosave = {
}
},
+ /**
+ * Inform the user that their answers have been saved.
+ *
+ * @method update_saved_time_display
+ */
+ update_saved_time_display: function() {
+ // We fetch the current language's preferred time format from the language pack.
+ var timeFormat = M.util.get_string('strftimedatetimeshortaccurate', 'langconfig');
+ var message = M.util.get_string('lastautosave', 'quiz', Y.Date.format(new Date(), {'format': timeFormat}));
+
+ var infoDiv = Y.one('#mod_quiz_navblock .othernav .autosave_info');
+ infoDiv.set('text', message);
+ infoDiv.show();
+ },
+
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;
@@ -422,4 +439,14 @@ M.mod_quiz.autosave = {
};
-}, '@VERSION@', {"requires": ["base", "node", "event", "event-valuechange", "node-event-delegate", "io-form"]});
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "event-valuechange",
+ "node-event-delegate",
+ "io-form",
+ "datatype-date-format"
+ ]
+});
diff --git a/mod/quiz/yui/src/autosave/js/autosave.js b/mod/quiz/yui/src/autosave/js/autosave.js
index 7b8b1764e41..f0c95a58af9 100644
--- a/mod/quiz/yui/src/autosave/js/autosave.js
+++ b/mod/quiz/yui/src/autosave/js/autosave.js
@@ -385,6 +385,8 @@ M.mod_quiz.autosave = {
M.mod_quiz.timer.updateEndTime(autosavedata.timeleft);
}
+ this.update_saved_time_display();
+
Y.log('Save completed.', 'debug', 'moodle-mod_quiz-autosave');
this.save_transaction = null;
@@ -417,6 +419,21 @@ M.mod_quiz.autosave = {
}
},
+ /**
+ * Inform the user that their answers have been saved.
+ *
+ * @method update_saved_time_display
+ */
+ update_saved_time_display: function() {
+ // We fetch the current language's preferred time format from the language pack.
+ var timeFormat = M.util.get_string('strftimedatetimeshortaccurate', 'langconfig');
+ var message = M.util.get_string('lastautosave', 'quiz', Y.Date.format(new Date(), {'format': timeFormat}));
+
+ var infoDiv = Y.one('#mod_quiz_navblock .othernav .autosave_info');
+ infoDiv.set('text', message);
+ infoDiv.show();
+ },
+
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;
diff --git a/mod/quiz/yui/src/autosave/meta/autosave.json b/mod/quiz/yui/src/autosave/meta/autosave.json
index 0b265c5752d..c86e0497b0b 100644
--- a/mod/quiz/yui/src/autosave/meta/autosave.json
+++ b/mod/quiz/yui/src/autosave/meta/autosave.json
@@ -6,7 +6,8 @@
"event",
"event-valuechange",
"node-event-delegate",
- "io-form"
+ "io-form",
+ "datatype-date-format"
]
}
}