how to use v-for index as value for href - vue.js

I am using VueJS along with foundation css. I am trying to create an accordion from a v-for loop. However VueJS wants me to bind the href to the projectIndex variable.
I have tried creating a projectIndex parameter and then using a computed property to return a concatenation of "#" + projectIndex, but projectIndex is always undefined.
data: {
projectIndex: ''
}
....
computed:{
projectHref: function () {
return "#" + this.projectIndex;
}
}
Out of interest I am using [[]] rather than {{}} because I am also using twig which shares the same {{}} as VueJS
<ul class="accordion" data-accordion>
<li class="accordion-navigation" v-for="(priceData, projectIndex) in prices">
Price Project: [[priceData.meta.project.name]]
<div id="[[projectHref]]">
<div v-for="(prices, supplier) in priceData.prices">
<h6>[[supplier|SupplierKeyName]] ([[supplier|SupplierKeyCode]])</h6>
<table class="table">
<tr>
<td v-for="(price, priceBreak) in prices">[[priceBreak]]</td>
</tr>
<tr>
<td v-for="(price, priceBreak) in prices">[[price.currency]][[price.price]]</td>
</tr>
</table>
</div>
</div>
</li>
</ul>

is the prices variable already set?
if yes, then I think it is replaced by your second v-for that use same prices variable, so that the for loop breaks away
//here is the first 'prices' variable
<li class="accordion-navigation" v-for="(priceData, projectIndex) in prices">
Price Project: [[priceData.meta.project.name]]
<div id="[[projectHref]]">
//here the prices variable is replaced
<div v-for="(prices, supplier) in priceData.prices">
try to differentiate those vars

OK, I found the solution. I found that I could bind to a method and pass projectIndex in as a value. like so
<li class="accordion-navigation" v-for="(priceData, projectIndex) in prices">
<a :href="projectHref(projectIndex)">Price Project: [[priceData.meta.project.name]]</a>
My method then returns the concatonation that I needed.
methods: {
projectHref: function (index) {
return "#" + index.toString();
}
}

Related

Vue - change/set a variable value in template

Working in Vue, I am trying to set a variable based on another variable within the template. This is within a loop, and I need to set a value that can be used in 'next' iteration of the loop (to change the way a table is rendered, based on the variable).
I have the following:
<template>
...
<tbody v-for="(lo,index) in learn" :key="lo.id">
<tr>
<td colspan="2">LO{{index+1}} {{lo.attributes.field_lo}}</td>
<td v-if="!nextDist"> </td>
</tr>
<tr>
<td>
<div
v-for="(pass,index) in lo.attributes.field_pass"
:key="index"
>P{{pStep()}} {{pass}}</div>
</td>
<td>
<div
v-for="(merit,index) in lo.attributes.field_merit"
:key="index"
>M{{mStep()}} {{merit}}</div>
</td>
<td v-if="lo.attributes.field_dshared && next" ***SET VALUE OF this.next*** rowspan="3">
<span class="has-text-weight-bold">D{{dStep()}} </span>{{lo.attributes.field_dist}}
</td>
<td v-else-if="!lo.attributes.field_dshared" ***SET VALUE of this.next*** ><span class="has-text-weight-bold">D{{dStep()}} </span>{{lo.attributes.field_dist}}
</td>
***else render nothing***
</tr>
</tbody>
</template>
export default {
name: "SpecUnit",
components: {
EssentialContent
},
data() {
return {
unit: "",
learn: "",
nextDist: "",
next: ""
};
},
...
}
What I'd like to be able to do is set the value of 'next' (this.next) so that when the loop iterates, I can check to see if I should which of the I should render or render nothing (because we are 'rowspanning').
I've tried computed and methods, but can't seem to get this working. I've looked to use Vue.set, but I'm struggling with that.
I'm still new to Vue, so any help would be greatly appreciated.
Thanks
It looks like Florian Reuschel had a similar problem and already solved it (although with some caveats)
Let's say we have something like that:
<!-- List.vue -->
<ul>
<li v-for="id in users" :key="id">
<img :src="getUserData(id).avatar"><br>
🏷️ {{ getUserData(id).name }}<br>
πŸ”— {{ getUserData(id).homepage }}
</li>
</ul>
His approach is to use a helper renderless component with a scoped slot
const Pass = {
render() {
return this.$scopedSlots.default(this.$attrs)
}
}
and then
<!-- List.vue -->
<ul>
<Pass v-for="id in users" :key="id" :metadata="getUserData(id)">
<li slot-scope="{ metadata }">
<img :src="metadata.avatar"><br>
🏷️ {{ metadata.name }}<br>
πŸ”— {{ metadata.homepage }}
</li>
</Pass>
</ul>
If you take a look at the comments section on his blog article, you will see other approaches, too. For example, you can use an expression inside v-bind
<li v-for="id in users" :key="id" :demo="item = getUserData(id)">
<img :src="item.avatar" /><br />
🏷️ {{ item.name }}<br />
πŸ”— {{ item.homepage }}
</li>

v-bind:class not working with array variables

I have the following code in vue-js:
<table class="table table-sm user-perm-list">
<tbody v-for="index in totalUsers" v-bind:key="index">
<tr>
<td>
<div class="text-medium">{{userPermissionsName[index-1]}} </div>
<div class="d-flex py-1">
<a
style="cursor:pointer; margin-right:0.4rem"
#click=userPermissionsDownloadClicked(index-1)
:title="userPermissionsDownload[index-1] ? 'Download Permitted':'Download not permitted'"
v-bind:class="[userPermissionsDownload[index-1] ? 'permission-icon-class-active' : 'permission-icon-class-inactive']">
<downloadPermissionIcon/>
</a>
</div>
</td>
</tr>
</tbody>
</table>
the v-bind:class inside "a" tag is not working. Inside userPermissionsDownloadClicked, I am toggling the variable: userPermissionsDownload[index-1]. It works with a non array variable but not working with array variable
This is one of the "Vue Gotchas"
https://vuejs.org/2016/02/06/common-gotchas/
From "Why isn’t the DOM updating?"
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is syntax sugar for arr.splice(index, 1, value).

Using v-for and v-show to hide/show additional text inside an p element

I have this html code
<tr v-for="(help, index) in helps">
<td scope="row">{{ help.ID }}</td>
<td scope="row">{{ help.Date | formatDateWithTime }}</td>
<td>
<p #click="fullTextFun(index)" v-show="help.FullText">{{ help.Text.substring(0,16) + '...' }}</p>
<p #click="fullTextFun(index)" v-show="!help.FullText">{{ help.Text }}</p>
</td>
</tr>
I want to be able to show the full text when someone click on the current p element. This is my vue function
fullTextFun: function(index) {
this.helps[index].FullText = !this.helps[index].FullText;
},
It doesn't work. I also tried to do it using this code
<span #click="fullTextFun(help)" v-show="help.FullText">{{ help.Text.substring(0,16) + '...' }}</span>
fullTextFun: function(item) {
item.FullText = !item.FullText;
},
But again without any luck. It seems the v-show function don't care about the status of help.FullText
When I load the data I don't have FullText variable in my helps array. I don't know if this is the problem
This is what it is inside my helps variable when first loaded
[{"ID":"2","Date":"2019-05-15
17:27:29","Text":"randomText"},{"ID":"4","Date":"2019-05-17
09:53:59","Text":"some text"}]
It might be Vue reactivity issue.
fullTextFun: function(index) {
this.helps[index].FullText = !this.helps[index].FullText;
this.helps = JSON.parse(JSON.stringify(this.helps))
}
Basically Vue only updates if you change the reference.
When you update this.helps[index].FullText, this.helps still points to old object reference, and Vue can't recognize the change.
Another solution is using Vue.set
Vue.set(this.helps[index], 'FullText', !this.helps[index].FullText)
You can read more at Vue document

Getting data back into the root with nested components in vue

I am building a multiple page app with latest Laravel and latest Vue.js. At the end of this post you will see what I am trying to achieve - which I have done visually. However the user needs to be able to edit the text, assigned user and the date of each item. I have started with the date and as you can see I have the date picker working as well.
Where I am struggling is updating the main model of data in the root so that I can save the changes that the user has made via a HTTP request. Initially the tree's data is loaded in via HTTP as well (example below).
I have built the below using nested components and I have read that two binding has been depreciated for props on components. I know that I need to emit and user events but I'm sure how this would work if the components are nested?
Here is an example of the data that get's loaded via HTTP. Below is a very small example, however this could be much larger
{
"objective":"Test",
"user_id":null,
"by":"08\/09\/2018",
"colour":"#1997c6",
"children":[
{
"objective":"Test",
"user_id":11,
"by":"08\/09\/2018",
"colour":"#d7e3bc",
"children":[]
}, {
"objective":"Test",
"user_id":11,
"by":null,
"colour":"#1997c6",
"children":[]
}
]
}
Here are the components that I have put together so far.
Vue.component('tree-date', {
props: ['date'],
data () {
return {
id: 0
}
},
mounted() {
this.id = uniqueId();
$('#picker-' + this.id).datetimepicker({
format: 'DD/MM/YYYY',
ignoreReadonly: true
});
},
template: `
<div class="date-container" :id="'picker-' + id" data-target-input="nearest" data-toggle="datetimepicker" :data-target="'#picker-' + id">
<div class="row">
<div class="col-2">
<div class="icon">
<i class="fa fa-calendar-alt"></i>
</div>
</div>
<div class="col-10">
<input type="text" class="form-control datetimepicker-input" readonly="readonly" :data-target="'#picker-' + id" v-model="date">
</div>
</div>
</div>`
});
Vue.component('tree-section', {
props: ['data', 'teamUsers', 'first'],
methods: {
test () {
this.$emit('test');
}
},
template: `
<table v-if="data.length != 0">
<tr>
<td :colspan="data.children !== undefined && (data.children.length * 2) > 0 ? data.children.length * 2 : 2">
<div class="node" :class="{'first': first == true}">
<div class="inner">
<tree-date :date="data.by"></tree-date>
<div class="objective">
{{ data.objective }}
</div>
<div class="author" v-if="data.user_id !== null">
{{ teamUsers[data.user_id].first_name }} {{ teamUsers[data.user_id].last_name }}
</div>
<div class="author" v-if="data.user_id === null">
Unassigned
</div>
</div>
</div>
</td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td :colspan="data.children.length * 2"><div class="downLine"></div></td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td class="rightLine"></td>
<td class="topLine" v-for="index in ((data.children.length * 2) - 2)" :key="index" :class="{'rightLine': index % 2 == 0, 'leftLine': Math.abs(index % 2) == 1}"></td>
<td class="leftLine"></td>
</tr>
<tr v-if="data.children.length > 0">
<td colspan="2" v-for="child in data.children">
<tree-section :data="child" :team-users="teamUsers" :first="false"></tree-section>
</td>
</tr>
</table>
`
});
This all get's called in the view by:
<tree-section :data="data" :team-users="teamUsers" :first="true"></tree-section>
Any help getting data update in the components back into the root will be most helpful.
by default, vue props (if objects or arrays) are being passed by reference- that means that if you change your object on the child component, the original object on the parent component will get changed too.
from vue api:
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect parent state.
https://v2.vuejs.org/v2/guide/components-props.html

How to process the emitted event from the component in v-for of Vuejs

I'm trying to process the emitted the event from the component rendered by the v-for.
For example,
I've made a combobox component that emits the event when changing the value.
It emits the event by this.$emit('item_change', item);.
I want to process this event for the corresponded user.
In the below code, I want to change the status value of the user when changing the value of the combobox of user.
It gets item as parameter when using v-on:item_change="status_change"
example
But it doesn't get the item as parameter in v-on:item_change="status_change(item , user)" though combobox emits the event with item, and status of user keeps the original value.
How could I solve this issue?
JSFiddle Example
<div id="mainapp">
<table>
<thead>
<th>Name</th><th>Status</th>
</thead>
<tbody>
<tr v-for="user in users">
<td>{{user.name}}</td>
<td><combobox v-bind:default="user.status" v-bind:data="status_codes" v-on:item_change="status_change(item, user)"></combobox></td>
</tr>
</tbody>
</table>
</div>
JS code
var combobox = Vue.component('combobox', {
data: function () {
return {
selected_item:{title:'Select', value:-1},
visible:false
}
},
props:['data','default','symbol'],
template: `
<div class="combobox">
<span class="symbol" v-if="!symbol">
<i class="fa fa-chevron-down" aria-hidden="true" ></i>
</span>
<span class="main" v-on:click="toggleVisible">{{selected_item.title}}</span>
<ul class="combodata" v-if="visible">
<li class="item" v-for="item in data" v-on:click="select(item)">{{item.title}}</li>
</ul>
</div>
`,
created:function(){
if(this.data.length>0){
if(this.default == null || this.default == undefined || this.default =='') this.default=0;
this.selected_item = this.data[this.default];
}
},
methods:{
toggleVisible:function(){
this.visible = !this.visible;
},
select:function(item){
if(this.selected_item != item){
this.selected_item= item;
this.$emit('item_change', item);
}
this.visible = false;
}
}
});
var app=new Vue({
el:"#mainapp",
data:{
status_codes:[{title:'Inactive', value:0},{title:'Active', value:1}],
users:[{name:'Andrew', status:1},{name:'Jackson', status:0},{name:'Tom', status:1}]
},
methods:{
status_change:function(item,user){ //This gets only the parameter from the event. How could I pass the additional parameters to this function?
console.log(item,user);
try{
user.status = item.value;
}catch(e){ console.log}
}
}
});
You need to pass $event to your status_change handler instead of item
<div id="mainapp">
<table>
<thead>
<th>Name</th>
<th>Status</th>
</thead>
<tbody>
<tr v-for="user in users">
<td>{{user.name}}</td>
<td>
<combobox v-bind:default="user.status" v-bind:data="status_codes" v-on:item_change="status_change($event, user)"></combobox>
</td>
</tr>
</tbody>
</table>
</div>
JSFiddle
See the Vue docs here about event handling:
Sometimes we also need to access the original DOM event in an inline statement handler. You can pass it into a method using the special $event variable
Use $event
What you need is actually v-on:item_change="status_change($event , user)".
When you do this.$emit('item_change', whatever);, whatever will become $event in the event listener.
https://jsfiddle.net/jacobgoh101/bLsw085r/1/
Try passing parameters to your function like this:
v-on:item_change="status_change(item, user)"
And in your function declaration, specify the parameters:
status_change: function (item, user) {
console.log(item, user);
}