#10 Adding An Animated Settings Menu
Saturday, August 10, 2019
In this article we focus on adding an animated settings menu to our app bar. Initially this settings menu will contain our anchor tag for accessing the about page. We create it such that the menu can be toggled visible and hidden using the cog button on the right hand side of the app bar. We will also add the ability for the menu to be closed if and or when the user navigates to another page while the menu is open. We will then finish up by modifying our navigation service so that if the user tries to navigate to the current page we will just skip the navigation.
Parts
- Part 45: Adjusting Shares
- Part 44: Plan Percentages
- Part 43: Home Securities
- Part 42: Updating Plans
- Part 41: Plan Details View
- Part 40: Portfolio Getters
- Part 39: Portfolio Plan
- Part 38: Portfolio Category
- Part 37: Account Securities
- Part 36: Account Transfer
- Part 35: View Account Security
- Part 34: Updating Deposit
- Part 33: View Account Deposit
- Part 32: Display Account Details
- Part 31: Account Getters
- Part 30: Deposits And Securities
- Part 29: Add Accounts Details
- Part 28: Refactoring Accounts
- Part 27: Add Security Models
- Part 26: Edit Security Details
- Part 25: View Security Details
- Part 24: Navigating To Details
- Part 23: Getters Validation
- Part 22: Query Parameters
- Part 21: Tab Entries
- Part 20: Tab Header
- Part 19: List View
- Part 18: Vuex Getters
- Part 17: End Domain Model
- Part 16: Start Domain Model
- Part 15: Pop Routes
- Part 14: Push Routes
- Part 13: Removing Accounts
- Part 12: Vuex (Decorators)
- Part 11: Vuex (Accounts)
- Part 10: The App Bar (Settings)
- Part 9: Remove Consumer Rxjs
- Part 8: The App Bar (Back)
- Part 7: Structuring Our App
- Part 6: Animation Between Views
- Part 5: Navigation Fade
- Part 4: Navigation Requests
- Part 3: Fade Animations (cont.)
- Part 2: Fade Animations
- Part 1: Splash Screen
Adding A Settings Menu (Template)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
We will start by adding a little bit of markup to the template for our app bar settings (a). As a note we can see that we have moved the button with a cog icon into this template from the app bar itself.
TheAppBarSettings.vue
<template lang="pug">
div.settings-container
button.cog
FontAwesomeIcon(icon="cog")/
AnimatableItem.settings(
v-bind:subject="subject")
a(v-on:click.prevent="navigateToAbout") About
</template>
Adding A Settings Menu (Script)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
For the script we will start by just making sure that we can navigate to the about page when the anchor tag is clicked (b). For the moment we are not going to do any animation but we will take care of that shortly.
TheAppBarSettings.vue
<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";
import {
AnimatableItem,
AnimationSubject,
} from "@/components/animations";
import {
Routes,
RoutingService,
} from "@/components/routing";
@Component({
components: {
AnimatableItem,
},
})
export default class TheAppBarSettings extends Vue {
@Inject() private readonly routingService!: RoutingService;
private readonly subject = new AnimationSubject();
private navigateToAbout() {
this.routingService.navigateTo(Routes.About);
}
}
</script>
Adding A Settings Menu (Style)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
Last but not least for our initial setup we will do just a bit of styling (c). As I mentioned when we defined the template we have moved the cog button into this component so we need to include the styling that we were applying in the app bar component.
TheAppBarSettings.vue
<style lang="sass" scoped>
@import "../../bourbon/bourbon"
@import "../../bitters/functions"
@import "../../bitters/variables"
.settings-container
position: relative
.cog
flex: 0
@include padding(0.75rem 1rem)
min-height: rem(45.33px)
.settings
@include position(absolute, 100% 0 null null)
background-color: $viewport-background-color
& > a
display: block
@include padding(1rem)
</style>
Adding the settings component to our app bar (Template)
- root
- src
- components
- app-bar
- TheAppBar.vue
- app-bar
- components
- src
Now that we have defined the component we can add it to the template of our app bar (d)
TheAppBar.vue
<template lang="pug">
div.app-bar
...
Settings/
</template>
Adding the settings component to our app bar (Script)
- root
- src
- components
- app-bar
- TheAppBar.vue
- app-bar
- components
- src
Of course for everything to work we need to import and add the app bar settings component to the component definition for our app bar (e).
TheAppBar.vue
<script lang="ts">
...
import Settings from "@/components/app-bar/TheAppBarSettings.vue";
...
@Component({
components: {
Settings,
},
})
export default class TheAppBar extends Vue {
...
}
</script>
Adding the settings component to our app bar (Style)
- root
- src
- components
- app-bar
- TheAppBar.vue
- app-bar
- components
- src
Since we will have our settings menu animating in and out we might as well adjust the z index of our app bar itself (f).
TheAppBar.vue
<style lang="sass" scoped>
...
.app-bar
...
z-index: 9000
.angle-left, .cog // <-- move the cog styling to the app bar settings
...
...
</style>
Toggling the settings menu (Template)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
Next up is actually being able to toggle the menu in and out by pressing the cog button. Starting with adding the click handler to the button template (g).
TheAppBarSettings.vue
<template lang="pug">
div.settings-container
button.cog(v-on:click.prevent="toggle")
...
...
</template>
Toggling the settings menu (Script)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
Adding the ability to animate our settings menu in and out is a simple matter of using our animation subject along with a boolean is visible flag to decided which animation to run (h). In this case we are going to be animating the menu in from the right and out to the right.
TheAppBarSettings.vue
<script lang="ts">
...
import {
...
AnimationTypes,
} from "@/components/animations";
...
export default class TheAppBarSettings extends Vue {
...
private isVisible = false;
private toggle() {
switch (this.isVisible) {
case true:
this.subject.next(AnimationTypes.TranslateOutToRight);
this.isVisible = false;
break;
case false:
this.isVisible = true;
this.subject.next(AnimationTypes.TranslateInFromRight);
break;
}
}
}
</script>
Toggling the settings menu (Style)
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
The only thing we need to do to the styling, at the moment, is to start our menu off screen out to the right (i).
TheAppBarSettings.vue
<style lang="sass" scoped>
...
.settings
...
transform: translateX(100%)
</style>
Making tab stops work correctly
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
As it stands right now even if the menu is offscreen you can still get to it by using the tab key. In addition to this since our anchor tag does not possess an href attribute you can not tab to it. To fix both of these problems we will use a vue if directive to only add the menu to the DOM if it is visible and add a place holder to the href for the anchor (j).
TheAppBarSettings.vue
<template lang="pug">
div.settings-container
...
AnimatableItem.settings(
...
v-show="isVisible")
a(
href="#void"
...) About
</template>
Closing the menu when the user clicks the link
- root
- src
- components
- app-bar
- TheAppBarSettings.vue
- app-bar
- components
- src
Next up I would like for the menu to close if/when the user clicks on a navigation link within the menu. To do this we will simple add an open and a close helper methods and subscribe to the navigate stream of the routing service (k). Also now that we are using the vue if directive we need to pass in an options object to our out animation so that the menu is only removed once the animation is complete. Lastly just to err on the side of caution we are setting the is visible flag to true in the open method and waiting for the next animation frame before starting the animation.
TheAppBarSettings.vue
<script lang="ts">
...
import {
...,
IAnimationSubjectOptions,
} from "@/components/animations";
...
export default class TheAppBarSettings extends Vue {
...
private readonly animationOptionsClose: IAnimationSubjectOptions = {
complete: this.closed,
};
...
private close() {
if (this.isVisible === false) {
return;
}
this.subject.next(AnimationTypes.TranslateOutToRight, this.animationOptionsClose);
}
private closed() {
this.isVisible = false;
}
private created() {
this.routingService
.navigate$
.subscribe(this.close);
}
...
private open() {
if (this.isVisible) {
return;
}
this.isVisible = true;
requestAnimationFrame(() => {
this.subject.next(AnimationTypes.TranslateInFromRight);
});
}
private toggle() {
switch (this.isVisible) {
case true:
this.close();
break;
case false:
this.open();
break;
}
}
}
</script>
Hiding the overflow
- root
- src
- App.vue
- src
I thought we had already taken care of the overflow on the app div previously but looks like I forgot to do it. Either way to make sure that we do not see the horizontal scrollbar when we animating things in and out we just need to hide the overflow (l).
App.vue
<style lang="sass">
...
#app
...
overflow: hidden
...
</style>
If we are already there no need to navigate
- root
- src
- components
- routing
- route-entry.ts
- routing
- components
- src
Right now as it stands even if we are already on a page we can still issue a request to navigate to it. To prevent this from happening we just need to modify our route class to add a method that will tell us given an entry from the routes enum whether or not it is associated with a particular route entry (m).
route-entry.ts
...
export class RouteEntry {
...
public isSameRoute = (route: Routes) => {
return this._route === route;
}
}
Skip the navigation
- root
- src
- components
- routing
- routing-service.ts
- routing
- components
- src
With the change to our route entry we can update our routing service to check whether or not the requested navigation would point to the page we are already on. If it does we can just skip the navigation (n).
routing-service.ts
...
export class RoutingService {
...
public navigateTo = (to: Routes) => {
if (this.current.isSameRoute(to)) {
return;
}
this._navigate.next(to);
}
...
}