diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index b0681433b09d..deefb84ff199 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -171,7 +171,7 @@ def log_into_studio( world.log_in(username=uname, password=password, email=email, name=name) # Navigate to the studio dashboard world.visit('/') - assert_in(uname, world.css_text('h2.title', timeout=10)) + assert_in(uname, world.css_text('span.account-username', timeout=10)) def add_course_author(user, course): diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index 7b3ccbcbd2af..2640b5c233b8 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -33,8 +33,9 @@ def i_create_a_course(step): create_a_course() -@step('I click the course link in My Courses$') -def i_click_the_course_link_in_my_courses(step): +# pylint disable=unused-argument, invalid-name +@step('I click the course link in Studio Home$') +def i_click_the_course_link_in_studio_home(step): course_css = 'a.course-link' world.css_click(course_css) @@ -52,8 +53,9 @@ def courseware_page_has_loaded_in_studio(step): assert world.is_css_present(course_title_css) -@step('I see the course listed in My Courses$') -def i_see_the_course_in_my_courses(step): +@step('I see the course listed in Studio Home$') +# pylint disable=unused-argument +def i_see_the_course_in_studio_home(step): course_css = 'h3.class-title' assert world.css_has_text(course_css, world.scenario_dict['COURSE'].display_name) diff --git a/cms/djangoapps/contentstore/features/help.feature b/cms/djangoapps/contentstore/features/help.feature index eb0f872247b3..567a2f252664 100644 --- a/cms/djangoapps/contentstore/features/help.feature +++ b/cms/djangoapps/contentstore/features/help.feature @@ -11,7 +11,7 @@ Feature: CMS.Help Scenario: Users can access online help within a course Given I have opened a new course in Studio - And I click the course link in My Courses + And I click the course link in Studio Home Then I should see online help for "outline" And I go to the course updates page diff --git a/cms/djangoapps/contentstore/features/signup.feature b/cms/djangoapps/contentstore/features/signup.feature index 92ff0d393d7d..3f29c4fda68c 100644 --- a/cms/djangoapps/contentstore/features/signup.feature +++ b/cms/djangoapps/contentstore/features/signup.feature @@ -26,4 +26,4 @@ Feature: CMS.Sign in And I visit the url "/signin?next=http://www.google.com/" When I fill in and submit the signin form And I wait for "2" seconds - Then I should see that the path is "/course/" + Then I should see that the path is "/home/" diff --git a/cms/djangoapps/contentstore/features/signup.py b/cms/djangoapps/contentstore/features/signup.py index 26374fe963f7..a661b2473c2e 100644 --- a/cms/djangoapps/contentstore/features/signup.py +++ b/cms/djangoapps/contentstore/features/signup.py @@ -24,7 +24,7 @@ def i_press_the_button_on_the_registration_form(step): @step('I should see an email verification prompt') def i_should_see_an_email_verification_prompt(step): - world.css_has_text('h1.page-header', u'My Courses') + world.css_has_text('h1.page-header', u'Studio Home') world.css_has_text('div.msg h3.title', u'We need to verify your email address') diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 27714230dc62..a107d654494b 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1166,11 +1166,10 @@ def assert_course_permission_denied(self): def test_course_index_view_with_no_courses(self): """Test viewing the index page with no courses""" - # Create a course so there is something to view - resp = self.client.get_html('/course/') + resp = self.client.get_html('/home/') self.assertContains( resp, - '

My Courses

', + '

Studio Home

', status_code=200, html=True ) @@ -1189,7 +1188,7 @@ def test_item_factory(self): def test_course_index_view_with_course(self): """Test viewing the index page with an existing course""" CourseFactory.create(display_name='Robot Super Educational Course') - resp = self.client.get_html('/course/') + resp = self.client.get_html('/home/') self.assertContains( resp, '

Robot Super Educational Course

', @@ -1604,7 +1603,7 @@ def assertInCourseListing(self, course_key): Asserts that the given course key is in the accessible course listing section of the html and NOT in the unsucceeded course action section of the html. """ - course_listing = lxml.html.fromstring(self.client.get_html('/course/').content) + course_listing = lxml.html.fromstring(self.client.get_html('/home/').content) self.assertEqual(len(self.get_course_listing_elements(course_listing, course_key)), 1) self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 0) @@ -1613,7 +1612,7 @@ def assertInUnsucceededCourseActions(self, course_key): Asserts that the given course key is in the unsucceeded course action section of the html and NOT in the accessible course listing section of the html. """ - course_listing = lxml.html.fromstring(self.client.get_html('/course/').content) + course_listing = lxml.html.fromstring(self.client.get_html('/home/').content) self.assertEqual(len(self.get_course_listing_elements(course_listing, course_key)), 0) self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 1) diff --git a/cms/djangoapps/contentstore/tests/test_i18n.py b/cms/djangoapps/contentstore/tests/test_i18n.py index e9e17394883f..da7911722035 100644 --- a/cms/djangoapps/contentstore/tests/test_i18n.py +++ b/cms/djangoapps/contentstore/tests/test_i18n.py @@ -44,9 +44,9 @@ def test_course_plain_english(self): self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) - resp = self.client.get_html('/course/') + resp = self.client.get_html('/home/') self.assertContains(resp, - '

My Courses

', + '

Studio Home

', status_code=200, html=True) @@ -56,13 +56,13 @@ def test_course_explicit_english(self): self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( - '/course/', + '/home/', {}, HTTP_ACCEPT_LANGUAGE='en', ) self.assertContains(resp, - '

My Courses

', + '

Studio Home

', status_code=200, html=True) @@ -81,7 +81,7 @@ def test_course_with_accents(self): self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( - '/course/', + '/home/', {}, HTTP_ACCEPT_LANGUAGE='eo' ) diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 9975764345e1..76429befa6cf 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -234,13 +234,13 @@ def test_login_link_on_activation_age(self): def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ( - '/course/', + '/home/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ( - '/course/', + '/home/', ) # need an activated user @@ -266,7 +266,7 @@ def test_private_pages_auth(self): def test_index_auth(self): # not logged in. Should return a redirect. - resp = self.client.get_html('/course/') + resp = self.client.get_html('/home/') self.assertEqual(resp.status_code, 302) # Logged in should work. @@ -283,7 +283,7 @@ def test_inactive_session_timeout(self): self.login(self.email, self.pw) # make sure we can access courseware immediately - course_url = '/course/' + course_url = '/home/' resp = self.client.get_html(course_url) self.assertEquals(resp.status_code, 200) @@ -293,7 +293,7 @@ def test_inactive_session_timeout(self): resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page - self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/course/') + self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/home/') class ForumTestCase(CourseTestCase): diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 5bad2efa2207..f40e86f08204 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -1,6 +1,7 @@ """ Views related to operations on course objects """ +from django.shortcuts import redirect import json import random import string # pylint: disable=deprecated-module @@ -71,7 +72,8 @@ from xmodule.course_module import CourseFields -__all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler', +__all__ = ['course_info_handler', 'course_handler', 'course_listing', + 'course_info_update_handler', 'course_rerun_handler', 'settings_handler', 'grading_handler', @@ -230,7 +232,7 @@ def course_handler(request, course_key_string=None): return HttpResponseBadRequest() elif request.method == 'GET': # assume html if course_key_string is None: - return course_listing(request) + return redirect(reverse("home")) else: return course_index(request, CourseKey.from_string(course_key_string)) else: diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py index c599cc1559f9..9aeb9c9b13a2 100644 --- a/cms/djangoapps/contentstore/views/library.py +++ b/cms/djangoapps/contentstore/views/library.py @@ -142,7 +142,7 @@ def _create_library(request): 'ErrMsg': _( 'There is already a library defined with the same ' 'organization and library code. Please ' - 'change either organization or library code to be unique.' + 'change your library code so that it is unique within your organization.' ) }) diff --git a/cms/djangoapps/contentstore/views/public.py b/cms/djangoapps/contentstore/views/public.py index 597bb5e18781..7bc6545868c9 100644 --- a/cms/djangoapps/contentstore/views/public.py +++ b/cms/djangoapps/contentstore/views/public.py @@ -66,6 +66,6 @@ def login_page(request): def howitworks(request): "Proxy view" if request.user.is_authenticated(): - return redirect('/course/') + return redirect('/home/') else: return render_to_response('howitworks.html', {}) diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py index 965879e15428..32b5ccbf2cf2 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py @@ -42,7 +42,7 @@ def check_index_and_outline(self, authed_client): """ Test getting the list of courses and then pulling up their outlines """ - index_url = '/course/' + index_url = '/home/' index_response = authed_client.get(index_url, {}, HTTP_ACCEPT='text/html') parsed_html = lxml.html.fromstring(index_response.content) course_link_eles = parsed_html.find_class('course-link') @@ -68,7 +68,7 @@ def test_libraries_on_course_index(self): # Add a library: lib1 = LibraryFactory.create() - index_url = '/course/' + index_url = '/home/' index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html') parsed_html = lxml.html.fromstring(index_response.content) library_link_elements = parsed_html.find_class('library-link') diff --git a/cms/static/js/index.js b/cms/static/js/index.js index 3e64e911320e..e1c91d03a189 100644 --- a/cms/static/js/index.js +++ b/cms/static/js/index.js @@ -2,7 +2,7 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie "js/views/utils/create_library_utils", "js/views/utils/view_utils"], function (domReady, $, _, CancelOnEscape, CreateCourseUtilsFactory, CreateLibraryUtilsFactory, ViewUtils) { "use strict"; - var CreateCourseUtils = CreateCourseUtilsFactory({ + var CreateCourseUtils = new CreateCourseUtilsFactory({ name: '.new-course-name', org: '.new-course-org', number: '.new-course-number', @@ -21,7 +21,7 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie error: 'error' }); - var CreateLibraryUtils = CreateLibraryUtilsFactory({ + var CreateLibraryUtils = new CreateLibraryUtilsFactory({ name: '.new-library-name', org: '.new-library-org', number: '.new-library-number', @@ -60,17 +60,17 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie }; analytics.track('Created a Course', course_info); - CreateCourseUtils.createCourse(course_info, function (errorMessage) { + CreateCourseUtils.create(course_info, function (errorMessage) { $('.create-course .wrap-error').addClass('is-shown'); $('#course_creation_error').html('

' + errorMessage + '

'); - $('.new-course-save').addClass('is-disabled'); + $('.new-course-save').addClass('is-disabled').attr('aria-disabled', true); }); }; var makeCancelHandler = function (addType) { return function(e) { e.preventDefault(); - $('.new-'+addType+'-button').removeClass('is-disabled'); + $('.new-'+addType+'-button').removeClass('is-disabled').attr('aria-disabled', false); $('.wrapper-create-'+addType).removeClass('is-shown'); // Clear out existing fields and errors $('#create-'+addType+'-form input[type=text]').val(''); @@ -82,8 +82,8 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie var addNewCourse = function (e) { e.preventDefault(); - $('.new-course-button').addClass('is-disabled'); - $('.new-course-save').addClass('is-disabled'); + $('.new-course-button').addClass('is-disabled').attr('aria-disabled', true); + $('.new-course-save').addClass('is-disabled').attr('aria-disabled', true); var $newCourse = $('.wrapper-create-course').addClass('is-shown'); var $cancelButton = $newCourse.find('.new-course-cancel'); var $courseName = $('.new-course-name'); @@ -114,17 +114,17 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie }; analytics.track('Created a Library', lib_info); - CreateLibraryUtils.createLibrary(lib_info, function (errorMessage) { + CreateLibraryUtils.create(lib_info, function (errorMessage) { $('.create-library .wrap-error').addClass('is-shown'); $('#library_creation_error').html('

' + errorMessage + '

'); - $('.new-library-save').addClass('is-disabled'); + $('.new-library-save').addClass('is-disabled').attr('aria-disabled', true); }); }; var addNewLibrary = function (e) { e.preventDefault(); - $('.new-library-button').addClass('is-disabled'); - $('.new-library-save').addClass('is-disabled'); + $('.new-library-button').addClass('is-disabled').attr('aria-disabled', true); + $('.new-library-save').addClass('is-disabled').attr('aria-disabled', true); var $newLibrary = $('.wrapper-create-library').addClass('is-shown'); var $cancelButton = $newLibrary.find('.new-library-cancel'); var $libraryName = $('.new-library-name'); diff --git a/cms/static/js/spec/views/pages/container_spec.js b/cms/static/js/spec/views/pages/container_spec.js index ce862aac7d7f..7ef32bb7b779 100644 --- a/cms/static/js/spec/views/pages/container_spec.js +++ b/cms/static/js/spec/views/pages/container_spec.js @@ -494,6 +494,15 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel containerPage.$(".new-component .new-component-type a.single-template")[index].click(); }; + it('Attaches a handler to new component button', function() { + containerPage = getContainerPage(); + containerPage.render(); + // Stub jQuery.scrollTo module. + $.scrollTo = jasmine.createSpy('jQuery.scrollTo'); + containerPage.$('.new-component-button').click(); + expect($.scrollTo).toHaveBeenCalled(); + }); + it('sends the correct JSON to the server', function () { renderContainerPage(this, mockContainerXBlockHtml); clickNewComponent(0); diff --git a/cms/static/js/spec/views/pages/course_rerun_spec.js b/cms/static/js/spec/views/pages/course_rerun_spec.js index 320f8d6557ec..01a5b9c858a5 100644 --- a/cms/static/js/spec/views/pages/course_rerun_spec.js +++ b/cms/static/js/spec/views/pages/course_rerun_spec.js @@ -26,7 +26,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper }, mockCreateCourseRerunHTML = readFixtures('mock/mock-create-course-rerun.underscore'); - var CreateCourseUtils = CreateCourseUtilsFactory(selectors, classes); + var CreateCourseUtils = new CreateCourseUtilsFactory(selectors, classes); var fillInFields = function (org, number, run, name) { $(selectors.org).val(org); @@ -62,7 +62,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper describe("Error messages", function () { var setErrorMessage = function(selector, message) { var element = $(selector).parent(); - CreateCourseUtils.setNewCourseFieldInErr(element, message); + CreateCourseUtils.setFieldInErr(element, message); return element; }; diff --git a/cms/static/js/spec/views/pages/index_spec.js b/cms/static/js/spec/views/pages/index_spec.js index 52b71a834957..3217df1b2cc8 100644 --- a/cms/static/js/spec/views/pages/index_spec.js +++ b/cms/static/js/spec/views/pages/index_spec.js @@ -66,6 +66,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper expect($('.create-course .wrap-error')).toHaveClass('is-shown'); expect($('#course_creation_error')).toContainText('error message'); expect($('.new-course-save')).toHaveClass('is-disabled'); + expect($('.new-course-save')).toHaveAttr('aria-disabled', 'true'); }); it("saves new libraries", function () { @@ -97,6 +98,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper fillInLibraryFields.apply(this, values_with_blank); expect($('.create-library li.field.text input[value=]').parent()).toHaveClass('error'); expect($('.new-library-save')).toHaveClass('is-disabled'); + expect($('.new-library-save')).toHaveAttr('aria-disabled', 'true'); $('.new-library-save').click(); expect(requests.length).toEqual(requests_count); // Expect no new requests } @@ -123,6 +125,7 @@ define(["jquery", "js/common_helpers/ajax_helpers", "js/spec_helpers/view_helper expect($('.create-library .wrap-error')).toHaveClass('is-shown'); expect($('#library_creation_error')).toContainText('error message'); expect($('.new-library-save')).toHaveClass('is-disabled'); + expect($('.new-library-save')).toHaveAttr('aria-disabled', 'true'); }); it("can switch tabs", function() { diff --git a/cms/static/js/views/course_rerun.js b/cms/static/js/views/course_rerun.js index bfa6370d1b5f..ee99ee2c8102 100644 --- a/cms/static/js/views/course_rerun.js +++ b/cms/static/js/views/course_rerun.js @@ -1,6 +1,6 @@ define(["domReady", "jquery", "underscore", "js/views/utils/create_course_utils", "js/views/utils/view_utils"], function (domReady, $, _, CreateCourseUtilsFactory, ViewUtils) { - var CreateCourseUtils = CreateCourseUtilsFactory({ + var CreateCourseUtils = new CreateCourseUtilsFactory({ name: '.rerun-course-name', org: '.rerun-course-org', number: '.rerun-course-number', @@ -41,7 +41,7 @@ define(["domReady", "jquery", "underscore", "js/views/utils/create_course_utils" }; analytics.track('Reran a Course', course_info); - CreateCourseUtils.createCourse(course_info, function (errorMessage) { + CreateCourseUtils.create(course_info, function (errorMessage) { $('.wrapper-error').addClass('is-shown').removeClass('is-hidden'); $('#course_rerun_error').html('

' + errorMessage + '

'); $('.rerun-course-save').addClass('is-disabled').removeClass('is-processing').html(gettext('Create Re-run')); diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index cea6c4312090..593821788857 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -16,7 +16,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views events: { "click .edit-button": "editXBlock", "click .duplicate-button": "duplicateXBlock", - "click .delete-button": "deleteXBlock" + "click .delete-button": "deleteXBlock", + "click .new-component-button": "scrollToNewComponentButtons" }, options: { @@ -321,6 +322,11 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views temporaryView.unbind(); // Remove the temporary view } }); + }, + + scrollToNewComponentButtons: function(event) { + event.preventDefault(); + $.scrollTo(this.$('.add-xblock-component'), {duration: 250}); } }); diff --git a/cms/static/js/views/utils/create_course_utils.js b/cms/static/js/views/utils/create_course_utils.js index 1fc061a4dfed..68ee1c7a70bb 100644 --- a/cms/static/js/views/utils/create_course_utils.js +++ b/cms/static/js/views/utils/create_course_utils.js @@ -1,57 +1,17 @@ /** * Provides utilities for validating courses during creation, for both new courses and reruns. */ -define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"], - function ($, _, gettext, ViewUtils) { +define(["jquery", "gettext", "js/views/utils/view_utils", "js/views/utils/create_utils_base"], + function ($, gettext, ViewUtils, CreateUtilsFactory) { + "use strict"; return function (selectors, classes) { - var validateTotalCourseItemsLength, setNewCourseFieldInErr, hasInvalidRequiredFields, - createCourse, validateFilledFields, configureHandlers; + var keyLengthViolationMessage = gettext("The combined length of the organization, course number, and course run fields cannot be more than <%=limit%> characters."); + var keyFieldSelectors = [selectors.org, selectors.number, selectors.run]; + var nonEmptyCheckFieldSelectors = [selectors.name, selectors.org, selectors.number, selectors.run]; - var validateRequiredField = ViewUtils.validateRequiredField; - var validateURLItemEncoding = ViewUtils.validateURLItemEncoding; + CreateUtilsFactory.call(this, selectors, classes, keyLengthViolationMessage, keyFieldSelectors, nonEmptyCheckFieldSelectors); - var keyLengthViolationMessage = gettext('The combined length of the organization, course number, and course run fields cannot be more than <%=limit%> characters.'); - - // Ensure that org, course_num and run passes checkTotalKeyLengthViolations - validateTotalCourseItemsLength = function () { - ViewUtils.checkTotalKeyLengthViolations( - selectors, classes, - [selectors.org, selectors.number, selectors.run], - keyLengthViolationMessage - ); - }; - - setNewCourseFieldInErr = function (el, msg) { - if (msg) { - el.addClass(classes.error); - el.children(selectors.tipError).addClass(classes.showing).removeClass(classes.hiding).text(msg); - $(selectors.save).addClass(classes.disabled); - } - else { - el.removeClass(classes.error); - el.children(selectors.tipError).addClass(classes.hiding).removeClass(classes.showing); - // One "error" div is always present, but hidden or shown - if ($(selectors.error).length === 1) { - $(selectors.save).removeClass(classes.disabled); - } - } - }; - - // One final check for empty values - hasInvalidRequiredFields = function () { - return _.reduce( - [selectors.name, selectors.org, selectors.number, selectors.run], - function (acc, ele) { - var $ele = $(ele); - var error = validateRequiredField($ele.val()); - setNewCourseFieldInErr($ele.parent(), error); - return error ? true : acc; - }, - false - ); - }; - - createCourse = function (courseInfo, errorHandler) { + this.create = function (courseInfo, errorHandler) { $.postJSON( '/course/', courseInfo, @@ -64,59 +24,5 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"], } ); }; - - // Ensure that all fields are not empty - validateFilledFields = function () { - return _.reduce( - [selectors.org, selectors.number, selectors.run, selectors.name], - function (acc, ele) { - var $ele = $(ele); - return $ele.val().length !== 0 ? acc : false; - }, - true - ); - }; - - // Handle validation asynchronously - configureHandlers = function () { - _.each( - [selectors.org, selectors.number, selectors.run], - function (ele) { - var $ele = $(ele); - $ele.on('keyup', function (event) { - // Don't bother showing "required field" error when - // the user tabs into a new field; this is distracting - // and unnecessary - if (event.keyCode === 9) { - return; - } - var error = validateURLItemEncoding($ele.val(), $(selectors.allowUnicode).val() === 'True'); - setNewCourseFieldInErr($ele.parent(), error); - validateTotalCourseItemsLength(); - if (!validateFilledFields()) { - $(selectors.save).addClass(classes.disabled); - } - }); - } - ); - var $name = $(selectors.name); - $name.on('keyup', function () { - var error = validateRequiredField($name.val()); - setNewCourseFieldInErr($name.parent(), error); - validateTotalCourseItemsLength(); - if (!validateFilledFields()) { - $(selectors.save).addClass(classes.disabled); - } - }); - }; - - return { - validateTotalCourseItemsLength: validateTotalCourseItemsLength, - setNewCourseFieldInErr: setNewCourseFieldInErr, - hasInvalidRequiredFields: hasInvalidRequiredFields, - createCourse: createCourse, - validateFilledFields: validateFilledFields, - configureHandlers: configureHandlers - }; }; }); diff --git a/cms/static/js/views/utils/create_library_utils.js b/cms/static/js/views/utils/create_library_utils.js index b53f7c8b5398..9cda9580dae6 100644 --- a/cms/static/js/views/utils/create_library_utils.js +++ b/cms/static/js/views/utils/create_library_utils.js @@ -1,58 +1,17 @@ /** * Provides utilities for validating libraries during creation. */ -define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"], - function ($, _, gettext, ViewUtils) { +define(["jquery", "gettext", "js/views/utils/view_utils", "js/views/utils/create_utils_base"], + function ($, gettext, ViewUtils, CreateUtilsFactory) { "use strict"; return function (selectors, classes) { - var validateTotalKeyLength, setNewLibraryFieldInErr, hasInvalidRequiredFields, - createLibrary, validateFilledFields, configureHandlers; + var keyLengthViolationMessage = gettext("The combined length of the organization and library code fields cannot be more than <%=limit%> characters.") + var keyFieldSelectors = [selectors.org, selectors.number]; + var nonEmptyCheckFieldSelectors = [selectors.name, selectors.org, selectors.number]; - var validateRequiredField = ViewUtils.validateRequiredField; - var validateURLItemEncoding = ViewUtils.validateURLItemEncoding; + CreateUtilsFactory.call(this, selectors, classes, keyLengthViolationMessage, keyFieldSelectors, nonEmptyCheckFieldSelectors); - var keyLengthViolationMessage = gettext("The combined length of the organization and library code fields cannot be more than <%=limit%> characters."); - - // Ensure that org/librarycode passes validateTotalKeyLength check - validateTotalKeyLength = function () { - ViewUtils.checkTotalKeyLengthViolations( - selectors, classes, - [selectors.org, selectors.number], - keyLengthViolationMessage - ); - }; - - setNewLibraryFieldInErr = function (element, message) { - if (message) { - element.addClass(classes.error); - element.children(selectors.tipError).addClass(classes.showing).removeClass(classes.hiding).text(message); - $(selectors.save).addClass(classes.disabled); - } - else { - element.removeClass(classes.error); - element.children(selectors.tipError).addClass(classes.hiding).removeClass(classes.showing); - // One "error" div is always present, but hidden or shown - if ($(selectors.error).length === 1) { - $(selectors.save).removeClass(classes.disabled); - } - } - }; - - // One final check for empty values - hasInvalidRequiredFields = function () { - return _.reduce( - [selectors.name, selectors.org, selectors.number], - function (acc, element) { - var $element = $(element); - var error = validateRequiredField($element.val()); - setNewLibraryFieldInErr($element.parent(), error); - return error ? true : acc; - }, - false - ); - }; - - createLibrary = function (libraryInfo, errorHandler) { + this.create = function (libraryInfo, errorHandler) { $.postJSON( '/library/', libraryInfo @@ -70,60 +29,6 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"], } errorHandler(reason); }); - }; - - // Ensure that all fields are not empty - validateFilledFields = function () { - return _.reduce( - [selectors.org, selectors.number, selectors.name], - function (acc, element) { - var $element = $(element); - return $element.val().length !== 0 ? acc : false; - }, - true - ); - }; - - // Handle validation asynchronously - configureHandlers = function () { - _.each( - [selectors.org, selectors.number], - function (element) { - var $element = $(element); - $element.on('keyup', function (event) { - // Don't bother showing "required field" error when - // the user tabs into a new field; this is distracting - // and unnecessary - if (event.keyCode === $.ui.keyCode.TAB) { - return; - } - var error = validateURLItemEncoding($element.val(), $(selectors.allowUnicode).val() === 'True'); - setNewLibraryFieldInErr($element.parent(), error); - validateTotalKeyLength(); - if (!validateFilledFields()) { - $(selectors.save).addClass(classes.disabled); - } - }); - } - ); - var $name = $(selectors.name); - $name.on('keyup', function () { - var error = validateRequiredField($name.val()); - setNewLibraryFieldInErr($name.parent(), error); - validateTotalKeyLength(); - if (!validateFilledFields()) { - $(selectors.save).addClass(classes.disabled); - } - }); - }; - - return { - validateTotalKeyLength: validateTotalKeyLength, - setNewLibraryFieldInErr: setNewLibraryFieldInErr, - hasInvalidRequiredFields: hasInvalidRequiredFields, - createLibrary: createLibrary, - validateFilledFields: validateFilledFields, - configureHandlers: configureHandlers - }; + } }; }); diff --git a/cms/static/js/views/utils/create_utils_base.js b/cms/static/js/views/utils/create_utils_base.js new file mode 100644 index 000000000000..480587223b81 --- /dev/null +++ b/cms/static/js/views/utils/create_utils_base.js @@ -0,0 +1,123 @@ +/** + * Mixin class for creation of things like courses and libraries. + */ +define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"], + function ($, _, gettext, ViewUtils) { + return function (selectors, classes, keyLengthViolationMessage, keyFieldSelectors, nonEmptyCheckFieldSelectors) { + var self = this; + + this.selectors = selectors; + this.classes = classes; + this.validateRequiredField = ViewUtils.validateRequiredField; + this.validateURLItemEncoding = ViewUtils.validateURLItemEncoding; + this.keyLengthViolationMessage = keyLengthViolationMessage; + // Key fields for your model, like [selectors.org, selectors.number] + this.keyFieldSelectors = keyFieldSelectors; + // Fields that must not be empty on your model. + this.nonEmptyCheckFieldSelectors = nonEmptyCheckFieldSelectors; + + this.create = function (courseInfo, errorHandler) { + // Replace this with a function that will make a request to create the object. + }; + + // Ensure that key fields passes checkTotalKeyLengthViolations check + this.validateTotalKeyLength = function () { + ViewUtils.checkTotalKeyLengthViolations( + self.selectors, self.classes, + self.keyFieldSelectors, + self.keyLengthViolationMessage + ); + }; + + this.toggleSaveButton = function (is_enabled) { + var is_disabled = !is_enabled; + $(self.selectors.save).toggleClass(self.classes.disabled, is_disabled).attr('aria-disabled', is_disabled); + }; + + this.setFieldInErr = function (element, message) { + if (message) { + element.addClass(self.classes.error); + element.children(self.selectors.tipError).addClass(self.classes.showing).removeClass(self.classes.hiding).text(message); + self.toggleSaveButton(false); + } + else { + element.removeClass(self.classes.error); + element.children(self.selectors.tipError).addClass(self.classes.hiding).removeClass(self.classes.showing); + // One "error" div is always present, but hidden or shown + if ($(self.selectors.error).length === 1) { + self.toggleSaveButton(true); + } + } + }; + + // One final check for empty values + this.hasInvalidRequiredFields = function () { + return _.reduce( + self.nonEmptyCheckFieldSelectors, + function (acc, element) { + var $element = $(element); + var error = self.validateRequiredField($element.val()); + self.setFieldInErr($element.parent(), error); + return error ? true : acc; + }, + false + ); + }; + + // Ensure that all fields are not empty + this.validateFilledFields = function () { + return _.reduce( + self.nonEmptyCheckFieldSelectors, + function (acc, element) { + var $element = $(element); + return $element.val().length !== 0 ? acc : false; + }, + true + ); + }; + + // Handle validation asynchronously + this.configureHandlers = function () { + _.each( + self.keyFieldSelectors, + function (element) { + var $element = $(element); + $element.on('keyup', function (event) { + // Don't bother showing "required field" error when + // the user tabs into a new field; this is distracting + // and unnecessary + if (event.keyCode === $.ui.keyCode.TAB) { + return; + } + var error = self.validateURLItemEncoding($element.val(), $(self.selectors.allowUnicode).val() === 'True'); + self.setFieldInErr($element.parent(), error); + self.validateTotalKeyLength(); + if (!self.validateFilledFields()) { + self.toggleSaveButton(false); + } + }); + } + ); + + var $name = $(self.selectors.name); + $name.on('keyup', function () { + var error = self.validateRequiredField($name.val()); + self.setFieldInErr($name.parent(), error); + self.validateTotalKeyLength(); + if (!self.validateFilledFields()) { + self.toggleSaveButton(false); + } + }); + }; + + return { + validateTotalKeyLength: self.validateTotalKeyLength, + setFieldInErr: self.setFieldInErr, + hasInvalidRequiredFields: self.hasInvalidRequiredFields, + create: self.create, + validateFilledFields: self.validateFilledFields, + configureHandlers: self.configureHandlers + }; + } + } +); diff --git a/cms/static/sass/views/_dashboard.scss b/cms/static/sass/views/_dashboard.scss index 63f55a9c097e..fc4bf00dcd20 100644 --- a/cms/static/sass/views/_dashboard.scss +++ b/cms/static/sass/views/_dashboard.scss @@ -299,19 +299,21 @@ line-height: $baseline*2; margin: 0 10px; - &.active, &:hover { + &.active { border-bottom: 4px solid $blue; } + &.active, &:hover { + a { + color: $gray-d2; + } + } + a { color: $blue; cursor: pointer; display: inline-block; } - - &.active a { - color: $gray-d2; - } } } diff --git a/cms/templates/index.html b/cms/templates/index.html index cd2f8978aecb..12c60a0eb6f3 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -2,7 +2,7 @@ <%inherit file="base.html" /> <%def name="online_help_token()"><% return "home" %> -<%block name="title">${_("My Courses")} +<%block name="title">${_("Studio Home")} <%block name="bodyclass">is-signedin index view-dashboard <%block name="requirejs"> @@ -14,7 +14,7 @@ <%block name="content">
-

${_("My Courses")}

+

${_("Studio Home")}

% if user.is_active:
diff --git a/cms/templates/js/mock/mock-index-page.underscore b/cms/templates/js/mock/mock-index-page.underscore index 83f1bc509e26..8a149ece9d23 100644 --- a/cms/templates/js/mock/mock-index-page.underscore +++ b/cms/templates/js/mock/mock-index-page.underscore @@ -1,6 +1,6 @@
-

My Courses

+

Studio Home