How to pass URL parameters in router-link in VueJS - vue.js

I am trying to send a request to '/trackers/:id/Update' or '/trackers/:id/Delete' from my Trackers page in VueJS, the problem is my id is in a data called items which I am fetching from my API and storing it locally for displaying, I need to get that {{item.trackerid}} in my destination URL i.e '/trackers/:id/Delete'. I am unable to find a way. Can somebody show me the correct path.
TrackersView.vue:
<template>
<h1>Hello {{name}}</h1>
<h3>This is your Trackers page</h3>
<table class="table table-striped">
<thead>
<th>Sno</th>
<th>Tracker name</th>
<th>Tracker Description</th>
<th>Tracker settings</th>
<th>Date Created</th>
<th>Add tracker log</th>
<th>Actions</th>
</thead>
<tbody>
<tr v-for="item,index in items" :key="index">
<th>{{index}}</th>
<th>{{item.trackername}}</th>
<th>{{item.trackerdesc}}</th>
<th>{{item.tracker_settings}}</th>
<th>{{item.datecreated}}</th>
<th><button class="btn btn-outline-dark">+</button></th>
<th><button class="btn btn-outline-dark"><router-link :to="{ name: 'tupdate', params : {id : localStorage['id'] } }" style="text-decoration:none; color: black;">Update</router-link></button>
</th>
<th> <button class="btn btn-outline-dark"> <router-link to="/trackers/{{item.trackerid}}/Delete" style="text-decoration:none; color: black;">Delete</router-link></button></th>
</tr>
</tbody>
</table>
<button class="btn btn-outline-dark" #click="createtracker">Click to add trackers</button>
</template>
<script>
import { useRouter } from 'vue-router';
import axios from 'axios';
export default {
Name: 'TrackersView',
mounted() {
let id = localStorage['id']
return{
id,
}
},
data() {
return {
name: localStorage.name,
items : [],
}
},
methods:{
getTrackers(){
const path = 'http://localhost:5000/trackers/'+ localStorage['id']
axios.get(path)
.then((res) => {
this.items = res.data.tracker
})
.catch((err) => console.log(err.response))
console.log(this.items);
}
},
setup() {
const router = useRouter();
const createtracker = async () => {
await router.push({name: 'createtracker',params : {id : localStorage['id']}});
};
return{
createtracker,
}
},
created() {
this.getTrackers();
}
}
</script>
<style scoped>
body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
height: 100vh;
}
#keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
</style>t
the end result is like this:
This is what is present in localStorage:

The current route's query is in this.$route.query and you can pass that entire query object on to another route if you want to preserve it in the destination:
<router-link
:to="{ path: '/', query: $route.query }"
>
Go There
</router-link>
If you only want to pass a specific query param:
<router-link
:to="{ path: '/', query: { myQuery: $route.query.myQuery }}"
>
Go There
</router-link>

Related

Embeded template in vue

I would like to use a local component in VueJS:
My component file (cleaned up a bit):
<template id="heroValuePair">
<td class="inner label">{{label}}</td>
<td class="inner value">{{c}}</td>
<td class="inner value">
{{t}}
<span v-if="c < t" class="more">(+{{t-c}})</span>
<span v-if="c > t" class="less">({{t-c}})</span>
</td>
</template>
<template id="hero">
<table class="hero card" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>other data...</td>
</tr>
<tr>
<hvp label="Label" v-bind:c="current.level" :t="target.level" :key="hero.id"/>
</tr>
</table>
</template>
<script>
var HeroValuePair = {
template: "#heroValuePair",
props: {
label : String,
c : Number,
t : Number
},
created() {
console.log("HVP: "+this.c+" "+this.t);
}
};
Vue.component("Hero", {
template: "#hero",
props: {
heroId : String
},
components: {
"hvp" : HeroValuePair
},
data: () => ({
hero: {},
current: {},
target: {}
}),
computed: {
},
created() {
fetch("/api/hero/"+this.heroId)
.then(res => res.json())
.then(res => {
this.hero = res.hero
this.current = res.current
this.target = res.target
})
}
});
</script>
<style>
</style>
This outer Hero template is used in a list iterator:
<template id="card-list">
<table>
Card list
<div id="">
<div v-for="card in cards" class="entry">
<Hero :hero-id="card.hero.id" :key="card.hero.id"/>
</div>
</div>
</table>
</template>
<script>
Vue.component("card-list", {
template: "#card-list",
data: () => ({
cards: [],
}),
created() {
fetch("/api/cards")
.then(res => res.json())
.then(res => {
this.cards = res.heroes
})
.catch((e) => alert("Error while fetching cards: "+e));
}
});
</script>
<style>
</style>
However, when I render the card list, it only produces the list of the first td in hvp template:
When I comment out the call of hpv the page is rendered correctly with all the HTML code from Hero template.
I tried to figure out what step I left out, but can't find the clue.
One last info: I used JavalinVue to support the server side, not nodejs-based Vue CLI. I don't know if it has any impact, but may be important.
UPDATE 1
After IVO GELOV spot the problem with multiple root tags, and because I can't move to Vue3, I tried to make it as a functional template, as he suggested. I removed the template and created the render function:
var HeroValuePair = {
template: "#heroValuePair",
functional: true,
props: {
label : String,
c : Number,
t : Number
},
render(createElement, context) {
console.log("HVP: "+context.props.c+" "+context.props.t);
if (typeof context.props.c === undefined) return createElement("td" )
else return [
createElement("td", context.props.label ),
createElement("td", context.props.c ),
createElement("td", context.props.t )
]
}
}
Although the console indicated the render is called correctly, the result is the same: there is neither the rendered nodes, nor the parent Hero component displayed. I tried to move into different file, tried the functional template format, but none worked.

Vue Sorted Table (Sorted from highest to lowest)

Hi i am a newbie in vuejs. I wanted to sort a table by Highest to lowest. However i install the library of vue-sorted-table. But when im trying run the code the data always return lowest to highest. Can anyone help me? Thank you..
Here is the code:
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
code can access here:
https://codesandbox.io/s/pedantic-kirch-bx9zv
Add dir property to sorted-table, and make its value equals to desc
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort" dir="desc">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
check https://github.com/BernhardtD/vue-sorted-table and pay attention to dir property of the table, you'll find the answer

:class not updating when clicked in vuejs v-for

I want to add a css class to a item in v-for when the td in clicked
<template>
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
</tr>
<th>Delete</th>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<td>
<router-link :to="{ name: 'ListForce', params: { name: force.id } }">Link</router-link>
</td>
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</template>
<script>
import {APIService} from '../APIService';
const apiService = new APIService();
const _ = require('lodash');
export default {
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
computed: {
/* forcesOrdered() {
return this.orderByName ? _.orderBy(this.forces, 'name', 'desc') : this.forces;
}*/
},
mounted() {
this.getForces();
},
}
</script>
<style>
.completed {
text-decoration: line-through;
}
</style>
I want a line to go through the items when clicked, but the dom doesn't update. If I go into the vue tab in chrome I can see the force.done changes for each item but only if I click out of the object and click back into it. I'm not to sure why the state isn't updating as it's done so when I have used an click and a css bind before.
I've tried to make minimal changes to get this working:
// import {APIService} from '../APIService';
// const apiService = new APIService();
// const _ = require('lodash');
const apiService = {
getForces () {
return Promise.resolve([
{ id: 1, name: 'Red' },
{ id: 2, name: 'Green' },
{ id: 3, name: 'Blue' }
])
}
}
new Vue({
el: '#app',
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
for (const force of data) {
force.done = false;
}
this.forces = data;
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
mounted() {
this.getForces();
}
})
.completed {
text-decoration: line-through;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</div>
The key problem was here:
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
The problem here is that the property done is being added to the data after it has been made reactive. The reactivity observers get added as soon as the line this.forces = data runs. Adding done after that counts as adding a new property, which won't work.
It's also a misuse of map so I've switched it to a for/of loop instead.
for (const force of data) {
force.done = false;
}
this.forces = data; // <- data becomes reactive here, including 'done'
On an unrelated note I've tweaked the template to move the Delete header inside the top row.
Make sure force.done is set to false in data before initialization so it is reactive.
data() {
return {
force:{
done: false;
}
}
}
If force exists but has no done member set, you can use Vue.set instead of = to create a reactive value after initialization.
Vue.set(this.force, 'done', true);

Reload a Component

I have 3 components. They are dashboard.vue, datatable.vue and modalbody.vue. After login my application reach in dashboard.vue. Where I call datatable.vue to display a list with some props. I have a button in datatable.vue. If I click on that button a modal will open to add new record to add that list (datatable.vue) using modalbody.vue.
Now I need to reload that list (datatable.vue) after inserting new record through modal (modalbody.vue).
How can I do that ?
I am going to show you a simple example how to do it.Hope you will get the logic
Component which has the table:
<template>
<div>
<cmp :modal.sync="modal" #personAdded="addItemInTable"></cmp>
<button #click="addNewPerson">add person</button>
<table>
<tr v-for="row in tableRows">
<td>{{ row.name }}</td>
<td>{{ row.lastName }}</td>
</tr>
</table>
</div>
</template>
<script>
import childComponent from 'ChildComponent.vue'
export default {
components: {
"cmp": childComponent
},
data() {
return {
modal: false,
tableRows: [
{ name: "person1", lastName: "lperson1" },
{ name: "person2", lastName: "lperson2" },
{ name: "person3", lastName: "lperson3" },
{ name: "person4", lastName: "lperson4" },
]
}
},
methods: {
addNewPerson() {
this.modal = true //open the modal
},
addItemInTable(data) {
//saving the data passed from modal
this.tableRows.unshift(data)
this.modal = false
}
}
}
</script>
Modal Component:
<template>
<div id="modal" v-if="modal">
<input type="text" v-model="name">
<input type="text" v-model="">
<button #click="save">Save</button>
<button #click="cancel">Cancel</button>
</div>
</template>
<script>
export default {
props: {
modal: {
default: false
}
},
data() {
return {
name: '',
lastName: ''
}
},
methods: {
save() {
const savedData = {
name: this.name,
lastName: this.lastName
}
this.$emit('personAdded', savedData)
},
cancel() {
this.$emit('update:modal', false)
}
}
}
</script>
<style>
.modal {
position: absolute;
height: 500px;
width: 500px;
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
</style>
I dont have run the above code but the logic is correct.Please read below to understand:
For .sync modifier read this
For emitting events ($emit) read this
For reusing components read this

HeaderComponent with custom functionality

I would like to know how can I implement a CustomHeader with a custom functionality (other than sort, for example).
Basically what I want to know is how to communicate my HeaderComponent with my component that holds the grid. E.g.:
<template>
<div style="height: 100%" class="table-chart" ref="root">
<div class="title" ref="title">{{ objectData.title }}</div>
<div class="ag-dashboard" style="height: 100%; width: 90%; margin: 0 auto">
<ag-grid-vue
:style="{ height: tableHeight }"
:gridOptions="gridOptions"
:columnDefs="columnDefs"
:rowData="rowData"
/>
</div>
</div>
</template>
<script>
export default {
components: {
'HeaderComponent': {
template: '<span>{{this.params.displayName}} <span #click="custom">CLICK</span></span>',
methods: {
custom() {
// emmit an event here or find a way to comunnicate with the function "customEvent" below
}
}
}
},
methods: {
customEvent() {
console.log('Event from header');
}
},
beforeMount() {
// ... setup Ag-Grid and the HeaderComponent in the columns' headerComponentFramework
}
}
</script>
Apreciate any help,
The cleaner way I found of doing this was through an EventBus:
import Vue from 'vue';
const EventBus = new Vue();
export default {
//...
'HeaderComponent': {
// ...
methods: {
custom() {
EventBus.$emit('customEvent');
}
}
// ...
mounted() {
EventBus.$on('customEvent', () => {
// Do whatever...
});
}
}