Nativescript-Vue-Dynamo is a dynamic component router implementation that is suitable for NativeScript-Vue.
It uses Vue Dynamic Components to simulate the normal navigation patterns of a Nativescript-Vue
app. It does this by plugging into Vue-Router
and Vuex
. This implementation also supports "Child Routes" by using the same Dynamic Component behavior, in a nested manner, to allow you to navigate within the parent/child routes.
To be clear: we are not actually using the underlying mechanics of Vue-Router
to navigate through the app as this has been problematic issue when trying to use Nativescript-Vue
historically. However, we are doing things like plugging into the main Route Config as a common shared configuration and then using a router.afterEach
hook to update the route history that we are keeping in the state manager. When the route history state changes, the dynamic components will cause the app to load a new route or Page. This effectively mimcs normal navigation patterns.
A further point must be made: we are also not using the underlying mechanics of Nativescript-Vue
to navigate. There is no $navigateTo
or $navigateBack
going on. Thus, we keep something similar to Nativescript's navigation stack inside of our state manager so that we can track forwards and backwards movement as needed.
This project takes many of the same ideas in Vuex-Router-Sync and Nativescript-Vue-Navigator and combines them in a way that simulates the traditional routing experience withing a Nativescript-Vue
app.
npm install --save nativescript-vue-dynamo
import Vue from 'nativescript-vue';
import App from './App.vue';
import store from './store';
import router, { routes } from './router';
import Dynamo from 'nativescript-vue-dynamo';
Vue.use( Dynamo, {
store,
router,
routes,
});
Inside or your Vue-Router
config, you will want to split out your route config into it's own array as modeled below. You can then use many of the extra options provided by Vue-Router
out of the box such as adding the meta
object. Most built in router hooks should be available to help assist you as well. Notice the routeHistoryName
in the meta tags for all routes. Also, for child routes, notice the parentRouteHistoryName
. These two items should be used in this manner to help assist Dynamo with tracking where it is at and where it needs to go. Finally, also notice the alias
property in the first child route. This helps tell Dynamo which child route is the default child route.
export const routes = [
{
name: 'home',
path: '/home',
component: () => import(/* webpackChunkName: "home" */ '~/views/Home'),
meta: {
title: 'Home',
auth: true,
routeHistoryName: 'main',
},
},
{
name: 'login',
path: '/login',
component: () => import(/* webpackChunkName: "login" */ '~/views/Login'),
meta: {
title: 'Login',
auth: false,
routeHistoryName: 'main',
},
},
{
name: '',
path: '/first',
component: () => import(/* webpackChunkName: "first" */ '~/views/First'),
props: true,
meta: {
title: 'First',
auth: true,
routeHistoryName: 'main',
},
children: [
{
name: 'dynamo-one',
alias: '/',
path: '/dynamo-one',
component: () => import(/* webpackChunkName: "dynamo-one" */ '~/views/Dynamo-One'),
meta: {
title: 'Dynamo One',
auth: true,
routeHistoryName: 'first',
parentRouteHistoryName: 'main',
},
},
{
name: 'dynamo-two',
path: '/dynamo-two',
component: () => import(/* webpackChunkName: "dynamo-two" */ '~/views/Dynamo-Two'),
meta: {
title: 'Dynamo Two',
auth: true,
routeHistoryName: 'first',
parentRouteHistoryName: 'main',
},
},
],
}
]
Inside of your App.vue, you can do something simple as below. Notice we are sending two required props into the component:
defaultRoute
- This will make sure that the first page loaded is the one specified by this value. It must also match an actual route in your router config.routeHistoryName
- This name will end up being theid
of theframe/div
loaded and will also be used to track via state manager whichframe/div
we are navigating within.
These two options are mandatory for this package to work correctly.
<template>
<Page actionBarHidden="true">
<Dynamo
:routeHistoryName="'main'"
:defaultRoute="'home'"
/>
</Page>
</template>
Inside of App.vue
, you could even provide your own wrapper around the Dynamo
component. This could be a StackLayout
or any of the other layout components such as GridLayout
. Just be aware that any alternative layout items will not be replaced when the Dynamo
component updates itself. This might be useful if you want a bottom navigation button area and then have the buttons selected switch out the item in the first row of the Grid:
<template>
<Page actionBarHidden="true">
<GridLayout row="*, auto, auto, auto">
<Dynamo
:routeHistoryName="'main'"
:defaultRoute="defaultRoute"
row="0"
/>
<Button text="First" @tap="$goTo({ path: '/first'})" row="0" />
<Button text="Second" @tap="$goTo({ name: 'second', params: { msg: 'Hello, this is a prop' }})" row="1" />
<Button text="Logout" @tap="$logout('main')" row="2" />
</GridLayout>
</Page>
</template>
Because we are plugging into Vue-Router
, many of the same programmatic navigation aides available there should be usable within Nativescript-Vue
. So things like route.push()
and route.back()
should work. Others may not and if you find something that is not working, please submit an issue.
In the example above, we are providing two different ways to route within the $goTo
navigation aide. Refer to the Navigation Aides section below for more information about this function.
The first passes an object that should look similiar to something you pass for a route.push
with vue-router
, while the second also passes params
to the route. Any params passed to the route will be forwarded on to the route as a prop
. This allows you to dynamically pass props to your route.
You can use "child route" like features as well. You will build out your child route config as you normally would with Vue-Router
except you must add a meta
tag into the child route's config:
meta: {
title: 'Dynamo One',
auth: true,
routeHistoryName: 'first',
parentRouteHistoryName: 'main',
},
By adding the meta take and then adding the parentRouteHistoryName
property you will effecively activate the parent/child relationship between components and this will mimic many of the parent/child functions of Vue-Router
. Notice that the parentRouteHistoryName
property is equal to the previously used routeHistoryName
and routeHistoryName
is now something entirely different. You would then just add a Dynamo component into the Parent view as normal. Notice the routeHistoryName
in the route config above matches the routeHistoryName
prop in the component below.
<template>
<Page>
<ActionBar :title="navbarTitle" />
<Dynamo
:routeHistoryName="'first'"
:defaultRoute="'dynamo-one'"
/>
</Page>
</template>
You can also provide an option of simulating Nativescript's built in clearHistory
navigation option. You can provide a route parameter named clearHistory
and this will reset the route history state for the corrosponding routeHistoryName
.
For example, given you are on a Login page, and successfully log in you would navigate to the Home page with
$router.push({ name: 'home', params: { routeHistoryName: 'main', clearHistory: 'true'}});
or
this.$goTo('home', true);
Note that we used clearHistory: true
to prevent the back button from going back to the login page or just passed true
as the 2nd prop in this.$goTo
.
This package is also providing some additional Navigation Aides via Vue.prototype
that will help assist you navigating within your app.
-
$goTo(location, routeHistoryName, parentRouteHistoryName?, clearHistory?, onComplete?, onAbort?)
Parameter Type Required Purpose location string or Location X Where you want to go. If this is a string, then it will navigate to that route. If it is a vue-router
Location Object, then it will take the included options into account when navigating.clearHistory boolean clear the navigation history being kept in the state manager. Will default to false
onComplete Function Callback to be called when the navigation successfully completes (after all async hooks are resolved). onAbort Error Handler Callback for when the navigation is aborted (navigated to the same route, or to a different route before current navigation has finished) For convience we are constructing a router.push behind the scenes. We did this as a matter of convience since we are adding a required route parameter as well as some optional route parameters to help us track navigation. You could just as easily still use
router.push
if you want as seen in the Login example above. -
$goBack()
- It will check to see if you are navigating back to a sibling route, or going backwards to a parent route. It will also check to see if this is the last page left in the frame and if there is a parent route, it will transition to it. -
$goBackToParent(routeHistoryName, parentRouteHistoryName)
- Provide the childrouteHistoryName
and theparentRouteHistoryName
and it will navigate back up the route tree. This method is actually called for you if when you use$goBack()
and there is a need to transition backwards to a parent Route.
The Dynamo
component is acting as a "middle-man" between the components you are concerned with. Thus, the relationship looks something like this Parent -> Child(Dynamo) -> Grandchild. This means you won't be able to handle events or use refs to call functions on the grandchild as you normally would. Because of this, we have provided a way to handle both of these common patterns.
When you call the Dynamo component, you can add an additional parameter that has a very specific way of referencing it whereby it must include the routeHistoryName
within it. Notice that with the v-on
we are including first
as the first string in the kebab case name. The pattern you will need to use is "routeHistoryName
+ -event-handler
". This lets Dynamo
know which parent component to bubble the event up to.
<Dynamo
:routeHistoryName="'first'"
:defaultRoute="'dynamo-one'"
:functionHandler="functionHandler"
@first-event-handler="eventHandler"
/>
Then inside your parent component you can include a function similar to this:
public eventHandler(e){
console.log('first.vue - eventHandler - ', e);
}
Inside your grandchild component you can then emit an event as you normally would: this.$emit('dynamo-event', "emit event one");
. Notice the event name is dynamo-event
. This is the required event name as it is hard coded into the Dynamo
component.
In theory, you can use this single event handler to do multiple things in the parent component via something like a switch
statement, or even get more advanced and pass an object back up through the event and convert it into calling a method somewhere else. Here's an example object: { function: 'anotherMethod', data: 'data' }
and then within your eventHandler function you could do something like below and it would automatically call your anotherMethod
method and pass it the data
:
public eventHandler(e){
this[e.function](e.data)
}
In your Dynamo
component you can include an option named functionHandler
and then have it reference a property in your parent component. In reality what we are doing is passing a Prop to the Dynamo
component which in turn is passing that Prop down to the grandchild component.
For the sake of convienence, we've named the function functionHandler
in the example below.
<Dynamo
:routeHistoryName="'first'"
:defaultRoute="'dynamo-one'"
:functionHandler="functionHandler"
@first-event-handler="eventHandler"
/>
Then you could do something like below inside your script
tag's export. The parentButton
method is just a convience provided in the example that is called after a button click. In turn, the parentButton
method replaces the value of this.functionHandler
. The object provided works much like the eventHandler example above, but in reverse. It references a method to call on the grandchild: parentToChild
and some data to pass to the grandchild's method.
public functionHandler: object = {};
public parentButton() {
console.log('parentButton clicked');
this.functionHandler = { method: 'parentToChild', data: 'hello there' };
}
Then inside your grandchild component you could do something like below. Notice we've setup the Prop
but also setup a watcher
. This allows us to use this single Prop
to act as a gateway to calling multiple methods in the grandchild component. Think of it as a one-to-many
relationship. Inside the watcher
we've included an example where we dynamically call other methods.
In the example above, we are passing in parentToChild
as the method to call with the data of hello there
. This means in the example below, we will write the following to the console: parentToChild - hello there
. Also notice we've included an example of emitting an event back up as well. This is optional and is not necessary.
@Prop() functionHandler: object = {};
@Watch('functionHandler')
onfunctionHandlerChanged(val: Function) {
this[val.method](val.data);
}
public parentToChild(data: string) {
console.log('parentToChild - ', data);
this.$emit('dynamo-event', "emit event two from parentToChild function");
}
Take a look at the demo project for a simplistic project that implements this plugin with many of the examples discussed above. Notice within the utils/global-mixin-native
class file there is an example of intercepting the Android back button behavior and then plugging it into the $goBack()
navigation aide.