Remove duplicate list item in v-for - javascript

I'm retrieving all groups from an axios request at the page load and I store that data inside an empty array inside a reactive object like this,
const groupHandler = reactive({
groups: [],
});
And when the user clicked one of his product and click on edit a form will appear like this,
Here you can see Group 1 has been repeated. And there is another reactive object to store that user's product's group id,
const productForm = reactive({
group: 2,
});
So when the user clicks on a product productForm.group will be filled with that product's group id. I want to prevent this been duplicated in my edit product form. I'm using the v-for directive to loop the groups array,
<li
v-for="group in groupHandler.groups"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
So how to prevent this duplicate? In the v-for directive, I could use a condition like if group.id is not equal to productForm.group print group.name But I have no clue to do this. Really appreciate if somebody could help thanks.

You can use v-for in the template element, and then in the li element, you can use v-if condition to only render the group which doesn't have that id
<template v-for="group in groupHandler.groups">
<li
v-if="group.id !== productForm.group"
:key="group.id"
:group-id="group.id" >
{{ group.name }}
</li>
</template>

You can simply achieve this by using Array.filter() method in the v-for directive itself.
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
Live Demo :
new Vue({
el: '#app',
data: {
uniqPropValue: {},
groupHandler: {
groups: [{
id: 1,
name: 'Group 1'
}, {
id: 2,
name: 'Group 1'
}, {
id: 3,
name: 'Group 2'
}, {
id: 4,
name: 'Group 3'
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<li
v-for="group in groupHandler.groups.filter(({ name }) => !uniqPropValue[name] && (uniqPropValue[name] = true))"
:key="group.id"
:group-id="group.id">
{{ group.name }}
</li>
</div>

Related

V-model doesn't update on checkbox list change

I have a list of checkboxes that are rendered with a v-for on a component. When checked, the checkboxes fill up an array with currently selected checkboxes. The problem comes when one or more of the checked checkboxes is removed - the v-model still counts the removed checkbox. How do I update the v-model as my array updates? I tried force re-rendering the whole component which solves the problem but it's not the solution I need.
<div v-for="player in players" :key="player.id">
<input
v-model="selectedPlayers"
:value="player.id"
type="checkbox"
:id="player.id"
/>
<label :for="player.id">
{{ player.name }}
</label>
</div>
Sandbox
Problem
Desired outcome
V-model won't remove data when the component is no longer rendered, you need to do that explicitly.
You could filter selectedPlayers from the #click handler so that it only includes ids that are in the new variable.
this.selectedPlayers = this.selectedPlayers.filter(
id => players2.find(
player => player.id === id
)
)
So from what I understand, you have this player2 array, that you need to compare against. Whatever is there in the players2 array needs to be there in the selectedPlayers array. To do this just use the use map array function to iterate over the players2 array to return only the ids of the players and then store them in the selected players array which being a reactive property will automatically patch the DOM. There's absolutely no need to re-render the component.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: "#app",
data() {
return {
selectedPlayers: [],
players: [
{name: "Arnold",id: "1"},
{name: "Rambo",id: 2},
{name: "Terminator",id: 3},
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
players2: [
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
};
},
methods: {
clicked() {
this.players = this.players2;
this.selectedPlayers = this.players2.map(p => p.id);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
If you check all checkboxes and press the button, the array still contains all elements
<div v-for="player in players" :key="player.id">
<input v-model="selectedPlayers" :key="players.length" :value="player.id" type="checkbox" :id="player.id" />
<label :for="player.id">
{{ player.name }}
</label>
</div>
{{ selectedPlayers }}
<button #click="clicked">Press me</button>
</div>

How to iterate the HTML elements with some specific condition?

Problem statement : v-for loop is iterating and binding the data properly in HTML template but not able to iterate it partially based on some condition. Please find below the JSFiddle link for demo.
Requirement : In above demo link, for "Second Section" I want to display the input textbox only once which will be vertically aligned center (in front of beta) instead of repeating it multiple times. other values will be repeat (i.e. alpha, beta, gama).
Fiddle
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="item in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span> <input type="textbox"/>
</div>
</div>
</div>
You can pass index into your v-for, then use it in conditionals inside the loop. I've modified your example to show the principle. The text box appears for every section on the first loop, or if sectionData === beta. You can see this condition in the v-if.
This works, but in general, every time you use v-for, you should create a component. The structure quickly gets difficult to understand otherwise.
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="(item, index) in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span>
<input
v-if="index === 0 || sectionData === 'beta'"
type="textbox"
/>
</div>
</div>
</div>

How can I change the selected v-select item using a method?

I am trying to set the selected value of my v-select component in a method in the script part.
These are the the code parts:
<v-flex xs4>
<v-select v-model="selected" :items="items" item-text="name"
item-value="value" outlined class="ml-2 mr-1" return-object></v-select>
</v-flex>
and the part of the script:
export default {
return{
data: function () {
items: [
{ name: 'item 1', value: 1 },
{ name: 'item 2', value: 2 },
{ name: 'item 3', value: 3 }],
selected: { name: 'iteam 1', value: 1 }
},
methods: {
apply (component) {
for (var i in this.items.entries()) {
if (i.name === component.item) {
this.selected.name = i.name
this.selected.value = i.value
}
}
}
}
}
}
I've tried different versions like
this.selected = i
this.selected[name] = i.name
this.selected[value] = i.value
this.selected = { i.name, i.value }
but nothing is working.
It's and old question but Let me post my answer to help others as well, after alot of search I have come to this point, and I want to share it with others as well.
//This will load all your items in dropdown
<v-select
v-model="selected"
:items="items"
item-text="name"
item-value="value"
label="Select Item"
dense>
</v-select>
Now Edit Part
Lets say you want to edit a record, so you will probably pass the record id in edit method of your vuejs then inside edit method of vuejs, you will do an edit axios call for that specific record to first show it inside fields and then you will update. But before update you need to do something inside edit method of your vuejs when you will have already loaded data for that specific id.
Now lets say you have received a record from database according to a specific id, you will see a dropdown id I mean to say a foreign key for a dropdown that you had saved during storing data.
Suppose you have items array which holds whole data for a dropdown. Inside this you are having an value and name fields. So you have this items array and an object from database during edit for a specific record. Now you are good to go with below code.
Inside Edit Method of vuejs
//item_id it is like a foreign key for dropdown you have in your table
this.selected = this.items.find(item => item.value === parseInt(res.data.item_id))
this.selected = parseInt(this.selected.item_id)
Note: I am doing parseInt() it's because when you check in console the primary key is an integer like 1 but foreign key like rating_id is string i-e "1". You can also use two equals == if you are not using parseInt() but I haven't checked that.
For clearly understanding I am just sharing a sample edit code which might help you a bit
editItem(id){
axios.get( `/api/category/${id}` ).then( res => {
if(res.data.status === 200){
console.log(res.data.data)
this.name = res.data.data.name
this.id = res.data.data.id
this.parent_id = this.list_categories.find(item => item.id === parseInt(res.data.data.parent_id))
this.parent_id = parseInt(this.parent_id.id)
this.edited = true
}else{
this.$toaster.error( res.data.message )
}
});
}
Here's a complete example:
new Vue({
el: '#app',
data () {
const items = [
{ name: 'item 1', value: 1 },
{ name: 'item 2', value: 2 },
{ name: 'item 3', value: 3 }
]
return {
items,
selected: items[1]
}
},
methods: {
apply (component) {
this.selected = this.items.find(item => item.name === component.item)
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify#1.5.16/dist/vuetify.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#1.5.16/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-flex xs4>
<v-btn
v-for="item in items"
:key="item.value"
#click="apply({item: item.name})"
>
{{ item.name }}
</v-btn>
<v-select
v-model="selected"
:items="items"
item-text="name"
item-value="value"
outlined
class="ml-2 mr-1"
return-object
></v-select>
</v-flex>
</v-app>
</div>
In your original code the apply method seemed to be expecting to be passed an object (component) with an item property that matched the name of one of the items. I've tried to preserve that behaviour though it isn't clear why you would want that. Typically the value used for item-value would be the underlying id value used behind the scenes, not the item-text.
Rather than trying to copy values around between objects I've treated the 3 values in items as canonical and ensured that selected is always set to one of those 3. Not merely an object with the same values but actually one of those objects. This isn't strictly required but it seemed like the easiest way to me.
You need to set selected all at once, like this.selected = { name: 'item 3', value: 3 }
I changed the parameter type of apply for testing to string but it should look something like:
apply (component) {
temp={}
this.items.forEach((i)=> {
if (i.name === component) {
temp.name = i.name;
temp.value = i.value;
}
});
this.selected=temp
}
and I called apply with a btn
<v-btn v-for="n in 3" #click="apply(`item ${n}`)">Apply {{n}}</v-btn>

binding the property of a data object to DOM element's attribute

I'm a newbie in Vue.js. I have the following lines of code in my HTML and JS file:
HTML
<div id="app">
<ul>
<li v-for="item in items" v-bind:class="{{item.className}}">{{item.text}}</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
items: [
{
className: 'item-1',
text: 'Item 1'
},
{
className: 'item-2',
text: 'Item 2'
},
{
className: 'item-3',
text: 'Item 3'
}
]
}
})
What I want to happen is bind the value of each className to the class attribute of each DOM element. I hope someone could correct me on this.
When using v-bind you don't need to use the {{...}} syntax, because Vue already assumes you will want to use some kind of a property or object.
So you can for example output the value of each className simply like this:
<li v-for="item in items" v-bind:class="item.className">{{item.text}}</li>
Or shorthand version:
<li v-for="item in items" :class="item.className">{{item.text}}</li>
Or if the classes are always going to follow the pattern of item-i:
<li v-for="item, i in items" :class="`item-` + i">{{item.text}}</li>

Vue2 error when trying to splice last element of object

I have a Vue2 app wit a list of items which I can choose and show, or delete.
When deleting the last element in the list (and only the last one) - I get Vue warn - "[Vue warn]: Error when rendering root instance: "
my HTML:
<body >
<div id="app">
<ul>
<li v-for="(item, index) in list" v-on:click = "selectItem(index)" >
<a>{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
</ul>
<div>
<span>{{selectedItem.name}}</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
The JS:
var app = new Vue({
el: '#app',
data: {
index: 0,
selectedItem: {},
list : [
{ id: 1, name: 'org1', desc: "description1"},
{ id: 2, name: 'org2', desc: "description2"},
{ id: 3, name: 'org3', desc: "description3"},
{ id: 4, name: 'org4', desc: "description4"}
]
},
methods: {
deleteItem: function(index) {
this.list.splice(index,1);
},
selectItem: function(index) {
this.selectedItem = this.list[index];
},
}
})
Can you please advise why does this happen and how to solve this issue?
The problem is happening as you have having selectItem bind at li level, so event when you click cross button, selectItem gets executed and that same item gets deleted as well, causing this error.
One way to solve this problem can be moving the selectItem binding inside li as follows
<li v-for="(item, index) in list">
<a v-on:click = "selectItem(index)" >{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
See working fiddle.
Another approach can be when printing selectedItem.name in your HTML, you put a null check, whether selectedItem exist or not like following:
<span>{{selectedItem && selectedItem.name}}</span>
See Working fiddle.

Categories

Resources