Accessing another model-view from a page inside the main router - aurelia

I’m trying to find out a way to creating a custom view-model and making its functions accessible from a page within the main router-view element. This custom view-model, in my case called "secondbar" is supposed to be located under main nav-bar and should contain a login status ("Logged in as ..." / "Not logged in").
In my router-view, one of the pages is a login page. After successful login, I want to be able to call a function of "secondbar" directly in order to change the login status there without page refresh.
I tried to inject "secondbar" class in login.js file; this way I can access the functions, but the message on the page wouldn't change (it seems like I'm accessing another instance of "secondbar"). I also tried to print out the same message directly on the main nav-bar, but it seems like this is not the right approach and it didn’t work either.
Is there some way, how I can access a "secondbar" class directly (the same instance is being shown in the browser) and call a function located there from a page inside a router-view?
App.html
<template bindable="router">
<require from="secondbar/secondbar"></require>
<!-- navbar -->
<secondbar view-model.ref="secondbar"></secondbar>
<router-view>
<!—- page content -->
</router-view>
</template>
App.js
import {Redirect} from 'aurelia-router';
import {inject} from 'aurelia-framework';
export class App {
configureRouter(config, router) {
this.router = router;
config.title = ‘’;
config.map([
{ route: [''], name: 'home', moduleId: 'home/index', nav: true, title: 'Home' , settings: { roles: [''] }},
{ route: 'login', name: 'login', moduleId: 'login/login', nav: true, title: 'Log In', settings: { roles: [''] }},
]);
}
}

What you could do is create a login-service or something similar, where you can bind to in both the secondbar and the login page-view. The basic would look like this:
login-page:
import login-service from "/login-service/login-service";
// inject
login() {
// do login stuff
this.loginService.currentUser = user; // user would be any object of choice
}
secondbar.js:
import login-service from "/login-service/login-service";
// inject
secondbar.html
<span if.bind="loginService.currentUser">
Logged in as: ${loginService.currentUser.username}
</span>
<span if.bind="!loginService.currentUser">
Not logged in
</span>

Related

Vue Router does not redirect to the right page

When I try to push a new profile with an profileID with VueRouter I get an error saying: Avoided redundant navigation to current location: "/user/ID". When clicking on the button it is not redirecting me to another page, it just jumps to the beginning of the current page.
I declared my routes in my index.js file like this:
const routes = [
{
path: '/',
name: 'EntryPoint',
component: EntryPoint
},
{
path: '/main',
name: 'Main',
component: Main
},
{
path: '/user/:id',
name: 'User Current',
component: CurrentUser
},
When I am on an user page the path in the url already contains an userID - so f.e. #/user/1111.
Now on the same user page I want to navigate to another user when the user clicks on a button:
<ContactCard
v-for="user in users"
#goToUser="goToUser(user.id)"
/>
goToUser(userId) {
this.$router.push({ name: "User Current", params: { id: userId } });
},
The id which I get from my users array contains different id´s for each user.
Any suggestions why the routing is not working properly?
When clicking on the button I see for an instance that the url is changing with the right path: #/user/1112. Inseatd of updating the page it removes the url, jumps to top and gives me the error message from above when selecting the button again.
When I log
console.log(this.$route.path);
on button click I get the correct route - /user/ID but it is not updating anything.
UPDATE:
As Zdravko Pernikov suggested I keyed my and it works:
<template>
<div id="app">
<div id="nav">
<label>Welcome</label>
<router-link to="/main">Welcome</router-link>
<router-link to="/User">User</router-link>
</div>
<router-view :key="$route.path"/>
</div>
</template>
This may happen because you are reusing your CurrentUser component and you are not listening for changes since it's already rendered.
You can try keying your global router view <router-view :key="$route.path"></router-view> your components will be rerendered on different routes.

Dynamic Vue Router

I am researching whether a vue router is the best approach for the following scenario:
I have a page containing 'n' number of divs. Each of the divs have different content inside them. When a user clicks on a button in the div, I would like the div to open in a separate browser window (including its contents).
Can a route name/component be created on the fly to route to? Since I have 'n' number of divs, that are created dynamically, I cannot hard-code name/components for each one
<router-link :to="{ name: 'fooRoute'}" target="_blank">
Link Text
</router-link>
I want to avoid the same component instance being used (via route with params) since I may want multiple divs to be open at the same time (each one in their own browser window)
If the link is opening in a separate window, it makes no sense to use a <router-link> component as the application will load from scratch in any case. You can use an anchor element instead and generate the href property dynamically for each div.
To answer your questions:
A route name cannot be created dynamically since all routes must be defined at the beginning, when the app (along with router) is being initialized. That said, you can have a dynamic route and then dynamically generate different paths that will be matched by that route.
There is no way for the same component instance to be reused if it's running in a separate browser window/tab.
It is possible to create dynamic router name.
profileList.vue
<template>
<main>
<b-container>
<b-card
v-for="username in ['a', 'b']"
:key="username"
>
<b-link :to="{ name: profileType + 'Profile', params: { [profileType + 'name']: username }}">Details</b-link>
</b-container>
</main>
</template>
<script>
export default {
name: 'profileList',
data () {
return {
profileType: ''
}
},
watch: {
// Call again the method if the route changes.
'$route': function () {
this.whatPageLoaded()
}
},
created () {
this.whatPageLoaded()
},
methods: {
whatPageLoaded () {
this.profileType = this.$route.path // /user or /place
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
b-container, b-card, b-link are taken from bootstrap-vue, so you can freely change it.
router.js
const router = new Router({
mode: 'hash',
base: process.env.BASE_URL,
linkExactActiveClass: 'active',
routes: [
// USERS
{
path: '/user/:username',
name: userProfile,
component: userProfile
},
{
path: '/user',
name: 'userList',
component: profileList
},
// PLACES
{
path: '/place/:placename',
name: placeProfile,
component: placeProfile
},
{
path: '/place',
name: 'placeList',
component: ProfileList
}
]
})

vue-router route with params not working on page reload

I am using vue-router on my project.
I am able to navigate to my named routes perfectly fine. My only problem is when I use a named route which expects a parameter, it does not load when I refresh the page.
here is my route:
'/example/:username': {
name: 'example-profile',
title: 'Example Profile',
component: ExampleComponent
}
this is how I am using the vue-router route:
<a v-link="{ name: 'example-profile', params: { username: raaaaf } }">
Example Link
</a>
When I select Example Link I get mydomain.com/example/raaaaf.
On first load, it renders the correct template, but when I refresh or manually entered the link on the address bar, I am redirected to my Page Not Found page and the method called when the page is created is not triggered.
This is what I have on my ExampleComponent:
<template>
<div class="small-12 left">
<side-bar></side-bar>
<div class="store-right">
<store-details></store-details>
<store-menu></store-menu>
<store-listings></store-listings>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: null,
user: null,
}
},
created() {
this.getUser()
},
methods: {
getUser() {
console.log(this.$route.params);
}
}
}
</script>
I don't know if anyone else if facing the same issue, but I was having a problem getting route params on refresh. The route parameter I was trying to get was an ID number and I use that ID to fetch data and populate the page. I found (through many console logs) when I refreshed, the number was turning into a string and thats why the page was not working. I sorted it out but casting the ID to number before using it:
Number($route.params.id)
You need to configure your server properly. Your server is essetially looking for an index file in a /example/raaaaf directory. I'd read through this page carefully: http://router.vuejs.org/en/essentials/history-mode.html

Aurelia: How to access data stored in a child-router's view-model

I am fairly new to Aurelia and I am trying to understand what’s the best way to access and display data in a subpage of a child-router. The data is stored in the activate method of the child-router’s view-model. My Problem is to display the data when I first enter or reload a subpage of the child-router. Unfortunately it's not working. As soon as I have displayed a subpage and go to another, it all works fine.
child-router.js
export class ChildRouter {
configureRouter(config, router) {
config.title = 'Child Router Title';
config.map([
{ route: '', redirect: 'basics'},
{ route: ['basics', ''], name: 'basics', moduleId: './basics/basics', nav: true, title: 'Basics' },
{ route: 'data', name: 'data', moduleId: './data/data', nav: true, title: 'Data' }
]);
this.router = router;
}
activate() {
this.var1 = "Var1. Only works when I reenter a subpage.";
this.var2 = "Var2. Only works when I reenter a subpage.";
}
}
child-router.html
<template>
<require from="../components/detail-navigation.html"></require>
<h2>${router.title}</h2>
<detail-navigation router.bind="router"></detail-navigation>
<div class="page-host">
<router-view></router-view>
</div>
</template>
basics.html
<template>
<h2>Basics Title</h2>
<h3>${var1}</h3>
</template>
data.html
<template>
<h2>Data Title</h2>
<h3>${var2}</h3>
</template>
I hope you understand my problem.
Here is a link to a test projekt on git.
I am looking forward for any recommendations.
Personally, I would really recommend you not try to do this. This is introducing tight coupling between the ChildRouter page and any pages displayed as routes on it. If you need these pages to talk to each other, consider using the Dependency Injection provider to inject an instance of the same class in to each page and sharing information that way.

Hiding routes in Aurelia nav bar until authenticated

Is there a proper way to hide items in the Aurelia getting started app behind some authentication.
Right now I'm just adding a class to each element based on a custom property. This feels extremely hacky.
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}${!row.isVisible ? 'navbar-hidden' : ''}">
<a href.bind="row.href">${row.title}</a>
</li>
There are two directions you can take here.
The first is to only show nav links in the nav bar when the custom property is set like you are. To clean it up a bit let's use the show binding -
<li repeat.for="row of router.navigation" show.bind="isVisible" class="${row.isActive ? 'active' : ''}">
<a href.bind="row.href">${row.title}</a>
</li>
The issue here is you still need to maintain the custom property like you are already doing. The alternative is to reset the router. This basically involves building out a set of routes that are available when the user is unauthenticated and then a separate set once the user is authenticated -
this.router.configure(unauthenticatedRoutes);
// user authenticates
this.router.reset();
this.router.configure(authenticatedRoutes);
This gives you the flexibility to reconfigure the router whenever you need to.
These answers are great, though for the purposes of authentication, I don't think any have the security properties you want. For example, if you have a route /#/topsecret, hiding it will keep it out of the navbar but will not prevent a user from typing it in the URL.
Though it's technically a bit off topic, I think a much better practice is to use multiple shells as detailed in this answer: How to render different view structures in Aurelia?
The basic idea is to send the user to a login application on app startup, and then send them to the main app on login.
main.js
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
// notice that we are setting root to 'login'
aurelia.start().then(app => app.setRoot('login'));
}
app.js
import { inject, Aurelia } from 'aurelia-framework';
#inject(Aurelia)
export class Login {
constructor(aurelia) {
this.aurelia = aurelia;
}
goToApp() {
this.aurelia.setRoot('app');
}
}
I've also written up an in-depth blog with examples on how to do this: http://davismj.me/blog/aurelia-login-best-practices-pt-1/
Although I like PW Kad's solution (it just seems cleaner), here's an approach that I took using a custom valueConvertor:
nav-bar.html
<ul class="nav navbar-nav">
<li repeat.for="row of router.navigation | authFilter: isLoggedIn" class="${row.isActive ? 'active' : ''}" >
<a data-toggle="collapse" data-target="#bs-example-navbar-collapse-1.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
nav-bar.js
import { bindable, inject, computedFrom} from 'aurelia-framework';
import {UserInfo} from './models/userInfo';
#inject(UserInfo)
export class NavBar {
#bindable router = null;
constructor(userInfo){
this.userInfo = userInfo;
}
get isLoggedIn(){
//userInfo is an object that is updated on authentication
return this.userInfo.isLoggedIn;
}
}
authFilter.js
export class AuthFilterValueConverter {
toView(routes, isLoggedIn){
console.log(isLoggedIn);
if(isLoggedIn)
return routes;
return routes.filter(r => !r.config.auth);
}
}
Note the following:
Your isLoggedIn getter will be polled incessantly
You can achieve the same with an if.bind="!row.config.auth || $parent.isLoggedIn" binding, but make sure that your if.bind binding comes after your repeat.for
I realize this is a bit of thread necromancy, but I wanted to add an answer because the accepted answer offers a solution that's explicitly recommended against by the Aurelia docs (you have to scroll down to the reset() method.
I tried several other methods, to varying degrees of success before I realized that I was looking at it wrong. Restriction of routes is a concern of the application, and so using the AuthorizeStep approach is definitely the way to go for blocking someone from going to a given route. Filtering out which routes a user sees on the navbar, though, is a viewmodel concern in my opinion. I didn't really feel like it was a value converter like #MickJuice did, though, as every example I saw of those were about formatting, not filtering, and also I felt like it's a bit cleaner / more intuitive to put it in the nav-bar view model. My approach was as follows:
// app.js
import AuthenticationService from './services/authentication';
import { inject } from 'aurelia-framework';
import { Redirect } from 'aurelia-router';
#inject(AuthenticationService)
export class App {
constructor(auth) {
this.auth = auth;
}
configureRouter(config, router) {
config.title = 'RPSLS';
const step = new AuthenticatedStep(this.auth);
config.addAuthorizeStep(step);
config.map([
{ route: ['', 'welcome'], name: 'welcome', moduleId: './welcome', nav: true, title: 'Welcome' },
{ route: 'teams', name: 'teams', moduleId: './my-teams', nav: true, title: 'Teams', settings: { auth: true } },
{ route: 'login', name: 'login', moduleId: './login', nav: false, title: 'Login' },
]);
this.router = router;
}
}
class AuthenticatedStep {
constructor(auth) {
this.auth = auth;
}
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.auth)) {
if (!this.auth.currentUser) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
OK, so that by itself will restrict user access to routes if the user isn't logged in. I could easily extend that to something roles based, but I don't need to at this point. The nav-bar.html then is right out of the skeleton, but rather than binding the router directly in nav-bar.html I created nav-bar.js to use a full view-model, like so:
import { inject, bindable } from 'aurelia-framework';
import AuthenticationService from './services/authentication';
#inject(AuthenticationService)
export class NavBar {
#bindable router = null;
constructor(auth) {
this.auth = auth;
}
get routes() {
if (this.auth.currentUser) {
return this.router.navigation;
}
return this.router.navigation.filter(r => !r.settings.auth);
}
}
Rather than iterating over router.navigation at this point, nav-bar.html will iterate over the routes property I declared above:
<ul class="nav navbar-nav">
<li repeat.for="row of routes" class="${row.isActive ? 'active' : ''}">
<a data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
Again, your mileage may vary, but I wanted to post this as I thought it was a fairly clean and painless solution to a common requirement.