Advertisement

#21 Hiding the Control Groups

In this article we will focus on adding the ability to hide control groups when we wish to but during the process we will identify an issue with our input pages which will require us to modify how we are hiding them as well. We will also hide the forgot links when the user is registering, add a little bit of styling and finsih up with adding the ability to reset our form when the user presses the reset button.

You can view a completed example of the authenticator component here.

Setting up the ability to hide groups

  • WebUi
    • Source
      • components
        • authenticator
          • control-group.ts
          • control-pair.ts
          • control-pair-collection.ts

We will start as we have before by creating methods to hide and show our control groups (a) and propagating that ability up the chain. That means after adding the methods to the control groups we will add them to the control pairs (b) and finally to the control pair collection (c). Our approach to hiding the control groups is to add a css class using the classlist api and then removing that class to show the groups.

control-group.ts (1)

...
export class ControlGroup {
    ...
    private get hiddenGroupClass() {
        return "hidden-group";
    }
    ...
    public hideGroup = () => {
        this.groupDOM.classList.add(this.hiddenGroupClass);
    }
    ...
    public showGroup = () => {
        this.groupDOM.classList.remove(this.hiddenGroupClass);
    }
}
(a) Adding methods to hide and show the control groups by applying and removing a css class.

control-pair.ts (1)

...
export class ControlPair {
    ...
    public hideGroup = (id: string) => {
        this.find(id).hideGroup();
    }
    ...
    public showGroup = (id: string) => {
        this.find(id).showGroup();
    }
    ...
}
(b) In order to hide/show a group we need to add those methods to our control pairs.

control-pair-collection.ts (1)

export class ControlPairCollection {
    ...
    public hideGroup = (id: string) => {
        this._pairs[id].hideGroup(id);
    }
    ...
    public showGroup = (id: string) => {
        this._pairs[id].showGroup(id);
    }
    ...
}
(c) The last step is to add the hide/show methods to our control pair collection.

Actually hiding a control group

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

Now that we can add/remove the 'hidden-group' css class to/from our control groups we need to provide the styling for that class. It is probably pretty easy to guess what that styling is going to be. We are simple going to set the display to none as shown in (d). To see that everything is working the way we intend it to we can just test it by hiding the password confirm control group (e).

authenticator.component.scss (1)

.authenticator {
    .hidden-group {
        display: none;
    }
}
(d) When we want to hide a control group we will just set it's display property to none.

authenticator.component.ts (1)

...
export class AuthenticatorComponent ... {
    ...
    public ngAfterViewInit(): void {
        ...
        this._controlPairCollection.hideGroup(this._keyToGroupIdMap[this.controlPasswordConfirmKey]);
    }
}
(e) To test that everything is working we will just hide the password confirm control group.

We can see in (f) that the password confirm control group does in fact have the 'hidden-group' class applied to it which results in it being hidden in (g).

Image showing the password confirm control group has the 'hidden-group'
        css class applied to it.
(f) Image showing the password confirm control group has the 'hidden-group' css class applied to it.
Image showing that with the css class applied the password confirm control group is hidden.
(g) Image showing that with the css class applied the password confirm control group is hidden.

Now that we are sure it is working we need to remove the this._controlPairCollection.hideGroup(this._keyToGroupIdMap[this.controlPasswordConfirmKey]); statement from our code.

Hide control groups when they are instantiated

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts
          • control-group.ts
          • control-pair-collection.ts

We do have several control groups that we would like to have hidden by default. For example whether the user is attempting to login or register we would like both confirm groups to be hidden to begin with. We would also like the email group to be hidden, again at the start, if the user is attempting to login. To accomplish this we start by defining a parameter on the config object for creating a control group which is an optional boolean (h). If this boolean is defined and set to true we will invoke the hide method when the group is created. In order to be able to pass this parameter into our control group constructors we also need to modify the control pair collection (i). With these changes made we can now specify whether a control group should be hidden from the start (j).

control-group.ts (2)

import loDashIsNil = require("lodash/isNil");
...
export interface IControlGroupConfig {
    ...
    hideGroup?: boolean;
    ...
}

export class ControlGroup {
    ...
    constructor(...) {
        ...
        if (config.hideGroup) {
            this.hideGroup();
        }
    }
    ...
}
(h) Adding an optional boolean to our constructor config object to specify whether the group should be hidden to start with.

control-pair-collection.ts (2)

...
export interface ICreatePairConfig {
    ...
    hideGroup?: boolean;
    ...
}
...
export class ControlPairCollection {
    ...
    public create = (...) => {
        const createConfig = (c: ICreatePairConfig) => {
            return {
                ...
                hideGroup: c.hideGroup,
                ...
            } ...
        };
        ...
    }
    ...
}
(i) To be able to pass the new boolean to our control groups we need to modify how they are constructed within our control pair collection.

authenticator.component.ts (2)

export class AuthenticatorComponent ... {
    ...
    public ngAfterViewInit(): void {
        this._controlPairCollection.create(
            { // Email
                hideGroup: this.isLogin,
                ...
            },
            { // Email Confirm
                hideGroup: true,
                ...
            });
        this._controlPairCollection.create(
            { // Password
                ...
            },
            { // Password Confirm
                hideGroup: true,
                ...
            });
        this._controlPairCollection.create(
            { // Remember Me
                ...
                hideGroup: this.isRegister,
                ...
            });
        ...
    }
    ...
}
(j) Now that we have the ability to hide groups from the start we need specify which should be hidden.

Now that we are specifying which groups should be hidden when they are constructed the ui when the user is attempting to login should look like (k) and if they are attempting to register it should look like (l).

When the user is attempting to login we only need to show the username and password groups.
(k) When the user is attempting to login we only need to show the username and password groups.
When the user is attempting to register we need to show the username, password, and email groups.
(l) When the user is attempting to register we need to show the username, password, and email groups.
Advertisement

A problem lurking in the background

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.pug
          • authenticator.component.scss

The next thing we want to do is to remove the forgot urls from the interface if the user is registering. This is of course a very simple thing to do by using a couple of *ngIf statements within the template (m). I would also like to have some separation between the last control and the reset and send buttons so we need to put the control groups in their own container so that we can expand it to fill the available space thereby pushing the buttons down. In order to create the separation we will also set a minimum height to the inputs container and while we are at it we set the info pages overflow property and style the scrollbars. Although I really do like the styling of the scrollbars I should mention that this will only work on webkit browsers for now.

authenticator.component.pug (1)

...
div.authenticator
    div.inputs-container#inputs-container
        ...
        form.inputs(...)
            ...
            div.forgot(*ngIf="isLogin")
                ...
            ...
            div.forgot(*ngIf="isLogin")
                ...
            ...
        div.input-buttons
            ...
(m) Modifying the template so that we only see the forgot links if the user is logging in and we have placed the controls in their own container at the same level as the buttons container.

authenticator.component.scss (2)

::-webkit-scrollbar {
    width: 16px;
    background-color: #292929;
}

::-webkit-scrollbar-track {
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
    border-radius: 0;
    background-color: #292929;
}

::-webkit-scrollbar-thumb {
    border-radius: 0;
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: #484848;
}

.authenticator {
    .inputs-container {
        min-height: 383px;
        display: flex;
        flex-direction: column;

        .inputs {
            flex: 1;
        }
    }

    .info-pages {
        overflow-y: auto;
    }
}
(n) Various styling changes ranging from setting a minimum height to our inputs container and changing its display to flex to styling the look of the scrollbars in webkit browsers.

With the scrollbars being shown now we can see that we have a bit of problem. The problem is we are just setting the opacity to zero for hidden info pages so although we cannot see them they are still influencing the way the DOM is being displayed. Although we can see it now the problem already existed when the changes that we made resulted in the height of the ui getting smaller, especially for logging in. In that case the 'hidden' info pages were already overlapping the need to register an account link making it impossible to click it. We will fix these problems in the next section.

Image showing the current state of our login ui.
(o) Image showing the current state of our login ui.
Image showing the current state of our register ui.
(p) Image showing the current state of our register ui.

Hidden info pages should actually be hidden

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.scss
          • authenticator.component.ts
          • control-group.ts

We have already talked about the problem being we are only setting the opacity to 0 when hiding an info page. Our solution is of course to do the same thing that we did with the control groups and add/remove a css class that we can use to change a page's display to none when appropriate. This is of course complicated a little bit by the fact that we would like the hide animation to finish before adding this class. We start by defining the hidden info class and applying it to our info DOM when the control is constructed if the control does have an info page (we are looking at you remember me) (q). It's also very easy for us to deal with the show info method since we just need to remove the hidden class before we do anything else. The slightly difficult part is when we want to hide the info we will modify our code to remove the class when the hide animation is complete. Now from the standpoint of the control group this doesn't really add any complication. Before we get to the complication we just need to define the hidden info class (r). Now we turn to our component's typescript class. The complication that I was mentioning stems from the fact that when we are showing an info page we are hiding the previous page and in so doing we are setting the onfinish function which will just over write what we added in the control group. To deal with this we will simple cache any onfinish function that already exists, set our own, and then call the cached function when the hide animation is complete (s).

control-group.ts (3)

...
export class ControlGroup {
    ...
    private get hiddenInfoClass() {
        return "hidden-info";
    }
    ...
    constructor(config: IControlGroupConfig) {
        ...
        if (typeof this._infoId !== "undefined") {
            this.infoDOM.classList.add(this.hiddenInfoClass);
        }
    }
    ...
    public hideInfo = () => {
        const animation = this.infoDOM.animate(this._hideInfoEffect, this._infoTiming);
        animation.onfinish = () => {
            this.infoDOM.classList.add(this.hiddenInfoClass);
        };
        return animation;
    }
    ...
    public showInfo = () => {
        this.infoDOM.classList.remove(this.hiddenInfoClass);
        ...
    }
    ...
}
(q) We need to add the ability to set the display of a control group's info page to none by using a css class.

authenticator.component.scss (3)

.authenticator {
    .hidden-info {
        display: none;
    }
}
(r) When the hidden info class is applied to an info page we just want to set its display to none.

authenticator.component.ts (3)

...
export class AuthenticatorComponent ... {
    ...
    private showInfo = (key: string) => {
        ...
        let onfinish: AnimationEventListener;
        switch(...) {
            case true:
                ...
                onfinish = animation.onfinish;
                animation.onfinish = (evt: AnimationPlaybackEvent) => {
                    if (loDashIsNil(onfinish) === false) {
                        onfinish(evt);
                    }
                    ...
                };
                return;
            case false:
                if (...) {
                    onfinish = this._blurHideAnimation.onfinish;
                    this._blurHideAnimation.onfinish = (evt: AnimationPlaybackEvent) => {
                        if (loDashIsNil(onfinish) === false) {
                            onfinish(evt);
                        }
                        ...
                    };
                } else {
                    ...
                }
                return;
        }
        ...
    }
    ...
}
(s) Modifying our show info method with our component's typescript class to take into account that we have set the onfinish function within the control group.

With these changes made the ui is performing almost as expected. It now only shows the scrollbars when a particular control is focused and its info page is tall enough to warrant it. I say that it is almost performing as expected because the way we have set it up now the default info page never has the hidden info class applied to it since it does not have an associated control group. If you guessed that we would be fixing this in the next section you would be absolutely correct.

The login ui is no longer showing the scrollbars when the default info
            page is shown.
(t) The login ui is no longer showing the scrollbars when the default info page is shown.
The register ui is also no longer showing the scrollbars when the default
            info page is shown.
(u) The register ui is also no longer showing the scrollbars when the default info page is shown.

You are not special default info page

  • WebUi
    • Source
      • components
        • authenticator
          • authenticator.component.ts
          • control-group.ts
          • control-pair-collection.ts

Our solution to this current difficulty is the same as the one that we used when we wanted to animate the default info page. Instead of defining the hidden info class within the control group we will pass it in when they are constructed (v). This of course means we need to pass it into our control pair collection (w). And again since this is something needed for every control group we will pass it in to the constructor of our control pair collection and the collection will pass it into the control groups. Lastly we just need to define it within the authenticator component, pass it in when constructing the control pair collection, and add/remove it when hidding/showing the default info page (x).

control-group.ts (4)

...
export interface IControlGroupConfig {
    ...
    hiddenInfoClass: string;
    ...
}

export class ControlPair {
    ...
    private readonly _hiddenInfoClass: string = null;
    ...
    constructor(config: IControlGroupConfig) {
        ...
        this._hiddenInfoClass  = config.hiddenInfoClass;
        ...
        if (typeof this._infoId !== "undefined") {
            this.infoDOM.classList.add(this._hiddenInfoClass);
        }
    }
    ...
    public hideInfo = () => {
        ...
        animation.onfinish = () => {
            this.infoDOM.classList.add(this._hiddenInfoClass);
        };
        ...
    }
    ...
    public showInfo = () => {
        this.infoDOM.classList.remove(this._hiddenInfoClass);
        ...
    }
    ...
}
(v) Setting up the control group to be passed the hidden info class instead of defining it here.

control-pair-collection.ts (3)

...
export interface IControlPairCollectionConfig {
    ...
    hiddenInfoClass: string;
    ...
}
...
export class ControlPairCollection {
    ...
    private readonly _hiddenInfoClass: string = null;
    ...
    constructor(config: IControlPairCollectionConfig) {
        ...
        this._hiddenInfoClass = config.hiddenInfoClass;
        ...
    }

    public create = (left: ICreatePairConfig, right?: ICreatePairConfig) => {
        const createConfig = (c: ICreatePairConfig) => {
            return {
                ...
                hiddenInfoClass: this._hiddenInfoClass,
                ...
            } as IControlGroupConfig;
        };
        ...
    }
}
(w) We will pass the hidden info class into the constructor of our control pair collection and the collection will pass it to each control group.

authenticator.component.ts (4)

...
export class AuthenticatorComponent ... {
    ...
    private get hiddenInfoClass() {
        return "hidden-info";
    }
    ...
    constructor(...) {
        ...
        this._controlPairCollection = new ControlPairCollection({
            ...
            hiddenInfoClass: this.hiddenInfoClass,
            ...
        });
        ...
    }
    ...
    private hideInfo = (...) => {
        if (key === null) {
            ...
            const animation = this._defaultInfoDOM.animate(this._hideInfoEffect, this._infoTiming);
            animation.onfinish = () => {
                this._defaultInfoDOM.classList.add(this.hiddenInfoClass);
            };
            return animation;
        }
        ...
    }
    ...
    private showInfo = (key: string) => {
        if (key === null) {
            ...
            this._defaultInfoDOM.classList.remove(this.hiddenInfoClass);
            ...
        }
        ...
    }
}
(x) Last step is to define the hidden info class, pass it in when constructing the control pair collection and adding/removing it when we hide/show the default info page.
Exciton Interactive LLC
Advertisement