Deleting specific component in v-for array - javascript

I have below array, that contains a number of columns. Below example contains three columns, but columns can be added/removed dynamically:
[['position', '30'], ['position', '60'], ['position', '90']]
I am facing issues when deleting the correct column (index in array) with Vue.
Consider below snippet:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: [['position', '30'], ['position', '60'], ['position', '90']]
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}} - <a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
If you run the above code snippet end try to delete the #1 column, it will actually remove the #2 column (last item of the array). Same goes for #0.
I thought that by providing the index to my deleteColumn function, I could remove the "right" index from the array.
Any help is appreciated.

Just give them a property name and you are done. Notice what I changed here. Columns is no more a 2D array, but objects. Use this.$delete(this.columns, index); to delete the objects.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: {
'1': {
position: 30
},
'2': {
position: 60
},
'3': {
position: 90
}
}
},
methods: {
deleteColumn: function(index) {
this.$delete(this.columns, index);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}} - <a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
{
'1': {
position: 30
},
'2': {
position: 60
},
'3': {
position: 90
}
}
Here, '1' is a property name and it's value is another object. It's like giving ids to your data.
The format for value of object is this
{ property_name : value }
Here, value is another object, and in that object, there is another property, named "position" with your corresponding values.

When you clicked any item you are removing it in the right way, your index is your key, that's the problem, but is visually, in the logic it's right. Display your position in your template just for you can see it. ANd for me your data it's not in the right way.
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}}-{{item.position}} -
<a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
and your script for you can see the change
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: [{position: 30}, {position: 60}, {position: 90}]
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})

The splice method reindexes the array, moving all elements after the splice point up or down so that any new inserted values will fit and so that the array indices remain contiguous. You can see it more clearly if you also display the values of the items in your list:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: ['foo', 'bar', 'baz']
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #{{index}} = {{item}} - <a #click="deleteColumn(index)" style="cursor:pointer">Delete me</a>
</div>
</div>
Initially, the snippet above will render like this:
Column #0 = foo - Delete me
Column #1 = bar - Delete me
Column #2 = baz - Delete me
If you now click the "Delete me" link on column #0 ("foo"), it will change to:
Column #0 = bar - Delete me
Column #1 = baz - Delete me
You can see that the value "foo" indeed got spliced out of the array — and the values "bar" and "baz" were shifted down by one position to become the new elements #0 and #1.
Anyway, the fix for this problem is simply "don't do that":
If you're using v-for with a simple array whose elements have no natural key value, you can just omit :key entirely and let Vue decide how to best handle changes to the underlying array. As long as the contents within the v-for loop doesn't contain any form inputs or stateful components or other fancy stuff that doesn't react well to the array being reindexed, it should work just fine.
Conversely, if you do have a natural unique key available for each array element, use it. If you don't, but can create one, consider doing that.

You should not use index as the key with CRUD operations since this will confuse Vue when it comes to deleting. The key should be a unique identifier that relates to the data.
You can create a new formatted array of objects on mount with a key generated from the data within the array (note: I haven't tested the code in a browser if there are any mistakes).
<template>
<div>
<div v-for="col in formattedColumns" :key="col.key">
{{ col.value }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
columns: [['position', '30'], ['position', '60'], ['position', '90']],
formattedColumns: null,
};
},
mounted() {
let columns = [];
for (let i = 0; i < this.columns.length; i++) {
columns.push({
value: this.columns[i],
key: this.columns[i][0] + this.columns[i][1],
});
}
this.formattedColumns = columns;
},
};
</script>

Try this this.$delete(this.columns, index) which is the same as Vue.delete(this.columns, index)
https://v2.vuejs.org/v2/api/index.html#Vue-delete

Related

How to Store Data Property Value from a Specific Item in Rendered List in Vue

I'm trying create a follow button on list items in Vue. My strategy is to grab the value of a particular list item property and store it in the data object. Then use this value in a method to add it to an array in my database.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock">+follow</button>
</div>
I'm not sure how to get the value of result.symbol "into" the button element to set the value symbol in the data object below.
<script>
export default {
data() {
return {
results: [ // this is populated by an api call
{
currency: "USD"
exchangeShortName: "NYSE"
name: "International Game Technology PLC"
stockExchange: "NYSE"
symbol: "IGT"
},
{...},
...
],
symbol: "",
};
},
followStock() {
// add this.symbol to database array
},
},
};
</script>
I'm guessing there might be an easier strategy I'm overlooking as I'm still new to Vue, so any other solution that essentially allows me to fire off the value of result.symbol from any rendered result to my database would be awesome.
You can just pass the result as a parameter to your method.
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result)">+follow</button>
</div>
And in your method:
methods: {
followStock(result) {
// do something with result
console.log({result});
let symbol = result.symbol;
},
}
P.S I didn't see you put your followStock() inside a methods object, but I did so in the example. https://v2.vuejs.org/v2/api/#methods
Write directly as a function call.
The vue compiler will turn followStock(result.symbol) into function(event) {followStock(result.symbol)}.
new Vue({
el: '#app',
data() {
return {
results: [
{
name: "International Game Technology PLC",
symbol: "IGT"
},
{
name: "A name",
symbol: "A symbol"
}
]
};
},
methods: {
followStock(symbol) {
console.log(symbol)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
</div>
As Nazaire mentioned you can access the results anywhere inside the child elements when using v-for.
(it works like a normal for-loop)
It's not only limited to the corresponding element (the element in which you do v-for)
<div v-for="result in results" :key="result.symbol">
{{ result.name }}
<button #click="followStock(result.symbol)">+follow</button>
</div>
followStock(symbol){
// you can now add symbol to db
}

How to fix setInterval bugg?

I am having a h1 with v-for and i am writing out things from my array ,it looks like this:
<h1
v-for="(record, index) of filteredRecords"
:key="index"
:record="record"
:class="getActiveClass(record, index)"
>
<div :class="getClass(record)">
<strong v-show="record.path === 'leftoFront'"
>{{ record.description }}
</strong>
</div>
</h1>
as you can see i am bindig a class (getActiveClass(record,index) --> passing it my record and an index)
This is my getActiveClass method:
getActiveClass(record, index) {
this.showMe(record);
return {
"is-active": index == this.activeSpan
};
}
i am calling a function called showMe passing my record to that and thats where the problem begins
the showMe method is for my setInterval so basically what it does that i am having multiple objects in my array and it is setting up the interval so when the record.time for that one record is over then it switches to the next one. Looks like this:
showMe(record) {
console.log(record.time)
setInterval(record => {
if (this.activeSpan === this.filteredRecords.length - 1) {
this.activeSpan = 0;
} else {
this.activeSpan++;
}
}, record.time );
},
this activeSpan is making sure that the 'is-active' class (see above) is changing correctly.
Now my problem is that the record.time is not working correctly when i print it out it gives me for example if iam having two objects in my array it console logs me both of the times .
So it is not changing correctly to its record.time it is just changing very fastly, as time goes by it shows just a very fast looping through my records .
Why is that? how can i set it up correctly so that when i get one record its interval is going to be the record.time (what belongs to it) , and when a record changes it does again the same (listening to its record.time)
FOR EXAMPLE :
filteredRecords:[
{
description:"hey you",
time:12,
id:4,
},
{
description:"hola babe",
time:43,
id:1
},
]
it should display as first the "hey you" text ,it should be displayed for 12s, and after the it should display the "hola babe" for 43 s.
thanks
<template>
<h1 ...>{{ filteredRecords[index].description }}</h1>
</template>
<script>
{
data() {
return {
index: 0,
// ...
};
},
methods: {
iterate(i) {
if (this.filteredRecords[i]) {
this.index = i;
window.setTimeout(() => iterate(i + 1), this.filteredRecords[i].time * 1000);
}
},
},
mounted() {
this.iterate(0);
},
}
</script>
How about this? Without using v-for.

Render nested list in vue without nesting HTML elements?

I am in a bit of a problem cant think of way to solve neither could find anything relevant on google or rather I don't know what to search for.
I have an array categories and an object containing subcategories
categories: [
'Mobiles',
'Mobiles Accessories',
...
]
subcategories: {
'Mobiles': [
'Mi',
'Samsung',
'Infinix',
...
],
'Mobile Accessories': [
'Mobiles Cases',
'Headphones & Earphones',
...
]
}
Now i want render a menu using that in following way.
<span class="category">Mobiles</span>
<span class="subcategory">Mi</span>
<span class="subcategory">Samsung</span>
<span class="subcategory">Infinix</span>
...
<span class="category">Mobile Accessories</span>
<span class="subcategory">Mobiles Cases</span>
<span class="subcategory">Headphones & Earphones</span>
...
If i use v-for in vue i will have to nest .categories and .subcategories inside a parent element for each category, which i don't want to do.
This whole mess is that i can use flex-direction: column in the parent element and when rendering the list, it automatically flows to next column which wont happen if i nest .categories and .subcategories in a parent element as now each parent element for categories will move to next column instead of its height.
So how can i achieve this either by changing CSS or the data structure.
Thanks
Akash.
Note that in your question Mobiles Accessories does not equal Mobile Accessories so you have to make sure the categories array contains the exaxt names of the keys in subcategories.
You can use reduce and map:
const categories = ['Mobiles', 'Mobile Accessories'];
const subcategories = {
Mobiles: ['Mi', 'Samsung', 'Infinix'],
'Mobile Accessories': ['Mobiles Cases', 'Headphones & Earphones'],
};
console.log(
categories.reduce(
(result, value) =>
result.concat(
{ class: 'category', value },
subcategories[value].map((value) => ({
class: 'subcategory',
value,
})),
),
[],
),
);
Here is the Solution for You!!!
Using two for loops you can do this.
var app = new Vue({
el: '#app',
data: {
menu: {
categories:[
'Accessories',
'Mobiles',
],
subcategories: {
'Accessories': [
'Mobiles Cases',
'Headphones & Earphones',
],
'Mobiles': [
'Mi',
'Samsung',
'Infinix'
],
}
}
}
});
.category{
display:block;
padding:10px 0px;
}
.subcategory{
display:block:
margin:5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(menus,index) in menu.categories">
<span class="category">{{ menus }}</span>
<span class="subcategory" v-for="(submenu,index) in menu.subcategories[menus]">{{submenu}}<br/></span>
</div>
</div>

Vue JS, add event handler for elements created within JS

I am populating a table inside an Axios API call and need to add a delete button to each row.
Im not quite sure how to handle this, in my current experience:
formattedVehicles.push([
"<div class='btn btn-danger' v-on:click='deleteVehicle(id)'>Delete</div>"
]);
Ofcourse, this doesn't work. How do I go about getting a click handler for the delete button to take a parametre and handle it as a method?
In Vue.js you don't have to create div like in jQuery.
Here you have an array of vehicles. The template will update when the array change.
You just need to manage the array of vehicles like this :
new Vue({
el: "#app",
data: function() {
return {
formattedVehicles: [
{ id: 1, name: 'vehi1' },
{ id: 2, name: 'vehi2' },
{ id: 3, name: 'vehi3' }
]
}
},
methods: {
callingAxiosApi: function() {
//---> Inside your '.then(function (response) {' you do:
//---> this.formattedVehicles = response; If response is the array of vehicles
},
addVehicle: function() {
var rand = Math.floor(Math.random() * (100 - 4)) + 4;
this.formattedVehicles.push({ id: rand, name: 'vehi' + rand });
},
deleteVehicle: function(id, index) {
//---> Here you can use 'id' to do an Axios API call.
this.formattedVehicles.splice(index, 1);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.5/vue.js"></script>
<div id="app">
<button #click="addVehicle">Add vehicle</button>
<div v-for="(vehicle, index) in formattedVehicles" :key="index">
id: {{ vehicle.id }}
<br />
name: {{ vehicle.name }}
<button #click="deleteVehicle(vehicle.id, index)">Delete this vehicle</button>
</div>
</div>
To understand the code above :
Use v-for when you have a list to show in html :
v-for="(anyNameYouWantForItemOfArray, index) in yourArray"
Inside the div that contains the v-for you can access the item of the aray : {{ vehicle.id }}, {{ vehicle.name }} or pass data in event handler : #click="deleteVehicle(vehicle.id, index)"
You must use key property in v-for since version 2.2.0+ key :
In 2.2.0+, when using v-for with a component, a key is now required.
To add event handler you just put v-on:click="method" or the shortcut #click="method"
In this case we put <button #click="deleteVehicle(vehicle.id, index)">Delete this vehicle</button> in the v-for so when we clicked on the button, we call the deleteVehicle method with the index of the row. In your case you can use id to do an API call with axios.
We use the v-bind directive to put javascript code in html attribute v-bind :
We are in the v-for so we have access to index variable :
v-bind:key="index" or with the shortcut ':' (a colon) : :key="index"

How to output html from filter inside mustache

I have a input (top right) where users can search things, when it's directive length get 3 characters it will display a list of products and highlight the matches...
Look at my code:
html
<div id="app">
<div id="header">
<div class="right"><input type="text" v-model="message" v-on:keyup="searchStart()" v-on:blur="searchLeave()"/>
<ul v-if="this.searchInput" class="product-list">
<li v-for="product in products">
{{ product.id }} - {{ product.name | highlight }} - {{ product.qtd }}</li></ul>
</div>
</div>
<div id="main">
<div id="menu">fdfds</div>
<div id="container">{{ message }}</div>
</div>
</div>
js
var search = new Vue({
el: "#app",
data: {
message: "",
searchInput: false,
products: [
{
id: 1,
name: "produto 01",
qtd: 20
},
{
id: 2,
name: "produto 02",
qtd: 40
},
{
id: 3,
name: "produto 03",
qtd: 30
},
]
},
methods: {
searchStart: function(){
if(this.message.length >= 3)
this.searchInput = true;
console.log(this.searchInput);
},
searchLeave: function(){
this.searchInput = false;
this.message = "";
console.log(this.searchInput);
}
},
filters: {
highlight: function(value){
return value.replace(search.message, '<span class=\'highlight\'>' + search.message + '</span>');
}
}
});
Here you can see a live pen: http://codepen.io/caiokawasaki/pen/dXaPyj
try to type prod inside the pen...
Is my filter correct? The way I created the filter is correct?
The main question is: How to output the HTML from my filter?
Edit/Solution
The problem in the case was codepen, there is some kind of conflict with vue, so I was not able to escape the html using {{{}}}, put the code in another editor (jsfidle) and it worked.
I'm accepting the answer given to the reward because it's right.
You'll need 3 steps here for achieve what you want:
Use triple braces {{{ }}} to display unescaped html
Filter your users by your v-model variable, in order to just show the matches
Replace the substring matching by the <span> tag
Check out the computed property filteredUsers and the filter in this working jsfiddle

Categories

Resources