I am using vuetify and trying to create a method to add chips from the dropdown. Now i got most of the logic down but as you can see, the method multipleSelection does not fire off on the enter key but works fine onClick.
Here is a demo for the codepen.
So the method multipleSelection should fire off on the enter key.
new Vue({
el: '#app',
data() {
return {
arr: [],
items: [{
text: 'Foo',
value: 'foo'
},
{
text: 'Bar',
value: 'bar'
},
{
text: 'biz',
value: 'buzz'
},
{
text: 'buzz',
value: 'buzz'
}
],
}
},
methods: {
multipleSelection(item) {
this.arr.push(item)
console.log(this.arr)
},
deleteChip(item) {
console.log('delete')
this.arr = this.arr.filter(x => x !== item);
console.log(this.arr)
}
},
})
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container>
<v-combobox :items="items" label="Add Multiple Chips" multiple small-chips solo deletable-chips :value="arr">
<template v-slot:item="{ index, item }">
<v-list-tile-content #click.stop.prevent="multipleSelection(item)">
{{item.text}}
</v-list-tile-content>
</template>
<template v-slot:selection="{ index, item }">
<v-chip close dark color="info" #click:close="deleteChip(item)">
{{ item.text }}
</v-chip>
</template>
</v-combobox>
</v-container>
</v-app>
</div>
Any help will be appreciated. Thank you.
Since multipleSelection() is not being called from #keypress on v-slot:item, likely it's not where the event is being captured.
Taking a look at events on Vue Dev Tools, can see input $emit by <VCombobox> is the first one after pressing Enter.
So the following will get it, but this seems to mess with the position in the list for some reason I don't understand.
<v-combobox
...
#input.capture="(item) => multipleSelection(item)"
>
Better to add a listener,
mounted() {
this.$refs.combobox.$on('input', (items) => {
const item = items[items.length -1]; // getting all selected, so take the last
this.multipleSelection(item)
})
},
Note, I tested this on a local project with Vuetify v1.5.14.
Looks like you need #keyup
<v-list-tile-content
#keyup.enter.prevent="multipleSelection(item)" #click.stop.prevent="multipleSelection(item)">{{item.text}}
</v-list-tile-content>
Not sure about keypress....Vue docs show #keyup
https://v2.vuejs.org/v2/guide/events.html#Key-Modifiers
Related
In the following code:
JS
const App = {
template: '#app-template',
data: () => ({
selected: [],
items: Array.from({length: 50}, (x, i) => i+1).map(i => ({
id: i ,
name: `Item ${i}`,
subtitle: `Subtitle ${i}`
}))
}),
computed: {
parsedItems() {
this.selected;
return this.items.map(item => ({
someValue: 3,
...item
}));
}
}
}
new Vue({
vuetify: new Vuetify(),
render: h => h(App)
}).$mount('#app')
HTML
<script type="text/x-template" id="app-template">
<v-app>
{{selected}}
<v-container>
<v-virtual-scroll
:items="parsedItems"
:item-height="65"
height="500"
>
<template v-slot="{ item, index }">
<v-list-item-group
v-model="selected"
multiple
>
<v-list-item :key="item.id" :value="item">
<v-list-item-action>
<v-checkbox
:input-value="selected.includes(item.id)"
color="primary"
/>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
Index: {{ index }} {{ item.name }}
</v-list-item-title>
<v-list-item-subtitle>
{{ item.subtitle }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</template>
</v-virtual-scroll>
</v-container>
</v-app>
</script>
<div id="app"></div>
When either of the checkboxes i checked or unchecked - the selected v-model always adds up another instance though it previously contains one already.
Removing this.selected; (line 16 in the Codepen below) fixes the issue.
I suspect that this.selected is somehow dereferences its own values and then can't validate the appearance of previously selected items.
Here's a Codepen with the issue at hand: https://codepen.io/MichaelKatz/pen/vYXXdgb
In my real-world scenario, I need to filter and manipulate items within the list, according to previously made selections (i.e remove / re-add items).
I do it by using a computed item property which derives its content from a previously selected items, from the selected v-model and my current solution will require me to JSON.stringify all of my objects, essentially making them value-based strings to keep things in check.
The v-model does not seem to work with Objects
<v-list-item :key="item.id" :value="item"> <!-- change this -->
<v-list-item :key="item.id" :value="item.id"> <!-- into this -->
And create a new computed property to "hydrate" those ids:
selectedItems() {
return this.selected.map(id => this.parsedItems.find(x => x.id === id))
}
Updated Codepen
It seems like accessing a v-model while filtering the items it refers to, creates a de-reference of the objects within it.
The best solution I could come up with was adding an additional computed property which will contain the logic involving this.selected.
It has indeed solved the issue for me.
computed: {
parsedItems() {
return this.items.map(item => ({
someValue: 3,
...item
}));
},
filteredItems() { // adding another computed property while using this.selected
this.selected;
return this.parsedItems;
}
}
}
From my perspective, the problem is you have used the multiple props, which will allow multiple selections.
<template v-slot="{ item, index }">
<v-list-item-group
v-model="selected"
multiple
>
Simply removing it will solve your problem.
I have an array cars which returns me the names and each of them has a property starred which i want to toggle true and false back and forth. However i want to set starred to true for only one of them at a time. So i created a method setStarred and inside the method, i am using a map to set others to false. However i am able to set the starred to true however i am not able to set it back to false.
Please check this Codepen
This is working example
new Vue({
el: "#app",
data() {
return {
cars: [{
name: "Toyota",
starred: false
},
{
name: "BMW",
starred: false
},
{
name: "Ford",
starred: false
}
]
};
},
methods: {
setStarred(index) {
this.cars.map((i) => {
i.starred = false;
});
this.cars[index].starred = !this.cars[index].starred;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center column>
<v-flex xs6 v-for="(car,index) in cars" :key="index">
<h2>{{car.name}}
<v-icon :color="car.starred ? 'primary': '' " #click="setStarred(index)">star_border
</v-icon>
</h2>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
Essentially i am trying to set the selected back to false. Any help will be appreciated. Thank you
Try this:
this.cars[index].starred = !this.cars[index].starred;
this.cars.map((i) => {
if(i.name!=this.cars[index].name)
i.starred = false;
});
I prefer saving the 'starred' state of the target first, then toggle it later.
If so, you don't need to put one if statement in the for-loop. Though in this case, it doesn't improve the performance a lot, but I believe avoid too many if from for-loop is one good habit.
Below is the example:
new Vue({
el: "#app",
data() {
return {
cars: [{
name: "Toyota",
starred: false
},
{
name: "BMW",
starred: false
},
{
name: "Ford",
starred: false
}
]
};
},
methods: {
setStarred(index) {
let cur = this.cars[index].starred
this.cars.forEach((i) => {
i.starred = false;
});
this.cars[index].starred = !cur;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.18/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center column>
<v-flex xs6 v-for="(car,index) in cars" :key="index">
<h2>{{car.name}}
<v-icon :color="car.starred ? 'primary': '' " #click="setStarred(index)">star_border
</v-icon>
</h2>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
I have a text field in which you can input the field and on Click it adds a value to an array which renders the chips. Now each chips has a value property and a toggle property. By Default every new chip has toggle set to false. Now i can add as well as delete chips. The chips are deleted by using the deleteChips method. What i am trying to do is when there is only one chip left, the chip's toggle value should change dynamically to false or true depending on whatever it was at the time. So basically toggle = !toggle.
I have tried using the array.length but wasn't able to get it working.
Here is a sample pen
Here is the sample code:-
new Vue({
el: "#app",
data() {
return {
inputValue: "",
inputArray: [],
selectedChip: ""
};
},
methods: {
createChips() {
this.inputArray.unshift({
value: this.inputValue,
toggle: false
});
this.inputValue = "";
},
deleteChips(chip) {
let index = this.inputArray.filter((el) => el.chip === chip);
this.inputArray.splice(index, 1);
},
chipSelection(item) {
this.selectedChip = item;
},
toggleChip(item) {
item.toggle = true;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center>
<v-flex xs6>
<v-text-field v-model="inputValue"></v-text-field>
<v-btn #click="createChips">Click Me</v-btn>
<div v-if="inputArray.length > 0">
<div v-for="chip in inputArray">
<v-chip :key="chip.value" close #click="chipSelection(chip.value)" #input=deleteChips(chip)>
<v-avatar>
<v-icon #click="toggleChip(chip.value)">
account_circle
</v-icon>
</v-avatar>
<span v-if="selectedChip === chip.value">{{chip.value}}</span>
</v-chip>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
Any help will be appreciated. Thank you.
tbh, I can't really follow your intent, but based on the comments, perhaps you want to watch the inputArray and execute code based on changes to its length.
watch: {
inputArray(newValue, oldValue) {
if ((newValue.length !== oldValue.length) && (newValue.length === 1)) {
this.inputArray[0].toggle = !this.inputArray[0].toggle;
}
}
}
I want to use Vuetify's v-list-item-group component for my Vue app. This list represents nodes that are related to a graph. I can select none, some or all of them and delete the selected ones.
For a better user experience I want to provide a "select all / deselect all" checkbox at the top next to the header. If only some nodes are selected, the checkbox should render the indeterminate state.
Currently this is the code I'm using
<div id="app">
<v-app id="inspire">
<v-list>
<v-list-item>
<v-list-item-action>
<v-checkbox :indeterminate="someNodesSelected" :input-value="allNodesSelected" #click="toggleCompleteSelection" />
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="graphWithNodes.name"></v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon :disabled="noNodesSelected" #click="deleteNodes">
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<v-list-item-group v-model="selectedNodeIds" multiple>
<v-list-item v-for="node in graphWithNodes.nodes" :key="node.id" :value="node.id">
<template v-slot:default="{ active, toggle }">
<v-list-item-action>
<v-checkbox :input-value="active" :true-value="node.id" #click="toggle" />
</v-list-item-action>
<v-list-item-content>
<v-list-item-subtitle v-text="node.id"></v-list-item-subtitle>
</v-list-item-content>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
selectedNodeIds: [],
graphWithNodes: {
id: 1,
name: "The graph",
nodes: [{
id: 1,
graphId: 1
}, {
id: 2,
graphId: 1
}]
},
}
},
computed: {
noNodesSelected() {
return this.selectedNodeIds.length === 0;
},
someNodesSelected() {
return this.selectedNodeIds.length > 0 && !this.allNodesSelected;
},
allNodesSelected() {
return (
this.selectedNodeIds.length === this.graphWithNodes.nodes.length
);
}
},
methods: {
deleteNodes(nodeIds) {
for (const nodeId of this.selectedNodeIds) {
this.deleteNode(nodeId);
}
this.selectedQueueIds = [];
},
deleteNode(id) {
this.graphWithNodes.nodes = this.graphWithNodes.nodes.filter(node => node.id !== id);
},
toggleCompleteSelection() {
if(this.noNodesSelected || this.someNodesSelected) {
this.selectedNodeIds = this.graphWithNodes.nodes.map(node => node.id);
} else {
this.selectedNodeIds = [];
}
}
}
})
If you want to play around I created a codepen for this
https://codepen.io/magicfoobar/pen/RwPBNmV?editors=1010
So the problem I have is that when I click on the header checkbox the function toggleCompleteSelection gets executed twice and I can't figure out why.
Does someone know why the header checkbox is broken and how to fix it?
Thanks in advance
It works if you change the checkbox trigger from #click to #change
<v-checkbox
:indeterminate="someNodesSelected"
:input-value="allNodesSelected"
#change="toggleCompleteSelection" />
Just add .stop after the click and it works.
<v-checkbox :indeterminate="someNodesSelected" :input-value="allNodesSelected" #click.stop="toggleCompleteSelection" />
codepen - https://codepen.io/Pratik__007/pen/MWwBKRL?editors=1010
I'm not sure that you need to run toggleCompleteSelection directly from the checkbox.
I would achieve the "select all" functionality with a Watcher, see https://codepen.io/joffff/pen/06cd75ea651660d13d4ddc288b8448d7?editors=1010
I'm try to use a computed value in a v-select (from vuetify) and when I select a value in the select there is an endless loop.
I've reproduce my dirty code in this pen to illustrate my problem. Be careful this might block your navigator.
HTML code
<div id="app">
<v-app id="inspire">
<v-card color="grey lighten-4" flat>
<v-card-text>
<v-select
v-model="select"
label="Be careful when select a value :)"
chips
tags
:items="items">
</v-select>
</v-card-text>
</v-card>
</v-app>
</div>
JS Code
new Vue({
el: '#app',
data () {
return {
obj: {
values: [{'name':'Testing'}]
},
items: [
'Programming',
'Design',
'Vue',
'Vuetify'
]
}
},
computed: {
select: {
get: function () {
return this.obj.values.map(val => val.name).sort()
},
set: function (chipsValues) {
this.obj.values = chipsValues.map(val => {return {'name': val}})
}
}
}
})
What's the proper way to code this behavior ?
A quick fix would be to validate before setting the this.obj.values whether you are getting any new values. If the new value is larger/smaller than old value, you can set it, else ignore it.
Since Javascript is synchronous, you can get away with just checking the length of the arrays.
set: function (chipsValues) {
if( this.obj.values.length != chipsValues.length) {
this.obj.values = chipsValues.map(val => {return {'name': val}})
}
}
Here's the updated pen: https://codepen.io/anon/pen/XewjdJ?editors=1010