#18 Finish Animating the Info Pages
Thursday, February 1, 2018
In this article we pick up where we left off and finish the animation of our info pages. By the time we finish this article the pages will be hidden and only shown one by one depending on which control has focus. If no control has focus we will show the default page. Once the info pages are complete we will modify the need to login and need to register anchors to only be displayed one at a time depending on whether the user is attempting to login or register. We will also set their href attribute so they behave correctly when clicked.
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.
We need a reference to the default info page
-
WebUi
-
Source
-
components
- authenticator.component.ts
-
components
-
Source
Next we need to have a reference to the DOM for the default page and since we know for sure that we will need it no matter what there is no need to use the lazy getter method that we have used previously (a). For testing we are also going to log the reference to the console as usual.
authenticator.component.ts (1)
...
export class AuthenticatorComponent implements ... {
...
private _defaultInfoDOM: HTMLElement = null;
...
public ngAfterViewInit(): void {
...
this._defaultInfoDOM = this._domReader.findChildById(this._elementRef.nativeElement, this.defaultInfoId);
console.log(this._defaultInfoDOM);
}
}
Once again we just need to quickly check that we do have access to the default info page's DOM (b) .
With the test complete we can remove console.log(this._defaultInfoDOM);
statement from our code.
Show and hide helper methods
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
components
-
Source
Since we are going to need to show the default page to start with, as well as when a user focuses and input,
we are going to create
showInfo
and showInfo
helper
methods (c). The main point to notice is that we will pass in
null
when we want to show/hide the default page.
authenticator.component.ts (2)
...
export class AuthenticatorComponent implements ... {
...
public ngAfterViewInit(): void {
...
this.showInfo(null);
}
...
public onBlurInput = (key: string) => {
...
this.hideInfo(key);
}
public onFocusInput = (key: string) => {
this.showInfo(key);
}
...
private hideInfo = (key: string) => {
if (key === null) {
this._defaultInfoDOM.animate(this._hideInfoEffect, this._infoTiming);
return;
}
this._controlPairCollection.hideInfo(this._keyToGroupIdMap[key]);
}
private showInfo = (key: string) => {
if (key === null) {
this._defaultInfoDOM.animate(this._showInfoEffect, this._infoTiming);
return;
}
this._controlPairCollection.showInfo(this._keyToGroupIdMap[key]);
}
...
}
Wait to show info until the previous one is hidden
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
components
-
Source
The way that our animations are set up currently is that if an input is blurred and a new one is immediately focused then the hide and show animations are performed at the same time. This is not the behaviour we would like to have. We would like the showing of the new info to occur after the previous one is hidden. Our first step in achieving our desired effect is shown in (d).
authenticator.component.ts (3)
...
export class AuthenticatorComponent implements ... {
...
private _isDefaultInfoActive: boolean = false;
...
public onBlurInput = (key: string) => {
...
const animation = this.hideInfo(key);
animation.onfinish = () => {
this.showInfo(null);
};
}
...
private hideInfo = (...) => {
if (key === null) {
this._isDefaultInfoActive = false;
return this._defaultInfoDOM.animate(this._hideInfoEffect, this._infoTiming);
}
return this._controlPairCollection.hideInfo(this._keyToGroupIdMap[key]);
}
private showInfo = (...) => {
if (key === null) {
this._isDefaultInfoActive = true;
...
}
switch (this._isDefaultInfoActive) {
case true:
const animation = this.hideInfo(null);
animation.onfinish = () => {
this._controlPairCollection.showInfo(this._keyToGroupIdMap[key]);
return;
};
return;
case false:
this._controlPairCollection.showInfo(this._keyToGroupIdMap[key]);
return;
}
throw new Error(`The field '_isDefaultInfoActive' should be true or false but found ${this._isDefaultInfoActive}.`);
}
...
}
Can't be perfect on the first try
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.ts
-
authenticator
-
components
-
Source
The next problem that we need to solve is that if we move the focus from one input to another one after the other the default info is going to be displayed anyway. To solve this we will introduce a timeout, which we can cancel, that we will use to display the default info. If the timeout is allowed to run its course then the default info page will be displayed otherwise it will not be.
authenticator.component.ts (4)
...
export class AuthenticatorComponent implements ... {
...
private _defaultInfoTimeout: any = null;
...
public onBlurInput = (key: string) => {
...
this.hideInfo(key);
this._defaultInfoTimeout = setTimeout(() => {
this.showInfo(null);
}, this._infoTiming.duration + 100);
}
...
private showInfo = (key: string) => {
...
clearTimeout(this._defaultInfoTimeout);
...
}
...
}
With these changes we now have the animation behaviour that we were looking for.
I don't like yellow
-
WebUi
-
Source
-
components
-
authenticator
- authenticator.component.scss
- authenticator.component.ts
-
authenticator
-
components
-
Source
With the animation behaviour working correctly we can turn to actually animating the properties that we would like to be animated. In this case we will begin by adding just a little bit of styling (f). This styling will accomplish placing the info pages one on top of the other and making sure they expand horizontally to fill the available space. With that done we simple need to change the animation effects from changing the color of the pages to changing their opacities (g).
authenticator.component.scss (1)
.authenticator {
.info-pages {
position: relative;
.info {
position: absolute;
opacity: 0;
left: 0;
right: 0;
}
}
}
authenticator.component.ts (5)
...
export class AuthenticatorComponent implements ... {
...
private readonly _hideInfoEffect: AnimationKeyFrame = {
opacity: [1, 0]
};
private readonly _showInfoEffect: AnimationKeyFrame = {
opacity: [0, 1]
};
...
private _blurHideAnimation: Animation = null;
...
public onBlurInput = (key: string) => {
...
this._blurHideAnimation = this.hideInfo(key);
...
}
...
private showInfo = (key: string) => {
...
switch (...) {
case true:
...
case false:
if (this._blurHideAnimation !== null && this._blurHideAnimation.playState !== "finished") {
this._blurHideAnimation.onfinish = () => {
this._controlPairCollection.showInfo(this._keyToGroupIdMap[key]);
};
} else {
this._controlPairCollection.showInfo(this._keyToGroupIdMap[key]);
}
}
...
}
}
Fixing the need urls
-
WebUi
-
Source
-
authenticator
- authenticator.component.pug
- authenticator.component.ts
- authenticator-http.service.ts
-
authenticator
-
Source
The last that we will take care of in this article is to modify the need to login and need to register anchors located at the bottom of our info container. We will start by modifying the template file so that only one anchor is shown depending on whether the user is attempting to login or register (h). While we are at it we will also bind each of the anchor's href attribute to a property on our typescript class, which we will define shortly. Next we need to modify our authenticator http service so that we have access to both the login and register urls (i). Finally we just need to modify our typescript class so that our template can access the urls (j).
authenticator.component.pug (1)
...
div.auth-container
div.auth-info-container
...
div.need-to-login(*ngIf="isRegister")
a([href]="loginUrl") ...
div.need-to-register(*ngIf="isLogin")
a([href]="registerUrl") ...
authenticator-http.service.ts (1)
...
export class AuthenticatorHttpService extends HttpService {
...
public static readonly loginUrl = "/account/login";
public static readonly registerUrl = "/account/register";
...
}
authenticator.component.ts (6)
...
export class AuthenticatorComponent implements ... {
...
public get loginUrl() {
return AuthenticatorHttpService.loginUrl;
}
public get registerUrl() {
return AuthenticatorHttpService.registerUrl;
}
...
}