Using vue-router, how can I make menu selected styles? - javascript

I'm using vue routers to create a single-page application. I have a navigation system where clicking on a menu item takes them to the corresponding page. For example:
var pageView = require('./components/pageView.vue')
// Define some components
var Boards_page = Vue.extend( {
template: '<p>Boards</p>'
})
var Fantheories = Vue.extend( {
template: '<p>Character</p>'
})
// The router needs a root component to render.
// For demo purposes, we will just use an empty one
// because we are using the HTML as the app template.
// !! Note that the App is not a Vue instance.
var App = Vue.extend({})
// Create a router instance.
// You can pass in additional options here, but let's
// keep it simple for now.
var router = new VueRouter({
history: true
})
// Define some routes.
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
router.map({
'/': {
component: pageView
},
'/boards': {
component: Boards_page
},
'/fantheories': {
component: Fantheories_page
}
})
// Now we can start the app!
// The router will create an instance of App and mount to
// the element matching the selector #app.
router.start(App, '#container')
How would I make it so that whatever URL I'm on the menu item has a different CSS style so it appears as if its selected?

The router option linkActiveClass determines the class that is applied to an active link. It defaults to v-link-active.
http://vuejs.github.io/vue-router/en/options.html

Related

Angular show/hide on parent component based on child component

I have a parent component with the following template:
<app-sidebar></app-sidebar>
<router-outlet></router-outlet>
<app-footer></app-footer>
How can I show or hide the app-sidebar or app-footer component based on the component routing through the router outlet? I have a boolean showSidebar I'd like to use, with it's value set to true or false on each child component.
<app-sidebar *ngIf=showSidebar></app-sidebar>
In the app routes add a data property.
{ path: 'no-sidebar', component: NoSidebarComponent, canActivate: [AuthGuard], data: { showSidebar: false } },
and subscribe to the router events in the parent component like this
this.router.events.subscribe(event => {
if (event instanceof RoutesRecognized) {
const routeData = event.state.root.firstChild.data;
if (routeData) {
this.showSidebar = routeData.showSidebar === false ? false : true
}
}
}
Instead of direct component to component communication, you could use a service to share the show/hide flag and inject the service in the enclosing component as well as the the ones routed to.
For example child components' onInit, set a flag in the shared service instance. The parent component containing the router outlet, would read that flag and use it along with an *ngIf to show or hide parts of the template.
You can use subject variable, whenever you want to hide or show that time emits value of variable and subscribe to that variable in sidebar and footbar

Vue.js - Helper function that accesses the app instance

Let's say I want to make a utility function that redirects users to a route if they aren't authenticated.
This function resides in a utils.js file; it isn't declared in any one component.
// utils.js
export function redirectIfNotAuthenticated (app, path) {
if (!app.$store.state.isAuthenticated) {
app.$router.push(path)
}
}
this function is then imported into single-file components and called like:
redirectIfNotAuthenticated(this, '/login').
I think having an app parameter and having to explicitly pass this from the component is ugly. Is there a better way to have a stateless helper function access the scope of the calling component in vue? As in, is there a way to bind functions that don't live inside of a .vue file to the calling instance of the vue app?
To handle redirection, instead of putting logic inside component, you can use vue-router's navigation guards to handle on top level instead:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
if (!isAuthenticationRequired(to)) { // your logic to ignore authentication for custom routes, e.g. login
next();
} else if (!isAuthenticated()) { // your logic which needs access to Vuex's store
next('login');
} else {
next();
}
})
Then, if you create a store object and add it to your Vue app instance, like in the default example of Vuex , you can refer to the object directly without any need to access the Vue app, since same store object is used:
// utils.js
import store from '../your_path/your_file.js';
export function isAuthenticated() {
return store.state.isAuthenticated;
}
The best practice in this situation is to use router navigation guards :
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
next(vm=>!vm.$store.state.isAuthenticated)
//vm refers to the vue instance
})

Bind the vue to root element after plugin finishes ajax call

I am binding my application root element #app to vue and before that I am loading my custom plugin with Vue.use(myplugin). My plugin makes an ajax call, load the data and set it into Vue.$permission property.. so in short I want to load my user permission before mounting the app. but while my ajax call is fetching permission data, app is mounted and my page is getting rendered, which need the permission object.
is there a way I can bind the app root element to vue after my plugin finishes.. or any other alternate solution?
Yeah, that's quite simple actually:
const Vue = require('vue');
const vueInstance = new Vue({
// don't specify the 'el' prop there
});
doingAnAjaxCall() // assuming it returns a promise
.then(() => {
vueInstance.$mount('#root'); // only now the vue instance is mounted
});
If a Vue instance didn’t receive the el option at instantiation, it will be in “unmounted” state, without an associated DOM element. vm.$mount() can be used to manually start the mounting of an unmounted Vue instance.
See: https://v2.vuejs.org/v2/api/#vm-mount
So for your case you may use any asynchronous mechanism to detect the end of the ajax call. Maybe the simplest solution is to pass a callback function to your plugin object, and mount your vue instance inside.
/* Define plugin */
MyPlugin = {};
MyPlugin.install = function(Vue, options) {
doingAnAjaxCall()
.then(data => {
// do something with data
options.callback();
});
};
const Vue = require('vue');
/* Create the vue instance */
const vueInstance = new Vue({
// don't specify the 'el' prop there
});
/* Install the plugin */
Vue.use(MyPlugin, { // This object will be passed as options to the plugin install()
callback: () => {
vueInstance.$mount('#root'); // only now the vue instance is mounted
}
});

How to get route parameter in aurelia custom component

I'm creating a custom component in aureliajs framework and injecting it a Router instance:
#inject(Router)
export class Pagination {
constructor(router) {
this.router = router;
}
}
It will be used with list view-models in order to setup some basic pagination. Therefore I need to read current page number from an active route (that would look like: orders/:pageNum. I'm not sure however how to do it? I mean - I know it should probably be placed in Pagination attached method, but how to get to this :pageNum param?
Create a bindable property in your custom element. Take the page number from the active route and bind it to the custom element. For instance:
import { bindable } from 'aurelia-framework';
#inject(Router)
export class Pagination {
#bindable page;
constructor(router) {
this.router = router;
}
}
Usage:
<pagination page.bind="page"></pagination>
To get the page number in the active route, use the activate() hook:
activate(params) {
this.page = params.pageNum;
}
You can use router.currentInstruction.params and router.currentInstruction.queryParams.
You can also observe the router to be notified when the route changes:
let sub = this.bindingEngine.propertyObserver(
this.router, 'currentInstruction').subscribe(currentInstruction => {
console.log(currentInstruction);
});

MarionetteJS: who is responsible for showing sub-applications?

I am building a quite simple Marionette application; I am not using Marionette.Module since it's deprecated and since I want to use ES2015 with Webpack.
I have just a couple of "pages": one is the Welcome screen and the other one is the Playground. Each one of those pages are Applications, plus there is a root application with a Layout with just three regions: header, main and footer.
The view (or layout) of those applications are meant to fill the main region of the root layout.
Now, whenever I want to show one of those sub-applications, I don't know exactly how (or, I am not finding the most satisfying way) to ask the Layout of the root application to showChildView of the view/layout of those sub-apps.
So far, I came up with two approaches (none of which is fantastic):
EDIT: I added another approach at the end of the Q
on the controller of each sub-application, trigger the event "application:show" passing in the view. The root application is listening to this event and showChildView as soon as it receives the message
whenever I start a sub-application, I pass it the root application instance. Whenever the sub-application needs to show itself, it will call the showChildView inside the main region
The first approach is weak, because it's totally asynchronous (fire and forget). I'd like to know when my application is shown (attached to the DOM) but relying again on another event seems cumbersome
The second approach is more robust but it sucks, because of course I don't want the sub-application's views to be responsible of their own rendering in the root layout. The root application knows better.
Some code follows to try to show both ideas:
// Approach #1
// In the root app
welcomeApp.on('app:show', (view) => {
rootApp.rootView.showChildView('main', view);
});
// In the Welcome sub-app
var Controller = {
show() {
app.trigger('app:show', new WelcomeView());
}
};
// Approach #2
// In the root application
const welcomeApp = new WelcomeApp({
rootApp: this
});
// In the Welcome sub-app
var Controller = {
show() {
app.rootApp.rootLayout.showChildView('main', new WelcomeView());
}
};
EDIT: 12 Jan.
OK, digging a bit more in the documentation I found what I think is the correct way to do that. The idea is that the root Application will listen for Commands from the subapplications.
So, in my root view I will have:
this.channel.commands.setHandler("application:show", function(view) {
this.rootView.showChildView('main', view);
}.bind(this));
In all the other subapplications, I will have (for example, in the WelcomeView):
getController() {
const greet = function () {
this.channel.commands.execute('application:show', new WelcomeView());
}.bind(this);
return {
greet: greet
};
}
Personally I wouldn't use multiple applications, this seems to be just getting around the issue of modules being removed. Have you tried using a single application with a LayoutView, a Region for each component or (module) and the base of the component either being a singleton JS object, or you could try Backbone.Service to split it up. If you are using multiple routes, you could have a look at Backbone.Routing, with each Route being the base 'controller' for the 'page'.
I find a brilliant architecture for large scale Marionette applications is James Kyle's Marionette Wires. This uses Service for reusable components, and routers/routes for different data types.
EDIT
Another way of architecting using services, but if you don't want the root application's regions show methods to be called from child components, would be to import the instantiated application into the child component and use the app.addRegions method to add regions within the child. EG
//app.js
import { Application } from 'backbone.marionette';
const App = Application.extend({
onStart() {
//doSomething();
}...
});
export const app = new App();
//submodule/service.js
import { Service } from 'backbone.service';
import { SubmoduleController } from './controller';
const SubmoduleService = Service.extend({
requests() {
show: 'show',
hide: 'hide'
},
setup(options = {}) {
this.el = options.el
},
start() {
this.controller = new SubmoduleController({
el: this.el
});
},
show() {
this.controller.show();
},
hide() {
this.controller.destroy();
}
});
export const submoduleService = new SubmoduleService();
//submodule/controller.js
import { Object as Controller, Region } from 'backbone.marionette';
import { View } from './view';
import { app } from '../app';
export const SubmoduleController = Controller.extend({
initialize(options = {}) {
this.el = options.el;
this._addRegions();
},
_addRegions() {
const region = new Region({
el: this.el
});
app.addRegions({
submoduleRegion: region
});
},
show() {
const view = new View();
app.submoduleRegion.show(view);
},
onDestroy() {
app.submoduleRegion.reset();
}
});
//main.js
import { submoduleService } from './submodule/service';
const config = {
submodule: {
el: '#submodule';
},
...
};
submoduleService.setup(config.submodule);
submoduleService.show();
A gotcha with doing it this way is the submodule's region needs it's element to exist in the DOM when it's region's show method is called. This can either be achieved by having a root view on the application, whose template contains all component root elems, or to have a root submodule - page or something.

Categories

Resources