diff --git a/resources/static/css/common.css b/resources/static/css/common.css index 6a96e10d20677178cff948c6ddf6cc00131917fe..0a8a061ecd70d2dbf991c3bd1eefdf92af22c4e2 100644 --- a/resources/static/css/common.css +++ b/resources/static/css/common.css @@ -195,13 +195,16 @@ button::-moz-focus-inner, .button::-moz-focus-inner { border: 0 } -button[disabled] { - -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; - opacity: .5; +button[disabled], .submit_disabled button, .submit_disabled .button, +.submit_disabled button:focus, .submit_disabled .button:focus, +.submit_disabled button:active, .submit_disabled .button:active { + background-color: #37A6FF; + background-image: -moz-linear-gradient(center top , #76C2FF 0pt, #37A6FF 100%); + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #76C2FF), color-stop(100%, #37A6FF)); + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; + opacity: .5; } - - hr { height: 1px; border: none; diff --git a/resources/static/dialog/controllers/authenticate.js b/resources/static/dialog/controllers/authenticate.js index 997a248b02a9fe02462752dccd5b7feb87d6b00f..b4733b69c5ea18ce5a9e406a9c49b6c69a06446a 100644 --- a/resources/static/dialog/controllers/authenticate.js +++ b/resources/static/dialog/controllers/authenticate.js @@ -14,7 +14,7 @@ BrowserID.Modules.Authenticate = (function() { tooltip = bid.Tooltip, helpers = bid.Helpers, dialogHelpers = helpers.Dialog, - cancelEvent = dialogHelpers.cancelEvent, + cancelEvent = helpers.cancelEvent, dom = bid.DOM, lastEmail = ""; diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js index 3191825e4e3ea07268e7171ae192087748a6d18c..bcc103b82e079bfc5fb66e988065306f60ddf6ae 100644 --- a/resources/static/dialog/resources/helpers.js +++ b/resources/static/dialog/resources/helpers.js @@ -125,13 +125,6 @@ } } - function cancelEvent(callback) { - return function(event) { - event && event.preventDefault(); - callback.call(this); - }; - } - helpers.Dialog = helpers.Dialog || {}; helpers.extend(helpers.Dialog, { @@ -140,7 +133,7 @@ createUser: createUser, addEmail: addEmail, resetPassword: resetPassword, - cancelEvent: cancelEvent + cancelEvent: helpers.cancelEvent }); }()); diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js index 5da6f82361decb6c47c57838282f9050c46c9d37..9566d978bfac61c2b51c2c305b3434902b7ce9bd 100644 --- a/resources/static/dialog/start.js +++ b/resources/static/dialog/start.js @@ -32,9 +32,11 @@ moduleManager.register("primary_user_provisioned", modules.PrimaryUserProvisioned); moduleManager.register("email_chosen", modules.EmailChosen); moduleManager.register("xhr_delay", modules.XHRDelay); + moduleManager.register("xhr_disable_form", modules.XHRDisableForm); moduleManager.start("xhr_delay"); + moduleManager.start("xhr_disable_form"); moduleManager.start("dialog"); } } diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js index 638778590e6bf9ad5924f32ff7ab5655a9daa7be..b15cf2eb8002c985150dd4f92af6e76628756511 100644 --- a/resources/static/pages/page_helpers.js +++ b/resources/static/pages/page_helpers.js @@ -130,13 +130,6 @@ BrowserID.PageHelpers = (function() { dom.focus("input:visible:eq(0)"); } - function cancelEvent(callback) { - return function(event) { - event && event.preventDefault(); - callback && callback(); - }; - } - function openPrimaryAuth(winchan, email, baseURL, callback) { if(!(email && baseURL)) { throw "cannot verify with primary without an email address and URL" @@ -195,7 +188,7 @@ BrowserID.PageHelpers = (function() { emailSent: emailSent, cancelEmailSent: cancelEmailSent, userValidationComplete: userValidationComplete, - cancelEvent: cancelEvent, + cancelEvent: helpers.cancelEvent, openPrimaryAuth: openPrimaryAuth }; }()); diff --git a/resources/static/pages/start.js b/resources/static/pages/start.js index 132ce6e894e6b7765b1528ddda4646aee94b6ca2..d64a447b60a46c19eccacc0ee43ba8a4ba624369 100644 --- a/resources/static/pages/start.js +++ b/resources/static/pages/start.js @@ -16,11 +16,14 @@ $(function() { user = bid.User, token = pageHelpers.getParameterByName("token"), path = document.location.pathname, - XHRDelay = bid.Modules.XHRDelay; + XHRDelay = bid.Modules.XHRDelay, + XHRDisableForm = bid.Modules.XHRDisableForm; network.init({ time_until_delay: 10 * 1000 }); var xhrDelay = XHRDelay.create({}); xhrDelay.start(); + var xhrDisableForm = XHRDisableForm.create({}); + xhrDisableForm.start(); if (!path || path === "/") { bid.index(); diff --git a/resources/static/shared/helpers.js b/resources/static/shared/helpers.js index 1a94fa11ad939d4e3fa5d18a89f8c8ec70ed68f5..edbb0e5b36a2914b2e4a3542baca49fac78cf4e2 100644 --- a/resources/static/shared/helpers.js +++ b/resources/static/shared/helpers.js @@ -132,6 +132,12 @@ return dObj; } + function cancelEvent(callback) { + return function(event) { + event && event.preventDefault(); + callback.call(this); + }; + } extend(helpers, { /** @@ -175,8 +181,14 @@ * @param {Date} date * @returns {string} date relative to now. */ - relativeDate: relativeDate - + relativeDate: relativeDate, + /** + * Return a function that calls preventDefault on the event and then calls + * the callback with the arguments. + * @method cancelEvent + * @param {function} function to call after the event is cancelled. + */ + cancelEvent: cancelEvent }); diff --git a/resources/static/shared/modules/page_module.js b/resources/static/shared/modules/page_module.js index b702f6163dbf07d0970ec95d607328eb455876d3..ecefd075190ea4e251e8e438a5e4bb1e2cd1e56a 100644 --- a/resources/static/shared/modules/page_module.js +++ b/resources/static/shared/modules/page_module.js @@ -11,13 +11,12 @@ BrowserID.Modules.PageModule = (function() { bid = BrowserID, dom = bid.DOM, screens = bid.Screens, + helpers = bid.Helpers, + cancelEvent = helpers.cancelEvent, mediator = bid.Mediator; - function onSubmit(event) { - event.stopPropagation(); - event.preventDefault(); - - if (this.validate()) { + function onSubmit() { + if (!dom.hasClass("body", "submit_disabled") && this.validate()) { this.submit(); } return false; @@ -55,8 +54,8 @@ BrowserID.Modules.PageModule = (function() { start: function(options) { var self=this; - self.bind("form", "submit", onSubmit); - self.bind("#thisIsNotMe", "click", self.close.bind(self, "notme")); + self.bind("form", "submit", cancelEvent(onSubmit)); + self.bind("#thisIsNotMe", "click", cancelEvent(self.close.bind(self, "notme"))); }, stop: function() { @@ -69,6 +68,14 @@ BrowserID.Modules.PageModule = (function() { this.stop(); }, + /** + * Bind a dom event + * @method bind + * @param {string} target - css selector + * @param {string} type - event type + * @param {function} callback + * @param {object} [context] - optional context, if not given, use this. + */ bind: function(target, type, callback, context) { var self=this, cb = callback.bind(context || this); @@ -123,10 +130,20 @@ BrowserID.Modules.PageModule = (function() { screens.error.hide(); }, + /** + * Validate the form, if returns false when called, submit will not be + * called on click. + * @method validate. + */ validate: function() { return true; }, + /** + * Submit the form. Can be called to force override the + * disableSubmit function. + * @method submit + */ submit: function() { }, @@ -137,12 +154,25 @@ BrowserID.Modules.PageModule = (function() { } }, + /** + * Publish a message to the mediator. + * @method publish + * @param {string} message + * @param {object} data + */ publish: function(message, data) { mediator.publish(message, data); }, - subscribe: function(message, callback) { - mediator.subscribe(message, callback.bind(this)); + /** + * Subscribe to a message on the mediator. + * @method subscribe + * @param {string} message + * @param {function} callback + * @param {object} [context] - context, if not given, use this. + */ + subscribe: function(message, callback, context) { + mediator.subscribe(message, callback.bind(context || this)); }, /** @@ -161,6 +191,11 @@ BrowserID.Modules.PageModule = (function() { }, lowLevelInfo), onerror); }; } + + // BEGIN TESTING API + , + onSubmit: onSubmit + // END TESTING API }); return Module; diff --git a/resources/static/shared/modules/xhr_disable_form.js b/resources/static/shared/modules/xhr_disable_form.js new file mode 100644 index 0000000000000000000000000000000000000000..23ba3ebed9c47d3b571abb7db95704859163e85f --- /dev/null +++ b/resources/static/shared/modules/xhr_disable_form.js @@ -0,0 +1,30 @@ +/*globals BrowserID: true */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +BrowserID.Modules.XHRDisableForm = (function() { + "use strict"; + + var bid = BrowserID, + dom = bid.DOM, + sc; + + var Module = bid.Modules.PageModule.extend({ + start: function(options) { + var self=this; + + self.subscribe("xhr_start", + dom.addClass.curry("body", "submit_disabled")); + self.subscribe("xhr_complete", + dom.removeClass.curry("body", "submit_disabled")); + + sc.start.call(self, options); + } + }); + + sc = Module.sc; + + return Module; + +}()); + diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js index f0e007d1b988e58d59cd90ebef58e697b631f82e..47f1d9ad3807c4680713c047987f87cfa9da0aed 100644 --- a/resources/static/shared/network.js +++ b/resources/static/shared/network.js @@ -84,6 +84,7 @@ BrowserID.Network = (function() { delayTimeout = setTimeout(xhrDelay.curry(reqInfo), time_until_delay); }; + mediator.publish("xhr_start", reqInfo); xhr.ajax(req); } diff --git a/resources/static/test/index.html b/resources/static/test/index.html index ebea44ef7e5798eb844e8219a27523b2c53f0b19..08d998779f3fb06182f0de9e39def60204cfde8e 100644 --- a/resources/static/test/index.html +++ b/resources/static/test/index.html @@ -95,6 +95,7 @@ <script src="/shared/modules/page_module.js"></script> <script src="/shared/modules/xhr_delay.js"></script> + <script src="/shared/modules/xhr_disable_form.js"></script> <script src="/dialog/resources/internal_api.js"></script> <script src="/dialog/resources/helpers.js"></script> @@ -136,8 +137,10 @@ <script src="qunit/shared/storage_unit_test.js"></script> <script src="qunit/shared/network_unit_test.js"></script> <script src="qunit/shared/user_unit_test.js"></script> - <script src="qunit/shared/page_module_unit_test.js"></script> - <script src="qunit/shared/xhr_delay_unit_test.js"></script> + + <script src="qunit/shared/modules/page_module_unit_test.js"></script> + <script src="qunit/shared/modules/xhr_delay_unit_test.js"></script> + <script src="qunit/shared/modules/xhr_disable_form_unit_test.js"></script> <script src="qunit/pages/browserid_unit_test.js"></script> <script src="qunit/pages/page_helpers_unit_test.js"></script> diff --git a/resources/static/test/qunit/pages/forgot_unit_test.js b/resources/static/test/qunit/pages/forgot_unit_test.js index 621d7ceccb46f188ee0a676f62911762aa928745..22491ef900b829917dda7841d34b89671b0a492f 100644 --- a/resources/static/test/qunit/pages/forgot_unit_test.js +++ b/resources/static/test/qunit/pages/forgot_unit_test.js @@ -76,7 +76,7 @@ $("#email").val("testuser@testuser.com"); testEmailNotSent(function() { - equal($("#error").is(":visible"), true, "error is visible"); + testHelpers.testErrorVisible(); start(); }); }); diff --git a/resources/static/test/qunit/pages/signin_unit_test.js b/resources/static/test/qunit/pages/signin_unit_test.js index d03a0855648e59e9c7b9edc430155a612ea10f6d..79baa83d22529f7981ad6f6d499ce7cec383d999 100644 --- a/resources/static/test/qunit/pages/signin_unit_test.js +++ b/resources/static/test/qunit/pages/signin_unit_test.js @@ -155,9 +155,7 @@ $("#email").val("registered@testuser.com"); $("#password").val("password"); - testUserNotSignedIn(function() { - equal($("#error").is(":visible"), true, "error is visible"); - }); + testUserNotSignedIn(testHelpers.testErrorVisible); }); asyncTest("authWithPrimary opens winchan", function() { diff --git a/resources/static/test/qunit/pages/verify_email_address_test.js b/resources/static/test/qunit/pages/verify_email_address_test.js index f8af66a957bc7b07e72111f4a6d049d288d519d5..a4c3ef06d7407dd377bcb79b1310da0c59424357 100644 --- a/resources/static/test/qunit/pages/verify_email_address_test.js +++ b/resources/static/test/qunit/pages/verify_email_address_test.js @@ -58,7 +58,7 @@ asyncTest("verifyEmailAddress with emailForVerficationToken XHR failure", function() { xhr.useResult("ajaxError"); bid.verifyEmailAddress("token", function() { - ok($("#error").is(":visible"), "cannot communicate box is visible"); + testHelpers.testErrorVisible(); start(); }); }); diff --git a/resources/static/test/qunit/shared/page_module_unit_test.js b/resources/static/test/qunit/shared/modules/page_module_unit_test.js similarity index 92% rename from resources/static/test/qunit/shared/page_module_unit_test.js rename to resources/static/test/qunit/shared/modules/page_module_unit_test.js index 82b472d8881e876f170468a38251e16487fc5e2d..5b52f0346e54337e52cb5409ec081dab3f13bcc2 100644 --- a/resources/static/test/qunit/shared/page_module_unit_test.js +++ b/resources/static/test/qunit/shared/modules/page_module_unit_test.js @@ -17,7 +17,7 @@ controller.start(); } - module("shared/page_controller", { + module("shared/page_module", { setup: function() { el = $("#controller_head"); bid.TestHelpers.setup(); @@ -51,11 +51,6 @@ var html = el.find("#formWrap .contents").html(); ok(html.length, "with template specified, form text is loaded"); -/* - - var input = el.find("input").eq(0); - ok(input.is(":focus"), "make sure the first input is focused"); -*/ html = el.find("#wait .contents").html(); equal(html, "", "with body template specified, wait text is not loaded"); }); @@ -233,5 +228,23 @@ equal(error, "missing config option: requiredField"); }); + test("form is not submitted when 'submit_disabled' class is added to body", function() { + createController(); + + var submitCalled = false; + controller.submit = function() { + submitCalled = true; + }; + + $("body").addClass("submit_disabled"); + controller.onSubmit(); + + equal(submitCalled, false, "submit was prevented from being called"); + + + $("body").removeClass("submit_disabled"); + controller.onSubmit(); + equal(submitCalled, true, "submit permitted to complete"); + }) }()); diff --git a/resources/static/test/qunit/shared/xhr_delay_unit_test.js b/resources/static/test/qunit/shared/modules/xhr_delay_unit_test.js similarity index 100% rename from resources/static/test/qunit/shared/xhr_delay_unit_test.js rename to resources/static/test/qunit/shared/modules/xhr_delay_unit_test.js diff --git a/resources/static/test/qunit/shared/modules/xhr_disable_form_unit_test.js b/resources/static/test/qunit/shared/modules/xhr_disable_form_unit_test.js new file mode 100644 index 0000000000000000000000000000000000000000..61a1d9bb1ac2584dee36dab3f062c6dec7af027a --- /dev/null +++ b/resources/static/test/qunit/shared/modules/xhr_disable_form_unit_test.js @@ -0,0 +1,41 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +(function() { + "use strict"; + + var bid = BrowserID, + Module = bid.Modules.XHRDisableForm, + testHelpers = bid.TestHelpers, + mediator = bid.Mediator, + mod; + + function createModule(options) { + mod = Module.create({}); + mod.start(options); + return mod; + } + + module("shared/xhr_disable_form", { + setup: function() { + testHelpers.setup(); + createModule(); + }, + + teardown: function() { + testHelpers.teardown(); + } + }); + + test("xhr_start adds 'submit_disabled' to class, xhr_complete removes it", function() { + var body = $("body"); + + mediator.publish("xhr_start"); + equal(body.hasClass("submit_disabled"), true, "xhr_start adds submit_disabled"); + + mediator.publish("xhr_complete"); + equal(body.hasClass("submit_disabled"), false, "xhr_complete removes submit_disabled"); + }); +}()); diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js index 5bfd7148907a71704f864239c28d76e97020e75f..f7703e275999f70d6b2f5c4b5e00a5f064b2d230 100644 --- a/resources/static/test/qunit/testHelpers/helpers.js +++ b/resources/static/test/qunit/testHelpers/helpers.js @@ -103,12 +103,15 @@ BrowserID.TestHelpers = (function() { isTriggered: function(message) { return calls[message]; }, + errorVisible: function() { return screens.error.visible; }, + testErrorVisible: function() { - equal(this.errorVisible(), true, "error screen is visible"); + equal(TestHelpers.errorVisible(), true, "error screen is visible"); }, + checkNetworkError: checkNetworkError, unexpectedSuccess: function() { ok(false, "unexpected success"); diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs index 18f29c49b688d84877787caf49b5c1a93211c647..4d74033221e550e7ee98d9ae0858d0a456136deb 100644 --- a/resources/views/dialog_layout.ejs +++ b/resources/views/dialog_layout.ejs @@ -82,6 +82,7 @@ <script src="/shared/modules/page_module.js"></script> <script src="/shared/modules/xhr_delay.js"></script> + <script src="/shared/modules/xhr_disable_form.js"></script> <script src="/dialog/resources/internal_api.js"></script> <script src="/dialog/resources/helpers.js"></script> diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs index 3e4fbcfb47e48bf39d082dd8768f4a07bd95c508..9b64750678fd890c558f1f634b0a6f7fb64b6635 100644 --- a/resources/views/layout.ejs +++ b/resources/views/layout.ejs @@ -47,6 +47,7 @@ <script src="/shared/modules/page_module.js"></script> <script src="/shared/modules/xhr_delay.js"></script> + <script src="/shared/modules/xhr_disable_form.js"></script> <script src="/pages/page_helpers.js"></script> <script src="/pages/index.js"></script> diff --git a/scripts/compress.sh b/scripts/compress.sh index 8c63c4fcd0375cc226c19a62018f684a46753ae8..7a3557f0e0c1df2b1435fb1a7aa9f4e9d0abec13 100755 --- a/scripts/compress.sh +++ b/scripts/compress.sh @@ -58,7 +58,7 @@ cp templates.js $BUILD_PATH/templates.js cd ../.. # produce the dialog js -cat lib/jquery-1.7.1.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js lib/jschannel.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/provisioning.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js shared/modules/page_module.js shared/modules/xhr_delay.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgot_password.js dialog/controllers/check_registration.js dialog/controllers/pick_email.js dialog/controllers/add_email.js dialog/controllers/required_email.js dialog/controllers/verify_primary_user.js dialog/controllers/provision_primary_user.js dialog/controllers/primary_user_provisioned.js dialog/controllers/email_chosen.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js +cat lib/jquery-1.7.1.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js lib/jschannel.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/provisioning.js shared/network.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js shared/modules/page_module.js shared/modules/xhr_delay.js shared/modules/xhr_disable_form.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgot_password.js dialog/controllers/check_registration.js dialog/controllers/pick_email.js dialog/controllers/add_email.js dialog/controllers/required_email.js dialog/controllers/verify_primary_user.js dialog/controllers/provision_primary_user.js dialog/controllers/primary_user_provisioned.js dialog/controllers/email_chosen.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js # produce the dialog css cat css/common.css dialog/css/popup.css dialog/css/m.css > $BUILD_PATH/dialog.uncompressed.css @@ -71,7 +71,7 @@ echo '****Building BrowserID.org HTML, CSS, and JS****' echo '' #produce the main site js -cat lib/vepbundle.js lib/jquery-1.7.1.min.js lib/underscore-min.js lib/ejs.js shared/javascript-extensions.js shared/browserid.js lib/dom-jquery.js lib/jschannel.js lib/winchan.js lib/hub.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/error-messages.js shared/wait-messages.js shared/mediator.js shared/storage.js shared/network.js shared/provisioning.js shared/user.js shared/tooltip.js shared/validation.js shared/helpers.js shared/class.js shared/modules/page_module.js shared/modules/xhr_delay.js pages/page_helpers.js pages/start.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/forgot.js pages/manage_account.js pages/signin.js pages/signup.js > $BUILD_PATH/browserid.uncompressed.js +cat lib/vepbundle.js lib/jquery-1.7.1.min.js lib/underscore-min.js lib/ejs.js shared/javascript-extensions.js shared/browserid.js lib/dom-jquery.js lib/jschannel.js lib/winchan.js lib/hub.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/error-messages.js shared/wait-messages.js shared/mediator.js shared/storage.js shared/network.js shared/provisioning.js shared/user.js shared/tooltip.js shared/validation.js shared/helpers.js shared/class.js shared/modules/page_module.js shared/modules/xhr_delay.js shared/modules/xhr_disable_form.js pages/page_helpers.js pages/start.js pages/index.js pages/add_email_address.js pages/verify_email_address.js pages/forgot.js pages/manage_account.js pages/signin.js pages/signup.js > $BUILD_PATH/browserid.uncompressed.js # produce the main site css cat css/common.css css/style.css css/m.css > $BUILD_PATH/browserid.uncompressed.css