Skip to content

Commit

Permalink
MDL-42504 quiz autosave: alert users if connection lost.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
timhunt committed Nov 15, 2013
1 parent b3f2d75 commit e9a504c
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 7 deletions.
1 change: 1 addition & 0 deletions mod/quiz/autosave.ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@

$attemptobj->process_auto_save($timenow);
$transaction->allow_commit();
echo 'OK';
6 changes: 6 additions & 0 deletions mod/quiz/lang/en/quiz.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@
$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';
Expand Down
14 changes: 14 additions & 0 deletions mod/quiz/renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ public function attempt_form($attemptobj, $page, $slots, $id, $nextpage) {
$output .= html_writer::end_tag('div');
$output .= html_writer::end_tag('form');

$output .= $this->connection_warning();

return $output;
}

Expand Down Expand Up @@ -1164,6 +1166,18 @@ public function graph(moodle_url $url, $title) {

return $this->heading($title) . 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 {
Expand Down
22 changes: 22 additions & 0 deletions mod/quiz/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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: {},

/**
Expand Down Expand Up @@ -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
});
},
Expand All @@ -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() {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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: {},

/**
Expand Down Expand Up @@ -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
});
},
Expand All @@ -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() {
Expand Down
38 changes: 36 additions & 2 deletions mod/quiz/yui/src/autosave/js/autosave.js
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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: {},

/**
Expand Down Expand Up @@ -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
});
},
Expand All @@ -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() {
Expand Down

0 comments on commit e9a504c

Please sign in to comment.