sequence navs keyboard support and test update
This commit is contained in:
@@ -272,6 +272,7 @@ nav.sequence-bottom {
|
||||
// hover and active states
|
||||
.sequence-nav-button,
|
||||
.sequence-nav button {
|
||||
&.focused,
|
||||
&:hover,
|
||||
&:active,
|
||||
&.active {
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
<div id="sequence_1" class="sequence">
|
||||
<nav class="sequence-nav">
|
||||
<ol id="sequence-list">
|
||||
</ol>
|
||||
<div class="xblock-student_view-sequential">
|
||||
<div id="sequence_workflow" class="sequence">
|
||||
<div class="sequence-nav">
|
||||
<button class="sequence-nav-button button-previous">
|
||||
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
|
||||
<span>Previous</span>
|
||||
</button>
|
||||
<nav class="sequence-list-wrapper" aria-label="Unit">
|
||||
<ol id="sequence-list" role="tablist">
|
||||
<li>
|
||||
<button role="tab" tabindex="0" aria-selected="true" aria-expanded="true" aria-controls="seq_content" class="seq_problem nav-item tab active" data-index="0" data-id="block-v1:edX+DemoX+Demo_Course+type@vertical+block@fb79dcbad35b466a8c6364f8ffee9050" data-element="1" data-page-title="Unit 101" data-path="Example Week 2: Get Interactive > Homework - Part 1 > Unit 101" id="tab_0">
|
||||
<span class="icon fa seq_problem" aria-hidden="true"></span>
|
||||
<span class="fa fa-fw fa-bookmark bookmark-icon is-hidden" aria-hidden="true"></span>
|
||||
<div class="sequence-tooltip sr"><span class="sr">problem</span>Unit 101<span class="sr bookmark-icon-sr"></span></div>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button role="tab" tabindex="-1" aria-selected="true" aria-expanded="true" aria-controls="seq_content" class="seq_problem inactive nav-item tab" data-index="1" data-id="block-v1:edX+DemoX+Demo_Course+type@vertical+block@fb79dcbad35b466a8c6364f8ffee9051" data-element="2" data-page-title="Unit 102" data-path="Example Week 2: Get Interactive > Homework - Part 1 > Unit 102" id="tab_1">
|
||||
<span class="icon fa seq_problem" aria-hidden="true"></span>
|
||||
<span class="fa fa-fw fa-bookmark bookmark-icon is-hidden" aria-hidden="true"></span>
|
||||
<div class="sequence-tooltip sr"><span class="sr">problem</span>Unit 102<span class="sr bookmark-icon-sr"></span></div>
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<button class="sequence-nav-button button-next">
|
||||
<span>Next</span>
|
||||
<span class="icon fa fa-chevron-next" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul class="sequence-nav-buttons">
|
||||
<li class="prev"><button>Previous</button></li>
|
||||
<li class="next"><button>Next</button></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="sr-is-focusable" tabindex="-1"></div>
|
||||
|
||||
<div id="seq_content"></div>
|
||||
<div id="seq_content" role="tabpanel"></div>
|
||||
|
||||
<nav class="sequence-bottom">
|
||||
<ul class="sequence-nav-buttons">
|
||||
<li class="prev"><button>Previous</button></li>
|
||||
<li class="next"><button>Next</button></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="sequence-bottom" aria-label="Section">
|
||||
<button class="sequence-nav-button button-previous">
|
||||
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
|
||||
<span>Previous</span>
|
||||
</button>
|
||||
<button class="sequence-nav-button button-next">
|
||||
<span>Next</span>
|
||||
<span class="icon fa fa-chevron-next" aria-hidden="true"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
!time_spec.js
|
||||
!collapsible_spec.js
|
||||
!xmodule_spec.js
|
||||
!sequence/display_spec.js
|
||||
|
||||
68
common/lib/xmodule/xmodule/js/spec/sequence/display_spec.js
Normal file
68
common/lib/xmodule/xmodule/js/spec/sequence/display_spec.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/* globals Sequence */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
describe('Sequence', function() {
|
||||
var local = {},
|
||||
keydownHandler,
|
||||
keys = {
|
||||
ENTER: 13,
|
||||
LEFT: 37,
|
||||
RIGHT: 39
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('sequence.html');
|
||||
local.XBlock = window.XBlock = jasmine.createSpyObj('XBlock', ['initializeBlocks']);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
delete local.XBlock;
|
||||
});
|
||||
|
||||
keydownHandler = function(key) {
|
||||
var event = document.createEvent('Event');
|
||||
event.keyCode = key;
|
||||
event.initEvent('keydown', false, false);
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
describe('Navbar', function() {
|
||||
it('works with keyboard navigation LEFT and ENTER', function() {
|
||||
this.sequence = new Sequence($('.xblock-student_view-sequential'));
|
||||
this.sequence.$('.nav-item[data-index=0]').focus();
|
||||
keydownHandler(keys.LEFT);
|
||||
keydownHandler(keys.ENTER);
|
||||
|
||||
expect(this.sequence.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(this.sequence.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
|
||||
it('works with keyboard navigation RIGHT and ENTER', function() {
|
||||
this.sequence = new Sequence($('.xblock-student_view-sequential'));
|
||||
this.sequence.$('.nav-item[data-index=0]').focus();
|
||||
keydownHandler(keys.RIGHT);
|
||||
keydownHandler(keys.ENTER);
|
||||
|
||||
expect(this.sequence.$('.nav-item[data-index=0]')).toHaveAttr({
|
||||
'aria-expanded': 'false',
|
||||
'aria-selected': 'false',
|
||||
tabindex: '-1'
|
||||
});
|
||||
expect(this.sequence.$('.nav-item[data-index=1]')).toHaveAttr({
|
||||
'aria-expanded': 'true',
|
||||
'aria-selected': 'true',
|
||||
tabindex: '0'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}).call(this);
|
||||
@@ -38,6 +38,12 @@
|
||||
this.displayTabTooltip = function(event) {
|
||||
return Sequence.prototype.displayTabTooltip.apply(self, [event]);
|
||||
};
|
||||
this.arrowKeys = {
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40
|
||||
};
|
||||
|
||||
this.updatedProblems = {};
|
||||
this.requestToken = $(element).data('request-token');
|
||||
@@ -52,6 +58,7 @@
|
||||
this.nextUrl = this.el.data('next-url');
|
||||
this.prevUrl = this.el.data('prev-url');
|
||||
this.base_page_title = ' | ' + document.title;
|
||||
this.keydownHandler($(element).find('#sequence-list .tab'));
|
||||
this.bind();
|
||||
this.render(parseInt(this.el.data('position'), 10));
|
||||
}
|
||||
@@ -62,12 +69,63 @@
|
||||
|
||||
Sequence.prototype.bind = function() {
|
||||
this.$('#sequence-list .nav-item').click(this.goto);
|
||||
this.$('#sequence-list .nav-item').keypress(this.keyDownHandler);
|
||||
this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
|
||||
this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
|
||||
this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
|
||||
this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
|
||||
};
|
||||
|
||||
Sequence.prototype.previousNav = function(focused, index) {
|
||||
var $navItemList,
|
||||
$sequenceList = $(focused).parent().parent();
|
||||
if (index === 0) {
|
||||
$navItemList = $sequenceList.find('li').last();
|
||||
} else {
|
||||
$navItemList = $sequenceList.find('li:eq(' + index + ')').prev();
|
||||
}
|
||||
$sequenceList.find('.tab').removeClass('visited').removeClass('focused');
|
||||
$navItemList.find('.tab').addClass('focused').focus();
|
||||
};
|
||||
|
||||
Sequence.prototype.nextNav = function(focused, index, total) {
|
||||
var $navItemList,
|
||||
$sequenceList = $(focused).parent().parent();
|
||||
if (index === total) {
|
||||
$navItemList = $sequenceList.find('li').first();
|
||||
} else {
|
||||
$navItemList = $sequenceList.find('li:eq(' + index + ')').next();
|
||||
}
|
||||
$sequenceList.find('.tab').removeClass('visited').removeClass('focused');
|
||||
$navItemList.find('.tab').addClass('focused').focus();
|
||||
};
|
||||
|
||||
Sequence.prototype.keydownHandler = function(element) {
|
||||
var self = this;
|
||||
element.keydown(function(event) {
|
||||
var key = event.keyCode,
|
||||
$focused = $(event.currentTarget),
|
||||
$sequenceList = $focused.parent().parent(),
|
||||
index = $sequenceList.find('li')
|
||||
.index($focused.parent()),
|
||||
total = $sequenceList.find('li')
|
||||
.size() - 1;
|
||||
switch (key) {
|
||||
case self.arrowKeys.LEFT:
|
||||
event.preventDefault();
|
||||
self.previousNav($focused, index);
|
||||
break;
|
||||
|
||||
case self.arrowKeys.RIGHT:
|
||||
event.preventDefault();
|
||||
self.nextNav($focused, index, total);
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Sequence.prototype.displayTabTooltip = function(event) {
|
||||
$(event.currentTarget).find('.sequence-tooltip').removeClass('sr');
|
||||
};
|
||||
@@ -317,13 +375,22 @@
|
||||
Sequence.prototype.mark_visited = function(position) {
|
||||
// Don't overwrite class attribute to avoid changing Progress class
|
||||
var element = this.link_for(position);
|
||||
element.removeClass('inactive').removeClass('active').addClass('visited');
|
||||
element.attr({tabindex: '-1', 'aria-selected': 'false', 'aria-expanded': 'false'})
|
||||
.removeClass('inactive')
|
||||
.removeClass('active')
|
||||
.removeClass('focused')
|
||||
.addClass('visited');
|
||||
};
|
||||
|
||||
Sequence.prototype.mark_active = function(position) {
|
||||
// Don't overwrite class attribute to avoid changing Progress class
|
||||
var element = this.link_for(position);
|
||||
element.removeClass('inactive').removeClass('visited').addClass('active');
|
||||
element.attr({tabindex: '0', 'aria-selected': 'true', 'aria-expanded': 'true'})
|
||||
.removeClass('inactive')
|
||||
.removeClass('visited')
|
||||
.removeClass('focused')
|
||||
.addClass('active');
|
||||
this.$('.sequence-list-wrapper').focus();
|
||||
};
|
||||
|
||||
Sequence.prototype.addBookmarkIconToActiveNavItem = function(event) {
|
||||
|
||||
@@ -125,7 +125,7 @@ class SplitTestBase(SharedModuleStoreTestCase):
|
||||
content = resp.content
|
||||
|
||||
# Assert we see the proper icon in the top display
|
||||
self.assertIn('<button class="{} inactive nav-item"'.format(self.ICON_CLASSES[user_tag]), content)
|
||||
self.assertIn('<button class="{} inactive nav-item tab"'.format(self.ICON_CLASSES[user_tag]), content)
|
||||
# And proper tooltips
|
||||
for tooltip in self.TOOLTIPS[user_tag]:
|
||||
self.assertIn(tooltip, content)
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
'xblock/lms.runtime.v1': 'lms/js/xblock/lms.runtime.v1',
|
||||
'xblock': 'common/js/xblock',
|
||||
'capa/display': 'xmodule_js/src/capa/display',
|
||||
'sequence/display': 'xmodule_js/src/sequence/display',
|
||||
'string_utils': 'xmodule_js/common_static/js/src/string_utils',
|
||||
'logger': 'xmodule_js/common_static/js/src/logger',
|
||||
'Markdown.Converter': 'js/Markdown.Converter',
|
||||
|
||||
@@ -17,16 +17,22 @@
|
||||
<span class="icon fa fa-chevron-prev" aria-hidden="true"></span>
|
||||
<span>${_('Previous')}</span>
|
||||
</button>
|
||||
<nav class="sequence-list-wrapper" aria-label="${_('Unit')}">
|
||||
<ol id="sequence-list">
|
||||
<nav class="sequence-list-wrapper" aria-label="${_('Sequence')}">
|
||||
<ol id="sequence-list" role="tablist">
|
||||
% for idx, item in enumerate(items):
|
||||
<li>
|
||||
<button class="seq_${item['type']} inactive nav-item"
|
||||
data-id="${item['id']}"
|
||||
data-element="${idx+1}"
|
||||
data-page-title="${item['page_title']}"
|
||||
data-path="${item['path']}"
|
||||
id="tab_${idx}">
|
||||
<button class="seq_${item['type']} inactive nav-item tab"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
aria-selected="false"
|
||||
aria-expanded="false"
|
||||
aria-controls="seq_content"
|
||||
data-index="${idx}"
|
||||
data-id="${item['id']}"
|
||||
data-element="${idx+1}"
|
||||
data-page-title="${item['page_title']}"
|
||||
data-path="${item['path']}"
|
||||
id="tab_${idx}">
|
||||
<span class="icon fa seq_${item['type']}" aria-hidden="true"></span>
|
||||
<span class="fa fa-fw fa-bookmark bookmark-icon ${"is-hidden" if not item['bookmarked'] else "bookmarked"}" aria-hidden="true"></span>
|
||||
<div class="sequence-tooltip sr"><span class="sr">${item['type']} </span>${item['page_title']}<span class="sr bookmark-icon-sr"> ${_("Bookmarked") if item['bookmarked'] else ""}</span></div>
|
||||
@@ -51,7 +57,7 @@
|
||||
${item['content']}
|
||||
</div>
|
||||
% endfor
|
||||
<div id="seq_content"></div>
|
||||
<div id="seq_content" role="tabpanel"></div>
|
||||
|
||||
<nav class="sequence-bottom" aria-label="${_('Section')}">
|
||||
<button class="sequence-nav-button button-previous">
|
||||
|
||||
Reference in New Issue
Block a user