Repository: ignite Updated Branches: refs/heads/master e1a62b2f0 -> b2a707748
IGNITE-8370 Web Console: Trigger validation before submitting auth forms. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/b2a70774 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/b2a70774 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/b2a70774 Branch: refs/heads/master Commit: b2a7077484f59e2bde76299577d8c3aafe0e1152 Parents: e1a62b2 Author: Ilya Borisov <klast...@gmail.com> Authored: Thu May 17 15:53:46 2018 +0700 Committer: Alexey Kuznetsov <akuznet...@apache.org> Committed: Thu May 17 15:53:46 2018 +0700 ---------------------------------------------------------------------- .../testcafe/fixtures/auth/forgot-password.js | 3 +- .../fixtures/auth/signup-validation-local.js | 52 ++++++++++++++++++++ .../e2e/testcafe/fixtures/auth/signup.js | 15 +----- modules/web-console/frontend/app/app.js | 2 + .../frontend/app/components/form-field/index.js | 23 +++++++++ .../form-field/showValidationError.directive.js | 47 ++++++++++++++++++ .../page-configure/components/pcValidation.js | 22 --------- .../page-forgot-password/controller.js | 22 +++++++-- .../page-forgot-password/template.pug | 1 - .../app/components/page-signin/controller.js | 24 +++++++-- .../app/components/page-signin/template.pug | 5 +- .../app/components/page-signup/controller.js | 21 ++++++-- .../app/components/page-signup/template.pug | 1 - .../app/primitives/form-field/dropdown.pug | 1 + .../app/primitives/form-field/index.scss | 28 +++++++++++ .../frontend/app/services/FormUtils.service.js | 6 +-- 16 files changed, 214 insertions(+), 59 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/e2e/testcafe/fixtures/auth/forgot-password.js ---------------------------------------------------------------------- diff --git a/modules/web-console/e2e/testcafe/fixtures/auth/forgot-password.js b/modules/web-console/e2e/testcafe/fixtures/auth/forgot-password.js index 1163595..33963fa 100644 --- a/modules/web-console/e2e/testcafe/fixtures/auth/forgot-password.js +++ b/modules/web-console/e2e/testcafe/fixtures/auth/forgot-password.js @@ -33,8 +33,7 @@ fixture('Password reset') test('Incorrect email', async(t) => { await t .typeText(page.email.control, 'aa') - .expect(page.email.getError('email').exists).ok('Marks field as invalid') - .expect(page.remindPasswordButton.getAttribute('disabled')).ok('Disables submit button'); + .expect(page.email.getError('email').exists).ok('Marks field as invalid'); }); test('Unknown email', async(t) => { http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/e2e/testcafe/fixtures/auth/signup-validation-local.js ---------------------------------------------------------------------- diff --git a/modules/web-console/e2e/testcafe/fixtures/auth/signup-validation-local.js b/modules/web-console/e2e/testcafe/fixtures/auth/signup-validation-local.js new file mode 100644 index 0000000..d2e125c --- /dev/null +++ b/modules/web-console/e2e/testcafe/fixtures/auth/signup-validation-local.js @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {resolveUrl} from '../../envtools'; +import {pageSignup as page} from '../../page-models/pageSignup'; + +fixture('Signup validation local').page(resolveUrl('/signup')); + +test('Most fields have validation', async(t) => { + await page.fillSignupForm({ + email: 'foobar', + password: '1', + passwordConfirm: '2', + firstName: ' ', + lastName: ' ', + company: ' ', + country: 'Brazil' + }); + + await t + .expect(page.email.getError('email').exists).ok() + .expect(page.passwordConfirm.getError('mismatch').exists).ok() + .expect(page.firstName.getError('required').exists).ok() + .expect(page.lastName.getError('required').exists).ok() + .expect(page.company.getError('required').exists).ok(); +}); + +test('Errors on submit', async(t) => { + await t + .typeText(page.email.control, 'em...@example.com') + .click(page.signupButton) + .expect(page.password.control.focused).ok() + .expect(page.password.getError('required').exists).ok() + .typeText(page.password.control, 'Foo') + .click(page.signupButton) + .expect(page.passwordConfirm.control.focused).ok() + .expect(page.passwordConfirm.getError('required').exists).ok(); +}); http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/e2e/testcafe/fixtures/auth/signup.js ---------------------------------------------------------------------- diff --git a/modules/web-console/e2e/testcafe/fixtures/auth/signup.js b/modules/web-console/e2e/testcafe/fixtures/auth/signup.js index f0951d9..cd08282 100644 --- a/modules/web-console/e2e/testcafe/fixtures/auth/signup.js +++ b/modules/web-console/e2e/testcafe/fixtures/auth/signup.js @@ -31,8 +31,6 @@ fixture('Signup') }); test('Local validation', async(t) => { - const isButtonDisabled = page.signupButton.hasAttribute('disabled'); - await t.expect(isButtonDisabled).ok('Button disabled by default'); await page.fillSignupForm({ email: 'foobar', password: '1', @@ -45,18 +43,7 @@ test('Local validation', async(t) => { await t .expect(page.email.getError('email').exists).ok() .expect(page.passwordConfirm.getError('mismatch').exists).ok() - .expect(page.firstName.getError('required').exists).ok() - .expect(isButtonDisabled).ok('Button disabled with invalid fields'); - await page.fillSignupForm({ - email: 'foo...@bar.baz', - password: '1', - passwordConfirm: '1', - firstName: 'John', - lastName: 'Doe', - company: 'FooBar', - country: 'Brazil' - }); - await t.expect(isButtonDisabled).notOk('Button enabled with valid fields'); + .expect(page.firstName.getError('required').exists).ok(); }); test('Server validation', async(t) => { await page.fillSignupForm({ http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/app.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js index b4e71fa..d28f1bc 100644 --- a/modules/web-console/frontend/app/app.js +++ b/modules/web-console/frontend/app/app.js @@ -139,6 +139,7 @@ import connectedClusters from './components/connected-clusters'; import pageLanding from './components/page-landing'; import passwordVisibility from './components/password-visibility'; import progressLine from './components/progress-line'; +import formField from './components/form-field'; import pageProfile from './components/page-profile'; import pagePasswordChanged from './components/page-password-changed'; @@ -246,6 +247,7 @@ angular.module('ignite-console', [ breadcrumbs.name, passwordVisibility.name, progressLine.name, + formField.name, // Ignite modules. IgniteModules.name ]) http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/form-field/index.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-field/index.js b/modules/web-console/frontend/app/components/form-field/index.js new file mode 100644 index 0000000..323c30a --- /dev/null +++ b/modules/web-console/frontend/app/components/form-field/index.js @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import angular from 'angular'; +import {directive as showValidationError} from './showValidationError.directive'; + +export default angular + .module('ignite-console.form-field', []) + .directive('ngModel', showValidationError); http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/form-field/showValidationError.directive.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/form-field/showValidationError.directive.js b/modules/web-console/frontend/app/components/form-field/showValidationError.directive.js new file mode 100644 index 0000000..32123cc --- /dev/null +++ b/modules/web-console/frontend/app/components/form-field/showValidationError.directive.js @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Brings user attention to invalid form fields. + * Use IgniteFormUtils.triggerValidation to trigger the event. + */ +export function directive($timeout) { + return { + require: ['ngModel', '?^^bsCollapseTarget', '?^^igniteFormField', '?^^panelCollapsible'], + link(scope, el, attr, [ngModel, bsCollapseTarget, igniteFormField, panelCollapsible]) { + const off = scope.$on('$showValidationError', (e, target) => { + if (target !== ngModel) return; + ngModel.$setTouched(); + bsCollapseTarget && bsCollapseTarget.open(); + panelCollapsible && panelCollapsible.open(); + $timeout(() => { + if (el[0].scrollIntoViewIfNeeded) + el[0].scrollIntoViewIfNeeded(); + else + el[0].scrollIntoView(); + + if (!attr.bsSelect) + $timeout(() => el[0].focus()); + + igniteFormField && igniteFormField.notifyAboutError(); + }); + }); + } + }; +} + +directive.$inject = ['$timeout']; http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js b/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js index 5ddd4ef..e4c7314 100644 --- a/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js +++ b/modules/web-console/frontend/app/components/page-configure/components/pcValidation.js @@ -146,28 +146,6 @@ export default angular.module('ignite-console.page-configure.validation', []) }] }; }) - .directive('ngModel', ['$timeout', function($timeout) { - return { - require: ['ngModel', '?^^bsCollapseTarget', '?^^igniteFormField', '?^^panelCollapsible'], - link(scope, el, attr, [ngModel, bsCollapseTarget, igniteFormField, panelCollapsible]) { - const off = scope.$on('$showValidationError', (e, target) => { - if (target !== ngModel) return; - ngModel.$setTouched(); - bsCollapseTarget && bsCollapseTarget.open(); - panelCollapsible && panelCollapsible.open(); - $timeout(() => { - if (el[0].scrollIntoViewIfNeeded) - el[0].scrollIntoViewIfNeeded(); - else - el[0].scrollIntoView(); - - if (!attr.bsSelect) $timeout(() => el[0].focus()); - igniteFormField && igniteFormField.notifyAboutError(); - }); - }); - } - }; - }]) .directive('igniteFormField', function() { return { restrict: 'C', http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-forgot-password/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-forgot-password/controller.js b/modules/web-console/frontend/app/components/page-forgot-password/controller.js index 92e8541..7d06773 100644 --- a/modules/web-console/frontend/app/components/page-forgot-password/controller.js +++ b/modules/web-console/frontend/app/components/page-forgot-password/controller.js @@ -25,14 +25,15 @@ export default class PageForgotPassword { /** @type {string} */ serverError = null; - static $inject = ['Auth', 'IgniteMessages']; + static $inject = ['Auth', 'IgniteMessages', 'IgniteFormUtils']; /** * @param {import('app/modules/user/Auth.service').default} Auth */ - constructor(Auth, IgniteMessages) { + constructor(Auth, IgniteMessages, IgniteFormUtils) { this.Auth = Auth; this.IgniteMessages = IgniteMessages; + this.IgniteFormUtils = IgniteFormUtils; } /** @param {import('./types').IForgotPasswordFormController} form */ canSubmitForm(form) { @@ -41,6 +42,13 @@ export default class PageForgotPassword { $postLink() { this.form.email.$validators.server = () => !this.serverError; } + + /** @param {string} error */ + setServerError(error) { + this.serverError = error; + this.form.email.$validate(); + } + /** * @param {{email: ng.IChangesObject<string>}} changes */ @@ -48,10 +56,16 @@ export default class PageForgotPassword { if ('email' in changes) this.data.email = changes.email.currentValue; } remindPassword() { + this.IgniteFormUtils.triggerValidation(this.form); + + this.setServerError(null); + + if (!this.canSubmitForm(this.form)) + return; + return this.Auth.remindPassword(this.data.email).catch((res) => { this.IgniteMessages.showError(null, res.data); - this.serverError = res.data; - this.form.email.$validate(); + this.setServerError(res.data); }); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-forgot-password/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-forgot-password/template.pug b/modules/web-console/frontend/app/components/page-forgot-password/template.pug index 6a11baf..2532959 100644 --- a/modules/web-console/frontend/app/components/page-forgot-password/template.pug +++ b/modules/web-console/frontend/app/components/page-forgot-password/template.pug @@ -44,7 +44,6 @@ web-console-header button.btn-ignite.btn-ignite--primary( tabindex='1' type='submit' - ng-disabled=`!$ctrl.canSubmitForm(${form})` ) Send it to me web-console-footer http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-signin/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signin/controller.js b/modules/web-console/frontend/app/components/page-signin/controller.js index b13cf4b..f2d28e8 100644 --- a/modules/web-console/frontend/app/components/page-signin/controller.js +++ b/modules/web-console/frontend/app/components/page-signin/controller.js @@ -26,14 +26,15 @@ export default class { /** @type {string} */ serverError = null; - static $inject = ['Auth', 'IgniteMessages']; + static $inject = ['Auth', 'IgniteMessages', 'IgniteFormUtils']; /** * @param {import('app/modules/user/Auth.service').default} Auth */ - constructor(Auth, IgniteMessages) { + constructor(Auth, IgniteMessages, IgniteFormUtils) { this.Auth = Auth; this.IgniteMessages = IgniteMessages; + this.IgniteFormUtils = IgniteFormUtils; } /** @param {import('./types').ISigninFormController} form */ @@ -42,15 +43,28 @@ export default class { } $postLink() { + this.form.email.$validators.server = () => !this.serverError; this.form.password.$validators.server = () => !this.serverError; } + /** @param {string} error */ + setServerError(error) { + this.serverError = error; + this.form.email.$validate(); + this.form.password.$validate(); + } + signin() { + this.IgniteFormUtils.triggerValidation(this.form); + + this.setServerError(null); + + if (!this.canSubmitForm(this.form)) + return; + return this.Auth.signin(this.data.email, this.data.password).catch((res) => { this.IgniteMessages.showError(null, res.data); - this.serverError = res.data; - this.form.email.$validate(); - this.form.password.$validate(); + this.setServerError(res.data); }); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-signin/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signin/template.pug b/modules/web-console/frontend/app/components/page-signin/template.pug index fd5e95f..6047e65 100644 --- a/modules/web-console/frontend/app/components/page-signin/template.pug +++ b/modules/web-console/frontend/app/components/page-signin/template.pug @@ -36,7 +36,7 @@ web-console-header autocomplete='email' ignite-auto-focus ) - +form-field__error({error: 'server', message: `{{$ctrl.serverErrors.}`}) + +form-field__error({error: 'server', message: `{{$ctrl.serverError}}`}) +form-field__password({ label: 'Password:', model: '$ctrl.data.password', @@ -47,12 +47,11 @@ web-console-header ng-model-options='{allowInvalid: true}' autocomplete='current-password' ) - +form-field__error({error: 'server', message: `{{$ctrl.serverErrors}}`}) + +form-field__error({error: 'server', message: `{{$ctrl.serverError}}`}) footer.form-footer a(ui-sref='forgotPassword({email: $ctrl.data.email})') Forgot password? button.btn-ignite.btn-ignite--primary( type='submit' - ng-disabled=`!$ctrl.canSubmitForm(${form})` ) Sign In footer.page-signin__no-account-message | Don't have an account? #[a(ui-sref='signup') Get started] http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-signup/controller.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/controller.js b/modules/web-console/frontend/app/components/page-signup/controller.js index ffc534a..57f404a 100644 --- a/modules/web-console/frontend/app/components/page-signup/controller.js +++ b/modules/web-console/frontend/app/components/page-signup/controller.js @@ -30,15 +30,16 @@ export default class PageSignup { /** @type {string} */ serverError = null; - static $inject = ['IgniteCountries', 'Auth', 'IgniteMessages']; + static $inject = ['IgniteCountries', 'Auth', 'IgniteMessages', 'IgniteFormUtils']; /** * @param {import('app/modules/user/Auth.service').default} Auth */ - constructor(Countries, Auth, IgniteMessages) { + constructor(Countries, Auth, IgniteMessages, IgniteFormUtils) { this.Auth = Auth; this.IgniteMessages = IgniteMessages; this.countries = Countries.getAll(); + this.IgniteFormUtils = IgniteFormUtils; } /** @param {import('./types').ISignupFormController} form */ @@ -50,11 +51,23 @@ export default class PageSignup { this.form.email.$validators.server = () => !this.serverError; } + /** @param {string} error */ + setServerError(error) { + this.serverError = error; + this.form.email.$validate(); + } + signup() { + this.IgniteFormUtils.triggerValidation(this.form); + + this.setServerError(null); + + if (!this.canSubmitForm(this.form)) + return; + return this.Auth.signnup(this.data).catch((res) => { this.IgniteMessages.showError(null, res.data); - this.serverError = res.data; - this.form.email.$validate(); + this.setServerError(res.data); }); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/components/page-signup/template.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/components/page-signup/template.pug b/modules/web-console/frontend/app/components/page-signup/template.pug index 8cc7b14..254243e 100644 --- a/modules/web-console/frontend/app/components/page-signup/template.pug +++ b/modules/web-console/frontend/app/components/page-signup/template.pug @@ -114,7 +114,6 @@ web-console-header footer.full-width.form-footer button.btn-ignite.btn-ignite--primary( type='submit' - ng-disabled=`!$ctrl.canSubmitForm(${form})` ) Sign Up footer.page-signup__has-account-message | Already have an account? #[a(ui-sref='signin') Sign in here] http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/primitives/form-field/dropdown.pug ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/form-field/dropdown.pug b/modules/web-console/frontend/app/primitives/form-field/dropdown.pug index c41825c..de83bf9 100644 --- a/modules/web-console/frontend/app/primitives/form-field/dropdown.pug +++ b/modules/web-console/frontend/app/primitives/form-field/dropdown.pug @@ -15,6 +15,7 @@ limitations under the License. mixin form-field__dropdown({ label, model, name, disabled, required, multiple, placeholder, placeholderEmpty, options, tip }) + -var errLbl = label.substring(0, label.length - 1) mixin __form-field__input() button.select-toggle( type='button' http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/primitives/form-field/index.scss ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/primitives/form-field/index.scss b/modules/web-console/frontend/app/primitives/form-field/index.scss index 800daa8..83dd55d 100644 --- a/modules/web-console/frontend/app/primitives/form-field/index.scss +++ b/modules/web-console/frontend/app/primitives/form-field/index.scss @@ -309,6 +309,7 @@ } .form-field__checkbox { + $errorSize: 16px; display: flex; .form-field { @@ -321,6 +322,7 @@ width: auto; margin-right: 10px; padding: 3px 0; + flex: 0 0 auto; input { width: auto; @@ -328,6 +330,32 @@ margin: 0; } } + + &__errors { + position: static; + right: initial; + bottom: initial; + order: 3; + margin-left: 5px; + + .form-field__error { + width: $errorSize; + height: $errorSize; + + div { + // Required to correctly position error popover + top: -10px; + height: 36px; + + width: $errorSize; + } + + [ignite-icon] { + width: $errorSize; + top: 0; + } + } + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/b2a70774/modules/web-console/frontend/app/services/FormUtils.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/services/FormUtils.service.js b/modules/web-console/frontend/app/services/FormUtils.service.js index da1d737..da6b417 100644 --- a/modules/web-console/frontend/app/services/FormUtils.service.js +++ b/modules/web-console/frontend/app/services/FormUtils.service.js @@ -16,7 +16,7 @@ */ import _ from 'lodash'; -export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) => { +export default ['IgniteFormUtils', ['$window', 'IgniteFocus', '$rootScope', ($window, Focus, $rootScope) => { function ensureActivePanel(ui, pnl, focusId) { if (ui && ui.loadPanel) { const collapses = $('[bs-collapse-target]'); @@ -326,7 +326,7 @@ export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) = } // TODO: move somewhere else - function triggerValidation(form, $scope) { + function triggerValidation(form) { const fe = (m) => Object.keys(m.$error)[0]; const em = (e) => (m) => { if (!e) return; @@ -338,7 +338,7 @@ export default ['IgniteFormUtils', ['$window', 'IgniteFocus', ($window, Focus) = return walk(m); }; - $scope.$broadcast('$showValidationError', em(fe(form))(form)); + $rootScope.$broadcast('$showValidationError', em(fe(form))(form)); } return {