#5 Authenticator Validators
Friday, November 3, 2017
In this article we will finish the ui of our authenticator component by creating and styling the validators for our inputs. Once we have completed this we will also create a service for parsing a url. We will import this service into our component and we will eventually use it to tell whether the user is attempting to login or register.
Parts
- Part 30: User Database
- Part 29: Canonical Url
- Part 28: Razor Page Handlers
- Part 27: Send Screen Finished
- Part 26: Stroke Dashoffset
- Part 25: Send Screen
- Part 24: Forgot Link Behaviour
- Part 23: Animating Controls (cont.)
- Part 22: Animating Controls
- Part 21: Hiding Control Groups
- Part 20: ModelState Errors (cont.)
- Part 19: ModelState Errors
- Part 18: Animating Info Pages (cont.)
- Part 17: Animating Info Pages
- Part 16: Keyboard Navigation
- Part 15: Accessing the DOM
- Part 14: All About the Username
- Part 13: CSRF Attacks
- Part 12: Http Requests
- Part 11: HMR
- Part 10: Color Inputs And Buttons
- Part 9: Animating Sub-Actions
- Part 8: Form Validation (cont.)
- Part 7: Form Validation
- Part 6: Form Group
- Part 5: Authenticator Validators
- Part 4: Authenticator Inputs
- Part 3: First Angular Component
- Part 2: Webpack
- Part 1: Beginning
You can view a completed example of the authenticator component here.
Info pages
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
- authenticator.component.scss
-
authenticator
-
components
-
Source
For the right hand side of our component we will have the concept of an info page. An info page consists of a heading and several statement, usually notification of pass or failure of a particular validator. In (a) we see examples of two information pages, the first starting with the 'Welcome!' heading and the other starting with the 'Username' heading. When we are done we will have a default page, which is the welcome page, and a page for each control which will be displayed when the corresponding control has focus.
authenticator.component.pug (1)
mixin formControlGroup(label)
div.form-control-group
label #{label}
div.input-group.invalid
input
button
i.fa.fa-times
div.authenticator
div.inputs-container
h3 Login
form(novalidate)
+formControlGroup("Username")
div.forgot
a Forgot your username?
div.dual-input-group
+formControlGroup("Password")
+formControlGroup("Confirm Password")
div.forgot
a Forgot your password?
div.dual-input-group
+formControlGroup("Email")
+formControlGroup("Confirm Email")
div.form-control-group.control-group-remember-me
input#remember-me(type="checkbox")
label(for="remember-me")
span #[i.fa]
| Remember me?
div.input-buttons
button Reset
button(disabled="true") Send
div.info-container
div.info-pages
div.info
h3.info-heading Welcome!
div.info-validator-container
div.info-validator
p.validator Hello again, we are happy to see you. Please enter your username and password to login.
div.info
h3.info-heading Username
div.info-validator-container
div.info-validator
p.validator Lorem ipsum dolor sit amet
i.validator-icon.fa.fa-times.invalid
div.info-validator
p.validator Lorem ipsum dolor sit amet
i.validator-icon.fa.fa-times.invalid
div.info-validator
p.validator Lorem ipsum dolor sit amet
i.validator-icon.fa.fa-times.invalid
div.need-to-login
a Already have an account?
div.need-to-register
a Need to register an account?
authenticator.component.scss (1)
$info-heading-background-color: darken($info-container-background-color, 2%);
.authenticator {
.info-container {
display: flex;
flex-direction: column;
color: $info-container-color;
}
.info-pages {
flex: 1;
}
.need-to-login, .need-to-register {
text-align: right;
padding: em(10px);
a {
color: $info-container-color;
}
}
.info-heading {
padding: em(10px);
background-color: $info-heading-background-color;
border-bottom: 1px solid #3b3b3b;
h3 {
margin-bottom: 0;
}
}
.info-validator {
display: flex;
flex-direction: row;
margin-bottom: em(4px);
padding: em(5px) em(10px);
&:nth-child(2n) {
background-color: $info-heading-background-color;
}
.validator {
margin-bottom: 0;
flex: 1;
color: $info-container-color;
}
.validator-icon {
padding-top: em(2px);
&.fa-circle-o-notch {
color: $indeterminate-color;
}
&.fa-check {
color: $valid-color;
}
&.fa-times {
color: $invalid-color;
font-size: 1.3em;
}
}
}
}
We are also adding links for users to be able to change from login to register at the bottom of the info pages side of the component.
Info Pages Mixins
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
-
authenticator
-
components
-
Source
In (a) we see that .info
, .validator
, and
the .validator-icon
classes are used repeatedly and would benefit greatly from mixins of their own. So in
(a) we have added the info
and validator
mixins.
We are splitting it into two different mixins because each info page will potentially contain a set of validators that is different than every other page.
authenticator.component.pug (2)
mixin formControlGroup(label)
div.form-control-group
label #{label}
div.input-group.invalid
input
button
i.fa.fa-times
mixin info(heading)
div.info
h3.info-heading #{heading}
div.info-validator-container
block
mixin validator(message)
div.info-validator
p.validator #{message}
i.validator-icon.fa.fa-times.invalid
div.authenticator
div.inputs-container
h3 Login
form(novalidate)
+formControlGroup("Username")
div.forgot
a Forgot your username?
div.dual-input-group
+formControlGroup("Password")
+formControlGroup("Confirm Password")
div.forgot
a Forgot your password?
div.dual-input-group
+formControlGroup("Email")
+formControlGroup("Confirm Email")
div.form-control-group.control-group-remember-me
input#remember-me(type="checkbox")
label(for="remember-me")
span #[i.fa]
| Remember me?
div.input-buttons
button Reset
button(disabled="true") Send
div.info-container
div.info-pages
+info("Welcome!")
div.info-validator
p.validator Hello again, we are happy to see you. Please enter your username and password to login.
+info("Username")
+validator("Lorem ipsum dolor sit amet")
+validator("Lorem ipsum dolor sit amet")
+validator("Lorem ipsum dolor sit amet")
div.need-to-login
a Already have an account?
div.need-to-register
a Need to register an account?
The final set of info pages
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
-
authenticator
-
components
-
Source
We have already added the default and username info pages but we need to add a few more. In particular we need to add a page for: password, confirm password, email and confirm email controls. We can do this very simply by making calls to the mixins that we created earlier. Soon we will add actual validators to these pages for the corresponding controls but for now the temporary text will do. Once we have added interaction to our component we will stack all of the pages on-top of each other so that we can fade from one to the other in the same location.
authenticator.component.pug (3)
mixin formControlGroup(label)
div.form-control-group
label #{label}
div.input-group.invalid
input
button
i.fa.fa-times
mixin info(heading)
div.info
div.info-heading
h3 #{heading}
div.info-validator-container
block
mixin validator(message)
div.info-validator
p.validator #{message}
i.validator-icon.fa.fa-times.invalid
div.authenticator
div.inputs-container
h3 Login
form(novalidate)
+formControlGroup("Username")
div.forgot
a Forgot your username?
div.dual-input-group
+formControlGroup("Password")
+formControlGroup("Confirm Password")
div.forgot
a Forgot your password?
div.dual-input-group
+formControlGroup("Email")
+formControlGroup("Confirm Email")
div.form-control-group.control-group-remember-me
input#remember-me(type="checkbox")
label(for="remember-me")
span #[i.fa]
| Remember me?
div.input-buttons
button Reset
button(disabled="true") Send
div.info-container
div.info-pages
div.info
div.info-heading
h3 Welcome!
div.info-validator-container
div.info-validator
p.validator Hello again, we are happy to see you. Please enter your username and password to login.
+info("Username")
+validator("Lorem ipsum dolor sit amet")
+validator("Lorem ipsum dolor sit amet")
+validator("Lorem ipsum dolor sit amet")
+info("Password")
+validator("Lorem ipsum dolor sit amet")
+info("Confirm Password")
+validator("Lorem ipsum dolor sit amet")
+info("Email Address")
+validator("Lorem ipsum dolor sit amet")
+info("Confirm Email Address")
+validator("Lorem ipsum dolor sit amet")
div.need-to-login
a Already have an account?
div.need-to-register
a Need to register an account?
Actual Validators
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.pug
-
authenticator
-
components
-
Source
Now that we have added all of the info pages that we need it's time to remove the temporary validators and replace them with the actual validators that we will be needing. At this point though this just means adding the correct number to each page and replacing their temporary messages with real messages.
authenticator.component.pug (4)
mixin formControlGroup(label)
div.form-control-group
label #{label}
div.input-group.invalid
input
button
i.fa.fa-times
mixin info(heading)
div.info
div.info-heading
h3 #{heading}
div.info-validator-container
block
mixin validator(message)
div.info-validator
p.validator #{message}
i.validator-icon.fa.fa-times.invalid
div.authenticator
div.inputs-container
h3 Login
form(novalidate)
+formControlGroup("Username")
div.forgot
a Forgot your username?
div.dual-input-group
+formControlGroup("Password")
+formControlGroup("Confirm Password")
div.forgot
a Forgot your password?
div.dual-input-group
+formControlGroup("Email")
+formControlGroup("Confirm Email")
div.form-control-group.control-group-remember-me
input#remember-me(type="checkbox")
label(for="remember-me")
span #[i.fa]
| Remember me?
div.input-buttons
button Reset
button(disabled="true") Send
div.info-container
div.info-pages
div.info
div.info-heading
h3 Welcome!
div.info-validator-container
div.info-validator
p.validator Hello again, we are happy to see you. Please enter your username and password to login.
+info("Username")
+validator("Unique")
+validator("Required")
+validator("Minimum length is 8")
+validator("Maximum length is 100")
+validator("May only contain alphanumeric and '-._@+' characters")
+info("Password")
+validator("Required")
+validator("Minimum length is 12")
+validator("Maximum length is 100")
+validator("Must contain at least one lowercase letter")
+validator("Must contain at least one capital letter")
+validator("Must contain at least one digit")
+validator("Must contain at least one character that is not a letter or number")
+info("Confirm Password")
+validator("The password and confirm password must match.")
+info("Email Address")
+validator("Required")
+validator("Must be a valid email address")
+info("Confirm Email Address")
+validator("The email address and confirm email address must match.")
div.need-to-login
a Already have an account?
div.need-to-register
a Need to register an account?
Url Parsing
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
services
- uri-parser.service.ts
-
components
-
Source
Finally we will add a small service (i) that we will use to parse the current url of the browser. As I
have mentioned before we will use the url to determine whether or not our component should be configured for logging in or
registering. The code that we will be using for this has been slightly adapted from
code written by Steven Levithan which you
can find here. Once we have the parser service in place we can then
import it into our authenticator.component.ts file (j). By adding a reference to the
UriParser
in the metadata of our component, within the newly added providers array, angular will inject an instance
into our component constructor.
uri-parser.service.ts
export interface IUriParserResult {
anchor: string;
authority: string;
directory: string;
file: string;
host: string;
password: string;
path: string;
port: string;
protocol: string;
query: string;
queryKey: any;
relative: string;
source: string;
user: string;
userInfo: string;
}
export class UriParserService {
/**
* The parse options
*/
public options = {
strictMode: false,
key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
}
public parseUri =
/**
* Parses the provide string.
* @param str
* @returns {}
*/
(str: string): IUriParserResult => {
const o = this.options;
const m = o.parser[o.strictMode ? "strict" : "loose"].exec(str);
const uri = {};
let i = 14;
while (i--) uri[o.key[i]] = m[i] || "";
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, ($0: any, $1: any, $2: any) => {
if ($1) uri[o.q.name][$1] = $2;
});
return uri as IUriParserResult;
};
}
authenticator.component.ts
import { Component } from "@angular/core";
import { UriParserService } from "../../services/uri-parser.service";
@Component({
selector: "authenticator",
template: require("./authenticator.component.pug"),
styles: [require("./authenticator.component.scss")],
providers: [UriParserService]
})
export class AuthenticatorComponent {
constructor(private _uriParser: UriParserService) {
console.log(this._uriParser.parseUri(window.location.href));
}
}
In (k) we see the result of parsing of a url using our uri parsing service. From the results we can see that there are several properties that would can use to determine if the user is trying to login or register with our application. In the end we will probably just parse the path property.
In the next video we will begin the process of adding user interaction to our component by creating the means of applying conditional css classes to our template.