diff options
author | beccabroek <beccabroek@gmail.com> | 2019-01-23 14:26:55 -0600 |
---|---|---|
committer | Ed Tanous <ed.tanous@intel.com> | 2019-02-02 01:09:14 +0000 |
commit | a09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8 (patch) | |
tree | 596738144339961f447244c709d904dc7725f362 | |
parent | 97280b3efde6a19a517096940fd8e68c3c192331 (diff) | |
download | phosphor-webui-a09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8.tar.gz phosphor-webui-a09cc2da1b3a9bad0eaf34fbfdec8a3ee32d47f8.zip |
Form validation on login page
Using ngMessages, adds form validation to login page. Also creates a
directive, hasError, to be used to validate form field by passing
in a boolean. This is a proposed pattern to be used moving forward,
as form validation is added to additional pages.
Validation error messages are shown on $touched and on submit.
Unreachable Server and Invalid username and password error messages
remain until input is no longer $pristine after form submission.
In addition, this removes unneeded and unused css styling
Resolves openbmc/phosphor-webui#47
Change-Id: I7a067af67ac74d4cf2977d10f66445720ecae9eb
Signed-off-by: beccabroek <beccabroek@gmail.com>
-rw-r--r-- | app/common/directives/form-input-error.js | 11 | ||||
-rw-r--r-- | app/common/styles/base/colors.scss | 1 | ||||
-rw-r--r-- | app/common/styles/base/forms.scss | 31 | ||||
-rw-r--r-- | app/common/styles/base/foundation.scss | 25 | ||||
-rw-r--r-- | app/common/styles/elements/alerts.scss | 68 | ||||
-rw-r--r-- | app/common/styles/elements/errors.scss | 11 | ||||
-rw-r--r-- | app/common/styles/elements/index.scss | 1 | ||||
-rw-r--r-- | app/index.js | 1 | ||||
-rw-r--r-- | app/login/controllers/login-controller.html | 36 | ||||
-rw-r--r-- | app/login/controllers/login-controller.js | 14 | ||||
-rw-r--r-- | app/login/styles/index.scss | 5 |
11 files changed, 81 insertions, 123 deletions
diff --git a/app/common/directives/form-input-error.js b/app/common/directives/form-input-error.js new file mode 100644 index 0000000..69b0487 --- /dev/null +++ b/app/common/directives/form-input-error.js @@ -0,0 +1,11 @@ +angular.module('app.common.directives').directive('hasError', function() { + return { + scope: {hasError: '='}, + require: 'ngModel', + link: function(scope, elm, attrs, ngModel) { + scope.$watch('hasError', function(value) { + ngModel.$setValidity('hasError', value ? false : true); + }); + } + }; +}); diff --git a/app/common/styles/base/colors.scss b/app/common/styles/base/colors.scss index 46285c8..405dad4 100644 --- a/app/common/styles/base/colors.scss +++ b/app/common/styles/base/colors.scss @@ -52,6 +52,7 @@ $status-warn: #ffb000; $alert__error: rgb(230, 35, 37); $alert__warning: rgb(255, 127, 0); $alert__message: rgb(203, 221, 235); +$alert__danger: #fad3D3; // Severity $critical-lightbg: #e62325; diff --git a/app/common/styles/base/forms.scss b/app/common/styles/base/forms.scss index 0957609..73c25c1 100644 --- a/app/common/styles/base/forms.scss +++ b/app/common/styles/base/forms.scss @@ -21,13 +21,13 @@ textarea { border-radius: 0px; border: 1px solid $input-border; height: 3.1em; - margin: 0 0 1em 0; + margin: 0; background: $white; box-shadow: 0 0 0; transition: none !important; &:focus { border-color: $medgrey; - box-shadow: 0 -5px $field__focus inset; + box-shadow: 0 -3px $field__focus inset; } &:disabled, .disabled { @@ -43,6 +43,33 @@ textarea { } } +//input validation +.ng-invalid.ng-touched { + box-shadow: 0 -3px $error-color inset; + &:focus { + border-color: $medgrey; + box-shadow: 0 -3px $error-color inset; + } +} +.submitted .ng-invalid { + box-shadow: 0 -3px $error-color inset; + &:focus { + border-color: $medgrey; + box-shadow: 0 -3px $error-color inset; + } +} + +.form-error { + margin-bottom: .7em; + font-size: 0.8rem; + color: #c60f13; + height:1rem; + display: block; + visibility: hidden; + .visible { + visibility: visible; + } +} //Foundation overwrite [type=color], [type=date], [type=datetime-local], [type=datetime], [type=email], [type=month], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=time], [type=url], [type=week], textarea { border-color: $input-border; diff --git a/app/common/styles/base/foundation.scss b/app/common/styles/base/foundation.scss index f1bcb2f..a422dec 100644 --- a/app/common/styles/base/foundation.scss +++ b/app/common/styles/base/foundation.scss @@ -705,31 +705,6 @@ select[multiple] { height: auto; background-image: none; } -.is-invalid-input:not(:focus) { - border-color: #c60f13; - background-color: #f8e6e7; } -.is-invalid-input:not(:focus)::-webkit-input-placeholder { - color: #c60f13; } -.is-invalid-input:not(:focus)::-moz-placeholder { - color: #c60f13; } -.is-invalid-input:not(:focus):-ms-input-placeholder { - color: #c60f13; } -.is-invalid-input:not(:focus)::placeholder { - color: #c60f13; } - -.is-invalid-label { - color: #c60f13; } - -.form-error { - display: none; - margin-top: -0.5rem; - margin-bottom: 1rem; - font-size: 0.75rem; - font-weight: bold; - color: #c60f13; } -.form-error.is-visible { - display: block; } - .hide { display: none !important; } diff --git a/app/common/styles/elements/alerts.scss b/app/common/styles/elements/alerts.scss index 96f0a4c..6242257 100644 --- a/app/common/styles/elements/alerts.scss +++ b/app/common/styles/elements/alerts.scss @@ -1,67 +1,11 @@ //Fixed alerts -.alert__error, -.alert__warning, -.alert__message { - color: $darkbg__primary; - padding: 1em; - margin: .5em 0; - position: relative; - display: flex; - justify-content: center; - flex-direction: column; - .close { - color: $lightbg__primary; - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - font-size: 1.5em; - padding: 1em; - box-sizing: border-box; - line-height: 0; - display: flex; - justify-content: center; - flex-direction: column; - background: transparent; - border: 0; - margin: 0; - &:hover { - color: $lightbg__accent; - } - } -} - -.alert__error { - background: $alert__error; - -} - -.alert__warning { - background: $alert__warning; - -} -.alert__message { - background: $alert__message; -} - -// Power confirmation buttons -.power__confirm-buttons { - .btn-primary { - background: transparent; - border: 2px solid $white; - padding: 1em 2.2em; - margin: 0 10px; - border-radius: 4px; - &:focus, - &:hover { - background: $primebtn__bg; - border: 2px solid $primebtn__bg; - } - } - @include mediaQuery(large) { - float: right; - } +.alert-danger{ + background-color: $alert__danger; + border-color: $critical-lightbg; + border-radius: 0; + color: #333; + text-align: left; } diff --git a/app/common/styles/elements/errors.scss b/app/common/styles/elements/errors.scss deleted file mode 100644 index 3076a3d..0000000 --- a/app/common/styles/elements/errors.scss +++ /dev/null @@ -1,11 +0,0 @@ -.error-msg { - background: lighten($error-color, 20%); - padding: 1em; - text-align: center; - font-size: 1em; - border: 1px solid $error-color; - color: $black; - font-family: "Courier New", Helvetica, Arial, sans-serif; - font-weight: 700; - max-width: 325px; -} diff --git a/app/common/styles/elements/index.scss b/app/common/styles/elements/index.scss index 3c5754e..25162bb 100644 --- a/app/common/styles/elements/index.scss +++ b/app/common/styles/elements/index.scss @@ -11,5 +11,4 @@ @import "export"; @import "modals"; @import "quicklinks"; -@import "errors"; @import "toast"; diff --git a/app/index.js b/app/index.js index 37e27c1..2313016 100644 --- a/app/index.js +++ b/app/index.js @@ -54,6 +54,7 @@ import loader from './common/directives/loader.js'; import paginate from './common/directives/paginate.js'; import serial_console from './common/directives/serial-console.js'; import dir_paginate from './common/directives/dirPagination.js'; +import form_input_error from './common/directives/form-input-error.js'; import login_index from './login/index.js'; import login_controller from './login/controllers/login-controller.js'; diff --git a/app/login/controllers/login-controller.html b/app/login/controllers/login-controller.html index b8fdf09..b89c2c4 100644 --- a/app/login/controllers/login-controller.html +++ b/app/login/controllers/login-controller.html @@ -4,20 +4,34 @@ <img src="../../assets/images/logo.svg" class="login__logo" alt="OpenBMC logo"/> <h1 class="login__desc">OpenBMC</h1> </div> - <div class="columns large-6 disabled"> - <form id="login__form" action=""> - <label >BMC Host or BMC IP Address</label> - <input type="text" ng-model="host" required ng-class="{'error': error && description != 'Invalid username or password'}" autofocus ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading"> - - <label for="username">Username</label> - <input type="text" id="username" name="username" required ng-model="username" ng-class="{'error': description == 'Invalid username or password'}" ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading" autocomplete="off"> + <div class="columns large-6"> + <form id="login__form" name="login__form" action="" ng-class="{'submitted' : submitted}"> + <fieldset ng-disabled="dataService.loading"> + <div class="alert alert-danger" role="alert" ng-if="invalidCredentials"> + <b>Invalid username or password.</b> + <br>Please try again. + </div> + <label for="host">BMC Host or BMC IP Address</label> + <input type="text" id="host" name="host" class="validate-input" ng-model="host" has-error="serverUnreachable && login__form.host.$pristine" required autofocus ng-keydown="tryLogin(host, username, password, $event)"> + <div ng-messages="login__form.host.$error" class="form-error" ng-class="{'visible' : login__form.host.$touched || submitted}"> + <p ng-message="required">Field is required</p> + <p ng-message="hasError">Server unreachable</p> + </div> - <label for="password">Password</label> - <input type="password" id="password" name="password" required ng-class="{'error': description == 'Invalid username or password'}" ng-model="password" ng-keydown="tryLogin(host, username, password, $event)" ng-disabled="dataService.loading" autocomplete="off"> + <label for="username">Username</label> + <input type="text" id="username" name="username" has-error="invalidCredentials && login__form.$pristine" required ng-model="username" ng-keydown="tryLogin(host, username, password, $event)" autocomplete="off"> + <div ng-messages="login__form.username.$error" class="form-error" ng-class="{'visible' : login__form.username.$touched || submitted}"> + <p ng-message="required">Field is required</p> + </div> - <input id="login__submit" class="btn-primary submit" type="button" value="Log in" role="button" ng-click="login(host, username, password)" ng-class="{error: error}" ng-disabled="dataService.loading"> + <label for="password">Password</label> + <input type="password" id="password" name="password" has-error="invalidCredentials && login__form.$pristine" required ng-model="password" ng-keydown="tryLogin(host, username, password, $event)" autocomplete="off"> + <div ng-messages="login__form.password.$error" class="form-error" ng-class="{'visible': login__form.password.$touched || submitted}"> + <p ng-message="required">Field is required</p> + </div> - <p class="login__error-msg error-msg" role="alert" ng-if="error">{{description}}</p> + <input id="login__submit" class="btn-primary submit" type="button" value="Log in" role="button" ng-click="login(host, username, password); submitted = true; login__form.$setPristine()" ng-class="{error: error}" ng-disabled="dataService.loading"> + </fieldset> </form> </div> </div> diff --git a/app/login/controllers/login-controller.js b/app/login/controllers/login-controller.js index e31a67e..518ede8 100644 --- a/app/login/controllers/login-controller.js +++ b/app/login/controllers/login-controller.js @@ -17,6 +17,8 @@ window.angular && (function(angular) { '$location', function($scope, $window, dataService, userModel, $location) { $scope.dataService = dataService; + $scope.serverUnreachable = false; + $scope.invalidCredentials = false; $scope.host = $scope.dataService.host.replace(/^https?\:\/\//ig, ''); $scope.tryLogin = function(host, username, password, event) { @@ -27,9 +29,8 @@ window.angular && (function(angular) { } }; $scope.login = function(host, username, password) { - $scope.error = false; - $scope.description = 'Error logging in'; - + $scope.serverUnreachable = false; + $scope.invalidCredentials = false; if (!username || username == '' || !password || password == '' || !host || host == '') { return false; @@ -45,9 +46,10 @@ window.angular && (function(angular) { $window.location.href = next; } } else { - $scope.error = true; - if (description) { - $scope.description = description; + if (description === 'Invalid username or password') { + $scope.invalidCredentials = true; + } else { + $scope.serverUnreachable = true; } } }); diff --git a/app/login/styles/index.scss b/app/login/styles/index.scss index 0bc424b..07d45bf 100644 --- a/app/login/styles/index.scss +++ b/app/login/styles/index.scss @@ -99,8 +99,3 @@ } } -.login__error-msg { - @include mediaQuery(medium) { - max-width: 100%; - } -} |