Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-ora2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-ora2
Commits
3baad585
Commit
3baad585
authored
May 08, 2014
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #313 from edx/will/autosave
Implemented autosave
parents
b7e87326
05f13bdd
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
193 additions
and
17 deletions
+193
-17
apps/openassessment/templates/openassessmentblock/response/oa_response.html
+1
-3
apps/openassessment/xblock/static/js/openassessment.min.js
+0
-0
apps/openassessment/xblock/static/js/spec/oa_response.js
+106
-8
apps/openassessment/xblock/static/js/src/oa_response.js
+86
-6
No files found.
apps/openassessment/templates/openassessmentblock/response/oa_response.html
View file @
3baad585
...
@@ -62,9 +62,7 @@
...
@@ -62,9 +62,7 @@
id=
"submission__answer__value"
id=
"submission__answer__value"
placeholder=
""
placeholder=
""
maxlength=
"100000"
maxlength=
"100000"
>
>
{{ saved_response }}
</textarea>
{{ saved_response }}
</textarea>
<span
class=
"tip"
>
{% trans "You may continue to work on your response until you submit it." %}
</span>
<span
class=
"tip"
>
{% trans "You may continue to work on your response until you submit it." %}
</span>
</li>
</li>
</ol>
</ol>
...
...
apps/openassessment/xblock/static/js/openassessment.min.js
View file @
3baad585
This diff is collapsed.
Click to expand it.
apps/openassessment/xblock/static/js/spec/oa_response.js
View file @
3baad585
...
@@ -86,24 +86,29 @@ describe("OpenAssessment.ResponseView", function() {
...
@@ -86,24 +86,29 @@ describe("OpenAssessment.ResponseView", function() {
});
});
});
});
afterEach
(
function
()
{
// Disable autosave polling (if it was enabled)
view
.
setAutoSaveEnabled
(
false
);
});
it
(
"updates submit/save buttons and save status when response text changes"
,
function
()
{
it
(
"updates submit/save buttons and save status when response text changes"
,
function
()
{
// Response is blank --> save/submit buttons disabled
// Response is blank --> save/submit buttons disabled
view
.
response
(
''
);
view
.
response
(
''
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
submitEnabled
()).
toBe
(
false
);
expect
(
view
.
submitEnabled
()).
toBe
(
false
);
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
// Response is whitespace --> save/submit buttons disabled
// Response is whitespace --> save/submit buttons disabled
view
.
response
(
'
\
n
\
n '
);
view
.
response
(
'
\
n
\
n '
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
submitEnabled
()).
toBe
(
false
);
expect
(
view
.
submitEnabled
()).
toBe
(
false
);
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
// Response is not blank --> submit button enabled
// Response is not blank --> submit button enabled
view
.
response
(
'Test response'
);
view
.
response
(
'Test response'
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
submitEnabled
()).
toBe
(
true
);
expect
(
view
.
submitEnabled
()).
toBe
(
true
);
expect
(
view
.
saveEnabled
()).
toBe
(
true
);
expect
(
view
.
saveEnabled
()).
toBe
(
true
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
...
@@ -135,14 +140,14 @@ describe("OpenAssessment.ResponseView", function() {
...
@@ -135,14 +140,14 @@ describe("OpenAssessment.ResponseView", function() {
// Keep the text the same, but trigger an update
// Keep the text the same, but trigger an update
// Should still be saved
// Should still be saved
view
.
response
(
'Lorem ipsum'
);
view
.
response
(
'Lorem ipsum'
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveEnabled
()).
toBe
(
false
);
expect
(
view
.
saveStatus
()).
toContain
(
'saved but not submitted'
);
expect
(
view
.
saveStatus
()).
toContain
(
'saved but not submitted'
);
// Change the text
// Change the text
// This should cause it to change to unsaved draft
// This should cause it to change to unsaved draft
view
.
response
(
'changed '
);
view
.
response
(
'changed '
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
saveEnabled
()).
toBe
(
true
);
expect
(
view
.
saveEnabled
()).
toBe
(
true
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
expect
(
view
.
saveStatus
()).
toContain
(
'This response has not been saved.'
);
});
});
...
@@ -238,7 +243,7 @@ describe("OpenAssessment.ResponseView", function() {
...
@@ -238,7 +243,7 @@ describe("OpenAssessment.ResponseView", function() {
// Change the text, then expect the unsaved warning to be enabled.
// Change the text, then expect the unsaved warning to be enabled.
view
.
response
(
'Lorem ipsum'
);
view
.
response
(
'Lorem ipsum'
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
// Expect the unsaved work warning to be enabled
// Expect the unsaved work warning to be enabled
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
...
@@ -247,7 +252,7 @@ describe("OpenAssessment.ResponseView", function() {
...
@@ -247,7 +252,7 @@ describe("OpenAssessment.ResponseView", function() {
it
(
"disables the unsaved work warning when the user saves a response"
,
function
()
{
it
(
"disables the unsaved work warning when the user saves a response"
,
function
()
{
// Change the text, then expect the unsaved warning to be enabled.
// Change the text, then expect the unsaved warning to be enabled.
view
.
response
(
'Lorem ipsum'
);
view
.
response
(
'Lorem ipsum'
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
// Save the response and expect the unsaved warning to be disabled
// Save the response and expect the unsaved warning to be disabled
...
@@ -258,11 +263,104 @@ describe("OpenAssessment.ResponseView", function() {
...
@@ -258,11 +263,104 @@ describe("OpenAssessment.ResponseView", function() {
it
(
"disables the unsaved work warning when the user submits a response"
,
function
()
{
it
(
"disables the unsaved work warning when the user submits a response"
,
function
()
{
// Change the text, then expect the unsaved warning to be enabled.
// Change the text, then expect the unsaved warning to be enabled.
view
.
response
(
'Lorem ipsum'
);
view
.
response
(
'Lorem ipsum'
);
view
.
r
esponseChanged
();
view
.
handleR
esponseChanged
();
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
true
);
// Submit the response and expect the unsaved warning to be disabled
// Submit the response and expect the unsaved warning to be disabled
view
.
submit
();
view
.
submit
();
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
false
);
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
false
);
});
});
it
(
"autosaves after a user changes a response"
,
function
()
{
// Disable the autosave delay after changing/saving a response
view
.
AUTO_SAVE_WAIT
=
-
1
;
// Check that the problem is initially unsaved
expect
(
view
.
saveStatus
()).
toContain
(
'not been saved'
);
// Change the response
view
.
response
(
'Lorem ipsum'
);
view
.
handleResponseChanged
();
// Usually autosave would be called by a timer.
// For testing purposes, we disable the timer
// and trigger the autosave manually.
view
.
autoSave
();
// Expect that the problem has been saved
expect
(
view
.
saveStatus
()).
toContain
(
'saved but not submitted'
);
// Expect that the unsaved warning is disabled
expect
(
view
.
unsavedWarningEnabled
()).
toBe
(
false
);
});
it
(
"schedules autosave polling"
,
function
()
{
runs
(
function
()
{
// Spy on the autosave call
spyOn
(
view
,
'autoSave'
).
andCallThrough
();
// Enable autosave with a short poll interval
view
.
AUTO_SAVE_POLL_INTERVAL
=
1
;
view
.
setAutoSaveEnabled
(
true
);
});
// Wait for autosave to be called
waitsFor
(
function
()
{
return
view
.
autoSave
.
callCount
>
0
;
},
"AutoSave should have been called"
,
5000
);
});
it
(
"stops autosaving after a save error"
,
function
()
{
// Disable the autosave delay after changing/saving a response
view
.
AUTO_SAVE_WAIT
=
-
1
;
// Simulate a server error
var
errorPromise
=
$
.
Deferred
(
function
(
defer
)
{
defer
.
rejectWith
(
this
,
[
"This response could not be saved"
]);
}).
promise
();
spyOn
(
server
,
'save'
).
andCallFake
(
function
()
{
return
errorPromise
;
});
// Change the response and save it
view
.
response
(
'Lorem ipsum'
);
view
.
handleResponseChanged
();
view
.
save
();
// Expect that the save status shows an error
expect
(
view
.
saveStatus
()).
toContain
(
'Error'
);
// Autosave (usually would be called by a timer, but we disable
// that for testing purposes).
view
.
autoSave
();
// The server save shoulde have been called just once
// (autosave didn't call it).
expect
(
server
.
save
.
callCount
).
toEqual
(
1
);
});
it
(
"waits after user changes a response to autosave"
,
function
()
{
// Set a long autosave delay
view
.
AUTO_SAVE_WAIT
=
900000
;
// Change the response
view
.
response
(
'Lorem ipsum'
);
view
.
handleResponseChanged
();
// Autosave
view
.
autoSave
();
// Expect that the problem is still unsaved
expect
(
view
.
saveStatus
()).
toContain
(
'not been saved'
);
});
it
(
"does not autosave if a user hasn't changed the response"
,
function
()
{
// Disable the autosave delay after changing/saving a response
view
.
AUTO_SAVE_WAIT
=
-
1
;
// Autosave (usually would be called by a timer, but we disable
// that for testing purposes).
view
.
autoSave
();
// Since we haven't made any changes, the response should still be unsaved.
expect
(
view
.
saveStatus
()).
toContain
(
'not been saved'
);
});
});
});
apps/openassessment/xblock/static/js/src/oa_response.js
View file @
3baad585
...
@@ -14,10 +14,21 @@ OpenAssessment.ResponseView = function(element, server, baseView) {
...
@@ -14,10 +14,21 @@ OpenAssessment.ResponseView = function(element, server, baseView) {
this
.
server
=
server
;
this
.
server
=
server
;
this
.
baseView
=
baseView
;
this
.
baseView
=
baseView
;
this
.
savedResponse
=
""
;
this
.
savedResponse
=
""
;
this
.
lastChangeTime
=
Date
.
now
();
this
.
errorOnLastSave
=
false
;
this
.
autoSaveTimerId
=
null
;
};
};
OpenAssessment
.
ResponseView
.
prototype
=
{
OpenAssessment
.
ResponseView
.
prototype
=
{
// Milliseconds between checks for whether we should autosave.
AUTO_SAVE_POLL_INTERVAL
:
2000
,
// Required delay after the user changes a response or a save occurs
// before we can autosave.
AUTO_SAVE_WAIT
:
2000
,
/**
/**
Load the response (submission) view.
Load the response (submission) view.
**/
**/
...
@@ -28,6 +39,7 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -28,6 +39,7 @@ OpenAssessment.ResponseView.prototype = {
// Load the HTML and install event handlers
// Load the HTML and install event handlers
$
(
'#openassessment__response'
,
view
.
element
).
replaceWith
(
html
);
$
(
'#openassessment__response'
,
view
.
element
).
replaceWith
(
html
);
view
.
installHandlers
();
view
.
installHandlers
();
view
.
setAutoSaveEnabled
(
true
);
}
}
).
fail
(
function
(
errMsg
)
{
).
fail
(
function
(
errMsg
)
{
view
.
baseView
.
showLoadError
(
'response'
);
view
.
baseView
.
showLoadError
(
'response'
);
...
@@ -46,7 +58,7 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -46,7 +58,7 @@ OpenAssessment.ResponseView.prototype = {
// Install change handler for textarea (to enable submission button)
// Install change handler for textarea (to enable submission button)
this
.
savedResponse
=
this
.
response
();
this
.
savedResponse
=
this
.
response
();
var
handleChange
=
function
(
eventData
)
{
view
.
r
esponseChanged
();
};
var
handleChange
=
function
(
eventData
)
{
view
.
handleR
esponseChanged
();
};
sel
.
find
(
'#submission__answer__value'
).
on
(
'change keyup drop paste'
,
handleChange
);
sel
.
find
(
'#submission__answer__value'
).
on
(
'change keyup drop paste'
,
handleChange
);
// Install a click handler for submission
// Install a click handler for submission
...
@@ -69,6 +81,29 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -69,6 +81,29 @@ OpenAssessment.ResponseView.prototype = {
},
},
/**
/**
Enable or disable autosave polling.
Args:
enabled (boolean): If true, start polling for whether we need to autosave.
Otherwise, stop polling.
**/
setAutoSaveEnabled
:
function
(
enabled
)
{
if
(
enabled
)
{
if
(
this
.
autoSaveTimerId
===
null
)
{
this
.
autoSaveTimerId
=
setInterval
(
$
.
proxy
(
this
.
autoSave
,
this
),
this
.
AUTO_SAVE_POLL_INTERVAL
);
}
}
else
{
if
(
this
.
autoSaveTimerId
!==
null
)
{
clearInterval
(
this
.
autoSaveTimerId
);
}
}
},
/**
Enable/disable the submit button.
Enable/disable the submit button.
Check that whether the submit button is enabled.
Check that whether the submit button is enabled.
...
@@ -88,7 +123,7 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -88,7 +123,7 @@ OpenAssessment.ResponseView.prototype = {
if
(
typeof
enabled
===
'undefined'
)
{
if
(
typeof
enabled
===
'undefined'
)
{
return
!
sel
.
hasClass
(
'is--disabled'
);
return
!
sel
.
hasClass
(
'is--disabled'
);
}
else
{
}
else
{
sel
.
toggleClass
(
'is--disabled'
,
!
enabled
)
sel
.
toggleClass
(
'is--disabled'
,
!
enabled
)
;
}
}
},
},
...
@@ -193,29 +228,69 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -193,29 +228,69 @@ OpenAssessment.ResponseView.prototype = {
}
}
},
},
/**
Check whether the response text has changed since the last save.
Returns: boolean
**/
responseChanged
:
function
()
{
var
currentResponse
=
$
.
trim
(
this
.
response
());
var
savedResponse
=
$
.
trim
(
this
.
savedResponse
);
return
savedResponse
!==
currentResponse
;
},
/**
Automatically save the user's response if certain conditions are met.
Usually, this would be called by a timer (see `setAutoSaveEnabled()`).
For testing purposes, it's useful to disable the timer
and call this function synchronously.
**/
autoSave
:
function
()
{
var
timeSinceLastChange
=
Date
.
now
()
-
this
.
lastChangeTime
;
// We only autosave if the following conditions are met:
// (1) The response has changed. We don't need to keep saving the same response.
// (2) Sufficient time has passed since the user last made a change to the response.
// We don't want to save a response while the user is in the middle of typing.
// (3) No errors occurred on the last save. We don't want to keep refreshing
// the error message in the UI. (The user can still retry the save manually).
if
(
this
.
responseChanged
()
&&
timeSinceLastChange
>
this
.
AUTO_SAVE_WAIT
&&
!
this
.
errorOnLastSave
)
{
this
.
save
();
}
},
/**
/**
Enable/disable the submission and save buttons based on whether
Enable/disable the submission and save buttons based on whether
the user has entered a response.
the user has entered a response.
**/
**/
r
esponseChanged
:
function
()
{
handleR
esponseChanged
:
function
()
{
// Enable the save/submit button only for non-blank responses
// Enable the save/submit button only for non-blank responses
var
currentResponse
=
$
.
trim
(
this
.
response
());
var
isBlank
=
(
$
.
trim
(
this
.
response
())
!==
''
);
var
isBlank
=
(
currentResponse
!==
''
);
this
.
submitEnabled
(
isBlank
);
this
.
submitEnabled
(
isBlank
);
// Update the save button, save status, and "unsaved changes" warning
// Update the save button, save status, and "unsaved changes" warning
// only if the response has changed
// only if the response has changed
if
(
$
.
trim
(
this
.
savedResponse
)
!==
currentResponse
)
{
if
(
this
.
responseChanged
()
)
{
this
.
saveEnabled
(
isBlank
);
this
.
saveEnabled
(
isBlank
);
this
.
saveStatus
(
gettext
(
'This response has not been saved.'
));
this
.
saveStatus
(
gettext
(
'This response has not been saved.'
));
this
.
unsavedWarningEnabled
(
true
);
this
.
unsavedWarningEnabled
(
true
);
}
}
// Record the current time (used for autosave)
this
.
lastChangeTime
=
Date
.
now
();
},
},
/**
/**
Save a response without submitting it.
Save a response without submitting it.
**/
**/
save
:
function
()
{
save
:
function
()
{
// If there were errors on previous calls to save, forget
// about them for now. If an error occurs on *this* save,
// we'll set this back to true in the error handler.
this
.
errorOnLastSave
=
false
;
// Update the save status and error notifications
// Update the save status and error notifications
this
.
saveStatus
(
gettext
(
'Saving...'
));
this
.
saveStatus
(
gettext
(
'Saving...'
));
this
.
baseView
.
toggleActionError
(
'save'
,
null
);
this
.
baseView
.
toggleActionError
(
'save'
,
null
);
...
@@ -240,6 +315,11 @@ OpenAssessment.ResponseView.prototype = {
...
@@ -240,6 +315,11 @@ OpenAssessment.ResponseView.prototype = {
}).
fail
(
function
(
errMsg
)
{
}).
fail
(
function
(
errMsg
)
{
view
.
saveStatus
(
gettext
(
'Error'
));
view
.
saveStatus
(
gettext
(
'Error'
));
view
.
baseView
.
toggleActionError
(
'save'
,
errMsg
);
view
.
baseView
.
toggleActionError
(
'save'
,
errMsg
);
// Remember that an error occurred
// so we can disable autosave
//(avoids repeatedly refreshing the error message)
view
.
errorOnLastSave
=
true
;
});
});
},
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment