Dynamic binding of class based on index in v-for - vue.js

<transition-group appear name="fade" class="row no-gutters" v-if="currentTab === 'living'">
<div class="col-6 pr-3 pb-3" :class="[ isEven(index) ? 'col-md-8' : 'col-md-4']" v-for="(item, index) in livingGallery" :key="'living' + index">
<img :src="item.photoThumbSmall" alt="Gallery Photo index" class="d-block w-100">
</div>
</transition-group>
This is the code for isEvent:
methods: {
isEven(i) {
return i / 2 === 0;
}
},
I need to render the div different based on the index. div with even index gets the class col-md-8, otherwise col-md-4.
This is what I expected from the code:
index 0 => col-md-8
index 1 => col-md-4
index 2 => col-md-8
......
However, only the first element has col-md-8. The remaining elements are assigned col-md-4.
What's the problem?

You want to do i modulus 2, not divided by 2.
isEven(i) {
return i % 2 === 0;
}
i / 2 will only ever be 0 if i is 0. Since you want to know whether the given index is even, you should check the remainder of the division, which is what the modulus operation returns.

Related

Vue does not correctly remove item from vfor

I have this custom component in vue callled "dm-vehicle-spec"
<dm-vehicle-spec #_handleRemoveSpec="_handleRemoveSpec" v-for="spec, index in vehicleSpecs" :key="index" :index="index" :spec="spec"></dm-vehicle-spec>
which looks like the following
<script>
export default {
props: ["spec"],
data() {
return {
specName: null,
specValue: null,
}
},
mounted() {
if (this.spec.detail_name && this.spec.detail_value) {
this.specName = this.spec.detail_name;
this.specValue = this.spec.detail_value;
}
},
computed: {
getSpecNameInputName() {
return `spec_${this.spec.id}_name`;
},
getSpecValueInputName() {
return `spec_${this.spec.id}_value`;
},
},
methods: {
_handleRemoveSpec() {
this.$emit("_handleRemoveSpec", this.spec.id);
}
},
}
</script>
<template>
<div class="specs-row flex gap-2 w-full items-center">
<div class="col-1 w-5/12">
<input placeholder="Naam" type="text" :id="getSpecNameInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specName">
</div>
<div class="col-2 w-5/12">
<input placeholder="Waarde" type="text" :id="getSpecValueInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specValue">
</div>
<div #click="_handleRemoveSpec" class="col-3 w-2/12 flex items-center justify-center">
<i class="fas fa-trash text-lg"></i>
</div>
</div>
</template>
so when i have 3 specs, 1 from the database and 2 customs i have the following array vehicleSpecs (Which i loop over)
[
{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},
{"id":24},
{"id":25}
]
so lets say i want to remove the second item from the list so the one with test1 as values, then the array looks like
[{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},{"id":25}]
So the second array item is removed and thats correct because object with id 24 no longer exsist but my html shows
that the value for object with id 24 still exists but the value for object with id 25 is removed, how is that possible?
If u need any more code or explaination, let me know
Any help or suggestions are welcome!
Index is not a good choice to use as v-for key.
Indexes are changing when you delete something from array.
Try using another property as a key.
Run the loop in reverse order. This way, deleted items do not effect the remaining item indexes
Hit: indexReverse = list.length - index

Creating pagination component

I am trying to create pagination component. For e.g if my API returns "pages": 9
For example, if I have 9 pages, I want to cut the list at 5 and add three dots like on the image. I want to be able to provide at which index I can cut the list. Whats the best way to do this? I am approaching this wrong?
<div v-for="index in pages" class="flex">
<button>{{index}}</button>
</div>
Assuming you receive cutIndex and apiPages as props to your component, then your template could look something like the following:
<div v-for="page in Math.min(cutIndex, apiPages)" class="flex">
<button>{{page}}</button>
</div>
<template v-if="cutIndex < apiPages">
<div class="flex">
<button>...</button>
</div>
<div class="flex">
<button>{{apiPages}}</button>
</div>
</template>
this code works for me, and here is the key part
<template>
...
<template v-for="index in pages"
v-if="(index < 4 || (length - index) < 3) || Math.abs(index - value) < 2">
<span v-if="index === (length - 2) && value < (length - 4)">
...
</span>
<button #click.prevent="updatePage(index)">
{{ index }}
</button>
<span v-if="(index === 3 && value > 5)">
...
</span>
</template>
...
</template>
the result:
<template>
<nav class="flex items-center justify-center" role="pagination">
<!-- go to previous page -->
<a :key="`${$route.name}-arrow-${value > 1 ? value - 1 : 1}`"
:href="getFullPath(value > 1 ? value - 1 : 1)"
:title="$t('previous_page')"
#click.prevent="updatePage(value > 1 ? value - 1 : 1)"
:disabled="value === 1"
class="arrow pop-btn default rounded-sm"
v-waves>
<i class="mdi-chevron-left mdi"></i>
</a>
<template v-for="index in length"
v-if="(index < 4 || (length - index) < 3) || Math.abs(index - value) < 2">
<span v-if="index === (length - 2) && value < (length - 4)">
...
</span>
<a :href="getFullPath(index)"
:title="$t('page_index', {index: index})"
#click.prevent="updatePage(index)"
class="pop-btn number default rounded-sm"
:class="{
'active': index === value
}"
v-waves>
{{ index }}
</a>
<span v-if="(index === 3 && value > 5)">
...
</span>
</template>
<!-- go to next page -->
<a :href="getFullPath(value === length ? value : value + 1)"
:title="$t('next_page')"
#click.prevent="updatePage(value === length ? value : value + 1)"
:disabled="value === length"
class="arrow pop-btn default rounded-sm"
v-waves>
<i class="mdi-chevron-right mdi"></i>
</a>
</nav>
</template>
<script type="text/javascript">
export default{
emits: ['update:value],
props: {
length: {
required: true,
type: Number
},
// the page filter
value: {
required: true,
type: Number
}
},
methods: {
updatePage(index){
this.$emit('update:value', index);
},
getFullPath(page){
let query = {...this.$route.query};
page === 1 ? delete query.page : query.page = page;
return this.$router.resolve(
this.r({
query: query,
name: this.$route.name,
params: this.$route.params
})
).href;
}
}
}
</script>
by the way, <a> may better than <button> for SEO

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 check in nested v-for if a record doesn't exist

I am trying to build a table where rows are items (first v-for), columns are locations (second v-for), cells are item_locations (third -v-for).
If for a certain location the item is present (hence an item_location object) I want to print yes, else I want to print No.
<div v-for="item in items" :key="item.id>
{{item.name}}
<div v-for="location in locations" :key="location.id">
<div v-for="item_location in location.item_locations" :key="item_location.id">
</div>
</div>
</div>
By adding this in the third loop:
<p v-if="item_location.item_id == item.id">Yes</p>
I it correctly prints yes for combination of item/location for which there is an item_location.
What I am unable to do is to print No only for the combinations of item/location for which an item_location object does not exist. To further clarify, item_locations is a joining table.
A little confused on what your asking but Im going to suggest this
Your nested v-for must use the property declared from its parent like so
<div class="table-cell" v-for="location in locations" :key="location.id">
<div v-for="item_location in location.item_locations" :key="item_location.id">
<div v-if="item_location.location_id == location.id ">Yes</div>
<div v-else>No</div>
</div>
</div>
so the nested v-for would be location.item_locations and if your only checking for one condition you can just do a v-else.
I solved this way:
<div v-for="item in items" :key="item.id>
{{item.name}}
<div v-for="location in locations" :key="location.id">
<div>{{filterLocation(item, location)}}</div>
</div>
</div>
filterLocation: function(item, location) {
let value = item.item_locations.filter(item_location => {
return item_location.location_id == location.id;
});
var that = this;
that.item_location_exists = value.length;
},
And in template I can do a conditional as item_location_exists equals to 0 or 1.

How to set class to child element inside v-for loop

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>