I have an array of elements, I need to render those elements in to a div and attach different on-click functions to each.
<template>
<div class="container">
<div v-for="option in options" :key="option.id" #click="option.clickFunction">.
{{option}}
</div>
</div>
</template>
<script>
export default{
data(){
return{
options: [
{
id: 1,
text: "option 1",
clickFunction: "function1",
},
{
id: 2,
text: "option 2",
clickFunction: "function2",
},
{
id: 3,
text: "option 3",
clickFunction: "function3",
},
{
id: 4,
text: "option 4",
clickFunction: "function4",
},
{
id: 5,
text: "option 5",
clickFunction: "function5",
},
],
}
}
methods:{
//defining all click functions
}
}
</script>
I have tried the above approach but its not working, is there any way of doing this?
This isn't working for you because each clickFunction property in each object is a string. What is the #click attribute supposed to do with a regular old string? Try
<template>
<div class="container">
<div v-for="option in options" :key="option.id" #click="option.clickFunction">
<!-- I'm guessing you meant option.text here -->
{{option.text}}
</div>
</div>
</template>
<script>
export default{
data(){
return{
// pass functions around instead of strings!
options: [
{
id: 1,
text: "option 1",
clickFunction: this.myUniqueClickHandler,
},
{
id: 2,
text: "option 2",
clickFunction: this.myOtherUniqueClickHandler,
},
// etc.
],
}
}
methods:{
myUniqueClickHandler() {
// ...
},
myOtherUniqueClickHandler() {
// ...
}
}
}
</script>
I think you want to listen for each item's onclick. So, you need to declare only one method or function for all, and pass the key value of each item as a parameter. Then, use a switch statement or if statement to detect which option is clicked.
I have changed your code as bellow:
<template>
<div class="container">
<div v-for="option in options" :key="option.id" #click="myFunction(option.id)">.
{{option}}
</div>
</div>
</template>
<script>
export default{
data(){
return{
}
}
methods:{
myFunction(id) {
switch (id) {
case 1:
// option 1 is clicked
break;
case 2:
// option 2 is clicked
break;
case 3:
// option 3 is clicked
break;
case 4:
// option 4 is clicked
break;
default:
break;
}
}
}
}
</script>
Related
How can I implement dynamic event handling in Vue.js when using a dynamic list of elements?
I have a list of items, each represented by a component, and I want to attach a unique event listener to each item in the list. The event listener should perform a specific action based on the item's unique data.
The issue I am facing is that I am not able to bind the event listener to the specific item using v-on directive, as the list is generated dynamically and the number of items can change. I have tried using v-for with v-on, but it attaches the same event listener to all the items in the list.
I have also researched using $listeners and $attrs, but I am not sure if that is the best solution for my use case.
Here is an example of my current code:
<template>
<div>
<item-component v-for="item in items" :key="item.id" v-on="uniqueEventListeners(item)" />
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: "item 1" },
{ id: 2, name: "item 2" },
{ id: 3, name: "item 3" }
]
};
},
methods: {
uniqueEventListeners(item) {
return {
click: () => {
console.log(`Clicked on item with id: ${item.id}`);
}
};
}
}
};
</script>
What is the best way to achieve dynamic event handling with a dynamic list of items in Vue.js? And also how can I make sure that the event is unique for each item.
You can do something like this:
<template>
<div>
<item-component v-for="item in items" :key="item.id" :data-id="item.id" v-on="item.events" />
</div>
</template>
<script>
export default
{
data()
{
return {
items: [
{
id: 1,
name: "item 1",
events:
{
click: this.handleClick,
mouseenter: this.handleMouseEnter,
mousemove: this.handleMouseMove,
}
},
{
id: 2,
name: "item 2",
events:
{
click: this.handleClick,
}
},
{
id: 3,
name: "item 3",
events:
{
mousemove: this.handleMouseMove,
}
},
]
};
},
methods:
{
handleClick(event)
{
console.log("CLICK", event.target.dataset.id);
},
handleMouseEnter(event)
{
console.log("MOUSE ENTER", event.target.dataset.id);
},
handleMouseMove(event)
{
console.log("MOUSE MOVE", event.target.dataset.id);
},
}
};
</script>
You should make sure that your item-component is emitting the events that you're listening for.
I am using Bootstrap Vue to build a reusable Drowndown component. I would like to dynamically render different elements: item, title, and divider. How can I do this?
So what I want to achieve is the Dropdown component like this:
<template>
<b-dropdown>
<b-dropdown-{{option.element}}
v-bind:key="index"
v-for="option in data"
:value="option.id"
>{{ option.value }}</b-dropdown-{{option.element}}
>
</b-dropdown>
</template>
<script>
export default {
name: 'Dropdown',
props: {
data: data
}
}
</script>
So that it would render like
<b-dropdown-title>I am a title</b-dropdown-title>
<b-dropdown-item>And I am an item</b-dropdown-item>
<b-dropdown-item>I am another item</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
Then from the parent component, I could pass data like:
<Dropdown id="modules-dropdown" v-data="data"></Dropdown>
import Dropdown from './Dropdown'
const dropdownData = [{id: 1, value: 'I am a title', element: 'title'}, {id: 2, value: 'And I am an item', element: 'item'}, {id: 3, value: 'I am another item', element: 'item'}, {id: 4, element: 'divider'}]
export default {
name: 'ParentComponent',
components: {
Dropdown
},
data () {
return {
data: dropdownData,
}
},
}
What you want is a Dynamic Component, which allows you to define which component should be rendered using the is prop.
new Vue({
el: '#app',
data() {
return {
options: [
{ value: "Hello", element: 'header' },
{ value: "Item 1", element: 'item' },
{ value: "Item 2", element: 'item' },
{ value: null, element: 'divider' },
{ value: "Item 3", element: 'item' },
{ value: "Item 4", element: 'item' }
]
}
}
})
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.css" rel="stylesheet" />
<div id="app">
<b-dropdown text="Dropdown">
<component
:is="`b-dropdown-${option.element}`"
v-for="(option, index) in options"
:key="index"
>
{{ option.value }}
</component>
</b-dropdown>
</div>
Basically I've written following snippet in which I am using v-model in input that has datalist. When I select one of the datalist, datalist immediately reappears again after I select a option as if I had manually typed the code.
new Vue({
el: '#app',
data: {
input: '',
list : [
{name:"Item 1", code:"i1"},
{name:"Item 2", code:"i2"},
{name:"Item 3", code:"i3"},
{name:"Item 4", code:"i4"},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input v-model="input" list="list" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
But this does not happen when I remove vue directive from the input field (v-model,#input). I've added both code for convenience. I hope someone will help me because I need v-moder if not #input in my input field.
new Vue({
el: '#app',
data: {
input: '',
list : [
{name:"Item 1", code:"i1"},
{name:"Item 2", code:"i2"},
{name:"Item 3", code:"i3"},
{name:"Item 4", code:"i4"},
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input list="list" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
EDIT As suggested by #muka.gergely I used external method to blur the input field after value was selected. It is not perfect solution, but works as expected. (probably bug in chrome). Here is code
new Vue({
el: '#app',
data: {
input: '',
list: [{
name: "Item 1",
code: "i1"
},
{
name: "Item 2",
code: "i2"
},
{
name: "Item 3",
code: "i3"
},
{
name: "Item 4",
code: "i4"
},
]
},
methods: {
onSelect(event) {
let val = event.target.value;
let select = false;
let options = document.getElementById("list").childNodes;
for (var i = 0; i < options.length; i++) {
//check if value in input box is one of the options
if (options[i].value === val.trim()) {
//value was selected
//do something
select = true;
//bluring input field so as not to show datalist again
event.target.blur();
break;
}
}
//value was typed
if (!select) {
//this.fetchAutocomplete(val);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input }}</p>
<input v-model="input" list="list" #input="onSelect" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
The code works as you wrote it:
<p>{{input}}</p> - this is the first line. On init it's empty, so now lines are shown.
<input v-model="input" list="list" /> this is the second line, but on init it is displayed first.
So, when your app reacts to data change, the input is pushed one line down. If you initialize your input data with something else than '', then you can see that nothing unexpected happens (OK, maybe unexpected, but not extraordinary :) ):
new Vue({
el: '#app',
data: {
input: '',
list: [{
name: "Item 1",
code: "i1"
},
{
name: "Item 2",
code: "i2"
},
{
name: "Item 3",
code: "i3"
},
{
name: "Item 4",
code: "i4"
},
]
},
methods: {
loseFocus(evt) {
evt.target.blur()
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>{{ input || 'Data comes here' }}</p>
<input v-model="input" list="list" #input="loseFocus" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.name}}</option>
</datalist>
</div>
EDIT
The problem was not the "jumping" of the input, but that the datalist appeared open after picking an option.
The problem is that this element stays focused after the input/change event, so it behaves in an unwanted way. (But as it's supposed to behave.)
I added #input on the element, and created a method to blur the element (so it loses focus, and the datalist doesn't open/closes).
2nd EDIT
We discussed the question a bit more in the chat, and came up a with a snippet that is closer to the actual solution #KshitijDhakal sought:
new Vue({
el: "#app",
data: {
input: '',
list: [{
type: 'posts',
title: 'Posts',
code: 'i1'
},
{
type: 'albums',
title: 'Albums',
code: 'i2'
}
],
singleItem: {
title: ''
}
},
methods: {
fetchData: function(type, id) {
return fetch(`https://jsonplaceholder.typicode.com/${type}/${id}`)
.then(response => response.json())
.then(json => {
return json
})
},
onSelect: async function(e) {
if (this.list.map(el => el.code).includes(e.target.value)) e.target.blur()
this.singleItem = await this.fetchData(this.list.find(el => el.code === e.target.value).type, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="input" list="list" #input="onSelect" />
<datalist id="list">
<option v-for="item in list" :value="item.code">{{item.title}}</option>
</datalist>
<div>
<h2>
{{singleItem.title}}
</h2>
</div>
</div>
I'm using a slot to display a button in Vue Tables 2. How can I pass the id of the warehouse i.e. 123 or 456 to the edit() event handler?
I've tried adding props (as this is what the docs show). But I haven't had any luck. I'm using Vue Tables 2 in a component.
<template>
<div>
<h1>How to pass warehouse id to edit() method?</h1>
<v-client-table :columns="columns" :data="warehouses" :options="options">
<span slot="actions" slot-scope="{ WarehousesMin }">
<button v-on:click="edit">Edit</button>
</span>
</v-client-table>
</div>
export default {
name: 'WarehousesMin',
data() {
return {
warehouses: [
{"id": 123, "name": "El Dorado", "loc": "EDO"},
{"id": 456, "name": "Tartarus", "loc": "TAR"}
],
options: {
headings: {name: 'Name', code: 'Loc', actions: 'Actions'}
},
columns: ['name', 'loc', 'actions'],
}
},
methods: {
edit (warehouseId) {
// How to get id of warehouse here? i.e. 123 or 456
}
}
}
I haven't used this library before, but as far as I know about Vue slots, you can change your code into this and try again:
<template>
<div>
<h1>How to pass warehouse id to edit() method?</h1>
<v-client-table :columns="columns" :data="warehouses" :options="options">
<span slot="actions" slot-scope="{row}">
<button v-on:click="edit(row.id)">Edit</button>
</span>
</v-client-table>
</div>
and in script part, change to:
export default {
name: 'WarehousesMin',
data() {
return {
warehouses: [
{"id": 123, "name": "El Dorado", "loc": "EDO"},
{"id": 456, "name": "Tartarus", "loc": "TAR"}
],
options: {
headings: {name: 'Name', code: 'Loc', actions: 'Actions'}
},
columns: ['id', 'name', 'loc', 'actions'],
}
},
methods: {
edit (warehouseId) {
// The id can be fetched from the slot-scope row object when id is in columns
}
}
}
I think this ought to work, but if not please let me know.
So I love Semantic UI and I just started working with Vue.js
Semantic UI dropdowns must be initialized using the dropdown() in semantic.js, it generates a complex div based HTML to show the dropdowns in a pretty way.
The problem comes when I bind Vue to a dropdown, it doesn't update the UI according to the model. Especially when I change the value of the parent dropdown.
For some reason the bidning works fine the first time an item is selected on the parent dropdown, but not after that :|
<div class="ui grid">
<div class="row">
<div class="column">
<div class="ui label">Vechicle Make</div>
<select class="ui dropdown" v-model="selected" id="vehicle-makes" v-on:change="onChange">
<option v-for="option in options" v-bind:value="option.id">
{{option.text}}
</option>
</select>
</div>
</div>
<div class="row">
<div class="column">
<div class="ui label">Vechicle Model</div>
<select class="ui dropdown" v-model="selected" id="vehicle-models">
<option v-for="option in options" v-bind:value="option.id">
{{option.text}}
</option>
</select>
</div>
</div>
</div>
var model_options = {
1: [{ text: "Accord", id: 1 }, { text: "Civic", id: 2 }],
2: [{ text: "Corolla", id: 3 }, { text: "Hi Ace", id: 4 }],
3: [{ text: "Altima", id: 5 }, { text: "Zuke", id: 6 }],
4: [{ text: "Alto", id: 7 }, { text: "Swift", id: 8 }]
};
var makes_options = [
{ text: "Honda", id: 1 },
{ text: "Toyota", id: 2 },
{ text: "Nissan", id: 3 },
{ text: "Suzuki", id: 4 }
];
var vm_makes = new Vue({
el: "#vehicle-makes",
data: {
selected: null,
options: makes_options
},
methods: {
onChange: function(event) {
vm_models.selected = null;
vm_models.options.splice(0);
for (index = 0; index < model_options[this.selected].length; index++) {
vm_models.options.push(model_options[this.selected][index]);
}
}
}
});
var vm_models = new Vue({
el: "#vehicle-models",
data: {
selected: null,
options: []
}
});
// Eveything works fine if I remove this...
$('.ui.dropdown').dropdown();
The exact code can be found here. https://codepen.io/adnanshussain/pen/KqVxXL?editors=1010
You are using a v-for directive on your option element without a key attribute. Without this key, Vue uses an "in-place patch" strategy to optimize rendering. This is most likely messing with what your Semantic UI dropdown is expecting as far as changes to the select element go.
Add a key attribute to your option tag, providing a unique id as the value:
<option v-for="option in options" :value="option.id" :key="option.id">
{{ option.text }}
</option>
To clear the value in the model's select element when the make changes, you can use $('#vehicle-models').dropdown('restore defaults').
Also, if you put all the logic in one Vue instance, things become a lot simpler: Here's an example codepen.