diff --git a/common/static/common/js/components/utils/view_utils.js b/common/static/common/js/components/utils/view_utils.js index 43761b2cb0..c99625d9ed 100644 --- a/common/static/common/js/components/utils/view_utils.js +++ b/common/static/common/js/components/utils/view_utils.js @@ -7,8 +7,8 @@ "common/js/components/views/feedback_prompt"], function ($, _, gettext, NotificationView, PromptView) { var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation, - runOperationShowingMessage, disableElementWhileRunning, getScrollOffset, setScrollOffset, - setScrollTop, redirect, reload, hasChangedAttributes, deleteNotificationHandler, + runOperationShowingMessage, withDisabledElement, disableElementWhileRunning, + getScrollOffset, setScrollOffset, setScrollTop, redirect, reload, hasChangedAttributes, deleteNotificationHandler, validateRequiredField, validateURLItemEncoding, validateTotalKeyLength, checkTotalKeyLengthViolations; // see https://openedx.atlassian.net/browse/TNL-889 for what is it and why it's 65 @@ -87,6 +87,25 @@ }); }; + /** + * 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. @@ -235,6 +254,7 @@ 'hideLoadingIndicator': hideLoadingIndicator, 'confirmThenRunOperation': confirmThenRunOperation, 'runOperationShowingMessage': runOperationShowingMessage, + 'withDisabledElement': withDisabledElement, 'disableElementWhileRunning': disableElementWhileRunning, 'deleteNotificationHandler': deleteNotificationHandler, 'setScrollTop': setScrollTop, 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 0aaae5148c..806a37e7c3 100644 --- a/common/static/common/js/spec/components/view_utils_spec.js +++ b/common/static/common/js/spec/components/view_utils_spec.js @@ -17,6 +17,29 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "common 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() { @@ -92,4 +115,4 @@ define(["jquery", "underscore", "common/js/components/utils/view_utils", "common }); }); }); -}).call(this, define || RequireJS.define); \ No newline at end of file +}).call(this, define || RequireJS.define); diff --git a/lms/djangoapps/teams/static/teams/js/views/edit_team.js b/lms/djangoapps/teams/static/teams/js/views/edit_team.js index fc6ca4616e..ac30f504a1 100644 --- a/lms/djangoapps/teams/static/teams/js/views/edit_team.js +++ b/lms/djangoapps/teams/static/teams/js/views/edit_team.js @@ -6,16 +6,17 @@ 'gettext', 'js/views/fields', 'teams/js/models/team', + 'common/js/components/utils/view_utils', 'text!teams/templates/edit-team.underscore'], - function (Backbone, _, gettext, FieldViews, TeamModel, editTeamTemplate) { + function (Backbone, _, gettext, FieldViews, TeamModel, ViewUtils, editTeamTemplate) { return Backbone.View.extend({ maxTeamNameLength: 255, maxTeamDescriptionLength: 300, events: { - 'click .action-primary': 'createOrUpdateTeam', - 'submit form': 'createOrUpdateTeam', + 'click .action-primary': ViewUtils.withDisabledElement('createOrUpdateTeam'), + 'submit form': ViewUtils.withDisabledElement('createOrUpdateTeam'), 'click .action-cancel': 'cancelAndGoBack' }, @@ -124,10 +125,9 @@ var validationResult = this.validateTeamData(data); if (validationResult.status === false) { this.showMessage(validationResult.message, validationResult.srMessage); - return; + return $().promise(); } - - this.teamModel.save(data, saveOptions) + return view.teamModel.save(data, saveOptions) .done(function(result) { view.teamEvents.trigger('teams:update', { action: view.action,