Commit ea256307 by Albert (AJ) St. Aubin Committed by GitHub

Merge pull request #958 from edx/aj/TNL-5883_announce_state

Status updates sent to the Aria live region as the user completes eac…
parents 3f31c1bd d2f3dbb4
......@@ -8,14 +8,14 @@
{% if score %}
<span class="step__label">{% trans "Your Grade" %}: </span>
<span class="grade__value">
<span class="grade__value__title">
{% with points_earned_string=score.points_earned|stringformat:"s" points_possible_string=score.points_possible|stringformat:"s" %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
<span class="grade__value__title">
{% with points_earned_string=score.points_earned|stringformat:"s" points_possible_string=score.points_possible|stringformat:"s" %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
</span>
</span>
</span>
{% else %}
<span class="step__label">{% trans "Your Grade" %}</span>
{% endif %}
......
......@@ -47,5 +47,6 @@
{% endif %}
</div>
</div>
<div class="sr reader-feedback" aria-live="polite"></div>
</div>
{% endspaceless %}
......@@ -13,7 +13,7 @@
{% block title %}
<span class="step__status">
<span class="step__status__value">
<span class="copy">{{review_num}}
<span class="copy">
{% with review_num_string=review_num|stringformat:"s" must_grade_string=must_grade|stringformat:"s" %}
{% blocktrans with review_number='<span class="step__status__number--current">'|safe|add:review_num_string|add:"</span>"|safe num_must_grade='<span class="step__status__value--required">'|safe|add:must_grade_string|add:"</span>"|safe %}
In Progress ({{ review_number }} of {{ num_must_grade }})
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -27,6 +27,7 @@ OpenAssessment.BaseView = function(runtime, element, server, data) {
// Staff-only area with information and tools for managing student submissions
this.staffAreaView = new OpenAssessment.StaffAreaView(this.element, this.server, this);
this.usageID = '';
this.srStatusUpdates = [];
};
if (typeof OpenAssessment.unsavedChanges === 'undefined' || !OpenAssessment.unsavedChanges) {
......@@ -68,6 +69,89 @@ OpenAssessment.BaseView.prototype = {
},
/**
* Add the text messages to the Aria live region.
*
* @param {string[]} texts
*/
srReadTexts: function(texts) {
var readerFeedbackID = '.sr.reader-feedback',
$readerFeedbackSelector = $(readerFeedbackID),
htmlFeedback = '';
$.each(texts, function(ids, value) {
htmlFeedback = htmlFeedback + '<p>' + value + '</p>\n';
});
$readerFeedbackSelector.html(htmlFeedback);
},
/**
* Checks the rendering status of the views that may require Screen Reader Status updates.
*
* The only views that should be added here are those that require Screen Reader updates when moving from one
* step to another.
*
* @returns {boolean} true if any step's view is still loading.
*/
areSRStepsLoading: function() {
return this.responseView.isRendering ||
this.peerView.isRendering ||
this.selfView.isRendering ||
this.gradeView.isRendering ||
this.trainingView.isRendering ||
this.staffView.isRendering;
},
/**
* Updates text in the Aria live region if all sections are rendered and focuses on the specified ID.
*
* @param {String} stepID - The id of the Step being worked on.
* @param {String} usageID - The Usage id of the xBlock.
* @param {boolean} gradeStatus - true if this is a Grade status, false if it is an assessment status.
* @param {Object} currentView - Current active view.
* @param {String} focusID - The ID of the region to focus on.
*/
announceStatusChangeToSRandFocus: function(stepID, usageID, gradeStatus, currentView, focusID) {
var text = this.getStatus(stepID, currentView, gradeStatus);
if (typeof usageID !== 'undefined' &&
$(stepID, currentView.element).hasClass("is--showing") &&
typeof focusID !== 'undefined') {
$(focusID, currentView.element).focus();
this.srStatusUpdates.push(text);
} else if (currentView.announceStatus) {
this.srStatusUpdates.push(text);
}
if (!this.areSRStepsLoading() && this.srStatusUpdates.length > 0) {
this.srReadTexts(this.srStatusUpdates);
this.srStatusUpdates = [];
}
currentView.announceStatus = false;
},
/**
* Retrieves and returns the current status of a given step.
*
* @param {String} stepID - The id of the Step to retrieve status for.
* @param {Object} currentView - The current view.
* @param {boolean} gradeStatus - true if the status to be retrieved is the grade status,
* false if it is the assessment status
* @returns {String} - the current status.
*/
getStatus: function(stepID, currentView, gradeStatus) {
var cssBase = stepID + " .step__header .step__title ";
var cssStringTitle = cssBase + ".step__label";
var cssStringStatus = cssBase + ".step__status";
if (gradeStatus) {
cssStringStatus = cssBase + ".grade__value";
}
return $(cssStringTitle, currentView.element).text().trim() + ' ' +
$(cssStringStatus, currentView.element).text().trim();
},
/**
* Install click handlers to expand/collapse a section.
*
* @param {element} parentElement JQuery selector for the container element.
......@@ -190,6 +274,11 @@ OpenAssessment.BaseView.prototype = {
// Send focus to the error message
$(container + " > .message", element).focus();
}
if (message !== null) {
var contentTitle = $(container + " .message__title").text();
this.srReadTexts([contentTitle, message]);
}
},
/**
......
......@@ -10,6 +10,8 @@ OpenAssessment.GradeView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
this.announceStatus = false;
this.isRendering = false;
};
OpenAssessment.GradeView.prototype = {
......@@ -20,18 +22,17 @@ OpenAssessment.GradeView.prototype = {
var view = this;
var baseView = this.baseView;
var stepID = ".step--grade";
var focusID = "[id='oa_grade_" + usageID + "']";
view.isRendering = true;
this.server.render('grade').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.server.renderLatex($(stepID, view.element));
view.isRendering = false;
view.installHandlers();
if (typeof usageID !== 'undefined' &&
!$(stepID, view.element).hasClass("is--unfinished") &&
!$(stepID, view.element).hasClass("is--unstarted") &&
!$(stepID, view.element).hasClass("is--waiting--staff")) {
$("[id='oa_grade_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, true, view, focusID);
}
).fail(function(errMsg) {
baseView.showLoadError('grade', errMsg);
......
......@@ -14,6 +14,8 @@ OpenAssessment.PeerView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
};
OpenAssessment.PeerView.prototype = {
......@@ -26,15 +28,20 @@ OpenAssessment.PeerView.prototype = {
load: function(usageID) {
var view = this;
var stepID = ".step--peer-assessment";
var focusID = "[id='oa_peer_" + usageID + "']";
view.isRendering = true;
this.server.render('peer_assessment').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers(false);
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_peer_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
}
).fail(function() {
view.baseView.showLoadError('peer-assessment');
......@@ -52,16 +59,22 @@ OpenAssessment.PeerView.prototype = {
**/
loadContinuedAssessment: function(usageID) {
var view = this;
var stepID = ".step--peer-assessment";
var focusID = "[id='oa_peer_" + usageID + "']";
view.continueAssessmentEnabled(false);
view.isRendering = true;
this.server.renderContinuedPeer().done(
function(html) {
// Load the HTML and install event handlers
$('.step--peer-assessment', view.element).replaceWith(html);
view.server.renderLatex($('.step--peer-assessment', view.element));
view.isRendering = false;
view.installHandlers(true);
if (typeof usageID !== 'undefined') {
$("[id='oa_peer_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
}
).fail(function() {
view.baseView.showLoadError('peer-assessment');
......@@ -123,6 +136,9 @@ OpenAssessment.PeerView.prototype = {
// Override default form submission
eventObject.preventDefault();
//Status will change in update announce it to the Screen Reader after Render
view.announceStatus = true;
// Handle the click
if (!isContinuedAssessment) { view.peerAssess(); }
else { view.continuedPeerAssess(); }
......
......@@ -24,6 +24,8 @@ OpenAssessment.ResponseView = function(element, server, fileUploader, baseView,
this.autoSaveTimerId = null;
this.data = data;
this.fileUploaded = false;
this.announceStatus = false;
this.isRendering = false;
};
OpenAssessment.ResponseView.prototype = {
......@@ -46,6 +48,9 @@ OpenAssessment.ResponseView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--response';
var focusID = "[id='oa_response_" + usageID + "']";
view.isRendering = true;
this.server.render('submission').done(
function(html) {
// Load the HTML and install event handlers
......@@ -53,9 +58,11 @@ OpenAssessment.ResponseView.prototype = {
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
view.setAutoSaveEnabled(true);
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_response_" + usageID + "']", view.element).focus();
}
view.isRendering = false;
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
}
).fail(function() {
view.baseView.showLoadError('response');
......@@ -363,7 +370,6 @@ OpenAssessment.ResponseView.prototype = {
submit: function() {
// Immediately disable the submit button to prevent multiple submission
this.submitEnabled(false);
var view = this;
var baseView = this.baseView;
var fileDefer = $.Deferred();
......@@ -423,9 +429,13 @@ OpenAssessment.ResponseView.prototype = {
moveToNextStep: function() {
var baseView = this.baseView;
var usageID = baseView.getUsageID();
var view = this;
this.load(usageID);
baseView.loadAssessmentModules(usageID);
view.announceStatus = true;
// Disable the "unsaved changes" warning if the user
// tries to navigate to another page.
baseView.unsavedWarningEnabled(false, this.UNSAVED_WARNING_KEY);
......
......@@ -14,6 +14,8 @@ OpenAssessment.SelfView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
};
OpenAssessment.SelfView.prototype = {
......@@ -26,15 +28,18 @@ OpenAssessment.SelfView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--self-assessment';
var focusID = "[id='oa_self_" + usageID + "']";
view.isRendering = true;
this.server.render('self_assessment').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_self_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
}
).fail(function() {
view.showLoadError('self-assessment');
......@@ -134,9 +139,8 @@ OpenAssessment.SelfView.prototype = {
).done(
function() {
baseView.unsavedWarningEnabled(false, view.UNSAVED_WARNING_KEY);
view.announceStatus = true;
baseView.loadAssessmentModules(usageID);
view.load(usageID);
baseView.scrollToTop(".step--self-assessment");
}
).fail(function(errMsg) {
baseView.toggleActionError('self', errMsg);
......
......@@ -9,6 +9,8 @@ OpenAssessment.StaffView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
this.isRendering = false;
this.announceStatus = false;
};
OpenAssessment.StaffView.prototype = {
......@@ -18,14 +20,16 @@ OpenAssessment.StaffView.prototype = {
**/
load: function(usageID) {
var view = this;
var stepID = ".step--staff-assessment";
var focusID = "[id='oa_staff_grade_" + usageID + "']";
view.isRendering = true;
this.server.render('staff_assessment').done(
function(html) {
$('.step--staff-assessment', view.element).replaceWith(html);
view.isRendering = false;
view.installHandlers();
if (typeof usageID !== 'undefined' &&
$(".step--staff-assessment", view.element).hasClass("is--showing")) {
$("[id='oa_staff_grade_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
}
).fail(function() {
view.baseView.showLoadError('staff-assessment');
......
......@@ -14,6 +14,8 @@ OpenAssessment.StudentTrainingView = function(element, server, baseView) {
this.server = server;
this.baseView = baseView;
this.rubric = null;
this.isRendering = false;
this.announceStatus = false;
};
OpenAssessment.StudentTrainingView.prototype = {
......@@ -24,15 +26,19 @@ OpenAssessment.StudentTrainingView.prototype = {
load: function(usageID) {
var view = this;
var stepID = '.step--student-training';
var focusID = "[id='oa_training_" + usageID + "']";
view.isRendering = true;
this.server.render('student_training').done(
function(html) {
// Load the HTML and install event handlers
$(stepID, view.element).replaceWith(html);
view.isRendering = false;
view.server.renderLatex($(stepID, view.element));
view.installHandlers();
if (typeof usageID !== 'undefined' && $(stepID, view.element).hasClass("is--showing")) {
$("[id='oa_training_" + usageID + "']", view.element).focus();
}
view.baseView.announceStatusChangeToSRandFocus(stepID, usageID, false, view, focusID);
view.announceStatus = false;
}
).fail(function() {
view.baseView.showLoadError('student-training');
......@@ -69,6 +75,7 @@ OpenAssessment.StudentTrainingView.prototype = {
// Handle the click
view.assess();
view.announceStatus = true;
}
);
},
......
......@@ -38,6 +38,9 @@ class BaseAssessmentPage(PageObject):
loc=self._problem_location
)
def get_sr_html(self):
return self.q(css='.sr.reader-feedback').html
class MultipleAssessmentPage(BaseAssessmentPage):
"""
......@@ -76,7 +79,6 @@ class OpenAssessmentPage(BaseAssessmentPage):
"""
return ".vert-{vertical_index}".format(vertical_index=self.vertical_index)
def submit(self, button_css=".action--submit"):
"""
Click the submit button on the page.
......
......@@ -790,16 +790,25 @@ class FullWorkflowMixin(object):
(str, str): the username and password of the newly created user
"""
username, email = self.do_submission()
actual = self.submission_page.get_sr_html()
EmptyPromise(self.submission_page.button(".step--student-training").is_focused(),
"Student training button should be focused")
self.assertIn('Your Response Complete', actual[0])
self.assertIn('Learn to Assess Responses In Progress (1 of 2)', actual[0])
self.do_training()
actual = self.submission_page.get_sr_html()
EmptyPromise(self.submission_page.button(".step--self-assessment").is_focused(),
"Self assessment button should be focused")
self.assertIn('Learn to Assess Responses Complete', actual[0])
self.assertIn('Assess Your Response In Progress', actual[0])
self.submit_self_assessment(self.SELF_ASSESSMENT)
actual = self.submission_page.get_sr_html()
EmptyPromise(self.submission_page.button(".step--grade").is_focused(),
"Grade button should be focused")
self.assertIn('Assess Your Response Complete', actual[0])
self.assertIn('Assess Peers In Progress (1 of 1)', actual[0])
return username, email
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment