Commit 0773f068 by Dave St.Germain

Answer checks should offer feedback to assistive tech. This commit adds

a page level javascript SR object to enable reading of alert messages.
LMS-2158
parent 021e6f5b
......@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
LMS: Enabled screen reader feedback of problem responses.
LMS-2158
Blades: Removed tooltip from captions. BLD-629.
Blades: Fix problem with loading YouTube API is it is not available. BLD-531.
......
......@@ -259,6 +259,8 @@ class InputTypeBase(object):
'id': self.input_id,
'value': self.value,
'status': self.status,
'status_class': self.status_class,
'status_display': self.status_display,
'msg': self.msg,
'STATIC_URL': self.capa_system.STATIC_URL,
}
......@@ -268,6 +270,34 @@ class InputTypeBase(object):
context.update(self._extra_context())
return context
@property
def status_class(self):
"""
Return the CSS class for the associated status.
"""
statuses = {
'unsubmitted': 'unanswered',
'incomplete': 'incorrect',
'queued': 'processing',
}
return statuses.get(self.status, self.status)
@property
def status_display(self):
"""
Return the human-readable and translated word for the associated status.
"""
_ = self.capa_system.i18n.ugettext
statuses = {
'correct': _('correct'),
'incorrect': _('incorrect'),
'incomplete': _('incomplete'),
'unanswered': _('unanswered'),
'unsubmitted': _('unanswered'),
'queued': _('queued'),
}
return statuses.get(self.status, self.status)
def _extra_context(self):
"""
Subclasses can override this to return extra context that should be passed to their templates for rendering.
......@@ -1135,16 +1165,10 @@ class FormulaEquationInput(InputTypeBase):
TODO (vshnayder): Get rid of 'previewer' once we have a standard way of requiring js to be loaded.
"""
# `reported_status` is basically `status`, except we say 'unanswered'
reported_status = ''
if self.status == 'unsubmitted':
reported_status = 'unanswered'
elif self.status in ('correct', 'incorrect', 'incomplete'):
reported_status = self.status
return {
'previewer': '{static_url}js/capa/src/formula_equation_preview.js'.format(
static_url=self.capa_system.STATIC_URL),
'reported_status': reported_status,
}
def handle_ajax(self, dispatch, get):
......
<section id="chemicalequationinput_${id}" class="chemicalequationinput">
<div id="chemicalequationinput_${id}" class="chemicalequationinput">
<div class="script_placeholder" data-src="${previewer}"/>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
<div class="${status_class}" id="status_${id}">
<input type="text" name="input_${id}" id="input_${id}" aria-label="${label}" aria-describedby="answer_${id}" data-input-id="${id}" value="${value|h}"
% if size:
......@@ -18,22 +10,14 @@
/>
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
${value|h} -
${status_display}
</p>
<div id="input_${id}_preview" class="equation">
</div>
<div id="input_${id}_preview" class="equation"></div>
<p id="answer_${id}" class="answer"></p>
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
</div>
<form class="choicegroup capa_inputtype" id="inputtype_${id}">
<div class="indicator_container">
% if input_type == 'checkbox' or not value:
% if status == 'unsubmitted' or show_correctness == 'never':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif status == 'correct':
<span class="correct" id="status_${id}"><span class="sr">Status: correct</span></span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}"><span class="sr">Status: incorrect</span></span>
% elif status == 'incomplete':
<span class="incorrect" id="status_${id}"><span class="sr">Status: incomplete</span></span>
% endif
<span class="status ${status_class if show_correctness != 'never' else 'unanswered'}"
id="status_${id}"
aria-describedby="inputtype_${id}">
<span class="sr">
%for choice_id, choice_description in choices:
% if choice_id in value:
${choice_description},
%endif
%endfor
-
${status_display}
</span>
</span>
% endif
</div>
<fieldset role="radiogroup" aria-label="${label}">
<fieldset role="${input_type}group" aria-label="${label}">
% for choice_id, choice_description in choices:
<label for="input_${id}_${choice_id}"
......@@ -39,20 +43,15 @@
% elif input_type != 'radio' and choice_id in value:
checked="true"
% endif
% if input_type != 'radio':
aria-multiselectable="true"
% endif
/> ${choice_description}
% if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ):
<%
if status == 'correct':
correctness = 'correct'
elif status == 'incorrect':
correctness = 'incorrect'
else:
correctness = None
%>
% if correctness and not show_correctness=='never':
<span class="sr" aria-describedby="input_${id}_${choice_id}">Status: ${correctness}</span>
% if status in ('correct', 'incorrect') and not show_correctness=='never':
<span class="sr status">${choice_description|h} - ${status_display}</span>
% endif
% endif
</label>
......
......@@ -10,15 +10,7 @@
<div class="script_placeholder" data-src="/static/js/capa/choicetextinput.js"/>
<div class="indicator_container">
% if input_type == 'checkbox' or not element_checked:
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif status == 'correct':
<span class="correct" id="status_${id}"></span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}"></span>
% elif status == 'incomplete':
<span class="incorrect" id="status_${id}"></span>
% endif
<span class="status ${status_class}" id="status_${id}"></span>
% endif
</div>
......
......@@ -16,27 +16,26 @@
>${value|h}</textarea>
<div class="grader-status" tabindex="-1">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Unanswered</span>
% elif status == 'correct':
<span class="correct" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Correct</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Incorrect</span>
% elif status == 'queued':
<span class="processing" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Queued</span>
<span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
<span id="status_${id}"
class="${status_class}"
aria-describedby="input_${id}"
>
<span class="status sr">${status_display}</span>
</span>
% if status == 'queued':
<span style="display:none;" class="xqueue" id="${id}">${queue_len}</span>
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<p class="debug">${status}</p>
<p class="debug">${status_display}</p>
</div>
<span id="answer_${id}"></span>
<div class="external-grader-message">
<div class="external-grader-message" aria-live="polite">
${msg|n}
</div>
</section>
<% doinline = 'style="display:inline-block;vertical-align:top"' if inline else "" %>
<section id="formulaequationinput_${id}" class="inputtype formulaequationinput" ${doinline}>
<div class="${reported_status}" id="status_${id}">
<div class="${status_class}" id="status_${id}">
<input type="text" name="input_${id}" id="input_${id}"
data-input-id="${id}" value="${value|h}"
aria-label="${label}"
......@@ -9,7 +9,19 @@
% endif
/>
<p class="status">${reported_status}</p>
<p class="status"
%if status != 'unsubmitted':
aria-hidden="true"
%endif
>
<span class="sr equation">
%if value:
${value|h}
% else:
${label}
%endif
</span> - ${status_display}
</p>
<div id="input_${id}_preview" class="equation">
\[\]
......
......@@ -17,30 +17,29 @@
>${value|h}</textarea>
<div class="grader-status" tabindex="-1">
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"><span class="sr">Status: </span>Unanswered</span>
% elif status == 'correct':
<span class="correct" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Correct</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Incorrect</span>
% elif status == 'queued':
<span class="processing" id="status_${id}" aria-describedby="input_${id}"><span class="sr">Status: </span>Queued</span>
<span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
<span id="status_${id}"
class="${status_class}"
aria-describedby="input_${id}"
>
<span class="status sr">${status_display}</span>
</span>
% if status == 'queued':
<span style="display:none;" class="xqueue" id="${id}">${queue_len}</span>
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif
<p class="debug">${status}</p>
<p class="debug">${status_display}</p>
</div>
<span id="answer_${id}"></span>
<div class="external-grader-message">
<div class="external-grader-message" aria-live="polite">
${msg|n}
</div>
<div class="external-grader-message">
<div class="external-grader-message" aria-live="polite">
${queue_msg|n}
</div>
......
......@@ -12,25 +12,12 @@
% endfor
</select>
<span id="answer_${id}"></span>
% if status == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: unsubmitted</span>
</span>
% elif status == 'correct':
<span class="correct" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: correct</span>
</span>
% elif status == 'incorrect':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incorrect</span>
<span id="answer_${id}"></span>
<span class="status ${status_class}"
id="status_${id}"
aria-describedby="input_${id}">
<span class="sr">${value|h} - ${status_display}</span>
</span>
% elif status == 'incomplete':
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
<span class="sr">Status: incomplete</span>
</span>
% endif
% if msg:
<span class="message">${msg|n}</span>
......
<% doinline = "inline" if inline else "" %>
<section id="inputtype_${id}" class="${'text-input-dynamath' if do_math else ''} capa_inputtype ${doinline} textline" >
<div id="inputtype_${id}" class="${'text-input-dynamath' if do_math else ''} capa_inputtype ${doinline} textline" >
% if preprocessor is not None:
<div class="text-input-dynamath_data ${doinline}" data-preprocessor="${preprocessor['class_name']}"/>
<div class="script_placeholder" data-src="${preprocessor['script_src']}"/>
% endif
% if status == 'unsubmitted':
<div class="unanswered ${doinline}" id="status_${id}">
% elif status == 'correct':
<div class="correct ${doinline}" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect ${doinline}" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect ${doinline}" id="status_${id}">
% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'):
<div class="${status_class} ${doinline}" id="status_${id}">
% endif
% if hidden:
<div style="display:none;" name="${hidden}" inputid="input_${id}" />
......@@ -33,28 +27,29 @@
/>
${trailing_text | h}
<p class="status" aria-describedby="input_${id}">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
<p class="status"
%if status != 'unsubmitted':
aria-hidden="true"
%endif
aria-describedby="input_${id}">
%if value:
${value|h}
% else:
${label}
%endif
-
${status_display}
</p>
<p id="answer_${id}" class="answer"></p>
<p id="answer_${id}" class="answer" aria-hidden="true"></p>
% if do_math:
<div id="display_${id}" class="equation">`{::}`</div>
<textarea style="display:none" id="input_${id}_dynamath" name="input_${id}_dynamath">
</textarea>
<textarea style="display:none" id="input_${id}_dynamath" name="input_${id}_dynamath"></textarea>
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'):
</div>
% endif
......@@ -62,4 +57,4 @@
<span class="message">${msg|n}</span>
% endif
</section>
</div>
......@@ -154,6 +154,8 @@ class CapaHtmlRenderTest(unittest.TestCase):
expected_textline_context = {
'STATIC_URL': '/dummy-static/',
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'value': '',
'preprocessor': None,
......
......@@ -57,6 +57,8 @@ class OptionInputTest(unittest.TestCase):
'value': 'Down',
'options': [('Up', 'Up'), ('Down', 'Down'), ('Don\'t know', 'Don\'t know')],
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
'label': '',
'msg': '',
'inline': False,
......@@ -117,6 +119,8 @@ class ChoiceGroupTest(unittest.TestCase):
'id': 'sky_input',
'value': 'foil3',
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
'label': '',
'msg': '',
'input_type': expected_input_type,
......@@ -170,6 +174,8 @@ class JavascriptInputTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
# 'label': '',
'msg': '',
'value': '3',
......@@ -203,6 +209,8 @@ class TextLineTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': 'testing 123',
'size': size,
'msg': '',
......@@ -235,6 +243,8 @@ class TextLineTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'size': size,
'msg': '',
......@@ -279,6 +289,8 @@ class TextLineTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'BumbleBee',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'size': size,
'msg': '',
......@@ -320,6 +332,8 @@ class FileSubmissionTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
'label': '',
'msg': the_input.submitted_msg,
'value': 'BumbleBee.py',
......@@ -370,6 +384,8 @@ class CodeInputTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
# 'label': '',
'msg': the_input.submitted_msg,
'mode': mode,
......@@ -424,6 +440,8 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
# 'label': '',
'msg': self.the_input.submitted_msg,
'mode': self.mode,
......@@ -455,6 +473,8 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
# 'label': '',
'msg': the_input.submitted_msg,
'mode': self.mode,
......@@ -486,6 +506,8 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': status,
'status_class': status,
'status_display': unicode(status),
# 'label': '',
'msg': '',
'mode': self.mode,
......@@ -516,6 +538,8 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'status_class': 'processing',
'status_display': u'queued',
# 'label': '',
'msg': the_input.submitted_msg,
'mode': self.mode,
......@@ -593,7 +617,7 @@ class MatlabTest(unittest.TestCase):
output = self.the_input.get_html()
self.assertEqual(
etree.tostring(output),
"""<div>{\'status\': \'queued\', \'button_enabled\': True, \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'cols\': \'80\', \'STATIC_URL\': \'/dummy-static/\', \'linenumbers\': \'true\', \'queue_msg\': \'\', \'value\': \'print "good evening"\', \'msg\': u\'Submitted. As soon as a response is returned, this message will be replaced by that feedback.\', \'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/addons/octave.js\', \'hidden\': \'\', \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
"""<div>{\'status\': \'queued\', \'button_enabled\': True, \'linenumbers\': \'true\', \'rows\': \'10\', \'queue_len\': \'3\', \'mode\': \'\', \'cols\': \'80\', \'value\': \'print "good evening"\', \'status_class\': \'processing\', \'queue_msg\': \'\', \'STATIC_URL\': \'/dummy-static/\', \'msg\': u\'Submitted. As soon as a response is returned, this message will be replaced by that feedback.\', \'matlab_editor_js\': \'/dummy-static/js/vendor/CodeMirror/addons/octave.js\', \'hidden\': \'\', \'status_display\': u\'queued\', \'id\': \'prob_1_2\', \'tabsize\': 4}</div>"""
)
# test html, that is correct HTML5 html, but is not parsable by XML parser.
......@@ -661,6 +685,8 @@ class SchematicTest(unittest.TestCase):
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'msg': '',
'initial_value': initial_value,
......@@ -704,6 +730,8 @@ class ImageInputTest(unittest.TestCase):
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'width': width,
'height': height,
......@@ -759,6 +787,8 @@ class CrystallographyTest(unittest.TestCase):
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
# 'label': '',
'msg': '',
'width': width,
......@@ -801,6 +831,8 @@ class VseprTest(unittest.TestCase):
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
'msg': '',
'width': width,
'height': height,
......@@ -833,6 +865,8 @@ class ChemicalEquationTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'H2OYeah',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': 'unanswered',
'label': '',
'msg': '',
'size': self.size,
......@@ -921,8 +955,9 @@ class FormulaEquationTest(unittest.TestCase):
'id': 'prob_1_2',
'value': 'x^2+1/2',
'status': 'unanswered',
'status_class': 'unanswered',
'status_display': u'unanswered',
'label': '',
'reported_status': '',
'msg': '',
'size': self.size,
'previewer': '/dummy-static/js/capa/src/formula_equation_preview.js',
......@@ -930,24 +965,6 @@ class FormulaEquationTest(unittest.TestCase):
}
self.assertEqual(context, expected)
def test_rendering_reported_status(self):
"""
Verify that the 'reported status' matches expectations.
"""
test_values = {
'': '', # Default
'unsubmitted': 'unanswered',
'correct': 'correct',
'incorrect': 'incorrect',
'incomplete': 'incomplete',
'not a status': ''
}
for self_status, reported_status in test_values.iteritems():
self.the_input.status = self_status
context = self.the_input._get_render_context() # pylint: disable=W0212
self.assertEqual(context['reported_status'], reported_status)
def test_formcalc_ajax_sucess(self):
"""
Verify that using the correct dispatch and valid data produces a valid response
......@@ -1069,6 +1086,8 @@ class DragAndDropTest(unittest.TestCase):
'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'status_class': 'unanswered',
'status_display': u'unanswered',
# 'label': '',
'msg': '',
'drag_and_drop_json': json.dumps(user_input)
......@@ -1122,6 +1141,8 @@ class AnnotationInputTest(unittest.TestCase):
'id': 'annotation_input',
'value': value,
'status': 'answered',
'status_class': 'answered',
'status_display': 'answered',
# 'label': '',
'msg': '',
'title': 'foo',
......@@ -1181,7 +1202,9 @@ class TestChoiceText(unittest.TestCase):
state = {
'value': '{}',
'id': 'choicetext_input',
'status': 'answered'
'status': 'answered',
'status_class': 'answered',
'status_display': u'answered',
}
first_input = self.build_choice_element('numtolerance_input', 'choiceinput_0_textinput_0', 'false', '')
......
......@@ -402,4 +402,3 @@ nav.sequence-bottom {
}
*/
}
......@@ -7,6 +7,10 @@ describe 'Problem', ->
@stubbedJax = root: jasmine.createSpyObj('jax.root', ['toMathML'])
MathJax.Hub.getAllJax.andReturn [@stubbedJax]
window.update_schematics = ->
# mock the screen reader alert
window.SR =
readElts: `function(){}`
readText: `function(){}`
# Load this function from spec/helper.coffee
# Note that if your test fails with a message like:
......@@ -232,7 +236,7 @@ describe 'Problem', ->
it 'toggle the show answer button', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
@problem.show()
expect($('.show .show-label')).toHaveText 'Hide Answer(s)'
expect($('.show .show-label')).toHaveText 'Hide Answer'
it 'add the showed class to element', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
......@@ -431,7 +435,7 @@ describe 'Problem', ->
it 'toggle the show answer button', ->
@problem.show()
expect($('.show .show-label')).toHaveText 'Show Answer(s)'
expect($('.show .show-label')).toHaveText 'Show Answer'
it 'remove the showed class from element', ->
@problem.show()
......
......@@ -129,11 +129,13 @@ class @Problem
render: (content) ->
if content
@el.attr({'aria-busy': 'true', 'aria-live': 'off', 'aria-atomic': 'false'})
@el.html(content)
JavascriptLoader.executeModuleScripts @el, () =>
@setupInputTypes()
@bind()
@queueing()
@el.attr('aria-busy', 'false')
else
$.postWithPrefix "#{@url}/problem_get", (response) =>
@el.html(response.html)
......@@ -226,7 +228,8 @@ class @Problem
required_files.splice(required_files.indexOf(file.name), 1)
if file.size > max_filesize
file_too_large = true
errors.push 'Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
max_size = max_filesize / (1000*1000)
errors.push "Your file #{file.name} is too large (max size: {max_size}MB)"
fd.append(element.id, file)
if element.files.length == 0
file_not_selected = true
......@@ -281,10 +284,12 @@ class @Problem
$.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
switch response.success
when 'incorrect', 'correct'
window.SR.readElts($(response.contents).find('.status'))
@render(response.contents)
@updateProgress response
if @el.hasClass 'showed'
@el.removeClass 'showed'
@$('div.action input.check').focus()
else
@gentle_alert response.success
Logger.log 'problem_graded', [@answers, response.contents], @id
......@@ -301,16 +306,23 @@ class @Problem
show: =>
if !@el.hasClass 'showed'
Logger.log 'problem_show', problem: @id
answer_text = []
$.postWithPrefix "#{@url}/problem_show", (response) =>
answers = response.answers
$.each answers, (key, value) =>
if $.isArray(value)
for choice in value
@$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true'
answer_text.push('<p>' + gettext('Answer:') + ' ' + value + '</p>')
else
answer = @$("#answer_#{key}, #solution_#{key}")
answer.html(value)
Collapsible.setCollapsibles(answer)
solution = $(value).find('.detailed-solution')
if solution.length
answer_text.push(solution)
else
answer_text.push('<p>' + gettext('Answer:') + ' ' + value + '</p>')
# TODO remove the above once everything is extracted into its own
# inputtype functions.
......@@ -327,15 +339,19 @@ class @Problem
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element]
`// Translators: the word Answer here refers to the answer to a problem the student must solve.`
@$('.show-label').text gettext('Hide Answer(s)')
@$('.show-label').text gettext('Hide Answer')
@$('.show-label .sr').text gettext('Hide Answer')
@el.addClass 'showed'
@updateProgress response
window.SR.readElts(answer_text)
else
@$('[id^=answer_], [id^=solution_]').text ''
@$('[correct_answer]').attr correct_answer: null
@el.removeClass 'showed'
`// Translators: the word Answer here refers to the answer to a problem the student must solve.`
@$('.show-label').text gettext('Show Answer(s)')
@$('.show-label').text gettext('Show Answer')
@$('.show-label .sr').text gettext('Reveal Answer')
window.SR.readText(gettext('Answer hidden'))
@el.find(".capa_inputtype").each (index, inputtype) =>
display = @inputtypeDisplays[$(inputtype).attr('id')]
......@@ -350,6 +366,7 @@ class @Problem
alert_elem = "<div class='capa_alert'>" + msg + "</div>"
@el.find('.action').after(alert_elem)
@el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700)
window.SR.readElts(msg)
save: =>
if not @check_save_waitfor(@save_internal)
......
......@@ -193,10 +193,12 @@ class @Sequence
mark_active: (position) ->
# Mark the correct tab as selected, for a11y helpfulness.
@$("#sequence-list a[aria-selected='true']").attr("aria-selected", "false")
@$('#sequence-list [role="tab"]').attr({
'aria-selected' : null
});
# Don't overwrite class attribute to avoid changing Progress class
element = @link_for(position)
element.removeClass("inactive")
.removeClass("visited")
.addClass("active")
.attr("aria-selected", "true")
.attr({"aria-selected": "true", 'tabindex': '0'})
......@@ -141,3 +141,39 @@ $('.nav-skip').keypress(function(e) {
}
}
});
// Creates a window level SR object that can be used for giving audible feedback to screen readers.
$(function(){
var SRAlert;
SRAlert = (function() {
function SRAlert() {
$('body').append('<div id="reader-feedback" class="sr" style="display:none" aria-hidden="false" aria-atomic="true" aria-live="assertive"></div>');
this.el = $('#reader-feedback');
}
SRAlert.prototype.clear = function() {
return this.el.html(' ');
};
SRAlert.prototype.readElts = function(elts) {
var feedback,
_this = this;
feedback = '';
$.each(elts, function(idx, value) {
return feedback += '<p>' + $(value).html() + '</p>\n';
});
return this.el.html(feedback);
};
SRAlert.prototype.readText = function(text) {
return this.el.text(text);
};
return SRAlert;
})();
window.SR = new SRAlert;
});
......@@ -130,11 +130,11 @@ Feature: LMS.Answer problems
Scenario: I can view and hide the answer if the problem has it:
Given I am viewing a "numerical" that shows the answer "always"
When I press the button with the label "Show Answer(s)"
Then the Show/Hide button label is "Hide Answer(s)"
When I press the button with the label "Show Answer"
Then the Show/Hide button label is "Hide Answer"
And I should see "4.14159" somewhere in the page
When I press the button with the label "Hide Answer(s)"
Then the Show/Hide button label is "Show Answer(s)"
When I press the button with the label "Hide Answer"
Then the Show/Hide button label is "Show Answer"
And I should not see "4.14159" anywhere on the page
Scenario: I can see my score on a problem when I answer it and after I reset it
......
......@@ -105,7 +105,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
resp = self.get_problem()
html = json.loads(resp.content)['html']
print html
sabut = '<button class="show"><span class="show-label">Show Answer(s)</span> <span class="sr">(for question(s) above - adjacent to each field)</span></button>'
sabut = '<button class="show"><span class="show-label" aria-hidden="true">Show Answer</span> <span class="sr">Reveal Answer</span></button>'
self.assertTrue(sabut in html)
def test_no_showanswer_for_student(self):
......@@ -115,5 +115,5 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
resp = self.get_problem()
html = json.loads(resp.content)['html']
sabut = '<button class="show"><span class="show-label">Show Answer(s)</span> <span class="sr">(for question(s) above - adjacent to each field)</span></button>'
sabut = '<button class="show"><span class="show-label" aria-hidden="true">Show Answer</span> <span class="sr">Reveal answer above</span></button>'
self.assertFalse(sabut in html)
......@@ -232,12 +232,6 @@ div.course-wrapper {
}
.xblock {
&:focus {
outline: 0;
}
}
textarea.short-form-response {
height: 200px;
padding: 5px;
......
......@@ -183,7 +183,7 @@ ${fragment.foot_html()}
<div class="course-wrapper">
% if accordion:
<div class="course-index">
<div class="course-index" role="navigation">
<header id="open_close_accordion">
<a href="#">${_("close")}</a>
</header>
......
......@@ -5,7 +5,7 @@
<i class="icon-remove"></i>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close Modal')}
${_('Close')}
</span>
</button>
......@@ -43,6 +43,5 @@ var accessible_confirm = function(message, callback) {
accessible_modal("#accessibile-confirm-modal #confirm_open_button", "#accessibile-confirm-modal .close-modal", "#accessibile-confirm-modal", ".content-wrapper");
$("#accessibile-confirm-modal #confirm_open_button").click();
$("#accessibile-confirm-modal .message-title").html(message);
// SR.readText(message);
};
</script>
......@@ -7,7 +7,7 @@
<div class="problem-progress"></div>
<div class="problem">
<div class="problem" role="application">
${ problem['html'] }
<div class="action">
......@@ -23,7 +23,7 @@
<input class="save" type="button" value="${_('Save')}" />
% endif
% if answer_available:
<button class="show"><span class="show-label">${_('Show Answer(s)')}</span> <span class="sr">${_("(for question(s) above - adjacent to each field)")}</span></button>
<button class="show"><span class="show-label" aria-hidden="true">${_('Show Answer')}</span> <span class="sr">${_("Reveal Answer")}</span></button>
% endif
% if attempts_allowed :
<div class="submission_feedback">
......
......@@ -24,7 +24,7 @@
id="tab_${idx}"
tabindex="0"
role="tab">
<p aria-hidden="false">${item['title']}<span class="sr" aria-hidden="true">, ${item['type']}</span></p>
<p aria-hidden="false">${item['title']}</p>
</a>
</li>
% endfor
......
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