How can I call a statement or method after the loop complete on vue component? - vue.js

My vue component like this :
<template>
<div class="row">
<div class="col-md-3" v-for="item in items">
...
</div>
</div>
</template>
<script>
export default {
...
computed: {
items() {
...
}
},
...
}
</script>
If the loop complete, I want to call a statement or method
So the statement is executed when the loop completes
How can I do it?
Update :
From Kira San answer, I try like this :
<template>
<div class="row">
<div class="col-md-3" v-for="(item, key) in items" v-for-callback="{key: key, array: items, callback: callback}">
...
</div>
</div>
</template>
<script>
export default {
...
computed: {
items() {
const n = ...
return n
}
},
directives: {
forCallback(el, binding) {
let element = binding.value
if (element.key == element.array.length - 1)
if (typeof element.callback === 'function') {
element.callback()
}
}
},
methods: {
callback() {
console.log('v-for loop finished')
}
}
}
</script>
The console log not display
My items is object
If do console.log(n) in items, the result like this :

Look at this example.
new Vue({
el: '#app',
computed: {
items() {
return {item1: 'value1', item2: 'value2'}
}
},
methods: {
callback() {
console.log('v-for loop finished')
}
},
directives: {
forCallback(el, binding) {
let element = binding.value
var key = element.key
var len = 0
if (Array.isArray(element.array)) {
len = element.array.length
}
else if (typeof element.array === 'object') {
var keys = Object.keys(element.array)
key = keys.indexOf(key)
len = keys.length
}
if (key == len - 1) {
if (typeof element.callback === 'function') {
element.callback()
}
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<div class="row">
<div class="col-md-3" v-for="(item, key) in items" v-for-callback="{key: key, array: items, callback: callback}">
...
</div>
</div>
</div>

You can accomplish that with a directive.
Example usage:
<div class="col-md-3" v-for="(item, key) in items" v-for-callback="{key: key, array: items, callback: callback}">
<!--content-->
</div>
Create a new directive for-callback which will keep track of the elements that was rendered with v-for. It will basically check if the current key is the end of the array. If so, it will execute a callback function that you provide.
Vue.directive('for-callback', function(el, binding) {
let element = binding.value
if (element.key == element.array.length - 1)
if (typeof element.callback === 'function') {
element.callback()
}
})
Or if you don't want to define it globally. Add this to your component options instead:
directives: {
forCallback(el, binding) {
let element = binding.value
if (element.key == element.array.length - 1)
if (typeof element.callback === 'function') {
element.callback()
}
}
}
v-for-callback expects an options object.
{
key: key, // this will contain the item key that was generated with `v-for`
array: items, // the actual `v-for` array
callback: callback // your callback function (in this example, it's defined in the component methods
}
Then in your component options:
methods: {
callback() {
console.log('v-for loop finished')
}
}

You can do with an if inside the loop, like this:
<template>
<div class="row">
<div class="col-md-3" v-for="(item, index in items)">
<div v-if="index === items.length -1">
Display what ever you want here
<\div>
</div>
</div>
</template>
Or if you want to do something in js then call a function when the same condition is true, like this:
<div class="col-md-3" v-for="(item, index in items)">
<div v-if="index === items.length -1 && lastIteration()">
Display what ever you want here
<\div>
</div>
methods: {
lastIteration() {
alert("last");
}
}
I didn't test it but the ideea is good and it should work. Hope to answer your question :)

Related

How to fire an event in mount in Vuejs

I have a sidebar that you can see below:
<template>
<section>
<div class="sidebar">
<router-link v-for="(element, index) in sidebar" :key="index" :to="{ name: routes[index] }" :class='{active : (index==currentIndex) }'>{{ element }}</router-link>
</div>
<div class="sidebar-content">
<div v-if="currentIndex === 0">
Profile
</div>
<div v-if="currentIndex === 1">
Meine Tickets
</div>
</div>
</section>
</template>
<script>
export default {
mounted() {
EventBus.$on(GENERAL_APP_CONSTANTS.Events.CheckAuthentication, () => {
this.authenticated = authHelper.validAuthentication();
});
console.log()
this.checkRouter();
},
data(){
return {
currentIndex:0,
isActive: false,
sidebar: ["Profile", "Meine Tickets"],
routes: ["profile", "my-tickets"],
authenticated: authHelper.validAuthentication(),
}
},
computed: {
getUser() {
return this.$store.state.user;
},
},
methods: {
changeSidebar(index) {
this.object = this.sidebar[index].products;
this.currentIndex=index;
},
checkRouter() {
let router = this.$router.currentRoute.name;
console.log(router);
if(router == 'profile') {
this.currentIndex = 0;
} else if(router == 'my-tickets') {
this.currentIndex = 1;
}
},
},
}
</script>
So when the link is clicked in the sidebar, the route is being changed to 'http://.../my-account/profile' or 'http://.../my-account/my-tickets'. But the problem is currentIndex doesn't change therefore, the content doesn't change and also I cannot add active class into the links. So how do you think I can change the currentIndex, according to the routes. Should I fire an event, could you help me with this also because I dont know how to do it in Vue. I tried to write a function like checkRouter() but it didn't work out. Why do you think it is happening? All solutions will be appreciated.
So if I understand correctly, you want currentIndex to be a value that's based on the current active route? You could create it as a computed property:
currentIndex: function(){
let route = this.$router.currentRoute.name;
if(router == 'profile') {
return 0;
} else if(router == 'my-tickets') {
return 1;
}
}
I think you could leverage Vue's reactivity a lot more than you are doing now, there's no need for multiple copies of the same element, you can just have the properties be reactive.
<div class="sidebar-content">
{{ sidebar[currentIndex] }}
</div>
Also, you might consider having object be a computed property, something like this:
computed: {
getUser() {
return this.$store.state.user;
},
object() {
return this.sidebar[currentIndex].products;
}
},
Just use this.$route inside of any component template. Docs .You can do it simple without your custom logic checkRouter() currentIndex. See simple example:
<div class="sidebar-content">
<div v-if="$route.name === 'profile'">
Profile
</div>
<div v-if="$route.name === 'my-tickets'">
Meine Tickets
</div>
</div>

How to pass index in Vue3 v-for Array Refs

In document, it gives code:
<div v-for="item in list" :ref="setItemRef"></div>
export default {
setup() {
let itemRefs = []
const setItemRef = el => {
if (el) {
itemRefs.push(el)
}
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
setItemRef
}
}
}
But I also want to pass the v-for index into handler, just like this:
// fake code
<div v-for="(item, index) in navItems" :key="index":ref="setNavItemRefs($el, index)">
<span>{{ item }}</span>
</div>
How can I bind this index?
Try out to define an inline handler like :
<div v-for="item in list" :ref="(el)=>setItemRef(el,index)"></div>
and
const setItemRef = (el,index) => {
if (el) {
itemRefs.push(el)
}
}

Why does clicking an accordion item open all items?

I tried to implement the below into my project. I double-checked and I cannot see any flaw in my implementation. Yet in my case, a click on one item opens all items of the accordion. Why?
Below is my code.
Markup:
<div
v-for="item in faqItems"
:key="item.id"
class="faq-item"
#click="toggle"
>
<transition
name="accordion"
#before-enter="beforeEnter"
#enter="enter"
#before-leave="beforeLeave"
#leave="leave"
>
<div v-show="show" class="faq-item-details">
<div class="faq-item-details-inner" v-html="item.text">
</div>
</div>
</transition>
</div>
JS:
methods: {
toggle () {
this.show = !this.show
},
beforeEnter (el) {
el.style.height = '0'
},
enter (el) {
el.style.height = el.scrollHeight + 'px'
},
beforeLeave (el) {
el.style.height = el.scrollHeight + 'px'
},
leave (el) {
el.style.height = '0'
}
}
You have the same show for all accordions.
You can use separate components (see answer from #Moisés Hiraldo) or use the following logic:
HTML
<div
v-for="item in faqItems"
:key="item.id"
class="faq-item"
#click="toggle(item.id)"
>
...
<div v-show="showItems[item.id]" class="faq-item-details">
JS
data() {
return {
showItems: {}
}
},
methods: {
toggle (id) {
const newVal = !this.showItems[id]
this.$set(this.showItems, id, newVal)
}
}
If you need only one opened item
HTML
<div
v-for="item in faqItems"
:key="item.id"
class="faq-item"
#click="select(item.id)"
>
...
<div v-show="item.id === selectedItemId" class="faq-item-details">
JS
data() {
return {
selectedItemId: null
}
},
methods: {
select(id) {
this.selectedItemId = this.selectedItemId !== id ? id : null
},
},
I would expect that code to open all items when you click one. The reason is they're all inside the same component, so they all access the same this.show variable.
You could have your main component as an accordion container than renders each element as a separate component, each one with its own this.show variable:
<accordion-item
v-for="item in faqItems"
:key="item.id"
:item="item"
>

Validate Child component Vue vee-validate

App (parent)
Hi I have these component (Child)
TextComponent
InfoErrorForm
When I press submit from the parent component App is not validate this form. So I tried to validate with inject $validator in the child component (TextComponent), and provide but not show message error .
If you can help me to validate children form inisde parent component.
This is my code
AppComponent
<template>
<div>
<!-- Form validar numero max input -->
<form :class="{'was-validated': error_in_form_save_progress}" >
<card-shadow v-for="(texto,key) in sections_template.texts" :key="key" >
<texto-component
:orden="key+2"
v-model="sections_template.texts[key].content"
:tituloComponente="texto.title"
:inputName="texto.title" >
<template slot="section_show_error_validate_input">
<info-error-form
:listErrors='errors'
:name_field = "texto.title"
v-show = "error_in_form_save_progress" >
</info-error-form>
</template>
</texto-component>
</card-shadow>
</form>
<div class="row foot_button" >
<div class="offset-md-3 col-md-3">
<button class="btn" #click.prevent="save_progrees"> Guardar Cambios</button>
</div>
</div>
</div>
</template>
<script>
export default {
provide() {
return {
$validator: this.$validator,
};
},
data: function(){
return {
sections_template: {
texts:[
{
section_template_id: 1,
type: "texto",
title: "fundamentacion",
content: ""
},
{
section_template_id: 2,
type: "texto",
title: "sumilla",
content: ""
}
] },
error_in_form_save_progress: true
}
},
methods:{
save_progrees(){
this.$validator.validateAll().then((result) => {
if (result) {
this.error_in_form_save_progress = false;
alert("se guardaran cambios");
return
}
this.error_in_form_save_progress = true;
});
}
}
}
</script>
I found solution with this code. In my parent component i add provide and i send the $validator
export default {
components:{
...
},
provide() {
return {
$validator: this.$validator,
}
},
In my child component i received this
inject: ['$validator'],
In my parent component i add this method to invoque validation
methods:{
save_progrees(){
var validationArray = this.$children.map(function(child){
return child.$validator.validateAll();
});
window.Promise.all(validationArray).then((v) => {
v.some( element => { if ( element == false ) { throw "exists error in child component";} });
this.error_in_form_save_progress = false;
alert("datos guardados");
return
}).catch(() => {
this.show_message_error_validation();
});
},
show_message_error_validation(){
this.error_in_form_save_progress = true;
}
}
Finally to show error in component info-error I use this code
<template>
<div class="row" v-if="errors.items">
<div class="col-md-12">
<template v-for="(e,key) in errors.items" >
<div class="text-danger" v-if="e.field ==name_field" :key="key"> {{e.msg}} </div>
</template>
</div>
</div>
</template>
In your child component do watch for this.errors and in the watch, put this.$emit
Something like this below :
watch: {
errors: function (value) {
this.$emit('TextComponent', value)
}
}
And then catch it on your parent component see it here https://v2.vuejs.org/v2/api/#vm-emit

How can I call a method after the loops(more than 1 loop) complete on vue js?

My vue component like this :
<template>
<div class="row">
<div class="col-md-3" v-for="item1 in items1">
...
</div>
<div class="col-md-3" v-for="item2 in items2">
...
</div>
<div class="col-md-3" v-for="item3 in items3">
...
</div>
</div>
</template>
<script>
export default {
...
computed: {
items1() {
const n = ... // this is object
return n
},
items2() {
const n = ... // this is object
return n
},
items3() {
const n = ... // this is object
return n
}
},
...
}
</script>
If the three loop complete, I want to call a method
So the method is executed when the three loop completes
How can I do it?
As promised, here is the example.
var counter = 0
const vm = new Vue({
el: '#app',
computed: {
items1() {
return {item1: 'value1', item2: 'value2'}
},
items2() {
return {item1: 'value3', item2: 'value4'}
},
items3() {
return {item1: 'value5', item2: 'value6'}
}
},
methods: {
callback() {
counter++
console.log('v-for loop finished')
var numberOfLoops = 3
if (counter >= numberOfLoops) {
console.log('All loops have finished executing.')
counter = 0
}
}
},
directives: {
forCallback(el, binding, vnode) {
let element = binding.value
var key = element.key
var len = 0
if (Array.isArray(element.array)) {
len = element.array.length
}
else if (typeof element.array === 'object') {
var keys = Object.keys(element.array)
key = keys.indexOf(key)
len = keys.length
}
if (key == len - 1) {
if (typeof element.callback === 'function') {
(element.callback.bind(vnode.context))()
}
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<div class="row">
<div class="col-md-3" v-for="(item, key) in items1" v-for-callback="{key: key, array: items1, callback: callback}">
...
</div>
<div class="col-md-3" v-for="(item, key) in items2" v-for-callback="{key: key, array: items2, callback: callback}">
...
</div>
<div class="col-md-3" v-for="(item, key) in items3" v-for-callback="{key: key, array: items3, callback: callback}">
...
</div>
</div>
</div>