Getting index of a data in an array in VUE js - javascript

I want to change the status of Tasks when a particular method is called. But The problem is I cannot get the index of the particular item of the array to change its status.
This is my HTML:
<div class="main" id="my-vue-app">
<ul>
<li v-for="task in completeTask">
{{ task.description }} <button #click="markIncomplete">Mark as Incomplete</button>
</li>
</ul>
<ul>
<li v-for="task in incompleteTask">
{{ task.description }} <button #click="markComplete">Mark as Complete</button>
</li>
</ul>
</div>
And this is my Vue:
<script>
new Vue(
{
el: '#my-vue-app',
data:
{
tasks: [
{description:'go to market', status: true},
{description:'buy book', status: true},
{description:'eat biriani', status: true},
{description:'walk half kilo', status: false},
{description:'eat icecream', status: false},
{description:'return to home', status: false}
]
},
computed:
{
incompleteTask()
{
return this.tasks.filter(task => ! task.status);
},
completeTask()
{
return this.tasks.filter(task => task.status);
}
},
methods:
{
markComplete()
{
return this.task.status = true;
},
markIncomplete()
{
return this.task.status = false;
}
}
}
)
</script>
I need make use of markComplete() and markIncomplete() but the problem is I couldn't find the way to get the index of current element to change its status.

You could get the index by declaring a second argument at the v-for:
<li v-for="(task, index) in incompleteTask">
{{ task.description }} <button #click="markComplete(index)">Mark as Complete</button>
</li>
methods:
{
markComplete(index)
{
return this.tasks[index].status = true;
},
But a, maybe simpler, alternative is to simply **pass the `task` as argument**:
<li v-for="task in incompleteTask">
{{ task.description }} <button #click="markComplete(task)">Mark as Complete</button>
</li>
methods:
{
markComplete(task)
{
return task.status = true;
},

RTFM:
You can use the v-repeat directive to repeat a template element
based on an Array of objects on the ViewModel. For every object in the
Array, the directive will create a child Vue instance using that
object as its $data object. These child instances inherit all data
on the parent, so in the repeated element you have access to
properties on both the repeated instance and the parent instance. In
addition, you get access to the $index property, which will be the
corresponding Array index of the rendered instance.
var demo = new Vue({
el: '#demo',
data: {
parentMsg: 'Hello',
items: [
{ childMsg: 'Foo' },
{ childMsg: 'Bar' }
]
}
})
<script src="https://unpkg.com/vue#0.12.16/dist/vue.min.js"></script>
<ul id="demo">
<li v-repeat="items" class="item-{{$index}}">
{{$index}} - {{parentMsg}} {{childMsg}}
</li>
</ul>
Source:
https://012.vuejs.org/guide/list.html
Note: The directive v-repeat is available in old versions of Vue.js :-)

Related

How to make it so that "Show more" & "Show less" buttons only work for the text in their own div and not for all texts using Vue.js [duplicate]

I'm making a list of items with v-for loop. Inside each item of the loop there is button with click event method that showing description text.
When i click on the button, it should toggle only inside it's own item, but it affecting all elements in v-for list.
So, how to make a toggle method that will affect only it's own item?
<template>
<div>
<div v-for="item in items" :class="{ activeclass: isActive }">
<div class="item-text">
{{item.text}}
</div>
<button #click="toggle()">show</button>
<div v-show="isActive" class="item-desc">
{{item.desc}}
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
items: [
{
text: 'Foo',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
},
{
text: 'Bar',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
}
],
isActive: false
}
},
methods: {
toggle: function () {
this.isActive = !this.isActive;
}
},
}
</script>
As #Nora said you can (and probably should) create a separate component for each list item, so you would have a component that accepts an item as a prop, then each component can have it's own isActive flag, which keeps the markup nice and clean:
Component:
Vue.component('toggle-list-item', {
template: '#list-item',
props: ['item'],
methods: {
toggle() {
this.isActive = !this.isActive;
}
},
data() {
return {
isActive: false
}
},
})
Markup
Now you can simply place the component inside your v-for:
<div id="app">
<div v-for="item in items">
<toggle-list-item :item="item"></toggle-list-item>
</div>
</div>
Here's the JSFiddle: https://jsfiddle.net/w10qx0dv/
You can add a property on each item in your list if description should be shown:
<template>
<ul>
<li v-for="item in items" :class="{ activeclass: item.isActive }">
<div class="item-text">
{{ item.text }}
</div>
<button #click="toggle(item)">show</button>
<div v-show="item.isActive" class="item-desc">
{{ item.desc }}
</div>
</li>
</ul>
</template>
<script>
export default {
data () {
return {
items: [
{
isActive: false,
text: 'Foo',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
},
{
isActive: false,
text: 'Bar',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
}
],
}
},
methods: {
toggle: function (item) {
item.isActive = !item.isActive;
}
},
}
</script>
Alternatively, you can extract the li into a separate component.
I've ran into the same problem, I solved it this way.
I took an array and pushed the clicked item's index and based on index available in the array, we can now show or hide the element.
<template>
<div>
<div v-for="(item,i) in items">
<div class="item-text">
{{ item.text }}
</div>
<button #click="toggle(i)">show</button>
<div v-show="clickedItems.includes(i)" class="item-desc">
{{ item.desc }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
text: 'Foo',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
},
{
text: 'Bar',
desc: 'The Array.from() method creates a new Array instance from an array-like or iterable object.',
}
],
clickedItems:[]
}
},
methods: {
toggle() {
if (this.clickedItems.includes(i)) {
this.clickedItems.splice(this.clickedItems.indexOf(i), 1);
} else {
this.clickedItems.push(i);
}
}
},
}
</script>

vuejs mouseover add class to this element

How can I add class on mouse over on list item & remove class on mouse leave
<li class="test" #mouseenter="thumbStyle" #mouseleave="thumbStyle2">1</li>
<li class="test" #mouseenter="thumbStyle" #mouseleave="thumbStyle2">2</li>
<li class="test" #mouseenter="thumbStyle" #mouseleave="thumbStyle2">3</li>
In data & methods
data(){
isActive:false
}
thumbStyle:function(){
this.isActive = true;
},
thumbStyle2:function(){
this.isActive = false;
},
I have tried this but it is only adding class to first element (li) when I over on any list(li). So what is the technique to add only this(hovered) li. Like in jquery there are $(this)
The ideal way to handle this is that each of the elements you want to control the state of, are a model, then you can easily manipulate the state of each item.
Here is an example:
new Vue({
el: '#app',
data: function() {
return {
items: [{
label: 'Planes',
active: false
}, {
label: 'Trains',
active: false
}, {
label: 'Automobiles',
active: false
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in items"
#mouseenter="item.active = true"
#mouseleave="item.active = false"
>{{ item.label }} is {{ item.active }}</li>
</ul>
</div>
I have solved as belows
thumbStyle:function(e){
var clickedElement = e.target;
$(clickedElement).addClass('active');
},
thumbStyle2:function(e){
var clickedElement = e.target;
$(clickedElement).removeClass('active');
},

VueJs set active class, when one li element clicked in v-for loop

I can't figure out how to properly select (set active class) when I do v-for loop and one class is already selected. Let me share the code and with further explanation
These are my available subscriptions and one is already selected, based on user data
<ul>
<li v-for="(s, key, index) in subscriptions"
:class="checkIfClassActive(s.subscription_key)"
#click="setActive(key, index)">
{{ s.name }}
</li>
</ul>
and my js code
checkIfClassActive(userSubscriptionKey) {
if (userSubscriptionKey === this.userSubscription.subscription_key) {
return 'active';
}
},
setActive(key, index) {
},
And this is how is displayed
Now the next step is when I click on one li element it should become active and all other li elements should lose active class, but the problem is that I can't figure out how to properly write setActive function. Can you please help me out, how to do this.
If you need any additional informations, let me know and I will provide. Thank you!
Add a data property called activeIndex:
data() {
return {
activeIndex: undefined
}
},
and your setActive method:
methods: {
setActive(subscription, index) {
this.activeIndex = index;
this.userSubscription.subscription_key = subscription.subscription_key
},
getSubscriptions() {
.....
// fetch subscriptions in this.subscriptions var
.....
// select preselected subscription, when fetching subscriptions
let userSubscriptionKey = this.userSubscription.subscription_key;
let indexOfObject = this.subscriptions.findIndex(
o => o.subscription_key === userSubscriptionKey
);
this.setActive(this.userSubscription, indexOfObject);
}
}
with a slight modification to your template:
<ul>
<li v-for="(s, index) in subscriptions"
:class="{ 'active': activeIndex === index }" :key="s.id"
#click="setActive(s, index)">
{{ s.name }}
</li>
</ul>
You basically set an index that should be active and that's it. active css class is added when list element's index is the same as activeIndex.
As for setting activeIndex to existing choice before the user changes it, you can set activeIndex when fetching subscriptions data to user's current subscription.
Fiddle: http://jsfiddle.net/eywraw8t/256701/
Change your this.userSubscription.subscription_key variable everytime a tab is selected. For this pass it through setActive(s.subscription_key)
You can do something like this,
<li v-for="(s, key, index) in subscriptions"
:class="checkIfClassActive(s.subscription_key)"
#click="setActive(s.subscription_key)">
{{ s.name }}
</li>
Js
checkIfClassActive(userSubscriptionKey) {
if (userSubscriptionKey === this.userSubscription.subscription_key) {
return 'active';
}
},
setActive(subscription_key) {
this.userSubscription.subscription_key=subscription_key;
},
A simple example showing the logic:
html part:
<div id="app">
<ul>
<li
v-for="item in items"
:key="item.id"
:class="item.class"
#click="set_active_id(item.id)"
>{{ item.text }}</li>
</ul>
</div>
Js part:
new Vue({
el: "#app",
data: {
items: [
{ id: 1, text: 'text1', class: 'active' }, //default active
{ id: 2, text: 'text2', class: '' },
{ id: 3, text: 'text3', class: '' }
],
previous_active_id: 1
},
methods: {
set_active_id(id) {
if (this.previous_active_id === id) return //no need to go further
this.items.find(item => item.id === this.previous_active_id).class = '' //remove the active class from old active li
this.items.find(item => item.id === id).class = 'active' //set active class to new li
this.previous_active_id = id //store the new active li id
}
}
})
See it in action
this is quicly
if you use v-for:
<template>
<div>
<ul>
<li
class="anyThings"
v-for="cat in cats"
:key="cat.index"
#click="itemActive(cat.index)"
:class="[(itemA == cat.index) ? 'active':'']"
>{{ cat.yourObjectKey }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
itemA:'0' // for first load and in curent path
}
},
computed: {
cats() {
...
}
},
methods: {
itemActive(e) {
this.itemA = e;
}
},
}
</script>
<style>
.active {
...
}
</style>
if you don't need use v-for and use router-link in element:
<template>
<div>
<ul>
<li #click="itemActive($route.path)">
<router-link to="/" class="anyThings"
:class="[(itemA == '/') ? 'active':'']"
>your text
</router-link>
</li>
<li #click="itemActive($route.path)">
<router-link to="/article" class="anyThings"
:class="[(itemA == '/article') ? 'active':'']"
>your text
</router-link>
</li>
.
.
.
</ul>
</div>
</template>
<script>
export default {
data() {
return {
itemA:this.$route.path // for first load and in curent path
}
},
methods: {
itemActive(e) {
this.itemA = e;
}
},
}
</script>
<style>
.active {
...
}
</style>
I had this problem the simplest answer is to change the "li" tags to "router-link", then change the default styling of the router-link.
In this case "router-link-active"
<ul>
<router-link v-for="item in items"
:key="item.id"
:to="item.route"
>
{{ s.name }}
</router-link>
</ul>
<style>
.router-link-active{
...
}
</style>

how to see changes in new data generated in a vue component?

Well I hope to explain, I'm generating this data from a component, when I click the checkbox changes in the generated data are not reflected, but when clicking the button with a data already in the instance changes are made, I appreciate if you explain Why or do they have a solution?
this my code
js
Vue.component('fighters', {
template: '#fighters-template',
data() {
return {
items: [
{ name: 'Ryu' },
{ name: 'Ken' },
{ name: 'Akuma' }
],
msg: 'hi'
}
},
methods: {
newData() {
this.items.forEach(item => {
item.id = Math.random().toString(36).substr(2, 9);
item.game = 'Street Figther';
item.show = false;
});
this.items.push()
},
greeting() {
this.msg = 'hola'
}
}
});
new Vue({
el: '#app'
})
html
<main id="app">
<fighters></fighters>
</main>
<template id="fighters-template">
<div class="o-container--sm u-my1">
<ul>
<li v-for="item in items">
<input type="checkbox" v-model="item.show">
{{ item.name }}</li>
</ul>
<button class="c-button c-button--primary" #click="newData()">New Data</button>
<h2>{{ msg }}</h2>
<button class="c-button c-button--primary" #click="greeting()">Greeting</button>
<hr>
<pre>{{ items }}</pre>
</div>
</template>
this live code
https://jsfiddle.net/cqx12a00/1/
Thanks for you help
You don't declare the show variables that your checkboxes are bound to, so they are not reactive – Vue is not aware when one is updated.
It should be initialized like so:
items: [
{ name: 'Ryu', show: false },
{ name: 'Ken', show: false },
{ name: 'Akuma', show: false }
]
You might think that newData would fix it, since it assigns a show member to each item, but Vue cannot detect added properties so they're still not reactive. If you initialized the data as I show above, then the assignment would be reactive.
If you want to add a new reactive property to an object, you should use Vue.set.

Use instance method in click handler

I have a Todo class that has a delete method. I would like to use the delete method as an #click handler:
<div v-for="todo in todos">
<v-btn #click="todo.delete">Delete</v-btn>
</div>
Unfortunately this gives me:
Invalid handler for event "click": got undefined
Here's a simple codepen example that shows the core concept working: https://codepen.io/nickforddesign/pen/YYwgKx
The issue is that the items in your todos array don't have the method.
<div class="app">
<ul>
<li v-for="todo in todos">
{{ todo }}
<button #click="todo.delete">Delete</button>
</li>
</ul>
</div>
And the js
new Vue({
el: '.app',
data() {
return {
todos: [{
name: '1',
delete() {
alert(`delete`)
}
},{
name: '2',
delete() {
alert(`delete`)
}
}]
}
}
})

Categories