Commit 52a8bc50 by clrux

Merge pull request #9367 from edx/clrux/ac-76-course-nav

Course navigation menu accessibility issue
parents 69ddbc0d ab569feb
......@@ -82,7 +82,7 @@ class CourseNavPage(PageObject):
# Click the section to ensure it's open (no harm in clicking twice if it's already open)
# Add one to convert from list index to CSS index
section_css = 'nav>div.chapter:nth-of-type({0})>h3>a'.format(sec_index + 1)
section_css = '.course-navigation .chapter:nth-of-type({0})'.format(sec_index + 1)
self.q(css=section_css).first.click()
# Get the subsection by index
......@@ -94,9 +94,10 @@ class CourseNavPage(PageObject):
return
# Convert list indices (start at zero) to CSS indices (start at 1)
subsection_css = "nav>div.chapter:nth-of-type({0})>ul>li:nth-of-type({1})>a".format(
sec_index + 1, subsec_index + 1
)
subsection_css = (
".course-navigation .chapter-content-container:nth-of-type({0}) "
".chapter-menu .menu-item:nth-of-type({1})"
).format(sec_index + 1, subsec_index + 1)
# Click the subsection and ensure that the page finishes reloading
self.q(css=subsection_css).first.click()
......@@ -130,7 +131,7 @@ class CourseNavPage(PageObject):
"""
Return a list of all section titles on the page.
"""
chapter_css = 'nav > div.chapter > h3 > a'
chapter_css = '.course-navigation .chapter .group-heading'
return self.q(css=chapter_css).map(lambda el: el.text.strip()).results
def _subsection_titles(self, section_index):
......@@ -140,7 +141,10 @@ class CourseNavPage(PageObject):
"""
# Retrieve the subsection title for the section
# Add one to the list index to get the CSS index, which starts at one
subsection_css = 'nav>div.chapter:nth-of-type({0})>ul>li>a>p:nth-of-type(1)'.format(section_index)
subsection_css = (
".course-navigation .chapter-content-container:nth-of-type({0}) "
".chapter-menu .menu-item a p:nth-of-type(1)"
).format(section_index)
# If the element is visible, we can get its text directly
# Otherwise, we need to get the HTML
......@@ -171,8 +175,8 @@ class CourseNavPage(PageObject):
That's true right after we click the section/subsection, but not true in general
(the user could go to a section, then expand another tab).
"""
current_section_list = self.q(css='nav>div.chapter.is-open>h3>a').text
current_subsection_list = self.q(css='nav>div.chapter.is-open li.active>a>p').text
current_section_list = self.q(css='.course-navigation .chapter.is-open .group-heading').text
current_subsection_list = self.q(css='.course-navigation .chapter-content-container .menu-item.active a p').text
if len(current_section_list) == 0:
self.warning("Could not find the current section")
......
......@@ -14,7 +14,7 @@ class CoursewarePage(CoursePage):
url_path = "courseware/"
xblock_component_selector = '.vert .xblock'
section_selector = '.chapter'
subsection_selector = '.chapter ul li'
subsection_selector = '.chapter-content-container .chapter-menu a'
def is_browser_on_page(self):
return self.q(css='body.courseware').present
......@@ -102,7 +102,7 @@ class CoursewarePage(CoursePage):
"""
return the url of the active subsection in the left nav
"""
return self.q(css='.chapter ul li.active a').attrs('href')[0]
return self.q(css='.chapter-content-container .chapter-menu .menu-item.active a').attrs('href')[0]
@property
def can_start_proctored_exam(self):
......
......@@ -1121,7 +1121,7 @@ class EntranceExamTest(UniqueCourseTest):
When I view the courseware that has an entrance exam
Then there should be an "Entrance Exam" chapter.'
"""
entrance_exam_link_selector = 'div#accordion nav div h3 a'
entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading'
# visit courseware page and make sure there is not entrance exam chapter.
self.courseware_page.visit()
self.courseware_page.wait_for_page()
......
......@@ -92,7 +92,7 @@ def when_i_navigate_to_a_section(step):
world.disable_jquery_animations()
# Open the 2nd section
world.css_click(css_selector='div.chapter', index=1)
world.css_click(css_selector='button.chapter', index=1)
subsection_css = 'a[href*="Test_Subsection_2/"]'
# Click on the subsection to see the content
......
......@@ -74,6 +74,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.x_module import XModuleDescriptor
from xmodule.mixin import wrap_with_license
from util.json_request import JsonResponse
from util.model_utils import slugify
from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
from util import milestones_helpers
from verify_student.services import ReverificationService
......@@ -165,6 +166,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
for chapter in chapters:
# Only show required content, if there is required content
# chapter.hide_from_toc is read-only (boo)
display_id = slugify(chapter.display_name_with_default)
local_hide_from_toc = False
if required_content:
if unicode(chapter.location) not in required_content:
......@@ -246,6 +248,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
sections.append(section_context)
toc_chapters.append({
'display_name': chapter.display_name_with_default,
'display_id': display_id,
'url_name': chapter.url_name,
'sections': sections,
'active': chapter.url_name == active_chapter
......
......@@ -155,7 +155,8 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase):
}
],
'url_name': u'Entrance_Exam_Section_-_Chapter_1',
'display_name': u'Entrance Exam Section - Chapter 1'
'display_name': u'Entrance Exam Section - Chapter 1',
'display_id': u'entrance-exam-section-chapter-1',
}
]
)
......@@ -182,19 +183,22 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase):
}
],
'url_name': u'Overview',
'display_name': u'Overview'
'display_name': u'Overview',
'display_id': u'overview'
},
{
'active': False,
'sections': [],
'url_name': u'Week_1',
'display_name': u'Week 1'
'display_name': u'Week 1',
'display_id': u'week-1'
},
{
'active': False,
'sections': [],
'url_name': u'Instructor',
'display_name': u'Instructor'
'display_name': u'Instructor',
'display_id': u'instructor'
},
{
'active': True,
......@@ -209,7 +213,8 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase):
}
],
'url_name': u'Entrance_Exam_Section_-_Chapter_1',
'display_name': u'Entrance Exam Section - Chapter 1'
'display_name': u'Entrance Exam Section - Chapter 1',
'display_id': u'entrance-exam-section-chapter-1'
}
]
)
......
......@@ -633,6 +633,7 @@ class TestTOC(ModuleStoreTestCase):
def test_toc_toy_from_chapter(self, default_ms, setup_finds, setup_sends, toc_finds):
with self.store.default_store(default_ms):
self.setup_modulestore(default_ms, setup_finds, setup_sends)
expected = ([{'active': True, 'sections':
[{'url_name': 'Toy_Videos', 'display_name': u'Toy Videos', 'graded': True,
'format': u'Lecture Sequence', 'due': None, 'active': False},
......@@ -642,11 +643,11 @@ class TestTOC(ModuleStoreTestCase):
'format': '', 'due': None, 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'Video', 'graded': True,
'format': '', 'due': None, 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'},
'url_name': 'Overview', 'display_name': u'Overview', 'display_id': u'overview'},
{'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': None, 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
'url_name': 'secret:magic', 'display_name': 'secret:magic', 'display_id': 'secretmagic'}])
course = self.store.get_course(self.toy_course.id, depth=2)
with check_mongo_calls(toc_finds):
......@@ -682,11 +683,11 @@ class TestTOC(ModuleStoreTestCase):
'format': '', 'due': None, 'active': False},
{'url_name': 'video_4f66f493ac8f', 'display_name': 'Video', 'graded': True,
'format': '', 'due': None, 'active': False}],
'url_name': 'Overview', 'display_name': u'Overview'},
'url_name': 'Overview', 'display_name': u'Overview', 'display_id': u'overview'},
{'active': False, 'sections':
[{'url_name': 'toyvideo', 'display_name': 'toyvideo', 'graded': True,
'format': '', 'due': None, 'active': False}],
'url_name': 'secret:magic', 'display_name': 'secret:magic'}])
'url_name': 'secret:magic', 'display_name': 'secret:magic', 'display_id': 'secretmagic'}])
with check_mongo_calls(toc_finds):
actual = render.toc_for_course(
......
......@@ -107,7 +107,6 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
'chapter': 'Chrome',
'section': displayname,
}))
self.assertEquals('open_close_accordion' in response.content, accordion)
self.assertEquals('course-tabs' in response.content, tabs)
self.assertTabInactive('progress', response)
......
......@@ -25,7 +25,6 @@ class RenderXBlockTestMixin(object):
# DOM elements that appear in the LMS Courseware,
# but are excluded from the xBlock-only rendering.
COURSEWARE_CHROME_HTML_ELEMENTS = [
'<header id="open_close_accordion"',
'<ol class="course-tabs"',
'<footer id="footer-openedx"',
'<div class="window-wrap"',
......
<div class="course-wrapper">
<header id="open_close_accordion">
<a href="#">close</a>
</header>
<div id="accordion"></div>
</div>
describe 'Courseware', ->
describe 'start', ->
it 'create the navigation', ->
spyOn(window, 'Navigation')
Courseware.start()
expect(window.Navigation).toHaveBeenCalled()
it 'binds the Logger', ->
spyOn(Logger, 'bind')
Courseware.start()
......
describe 'Navigation', ->
beforeEach ->
loadFixtures 'coffee/fixtures/accordion.html'
@navigation = new Navigation
describe 'constructor', ->
describe 'when the #accordion exists', ->
describe 'when there is an active section', ->
beforeEach ->
spyOn $.fn, 'accordion'
$('#accordion').append('<ul><li></li></ul><ul><li class="active"></li></ul>')
new Navigation
it 'activate the accordion with correct active section', ->
expect($('#accordion').accordion).toHaveBeenCalledWith
active: 1
header: 'h3'
autoHeight: false
heightStyle: 'content'
describe 'when there is no active section', ->
beforeEach ->
spyOn $.fn, 'accordion'
$('#accordion').append('<ul><li></li></ul><ul><li></li></ul>')
new Navigation
it 'activate the accordian with no section as active', ->
expect($('#accordion').accordion).toHaveBeenCalledWith
active: 0
header: 'h3'
autoHeight: false
heightStyle: 'content'
it 'binds the accordionchange event', ->
expect($('#accordion')).toHandleWith 'accordionchange', @navigation.log
it 'bind the navigation toggle', ->
expect($('#open_close_accordion a')).toHandleWith 'click', @navigation.toggle
describe 'when the #accordion does not exists', ->
beforeEach ->
$('#accordion').remove()
it 'does not activate the accordion', ->
spyOn $.fn, 'accordion'
expect($('#accordion').accordion).wasNotCalled()
describe 'toggle', ->
it 'toggle closed class on the wrapper', ->
$('.course-wrapper').removeClass('closed')
@navigation.toggle()
expect($('.course-wrapper')).toHaveClass('closed')
@navigation.toggle()
expect($('.course-wrapper')).not.toHaveClass('closed')
describe 'log', ->
beforeEach ->
spyOn Logger, 'log'
it 'submit event log', ->
@navigation.log {}, {
newHeader:
text: -> "new"
oldHeader:
text: -> "old"
}
expect(Logger.log).toHaveBeenCalledWith 'accordion',
newheader: 'new'
oldheader: 'old'
......@@ -2,7 +2,6 @@ class @Courseware
@prefix: ''
constructor: ->
new Navigation
Logger.bind()
@render()
......
class @Navigation
constructor: ->
if $('#accordion').length
# First look for an active section
active = $('#accordion ul:has(li.active)').index('#accordion ul')
# if we didn't find one, look for an active chapter
if active < 0
active = $('#accordion h3.active').index('#accordion h3')
# if that didn't work either, default to 0
if active < 0
active = 0
$('#accordion').bind('accordionchange', @log).accordion
active: active
header: 'h3'
autoHeight: false
heightStyle: 'content'
$('#accordion .ui-state-active').closest('.chapter').addClass('is-open')
$('#open_close_accordion a').click @toggle
$('#accordion').show()
$('#accordion a').click @setChapter
log: (event, ui) ->
Logger.log 'accordion',
newheader: ui.newHeader.text()
oldheader: ui.oldHeader.text()
toggle: ->
$('.course-wrapper').toggleClass('closed')
setChapter: ->
$('#accordion .is-open').removeClass('is-open')
$(this).closest('.chapter').addClass('is-open')
\ No newline at end of file
<div class="course-wrapper">
<div class="accordion">
<button class="button-chapter chapter" aria-controls="accordion-menu-1" aria-pressed="true">
<h3 class="group-heading">
Introduction Chapter
</h3>
</button>
<div class="chapter-content-container" tabindex="-1" aria-expanded="true">
<div class="chapter-menu" id="accordion-menu-1">
<div class="menu-item">
<a href="#">
<p>edX Homepage</p>
<p class="subtitle">Ungraded</p>
</a>
</div>
<div class="menu-item active">
<a href="#">
<p>The edX Blog</p>
</a>
</div>
<div class="menu-item graded">
<a href="#">
<p>Courses Dashboard</p>
<p class="subtitle">Graded</p>
</a>
</div>
</div>
</div>
<button class="button-chapter chapter" aria-controls="accordion-menu-2" aria-pressed="false">
<h3 class="group-heading">
Another Chapter
</h3>
</button>
<div class="chapter-content-container" tabindex="-1" aria-expanded="false">
<div class="chapter-menu" id="accordion-menu-2">
<div class="menu-item">
<a href="#">
<p>edX Homepage</p>
<p class="subtitle">Ungraded</p>
</a>
</div>
<div class="menu-item">
<a href="#">
<p>The edX Blog</p>
</a>
</div>
<div class="menu-item">
<a class="graded" href="#">
<p>Courses Dashboard</p>
<p class="subtitle">Graded</p>
</a>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
......@@ -64,6 +64,7 @@
'_split': 'js/split',
'mathjax_delay_renderer': 'coffee/src/mathjax_delay_renderer',
'MathJaxProcessor': 'coffee/src/customwmd',
'js/utils/navigation': 'js/utils/navigation',
// Manually specify LMS files that are not converted to RequireJS
'history': 'js/vendor/history',
......@@ -803,6 +804,8 @@
'lms/include/teams/js/spec/views/topic_teams_spec.js',
'lms/include/teams/js/spec/views/topics_spec.js',
'lms/include/teams/js/spec/views/team_profile_header_actions_spec.js'
'lms/include/teams/js/spec/views/team_join_spec.js',
'lms/include/js/spec/navigation_spec.js'
]);
}).call(this, requirejs, define);
define(['jquery', 'js/utils/navigation'], function($) {
'use strict';
describe('Course Navigation Accordion', function() {
var accordion, button, heading, chapterContent, chapterMenu;
beforeEach(function() {
loadFixtures('js/fixtures/accordion.html');
accordion = $('.accordion');
button = accordion.children('.button-chapter');
heading = button.children('.group-heading');
chapterContent = accordion.children('.chapter-content-container');
chapterMenu = chapterContent.children('.chapter-menu');
spyOn($.fn, 'focus').andCallThrough();
edx.util.navigation.init();
});
describe('constructor', function() {
describe('always', function() {
it('ensures accordion is present', function() {
expect(accordion.length).toBe(1);
});
it('ensures aria attributes are present', function() {
expect(button).toHaveAttr({
'aria-pressed': 'true'
});
expect(chapterContent).toHaveAttr({
'aria-expanded': 'true'
});
});
it('ensures only one active item', function() {
expect(chapterMenu.find('.active').length).toBe(1);
});
});
describe('open section', function() {
it('ensures new section is opened and previous section is closed', function() {
button.last().click();
expect(chapterContent.first()).toBeHidden();
expect(chapterContent.last()).not.toBeHidden();
expect(button.first()).not.toHaveClass('is-open');
expect(button.last()).toHaveClass('is-open');
expect(chapterContent.last().focus).toHaveBeenCalled();
});
it('ensure proper aria and attrs', function() {
expect(button.last()).toHaveAttr({
'aria-pressed': 'false'
});
expect(button.first()).toHaveAttr({
'aria-pressed': 'true'
});
expect(chapterContent.last()).toHaveAttr({
'aria-expanded': 'false'
});
expect(chapterContent.first()).toHaveAttr({
'aria-expanded': 'true'
})
});
});
});
});
});
\ No newline at end of file
var edx = edx || {},
Navigation = (function() {
var navigation = {
init: function() {
if ($('.accordion').length) {
navigation.openAccordion();
}
},
openAccordion: function() {
navigation.checkForCurrent();
navigation.listenForClick();
},
checkForCurrent: function() {
var active = $('.accordion .chapter-content-container .chapter-menu:has(.active)').index('.accordion .chapter-content-container .chapter-menu') ? $('.accordion .chapter-content-container .chapter-menu:has(.active)').index('.accordion .chapter-content-container .chapter-menu') : 0,
activeSection = $('.accordion .button-chapter:eq(' + active + ')');
navigation.closeAccordions();
navigation.openAccordionSection(activeSection);
},
listenForClick: function() {
$('.accordion').on('click', '.button-chapter', function(event) {
navigation.closeAccordions();
navigation.openAccordionSection(event.currentTarget);
});
},
closeAccordions: function() {
$('.chapter-content-container').hide();
$('.chapter-content-container .chapter-menu').hide();
$('.accordion .button-chapter').each(function(event) {
var el = $(this);
el.removeClass('is-open').attr('aria-pressed', 'false');
el.next('.chapter-content-container').attr('aria-expanded', 'false');
el.children('.group-heading').removeClass('active');
el.children('.group-heading').find('.icon').addClass('fa-caret-right').removeClass('fa-caret-down');
});
},
openAccordionSection: function(section) {
var elSection = $(section).next('.chapter-content-container');
elSection.show().focus();
elSection.find('.chapter-menu').show();
$(section).addClass('is-open').attr('aria-pressed', 'true');
$(section).next('.chapter-content-container').attr('aria-expanded', 'true');
$(section).children('.group-heading').addClass('active');
$(section).children('.group-heading').find('.icon').removeClass('fa-caret-right').addClass('fa-caret-down');
}
};
return {
init: navigation.init
};
})();
edx.util = edx.util || {};
edx.util.navigation = Navigation;
edx.util.navigation.init();
......@@ -516,7 +516,7 @@ div.course-wrapper {
}
}
div#accordion {
.accordion {
visibility: hidden;
width: 10px;
padding: 0;
......@@ -524,11 +524,6 @@ div.course-wrapper {
nav {
white-space: pre;
overflow: hidden;
ul {
overflow: hidden;
white-space: nowrap;
}
}
}
}
......
.course-index {
@extend .sidebar;
@extend .tran;
@include transition( all .2s $ease-in-out-quad 0s);
@include border-right(1px solid $border-color-2);
@include border-radius(3px, 0, 0, 3px);
display: table-cell; // needed to extend the sidebar the full height of the area
#open_close_accordion {
display: none;
}
header {
max-height: 47px;
h2 {
white-space: nowrap;
}
}
div#accordion {
width: auto;
font-size: 14px;
// reseting bolded fonts for the course index
h3 {
border-radius: 0;
margin: 0;
overflow: visible;
font-size: 16px;
&:first-child {
border: none;
@extend %t-regular;
}
&:hover, &:focus {
color: #666;
}
// we're targetting the .class now, instead of the #id
.accordion {
@extend %t-copy-sub1;
&.ui-state-hover, &.ui-state-focus {
a {
color: #666;
}
}
.course-navigation {
&.ui-accordion-header {
border-bottom: none;
color: $black;
a {
.button-chapter {
@include box-sizing(border-box);
@include linear-gradient(top, $sidebar-chapter-bg-top, $sidebar-chapter-bg-bottom);
@include transition(background-color .1s linear 0s);
width: 100% !important;
border: 0;
border-radius: 0;
box-shadow: none;
@include padding-left(19px);
padding: 0;
box-shadow: 0 1px 0 $white inset, 0 -1px 0 $shadow-l1 inset;
background-color: $sidebar-chapter-bg;
color: $link-color;
&.is-open {
background: $white;
box-shadow: none;
}
&.ui-state-active {
@extend .active;
border-bottom: none;
&.active {
&:hover, &:focus {
background: none;
.group-heading {
@extend %t-ultrastrong;
color: $base-font-color;
}
}
span.ui-icon {
@include left(0);
opacity: 0.3;
background-image: url("/static/images/ui-icons_222222_256x240.png"); // jQuery UI sprite
&.ui-icon-triangle-1-e {
// CASE: left to right layout
@include ltr {
background-position: -32px -16px; // jQuery UI east arrow position
}
.group-heading {
@extend %t-title6;
position: relative;
padding: ($baseline*.75) $baseline ($baseline*.75) ($baseline*2);
color: $link-color;
// CASE: right to left layout
@include rtl {
background-position: -96px -16px; // jQuery UI west arrow position
}
}
.icon {
position: absolute;
@include left($baseline);
top: $baseline;
color: $gray-d1;
}
}
}
.chapter {
width: 100% !important;
@include box-sizing(border-box);
padding: 11px 14px;
@include linear-gradient(top, $sidebar-chapter-bg-top, $sidebar-chapter-bg-bottom);
background-color: $sidebar-chapter-bg;
box-shadow: 0 1px 0 $white inset, 0 -1px 0 $shadow-l1 inset;
@include transition(background-color .1s linear 0s);
&.is-open {
.chapter-content-container {
display: none; // programmatically shown when the section is open; closed at the start
padding: 0 14px ($baseline/2) 14px;
border-bottom: 1px solid $shadow-l1;
background: $white;
}
&:first-child {
border-radius: 3px 0 0 0;
}
&:last-child {
border-radius: 0 0 0 3px;
box-shadow: 0 1px 0 $white inset;
}
&:hover, &:focus {
background-color: $white;
}
&:focus {
// we don't need the blue glow to be here at this point
// although focus is sent for correct keyboard/a11y
outline: none;
}
ul.ui-accordion-content {
background: transparent;
border: none;
border-radius: 0;
margin: 0;
padding: 9px 0 9px 9px;
overflow: auto;
width: 100%;
.chapter-menu {
padding: 0 ($baseline*.75);
background: inherit;
overflow: hidden;
li {
border-bottom: 0;
border-radius: 0;
margin-bottom: ($baseline/5);
.menu-item {
@extend %t-strong;
@include padding(($baseline/4) ($baseline/2));
margin: ($baseline/5) 0;
border-radius: ($baseline/5);
background: inherit;
a {
background: transparent;
border-radius: 4px;
display: block;
@include padding( ($baseline/4), ($baseline*1.5), ($baseline/4), ($baseline/2));
position: relative;
text-decoration: none;
display: block;
color: $base-font-color;
p {
font-weight: bold;
@extend %t-action3;
@extend %t-strong;
font-family: $sans-serif;
margin-bottom: 0;
line-height: 1.3;
&.subtitle {
@extend %t-copy-sub2;
@extend %t-weight2;
display: inline-block;
width: 90%;
color: $gray-d1;
@extend %t-action3;
@extend %t-regular;
display: block;
margin: 0;
color: $gray-d1;
&:empty {
// hide empty subtitles
display: none;
}
// definitions for proctored exam attempt status indicators
i.verified {
.verified {
color: $success-color;
}
i.rejected {
.rejected {
color: $alert-color;
}
i.error {
.error {
color: $alert-color;
}
}
}
&:hover, &:focus {
background: $shadow-l1;
&:hover,
&:focus {
color: $link-color;
> a p {
color: $gray-d3;
.subtitle {
color: $gray-d1;
}
}
}
&:active {
box-shadow: inset 0 1px 14px 0 $shadow-l1;
&.graded {
&:after {
opacity: 1.0;
right: 15px;
}
.menu-icon {
@include right(0);
position: absolute;
bottom: 0;
}
}
&.active {
@extend %t-weight5;
@extend %t-strong;
background: linear-gradient(to bottom, $gray-l4, #d6d6d6);
// pre-existing; but not sure where it appears
&:after {
content: '›';
@extend %t-regular;
@include transition(none);
@include right($baseline);
position: absolute;
top: 50%;
right: 20px;
margin-top: -13px;
font-size: 30px;
font-weight: normal;
color: #333;
color: $gray-d3;
content: '›';
opacity: 0;
@include transition(none);
}
> a {
border: 1px solid $border-color-1;
box-shadow: 0 1px 0 rgba(255, 255, 255, .35) inset;
background: $sidebar-active-image;
&:after {
opacity: 1.0;
right: 15px;
}
p {
color: #333;
}
}
span.subtitle {
@extend %t-weight2;
}
}
&.graded {
> a {
.icon {
vertical-align: middle;
}
}
&.active > a {
background: linear-gradient(to bottom, #e6e6e6, #d6d6d6);
}
}
}
......
......@@ -6,24 +6,24 @@
%>
<%def name="make_chapter(chapter)">
<div class="chapter">
<%
if chapter.get('active'):
<%
if chapter.get('active'):
aria_label = _('{chapter}, current chapter').format(chapter=chapter['display_name'])
active_class = ' class="active"'
else:
active_class = 'active"'
else:
aria_label = chapter['display_name']
active_class = ''
%>
<h3 ${active_class} aria-label="${aria_label}">
<a href="#">
%>
<button class="button-chapter chapter ${active_class}" id="${chapter['display_id']}-parent" aria-controls="${chapter['display_id']}-child" aria-pressed="false">
<h3 class="group-heading ${active_class}" aria-label="${aria_label}">
<span class="icon fa fa-caret-right" aria-hidden="true"></span>
${chapter['display_name']}
</a>
</h3>
<ul>
</button>
<div class="chapter-content-container" tabindex="-1" aria-expanded="false">
<div class="chapter-menu" id="${chapter['display_id']}-child" role="region" aria-labelledby="${chapter['display_id']}-parent" aria-hidden="false">
% for section in chapter['sections']:
<li class="${'active' if 'active' in section and section['active'] else ''} ${'graded' if 'graded' in section and section['graded'] else ''}">
<div class="menu-item ${'active' if 'active' in section and section['active'] else ''} ${'graded' if 'graded' in section and section['graded'] else ''}">
<a href="${reverse('courseware_section', args=[course_id, chapter['url_name'], section['url_name']])}">
<p>${section['display_name']} ${'<span class="sr">, current section</span>' if 'active' in section and section['active'] else ''}</p>
<%
......@@ -33,44 +33,15 @@
formatted_string = get_time_display(section['due'], due_date_display_format, coerce_tz=settings.TIME_ZONE_DISPLAYED_FOR_DEADLINES)
due_date = '' if len(formatted_string)==0 else _('due {date}').format(date=formatted_string)
%>
## There is behavior differences between
## rending of sections which have proctoring/timed examinations
## and those that do not.
##
## Proctoring exposes a exam status message field as well as
## a status icon
% if section['format'] or due_date or 'proctoring' in section:
<p class="subtitle">
% if 'proctoring' in section:
## Display the proctored exam status icon and status message
<i class="fa ${section['proctoring'].get('suggested_icon', 'fa-lock')} ${section['proctoring'].get('status', 'eligible')}"></i>&nbsp;
<span class="subtitle-name">${section['proctoring'].get('short_description', '')}
</span>
## completed proctored exam statuses should not show the due date
## since the exam has already been submitted by the user
% if not section['proctoring'].get('in_completed_state', False):
<span class="subtitle-name">${due_date}</span>
% endif
% else:
## non-proctored section, we just show the exam format and the due date
## this is the standard case in edx-platform
<span class="subtitle-name">${section['format']} ${due_date}
</span>
% endif
</p>
% endif
<p class="subtitle">${section['format']} ${due_date}</p>
% if 'graded' in section and section['graded']:
## sections that are graded should indicate this through an icon
<i class="icon fa fa-pencil-square-o" aria-hidden="true" data-tooltip="${_("This section is graded.")}"></i>
<img class="menu-icon" src="/static/images/graded.png" alt="Graded Section">
% endif
</a>
</li>
</div>
% endfor
</ul>
</div>
</div>
</%def>
% for chapter in toc:
......
......@@ -165,9 +165,6 @@ ${fragment.foot_html()}
% if disable_accordion is UNDEFINED or not disable_accordion:
<div class="course-index" role="navigation">
<header id="open_close_accordion">
<a href="#">${_("close")}</a>
</header>
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
<div id="courseware-search-bar" class="search-bar courseware-search-bar" role="search" aria-label="Course">
......@@ -186,8 +183,8 @@ ${fragment.foot_html()}
</div>
% endif
<div id="accordion" style="display: none">
<nav aria-label="${_('Course Navigation')}">
<div class="accordion">
<nav class="course-navigation" aria-label="${_('Course Navigation')}">
% if accordion.strip():
${accordion}
% else:
......
......@@ -204,11 +204,11 @@ import pytz
activeHeader: "ui-icon-carat-1-s"
};
var act = 0;
$("#accordion").accordion(
$(".accordion").accordion(
{
heightStyle: 'content',
activate: function(event, ui) {
var active = jQuery("#accordion").accordion('option', 'active');
var active = jQuery(".accordion").accordion('option', 'active');
$.cookie('saved_index', null);
$.cookie('saved_index', active);
$('#error-msg').val('');
......
......@@ -159,6 +159,7 @@ from branding import api as branding_api
<%block name="js_extra"/>
<script type="text/javascript" src="${static.url('js/vendor/noreferrer.js')}" charset="utf-8"></script>
<script type="text/javascript" src="${static.url('js/utils/navigation.js')}" charset="utf-8"></script>
</body>
</html>
......
......@@ -71,9 +71,6 @@ $("#open_close_accordion a").click(function(){
<div class="book-wrapper">
<section aria-label="${_('Textbook Navigation')}" class="book-sidebar">
<header id="open_close_accordion">
<a href="#">close</a>
</header>
<ul id="booknav" class="treeview-booknav">
<%def name="print_entry(entry)">
......
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