closure within v-for, attribute interpolation - vue.js

I have this basic setup
<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent(n)"></some-component>
</div>
the on-some-event is dispatched within some-component. but I need to know which of these components sent the message. with the setup above, only n is passed into the method. and the data that the event sends is nowhere.
I'd like to interpolate the function so that the method looks like this
onSomeEvent(n){
return (obj)=>{
console.log(`component ${n} sent ${obj}`);
};
}
but wrapping onSomeEvent with {{}} throws a warning: attribute interpolation is not allowed in Vue.js directives and special attributes.
I could just pass the n index into the component but that seems less elegant because I may not have the ability to modify some-component
I am somewhat new to Vue, so perhaps I am missing some core functionality for this type of thing?

<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent | pass n"></some-component>
</div>
....
filters: {
pass(handler, n) {
return function() {
handler()(n, ...arguments)
}
}
},
methods: {
onSomeEvent() {
console.log(...arguments)
}
}
https://jsfiddle.net/2s6hqcy5/2/

You didn't miss anything, the message is correct, in Vue, you won't be able to use interpolation like that.
http://vuejs.org/guide/syntax.html#Interpolations
However, you may want to change how you manage events and pass data between components. In your example, you can just bind the data like this:
<div v-for="n in 4">
<some-component :n="n"></some-component>
</div>
In your component, declare the prop:
Vue.component('some-component', {
props: ['n'],
...
Now, inside each component, you have the n available like any other property (http://vuejs.org/guide/components.html#Props).
Then when dispatching your event, you can call it like this, with no need for a param:
onSomeEvent()
On the event itself, you can access the n:
console.log('Component' + this.n + ...)
https://jsfiddle.net/crabbly/mjnjy1jt/

Related

how can i access data property in html template using loop in vue js

i'm trying to use data property commentsToShow in my html template to limit the amount of data that displays on my webpage
this is my template
<div v-if="index < products.length" v-for="(commentIndex, index) in computedProduct">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</div>
if i add commentsToShow in my for loop i get one product but the computed products doesn't work same way the other way round
this my script tag
<script>
export default {
data() {
return {
commentsToShow: 1,
totalComments: 0,
};
},
computed: {
computedProduct() {
let tempRecipes = this.products;
if (this.filterPrice !== "true");
}
};
</script>
if i change computed property to commentsToShow this the error i get in my console
The computed property "commentsToShow" is already defined in data.
please how can i get the value of commentToShow in my template
according to vue's official docs it's not recommended to use v-for and v-if on the same element
try using v-if on a wrapper div or template element
<div v-for="(commentIndex, index) in computedProduct">
<template v-if="index < products.length">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</template>
</div>
v-if has higher priority so it's executed first and index will not be defined yet.
also you have to return something on your computed property function in order to use it
You can use the slice method on a computed property, like this:
<script>
export default {
data() {
return {
commentsToShow: 1,
allComments: []
};
},
computed: {
listComments() {
return allComments.slice(0, commentsToShow);
}
};
</script>
You can also use pages to show the comments, in this case you can return like this:
return allComments.slice((currentPage - 1) * commentToShow, commentsToShow);
The first argument of slice is the start index, the second is the number of elements to get
The computed property "commentsToShow" is already defined in data.
Equivalently how you cannot have more than one variable with the same name defined in a scope. A computed property cannot have the same name as an existing data property. Essentially, they co-exist in the same namespace, thus they have to be unique.
You have a name clash, and that is what the error is saying.

Vue : Custom parameter [duplicate]

I pass a parameter in v-on:input directives. If I don't pass it, I can access the event in the method. Is there any way I can still access the event when passing the parameter to the function? Note that I am using vue-router.
This is without passing the parameter. I can access the event object:
HTML
<input type="number" v-on:input="addToCart" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (event) {
// I need to access the element by using event.target
console.log('In addToCart')
console.log(event.target)
}
}
This is when passing the parameter. I can't access the event object:
HTML
<input type="number" v-on:input="addToCart(ticket.id)" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (id) {
// How can I access the element by using event
console.log('In addToCart')
console.log(id)
}
}
Here is a fiddle of the code, it should be good enough to replicate the problem that I am having:
https://jsfiddle.net/lookman/vdhwkrmq/
If you want to access event object as well as data passed, you have to pass event and ticket.id both as parameters, like following:
HTML
<input type="number" v-on:input="addToCart($event, ticket.id)" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (event, id) {
// use event here as well as id
console.log('In addToCart')
console.log(id)
}
}
See working fiddle: https://jsfiddle.net/nee5nszL/
Edited: case with vue-router
In case you are using vue-router, you may have to use $event in your v-on:input method like following:
<input type="number" v-on:input="addToCart($event, num)" min="0" placeholder="0">
Here is working fiddle.
You can also do something like this...
<input #input="myHandler('foo', 'bar', ...arguments)">
Evan You himself recommended this technique in one post on Vue forum. In general some events may emit more than one argument. Also as documentation states internal variable $event is meant for passing original DOM event.
Depending on what arguments you need to pass, especially for custom event handlers, you can do something like this:
<div #customEvent='(arg1) => myCallback(arg1, arg2)'>Hello!</div>
I like this way of passing parameters because we can pass events and parameters both using an anonymous callback function.
<button v-on:click="(e)=>deleteHandler(e, args1)" > Click me </button>

VueJS - reusing prop (field name) at definition of other bindigns

I don't know how to exactly name this, I have some sort of forms in Vue which consist of different input types or more complex wrapped elements or even custom components. They all have one thing in common though - their fieldName which is used for checking various stuff such as validation, class binding etc.
Example:
<div class="field">
<div class="label">Product name</div>
<input :value="productName" #input="valueChanged" :class="{ changed: isChanged('productName') }" data-field="productName">
</div>
As you can see, productName is repeated 3 times in just a single line. I use it in dataset so valueChanged method (a global mixin) knows that field name has changed, then in the class binding to check if value has changed to style it properly, and next for the value binding itself.
It grows bigger and bigger as I want to add for example another class binding like error: hasErrors('productName')
Is there any way to define the field name once and re-use it in other bindings? It would still require some repetition, but at least changing the field name in the future would be just one change instead of 4-5. Something like this:
<input :fName="productName" :value="fName" #input="valueChanged" :class="{changed: isChanged(fName), error: hasErrors(fName)" :data-field="fName">
I know that wrapping it in some custom component would probably be one way, but that would require a lot of different conditions to render things correctly as I'm using various field types with different structures. And I would need to re-write half of my app.
Here are two potential solutions:
Use v-bind and generate an object with all of the attributes you need
<input v-bind="getAttrs('productName')" #input="valueChanged" />
...
methods: {
getAttrs(fieldName) {
return {
value: this[fieldName],
class: {
changed: this.isChanged(fieldName),
error: this.hasErrors(fieldName)
},
'data-field': fieldName
}
}
}
Store all the fields in a variable and loop through them:
<div
v-for="field in fields"
class="field"
>
<div class="label">
Product name
// Or e.g. {{ field.label }} if you have an array of field objects
</div>
<input
:value="getValue(field)"
:class="{ changed: isChanged(field), error: hasErrors(field) }"
// Still need to use v-bind to generate the data attribute on the fly
v-bind="getDataAttr(field)"
#input="valueChanged"
/>
</div>
...
data() {
return {
fields: [
'fieldName1',
'fieldName2',
'fieldName3'
],
}
},
methods: {
getValue(fieldName) {
return this[fieldName];
},
getDataAttr(fieldName) {
return {
'data-field': fieldName
}
}
}
I believe that having custom component handling input fields is the smartest way. I don't think, there would be that much conditions and even if there is some workaround, you will still need to rewrite half of your app. You may use input type as prop, so you can use component for different field types and if there are much different structures, you can use slots, to add some custom structure.

How dynamic add events to a tag in my custom grid component

<template>
<tbody>
<template v-for="(row,index) in datalist">
<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">
<td v-if="col.show" v-for="col in collist" #click="eventbus(row,$event)" #mouseover="eventbus(row,$event)">
<template v-if="col.type">
<component v-for="com in col.type" :is="com" :rowdata="row" :colname="col.colname"
:tdcbfun="col.cbfun"></component>
</template>
<template v-else>{{ row[col.colname] }}</template>
</td>
</tr>
</template>
</tbody>
</template>
```
now a question
`<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">`
how can i add events by data (props) ? dynamic v-on?
i don't want to write #click #mouseover #.......
i want like this ....
```
props: {
trevent: [{event:'click',eventfun:function (rowdata) {
if(rowdata.age<10){ //#:click=eventfun(rowdata)
alert('children')
}
}},{event:'mouseover',eventfun:function (rowdata) {
if(rowdata.age<10){//#mouseover=eventfun(rowdata)
tip('children')
}
}}]
}
```
another example button component
```
<template>
<div>
<button #click="eventbus" #mouseover="eventbus">{{options.btnname}}</button>
</div>
</template>
methods: {
eventbus: function (rowdata, event) {
var eventname = event.type
var eventpos = event.currentTarget.localName
this.$root.$emit(eventpos + eventname, rowdata)
}
}
vm.$on('trclick',function(){
.......do something
})
```
if sometime emit not $on dont do it ...this kind of settlement so .....
and i also can use component :is but javaer must write component so much
oh v-if
Sorry for my english..
终于可以写中文了
我们公司正在开发一个公共组件,刚开始做现在正在做表格的组件。
这个组件是通用的,想用在公司的不同的系统上,也是开源的。
麻烦大家帮看看 现在如何可以 根据传入的props 数据 ,动态添加事件到某个标签上?
我找不到办法动态添加v-on
想做的功能多一些 还不想总让研发人员写动态的component
我尽量将vue封装成 jquery那种调用形式,大家都比较容易会。
其次是我现在在mainjs 里把vue写好的组件暴露出来window.$grid = grid.vue 然后在引入webpack打包好的js
然后直接使用 请问还有其他更好的关于把vue做成组件在外部调用的例子吗?
还有如果我这种方式引用的话 是否还能使用vue-router ? 最好给个例子
最近半个月狂看Vue 在此感谢下尤大弄出这么好的东西!
借这里给大家拜个早年,祝各位在新的一年里身体健康,生活幸福!
英语不好麻烦各位了
One possible approach could be using the special propr ref and adding the event listener in mounted lifecycle. since it is added manually, you may want to remove it too, so I would add it in beforeDestroy life cycle.
Set the ref to the tag
<tr ref="my-tag" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">
Add and Remove the event in the livecycles
mounted() {
this.$refs['my-tag'].addEventListener(this.myEvent,() => {
// Some logic..
});
},
beforeDestroy() {
this.$refs['my-tag'].addEventListener(this.myEvent,() => {
// Some logic..
});
}
It may not be the nicer approach but would do the trick.
It is maybe not the best concept to modify the event listeners of a components DOM afters it was compiled. If found this quote from Evan You (creator of vuejs) here:
I think you are approaching this with wrong assumptions. Templates for a component is static, once it's defined you can't change it. You need to express the parts that may be changed inside the template.
It is possible to recompile a component template as Elfayer shows here, but it does not improve elegancy for this problem since one has to provide a template for every configuration of the properties. For one event attribute it's no problem, you would need two templates. But for three events you would already need 8 templates and so on...
Option 1: Handle logic within normal event handlers
Use normal event handlers which perform conditional execution of dynamic event listeners.
In your template you could replace
<template>
...
<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % ...
...
</template>
with:
<template>
...
<tr #click="tr_handler(row,this.$el)" :class="index % ...
...
</template>
and then use the tr_handler() method to check whether there is an event listener assigned to a certain property or not:
methods: {
//...
tr_handler: function(row,e) {
if (this.rowevent) {
return this.rowevent(row, e)
}
}
//...
}
This approach provides a clean structure and keeps the string template feature of vuejs.
Option 2: Use a render() function
One can render the whole template dynamically by using a render function. Also it is possible to apply event listeners to the nodes as described within the latter link:
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
`~!mouseover`: this.doThisOnceInCapturingMode
}
Both approaches do not avoid declaring the event within the template.
Here is some statement about this which explains how things are done in vue-world.
Since you don’t have to manually attach event listeners in JS, your
ViewModel code can be pure logic and DOM-free. This makes it easier to
test.

Use global functions in vue directives

I'm trying to use lodash methods (_.isEmpty) in vue directives like this:
<div class="post" v-for="post in posts"></div>
...
<div class="comments" v-if="! _.isEmpty(post.comments)">
<div class="comment" v-for="comment in post.comments"></div>
</div>
...
</div>
but getting the following error:
Uncaught TypeError: Cannot read property 'isEmpty' of undefined
It seems vue is looking for the _.isEmpty method inside the current scope. How should I call global functions in this case?
You can only access functions of the current Vue instance/component in a template:
data
props
methods
No "third-party" code can be run.
So, you would have to create a method in the Vue component to proxy to the lodash methods:
methods: {
isEmpty: function (arr) { return _.isEmpty(arr)}
}
and use this method in the template instead:
<div class="comments" v-if="! isEmpty(post.comments)">
Why not just add _ to your Vue component:
data(){
return {
_:require('lodash') //or however you include it. maybe just window._
}
}
Then it would be accessible. Not positive if _ is a valid object key, so might just call it lo or lodash if needed.
Also, assuming that comments is an array, there would be no problem using v-if='post.comments.length'. Lo-dash is great but unnecessary if you already know it's an array.