diff --git a/cms/static/coffee/spec/views/module_edit_spec.coffee b/cms/static/coffee/spec/views/module_edit_spec.coffee index 93a7b476d5..2c5ac486fe 100644 --- a/cms/static/coffee/spec/views/module_edit_spec.coffee +++ b/cms/static/coffee/spec/views/module_edit_spec.coffee @@ -1,4 +1,6 @@ -define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], ($, edit_helpers, ModuleEdit, ModuleModel) -> +define ["jquery", "common/js/components/utils/view_utils", "js/spec_helpers/edit_helpers", + "coffee/src/views/module_edit", "js/models/module_info", "xmodule"], + ($, ViewUtils, edit_helpers, ModuleEdit, ModuleModel) -> describe "ModuleEdit", -> beforeEach -> @@ -60,7 +62,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit" spyOn(@moduleEdit, 'loadDisplay') spyOn(@moduleEdit, 'delegateEvents') spyOn($.fn, 'append') - spyOn($, 'getScript').andReturn($.Deferred().resolve().promise()) + spyOn(ViewUtils, 'loadJavaScript').andReturn($.Deferred().resolve().promise()); window.MockXBlock = (runtime, element) -> return { } @@ -150,7 +152,7 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit" expect($('head').append).toHaveBeenCalledWith("") it "loads js urls from fragments", -> - expect($.getScript).toHaveBeenCalledWith("js-url") + expect(ViewUtils.loadJavaScript).toHaveBeenCalledWith("js-url") it "loads head html", -> expect($('head').append).toHaveBeenCalledWith("head-html") diff --git a/cms/static/js/spec/views/xblock_spec.js b/cms/static/js/spec/views/xblock_spec.js index 6eb9753b63..755d7904a5 100644 --- a/cms/static/js/spec/views/xblock_spec.js +++ b/cms/static/js/spec/views/xblock_spec.js @@ -1,7 +1,7 @@ -define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xblock", "js/models/xblock_info", - "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"], - function ($, AjaxHelpers, URI, XBlockView, XBlockInfo) { - +define(["jquery", "URI", "common/js/spec_helpers/ajax_helpers", "common/js/components/utils/view_utils", + "js/views/xblock", "js/models/xblock_info", "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"], + function ($, URI, AjaxHelpers, ViewUtils, XBlockView, XBlockInfo) { + "use strict"; describe("XBlockView", function() { var model, xblockView, mockXBlockHtml; @@ -89,11 +89,11 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc it('aborts rendering when a dependent script fails to load', function() { var requests = AjaxHelpers.requests(this), - mockJavaScriptUrl = "mock.js", + missingJavaScriptUrl = "no_such_file.js", promise; - spyOn($, 'getScript').andReturn($.Deferred().reject().promise()); + spyOn(ViewUtils, 'loadJavaScript').andReturn($.Deferred().reject().promise()); promise = postXBlockRequest(requests, [ - ["hash5", { mimetype: "application/javascript", kind: "url", data: mockJavaScriptUrl }] + ["hash5", { mimetype: "application/javascript", kind: "url", data: missingJavaScriptUrl }] ]); expect(promise.isRejected()).toBe(true); }); @@ -104,7 +104,7 @@ define([ "jquery", "common/js/spec_helpers/ajax_helpers", "URI", "js/views/xbloc postXBlockRequest(AjaxHelpers.requests(this), []); xblockView.$el.find(".notification-action-button").click(); expect(notifySpy).toHaveBeenCalledWith("add-missing-groups", model.get("id")); - }) + }); }); }); }); diff --git a/cms/static/js/views/xblock.js b/cms/static/js/views/xblock.js index 797cc8cb45..9a8643f58b 100644 --- a/cms/static/js/views/xblock.js +++ b/cms/static/js/views/xblock.js @@ -1,5 +1,6 @@ -define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], - function ($, _, BaseView, XBlock) { +define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/views/baseview", "xblock/runtime.v1"], + function ($, _, ViewUtils, BaseView, XBlock) { + 'use strict'; var XBlockView = BaseView.extend({ // takes XBlockInfo as a model @@ -83,7 +84,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], * may have thrown JavaScript errors after rendering in which case the xblock parameter * will be null. */ - xblockReady: function(xblock) { + xblockReady: function(xblock) { // jshint ignore:line // Do nothing }, @@ -95,7 +96,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], * represents this process. * @param fragment The fragment returned from the xblock_handler * @param element The element into which to render the fragment (defaults to this.$el) - * @returns {jQuery promise} A promise representing the rendering process + * @returns {Promise} A promise representing the rendering process */ renderXBlockFragment: function(fragment, element) { var html = fragment.html, @@ -131,7 +132,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], * Dynamically loads all of an XBlock's dependent resources. This is an asynchronous * process so a promise is returned. * @param resources The resources to be rendered - * @returns {jQuery promise} A promise representing the rendering process + * @returns {Promise} A promise representing the rendering process */ addXBlockFragmentResources: function(resources) { var self = this, @@ -171,7 +172,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], /** * Loads the specified resource into the page. * @param resource The resource to be loaded. - * @returns {jQuery promise} A promise representing the loading of the resource. + * @returns {Promise} A promise representing the loading of the resource. */ loadResource: function(resource) { var head = $('head'), @@ -189,8 +190,7 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], if (kind === "text") { head.append(""); } else if (kind === "url") { - // Return a promise for the script resolution - return $.getScript(data); + return ViewUtils.loadJavaScript(data); } } else if (mimetype === "text/html") { if (placement === "head") { @@ -202,11 +202,11 @@ define(["jquery", "underscore", "js/views/baseview", "xblock/runtime.v1"], }, fireNotificationActionEvent: function(event) { - var eventName = $(event.currentTarget).data("notification-action"); - if (eventName) { - event.preventDefault(); - this.notifyRuntime(eventName, this.model.get("id")); - } + var eventName = $(event.currentTarget).data("notification-action"); + if (eventName) { + event.preventDefault(); + this.notifyRuntime(eventName, this.model.get("id")); + } } }); diff --git a/common/static/common/js/components/utils/view_utils.js b/common/static/common/js/components/utils/view_utils.js index c99625d9ed..60226adaae 100644 --- a/common/static/common/js/components/utils/view_utils.js +++ b/common/static/common/js/components/utils/view_utils.js @@ -1,272 +1,294 @@ /** * Provides useful utilities for views. */ -;(function (define) { +;(function (define, require) { 'use strict'; + define(["jquery", "underscore", "gettext", "common/js/components/views/feedback_notification", - "common/js/components/views/feedback_prompt"], - function ($, _, gettext, NotificationView, PromptView) { - var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation, - runOperationShowingMessage, withDisabledElement, disableElementWhileRunning, - getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes, deleteNotificationHandler, - validateRequiredField, validateURLItemEncoding, validateTotalKeyLength, checkTotalKeyLengthViolations; + "common/js/components/views/feedback_prompt"], + function ($, _, gettext, NotificationView, PromptView) { + var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation, + runOperationShowingMessage, withDisabledElement, disableElementWhileRunning, + getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes, + deleteNotificationHandler, validateRequiredField, validateURLItemEncoding, + validateTotalKeyLength, checkTotalKeyLengthViolations, loadJavaScript; - // see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65 - var MAX_SUM_KEY_LENGTH = 65; + // see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65 + var MAX_SUM_KEY_LENGTH = 65; - /** - * Toggles the expanded state of the current element. - */ - toggleExpandCollapse = function(target, collapsedClass) { - // Support the old 'collapsed' option until fully switched over to is-collapsed - if (!collapsedClass) { - collapsedClass = 'collapsed'; - } - target.closest('.expand-collapse').toggleClass('expand collapse'); - target.closest('.is-collapsible, .window').toggleClass(collapsedClass); - target.closest('.is-collapsible').children('article').slideToggle(); - }; + /** + * Toggles the expanded state of the current element. + */ + toggleExpandCollapse = function(target, collapsedClass) { + // Support the old 'collapsed' option until fully switched over to is-collapsed + if (!collapsedClass) { + collapsedClass = 'collapsed'; + } + target.closest('.expand-collapse').toggleClass('expand collapse'); + target.closest('.is-collapsible, .window').toggleClass(collapsedClass); + target.closest('.is-collapsible').children('article').slideToggle(); + }; - /** - * Show the page's loading indicator. - */ - showLoadingIndicator = function() { - $('.ui-loading').show(); - }; + /** + * Show the page's loading indicator. + */ + showLoadingIndicator = function() { + $('.ui-loading').show(); + }; - /** - * Hide the page's loading indicator. - */ - hideLoadingIndicator = function() { - $('.ui-loading').hide(); - }; + /** + * Hide the page's loading indicator. + */ + hideLoadingIndicator = function() { + $('.ui-loading').hide(); + }; - /** - * Confirms with the user whether to run an operation or not, and then runs it if desired. - */ - confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) { - return new PromptView.Warning({ - title: title, - message: message, - actions: { - primary: { - text: actionLabel, - click: function(prompt) { - prompt.hide(); - operation(); - } - }, - secondary: { - text: gettext('Cancel'), - click: function(prompt) { - if (onCancelCallback) { - onCancelCallback(); + /** + * Confirms with the user whether to run an operation or not, and then runs it if desired. + */ + confirmThenRunOperation = function(title, message, actionLabel, operation, onCancelCallback) { + return new PromptView.Warning({ + title: title, + message: message, + actions: { + primary: { + text: actionLabel, + click: function(prompt) { + prompt.hide(); + operation(); + } + }, + secondary: { + text: gettext('Cancel'), + click: function(prompt) { + if (onCancelCallback) { + onCancelCallback(); + } + return prompt.hide(); } - return prompt.hide(); } } + }).show(); + }; + + /** + * Shows a progress message for the duration of an asynchronous operation. + * Note: this does not remove the notification upon failure because an error + * will be shown that shouldn't be removed. + * @param message The message to show. + * @param operation A function that returns a promise representing the operation. + */ + runOperationShowingMessage = function(message, operation) { + var notificationView; + notificationView = new NotificationView.Mini({ + title: gettext(message) + }); + notificationView.show(); + return operation().done(function() { + notificationView.hide(); + }); + }; + + /** + * Wraps a Backbone event callback to disable the event's target element. + * + * This paradigm is designed to be used in Backbone event maps where + * multiple events firing simultaneously is not desired. + * + * @param functionName the function to execute, as a string. + * The function must return a jQuery promise and be able to take an event + */ + withDisabledElement = function(functionName) { + return function(event) { + var view = this; + disableElementWhileRunning($(event.currentTarget), function() { + //call view.functionName(event), with view as the current this + return view[functionName].apply(view, [event]); + }); + }; + }; + + /** + * Disables a given element when a given operation is running. + * @param {jQuery} element the element to be disabled. + * @param operation the operation during whose duration the + * element should be disabled. The operation should return + * a JQuery promise. + */ + disableElementWhileRunning = function(element, operation) { + element.addClass("is-disabled").attr('aria-disabled', true); + return operation().always(function() { + element.removeClass("is-disabled").attr('aria-disabled', false); + }); + }; + + /** + * Returns a handler that removes a notification, both dismissing it and deleting it from the database. + * @param callback function to call when deletion succeeds + */ + deleteNotificationHandler = function(callback) { + return function (event) { + event.preventDefault(); + $.ajax({ + url: $(this).data('dismiss-link'), + type: 'DELETE', + success: callback + }); + }; + }; + + /** + * Performs an animated scroll so that the window has the specified scroll top. + * @param scrollTop The desired scroll top for the window. + */ + setScrollTop = function(scrollTop) { + $('html, body').animate({ + scrollTop: scrollTop + }, 500); + }; + + /** + * Returns the relative position that the element is scrolled from the top of the view port. + * @param element The element in question. + */ + getScrollOffset = function(element) { + var elementTop = element.offset().top; + return elementTop - $(window).scrollTop(); + }; + + /** + * Scrolls the window so that the element is scrolled down to the specified relative position + * from the top of the view port. + * @param element The element in question. + * @param offset The amount by which the element should be scrolled from the top of the view port. + */ + setScrollOffset = function(element, offset) { + var elementTop = element.offset().top, + newScrollTop = elementTop - offset; + setScrollTop(newScrollTop); + }; + + /** + * Redirects to the specified URL. This is broken out as its own function for unit testing. + */ + redirect = function(url) { + window.location = url; + }; + + /** + * Reloads the page. This is broken out as its own function for unit testing. + */ + reload = function() { + window.location.reload(); + }; + + /** + * Returns true if a model has changes to at least one of the specified attributes. + * @param model The model in question. + * @param attributes The list of attributes to be compared. + * @returns {boolean} Returns true if attribute changes are found. + */ + hasChangedAttributes = function(model, attributes) { + var i, changedAttributes = model.changedAttributes(); + if (!changedAttributes) { + return false; + } + for (i=0; i < attributes.length; i++) { + if (_.has(changedAttributes, attributes[i])) { + return true; + } } - }).show(); - }; - - /** - * Shows a progress message for the duration of an asynchronous operation. - * Note: this does not remove the notification upon failure because an error - * will be shown that shouldn't be removed. - * @param message The message to show. - * @param operation A function that returns a promise representing the operation. - */ - runOperationShowingMessage = function(message, operation) { - var notificationView; - notificationView = new NotificationView.Mini({ - title: gettext(message) - }); - notificationView.show(); - return operation().done(function() { - notificationView.hide(); - }); - }; - - /** - * Wraps a Backbone event callback to disable the event's target element. - * - * This paradigm is designed to be used in Backbone event maps where - * multiple events firing simultaneously is not desired. - * - * @param functionName the function to execute, as a string. - * The function must return a jQuery promise and be able to take an event - */ - withDisabledElement = function(functionName) { - return function(event) { - var view = this; - disableElementWhileRunning($(event.currentTarget), function() { - //call view.functionName(event), with view as the current this - return view[functionName].apply(view, [event]); - }); - }; - }; - - /** - * Disables a given element when a given operation is running. - * @param {jQuery} element the element to be disabled. - * @param operation the operation during whose duration the - * element should be disabled. The operation should return - * a JQuery promise. - */ - disableElementWhileRunning = function(element, operation) { - element.addClass("is-disabled").attr('aria-disabled', true); - return operation().always(function() { - element.removeClass("is-disabled").attr('aria-disabled', false); - }); - }; - - /** - * Returns a handler that removes a notification, both dismissing it and deleting it from the database. - * @param callback function to call when deletion succeeds - */ - deleteNotificationHandler = function(callback) { - return function (event) { - event.preventDefault(); - $.ajax({ - url: $(this).data('dismiss-link'), - type: 'DELETE', - success: callback - }); - }; - }; - - /** - * Performs an animated scroll so that the window has the specified scroll top. - * @param scrollTop The desired scroll top for the window. - */ - setScrollTop = function(scrollTop) { - $('html, body').animate({ - scrollTop: scrollTop - }, 500); - }; - - /** - * Returns the relative position that the element is scrolled from the top of the view port. - * @param element The element in question. - */ - getScrollOffset = function(element) { - var elementTop = element.offset().top; - return elementTop - $(window).scrollTop(); - }; - - /** - * Scrolls the window so that the element is scrolled down to the specified relative position - * from the top of the view port. - * @param element The element in question. - * @param offset The amount by which the element should be scrolled from the top of the view port. - */ - setScrollOffset = function(element, offset) { - var elementTop = element.offset().top, - newScrollTop = elementTop - offset; - setScrollTop(newScrollTop); - }; - - /** - * Redirects to the specified URL. This is broken out as its own function for unit testing. - */ - redirect = function(url) { - window.location = url; - }; - - /** - * Reloads the page. This is broken out as its own function for unit testing. - */ - reload = function() { - window.location.reload(); - }; - - /** - * Returns true if a model has changes to at least one of the specified attributes. - * @param model The model in question. - * @param attributes The list of attributes to be compared. - * @returns {boolean} Returns true if attribute changes are found. - */ - hasChangedAttributes = function(model, attributes) { - var i, changedAttributes = model.changedAttributes(); - if (!changedAttributes) { return false; - } - for (i=0; i < attributes.length; i++) { - if (_.has(changedAttributes, attributes[i])) { - return true; + }; + + /** + * Helper method for course/library creation - verifies a required field is not blank. + */ + validateRequiredField = function (msg) { + return msg.length === 0 ? gettext('Required field.') : ''; + }; + + /** + * Helper method for course/library creation. + * Check that a course (org, number, run) doesn't use any special characters + */ + validateURLItemEncoding = function (item, allowUnicode) { + var required = validateRequiredField(item); + if (required) { + return required; } - } - return false; - }; - - /** - * Helper method for course/library creation - verifies a required field is not blank. - */ - validateRequiredField = function (msg) { - return msg.length === 0 ? gettext('Required field.') : ''; - }; - - /** - * Helper method for course/library creation. - * Check that a course (org, number, run) doesn't use any special characters - */ - validateURLItemEncoding = function (item, allowUnicode) { - var required = validateRequiredField(item); - if (required) { - return required; - } - if (allowUnicode) { - if (/\s/g.test(item)) { - return gettext('Please do not use any spaces in this field.'); + if (allowUnicode) { + if (/\s/g.test(item)) { + return gettext('Please do not use any spaces in this field.'); + } } - } - else { - if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) { - return gettext('Please do not use any spaces or special characters in this field.'); + else { + if (item !== encodeURIComponent(item) || item.match(/[!'()*]/)) { + return gettext('Please do not use any spaces or special characters in this field.'); + } } - } - return ''; - }; + return ''; + }; - // Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars. - validateTotalKeyLength = function (key_field_selectors) { - var totalLength = _.reduce( - key_field_selectors, - function (sum, ele) { return sum + $(ele).val().length;}, - 0 - ); - return totalLength <= MAX_SUM_KEY_LENGTH; - }; + // Ensure that sum length of key field values <= ${MAX_SUM_KEY_LENGTH} chars. + validateTotalKeyLength = function (key_field_selectors) { + var totalLength = _.reduce( + key_field_selectors, + function (sum, ele) { return sum + $(ele).val().length;}, + 0 + ); + return totalLength <= MAX_SUM_KEY_LENGTH; + }; - checkTotalKeyLengthViolations = function(selectors, classes, key_field_selectors, message_tpl) { - if (!validateTotalKeyLength(key_field_selectors)) { - $(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding); - $(selectors.errorMessage).html('

' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '

'); - $(selectors.save).addClass(classes.disabled); - } else { - $(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding); - } - }; + checkTotalKeyLengthViolations = function(selectors, classes, key_field_selectors, message_tpl) { + if (!validateTotalKeyLength(key_field_selectors)) { + $(selectors.errorWrapper).addClass(classes.shown).removeClass(classes.hiding); + $(selectors.errorMessage).html( + '

' + _.template(message_tpl, {limit: MAX_SUM_KEY_LENGTH}) + '

' + ); + $(selectors.save).addClass(classes.disabled); + } else { + $(selectors.errorWrapper).removeClass(classes.shown).addClass(classes.hiding); + } + }; - return { - 'toggleExpandCollapse': toggleExpandCollapse, - 'showLoadingIndicator': showLoadingIndicator, - 'hideLoadingIndicator': hideLoadingIndicator, - 'confirmThenRunOperation': confirmThenRunOperation, - 'runOperationShowingMessage': runOperationShowingMessage, - 'withDisabledElement': withDisabledElement, - 'disableElementWhileRunning': disableElementWhileRunning, - 'deleteNotificationHandler': deleteNotificationHandler, - 'setScrollTop': setScrollTop, - 'getScrollOffset': getScrollOffset, - 'setScrollOffset': setScrollOffset, - 'redirect': redirect, - 'reload': reload, - 'hasChangedAttributes': hasChangedAttributes, - 'validateRequiredField': validateRequiredField, - 'validateURLItemEncoding': validateURLItemEncoding, - 'validateTotalKeyLength': validateTotalKeyLength, - 'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations - }; - }); -}).call(this, define || RequireJS.define); + /** + * Dynamically loads the specified JavaScript file. + * @param url The URL to a JavaScript file. + * @returns {Promise} A promise indicating when the URL has been loaded. + */ + loadJavaScript = function(url) { + var deferred = $.Deferred(); + require([url], + function() { + deferred.resolve(); + }, + function() { + deferred.reject(); + }); + return deferred.promise(); + }; + + return { + 'toggleExpandCollapse': toggleExpandCollapse, + 'showLoadingIndicator': showLoadingIndicator, + 'hideLoadingIndicator': hideLoadingIndicator, + 'confirmThenRunOperation': confirmThenRunOperation, + 'runOperationShowingMessage': runOperationShowingMessage, + 'withDisabledElement': withDisabledElement, + 'disableElementWhileRunning': disableElementWhileRunning, + 'deleteNotificationHandler': deleteNotificationHandler, + 'setScrollTop': setScrollTop, + 'getScrollOffset': getScrollOffset, + 'setScrollOffset': setScrollOffset, + 'redirect': redirect, + 'reload': reload, + 'hasChangedAttributes': hasChangedAttributes, + 'validateRequiredField': validateRequiredField, + 'validateURLItemEncoding': validateURLItemEncoding, + 'validateTotalKeyLength': validateTotalKeyLength, + 'checkTotalKeyLengthViolations': checkTotalKeyLengthViolations, + 'loadJavaScript': loadJavaScript + }; + }); +}).call(this, define || RequireJS.define, require || RequireJS.require); diff --git a/common/static/common/js/spec/components/view_utils_spec.js b/common/static/common/js/spec/components/view_utils_spec.js index 806a37e7c3..6ac85efe88 100644 --- a/common/static/common/js/spec/components/view_utils_spec.js +++ b/common/static/common/js/spec/components/view_utils_spec.js @@ -1,118 +1,119 @@ ;(function (define) { 'use strict'; -define(["jquery", "underscore", "common/js/components/utils/view_utils", "common/js/spec_helpers/view_helpers", 'jasmine-stealth'], - function ($, _, ViewUtils, ViewHelpers) { + define(["jquery", "underscore", "backbone", "common/js/components/utils/view_utils", + "common/js/spec_helpers/view_helpers", "jasmine-stealth"], + function ($, _, Backbone, ViewUtils, ViewHelpers) { - describe("ViewUtils", function() { - describe("disabled element while running", function() { - it("adds 'is-disabled' class to element while action is running and removes it after", function() { - var link, - deferred = new $.Deferred(), - promise = deferred.promise(); - setFixtures("ripe apples drop about my head"); - link = $("#link"); - expect(link).not.toHaveClass("is-disabled"); - ViewUtils.disableElementWhileRunning(link, function() { return promise; }); - expect(link).toHaveClass("is-disabled"); - deferred.resolve(); - expect(link).not.toHaveClass("is-disabled"); - }); - - it("uses withDisabledElement wrapper to disable element while running a Backbone event handler", function() { - var link, - eventCallback, - event, - deferred = new $.Deferred(), - promise = deferred.promise(), - MockView = Backbone.View.extend({ - testFunction: function() { - return promise; - } - }), - testView = new MockView(); - setFixtures("ripe apples drop about my head"); - link = $("#link"); - expect(link).not.toHaveClass("is-disabled"); - eventCallback = ViewUtils.withDisabledElement('testFunction'); - event = {currentTarget: link}; - eventCallback.apply(testView, [event]); - expect(link).toHaveClass("is-disabled"); - deferred.resolve(); - expect(link).not.toHaveClass("is-disabled"); - }); - }); - - describe("progress notification", function() { - it("shows progress notification and removes it upon success", function() { - var testMessage = "Testing...", - deferred = new $.Deferred(), - promise = deferred.promise(), - notificationSpy = ViewHelpers.createNotificationSpy(); - ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; }); - ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); - deferred.resolve(); - ViewHelpers.verifyNotificationHidden(notificationSpy); - }); - - it("shows progress notification and leaves it showing upon failure", function() { - var testMessage = "Testing...", - deferred = new $.Deferred(), - promise = deferred.promise(), - notificationSpy = ViewHelpers.createNotificationSpy(); - ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; }); - ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); - deferred.fail(); - ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); - }); - }); - - describe("course/library fields validation", function() { - describe("without unicode support", function() { - it("validates presence of field", function() { - var error = ViewUtils.validateURLItemEncoding('', false); - expect(error).toBeTruthy(); + describe("ViewUtils", function() { + describe("disabled element while running", function() { + it("adds 'is-disabled' class to element while action is running and removes it after", function() { + var link, + deferred = new $.Deferred(), + promise = deferred.promise(); + setFixtures("ripe apples drop about my head"); + link = $("#link"); + expect(link).not.toHaveClass("is-disabled"); + ViewUtils.disableElementWhileRunning(link, function() { return promise; }); + expect(link).toHaveClass("is-disabled"); + deferred.resolve(); + expect(link).not.toHaveClass("is-disabled"); }); - it("checks for presence of special characters in the field", function() { - var error; - // Special characters are not allowed. - error = ViewUtils.validateURLItemEncoding('my+field', false); - expect(error).toBeTruthy(); - error = ViewUtils.validateURLItemEncoding('2014!', false); - expect(error).toBeTruthy(); - error = ViewUtils.validateURLItemEncoding('*field*', false); - expect(error).toBeTruthy(); - // Spaces not allowed. - error = ViewUtils.validateURLItemEncoding('Jan 2014', false); - expect(error).toBeTruthy(); - // -_~. are allowed. - error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false); - expect(error).toBeFalsy(); - }); - - it("does not allow unicode characters", function() { - var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false); - expect(error).toBeTruthy(); + it("disables elements within withDisabledElement", function() { + var link, + eventCallback, + event, + deferred = new $.Deferred(), + promise = deferred.promise(), + MockView = Backbone.View.extend({ + testFunction: function() { + return promise; + } + }), + testView = new MockView(); + setFixtures("ripe apples drop about my head"); + link = $("#link"); + expect(link).not.toHaveClass("is-disabled"); + eventCallback = ViewUtils.withDisabledElement('testFunction'); + event = {currentTarget: link}; + eventCallback.apply(testView, [event]); + expect(link).toHaveClass("is-disabled"); + deferred.resolve(); + expect(link).not.toHaveClass("is-disabled"); }); }); - describe("with unicode support", function() { - it("validates presence of field", function() { - var error = ViewUtils.validateURLItemEncoding('', true); - expect(error).toBeTruthy(); + describe("progress notification", function() { + it("shows progress notification and removes it upon success", function() { + var testMessage = "Testing...", + deferred = new $.Deferred(), + promise = deferred.promise(), + notificationSpy = ViewHelpers.createNotificationSpy(); + ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; }); + ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); + deferred.resolve(); + ViewHelpers.verifyNotificationHidden(notificationSpy); }); - it("checks for presence of spaces", function() { - var error = ViewUtils.validateURLItemEncoding('My Field', true); - expect(error).toBeTruthy(); + it("shows progress notification and leaves it showing upon failure", function() { + var testMessage = "Testing...", + deferred = new $.Deferred(), + promise = deferred.promise(), + notificationSpy = ViewHelpers.createNotificationSpy(); + ViewUtils.runOperationShowingMessage(testMessage, function() { return promise; }); + ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); + deferred.fail(); + ViewHelpers.verifyNotificationShowing(notificationSpy, /Testing/); + }); + }); + + describe("course/library fields validation", function() { + describe("without unicode support", function() { + it("validates presence of field", function() { + var error = ViewUtils.validateURLItemEncoding('', false); + expect(error).toBeTruthy(); + }); + + it("checks for presence of special characters in the field", function() { + var error; + // Special characters are not allowed. + error = ViewUtils.validateURLItemEncoding('my+field', false); + expect(error).toBeTruthy(); + error = ViewUtils.validateURLItemEncoding('2014!', false); + expect(error).toBeTruthy(); + error = ViewUtils.validateURLItemEncoding('*field*', false); + expect(error).toBeTruthy(); + // Spaces not allowed. + error = ViewUtils.validateURLItemEncoding('Jan 2014', false); + expect(error).toBeTruthy(); + // -_~. are allowed. + error = ViewUtils.validateURLItemEncoding('2015-Math_X1.0~', false); + expect(error).toBeFalsy(); + }); + + it("does not allow unicode characters", function() { + var error = ViewUtils.validateURLItemEncoding('Field-\u010d', false); + expect(error).toBeTruthy(); + }); }); - it("allows unicode characters", function() { - var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true); - expect(error).toBeFalsy(); + describe("with unicode support", function() { + it("validates presence of field", function() { + var error = ViewUtils.validateURLItemEncoding('', true); + expect(error).toBeTruthy(); + }); + + it("checks for presence of spaces", function() { + var error = ViewUtils.validateURLItemEncoding('My Field', true); + expect(error).toBeTruthy(); + }); + + it("allows unicode characters", function() { + var error = ViewUtils.validateURLItemEncoding('Field-\u010d', true); + expect(error).toBeFalsy(); + }); }); }); }); }); - }); }).call(this, define || RequireJS.define);