VUE.js Routing Configuration - vue.js

I am creating an application where I have a list of users, when I click on a single user, it takes me to that specific users profile. I am using ASP.NET Core API with Vue.js as my front end. My API is working so when I click on the user, I am able to see the data coming from my database using Chrome dev Tools and Postman. However, once my page redirects to their profile, that page is blank. When I look in dev tools, I can see that its hitting my API and getting the correct information when looking at the preview tab.
So my question is, why is my page blank and not providing me with that users information. Can someone help me look at my routing, I think that is where my issue is?
I am passing their lastName as the param, but eventually I will pass a unique id
Here is my Profile.vue page, this is where I should see the users profile
<template>
<div class="container">
<v-card>
<v-data-table :headers="headers"
:items="records"
:search="search">
<template slot="items" slot-scope="records">
<td class="text-xl-left">{{ records.item.firstName }}</td>
<td class="text-xs-left">{{ records.item.email }}</td>
<td class="text-xs-left">{{ records.item.phone }}</td>
<td class="text-xs-left">{{ records.item.city }}</td>
<td class="justify-center layout px-0"></td>
</template>
</v-data-table>
</v-card>
</div>
</template>
<script>
import api from '../store/api.js'
export default {
data() {
return {
records: {},
headers: [
{ text: 'Full Name', value: 'fullName' },
{ text: 'Email', value: 'email' },
{ text: 'Phone', value: 'phone' },
{ text: 'City', value: 'city' },
{ text: 'Actions', value: 'name', sortable: false }
]
}
},
async created() {
this.GetInquiriesByUser()
},
methods: {
async GetInquiriesByUser() {
this.loading = true
try {
this.records = await api.GetInquiriesByUser()
} finally {
this.loading = false
}
},
}
}
</script>
I am using router.push to route me to the user profile from the previous page (the list)
editItem(lastName) {
this.$http.get(`http://localhost:61601/api/GetInquiry/${lastName}`)
this.$router.push({ path: `/Profile/${lastName}` })
},
Here is my routes.js file - I really think my error is in this, but cant seem to figure it out.
export const routes = [
{ name: 'home', path: '/', component: HomePage, display: 'Home', icon: 'home' },
{ name: 'AdminInquiry', path: '/Inquiry/AdminInquiry', component: AdminInquiry, display: 'Admin', icon: 'list' },
{ name: 'Profile', path: `/Profile/:lastName`, component: Profile }
]

I'm guessing that this is an issue with the router setup not matching your backend. The blank page can be indicative of this.
for your vue page, the default Router mode is hash which uses a url hack, which is that changes to anything after # character (with some caveats) do not cause the browser to redirect. So when you're going from let's say localhost:80#/Inquiry/AdminInquiry to localhost:80#/Profile/Drumpf, the change is handled entirely by the vue application. If however if you navigate from localhost:80/Inquiry/AdminInquiry to localhost:80/Profile/Drumpf the navigation is more complex. The js can handle the transition by artificially changing the url without an actual redirect taking place, if the event is triggered using js. If, however, that happens using a standard anchor, your browser redirect gets triggered, and it's up to your server-side application to handle what route is passed to the js app.
It looks like you're looking to implement history mode. This requires defining the mode as history in the vue app, and making the appropriate changes for your ASP, nginx, or apache to handle the routes. More info here: https://router.vuejs.org/guide/essentials/history-mode.html

Related

Nuxt / Vue redirect if vuex property is undefind not working

I got a nuxt app running which has an account page. This account page uses mapState computed properties for the user. User data is used in the account page template as well as its child components via props.
Whenever I start the app by going to myurl/account I get "can not read property x of undefined". Its obvious to me, as there is no logged in user when I go right away to /account.
I tried to push the routes back to the /login page within the created() hook of the account page, but its not working. I still get the same error.
How to deal with users trying to access a page before a property used by the template is set? The created hook logs only server side, not in the dev tools of chrome. Shouldnt this.$router.push("login") work?
ACCOUNT PAGE
<template>
<v-container fluid class="px-0 py-0 mt-12">
<v-row>
<accountheader :user="user" :company="company" />
</v-row>
</v-container>
</template>
<script>
import { mapState } from "vuex";
export default {
transitions: "page",
computed: {
...mapState("user", {
company: (state) => state.company,
user: (state) => state.user,
}),
},
head: {
title: "ACCOUNT",
meta: [
{
hid: "description",
name: "description",
content: "account page",
},
],
},
created() {
if (this.user === undefined) {
this.$router.push("/login");
}
},
};
</script>
<style></style>
I managed to get arround this myself by implementing a middleware in the page file itself like so. In case anyone runs into the same issue.
Solution
middleware({ store, redirect }) { // If the user is not authenticated if (store.state.user.user === undefined) { return redirect("/login"); } },

Vuex state not being initialised before router-view component being rendered - undefined error

I am relatively new to vue and have run into a small issue. I am rendering a component that depends on the state stored in vuex. I load this information in from a json file in the main part of the app. It all works fine if I always land on the root (index.html) of the app when it loads up. However, if I refresh the app from a page that is dynamically generated from the router I hit an error:
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
found in
---> <Room>
<RoomsOverview>
<Root>
As far as I can tell what is happening is that that the component is trying to access the state in vuex but it has not been initialised. Here is the component (Room.vue):
<template>
<div id="room">
<h2>{{ roomName }}</h2>
<div v-for="device in deviceList" v-bind:key="deviceList.name">
{{ device.name }} - {{ device.function}}
<svg-gauge v-bind:g-value="device.value" v-bind:g-min="0" v-bind:g-max="50" v-bind:g-decplace="1" g-units="℃">
<template v-slot:title>
Temperature
</template>
</svg-gauge>
</div>
</div>
</template>
<script>
module.exports = {
name: 'room',
/** Load external component files
* Make sure there is no leading / in the name
* To load from the common folder use like: 'common/component-name.vue' */
components: {
'svg-gauge': httpVueLoader('components/DisplayGauge.vue'),
}, // --- End of components --- //
data() {
return {
};
},
computed: {
roomName() {
// return this.$route.params.roomId;
return this.$store.getters['rooms/getRoomById'](this.$route.params.roomId);
},
deviceList() {
return this.$store.getters['rooms/getDevicesinRoom'](this.$route.params.roomId);
},
},
}
</script>
The error is triggered by the line
return this.$store.getters['rooms/getRoomById'](this.$route.params.roomId);
This tries to access the current state in the getter:
getRoomById: (state) => (id) => {
return state.rooms.find(room => room.id === id).name; // Needs fixing!
},
but it seems that the array:
// Initial state
const stateInitial = {
rooms: [],
};
has not been initialised under these circumstances. The initialisation occurs in the main entry point to the app in index.js in the mounted hook
// Load data from node-red into state
vueApp.$store.dispatch('rooms/loadRooms')
Where loadRooms uses axios to get the data. This works as expected if I arrive at the root of the site (http://192.168.0.136:1880/uibuilderadvanced/#/) but not if I arrive at a link such as (http://192.168.0.136:1880/uibuilderadvanced/#/rooms/office). I suspect it is all down to the order of things happening and my brain has not quite thought things through. If anyone has any ideas as to how to catch this I would be grateful - some kind of watcher is required I think, or a v-if (but I cannot see where to put this as the Room.vue is created dynamically by the router - see below).
Thanks
Martyn
Further information:
The room component is itself generated by router-view from within a parent (RoomsOverview.vue):
<template>
<div id="rooms">
<b-alert variant="info" :show="!hasRooms">
<p>
There are no rooms available yet. Pass a message that defines a room id and device id
to the uibuilder node first. See <router-link :to="{name: 'usage_info'}">the setup information</router-link>
for instructions on how start using the interface.
</p>
</b-alert>
<router-view></router-view>
</div>
</template>
<script>
module.exports = {
name: 'RoomsOverview',
data() {
return {
};
},
computed: {
hasRooms() {
return this.$store.getters['rooms/nRooms'] > 0;
},
roomList() {
return this.$store.getters['rooms/getAllRooms'];
},
},
}
</script>
and is dependent on the router file:
const IndexView = httpVueLoader('./views/IndexView.vue');
const AdminView = httpVueLoader('./views/AdminView.vue');
export default {
routes: [
{
path: '/',
name: 'index',
components: {
default: IndexView,
menu: HeaderMenu,
},
},
{
path: '/rooms',
name: 'rooms_overview',
components: {
default: httpVueLoader('./components/RoomsOverview.vue'),
menu: HeaderMenu,
},
children: [
{
path: ':roomId',
name: 'room',
component: httpVueLoader('./components/Room.vue'),
},
],
},
{
path: '/admin',
name: 'admin',
components: {
default: AdminView,
menu: HeaderMenu,
},
children: [
{
path: 'info',
name: 'usage_info',
component: httpVueLoader('./components/UsageInformation.vue'),
}
]
},
],
};
It seems you already got where the issue is.
When you land on you main entry point, the axios call is triggered and you have all the data you need in the store. But if you land on the component itself, the axios call does not get triggered and your store is empty.
To solve you can add some conditional logic to your component, to do an axios call if the required data is undefined or empty.

Where should route meta data be loaded in a Vue app?

I'm in the process of setting up a VueJs SPA. I'm using vue-router and I'm trying to find the best solution to the following problem. I have a series of routes. Each of which needs to call an API to get the meta data for the given ID.
/industry/:id/overview
/industry/:id/top-stories
/industry/:id/top-tweets
/brand/:id/overview
/brand/:id/top-stories
/brand/:id/top-tweets
I've been looking at using created or beforeRouteEnter/beforeRouteUpdate and I'm a bit lost. Ideally, I would only fetch new data when a new /industry/:id is reached, not when navigating between pages within the same ID. Also, I'd like to avoid having to define the fetch to grab data in every page component. Also don't want to over complicate this, so my question is, Is there a standard method for tackling this issue?
Clarification:
When I say meta here, I mean data returned from an API about the given industry or brand which I pull using the ID in the route. The api call includes the name of the industry/brand which I want to have on page as soon as the page is presented to the user.
I have something similar. I tackle this using the following approach:
I use the same component for all /industry/:id Vue likes to reuse components wherever it can so if two routes (for example /industry/:id/overview and /industry/:id/top-stories) are using the same component it will stay the same.
What does change, however, is the route meta. So if you add a page key to the meta object in the route objects, and probably add a computed property called page that return this.$route.meta.page, you can use v-if attributes to conditionally render any component. So you might have something like <div v-if="page === 'overview'"></div><div v-else-if="page==='top-stories'"></div>
What this allows you to do is fetch all the data from the API during created or mounted lifecycle and store it as the state. Since the route change doesn't reload the component the state stays the same.
Here is a code example
// router.js
const Project = () =>
import(/* webpackChunkName: "projects" */ "./views/projects/_id");
export default new Router({
mode: "history",
routes: [
{
path: "/projects/:project_id/views",
name: "ViewProject",
component: Project,
meta: {
page: "views",
}
},
{
path: "/projects/:project_id/export",
name: "ExportProject",
component: Project,
meta: {
page: "exports"
}
},
{
path: "/projects/:project_id/recommendations",
name: "ProjectRecommendations",
component: Project,
meta: {
page: "recommendations"
}
},
]
});
And here is the template
<template>
<div v-if="project">
<h1>{{ project.name }}</h1>
<router-link :to="/project/someid/views">Views</router-link>
<router-link :to="/project/someid/exports">Exports</router-link>
<router-link :to="/project/someid/recommendations">Recommendations</router-link>
<ul v-if="page==='views">
<li v-for="(view, i) in project.views" :key="i">{{ views }}</div>
</ul>
<ul v-else-if="page==='exports">
<li v-for="(export, i) in project.exports" :key="i">{{ export }}</div>
</ul>
<ul v-else-if="page==='recommendations">
<li v-for="(recommendation, i) in project.recommendations" :key="i">{{ recommendation }}</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
project: null
}
},
computed: {
page() {
return this.$route.meta.page;
}
},
mounted() {
this.getProject()
},
methods: {
getProject() {
axios
.get(`/projects/someid`)
.then(res => this.project = res.data)
}
}
}
</script>

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