Vue - define event handlers in array of dynamic components - javascript

I want do create some some different components by looping through an array of components like in my example. But I want to create different event handlers for each component. How can I define them in my componentData Array and bind them while looping?
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1 },
{ name: TestPane, props: { data: "bye" }, id: 2 },
],
]
<div v-for="component in componentData" :key="component.id">
<component v-bind:is="component.name" v-bind="component.props">
</component>
</div>

You can use the v-on directive. Let's understand how Vue bind your event listeners to the component first:
When you add a #input to a componnet what you are actualy doing is v-on:input. Did you notice the v-on over there? This means that you are actually passing an 'object of listeners' to the component.
Why not pass all of them in one go?
<template>
<section>
<div v-for="component in componentData" :key="component.id">
<component v-bind:is="component.name" v-bind="component.props" v-on="component.on">
</component>
</div>
</section>
</template>
<script>
export default {
data: () => ({
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1, on: { input: (e) => { console.log(e) } } },
{ name: TestPane, props: { data: "bye" }, id: 2, on: { input: (e) => { console.log(e); } } },
],
})
}
</script>
As you could guess you can listen to the events now inside of on object. You can add more if you would like as well:
{
name: TestPane,
props: { data: "hello" },
id: 1,
on: {
input: (e) => { console.log(e) },
hover: (e) => { console.log('This component was hovered') }
}
}

Add method names to your array like :
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1, method:"method1" },
{ name: TestPane, props: { data: "bye" }, id: 2 ,method:"method2"},
],
in template :
<component ... #click.native="this[component.method]()">
or add another method called handler which runs the appropriate component method :
<component ... #click.native="handler(component.method)">
methods:{
handler(methodName){
this[methodName]();
}
...
}
if the events are emitted from components, you should add their names and bind them dynamically :
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1,event:'refresh', method:"method1" },
{ name: TestPane, props: { data: "bye" }, id: 2 ,event:'input',method:"method2"},
],
<component ... #[component.event]="handler(component.method)">

Related

How to run a method using v-for in Vue.js?

I want to get the following output for the following data.
・3
・1
and sample data :
export const dummyData = [
{
id: "1",
name: "a",
sub: [
{
id: "1#1",
name: "b",
sub_sub: [
{ id: "1#1#1", name: "b-a" },
{ id: "1#1#2", name: "b-b" },
]
},
{
id: "1#2",
name: "c",
sub_sub: [
{ id: "1#2#1", name: "c-a" },
]
},
]
},
{
id: "2",
name: "d",
sub: [
{
id: "2#1",
name: "e",
sub_sub: [
{ id: "1#2#1", name: "e-a" },
]
}
]
},
]
I want to count how many elements of sub_sub are includes in object "a" and "d".
So, I made the following code.
<template>
<div>
<ul>
<li v-for="item in items" :key="item.i">{{rowSpanCalc(item.id)}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { dummyData } from '~/store/dummy'
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
</script>
I ran my code and got an error in console like
  
  item.sub.reduce is not a function
Could anyone please advise me how to fix this errors?
Methods in the template are used as events handler not for rendering, try to use that method inside a computed property then use that property for render your items :
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
get customItems(){
return this.items.map(item=>({...item,count:this.rowSpanCalc(item.id)}))
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
template :
...
<li v-for="item in customItems" :key="item.id">{{item.count}}</li>
...

How to use conditional rendering with Vue.js slots?

How can I use the result of a v-if to render two different components in Vue? If it matters, I'm also trying to do this while rendering a TreeView using v-for. Here's what I tried so far.
TreeView.vue:
<template>
<span>
<ul v-show="isOpen" v-if="isFolder">
<p
v-for="(child, index) in item.children"
v-bind:item="child"
v-bind:key="index"
<span v-if="!child.isFolder">
<slot v-bind:individualBookmark="child"></slot>
</span>
<span v-else>
<div v-bind:class="{bold: isFolder}" v-on:click="toggle" v-on:dblclick="makeFolder" v-on:contextmenu="rightClick">
{{ child.name }}
<span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
</div>
</span>
</p>
</ul>
</span>
</template>
<script>
export default {
name: "TreeView",
props: {
item: Object
},
components: {
},
data: function() {
return {
isOpen: false
}
},
computed: {
isFolder: function() {
return this.item.children.length > 0;
//return this.item.children && this.item.children.length;
}
},
methods: {
toggle: function() {
if (this.isFolder) {
this.isOpen = !this.isOpen;
}
},
makeFolder: function() {
if (!this.isFolder) {
this.$emit("make-folder", this.item);
this.isOpen = true;
}
},
rightClick: function() {
alert("Right click action")
}
}
}
</script>
App.vue (the Bookmark tag is a custom component I built that does not have the isFolder computed property):
<template>
<div id="app">
<TreeView v-slot:default="slotProps"
v-bind:item="treeData"
v-on:make-folder="makeFolder"
v-on:add-item="addItem">
<Bookmark v-bind="slotProps.individualBookmark"></Bookmark>
</TreeView>
</div>
</template>
<script>
import Bookmark from './components/Bookmark.vue'
import TreeView from './components/TreeView.vue'
import Vue from 'vue'
export default {
name: 'App',
components: {
Bookmark,
TreeView
},
data: function() {
return {
treeData : {
name: "My Tree",
children: [
{ name: "hello" },
{ name: "wat" },
{
name: "child folder 1",
children: [
{
name: "child folder",
children: [{ name: "hello" }, { name: "wat" }]
},
{ name: "hello" },
{ name: "wat" },
{
name: "child folder",
children: [{ name: "hello" }, { name: "wat" }]
}
]
}
]
}
}
},
methods: {
makeFolder: function(item) {
Vue.set(item, "children", []);
this.addItem(item);
},
addItem: function(item) {
item.children.push({
name: "new stuff"
});
}
}
}
</script>
When I run this, for some reason, the v-if="!child.isFolder" in TreeView.vue always evaluates to true so it renders the Bookmark component. Even in the case when the v-if should be evaluating to false like when given the "child folder 1" child object of treeData, it still evaluates to true. I have a feeling the problem is to do with how I'm using <slot></slot> in TreeView.vue but I'm not sure. Why is the v-if always evaluating to true and never false?
The problem may also have to do with how I wrote the isFolder property.
Add a computed property called computedChildren inside the Treeview component which maps the item prop by adding isFolder property :
computed: {
isFolder: function() {
return this.item.children.length > 0;
//return this.item.children && this.item.children.length;
},
computedChildren(){
return this.item.children.map(child=>{
child.isFolder=child.children?child.children.length>0:false //this add the property isFolder
return child
})
}
},
Then loop through it as follows :
<p
v-for="(child, index) in computedChildren"
...
For replacing the slot you could use template element :
<TreeView
v-bind:item="treeData"
v-on:make-folder="makeFolder"
v-on:add-item="addItem">
<template v-slot:default="slotProps">
<Bookmark v-bind="slotProps.individualBookmark"></Bookmark>
</template>
</TreeView>

How to check whether a v-checkbox is selected in Vue JS?

I have a checkbox with the following events and props:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="selectedPlanets"
/>
Given that all of the checkboxes are brought in using a v-for, how can I check whether a checkbox is selected using a method or mounted function in Vue JS?
For example:
methods: {
checkSelected() {
????
},
Add a planets.selected key.
allPlanets: [
{ name: 'Planet name', value: 'Planet value' , selected : false },
...,
...
],
}
And in your template:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="planets.selected"
/>
Similar to:
Display multiple checkbox in table format using <v-for> and <v-checkbox> in Vuetify?
you have to make such a structure, so you know for each id, whether it is checked or not
new Vue({
data: () => ({
allPlanets: [
{
id: 32,
name: "planent",
selected: false
},
{
id: 365,
name: "planet 2",
selected: false
}
],
}),
methods: {
checkSelectedByIndex(index) {
return this.allPlanets[index].selected
},
checkSelectedById(id) {
return this.allPlanets.find(p => p.id === id)?.selected ?? false
}
}
});
and in the you have to set the v-model="planets.selected"
Given your layout the simplest methods is:
methods: {
checkSelected(id) {
return this.selectedPlanets.includes(id)
},
}

vue.js push array component

1.vue.js problem component
i want insert a new message in new array with method on click event but
not work for me because function is incomplete
where is the problem.
help me please.
<div class="col-lg-12">
<h1>{{message.title}}</h1>
<h4>{{message.subtitle}}</h4>
</p> {{message.body}}</p>
<button v-on:click="newMessage">Reverse Message</button>
</div>
import {
VueTabs,
VTab
}
from "vue-nav-tabs";
import "vue-nav-tabs/themes/vue-tabs.css";
export default {
components: {
VueTabs,
VTab
},
data() {
return {
title: "elenco",
messages: [{
id: 1,
title: "titolo",
subtitle: "sottotitolo",
body: "argomento",
author: "Amedeo",
date: "17/07/2017",
files: [{
id: 1,
title: "Allegatoriunione",
openfile: "Allegato.pdf"
}, ],
methods: {
newMessage: function() {
this.message.title = this.message.title
.push("")
.split("")
.reverse()
.join("");
}
Your Code Contains many syntax errors which probably fails silently.
Try this new updated code:
<script>
import { VueTabs, VTab } from 'vue-nav-tabs'
import 'vue-nav-tabs/themes/vue-tabs.css'
export default {
components: { VueTabs, VTab },
data() {
return {
title: 'elenco',
messages: [
{
id: 1,
title: 'titolo',
subtitle: 'sottotitolo',
body: 'argomento',
author: 'Amedeo',
date: '17/07/2017',
files: [
{
id: 1,
title: 'Allegatoriunione',
openfile: 'Allegato.pdf'
}
]
}
]
}
},
methods: {
newMessage() {
this.message.title = this.message.title
.push('')
.split('')
.reverse()
.join('')
}
}
}
</script>

Multiple select Vue.js and computed property

I'm using Vue.js 2.0 and the Element UI library.
I want to use a multiple select to attribute some roles to my users.
The list of all roles available is received and assigned to availableRoles. Since it is an array of object and the v-model accepts only an array with value, I need to extract the id of the roles trough the computed property computedRoles.
The current roles of my user are received and assigned to userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}].
computedRoles is then equals to [1,3]
The preselection of the select is fine but I can't change anything (add or remove option from the select)
What is wrong and how to fix it?
http://jsfiddle.net/3ra1jscx/3/
<div id="app">
<template>
<el-select v-model="computedRoles" multiple placeholder="Select">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}]
}
},
computed : {
computedRoles () {
return this.userRoles.map(role => role.id)
}
}
}
I agree mostly with #wostex answer, but he doesn't give you the userRoles property back. Essentially you should swap computedRoles and userRoles. userRoles becomes a computed property and computedRoles is a data property. In my update, I changed the name of computedRoles to selectedRoles.
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
selectedRoles:[1,2]
}
},
computed : {
userRoles(){
return this.availableRoles.reduce((selected, role) => {
if (this.selectedRoles.includes(role.id))
selected.push(role);
return selected;
}, [])
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
And here is the fiddle.
Check the solution: jsfiddle
The caveat here is that computed properties are getters mainly. You can define setter for computed property, but my approach is more vue-like in my opinion.
In short, instead of v-model on computed set v-model for data property.
Full code:
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<template>
<el-select v-model="ids" multiple placeholder="Select" #change="logit()">
<el-option v-for="item in availableRoles" :label="item.name" :value="item.id">
</el-option>
</el-select>
</template>
</div>
var Main = {
data() {
return {
availableRoles: [{
id: 1,
name: 'Admin'
}, {
id: 2,
name: 'Power User'
}, {
id: 3,
name: 'User'
}],
userRoles: [{'id':1, 'name':'Admin'}, {'id':3, 'name':'User'}],
ids: []
}
},
mounted() {
this.ids = this.userRoles.map(role => role.id);
},
methods: {
logit: function() {
console.log(this.ids);
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

Categories

Resources