Commit 3cef87b9 by Matjaz Gregoric Committed by GitHub

Merge pull request #16 from open-craft/mtyaka/a11y-fixes

A11y fixes
parents 84580b6f 7ef8e5d1
......@@ -123,6 +123,19 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
return [(key, {'label': markdown(value['label']), 'img': value['img'], 'img_alt': value.get('img_alt')})
for key, value in items]
def _get_block_id(self):
"""
Return unique ID of this block. Useful for HTML ID attributes.
Works both in LMS/Studio and workbench runtimes:
- In LMS/Studio, use the location.html_id method.
- In the workbench, use the usage_id.
"""
if hasattr(self, 'location'):
return self.location.html_id() # pylint: disable=no-member
else:
return unicode(self.scope_ids.usage_id)
def img_alt_mandatory(self):
"""
Determine whether alt attributes for images are configured to be mandatory. Defaults to True.
......@@ -404,13 +417,13 @@ class PollBlock(PollBase):
'feedback': markdown(self.feedback) or False,
'js_template': js_template,
'any_img': self.any_image(self.answers),
# The SDK doesn't set url_name.
'url_name': getattr(self, 'url_name', ''),
'display_name': self.display_name,
'can_vote': self.can_vote(),
'max_submissions': self.max_submissions,
'submissions_count': self.submissions_count,
'can_view_private_results': self.can_view_private_results(),
# a11y: Transfer block ID to enable creating unique ids for questions and answers in the template
'block_id': self._get_block_id(),
})
if self.choice:
......@@ -465,6 +478,8 @@ class PollBlock(PollBase):
'plural': total > 1,
'display_name': self.display_name,
'any_img': self.any_image(self.answers),
# a11y: Transfer block ID to enable creating unique ids for questions and answers in the template
'block_id': self._get_block_id(),
}
@XBlock.json_handler
......@@ -613,19 +628,6 @@ class SurveyBlock(PollBase):
choices = Dict(help=_("The user's answers"), scope=Scope.user_state)
event_namespace = 'xblock.survey'
def _get_block_id(self):
"""
Return ID of this Survey block.
Take into account the needs of both LMS/Studio and workbench runtimes:
- In LMS/Studio, usage_id is a UsageKey object.
- In the workbench, usage_id is a string.
"""
usage_id = self.scope_ids.usage_id
# Try accessing block ID. If usage_id does not have it, return usage_id itself:
return unicode(getattr(usage_id, 'block_id', usage_id))
def student_view(self, context=None):
"""
The primary view of the SurveyBlock, shown to students
......@@ -649,8 +651,6 @@ class SurveyBlock(PollBase):
'any_img': self.any_image(self.questions),
# Mustache is treating an empty string as true.
'feedback': markdown(self.feedback) or False,
# The SDK doesn't set url_name.
'url_name': getattr(self, 'url_name', ''),
'block_name': self.block_name,
'can_vote': self.can_vote(),
'submissions_count': self.submissions_count,
......@@ -820,9 +820,15 @@ class SurveyBlock(PollBase):
detail, total = self.tally_detail()
return {
'answers': [
value for value in OrderedDict(self.answers).values()],
'tally': detail, 'total': total, 'feedback': markdown(self.feedback),
'plural': total > 1, 'block_name': self.block_name,
{'key': key, 'label': label} for key, label in self.answers
],
'tally': detail,
'total': total,
'feedback': markdown(self.feedback),
'plural': total > 1,
'block_name': self.block_name,
# a11y: Transfer block ID to enable creating unique ids for questions and answers in the template
'block_id': self._get_block_id()
}
@XBlock.json_handler
......
<script id="poll-results-template" type="text/html">
<script class="poll-results-template" type="text/html">
<h3 class="poll-header">{{display_name}}</h3>
<div class="poll-question-container">{{{question}}}</div>
<ul class="poll-answers-results poll-results {{~#if any_img}} has-images{{/if}}">
{{#each tally}}
<li class="poll-result">
<div class="poll-result-input-container">
<input id="answer-{{key}}" type="radio" disabled {{#if choice}}checked{{/if}} />
</div>
{{~#if ../any_img~}}
<div class="poll-image result-image">
<label for="answer-{{key}}" class="poll-image-label">
{{#if img}}
<img src="{{img}}" alt="{{img_alt}}"/>
{{/if}}
</label>
</div><div class="percentage-gauge-background"></div>
{{~/if~}}
<div class="percentage-gauge-container">
<div class="percentage-gauge" style="width:{{percent}}%;"></div>
<label class="poll-answer-label" for="answer-{{key}}">{{{answer}}}</label>
</div>
<div class="poll-percent-container">
<span class="poll-percent-display{{#if first}} poll-top-choice{{/if}}">{{percent}}%</span>
<div class="poll-results-wrapper" role="radiogroup" tabindex="0">
<h4 class="poll-header">{{i18n "Results" }}</h4>
<ul class="poll-answers-results poll-results {{~#if any_img}} has-images{{/if}}">
{{#each tally}}
<li class="poll-result">
<div class="poll-result-input-container">
<input id="answer-{{key}}-{{../block_id}}" type="radio" disabled {{#if choice}}checked{{/if}} />
</div>
{{~#if ../any_img~}}
<div class="poll-image result-image">
<label for="answer-{{key}}-{{../block_id}}" class="poll-image-label">
{{#if img}}
<img src="{{img}}" alt="{{img_alt}}"/>
{{/if}}
</label>
</div><div class="percentage-gauge-background"></div>
{{~/if~}}
<div class="percentage-gauge-container">
<div class="percentage-gauge" style="width:{{percent}}%;"></div>
<label class="poll-answer-label" for="answer-{{key}}-{{../block_id}}">{{{answer}}}</label>
</div>
<div class="poll-percent-container">
<span class="poll-percent-display{{#if first}} poll-top-choice{{/if}}">{{percent}}%</span>
</div>
</li>
{{/each}}
</ul>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
</div>
{{#if feedback}}
<hr />
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
</div>
</li>
{{/each}}
</ul>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
{{/if}}
</div>
{{#if feedback}}
<hr />
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
</div>
{{/if}}
</script>
<script id="poll-form-component" type="text/html">
<script class="poll-form-component" type="text/html">
{{#each items}}
<li class="field comp-setting-entry is-set poll-{{noun}}-studio-item">
<div class="wrapper-comp-setting">
......
<script id="survey-results-template" type="text/html">
<script class="survey-results-template" type="text/html">
<h3 class="poll-header">{{block_name}}</h3>
<table class="survey-table poll-results">
<thead>
<tr>
<td></td>
{{#each answers}}
<th class="survey-answer">{{{this}}}</th>
{{/each}}
</tr>
</thead>
{{#each tally}}
<tr class="survey-row">
<th class="survey-question">
{{#if img}}
<div class="poll-image-td">
<img src="{{img}}" alt="img_alt"/>
</div>
{{/if}}
{{{label}}}
</th>
{{#each answers}}
<td class="survey-percentage survey-option{{#if choice}} survey-choice{{/if}}{{#if top}} poll-top-choice{{/if}}">{{percent}}%</td>
{{/each}}
</tr>
{{/each}}
</table>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
</div>
{{#if feedback}}
<hr />
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
<div class="poll-results-wrapper" tabindex="0">
<h4 class="poll-header">{{i18n "Results" }}</h4>
<table class="survey-table poll-results">
<thead>
<tr>
<td></td>
{{#each answers}}
<th id="answer-{{key}}-{{../block_id}}" class="survey-answer">{{{label}}}</th>
{{/each}}
</tr>
</thead>
{{#each tally}}
<tr class="survey-row">
<th class="survey-question">
{{#if img}}
<div class="poll-image-td">
<img src="{{img}}" alt="img_alt"/>
</div>
{{/if}}
{{{label}}}
</th>
{{#each answers}}
<td class="survey-percentage survey-option{{#if choice}} survey-choice{{/if}}{{#if top}} poll-top-choice{{/if}}" aria-labelledby="answer-{{key}}-{{../../block_id}}">{{percent}}%</td>
{{/each}}
</tr>
{{/each}}
</table>
<input class="input-main" type="button" name="poll-submit" value="Submit" disabled>
<div class="poll-footnote">
{{interpolate (i18n_ngettext "Results gathered from {total} respondent." "Results gathered from {total} respondents." total) total=total}}
</div>
{{/if}}
{{#if feedback}}
<hr />
<h3 class="poll-header">{{i18n "Feedback" }}</h3>
<div class="poll-feedback">
{{{feedback}}}
</div>
{{/if}}
</div>
</script>
......@@ -5,29 +5,31 @@
<h3 class="poll-header">{{ display_name }}</h3>
<form>
<div class="poll-question-container">
{{ question|safe }}
</div>
<ul class="poll-answers">
{% for key, value in answers %}
<li class="poll-answer">
<div class="poll-input-container">
<input type="radio" name="choice" id="{{ url_name }}-answer-{{ key }}" value="{{ key }}"
{% if choice == key %}checked{% endif %}/>
</div>
{% if any_img %}
<div class="poll-image">
<label for="{{ url_name }}-answer-{{ key }}" class="poll-image-label">
{% if value.img %}
<img src="{{ value.img }}"/>
{% endif %}
</label>
<div role="group" aria-labelledby="{{ block_id }}-question">
<div id="{{ block_id }}-question" class="poll-question-container">
{{ question|safe }}
</div>
<ul class="poll-answers">
{% for key, value in answers %}
<li class="poll-answer">
<div class="poll-input-container">
<input type="radio" name="choice" id="{{ block_id }}-answer-{{ key }}" value="{{ key }}"
{% if choice == key %}checked{% endif %}/>
</div>
{% endif %}
<label class="poll-answer-text" for="{{ url_name }}-answer-{{ key }}">{{ value.label|safe }}</label>
</li>
{% endfor %}
</ul>
{% if any_img %}
<div class="poll-image">
<label for="{{ block_id }}-answer-{{ key }}" class="poll-image-label">
{% if value.img %}
<img src="{{ value.img }}"/>
{% endif %}
</label>
</div>
{% endif %}
<label class="poll-answer-text" for="{{ block_id }}-answer-{{ key }}">{{ value.label|safe }}</label>
</li>
{% endfor %}
</ul>
</div>
<input class="input-main" type="button" name="poll-submit"
value="{% if choice %}Resubmit{% else %}Submit{% endif %}" disabled/>
</form>
......
{{ js_template|safe }}
<div class="poll-block themed-xblock" data-private="{% if private_results %}1{% endif %}" data-can-vote="{% if can_vote %}1{% endif %}">
<div class="poll-block themed-xblock" data-private="{% if private_results %}1{% endif %}"
data-can-vote="{% if can_vote %}1{% endif %}">
<div class="poll-block-form-wrapper">
<h3 class="poll-header">{{block_name}}</h3>
<form>
......@@ -13,7 +14,7 @@
</tr>
</thead>
{% for key, question in questions %}
<tr class="survey-row">
<tr class="survey-row" role="group" aria-labelledby="{{block_id}}-{{key}}">
<th id="{{block_id}}-{{key}}" class="survey-question">
{% if question.img %}
<div class="poll-image-td">
......@@ -28,9 +29,8 @@
<input type="radio"
name="{{key}}"
value="{{answer}}"{% if question.choice == answer %} checked{% endif %}
aria-labelledby="{{block_id}}-{{key}} {{block_id}}-{{answer}}"
aria-labelledby="{{block_id}}-{{answer}}"
/>
<span class="sr">{{label}}</span>
</label>
</td>
{% endfor %}
......
......@@ -37,7 +37,7 @@ function PollUtil (runtime, element, pollType) {
});
});
this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());
this.resultsTemplate = Handlebars.compile($("." + pollType + "-results-template", element).html());
this.viewResultsButton = $('.view-results-button', element);
this.viewResultsButton.click(this.getResults);
......@@ -204,12 +204,13 @@ function PollUtil (runtime, element, pollType) {
data: JSON.stringify({}),
success: function (data) {
$('div.poll-block', element).html(self.resultsTemplate(data));
$('.poll-results-wrapper').focus();
whenImagesLoaded(adjustGaugeBackground);
}
});
};
this.enableSubmit = function () {
this.enableSubmit = function () {
// Enable the submit button.
self.submit.removeAttr("disabled");
self.answers.unbind("change.enableSubmit");
......
......@@ -12,7 +12,7 @@ function PollEditUtil(runtime, element, pollType) {
this.init = function () {
// Set up the editing form for a Poll or Survey.
var temp = $('#poll-form-component', element).html();
var temp = $('.poll-form-component', element).html();
// Set up gettext in case it isn't available in the client runtime:
if (typeof gettext == "undefined") {
......
......@@ -44,7 +44,7 @@ def package_data(pkg, roots):
setup(
name='xblock-poll',
version='1.2',
version='1.2.1',
description='An XBlock for polling users.',
packages=[
'poll',
......
......@@ -25,8 +25,6 @@ Tests a realistic, configured Poll to make sure that everything works as it
should.
"""
import itertools
from .base_test import PollBaseTest
......@@ -165,15 +163,15 @@ class TestSurveyFunctions(PollBaseTest):
answers = self.browser.find_elements_by_css_selector('.survey-answer')
question_ids = [question.get_attribute('id') for question in questions]
answer_ids = [answer.get_attribute('id') for answer in answers]
id_pairs = [
"{question_id} {answer_id}".format(question_id=question_id, answer_id=answer_id)
for question_id, answer_id in itertools.product(question_ids, answer_ids)
]
options = self.browser.find_elements_by_css_selector('.survey-option input')
self.assertEqual(len(options), len(id_pairs))
for option in options:
labelledby = option.get_attribute('aria-labelledby')
self.assertIn(labelledby, id_pairs)
rows = self.browser.find_elements_by_css_selector('.survey-row')
self.assertEqual(len(rows), len(questions))
for i, row in enumerate(rows):
self.assertEqual(row.get_attribute('role'), 'group')
self.assertEqual(row.get_attribute('aria-labelledby'), question_ids[i])
options = row.find_elements_by_css_selector('.survey-option input')
self.assertEqual(len(options), len(answers))
for j, option in enumerate(options):
self.assertEqual(option.get_attribute('aria-labelledby'), answer_ids[j])
def fill_survey(self, assert_submit=False):
"""
......
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