Best solution for getting {id} from url? - vue.js

I'm trying to create a feature where individuals can create Listings, and other users can submit an application. ATM I can't quite figure out how to get the ID from the URL of:
'http://localhost:3000/listings/2/'
I have a hunch that this needs Vue router and $route.params.id? Basically I want in my form.listing the ID of the listing in which users are applying to. In this case it would be '2'
My folder structure is .../listings/_id/index.vue
This is my backend for the user_applications model in DRF:
listing = models.ForeignKey(Listings, on_delete=models.CASCADE, default="1")
Here's the frontend: I was hoping I could use the props value in script, but it seems like it only works in template, oh well.
data: () => ({
form: {
role: null,
company: null,
status: "Pending"
// listing: listings.id
}
}),
props: ["listings"],
computed: {
...mapGetters(["loggedInUser"])
},
methods: {
submit() {
this.$axios
.post("/api/v1/apps/", this.form)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
}

You are on the right track when thinking of using VueRouter. Lets say that your component name is Listing.
import VueRouter from 'vue-router';
import Listing form './path/to/ListingComponent.vue';
const router = new VueRouter({
routes: [
{
path: '/listing/:id',
component: Listing,
// return parms.id as listingId for the `Listing` component.
props: route => ({ listingId: route.params.id })
}
]
})
In this way you have listingId passed to the props of Listing.

Yes, it's as easy as this.$route.params.id. You can invoke $route.params.id directly in your template, without using this. Don't use arrow functions in your component, though! They strip out the value of this! Also, all props and data attributes are available on this as well. So this.listing and this.$route are available, not only in your template, but in your Vue component's methods and computed properties, as well.
Try this approach, instead:
data() {
return {
form: {
role: null,
company: null,
status: "Pending",
listing: this.listings.id
}
}
},

Related

Is there a way i can get a route parameter in vue

I am new to vuejs, i have my api in laravel having a route such as
Route::get('/get-song-details/{id}','SongsController#getSongDetails');. How do get this id in vue so that i can display only information about the selected song.
in the sample below, when i pass 1, i get information about song 1, and so on. how do i make it dynamic?
export default {
data(){
return {
songs : []
}
},
created(){
axios.get('/api/get-song-details/1')
.then(response => this.songs = response.data);
}
};
</script>
asumming you are using vue-router, the route component should be declared so:
routes: [
{ path: '/song/:id', component: Song }
]
then in your created method of Song component can get the id param:
created() {
axios.get('/api/get-song-details/' + this.$route.params.id)
...
}
All route params can be accessed by using this.$route.params.paramName in JS code or by using $route.params.paramName in template. paramName is the parameter name as defined in your router.

How can I Filter Computers by Site ID in a Vuex getter

I'm building an app that will present the user with a list of sites, once the user has clicked on a site all of the computers present at that site will display, and then a user can click on a computer to get the details for that machine.
I've successfully used Axios to consume our API of sites and computers, i have also setup vue-router to generate the routes and im using vuex as the store. I can display the entire list of computers in the site component, but now I need to filter the computers so that only computers whose computer.site match site.id.
I'm new to vue.js and vuex so this is a challenge for me. I think I just need a push in the right direction and I can get over this hurdle. My goal is to re-use this type of filter for other logic as well as Alerts that match a site.id etc.
I'm not sure where to start with this, so here I am.
Here is my store.js file.
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex, axios);
export default new Vuex.Store({
state: {
sites: [],
computers: []
},
actions: {
// SITES
getSites({commit}) {
axios
.get("/sites/read.php", {})
.then(response => {
let sites = response.data.data;
commit("SET_SITES", sites);
})
.catch(error => {
// eslint-disable-next-line
console.log(error);
});
},
// SITES
getComputers({commit}) {
axios
.get("/sshtunnels/read.php", {})
.then(response => {
let computers = response.data.data;
commit("SET_COMPUTERS", computers);
})
.catch(error => {
// eslint-disable-next-line
console.log(error);
});
}
},
getters: {
siteData: state => state.sites,
// need to change this to filter to only have computers that match the current site id
// Can we derive the current id from this.$route.params.siteID?
computerData: state => state.computers,
//something like this?
getComputerbyID: (state, id) => (id) => {
return state.computers.find(computer => computer.Site === site.id)
}
},
mutations: {
SET_SITES(state, sites) {
state.sites = sites;
},
SET_COMPUTERS(state, computers) {
state.computers = computers;
}
}
});
computed props :
computed: {
sites() {
return this.$store.getters.siteData;
},
computers() {
return this.$store.getters.computerData;
}
}
I'd expect to have the output of {{ computer.hostname }} filtered to only show computers whose ID match the Site ID.
Thanks everyone!
UPDATE
Thanks for your input, however I get an error
Property or method "siteId" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Its true that I don't have a property called siteID. I do have site.ID in the sites array. But that doesn't appear to work either as I cant use site.ID in the fat arrow function.
This does not work
getComputersBySiteId: (state) => (site.id) => {
return state.computers.filter(computer => computer.Site === site.id);
Do I need to add a let id = site.id somewhere?
First off create a Vuex getter with params.
getComputersBySiteId: (state) => (id) => {
return state.computers.filter(computer => computer.siteId === id);
}
Then consume it in component like this: const computers = this.$store.getters.getComputersById(siteId);
Or using mapGetters helper.
import { mapGetters } from "vuex";
computed: {
...mapGetters(["getComputersBySiteId"])
}
Then in template:
<ul>
<li v-for="computer in getComputersBySiteId(siteId)" :key="computer.id">
{{ computer.name }}
</li>
</ul

Fetching before navigation with multiple components using vue-router

Trying to figure out what is the best way to fetch some data before navigating to some routes that I have.
My routes are:
let routes = [
{
path: '/',
component: require('./views/Home.vue')
},
{
path: '/messages',
component: require('./views/Messages.vue'),
},
{
path: '/posts',
component: require('./views/Post.vue'),
},
{
path: '/login',
component: require('./views/Login.vue')
},
{
path: '/dashboard',
component: require('./views/Dashboard.vue'),
}
];
For /messages, /posts and /dashboard I want to fetch some data and while doing that show a spinner.
Vue-router docs suggest to use beforeRouteEnter, Fetching Before Navigation
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
}
But my question is, must I implement this fetch logic in all my three view components?
Since it's the same data being fetched I don't want to repeat the logic in every component.
The beforeRouteEnter isn't called in my root component where I have this <router-view></router-view> otherwise I feel that it would be the best place to have this logic.
I'm really new to vue and this one is a really hard nut to crack for me.
Are you aware of vuex? Since you need to share the same data on three different pages, which sounds like a job for vuex store. Basically you can import the store into your router, and set data in the store instead of on the component, and then you can conditionally fetch the data by checking the status of the data in the store and if the data already exists, don't fetch. Something like the following:
import store from './store'
beforeRouteEnter (to, from, next) {
if (store.state.post === null) {
getPost(to.params.id, (err, post) => {
store.dispatch('setPost', post)
next()
})
}
}
Assume your store has a state called post and action called setPost.
const store = new Vuex.Store({
state: { post: null },
actions: {
setPost ({ commit }, post) {
// set the post state here
}
}
})

what is vuex-router-sync for?

As far as I know vuex-router-sync is just for synchronizing the route with the vuex store and the developer can access the route as follows:
store.state.route.path
store.state.route.params
However, I can also handle route by this.$route which is more concise.
When do I need to use the route in the store, and what is the scenario in which I need vuex-router-sync?
Here's my two cents. You don't need to import vuex-router-sync if you cannot figure out its use case in your project, but you may want it when you are trying to use route object in your vuex's method (this.$route won't work well in vuex's realm).
I'd like to give an example here.
Suppose you want to show a message in one component. You want to display a message like Have a nice day, Jack in almost every page, except for the case that Welcome back, Jack should be displayed when the user's browsing top page.
You can easily achieve it with the help of vuex-router-sync.
const Top = {
template: '<div>{{message}}</div>',
computed: {
message() {
return this.$store.getters.getMessage;
}
},
};
const Bar = {
template: '<div>{{message}}</div>',
computed: {
message() {
return this.$store.getters.getMessage;
}
}
};
const routes = [{
path: '/top',
component: Top,
name: 'top'
},
{
path: '/bar',
component: Bar,
name: 'bar'
},
];
const router = new VueRouter({
routes
});
const store = new Vuex.Store({
state: {
username: 'Jack',
phrases: ['Welcome back', 'Have a nice day'],
},
getters: {
getMessage(state) {
return state.route.name === 'top' ?
`${state.phrases[0]}, ${state.username}` :
`${state.phrases[1]}, ${state.username}`;
},
},
});
// sync store and router by using `vuex-router-sync`
sync(store, router);
const app = new Vue({
router,
store,
}).$mount('#app');
// vuex-router-sync source code pasted here because no proper cdn service found
function sync(store, router, options) {
var moduleName = (options || {}).moduleName || 'route'
store.registerModule(moduleName, {
namespaced: true,
state: cloneRoute(router.currentRoute),
mutations: {
'ROUTE_CHANGED': function(state, transition) {
store.state[moduleName] = cloneRoute(transition.to, transition.from)
}
}
})
var isTimeTraveling = false
var currentPath
// sync router on store change
store.watch(
function(state) {
return state[moduleName]
},
function(route) {
if (route.fullPath === currentPath) {
return
}
isTimeTraveling = true
var methodToUse = currentPath == null ?
'replace' :
'push'
currentPath = route.fullPath
router[methodToUse](route)
}, {
sync: true
}
)
// sync store on router navigation
router.afterEach(function(to, from) {
if (isTimeTraveling) {
isTimeTraveling = false
return
}
currentPath = to.fullPath
store.commit(moduleName + '/ROUTE_CHANGED', {
to: to,
from: from
})
})
}
function cloneRoute(to, from) {
var clone = {
name: to.name,
path: to.path,
hash: to.hash,
query: to.query,
params: to.params,
fullPath: to.fullPath,
meta: to.meta
}
if (from) {
clone.from = cloneRoute(from)
}
return Object.freeze(clone)
}
.router-link-active {
color: red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<div id="app">
<p>
<router-link to="/top">Go to Top</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
fiddle here
As you can see, the components are well decoupled from vuex and vue-router's logic.
This pattern sometimes works really effectively for the case that you're not concerned about the relationship between current route and the value returned from vuex's getter.
I saw this thread when I was learning Vue. Added some of my understanding on the question.
Vuex defines a state management pattern for Vue applications. Instead of defining component props and passing the shared state through props in all the places, we use a centralized store to organize the state shared by multiple components. The restriction on state mutation makes the state transition clearer and easier to reason about.
Ideally, we should get / build consistent (or identical) views if the provided store states are the same. However, the router, shared by multiple components, breaks this. If we need to reason about why the page is rendered like it is, we need to check the store state as well as the router state if we derive the view from the this.$router properties.
vuex-router-sync is a helper to sync the router state to the centralized state store. Now all the views can be built from the state store and we don't need to check this.$router.
Note that the route state is immutable, and we should "change" its state via the $router.push or $router.go call. It may be helpful to define some actions on store as:
// import your router definition
import router from './router'
export default new Vuex.Store({
//...
actions: {
//...
// actions to update route asynchronously
routerPush (_, arg) {
router.push(arg)
},
routerGo (_, arg) {
router.go(arg)
}
}
})
This wraps the route updates in the store actions and we can completely get rid of the this.$router dependencies in the components.

is it possible to specify which component should be used on router.go() in VueJS

In VueJS im trying to setup a scenario where the component used is determined by the url path without having to statically map it.
e.g.
router.beforeEach(({ to, next }) => {
FetchService.fetch(api_base+to.path)
.then((response) => {
router.app.$root.page = response
// I'd like to specify a path and component on the fly
// instead of having to map it
router.go({path: to.path, component: response.pageComponent})
})
.catch((err) => {
router.go({name: '404'})
})
})
Basically, I'd like to be able to create a route on the fly instead of statically specifying the path and component in the router.map
Hope that make sense. Any help would be appreciated.
I think that what you're trying to archive is programmatically load some component based on the current route.
I'm not sure if this is the recommended solution, but is what comes to my mind.
Create a DynamicLoader component whit a component as template
<template>
<component :is="CurrentComponent" />
</template>
Create a watch on $route to load new component in each route change
<script>
export default {
data() {
return {
CurrentComponent: undefined
}
},
watch: {
'$route' (to, from) {
let componentName = to.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
},
beforeMount() {
let componentName = this.$route.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
}
</script>
Register just this route on your router
{ path: '/:ComponentName', component: DynamicLoader }
In this example I'm assuming that all my componennt will be in components/ folder, in your example seems like you're calling an external service to get the real component location, that should work as well.
Let me know if this help you
As par the documentation of router.go, you either need path you want to redirect to or name of the route you want to redirect to. You don't the component.
Argument of router.go is either path in the form of:
{ path: '...' }
or name of route:
{
name: '...',
// params and query are optional
params: { ... },
query: { ... }
}
so you dont need to return component from your API, you can just return path or name of route, and use it to redirect to relevant page.
You can find more details here to create named routes using vue-router.