Vue - change/set a variable value in template - vue.js

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>

Related

How to emit function with id from child component to parent in vue.js

I have a card component and a house component . I want to emit function along with id from child to parent component.I want to call edit function from card component which is in house component.
card.vue
<tr class="registrationtable" v-for="valueList in values" v-bind:key="valueList">
<td class="registrationtable" v-for="head in columnHeader" v-bind:key="head">
<span v-if="href.includes(head)">
{{ head }}
<a
v-for="action in actionList"
v-bind:key="action"
v-on:click="methodToCall(valueList[head])"
>{{ action }}</a
></span
>
<span v-else></span>
</td>
</tr>
created() {
this.methodToCall(id);
},
house.vue
<card :method-to-call="editFunction" />
You can use this.$emit()
card.vue template
<tr class="registrationtable" v-for="valueList in values" v-bind:key="valueList">
<td class="registrationtable" v-for="head in columnHeader" v-bind:key="head">
<span v-if="href.includes(head)">
{{ head }}
<a
v-for="action in actionList"
v-bind:key="action"
v-on:click="methodToCall(valueList[head])"
>{{ action }}</a
></span
>
<span v-else></span>
</td>
</tr>
card.vue
methods:{
methodToCall:function(id){
this.$emit('methodToCall', id)
}
}
house.vue
<card :method-to-call="editFunction" />

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

v-for look using curly brackets

I ran into a situation that I need to loop true items without producing any HTML. I expect the code to look something like this.
<table id="detailTable">
<tr>
<th class='editRow'></th>
<th class='editRow'></th>
<!-- <th class='editRow'></th> -->
<th v-for='(columns, index) in $parent.columns' :key='index'>{{ firstLetterCaps(columns) }}</th>
</tr>
{{ for (row, index) in $parent.results }}
<resultsRows v-for='(row, index) in $parent.results' :key='index' :row='row' :index='index' :deleteQueryObjects='$parent.deleteQueryObjects'></resultsRows>
<resultsCommentRow v-for='(row, index) in $parent.results' :index='index'></resultsCommentRow>
{{ end-for}}
</table>
To make it clear I expect instead of using <div v-for=item in items></div> to this { for item in items } { end for } does this exist for vue ?
There is no such a syntax in vuejs, if you want to loop on something without touching it you can use <template> like :
<template v-for="element in elements" >
...
</template>
template tag description from : https://www.w3schools.com/TagS/tag_template.asp
you can go deeper : https://v2.vuejs.org/v2/guide/syntax.html
In your case :
<template v-for='(row, index) in $parent.results' >
<resultsRows :row='row' :index='index' :deleteQueryObjects = '$parent.deleteQueryObjects' />
<resultsCommentRow :index='index' />
</template>

Vue: How to conditionally render tr in tbody

I have a table body with multiple rows, such as this:
<table>
<tbody>
<tr>...</tr>
<tr>...</tr>
</tbody>
</table>
I want to conditionally combine v-if an v-for, to conditionally render one or more additional rows. The Vue manual says to wrap the v-for in a v-if, such as follows:
<div v-if="team.positions != null">
<my-row v-for="position in team.positions"
:position="position"
:key="position.id">
</my-row>
</div>
The problem is that I can't put a div in a tbody, or any other element for that matter. What's the solution?
In those situations where no element would fit, you can use <template>, like:
<template v-if="team.positions != null">
<my-row v-for="position in team.positions"
:position="position"
:key="position.id">
</my-row>
</template>
Demo:
new Vue({
el: '#app',
data: {
showTwoRows: true
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<table>
<tr>
<td>A</td><td>B</td>
</tr>
<template v-if="showTwoRows">
<tr>
<td>1</td><td>2</td>
</tr>
<tr>
<td>3</td><td>4</td>
</tr>
</template>
<tr>
<td>C</td><td>D</td>
</tr>
</table>
<button #click="showTwoRows = !showTwoRows">Toggle two middle rows</button>
</div>
Though in that specific example of yours, it doesn't seem needed. Have you tried simply not using the v-if:
<my-row v-for="position in team.positions"
:position="position"
:key="position.id">
</my-row>
Because the v-for just won't iterate (without throwing errors) if its value is undefined/null/0/[]/'':
new Vue({
el: '#app',
data: {
message: "If I'm being displayed, Vue works!",
team: {
positionsU: undefined,
positionsN: null,
positionsZ: 0,
positionsE: [],
positionsS: ''
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<table>
<tr v-for="position in team.positionsU"><td>u: {{ position }}</td></tr>
<tr v-for="position in team.positionsN"><td>n: {{ position }}</td></tr>
<tr v-for="position in team.positionsZ"><td>z: {{ position }}</td></tr>
<tr v-for="position in team.positionsE"><td>e: {{ position }}</td></tr>
<tr v-for="position in team.positionsS"><td>s: {{ position }}</td></tr>
<tr v-for="position in team.positionsF"><td>f: {{ position }}</td></tr>
</table>
</div>
You can use v-for and v-if on the same tag, however, it works differently to how you'd expect it to.
within the v-if you can reference the iterated item since v-for is performed before v-if
<div v-if="team.positions != null">
<my-row v-for="position in team.positions" v-if="position"
:position="position"
:key="position.id">
</my-row>
</div>
this would still iterate through all positions in team.positions, and not halt the for loop if the condition in the v-if was not met, but rather skip it.
think of it like this:
for (var i = 0; i < array.length-1; i++) {
if (array[i]) {
doTheThing();
}
}
I am not sure if this is exactly what the original question is looking for, but I just had a similar issue where I wanted to ignore rendering rows where the price of a item was 0.
I ran into the problem using v-if in the <tr> containing the v-for. I solved it by simply using a v-show instead.
So this worked perfectly in my case.
<tr v-show="item.price !== 0" :key="item._id" v-for="item in items"> ... </tr>

Vue access iteration item inside method from template

Learning Vue and stuck.
I am trying to access user in each of the methods to confirm true/false values for each isHuman and isPlayerTurn functions. How do I access the user in the loop instance inside each method?
I have the following table row in a template:
<template>
<div class="col-xs-12">
<h5>Enemies online</h5>
<span id="no-online-players" class="player-label pull-right">{{ usersCount }}</span>
<table id="new-game-opponents" class="new-game-opponents">
<tbody>
<tr v-for="(user, index) in users" :key="index" :class="[isPlayerTurn() ? playerTurnClass : '']">
<td class="player-status text-right">
<div v-if="isPlayerTurn">
<span :id="['player_turn-' + user.owner_id]" class="stage-label pull-right">{{ progress }}</span>
</div>
<div v-else>
<i class="fa fa-clock-o" aria-hidden="true" style="margin-right:5px;"></i>
</div>
</td>
<td class="player-status text-right">
<div v-if="isHuman">
<i class="fa fa-desktop" aria-hidden="true"></i>
</div>
<div v-else>
<i class="fa fa-user" aria-hidden="true" style="margin-right:2px;"></i>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: ['users', 'usersCount'],
data: function () {
return {
playerTurnClass: 'next-player-turn',
myPlayer: my_player,
progress: game.progress.status.turn_status.current_stage
}
},
methods: {
isPlayerTurn: function(user, index) {
return this.myPlayer.id === this.users[index]['id'];
},
isHuman: function(user, index) {
return this.users[index]['owner_id'] !== 'ai';
}
}
};
</script>
I am trying to access user in each of the methods to confirm true/false values for each isHuman and isPlayerTurn functions.
How do I access the user in the loop instance inside each method? Or should I be doing this a different way?
Additionally, the progress property is not rendered. But one step at a time!
First of all, try
<div v-if="isPlayerTurn(user, index)">...</div>
and
<div v-if="isHuman(user, index)">...</div>
I noticed you don't really use user in both isPlayerTurn and isHuman methods, so I suggest leaving user out.
And regarding progress, I don't know where game is from, but I'm guessing the value in game.progress.status.turn_status.current_stage is dynamic, so I suggest you first try changing progress to a computed property:
computed: {
progress() {
return game.progress.status.turn_status.current_stage
}
}