[a11y] Adds context to module level buttons

Makes it so that screenreaders are given specific module context
i.e., the module name, whenever they navigation to a module
level button

fixes CNVS-22866

Test Plan:
  - Go to the modules page
  - Using a screenreader go to the publish cloud at the module
    level.
  - It should indicate the name of the module, for example:
    "Published. Click to unpublish My Cool Module."
  - Similar things should occur for the Add content button and
    the manage module dropdown.
  - The publish button and cog buttons for items within modules
    should similarly give context.

Change-Id: I1ba3f5e5c09e8186667af38eb19852295d5f01e2
Reviewed-on: https://gerrit.instructure.com/63049
Product-Review: Aaron Cannon <acannon@instructure.com>
Reviewed-by: Sterling Cobb <sterling@instructure.com>
Tested-by: Jenkins
QA-Review: Clare Strong <clare@instructure.com>
This commit is contained in:
Clay Diffrient 2015-09-10 17:04:00 -06:00
parent f53907c0dd
commit 60d3834e9d
10 changed files with 85 additions and 21 deletions

View File

@ -16,6 +16,7 @@ define [
published: true
publishable: true
unpublishable: true
module_item_name: null
branch: (key) ->
(@[key][@get('module_type')] or @[key].generic).call(this)
@ -45,10 +46,24 @@ define [
module: -> module: @attributes
disabledMessages:
generic: -> I18n.t('disabled', 'Publishing is disabled for this item')
assignment: -> I18n.t('disabled_assignment', "Can't unpublish if there are student submissions")
quiz: -> I18n.t('disabled_quiz', "Can't unpublish if there are student submissions")
discussion_topic: -> I18n.t('disabled_discussion_topic', "Can't unpublish if there are student submissions")
generic: -> if @get('module_item_name')
I18n.t('Publishing %{item_name} is disabled', {item_name: @get('module_item_name')})
else
I18n.t('Publishing is disabled for this item')
assignment: -> if @get('module_item_name')
I18n.t("Can't unpublish %{item_name} if there are student submissions", {item_name: @get('module_item_name')})
else
I18n.t("Can't unpublish if there are student submissions")
quiz: -> if @get('module_item_name')
I18n.t("Can't unpublish %{item_name} if there are student submissions", {item_name: @get('module_item_name')})
else
I18n.t("Can't unpublish if there are student submissions")
discussion_topic: -> if @get('module_item_name')
I18n.t("Can't unpublish %{item_name} if there are student submissions", {item_name: @get('module_item_name')})
else
I18n.t("Can't unpublish if there are student submissions")
publish: ->
@save 'published', yes

View File

@ -16,6 +16,7 @@ define [
togglePublishClassOn: React.PropTypes.object
model: customPropTypes.filesystemObject
userCanManageFilesForContext: React.PropTypes.bool.isRequired
fileName: React.PropTypes.string
# == React Functions == #
getInitialState: -> @extractStateFromModel( @props.model )

View File

@ -13,6 +13,10 @@ define [
publishedClass: 'btn-published'
unpublishClass: 'btn-unpublish'
# These values allow the default text to be overridden if necessary
@optionProperty 'publishText'
@optionProperty 'unpublishText'
tagName: 'button'
className: 'btn'
@ -55,7 +59,7 @@ define [
addAriaLabel: (label) ->
$label = @$el.find('span.screenreader-only.accessible_label')
$('<span class="screenreader-only accessible_label"></span>').appendTo(@$el) unless $label.length
$label = $('<span class="screenreader-only accessible_label"></span>').appendTo(@$el) unless $label.length
$label.text label
@$el.attr 'aria-label', label
@ -134,14 +138,14 @@ define [
renderPublish: ->
@renderState
text: I18n.t 'buttons.publish', 'Publish'
label: I18n.t 'buttons.publish_desc', 'Unpublished. Click to publish.'
label: @publishText || I18n.t 'Unpublished. Click to publish.'
buttonClass: @publishClass
iconClass: 'icon-unpublish'
renderPublished: ->
@renderState
text: I18n.t 'buttons.published', 'Published'
label: I18n.t 'buttons.published_desc', 'Published. Click to unpublish.'
label: @unpublishText || I18n.t 'Published. Click to unpublish.'
buttonClass: @publishedClass
iconClass: 'icon-publish'

View File

@ -11,7 +11,12 @@ define [
tagName: 'span'
className: 'publish-icon'
# These values allow the default text to be overridden if necessary
@optionProperty 'publishText'
@optionProperty 'unpublishText'
initialize: ->
super
@events = _.extend({}, PublishButtonView.prototype.events, @events)
events: {'keyclick' : 'click'}

View File

@ -30,6 +30,7 @@ define([
};
PublishCloud.render = function () {
var fileName = this.props.fileName || I18n.t('This file');
if (this.props.userCanManageFilesForContext) {
if (this.state.published && this.state.restricted) {
return (
@ -40,7 +41,7 @@ define([
ref='publishCloud'
className='btn-link published-status restricted'
title={this.getRestrictedText()}
aria-label={this.getRestrictedText() + ' - ' + I18n.t('Click to modify')}
aria-label={`${fileName} is ${this.getRestrictedText()} - ${I18n.t('Click to modify')}`}
>
<i className='icon-cloud-lock' />
</button>
@ -54,7 +55,7 @@ define([
ref='publishCloud'
className='btn-link published-status hiddenState'
title={I18n.t('Hidden. Available with a link')}
aria-label={I18n.t('Hidden. Available with a link - Click to modify')}
aria-label={`${fileName} is ${I18n.t('Hidden. Available with a link - Click to modify')}`}
>
<i className='icon-cloud-lock' />
</button>
@ -68,7 +69,7 @@ define([
ref='publishCloud'
className='btn-link published-status published'
title={I18n.t('Published')}
aria-label={I18n.t('Published - Click to modify')}
aria-label={`${fileName} is ${I18n.t('Published - Click to modify')}`}
>
<i className='icon-publish' />
</button>
@ -82,7 +83,7 @@ define([
ref='publishCloud'
className='btn-link published-status unpublished'
title={I18n.t('Unpublished')}
aria-label={I18n.t('Unpublished - Click to modify')}
aria-label={`${fileName} is ${I18n.t('Unpublished - Click to modify')}`}
>
<i className='icon-unpublish' />
</button>
@ -97,7 +98,7 @@ define([
ref='publishCloud'
className='published-status restricted'
title={this.getRestrictedText()}
aria-label={this.getRestrictedText()}
aria-label={`${fileName} is ${this.getRestrictedText()}`}
>
<i className='icon-calendar-day' />
</div>

View File

@ -115,6 +115,8 @@
data-course-id="<%= context_module && context_module.context_id %>"
data-published="<%= published_status == 'published' %>"
data-publishable="<%= true %>"
data-publish-message="<%= t('Unpublished. Click to publish %{module_name}.', {module_name: context_module ? context_module.name : 'module'}) %>"
data-unpublish-message="<%= t('Published. Click to unpublish %{module_name}.', {module_name: context_module ? context_module.name : 'module'}) %>"
title=""
data-tooltip
class="publish-icon module <%= published_status %>"
@ -123,10 +125,10 @@
</span>
<button
aria-label="<%= t('aria_labels.add_item', %{Add Content}) %>"
aria-label="<%= t('Add Content to %{module_name}', {module_name: context_module ? context_module.name : 'module'}) %>"
rel="<%= context_url(@context, :context_url) %>/modules/<%= context_module ? context_module.id : "{{ id }}" %>/items"
class="add_module_item_link btn"><i class="icon-plus"></i><span class="screenreader-only"><%= t('links.add_item', %{Add Content}) %></span></button>
<button class="btn al-trigger" aria-label="<%= t("manager_module", "Manage module") %>">
class="add_module_item_link btn"><i class="icon-plus"></i><span class="screenreader-only"><%= t('Add Content to %{module_name}', {module_name: context_module ? context_module.name : 'module'}) %></span></button>
<button class="btn al-trigger" aria-label="<%= t('Manage %{module_name}', {module_name: context_module ? context_module.name : 'module'}) %>">
<i class="icon-settings"></i><i class="icon-mini-arrow-down"></i>
</button>
<ul class="al-options">

View File

@ -115,6 +115,7 @@
<% if editable || module_item.nil? %>
<div class="ig-admin">
<span
data-module-item-name="<%= tag && tag.title %>"
data-module-type="<%= module_item && module_item.content_type_class %>"
data-content-id="<%= module_item && module_item.content_id %>"
data-id="<%= module_item_publishable_id(module_item) %>"
@ -124,6 +125,8 @@
data-published="<%= module_item && published_status == 'published' %>"
data-publishable="<%= module_item_publishable?(module_item) %>"
data-unpublishable="<%= module_item_unpublishable?(module_item) %>"
data-publish-message="<%= t('Unpublished. Click to publish %{item_name}.', {item_name: tag && tag.title ? tag.title : ''}) %>"
data-unpublish-message="<%= t('Published. Click to unpublish %{item_name}.', {item_name: tag && tag.title ? tag.title : ''}) %>"
title=""
data-tooltip
class="publish-icon <%= published_status %>"
@ -134,7 +137,7 @@
<div class="inline-block cog-menu-container">
<a class="al-trigger al-trigger-gray" role="button" tabindex="0" href="#">
<i class="icon-settings"></i><i class="icon-mini-arrow-down"></i>
<span class="screenreader-only"><%= t('settings', 'Settings') %></span>
<span class="screenreader-only"><%= t('Manage %{item_name}', {item_name: tag && tag.title ? tag.title : 'item'}) %></span>
</a>
<ul class="al-options">

View File

@ -1323,6 +1323,7 @@ define([
var publishData = {
moduleType: data.type,
id: data.publishable_id,
moduleItemName: data.moduleItemName,
moduleId: data.context_module_id,
courseId: data.context_id,
published: data.published,
@ -1376,7 +1377,8 @@ define([
model: file,
togglePublishClassOn: $el.parents('.ig-row')[0],
userCanManageFilesForContext: ENV.MODULE_FILE_PERMISSIONS.manage_files,
usageRightsRequiredForContext: ENV.MODULE_FILE_PERMISSIONS.usage_rights_required
usageRightsRequiredForContext: ENV.MODULE_FILE_PERMISSIONS.usage_rights_required,
fileName: file.displayName()
}
React.render(PublishCloud(props), $el[0]);
@ -1389,13 +1391,26 @@ define([
id: data.id,
module_id: data.moduleId,
module_item_id: data.moduleItemId,
module_item_name: data.moduleItemName,
course_id: data.courseId,
published: data.published,
publishable: data.publishable,
unpublishable: data.unpublishable
});
var view = new PublishIconView({model: model, el: $el[0]});
var viewOptions = {
model: model,
el: $el[0]
};
if (data.publishMessage) {
viewOptions.publishText = data.publishMessage;
}
if (data.unpublishMessage) {
viewOptions.unpublishText = data.unpublishMessage;
}
var view = new PublishIconView(viewOptions);
var row = $el.closest('.ig-row');
if (data.published) { row.addClass('ig-published'); }

View File

@ -50,6 +50,24 @@ define [
equal btnView.$text.html().match(/Published/).length, 1
equal btnView.$el.attr('aria-label').match(/can't unpublish/).length, 1
test 'should render the provided publish text when given', ->
testText = 'Test Publish Text'
btnView = new PublishButtonView({
model: @publish,
publishText: testText
}).render()
equal btnView.$('.screenreader-only.accessible_label').text(), testText
test 'should render the provided unpublish text when given', ->
testText = 'Test Unpublish Text'
btnView = new PublishButtonView({
model: @published,
unpublishText: testText
}).render()
equal btnView.$('.screenreader-only.accessible_label').text(), testText
# state
test 'disable should add disabled state', ->
btnView = new PublishButtonView(model: @publish).render()

View File

@ -41,7 +41,7 @@ describe 'editing a quiz' do
type_in_tiny('#quiz_description', 'changed description')
click_save_settings_button
wait_for_ajax_requests
expect(f('#quiz-publish-link').text.strip!).to eq 'Published'
expect(f('#quiz-publish-link .publish-text').text.strip!).to eq 'Published'
end
it 'deletes the quiz', priority: "1", test_id: 351921 do
@ -54,7 +54,7 @@ describe 'editing a quiz' do
end
f('#quiz-publish-link').click
wait_for_ajax_requests
expect(f('#quiz-publish-link').text.strip!).to eq 'Publish'
expect(f('#quiz-publish-link .publish-text').text.strip!).to eq 'Publish'
end
end
@ -90,7 +90,7 @@ describe 'editing a quiz' do
end
f('#quiz-publish-link').click
wait_for_ajax_requests
expect(f('#quiz-publish-link').text.strip!).to eq 'Unpublish'
expect(f('#quiz-publish-link .publish-text').text.strip!).to eq 'Unpublish'
end
end