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

<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.

Related

How can I rerender my vue template after changing v-for list?

So I'm trying to change a list based on a whether the elements are considered active or not. I do this through a computed data array. Basically a Search Function. However my template does not rerender and update automatically, even though I try to force it with this.forceUpdate().
This is my v-for in template:
<ion-list>
<div v-for="project in activeProjects" :key="project">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>
This is my computed array. The Log returns the correct things.
computed: {
activeProjects: function() {
return this.myprojects.filter(function(u){
console.log(u);
return u.active
})
}
}
And this is where I update the activity. The Log also returns the correct things.
search: function(){
for(var i=0; i<this.myprojects.length; i++){
if(this.myprojects[i].name.includes(this.searchinput)){
this.myprojects[i].active=true;
console.log(this.myprojects[i])
}
}
this.$forceUpdate();
}
Grateful for any help
I understand what you're attempting with the $forceUpdate, but I'm not certain that's the intended behavior here. In particular, by directly modifying the property of an Object in an Array, I believe Vue is missing the changes completely, so it doesn't know what to forceUpdate.
(See these links to read more on when Vue does / doesn't recognize mutations to Objects and Arrays)
TBH I've never attempted to use forceUpdate in this way, but I have done some Array mutation in a spreadsheet-like scenario before and it was a pain... I would avoid it if at all possible.
Rather than modifying a property in the array, I'd compute the filter on-the-fly using a method. You should get the reactivity you want because you're calculating, not mutating, the properties of the list of projects.
<script>
export default {
props: ['myprojects'],
data() {
return {
searchinput: ''
}
},
computed: {
activeProjects() {
return this.myprojects.filter(this.isInSearch)
}
},
methods: {
isInSearch(project) {
return project.name.includes(this.searchinput)
}
}
}
</script>
Vue caches nodes based on :key value. You're passing the entire object, you should be using a unique property on your project.
Try yo use name or an unique id if you have one.
<ion-list>
<div v-for="project in activeProjects" :key="project.name">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>```

Callback after list leave animation and DOM update - VueJS

To do something after the DOM has been updated by Vue, you use the $nextTick binding.
To do something after a css transition has completed, you can use transitionend event.
I have a dynamic list in which things are added and removed by user actions. When removed, there is a CSS animation and then I need to check the state of the DOM immediately after the element is gone.
I was thinking that the $nextTick after the transitionend would be the state of the DOM immediately after the list item is removed, but it is not.
I need to do something after the transition has ended and the element from a list has been removed from the DOM.
Right now I have:
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>
dismissMessage(message){
const vm = this;
this.$refs[message.id][0].addEventListener("transitionend", function(){
vm.$nextTick(function(){
//This is called, but appears to be called before the element is actually removed from the DOM
//I need to query the DOM immediately after this element is removed
});
});
this.messages.splice(this.messages.indexOf(message), 1);
}
In the mounted function, I have added a MutationObserver that appears to be working as needed. I'll put this here as an answer as it does technically work and may be helpful to others, but I'm still interested in a better answer if Vue has something built in for this.
mounted(){
const vm = this;
const listItemRemoved = new MutationObserver(function(e){
if (e[0].removedNodes.length){
console.log("Removed");
}
});
listItemRemoved.observe(this.$el, {childList: true});
}
Perhaps you could use a custom directive. Perform the actions you need inside the unbind hook ..
created() {
this.vm = this
},
directives: {
foo: {
unbind(el, binding) {
// Here you can perform the actions you need.
// You can access the Vue instance using binding.value (eg: binding.value.$el)
}
}
},
And in your template ..
<transition-group class="message-bus" tag="ul" name="message-bus">
<li v-for="message in messages" v-bind:key="message.id" :ref="message.id" v-foo="vm">
<div>{{message.text}}</div>
<a class="dismiss-message" #click="dismissMessage(message)">×</a>
</li>
</transition-group>

Cannot get DOM events on component in host component

I have a Vue component that contains a list of objects named lines. I build a table from those lines using different components based on the line type. This works perfectly. Here's a stripped down version of the component:
<template>
<table>
<tr v-for="line in lines"
:key="line.key"
:is="componentForType[line.eventType] || 'LogLine'"
v-bind="line"
/>
</table>
</template>
<script>
export default {
name: 'DebugLog',
components: {
LogLine,
FormattedLogLine,
UserDebug,
Limits
},
data () {
return {
lines: [],
selectedKey: null,
componentForType: {
'USER_DEBUG' : 'UserDebug',
'LIMIT_USAGE_FOR_NS' : 'Limits',
'EXCEPTION_THROWN' : 'FormattedLogLine',
'FATAL_ERROR' : 'FormattedLogLine'
}
}
},
mounted() {
// code that loads this.lines
}
}
</script>
Now I want to be able to click any row of the table, and have the row become "selected", meaning that I want store line.key in this.selectedKey and use CSS to render that line differently. But I can't get the events working. Here's the updated <template>; nothing else is changed:
<template>
<table>
<tr v-for="line in lines"
:key="line.key"
:is="componentForType[line.eventType] || 'LogLine'"
v-bind="line"
:class="{selected: line.key == selectedKey}"
#click.capture="selectedKey = line.key"
/>
</table>
</template>
I've added the last 2 properties on the tr element - a dynamic class binding and a click event handler to set this.selectedKey to the active line's key. But it isn't working. I replaced the #click handler code with console.log(line.key) and nothing is logged, which tells me that my #click handler is never firing. I originally wrote it with out the .capture modifier, but tried adding the modifier when the original didn't work.
Is vue.js stopping propagation from the child component to the parent? Can I not bind the click event on the tr since it :is another vue component? Or is there something else going on? The examples I've found in the docs are much simpler and I'm not sure they correspond to my situation. The various child components are not binding any click events. I'd prefer to handle the event entirely in the parent as shown, since I will have a number of types of child component, and I don't want to have to implement click handlers in each.
Update: Looking at my child components, I note that each contains a tr tag that must effectively replace the tr in the parent template. For example, my most basic component is LogLine, shown here:
<template>
<tr>
<td>{{timeStamp}}</td>
<td>{{eventType}}</td>
<td>{{lineNumber}}</td>
<td>{{lineData}}</td>
</tr>
</template>
<script>
export default {
name: 'LogLine',
props: ['timeStamp', 'eventType', 'lineData', 'lineNumber'],
data: function () {
return {}
}
}
</script>
So I'm guessing that the binding in the parent isn't actually binding on the tr in the DOM; it's just binding on the Vue component, listening for a click event to be sent from the child with $emit; and that each child component will need to bind #click on its tr and emit it to the parent. Assuming I'm right, is there any shortcut I can use from the parent template to have vue forward the DOM events? Any other option I'm missing besides binding click in every child component?
Piggy-backing off of Jacob's answer here. Since you're essentially attaching an event listener to a dynamic component it expects a custom click event. So you have two options here:
Listen for the native DOM click event within that component (by attaching a click event listener to a normal DOM element within the component) and emit a custom click event to the parent.
Use the .native modifier to listen for the native DOM click event instead of a custom one directly in the parent.
Since you are using an :is prop, it's considered a dynamic Vue component, not a DOM element.
Events listener on a Vue component won't be passed down to its DOM element by default. You have to do it manually by going into the component template and add v-on="$listeners".
demo: https://jsfiddle.net/jacobgoh101/am59ojwx/7/
e.g. <div v-on="$listeners"> ... </div>
#Jacob Goh's use of v-on="$listeners" is simple and allows forwarding of all DOM events in one action, but I wanted to document an approach I tried on my own for completeness. I will be switching to Jacob's solution in my component. I am now using Husam's .native modifier in the parent as it is more suitable to my particular use case.
I was able to make my component work by editing each child component, capturing the click event and re-emitting it. For example:
<template>
<tr #click="$emit('click')">
<td>{{timeStamp}}</td>
<td>{{eventType}}</td>
<td>{{lineNumber}}</td>
<td>{{lineData}}</td>
</tr>
</template>
<script>
export default {
name: 'LogLine',
props: ['timeStamp', 'eventType', 'lineData', 'lineNumber'],
data: function () {
return {}
}
}
</script>

VueJs - preventDefault() on form submission

I need to submit a form programmatically, but I need it to preventDefault as well.
Right now I have the following:
submit() {
this.$refs.form.submit()
}
It is working fine, but I cannot prevent default on the submit which in the end, refreshes the page.
Short answer
You can add the .prevent modifier to the #submit (or any other v-on you're using), like this:
<form #submit.prevent="myMethod">
<button type="submit"></button>
</form>
In the case of submitting a form, this will prevent the default behavior of refreshing the page.
Long answer
There are several ways to modify events.
From the Vue 3 docs:
It is a very common need to call event.preventDefault() or
event.stopPropagation() inside event handlers. Although we can do this
easily inside methods, it would be better if the methods can be purely
about data logic rather than having to deal with DOM event details.
To address this problem, Vue provides event modifiers for v-on. Recall
that modifiers are directive postfixes denoted by a dot.
<!-- the click event's propagation will be stopped -->
<a #click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form #submit.prevent="onSubmit"></form>
<!-- modifiers can be chained -->
<a #click.stop.prevent="doThat"></a>
<!-- just the modifier -->
<form #submit.prevent></form>
<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div #click.capture="doThis">...</div>
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div #click.self="doThat">...</div>
Another option:
Sometimes we also need to access the original DOM event in an inline statement handler. You can pass it into a method using the special $event variable:
<button #click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// now we have access to the native event
if (event) {
event.preventDefault()
}
alert(message)
}
}
Cheers :)
Didn't quite understand #Armin Ayari's answer, for instance why the code would have to be in the methods object? Anyway in Vue this is what worked for me:
<form ref="form" #submit.prevent="myMethod">
<button type="submit"></button>
</form>
This blocked the page from refreshing and called myMethod instead.
You don't even need the ref. Understood this is an old question, but I found myself here after debugging, and found my form tags were simply mis-placed.
I don't know if I understood your question correctly but you can prevent the default behavior of your form like this:
this.$refs.form.addEventListener("submit", (event) => {
event.preventDefault()
});
Maybe this can help you:
new Vue({
el: '#app',
data: {},
methods: {
submit () {
this.$refs.form.addEventListener('submit', event => {
event.preventDefault()
})
},
alert () {
alert('hello')
}
}
})
<body>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
<div class="form-wrapper" #click='submit'>
<form ref='form' #submit='alert'>
<input type="text">
<button type='submit'>Submit</button>
</form>
</div>
</div>
</body>
I think it's dona help!!
<form method="POST" action="http::localhost:8080/" #submit.prevent="submit_login($event)">
// enter yours inputs here
</form>
submit_login(e) {
if (true) {
e.target.submit();
},
},
After some proper investigation with not a single answer here being related to the original question.
I have found your solution, however it isn't VueJS specific, referencing this article: Javascript e.preventDefault(); not working on submit()
Answer
Your programmatic way to execute submit this.$refs.form.submit() isn't correct if you want to properly preventDefault() or even run other functions.
You need to run this.$refs.form.requestSubmit(), this replicates the functionality as if you would've had a child <button> run the clicked event.
First, don't use preventDefault method. I will illustrate this problem on jQuery:
$('#myForm').on('submit', function (event) {
// step 1.
// stop current action, prevent submitting
event.preventDefault()
// step 2.
// validate inputs
// some validation code
// step 3.
// everything ok, submit it
this.submit()
})
Where is problem with this code? When you submit this form programatically in step 3., it will be again captured and will stop at step 1. again. So, you will be never able to submit this form. Solution:
$('#myForm').on('submit', function (event) {
// step 1.
// validate inputs
// some validation code
// this code will be always executed
// before this form will be submitted
// step 2.
// then do something like this
// continue submitting form and exit
// this callback with returning true
if (inputsAre === 'ok') return true
// if inputs are not ok, program continues
// with following line, which ends this
// callback with false and form will be not submitted
return false
})
I hope you got the point. So, I think what you need is not the preventDefault method, but return true or return false in your doSomething method called on #submit event.

closure within v-for, attribute interpolation

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/