Advertisement

#18 Finish Animating the Info Pages

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.

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

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);
    }
}
(a) Creating a reference to the DOM of the default info page.

Once again we just need to quickly check that we do have access to the default info page's DOM (b) .

(b) Console output showing the DOM element of the default info page.

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

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]);
    }
    ...
}
(c) Creating helper methods to show/hide the appropriate info page. If null is passed in the default info page is shown/hidden.
Advertisement

Wait to show info until the previous one is hidden

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts

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}.`);
    }
    ...
}
(d) The first step in animating our info pages so that they behave as desired.

Can't be perfect on the first try

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts

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);
        ...
    }
    ...
}
(e) Adding code to delay and cancel, if need be, the displaying of the default info.

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

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;
        }
    }
}
(f) We need to stack the info pages one on top of the other and expanded them horizontally to take up the available space.

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]);
                }
        }
        ...
    }
}
(g) Instead of animating the color we would like to animate the opacity.

Fixing the need urls

  • WebUi
    • Source
      • authenticator
        • authenticator.component.pug
        • authenticator.component.ts
        • authenticator-http.service.ts

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") ...
(h) Modifying the template so that only one need url is displayed at a time and binding each of the anchor's href attribute to the appropriate url.

authenticator-http.service.ts (1)

...
export class AuthenticatorHttpService extends HttpService {
    ...
    public static readonly loginUrl = "/account/login";
    public static readonly registerUrl = "/account/register";
    ...
}
(i) We need to modify the authenticator service so that the login and register urls are publicly available.

authenticator.component.ts (6)

...
export class AuthenticatorComponent implements ... {
    ...
    public get loginUrl() {
        return AuthenticatorHttpService.loginUrl;
    }

    public get registerUrl() {
        return AuthenticatorHttpService.registerUrl;
    }
    ...
}
(j) Just need to create a couple of getters so that our template has something to bind the anchor urls to.
Exciton Interactive LLC
Advertisement