From 508ded2673e70ccc8bc019dbe6b963c6d9e6af06 Mon Sep 17 00:00:00 2001 From: Ahsan Ulhaq Date: Wed, 22 Apr 2015 16:16:06 +0500 Subject: [PATCH] LMS: course navigation menu refactor --- .../test/acceptance/pages/lms/course_nav.py | 20 +- .../test/acceptance/pages/lms/courseware.py | 4 +- common/test/acceptance/tests/lms/test_lms.py | 2 +- .../courseware/features/navigation.py | 2 +- lms/djangoapps/courseware/module_render.py | 3 + .../courseware/tests/test_entrance_exam.py | 15 +- .../courseware/tests/test_module_render.py | 9 +- .../courseware/tests/test_navigation.py | 1 - lms/djangoapps/courseware/testutils.py | 1 - lms/static/coffee/fixtures/accordion.html | 6 - lms/static/coffee/spec/courseware_spec.coffee | 5 - lms/static/coffee/spec/navigation_spec.coffee | 72 ---- lms/static/coffee/src/courseware.coffee | 1 - lms/static/coffee/src/navigation.coffee | 33 -- lms/static/coffee/src/navigation.js | 5 + lms/static/js/fixtures/accordion.html | 56 +++ lms/static/js/spec/main.js | 2 + lms/static/js/spec/navigation_spec.js | 93 +++++ lms/static/js/utils/navigation.js | 130 ++++++ .../sass/course/courseware/_courseware.scss | 7 +- .../sass/course/courseware/_sidebar.scss | 391 ++++++++---------- lms/templates/courseware/accordion.html | 88 ++-- lms/templates/courseware/courseware.html | 8 +- .../instructor_dashboard_2/e-commerce.html | 4 +- lms/templates/main.html | 1 + lms/templates/staticbook.html | 3 - 26 files changed, 520 insertions(+), 442 deletions(-) delete mode 100644 lms/static/coffee/fixtures/accordion.html delete mode 100644 lms/static/coffee/spec/navigation_spec.coffee delete mode 100644 lms/static/coffee/src/navigation.coffee create mode 100644 lms/static/coffee/src/navigation.js create mode 100644 lms/static/js/fixtures/accordion.html create mode 100644 lms/static/js/spec/navigation_spec.js create mode 100644 lms/static/js/utils/navigation.js diff --git a/common/test/acceptance/pages/lms/course_nav.py b/common/test/acceptance/pages/lms/course_nav.py index a7bde281a7..5134b5bacf 100644 --- a/common/test/acceptance/pages/lms/course_nav.py +++ b/common/test/acceptance/pages/lms/course_nav.py @@ -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}) " + ".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}) " + ".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") diff --git a/common/test/acceptance/pages/lms/courseware.py b/common/test/acceptance/pages/lms/courseware.py index bb842682d8..a2395397e8 100644 --- a/common/test/acceptance/pages/lms/courseware.py +++ b/common/test/acceptance/pages/lms/courseware.py @@ -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 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 .menu-item.active a').attrs('href')[0] @property def can_start_proctored_exam(self): diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py index cd9cedd386..23e000938b 100644 --- a/common/test/acceptance/tests/lms/test_lms.py +++ b/common/test/acceptance/tests/lms/test_lms.py @@ -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() diff --git a/lms/djangoapps/courseware/features/navigation.py b/lms/djangoapps/courseware/features/navigation.py index 72eb109a7a..c63ff1070a 100644 --- a/lms/djangoapps/courseware/features/navigation.py +++ b/lms/djangoapps/courseware/features/navigation.py @@ -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='.chapter', index=1) subsection_css = 'a[href*="Test_Subsection_2/"]' # Click on the subsection to see the content diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index ddb321e4ed..43b9866782 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -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 diff --git a/lms/djangoapps/courseware/tests/test_entrance_exam.py b/lms/djangoapps/courseware/tests/test_entrance_exam.py index 636dcc1e3b..ae1fd8622a 100644 --- a/lms/djangoapps/courseware/tests/test_entrance_exam.py +++ b/lms/djangoapps/courseware/tests/test_entrance_exam.py @@ -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' } ] ) diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 8f8849a100..3f4da9b8a0 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -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( diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index ec8b15ff5d..0e669ed9ae 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -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) diff --git a/lms/djangoapps/courseware/testutils.py b/lms/djangoapps/courseware/testutils.py index b0e4e8b31f..bd0c804f1b 100644 --- a/lms/djangoapps/courseware/testutils.py +++ b/lms/djangoapps/courseware/testutils.py @@ -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 = [ - '
-
- close -
-
- diff --git a/lms/static/coffee/spec/courseware_spec.coffee b/lms/static/coffee/spec/courseware_spec.coffee index c6b0c605c7..129b4308a6 100644 --- a/lms/static/coffee/spec/courseware_spec.coffee +++ b/lms/static/coffee/spec/courseware_spec.coffee @@ -1,10 +1,5 @@ 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() diff --git a/lms/static/coffee/spec/navigation_spec.coffee b/lms/static/coffee/spec/navigation_spec.coffee deleted file mode 100644 index 162eff3f2f..0000000000 --- a/lms/static/coffee/spec/navigation_spec.coffee +++ /dev/null @@ -1,72 +0,0 @@ -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('') - 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('') - 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' diff --git a/lms/static/coffee/src/courseware.coffee b/lms/static/coffee/src/courseware.coffee index 50fcb3ecec..49e27c484b 100644 --- a/lms/static/coffee/src/courseware.coffee +++ b/lms/static/coffee/src/courseware.coffee @@ -2,7 +2,6 @@ class @Courseware @prefix: '' constructor: -> - new Navigation Logger.bind() @render() diff --git a/lms/static/coffee/src/navigation.coffee b/lms/static/coffee/src/navigation.coffee deleted file mode 100644 index 06c38b781a..0000000000 --- a/lms/static/coffee/src/navigation.coffee +++ /dev/null @@ -1,33 +0,0 @@ -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 diff --git a/lms/static/coffee/src/navigation.js b/lms/static/coffee/src/navigation.js new file mode 100644 index 0000000000..971f54d80f --- /dev/null +++ b/lms/static/coffee/src/navigation.js @@ -0,0 +1,5 @@ +// This file is intentionally blank as it was removed because it was not +// longer necessary, but the pipeline requires it for whatever reason. +// Until the pipeline issue is resolved this file is here. It shouldn't +// conflict with anything. +var nothingtoseehere; \ No newline at end of file diff --git a/lms/static/js/fixtures/accordion.html b/lms/static/js/fixtures/accordion.html new file mode 100644 index 0000000000..3c0aa504ae --- /dev/null +++ b/lms/static/js/fixtures/accordion.html @@ -0,0 +1,56 @@ +] \ No newline at end of file diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index 76b2d32a63..66ca5202bb 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -69,6 +69,7 @@ 'history': 'js/vendor/history', 'js/staff_debug_actions': 'js/staff_debug_actions', 'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit', + 'js/utils/navigation': 'js/utils/navigation', // Backbone classes loaded explicitly until they are converted to use RequireJS 'js/models/notification': 'js/models/notification', @@ -685,6 +686,7 @@ 'lms/include/js/spec/edxnotes/plugins/caret_navigation_spec.js', 'lms/include/js/spec/edxnotes/collections/notes_spec.js', 'lms/include/js/spec/search/search_spec.js', + 'lms/include/js/spec/navigation_spec.js', 'lms/include/js/spec/discovery/collections/filters_spec.js', 'lms/include/js/spec/discovery/models/course_card_spec.js', 'lms/include/js/spec/discovery/models/course_directory_spec.js', diff --git a/lms/static/js/spec/navigation_spec.js b/lms/static/js/spec/navigation_spec.js new file mode 100644 index 0000000000..33ea8ee3d3 --- /dev/null +++ b/lms/static/js/spec/navigation_spec.js @@ -0,0 +1,93 @@ +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(chapterContent).toHaveAttr({ + 'aria-expanded': 'true' + }); + }); + + it('ensures only one active item', function() { + expect(chapterMenu.find('.active').length).toBe(1); + }); + }); + + describe('open section with mouse click', function() { + + it('ensures new section is opened and previous section is closed', function() { + button:eq(1).click(); + + expect(chapterContent:eq(0)).not.toHaveClass('is-open'); + expect(chapterContent:eq(1)).toHaveClass('is-open'); + + expect(button:eq(0)).not.toHaveClass('is-open'); + expect(button:eq(1)).toHaveClass('is-open'); + + expect(chapterContent:eq(1).focus).toHaveBeenCalled(); + }); + + it('ensure proper aria and attrs', function() { + expect(chapterContent:eq(1)).toHaveAttr({ + 'aria-expanded': 'false' + }); + expect(chapterContent:eq(0)).toHaveAttr({ + 'aria-expanded': 'true' + }); + }); + }); + + describe('open section with spacebar', function() { + + function keyPressEvent(key) { + return $.Event('keydown', { keyCode: key }); + } + + it('ensures new section is opened and previous section is closed', function() { + button:eq(1).focus(); + button.trigger(keyPressEvent(32)); // Spacebar + + expect(chapterContent:eq(0)).not.toHaveClass('is-open'); + expect(chapterContent:eq(1)).toHaveClass('is-open'); + + expect(button:eq(0)).not.toHaveClass('is-open'); + expect(button:eq(1)).toHaveClass('is-open'); + + expect(chapterContent:eq(1).focus).toHaveBeenCalled(); + }); + + it('ensure proper aria and attrs', function() { + expect(chapterContent:eq(1)).toHaveAttr({ + 'aria-expanded': 'false' + }); + expect(chapterContent:eq(0)).toHaveAttr({ + 'aria-expanded': 'true' + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/lms/static/js/utils/navigation.js b/lms/static/js/utils/navigation.js new file mode 100644 index 0000000000..9f46907d0a --- /dev/null +++ b/lms/static/js/utils/navigation.js @@ -0,0 +1,130 @@ +var edx = edx || {}, + + Navigation = (function() { + + var navigation = { + + init: function() { + if ($('.accordion').length) { + navigation.loadAccordion(); + } + }, + + loadAccordion: function() { + navigation.checkForCurrent(); + navigation.listenForClick(); + navigation.listenForKeypress(); + }, + + getActiveIndex: function() { + var index = $('.accordion .button-chapter:has(.active)').index('.accordion .button-chapter'), + button = null; + + if (index > -1) { + button = $('.accordion .button-chapter:eq(' + index + ')'); + } + + return button; + }, + + checkForCurrent: function() { + var button = navigation.getActiveIndex(); + + navigation.closeAccordions(); + + if (button !== null) { + navigation.setupCurrentAccordionSection(button); + } + }, + + listenForClick: function() { + $('.accordion').on('click', '.button-chapter', function(event) { + event.preventDefault(); + + var button = $(event.currentTarget), + section = button.next('.chapter-content-container'); + + navigation.closeAccordions(button, section); + navigation.openAccordion(button, section); + }); + }, + + listenForKeypress: function() { + $('.accordion').on('keydown', '.button-chapter', function(event) { + // because we're changing the role of the toggle from an 'a' to a 'button' + // we need to ensure it has the same keyboard use cases as a real button. + // this is useful for screenreader users primarily. + if (event.which == 32) { // spacebar + event.preventDefault(); + $(event.currentTarget).trigger('click'); + } else { + return true; + } + }); + }, + + closeAccordions: function(button, section) { + var menu = $(section).find('.chapter-menu'), toggle; + + $('.accordion .button-chapter').each(function(index, element) { + toggle = $(element); + + toggle + .removeClass('is-open') + .attr('aria-expanded', 'false'); + + toggle + .children('.group-heading') + .removeClass('active') + .find('.icon') + .addClass('fa-caret-right') + .removeClass('fa-caret-down'); + + toggle + .next('.chapter-content-container') + .removeClass('is-open') + .find('.chapter-menu').not(menu) + .removeClass('is-open') + .slideUp(); + }); + }, + + setupCurrentAccordionSection: function(button) { + var section = $(button).next('.chapter-content-container'); + + navigation.openAccordion(button, section); + }, + + openAccordion: function(button, section) { + var sectionEl = $(section), + firstLink = sectionEl.find('.menu-item').first(), + buttonEl = $(button); + + buttonEl + .addClass('is-open') + .attr('aria-expanded', 'true'); + + buttonEl + .children('.group-heading') + .addClass('active') + .find('.icon') + .removeClass('fa-caret-right') + .addClass('fa-caret-down'); + + sectionEl + .addClass('is-open') + .find('.chapter-menu') + .addClass('is-open') + .slideDown(); + } + }; + + return { + init: navigation.init + }; + + })(); + + edx.util = edx.util || {}; + edx.util.navigation = Navigation; + edx.util.navigation.init(); diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index d7dd243905..d4393e0fe6 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -647,7 +647,7 @@ div.course-wrapper { } } - div#accordion { + .accordion { visibility: hidden; width: 10px; padding: 0; @@ -655,11 +655,6 @@ div.course-wrapper { nav { white-space: pre; overflow: hidden; - - ul { - overflow: hidden; - white-space: nowrap; - } } } } diff --git a/lms/static/sass/course/courseware/_sidebar.scss b/lms/static/sass/course/courseware/_sidebar.scss index 07a0bdf04d..fda4857eac 100644 --- a/lms/static/sass/course/courseware/_sidebar.scss +++ b/lms/static/sass/course/courseware/_sidebar.scss @@ -1,234 +1,171 @@ .course-index { - @extend .sidebar; - @extend .tran; - @include border-right(1px solid $border-color-2); - @include border-radius(3px, 0, 0, 3px); + @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; - - h3 { - border-radius: 0; - margin: 0; - overflow: visible; - font-size: 16px; - - &:first-child { - border: none; - } - - &:hover, &:focus { - color: #666; - } - - &.ui-state-hover, &.ui-state-focus { - a { - color: #666; - } - } - - &.ui-accordion-header { - border-bottom: none; + // provides sufficient contrast for all screen reader-only elements + .sr { color: $black; - - a { - border-radius: 0; - box-shadow: none; - @include padding-left(19px); - color: $link-color; - } - - &.ui-state-active { - @extend .active; - border-bottom: none; - - &:hover, &:focus { - background: none; - } - } - - 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 - } - - // CASE: right to left layout - @include rtl { - background-position: -96px -16px; // jQuery UI west arrow position - } - } - } - } - } - - .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 { 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; - } } - ul.ui-accordion-content { - background: transparent; - border: none; - border-radius: 0; - margin: 0; - padding: 9px 0 9px 9px; - overflow: auto; - width: 100%; - - li { - border-bottom: 0; - border-radius: 0; - margin-bottom: ($baseline/5); - - a { - background: transparent; - border-radius: 4px; - display: block; - @include padding( ($baseline/4), ($baseline*1.5), ($baseline/4), ($baseline/2)); - position: relative; - text-decoration: none; - - p { - font-weight: bold; - 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; - margin: 0; - - &:empty { - display: none; - } - - // definitions for proctored exam attempt status indicators - i.verified { - color: $success-color; - } - - i.rejected { - color: $alert-color; - } - - i.error { - color: $alert-color; - } - } - } - - &:hover, &:focus { - background: $shadow-l1; - - > a p { - color: $gray-d3; - } - } - - &:active { - box-shadow: inset 0 1px 14px 0 $shadow-l1; - - &:after { - opacity: 1.0; - right: 15px; - } - } - } - - &.active { - @extend %t-weight5; - - &:after { - content: '›'; - position: absolute; - top: 50%; - right: 20px; - margin-top: -13px; - font-size: 30px; - font-weight: normal; - color: #333; - 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); - } - } - } + // reseting bolded fonts for the course index + .group-heading { + @extend %t-regular; } - } -} + + // we're targetting the .class now, instead of the #id + .accordion { + @extend %t-copy-sub1; + + .course-navigation { + + .button-chapter { + @include transition(all $tmg-s3 ease-in-out); + @include box-sizing(border-box); + @include linear-gradient(top, $sidebar-chapter-bg-top, $sidebar-chapter-bg-bottom); + @include transition(background-color .1s linear 0s); + display: block; + width: 100%; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: 0 1px 0 $white inset, 0 -1px 0 $shadow-l1 inset; + background-color: $sidebar-chapter-bg; + color: $link-color; + -webkit-font-smoothing: subpixel-antialiased; // this brings back our nice, dark blue + + .group-heading { + @extend %t-title6; + position: relative; + display: block; + padding: ($baseline*.75) $baseline ($baseline*.75) ($baseline*2); + @include text-align(left); + + .icon { + position: absolute; + @include left($baseline); + top: $baseline; + font-size: $body-font-size; + color: $gray-l3; + } + } + + &.is-open { + background: $white; + box-shadow: none; + } + + &.active { + + .group-heading { + @extend %t-strong; + color: $base-font-color; + } + } + + &:hover, + &:focus { + background: $white; + } + + &:active { + outline: none; + } + } + + .chapter-content-container { + background: $white; + + &.is-open { + border-bottom: 1px solid $shadow-l1; + } + + .chapter-menu { + display: none; + padding: 0 14px; + overflow: hidden; + + .menu-item { + @extend %t-strong; + margin: ($baseline/5) 0; + background: inherit; + + a { + @extend %t-action3; + @extend %t-strong; + position: relative; + display: block; + @include padding(($baseline/4) ($baseline/2)); + border-radius: ($baseline/4); + font-family: $sans-serif; + color: $base-font-color; + + p { + + &.subtitle { + @extend %t-action4; + @extend %t-regular; + display: block; + margin: 0; + color: $gray-d1; + + &:empty { + // hide empty subtitles + display: none; + } + + // definitions for proctored exam attempt status indicators + .verified { + color: $success-color; + } + + .rejected { + color: $alert-color; + } + + .error { + color: $alert-color; + } + } + } + + &:hover, + &:focus { + color: $base-font-color; + background: $gray-l5; + + .subtitle { + color: $gray-d1; + } + } + } + + &.graded { + + .menu-icon { + @include right($baseline/2); + position: absolute; + bottom: ($baseline/2); + color: $link-color; + } + } + + &.active { + + a { + @extend %t-ultrastrong; + background: $gray-l4; + } + } + + &:last-of-type { + padding-bottom: ($baseline/2); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/lms/templates/courseware/accordion.html b/lms/templates/courseware/accordion.html index 2d429ef11d..161109ba6b 100644 --- a/lms/templates/courseware/accordion.html +++ b/lms/templates/courseware/accordion.html @@ -6,73 +6,45 @@ %> <%def name="make_chapter(chapter)"> -
- <% - if chapter.get('active'): - aria_label = _('{chapter}, current chapter').format(chapter=chapter['display_name']) - active_class = ' class="active"' - else: - aria_label = chapter['display_name'] - active_class = '' - %> -

- - ${chapter['display_name']} - -

- - -
+ + % endfor + + % for chapter in toc: ${make_chapter(chapter)} -% endfor +% endfor \ No newline at end of file diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index 0642cff160..e6dcf95804 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -165,10 +165,6 @@ ${fragment.foot_html()} % if disable_accordion is UNDEFINED or not disable_accordion:
-
- ${_("close")} -
- % if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'): % endif -