Advertisement

#27 Finishing the Send Screen

In this video we will add and animate a timer svg element to our send screen that will indicate to the user when the screen will be hidden or an action will be taken. We will also add a new svg element that will be used when the server responds with a success status code. We will end by adding a background element whose background color we will animate to indicate whether the current request was successful or not.

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

We need to add a timer

  • WebUi
    • Source
      • components
        • authenticator
          • send-screen.component.pug
          • send-screen.component.ts

It's very important that we try to eliminate any possibility of the user wondering if our application or components are actually doing anything. To that end we need to add a timer element that will let the user know when our send screen will perform an action after notifying them whether a request was successful or not. The timer that we will use is another svg line that we will add at the top of the svg element (a). Of course as usual in order to animate it we will add the familiar fields and properties to our typescript class (b). It also makes sense of course to create a helper method that we can call from different methods in order to animate the timer.

send-screen.component.pug (1)

div.send-screen(...)
    svg(...)
        | <line [attr.id]="timerId" fill="none" stroke-width="4" x1="0" y1="2" x2="638" y2="2"/>
        ...
(a) Adding a simple line element to our svg to act as a timer.

send-screen.component.ts (1)

...
export class SendScreenComponent ... {
    ...
    private _pathLengthTimer: number = null;
    ...
    private _timerDOM: SVGLineElement = null;
    ...
    public get timerId() { return "assp-s-timer"; }
    ...
    public ngAfterViewInit(): void {
        ...
        this._timerDOM = this._domReader.findChildById(this._sendScreenDOM, this.timerId) as any;

        this._pathLengthTimer = this.calculatePathLength(this._timerDOM);
    }
    ...
    public failure = (message: string) => {
        ...
        mainAnimation.onfinish = () => {
            ...
            supAnimation.onfinish = () => {
                this.animateTimer(() => {
                    this.xmarkMainDOM.style.stroke = "none";
                    this.xmarkSupDOM.style.stroke = "none";
                });
            };
        };
    }
    ...
    private animateTimer = (onhide?: () => void) => {
        this._timerDOM.style.stroke = "white";

        this._timerDOM.style.strokeDasharray = `${this._pathLengthTimer}`;
        const animation = this._timerDOM.animate(
            {
                strokeDashoffset: [0, this._pathLengthTimer]
            },
            {
                duration: 3000,
                easing: "linear",
                fill: "forwards"
            });

        animation.onfinish = () => {
            this._timerDOM.style.stroke = "none";

            this.hide(onhide);
        };
    }
}
(b) Adding the usual fields and properties so that we can animate an element as well as creating a helper method to do the animating.

In (c) I have adjust the stoke color to be blue so that we can see the placement of the timer and in (d) we can see what it looks like after having run for about a second or so.

Image showing our svg timer element with a temporary blue stroke so that we can see
            where it is placed within our svg.
(c) Image showing our svg timer element with a temporary blue stroke so that we can see where it is placed within our svg.
Image showing what our timer element looks like after having been animated
            for about one second or so.
(d) Image showing what our timer element looks like after having been animated for about one second or so.

At some point we are going to succeed

  • WebUi
    • Source
      • components
        • authenticator
          • send-screen.component.pug
          • send-screen.component.ts

So far the only thing our send screen is capable of reacting to is an error response from the server. Now it's time to think positively and create elements and methods that we can use if the server responds with a success code. The first step that we will take is to create a checkmark element within our svg (e). After taking the usual steps to be able to animate our checkmark we need to also create a success method that can be called with a message and to create an animatePath method that both our (f).

send-screen.component.pug (2)

div.send-screen(...)
    svg(...)
        ...
        | <path [attr.id]="checkmarkId" stroke-width="20" fill="none" d="M406.298,80.19c-21.572-22.361-51.826-36.305-85.501-36.305
        | c-65.506,0-118.912,53.405-118.912,118.912s53.405,118.912,118.912,118.912c65.507,0,118.912-53.405,118.912-118.912
        | c0-22.625-6.313-43.671-17.101-61.561l0,0L311.326,212.782l-54.983-54.984"/>
(e) Adding the checkmark path that we can use if the server responds with a success code.

send-screen.component.ts (2)

...
export class SendScreenComponent ... {
    ...
    private _checkmarkDOM: SVGPathElement = null;
    ...
    private _pathLengthCheckmark: number = null;
    ...
    public get checkmarkId() { return "assp-c-mark"; }
    ...
    public ngAfterViewInit(): void {
        ...
        this._checkmarkDOM = this._domReader.findChildById(this._sendScreenDOM, this.checkmarkId) as any;
        ...
        this._pathLengthCheckmark = this.calculatePathLength(this._checkmarkDOM);
        ...
    }
    ...
    public failure = (message: string) => {
        ...
        const stroke = "red";

        const mainAnimation = this.animatePath(this.xmarkMainDOM, this.pathLengthXmarkMain, mainDuration, stroke);
        mainAnimation.onfinish = () => {
            const supAnimation = this.animatePath(this.xmarkSupDOM, this.pathLengthXmarkSup, mainDuration * this.pathLengthXmarkSup / this.pathLengthXmarkMain, stroke);
            ...
        };
    }
    ...
    public success = (message: string) => {
        this.message = message;

        const mainDuration = 1500;
        const stroke = "green";

        const animation = this.animatePath(this._checkmarkDOM, this._pathLengthCheckmark, mainDuration, stroke);
        animation.onfinish = () => {
            this.animateTimer(() => {
                this._checkmarkDOM.style.stroke = "none";
            });
        };
    }
    ...
    private animatePath = (element: SVGElement, length: number, duration: number, stroke: string) => {
        element.style.strokeDasharray = `${length}`;
        element.style.strokeDashoffset = `${length}`;
        element.style.stroke = stroke;

        return element.animate(
            {
                strokeDashoffset: [length, 0]
            },
            {
                duration: duration,
                easing: "linear",
                fill: "forwards"
            });
    }
    ...
}
(f) We need to add a success to go along with our failure method and a animatePath method that both can use to animate their respective paths.

By adding a temporary green stroke to our checkmark we can see what it looks like (g).

Image showing what our new checkmark element looks like with a temporary
            green stroke.
(g) Image showing what our new checkmark element looks like with a temporary green stroke.

Our login method is never happy

  • WebUi
    • Pages
      • Account
        • Login.cshtml.cs
    • Source
      • components
        • authenticator
          • authenticator.component.ts

As it stands now the way we have implemented our login action we will always receive an error response so it is time to modify it so that we can test how our send screen will respond to a success code (h). After that is done we need to return to our authenticator component and add the call to the success method (i).

Login.cshtml.cs (1)

...
namespace WebUi.Pages.Account
{
    public class LoginModel : PageModel
    {
        ...
        public IActionResult OnPost(...)
        {
            ...
            if (request.Username == "chris.jamell")
            {
                ModelState.AddModelError("Username", "Pick a better username.");
                ModelState.AddModelError("Username", "A random error.");
                ModelState.AddModelError("Password", "Another random error.");
            }
            ...
        }
    }
}
(h) We need to move our code that is adding errors to our modelstate inside an if statement so that we can actually receive a success response from the server.

authenticator.component.ts (1)

...
export class AuthenticatorComponent ... {
    ...
    private login = () => {
        ...
        this._authenticatorHttpService.login(...,
            () => {
                this._sendScreen.success("Login was successful and you will be redirected shortly.");
            },
            () => {
                ...
            });
    }
    ...
}
(i) Once we receive a success response we need to add a call to the success method of our send screen.

With those changes made if we send in valid information to the server we should receive a success response which should cause our send screen to look like (j).

After receiving a success response our send screen should animate the checkmark
            in and start animating the timer down afterwards.
(j) After receiving a success response our send screen should animate the checkmark in and start animating the timer down afterwards.
Advertisement

The name of the game is change

  • WebUi
    • Source
      • components
        • authenticator
          • send-screen.component.pug
          • send-screen.component.scss
          • send-screen.component.ts

As you have seen, if you have checked out the finished example yourself or seen me show it in a video, the way that I styled it was to apply either a red or green color to the different paths indicating whether the server responded with a success or error code. I also added an additional element to the background of the svg that also adjusted its color accordingly. As I believe I have mentioned in the past I can't help changing things around so what we are about to do should not come as a complete surprise. Instead of changing the color of the paths we are going to add a background element that will animate in with the appropriate color and the other elements of the svg will just use white for their strokes. To that end we need to add a background element to our template (k). Next we need to style it so that it covers the entire send screen and is positioned behind all the other elements (l). Now that the background element exists and is styled we need to add the code that will animate it correctly (m).

send-screen.component.pug (3)

div.send-screen(...)
    div.send-screen-background([attr.id]="backgroundId")
    ...
(k) Adding a div that will act as the background of our send screen.

send-screen.component.scss (1)

...
.send-screen {
    ...
    .send-screen-background {
        @include position(absolute, 0 0 0 0);
        z-index: -1;
    }
    ...
}
(l) We need to style the background so that it expands the entire height and width of the container and is placed behind the other elements.

send-screen.component.ts (3)

...
export class SendScreenComponent ... {
    ...
    private _backgroundDOM: HTMLElement = null;
    ...
    public get backgroundId() { return "assp-s-bckgrnd"; }
    ...
    public ngAfterViewInit(): void {
        ...
        this._backgroundDOM = this._domReader.findChildById(this._sendScreenDOM, this.backgroundId);
        ...
    }
    ...
    public failure = (message: string) => {
        ...
        this.animateBackground("red");
        ...
        mainAnimation.onfinish = () => {
            ...
            supAnimation.onfinish = () => {
                this.animateTimer(() => {
                    ...
                    this._backgroundDOM.style.backgroundColor = "transparent";
                });
            };
        };
    }
    ...
    public success = (message: string) => {
        ...
        this.animateBackground("green");
        ...
        animation.onfinish = () => {
            this.animateTimer(() => {
                ...
                this._backgroundDOM.style.backgroundColor = "transparent";
            });
        };
    }
    ...
    private animateBackground = (color: string) => {
        this._backgroundDOM.style.opacity = "0";
        this._backgroundDOM.style.backgroundColor = color;
        this._backgroundDOM.animate(
            {
                opacity: [0, 1]
            },
            {
                duration: 1000,
                fill: "forwards"
            });
    }
    ...
}
(m) Adding the background DOM element so that we can animate as well as a helper method to perform the animation.

Not surprisingly if we add a temporary green background color to our background element we should see (n).

Image showing our background element, with a temporary green background color, expands
            the full height and width of our send screen.
(n) Image showing our background element, with a temporary green background color, expands the full height and width of our send screen.

How about using the same colors we have already been using?

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

If at some point while we have been adding colors to our elements in our typescript class you said to yourself 'I don't like this we have already defined the colors that we are using elsewhere' I completely agree with you so it's time to fix this situation. The first step in our solution is to define a variables scss file that we can import into both our send screen and authenticator style files (o). With this file created and our colors added to it we can remove the color definitions from our authenticator scss file and import the variables file (p).

_variables.scss

$awaiting-confirmation-color: #E87E04 !default;
$indeterminate-color: #BDC3C7 !default;
$invalid-color: #D91E18 !default;
$valid-color: #26A65B !default;
(o) Variables file that we can import into both our authenticator and send screen style files.

authenticator.component.scss (1)

...
@import "_variables.scss";
...
$awaiting-confirmation-color: #E87E04 !default; // <- Remove
$indeterminate-color: #BDC3C7 !default; // <- Remove
$invalid-color: #D91E18 !default; // <- Remove
$valid-color: #26A65B !default; // <- Remove
...
(p) Now we can import the variables file and remove the color variables from our authenticator scss file.

Coloring our svg elements using css

  • WebUi
    • Source
      • components
        • authenticator
          • send-screen.component.pug
          • authenticator.component.scss
          • authenticator.component.ts

Now it's time to adjust how we are applying the color to our svg elements. The first step is to add a css class to the xmark and checkmark elements (q). In order to add the correct color to the elements we will be adding an additional css class to them depending on whether the server responds with a success or error code (r). The last thing we need to do to make this work is to modify our animateBackground method so that it applies a class to our background instead of changing its background color directly (s).

send-screen.component.pug (4)

div.send-screen(...)
    div.send-screen-background(...)
    svg(...)
        ...
        | <path [attr.id]="xmarkMainId" ... class="sf-indicator" ... />

        | <line [attr.id]="xmarkSupId" ... class="sf-indicator" ... />

        | <path [attr.id]="checkmarkId" ... class="sf-indicator" ... />
    ...
(q) Adding a css class to the xmark and checkmark elements.

send-screen.component.scss (2)

...
@import "_variables.scss";

.send-screen {
    ...
    .send-screen-background {
        ...
        &.valid {
            background-color: $valid-color;
        }

        &.invalid {
            background-color: $invalid-color;
        }
    }

    svg {
        .sf-indicator {
            stroke-width: 10;
        }
    }
    ...
}
(r) We will apply the different colors to our elements depending on what additional css class is also applied to them.

send-screen.component.ts (4)

...
export class SendScreenComponent ... {
    ...
    public failure = (message: string) => {
        const backgroundClass = "invalid";
        ...
        const stroke = "white"; //<- Remove

        this.animateBackground(backgroundClass);
        ...
        const mainAnimation = this.animatePath(this.xmarkMainDOM, this.pathLengthXmarkMain, mainDuration);
        mainAnimation.onfinish = () => {
            const supAnimation = this.animatePath(this.xmarkSupDOM, this.pathLengthXmarkSup, mainDuration * this.pathLengthXmarkSup / this.pathLengthXmarkMain);
            supAnimation.onfinish = () => {
                this.animateTimer(() => {
                    ...
                    this._backgroundDOM.classList.remove(backgroundClass);
                });
            };
        };
    }
    ...
    public success = (message: string) => {
        const backgroundClass = "valid";
        ...
        const stroke = "white"; //<- Remove

        this.animateBackground(backgroundClass);

        const animation = this.animatePath(this._checkmarkDOM, this._pathLengthCheckmark, mainDuration);
        animation.onfinish = () => {
            this.animateTimer(() => {
                ...
                this._backgroundDOM.classList.remove(backgroundClass);
            });
        };
    }
    ...
    private animateBackground = (cssClass: string) => {
        ...
        this._backgroundDOM.classList.add(cssClass);
        ...
    }
    
    private animatePath = (element: SVGElement, length: number, duration: number) => {
        ...
        element.style.stroke = "white";

        return element.animate(...);
    }
    ...
}
(s) Modifying our animateBackground method so that it applies a class to our background instead of changing its background color directly

Helps if you can read the message

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

It's time to work on the message for our send screen to make it easier to read and maybe a bit more attractive. The first thing we need to do is modify the login failure message. We are doing this because I want it to occupy only one line at the default font size (t).

authenticator.component.ts (2)

...
export class AuthenticatorComponent ... {
    ...
    private login = () => {
        ...
        this._authenticatorHttpService.login(this._xhrData,
            ...,
            () => {
                ...
                this._sendScreen.failure("Error using the provided username and password.");
            });
    }
    ...
}
(t) Changing the login failure message so that it occupies only one line at the default font size.
  • WebUi
    • Source
      • components
        • authenticator
          • send-screen.component.pug
          • send-screen.component.scss
          • send-screen.component.ts

Next we need to modify the template in regards to the message so that we can add a background color and not have it effected by the fading in and out animations of the message text (u). Now we will modify the styling for the message and its text (v). As you can see we will change the color of the message text depending on an additional valid or invalid class. In order to apply this class we will change how we are setting the message text by removing the setter and adding a method (w).

send-screen.component.pug (5)

div.send-screen(...)
    ...
    div.message
        div.message-text([attr.id]="messageId", [innerHtml]="message")
(u) We need to move the text of the message to its own element so we can add a background color and not have it effected by the text animation.

send-screen.component.scss (3)

...
.send-screen {
    ...
    .message {
        ...
        @include padding(em(8px) null);
        ...
        background-color: $dark-gray;
        font-weight: 600;
        color: $light-gray;

        .message-text {
            
            &.invalid {
                color: $invalid-color;
            }

            &.valid {
                color: $valid-color;
            }
        }
    }
}
(v) Here we are modifying and adding some additional styling for our message.

send-screen.component.ts (5)

...
export class SendScreenComponent ... {
    ...
    public failure = (message: string) => {
        ...
        this.setMessage(message, backgroundClass);
        ...
        mainAnimation.onfinish = () => {
            ...
            supAnimation.onfinish = () => {
                this.animateTimer(() => {
                    ...
                    this._messageDOM.classList.remove(backgroundClass);
                });
            };
        };
    }
    ...
    public success = (message: string) => {
        ...
        this.setMessage(message, backgroundClass);
        ...
        animation.onfinish = () => {
            this.animateTimer(() => {
                ...
                this._messageDOM.classList.remove(backgroundClass);
            });
        };
    }
    ...
    private setMessage = (message: string, cssClass: string) => {
        const out = this._messageDOM.animate(
            {
                opacity: [1, 0]
            },
            this._messageTiming);
        out.onfinish = () => {
            this._messageDOM.classList.add(cssClass);

            this._message = message;
            this._changeDetectorRef.detectChanges();

            this._messageDOM.animate(
                {
                    opacity: [0, 1]
                },
                this._messageTiming);
        };
    }
}
(w) In order to only change the color of the message text when the text is faded out we need to remove the setter and create a new setMessage method.

With those changes made our send screen now looks like (x) by default while we are waiting for a response from the server and like (y) when there was an error and like (z) when the request was successful.

Appearance of the send screen while we are waiting for a response from the server.
(x) Appearance of the send screen while we are waiting for a response from the server.
Appearance of the send screen when the server responds with an error code.
(y) Appearance of the send screen when the server responds with an error code.
Appearance of the send screen when the server responds with a success code.
(z) Appearance of the send screen when the server responds with a success code.
Exciton Interactive LLC
Advertisement