How to set class to child element inside v-for loop - vue.js

I need some help, I have v-for loop which outputs elements of array referenceDetailsDocumentsData, I need to check at the same time if the id of this element exists in another array documentsData, in this case I need to add custom class to child of this element.
<div class="loading-doc-item"
v-for="referenceDetails in referenceDetailsDocumentsData"
:key="referenceDetails.id">
<div class="loading-doc-show">
{{ referenceDetails.name }}
<span class="upload-status" v-if="checkUploadedDocuments(referenceDetails.id)">
<i class="fa fa-check-circle"></i>
</span>
<span class="upload-status" v-else>
<i class="fa fa-check"></i>
</span>
</div>
</div>
methods() {
checkUploadedDocuments(id) {
return this.documentsData.filter(item => item.id === id);
}
}
In my case, I am getting an error
Error in render: "TypeError: this.documentsData.filter is not a
function"

Your logic is wrong - the method checkUploadedDocuments will return Array but it must return Boolean.
<div class="loading-doc-item"
v-for="referenceDetails in referenceDetailsDocumentsData" :key="referenceDetails.id">
<div class="loading-doc-show">
{{ referenceDetails.name }}
<span class="upload-status">
<i class="fa"
:class="{documentsData && documentsData.length &&
documentsData.findIndex(item => item.id === referenceDetails.id) !== -1
? 'fa-check-circle' : 'fa-check'}"></i>
</span>
</div>
</div>

Related

Cannot read property 'focus' of undefined in VUE When setting focus to button

I am new to vue I have component which if the endpoint fails, calls my generic 'Error' modal. All this is working fine but I keep getting the following error:
Cannot read property 'focus' of undefined
This only happens for the else part of my method function.
For this specific issue is I my 'failedPrcess' equals any of the following, this is when I get is, all others are fine:
existOrderSearchProdOrders
stockSearchStockLevels
cartFetchCouriers
Code
<template>
<div class="modal fade danger-modal" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalTitle" aria-hidden="true"
data-keyboard="false" data-backdrop="static" style="z-index: 99999">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content danger-modal-content">
<div class="modal-header danger-modal-headerfooter">An error has occurred</div>
<div class="modal-body">
<p v-if="failedProcess === 'appGetAccount' || failedProcess === 'existOrderSearchProdOrders' || failedProcess === 'stockSearchStockLevels'
|| failedProcess === 'cartFetchCouriers'">
{{ contactTxt | capitalize }}
</p>
<p v-else-if="errorCount < 3">If the error continues, {{ contactTxt }}</p>
<p v-else>As the error has continued, {{ contactTxt }}</p>
<p>
<b>
01234 567890
<br />
Open from 00:00 to 07:00
</b>
</p>
<p>Advising of what you were doing when the error occurred.</p>
</div>
<div class="modal-footer danger-modal-headerfooter">
<a v-if="failedProcess === 'appGetAccount'" ref="logoutButton" class="btn btn-primary" :class="logoutButtClicked" #click="logoutClicked = true" href="/site/logout">
<span v-if="!logoutClicked" id="logoutButtonLabel">Logout</span>
<span v-else id="logoutSpinner">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Logging out
</span>
</a>
<router-link v-else-if="failedProcess === 'fetchOrderReportDetails'" to="/review" tag="button"
ref="existOrdersButton" class="btn btn-primary" type="button" data-dismiss="modal" #click.native="closeButton">
Return to existing orders
</router-link>
<button v-else-if="errorCount < 3 && (failedProcess !== 'productsFetchProducts' && failedProcess !== 'existOrderSearchProdOrders'
&& failedProcess !== 'stockSearchStockLevels' && failedProcess !== 'cartFetchCouriers')" ref="closeButton" class="btn btn-primary"
type="button" data-dismiss="modal" #click="closeButton">
Close
</button>
<router-link v-else to="/" tag="button" ref="homeButton" class="btn btn-primary" type="button" data-dismiss="modal" #click="closeButton">
Return to homepage
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
import * as params from '../params';
export default {
name: "ErrorModal",
data() {
return {
contactTxt: 'please contact us on:',
errorCount: 0,
failedProcess: '',
}
},
mounted() {
VueEvent.$on('show-error-modal', (failedProcess) => {
if (this.failedProcess !== failedProcess) {
this.errorCount = 0;
}
this.failedProcess = failedProcess;
$('#errorModal').modal('show').on('shown.bs.modal', this.focus);
});
},
methods: {
focus() {
if (this.failedProcess === 'appGetAccount') {
this.$refs.logoutButton.focus();
} else if (this.failedProcess === 'fetchOrderReportDetails') {
this.$refs.existOrdersButton.$el.focus();
} else if (this.errorCount < 3 && this.failedProcess !== 'productsFetchProducts') {
this.$refs.closeButton.focus();
} else {
this.$refs.homeButton.$el.focus();
}
},
}
}
</script>`enter code here`
I've tried using v-if before and I also had similar problems and the best solution I found was, instead of using v-if/v-else-if/v-else, use v-show instead to perform conditional rendering.
Also, as the Vue.js doc says:
Generally speaking, v-if has higher toggle costs while v-show has higher initial render costs. So prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime.

Set focus to first item in v-for using vue

I have a vue app and I'm trying to set the focus to the first item in my v-for list but struggling
HTML
<div class="input-group-append">
<button class="btn btn-light" type="submit" style="border: 1px solid lightgray" #click.prevent="findTest">
<i class="fas fa-search"></i>
</button>
</div>
<div v-if="this.Tests.length >= 2" class="list-group accList">
<a v-for="(test, i) in tests" :key="i" class="list-group-item list-group-item-action" :ref="i" :class="{ 'active': i === 0 }" #click.prevent="selectTest(test)">
{{ test.test1 }} ({{ test.test2 | capitalize }})
</a>
</div>
Note the word 'test' has replaced my actual values
I have tried using the following in my method which is called on button click but I keep getting get an error
methods: {
findTest() {
axios.get(END_POINT).then((response) => {
...SOMEOTHER CODE
//this.$refs.0.$el.focus()
//this.$refs.0.focus();
//this.$refs.a.$el.children[0].focus();
}
}
}
Error
I am relativly new to vue but I have been able to set my focus using:
this.$refs.[INPUT_NAME].$el.focus()
But it doesn't like me using a number
this.$refs.0.$el.focus() //0 being the index number
As WebStorm complains saying:
Expecting newline or semicolon
console.log(this.$refs)
When using v-for, ref maybe array of refs.
Template: use ref="tests" instead of :ref="i"
<a v-for="(test, i) in tests" :key="i" class="list-group-item list-group-item-action" ref="tests" :class="{ 'active': i === 0 }" #click.prevent="selectTest(test)">
{{ test.test1 }} ({{ test.test2 | capitalize }})
</a>
Script
this.$refs.tests[0]
I guess its undefined because you try to access the element while the v-for loop didnt finished its rendering
Try this:
methods: {
findTest() {
axios.get(END_POINT).then((response) => {
...SOMEOTHER CODE
this.$nextTick(()=> {
//put your code here
//this.$refs.0.$el.focus()
//this.$refs.0.focus();
//this.$refs.a.$el.children[0].focus();
})
}
}
}
Put your code into $nextTick() that should ensure that its get executed when the loop is done

Vue inline click event `this` is undefined

i'm getting an error in the below code saying this is undefined.
<div class="location-list__item" v-for="(value, key) in locations.data">
<div class="location-list__item--text"
:class="{ selected: selected === key }"
#click="() => { this.selected = key; this.manageSurrounding = false }">
<i class="fas fa-compass"></i> {{ value.name }}
<span v-if="value.changed" class="has-text-danger"> Changed</span>
</div>
</div>
However if I change this line:
#click="() => { this.selected = key; this.manageSurrounding = false }"
to this
#click="selected = key"
It works fine, however I need to change manageSurrounding at the same time and I don't want to create a method for such a simple thing.
You can do multiple assignments by using semicolon like the above statement which you have written.
<div class="location-list__item" v-for="(value, key) in locations.data">
<div class="location-list__item--text"
:class="{ selected: selected === key }"
#click="selected = key;manageSurrounding = false"> # Like this
<i class="fas fa-compass"></i> {{ value.name }}
<span v-if="value.changed" class="has-text-danger"> Changed</span>
</div>
</div>
You can use a anonymous function like,
<div onclick="return function()
{ selected = key; manageSurrounding = false }'
</div>
Just create a method and put in the update lines, you are better off on the long run, if your list is changing/reordering/re-rendering often.
It’s an optimization opportunity, so don’t try to force it in just because it seems small. Have a look at this answer: anonymus function in template

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
}
}

Vue - check if you are on the last prop of a v-for loop

If I have the following data property:
person: {name: 'Joe', age: 35, department: 'IT'}
And wanted to loop through and output it as follows:
name: Joe, age: 35, department: IT
So far I have:
<span v-for="(val, key) in person">{{key}}: {{val}}, </span>
But this displays:
name: Joe, age: 35, department: IT,
with an extra comma on the end, how can I have it detect that it's the last prop and not show the comma? I thoughta v-show or v-if may be the solution but can't quite figure out how to make it work.
Here is one way.
<span v-for="(val,key,index) of person">
key: {{key}}, val: {{val}}, index: {{index}}
<span v-if="index != Object.keys(person).length - 1">, </span>
</span>
Here's a solution if you're looping through an array, rather than an object:
<div id="app">
<div v-for="(item, index) in items">
<div v-if="index == items.length - 1">yes</div>
{{ item }}, {{ index }}
</div>
</div>
You can also "cheat" by inserting the comma before each item, as it's easier to check for the first item (index !== 0).
<span v-for="(val, key, index) in person">
<span v-if="index !== 0">, </span>
{{key}}: {{val}}
</span>
You can do that with a computed to see if the current index (third parameter forv-if) is the last property:
computed: {
last(){
return Object.keys(this.person).length-1;
}
}
Then in your v-for:
<span v-for="(val, key, index) in person">{{key}}: {{val}}<span v-if="index !== last">, </span> </span>
Here's the JSFiddle: https://jsfiddle.net/wv2ujxvn/
This also works:
<span v-for="(value,key) in persons" :key='key'>
{{key}}: {{val}}
<span v-if="key+1 != persons.length">, </span>
</span>
A pity there is no shortcut provided by Vue.
I personally prefer using a small CSS:
<div class="list">
<span>Item 1</span>
<span>Item 2</span>
<span>Item 3</span>
</div>
.list span:not(:last-child)::after {
content: ',';
}
If you want to store the knowledge about this pattern in code instead of on Stack Overflow, you could create a component like this:
<template>
<span v-if="show"><slot></slot></span>
</template>
<script>
export default {
name: 'separator',
props: ['items', 'index'],
computed: {
show () {
return this.index !== (Array.isArray(this.items) ? this.items : Object.keys(this.items)).length - 1
}
}
}
</script>
This doesn't necessarily make the code shorted, but easier to remember:
<span v-for="(val, key, index) of person">key: {{key}}, val: {{val}}
<separator :items="person" :index="index">, </separator>
</span>