Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
d497e194
Commit
d497e194
authored
Jan 13, 2017
by
alisan617
Committed by
GitHub
Jan 13, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14218 from edx/alisan/kb-support-seq-nav-TNL-5917
Sequence navs arrow keyboard support
parents
4503ab42
88e10e0e
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
199 additions
and
28 deletions
+199
-28
common/lib/xmodule/xmodule/css/sequence/display.scss
+1
-0
common/lib/xmodule/xmodule/js/fixtures/sequence.html
+43
-16
common/lib/xmodule/xmodule/js/spec/.gitignore
+1
-0
common/lib/xmodule/xmodule/js/spec/sequence/display_spec.js
+68
-0
common/lib/xmodule/xmodule/js/src/sequence/display.js
+69
-2
lms/djangoapps/courseware/tests/test_split_module.py
+1
-1
lms/static/lms/js/spec/main.js
+1
-0
lms/templates/seq_module.html
+15
-9
No files found.
common/lib/xmodule/xmodule/css/sequence/display.scss
View file @
d497e194
...
@@ -272,6 +272,7 @@ nav.sequence-bottom {
...
@@ -272,6 +272,7 @@ nav.sequence-bottom {
// hover and active states
// hover and active states
.sequence-nav-button
,
.sequence-nav-button
,
.sequence-nav
button
{
.sequence-nav
button
{
&
.focused
,
&
:hover
,
&
:hover
,
&
:active
,
&
:active
,
&
.active
{
&
.active
{
...
...
common/lib/xmodule/xmodule/js/fixtures/sequence.html
View file @
d497e194
<div
id=
"sequence_1"
class=
"sequence"
>
<div
class=
"xblock-student_view-sequential"
>
<nav
class=
"sequence-nav"
>
<div
id=
"sequence_workflow"
class=
"sequence"
>
<ol
id=
"sequence-list"
>
<div
class=
"sequence-nav"
>
</ol>
<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"
>
<div
class=
"sr-is-focusable"
tabindex=
"-1"
></div>
<li
class=
"prev"
><button>
Previous
</button></li>
<li
class=
"next"
><button>
Next
</button></li>
</ul>
</nav>
<div
id=
"seq_content
"
></div>
<div
id=
"seq_content"
role=
"tabpanel
"
></div>
<nav
class=
"sequence-bottom"
>
<nav
class=
"sequence-bottom"
aria-label=
"Section"
>
<ul
class=
"sequence-nav-buttons"
>
<button
class=
"sequence-nav-button button-previous"
>
<li
class=
"prev"
><button>
Previous
</button></li>
<span
class=
"icon fa fa-chevron-prev"
aria-hidden=
"true"
></span>
<li
class=
"next"
><button>
Next
</button></li>
<span>
Previous
</span>
</ul>
</button>
</nav>
<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>
</div>
common/lib/xmodule/xmodule/js/spec/.gitignore
View file @
d497e194
...
@@ -7,3 +7,4 @@
...
@@ -7,3 +7,4 @@
!time_spec.js
!time_spec.js
!collapsible_spec.js
!collapsible_spec.js
!xmodule_spec.js
!xmodule_spec.js
!sequence/display_spec.js
common/lib/xmodule/xmodule/js/spec/sequence/display_spec.js
0 → 100644
View file @
d497e194
/* 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
);
common/lib/xmodule/xmodule/js/src/sequence/display.js
View file @
d497e194
...
@@ -38,6 +38,12 @@
...
@@ -38,6 +38,12 @@
this
.
displayTabTooltip
=
function
(
event
)
{
this
.
displayTabTooltip
=
function
(
event
)
{
return
Sequence
.
prototype
.
displayTabTooltip
.
apply
(
self
,
[
event
]);
return
Sequence
.
prototype
.
displayTabTooltip
.
apply
(
self
,
[
event
]);
};
};
this
.
arrowKeys
=
{
LEFT
:
37
,
UP
:
38
,
RIGHT
:
39
,
DOWN
:
40
};
this
.
updatedProblems
=
{};
this
.
updatedProblems
=
{};
this
.
requestToken
=
$
(
element
).
data
(
'request-token'
);
this
.
requestToken
=
$
(
element
).
data
(
'request-token'
);
...
@@ -52,6 +58,7 @@
...
@@ -52,6 +58,7 @@
this
.
nextUrl
=
this
.
el
.
data
(
'next-url'
);
this
.
nextUrl
=
this
.
el
.
data
(
'next-url'
);
this
.
prevUrl
=
this
.
el
.
data
(
'prev-url'
);
this
.
prevUrl
=
this
.
el
.
data
(
'prev-url'
);
this
.
base_page_title
=
' | '
+
document
.
title
;
this
.
base_page_title
=
' | '
+
document
.
title
;
this
.
keydownHandler
(
$
(
element
).
find
(
'#sequence-list .tab'
));
this
.
bind
();
this
.
bind
();
this
.
render
(
parseInt
(
this
.
el
.
data
(
'position'
),
10
));
this
.
render
(
parseInt
(
this
.
el
.
data
(
'position'
),
10
));
}
}
...
@@ -62,12 +69,63 @@
...
@@ -62,12 +69,63 @@
Sequence
.
prototype
.
bind
=
function
()
{
Sequence
.
prototype
.
bind
=
function
()
{
this
.
$
(
'#sequence-list .nav-item'
).
click
(
this
.
goto
);
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:add'
,
this
.
addBookmarkIconToActiveNavItem
);
this
.
el
.
on
(
'bookmark:remove'
,
this
.
removeBookmarkIconFromActiveNavItem
);
this
.
el
.
on
(
'bookmark:remove'
,
this
.
removeBookmarkIconFromActiveNavItem
);
this
.
$
(
'#sequence-list .nav-item'
).
on
(
'focus mouseenter'
,
this
.
displayTabTooltip
);
this
.
$
(
'#sequence-list .nav-item'
).
on
(
'focus mouseenter'
,
this
.
displayTabTooltip
);
this
.
$
(
'#sequence-list .nav-item'
).
on
(
'blur mouseleave'
,
this
.
hideTabTooltip
);
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
)
{
Sequence
.
prototype
.
displayTabTooltip
=
function
(
event
)
{
$
(
event
.
currentTarget
).
find
(
'.sequence-tooltip'
).
removeClass
(
'sr'
);
$
(
event
.
currentTarget
).
find
(
'.sequence-tooltip'
).
removeClass
(
'sr'
);
};
};
...
@@ -317,13 +375,22 @@
...
@@ -317,13 +375,22 @@
Sequence
.
prototype
.
mark_visited
=
function
(
position
)
{
Sequence
.
prototype
.
mark_visited
=
function
(
position
)
{
// Don't overwrite class attribute to avoid changing Progress class
// Don't overwrite class attribute to avoid changing Progress class
var
element
=
this
.
link_for
(
position
);
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
)
{
Sequence
.
prototype
.
mark_active
=
function
(
position
)
{
// Don't overwrite class attribute to avoid changing Progress class
// Don't overwrite class attribute to avoid changing Progress class
var
element
=
this
.
link_for
(
position
);
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
)
{
Sequence
.
prototype
.
addBookmarkIconToActiveNavItem
=
function
(
event
)
{
...
...
lms/djangoapps/courseware/tests/test_split_module.py
View file @
d497e194
...
@@ -125,7 +125,7 @@ class SplitTestBase(SharedModuleStoreTestCase):
...
@@ -125,7 +125,7 @@ class SplitTestBase(SharedModuleStoreTestCase):
content
=
resp
.
content
content
=
resp
.
content
# Assert we see the proper icon in the top display
# 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
# And proper tooltips
for
tooltip
in
self
.
TOOLTIPS
[
user_tag
]:
for
tooltip
in
self
.
TOOLTIPS
[
user_tag
]:
self
.
assertIn
(
tooltip
,
content
)
self
.
assertIn
(
tooltip
,
content
)
...
...
lms/static/lms/js/spec/main.js
View file @
d497e194
...
@@ -64,6 +64,7 @@
...
@@ -64,6 +64,7 @@
'xblock/lms.runtime.v1'
:
'lms/js/xblock/lms.runtime.v1'
,
'xblock/lms.runtime.v1'
:
'lms/js/xblock/lms.runtime.v1'
,
'xblock'
:
'common/js/xblock'
,
'xblock'
:
'common/js/xblock'
,
'capa/display'
:
'xmodule_js/src/capa/display'
,
'capa/display'
:
'xmodule_js/src/capa/display'
,
'sequence/display'
:
'xmodule_js/src/sequence/display'
,
'string_utils'
:
'xmodule_js/common_static/js/src/string_utils'
,
'string_utils'
:
'xmodule_js/common_static/js/src/string_utils'
,
'logger'
:
'xmodule_js/common_static/js/src/logger'
,
'logger'
:
'xmodule_js/common_static/js/src/logger'
,
'Markdown.Converter'
:
'js/Markdown.Converter'
,
'Markdown.Converter'
:
'js/Markdown.Converter'
,
...
...
lms/templates/seq_module.html
View file @
d497e194
...
@@ -17,16 +17,22 @@
...
@@ -17,16 +17,22 @@
<span
class=
"icon fa fa-chevron-prev"
aria-hidden=
"true"
></span>
<span
class=
"icon fa fa-chevron-prev"
aria-hidden=
"true"
></span>
<span>
${_('Previous')}
</span>
<span>
${_('Previous')}
</span>
</button>
</button>
<nav
class=
"sequence-list-wrapper"
aria-label=
"${_('
Unit
')}"
>
<nav
class=
"sequence-list-wrapper"
aria-label=
"${_('
Sequence
')}"
>
<ol
id=
"sequence-list"
>
<ol
id=
"sequence-list"
role=
"tablist"
>
% for idx, item in enumerate(items):
% for idx, item in enumerate(items):
<li>
<li>
<button
class=
"seq_${item['type']} inactive nav-item"
<button
class=
"seq_${item['type']} inactive nav-item tab"
data-id=
"${item['id']}"
role=
"tab"
data-element=
"${idx+1}"
tabindex=
"-1"
data-page-title=
"${item['page_title']}"
aria-selected=
"false"
data-path=
"${item['path']}"
aria-expanded=
"false"
id=
"tab_${idx}"
>
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=
"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>
<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>
<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 @@
...
@@ -51,7 +57,7 @@
${item['content']}
${item['content']}
</div>
</div>
% endfor
% endfor
<div
id=
"seq_content"
></div>
<div
id=
"seq_content"
role=
"tabpanel"
></div>
<nav
class=
"sequence-bottom"
aria-label=
"${_('Section')}"
>
<nav
class=
"sequence-bottom"
aria-label=
"${_('Section')}"
>
<button
class=
"sequence-nav-button button-previous"
>
<button
class=
"sequence-nav-button button-previous"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment