Calendar control now behaves like tabs

fixes CNVS-25666

Test plan:
* Visually the appearance of the calendar button control should be the
  same as it always has been
* For KO and SR users when the calendar button control is tabbed to, the
  active button should receive focus
* The following constraints should be true no matter which button in the
  calendar button control has focus
  * When tab is pressed and a button in the calendar button control
    already has focus, focus should transition to the next focusable
    element in the DOM outside of the calendar button control
  * Similarly, when shift+tab is pressed focus should transition to the
    previous focusable element in the DOM outside of the calendar button
    control
* When using the calendar button control with a SR it should announce
  itself as a tab list and state which tab is currently selected
* As new tabs are activated using the arrow keys the SR should announce
  which is currently selected
* Pressing the left and up arrow keys should change the active button to
  the previous button in the calendar button control and the calendar
  view should be updated to the newly activate button's view.
* This behavior should wrap around when the beginning of the list is
  reached
* Pressing the right and down arrow keys should change the active button
  to the next button in the calendar button control and the calendar
  view should be updated to the newly activate button's view.
* This behavior should wrap around when the end of the list is
  reached

Change-Id: I308b6cd121d71a7c24bbb7c235aa99b1d5250d44
Reviewed-on: https://gerrit.instructure.com/70237
Tested-by: Jenkins
Reviewed-by: Matthew Wheeler <mwheeler@instructure.com>
Product-Review: Aaron Cannon <acannon@instructure.com>
QA-Review: Adrian Russell <arussell@instructure.com>
This commit is contained in:
Andrew Butterfield 2016-01-13 17:15:30 -07:00
parent 7e527b7e1f
commit 53b1e3a1c8
4 changed files with 153 additions and 9 deletions

View File

@ -27,6 +27,7 @@ define [
'click .scheduler_done_button': '_triggerDone'
'click #create_new_event_link': '_triggerCreateNewEvent'
'click #refresh_calendar_link': '_triggerRefreshCalendar'
'keydown .calendar_view_buttons': '_handleKeyDownEvent'
initialize: ->
super
@ -49,12 +50,27 @@ define [
toggleView: (e) ->
e.preventDefault()
$target = $(this)
$target.attr('aria-checked', true)
$target = $(e.currentTarget)
$target.attr('aria-selected', true)
.addClass('active')
.attr('tabindex', 0)
$target.siblings()
.attr('aria-checked', false)
.attr('aria-selected', false)
.removeClass('active')
.attr('tabindex', -1)
moveToCalendarViewButton: (direction) ->
buttons = @$calendarViewButtons.children('button')
active = @$calendarViewButtons.find('.active')
activeIndex = buttons.index(active)
lastIndex = buttons.length - 1
if direction == 'prev'
activeIndex = (activeIndex + lastIndex) % buttons.length
else if direction == 'next'
activeIndex = (activeIndex + 1) % buttons.length
buttons.eq(activeIndex).focus().click()
showNavigator: ->
@$navigator.show()
@ -124,3 +140,12 @@ define [
_triggerRefreshCalendar: (event) ->
event.preventDefault()
@trigger('refreshCalendar')
_handleKeyDownEvent: (event) ->
switch event.which
when 37, 38 # left, up
event.preventDefault()
@moveToCalendarViewButton('prev')
when 39, 40 # right, down
event.preventDefault()
@moveToCalendarViewButton('next')

View File

@ -58,6 +58,6 @@
</div>
<div id="calendar_header"></div>
<div id="calendar-app"></div>
<div id="calendar-app" role="tabpanel"></div>
<div id="calendar-drag-and-drop-container"></div>

View File

@ -14,20 +14,20 @@
data-tooltip>
<span class="screenreader-only">{{#t "loading"}}Loading{{/t}}</span>
</span>
<span class="calendar_view_buttons btn-group" role="radiogroup">
<button type="button" id="week" class="btn calendar-button" role="radio" aria-checked="false">
<span class="calendar_view_buttons btn-group" role="tablist">
<button type="button" id="week" class="btn calendar-button" role="tab" aria-selected="false" aria-controls="calendar-app" tabindex="-1">
{{#t "links.calendar_week"}}Week{{/t}}
<span class="screenreader-only accessibility-warning">{{#t "links.accessibility_warning"}}Warning: For improved accessibility of calendar events, please use the agenda view.{{/t}}</span>
</button>
<button type="button" id="month" class="btn calendar-button" role="radio" aria-checked="false">
<button type="button" id="month" class="btn calendar-button" role="tab" aria-selected="false" aria-controls="calendar-app" tabindex="-1">
{{#t "links.calendar_month"}}Month{{/t}}
<span class="screenreader-only accessibility-warning">{{#t "links.accessibility_warning"}}Warning: For improved accessibility of calendar events, please use the agenda view.{{/t}}</span>
</button>
<button type="button" id="agenda" class="btn" role="radio" aria-checked="false">
<button type="button" id="agenda" class="btn" role="tab" aria-selected="false" aria-controls="calendar-app" tabindex="-1">
{{#t "links.calendar_agenda"}}Agenda{{/t}}
</button>
{{#if showScheduler}}
<button type="button" id="scheduler" class="btn" role="radio" aria-checked="false">
<button type="button" id="scheduler" class="btn" role="tab" aria-selected="false" aria-controls="calendar-app" tabindex="-1">
{{#t "links.calendar_scheduler"}}Scheduler{{/t}}
</button>
{{/if}}

View File

@ -0,0 +1,119 @@
define [
'jquery'
'compiled/views/calendar/CalendarHeader'
], ($, CalendarHeader) ->
module 'CalendarHeader',
setup: ->
@header = new CalendarHeader()
@header.$el.appendTo $('#fixtures')
teardown: ->
@header.$el.remove()
$("#fixtures").empty()
test '#moveToCalendarViewButton clicks the next calendar view button', (assert) ->
done = assert.async()
buttons = $('.calendar_view_buttons button')
buttons.first().click()
buttons.eq(1).on('click', ->
ok true, "next button was clicked"
done()
)
@header.moveToCalendarViewButton('next')
test '#moveToCalendarViewButton wraps around to the first calendar view button', (assert) ->
done = assert.async()
buttons = $('.calendar_view_buttons button')
buttons.last().click()
buttons.first().on('click', ->
ok true, "first button was clicked"
done()
)
@header.moveToCalendarViewButton('next')
test '#moveToCalendarViewButton clicks the previous calendar view button', (assert) ->
done = assert.async()
buttons = $('.calendar_view_buttons button')
buttons.last().click()
buttons.eq(buttons.length - 2).on('click', ->
ok true, "previous button was clicked"
done()
)
@header.moveToCalendarViewButton('prev')
test '#moveToCalendarViewButton wraps around to the last calendar view button', (assert) ->
done = assert.async()
buttons = $('.calendar_view_buttons button')
buttons.first().click()
buttons.last().on('click', ->
ok true, "last button was clicked"
done()
)
@header.moveToCalendarViewButton('prev')
test "calls #moveToCalendarViewButton with 'prev' when left key is pressed", (assert) ->
done = assert.async()
moveToCalendarViewButton = @header.moveToCalendarViewButton
@header.moveToCalendarViewButton = (direction) =>
equal direction, 'prev'
@header.moveToCalendarViewButton = moveToCalendarViewButton
done()
e = $.Event('keydown', { which: 37 })
$('.calendar_view_buttons').trigger(e)
test "calls #moveToCalendarViewButton with 'prev' when up key is pressed", (assert) ->
done = assert.async()
moveToCalendarViewButton = @header.moveToCalendarViewButton
@header.moveToCalendarViewButton = (direction) =>
equal direction, 'prev'
@header.moveToCalendarViewButton = moveToCalendarViewButton
done()
e = $.Event('keydown', { which: 38 })
$('.calendar_view_buttons').trigger(e)
test "calls #moveToCalendarViewButton with 'next' when right key is pressed", (assert) ->
done = assert.async()
moveToCalendarViewButton = @header.moveToCalendarViewButton
@header.moveToCalendarViewButton = (direction) =>
equal direction, 'next'
@header.moveToCalendarViewButton = moveToCalendarViewButton
done()
e = $.Event('keydown', { which: 39 })
$('.calendar_view_buttons').trigger(e)
test "calls #moveToCalendarViewButton with 'next' when down key is pressed", (assert) ->
done = assert.async()
moveToCalendarViewButton = @header.moveToCalendarViewButton
@header.moveToCalendarViewButton = (direction) =>
equal direction, 'next'
@header.moveToCalendarViewButton = moveToCalendarViewButton
done()
e = $.Event('keydown', { which: 40 })
$('.calendar_view_buttons').trigger(e)
test 'when a calendar view button is clicked it is properly activated', (assert) ->
done = assert.async()
$('.calendar_view_buttons button').last().on 'click', (e) =>
@header.toggleView(e)
button = $('.calendar_view_buttons button').last()
equal button.attr('aria-selected'), 'true'
equal button.attr('tabindex'), '0'
ok button.hasClass('active')
button.siblings().each ->
equal $(this).attr('aria-selected'), 'false'
equal $(this).attr('tabindex'), '-1'
notOk $(this).hasClass('active')
done()
$('.calendar_view_buttons button').last().click()