How can I call multiple functions in a single #click? (aka v-on:click)?
So far I tried
Splitting the functions with a semicolon: <div #click="fn1('foo');fn2('bar')"> </div>;
Using several #click: <div #click="fn1('foo')" #click="fn2('bar')"> </div>;
and as a workaround, I can just create a handler:
<div v-on:click="fn3('foo', 'bar')"> </div>
function fn3 (args) {
fn1(args);
fn2(args);
}
But sometimes this isn't nice. What would be the proper method/syntax?
On Vue 2.3 and above you can do this:
<div v-on:click="firstFunction(); secondFunction();"></div>
// or
<div #click="firstFunction(); secondFunction();"></div>
First of all you can use the short notation #click instead of v-on:click for readability purposes.
Second You can use a click event handler that calls other functions/methods as #Tushar mentioned in his comment above, so you end up with something like this :
<div id="app">
<div #click="handler('foo','bar')">
Hi, click me!
</div>
</div>
<!-- link to vue.js !-->
<script src="vue.js"></script>
<script>
(function(){
var vm = new Vue({
el:'#app',
methods:{
method1:function(arg){
console.log('method1: ',arg);
},
method2:function(arg){
console.log('method2: ',arg);
},
handler:function(arg1,arg2){
this.method1(arg1);
this.method2(arg2);
}
}
})
}());
</script>
If you want something a little bit more readable, you can try this:
<button #click="[click1($event), click2($event)]">
Multiple
</button>
To me, this solution feels more Vue-like hope you enjoy
updated dec-2021
you need to separate with a comma
like this:
<button #click="open(), onConnect()">Connect Wallet</button>
to add an anomymous function to do that may be an alternative:
<div v-on:click="return function() { fn1('foo');fn2('bar'); }()"> </div>
Separate into pieces.
Inline:
<div #click="f1() + f2()"></div>
OR: Through a composite function:
<div #click="f3()"></div>
<script>
var app = new Vue({
// ...
methods: {
f3: function() { f1() + f2(); }
f1: function() {},
f2: function() {}
}
})
</script>
This simple way to do
v-on:click="firstFunction(); secondFunction();"
This works for me when you need to open another dialog box by clicking a button inside a dialogue box and also close this one. Pass the values as params with a comma separator.
<v-btn absolute fab small slot="activator" top right color="primary" #click="(addTime = true),(ticketExpenseList = false)"><v-icon>add</v-icon></v-btn>
in Vue 2.5.1 for button works
<button #click="firstFunction(); secondFunction();">Ok</button>
I just want to add one small missing bit here which I felt missing in all of the answers above; that is you actually need to call the method rather than just passing its name as callable, when want to add multiple click handlers.
This might come as a surprise since Vue allows passing a callable to the click handler.
This works
<div><button #click="foo(); bar();">Button1</button></div>
<div><button #click="foo">Button2</button></div>
This does not
<div><button #click="foo; bar;">Button3</button></div>
JsFiddle example
The Vue event handling only allows for single function calls. If you need to do multiple ones you can either do a wrapper that includes both:
<div #click="handler"></div>
////////////////////////////
handler: function() { //Syntax assuming its in the 'methods' option of Vue instance
fn1('foo');
fn2('bar');
}
EDIT
Another option is to edit the first handler to have a callback and pass the second in.
<div #click="fn1('foo', fn2)"></div>
////////////////////////////////////
fn1: function(value, callback) {
console.log(value);
callback('bar');
},
fn2: function(value) {
console.log(value);
}
Html:
<div id="example">
<button v-on:click="multiple">Multiple</button>
</div>
JS:
var vm = new Vue({
el: '#example',
data: {
name: 'Vue.js'
},
// define methods under the `methods` object
methods: {
multiple: function (event) {
this.first()
this.second()
}
first: function (event) {
//yourstuff
}
second: function (event) {
//yourstuff
}
}
})
vm.multiple()
Based on ES6 with anonymous functions:
<button #click="() => { function1(); function2(); }"></button>
You can use this:
<div #click="f1(), f2()"></div>
Simply do like below:
with $event:
<div #click="function1($event, param1); function2($event,param1);"></div>
without $event:
<div #click="function1(param1); function2(param1);"></div>
I'd add, that you can also use this to call multiple emits or methods or both together by separating with ; semicolon
#click="method1(); $emit('emit1'); $emit('emit2');"
You can do it like
<button v-on:click="Function1(); Function2();"></button>
OR
<button #click="Function1(); Function2();"></button>
I was also looking this solution and used different methods and I found this one best for me. Just shared with you
***You can use template literals to use multiple function in one event in vuejs
<div #click="`${firstFunction() ${secondFunction() ${thirdFucntion()}`"></div>
Note:I am using vue3.
you can, however, do something like this :
<div onclick="return function()
{console.log('yaay, another onclick event!')}()"
#click="defaultFunction"></div>
yes, by using native onclick html event.
Related
I am novice in VueJs and As I am trying to implement the basic toggle class functionality using v-bind property of VueJs in my Laravel project. I am not getting the value of variable className while rendering of the page. Please guide me where I am doing wrong. The code is given below:
<div id="root">
<button type="button" v-bind:class="{'className':isLoading}" v-on:click="toggleClass">Toggle Me</button>
</div>
JavaScript is:
<script>
var app = new Vue({
el: '#root',
data: {
className:"color-red",
isLoading:false
},
methods:{
toggleClass(){
this.isLoading=true;
this.className="color-blue";
}
}
})
</script>
Style is:
<style>
.color-red{
background-color:red;
}
.color-blue{
background-color:blue;
}
</style>
You're mixing your approaches slightly. The main issue is in v-bind:class="{'className':isLoading}". This directive, the way you wrote it, toggles a class with the name "className" (literally that, not the value of the variable className) to your element if isLoading is true. If it's false, it doesn't assign any class.
Looking at your code, it seems you're actually trying to set two different classes depending on what the value of isLoading is. The easiest way to do this would be to use v-bind:class="isLoading ? 'color-red' : 'color-blue". Take a look at a working example here.
An even better solution that doesn't pollute your template with logic is to move that expression to a computed property, like this.
You can not have className as well as a variable name, as vue expects it as actual CSS class, documentation suggests one more way, have class object like following:
<script>
var app = new Vue({
el: '#root',
data: {
classObj:{ "color-red" : true } ,
isLoading:false
},
methods:{
toggleClass(){
this.isLoading=true;
this.classObj = { "color-blue" : true};
}
}
})
</script>
<div id="root">
<button type="button" v-bind:class="classObj" v-on:click="toggleClass">Toggle Me</button>
</div>
I've got two problems here. The first is that I can't get the star rendered properly. I can do it if I change the value in the data() function but if I want to do it in a function callback way, it doesn't work (see comments below). What's going wrong here? Does it have something to do with Vue's lifecycle?
The second one is that I want to submit the star-rate and the content of the textarea and when I refresh the page, the content should be rendered on the page and replace the <textarea></textarea> what can I do?
I want to make a JSFiddle here but I don't know how to make it in Vue's single-file component, really appreciate your help.
<div class="order-comment">
<ul class="list-wrap">
<li>
<span class="comment-label">rateA</span>
<star-rating :data="dimensionA"></star-rating>
</li>
</ul>
<div>
<h4 class="title">comment</h4>
<textarea class="content" v-model="content">
</textarea>
</div>
<mt-button type="primary" class="mt-button">submit</mt-button>
</div>
</template>
<script>
import starRating from 'components/starRating'
import dataService from 'services/dataService'
export default {
data () {
return {
dimensionA: '' //if I changed the value here the star rendered just fine.
}
},
components: {
starRating
},
methods: {
getComment (id) {
return dataService.getOrderCommentList(id).then(data => {
this.dimensionA = 1
})
}
},
created () {
this.getComment(1) // not working
}
}
</script>
What it seems is scope of this is not correct in your getComment method, you need changes like following:
methods: {
getComment (id) {
var self = this;
dataService.getOrderCommentList(id).then(data => {
self.dimensionA = 1
})
}
},
As you want to replace the <textarea> and render the content if present, you can use v-if for this, if content if available- show content else show <textarea>
<div>
<h4 class="title">comment</h4>
<span v-if="content> {{content}} </span>
<textarea v-else class="content" v-model="content">
</textarea>
</div>
See working fiddle here.
one more problem I have observed in your code is you are using dynamic props, but you have assigned the prop initially to the data variable value in star-rating component, but you are not checking future changes in the prop. One way to solve this, assuming you have some other usage of value variable is putting following watch:
watch:{
data: function(newVal){
this.value = newVal
}
}
see updated fiddle.
Following an answer to my question on debouncing I am wondering if vue.js and lodash/underscore are compatible for this functionality. The code in the answer
var app = new Vue({
el: '#root',
data: {
message: ''
},
methods: {
len: _.debounce(
function() {
return this.message.length
},
150 // time
)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message">Length: <span>{{ len() }}</span>
</div>
indeed holds on the execution of my function when there is continuous input, but when it is finally executed after some inactivity, the input for function() seems to be wrong.
A practical example after starting the code above:
quick sequence of characters, then no activity:
One extra character (b) added, and no activity -- the length is updated (but wrongly, see below)
Erase all the characters with Backspace in a quick sequence:
Add one character:
It looks like the function is ran on the last but one value of message.
Could it be that _.debounce handles the vue.js data before it is actually updated with the <input> value?
Notes:
tested with both lodash and underscore, with the same results (for both debounceand throttle functions).
I also tested it on JSFiddle in case there would be some interference with the SO snippet
Here's an improved version of #saurabh's version.
var app = new Vue({
el: '#root',
data: {
message: '',
messageLen: 0
},
methods: {
updateLen: _.debounce(
function() {
this.messageLen = this.message.length
}, 300)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message" v-on:keyup="updateLen">Length: <span>{{ messageLen }}</span>
</div>
Why this is happening is because Vue invokes methods only when vue variables used in the methods changes, if there are no changes in the vue varaibles, it will not trigger those methods.
In this case as well, once we stop typing, it will continue to show last called method's output and will only show again once you enter the input again.
One alternate approach if you dont want to call an function on all inputs, you can call a mehtod on blur event, so method will be invoked only when focus goes out of input field, like following:
var app = new Vue({
el: '#root',
data: {
message: '',
messageLen: 0
},
methods: {
updatateLen:
function() {
this.messageLen = this.message.length
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message" v-on:blur="updatateLen">Length: <span>{{ messageLen }}</span>
</div>
I have input which I use to filter my array of objects in Vue. I'm using Salvattore to build a grid of my filtered elements, but it doesn't work too well. I think I have to call rescanMediaQueries(); function after my v-model changes but can't figure how.
Here is my Vue instance:
var articlesVM = new Vue({
el: '#search',
data: {
articles: [],
searchInput: null
},
ready: function() {
this.$http.get('posts').then(function (response) {
this.articles = response.body;
});
}
});
And here is how I have built my search
<div class="container" id="search">
<div class="input-field col s6 m4">
<input v-model="searchInput" class="center-align" id="searchInput" type="text" >
<label class="center-align" for="searchInput"> search... </label>
</div>
<div id="search-grid" v-show="searchInput" data-columns>
<article v-for="article in articles | filterBy searchInput">
<div class="card">
<div class="card-image" v-if="article.media" v-html="article.media"></div>
<div class="card-content">
<h2 class="card-title center-align">
<a v-bind:href="article.link">{{ article.title }}</a>
</h2>
<div class="card-excerpt" v-html="article.excerpt"></div>
</div>
<div class="card-action">
<a v-bind:href="article.link"><?php _e('Read More', 'sage'); ?></a>
</div>
</div>
</article>
</div>
I did get the grid system working by adding watch option to my Vue, but every time I wrote something to my input and then erase it my filterBy method wouldn't work at all. It didn't populate any data even if I tried to retype the same keyword as earlier. Here is the watch option I used:
watch: {
searchInput: function (){
salvattore.rescanMediaQueries();
}
}
I think your problem is with the scoping of this in your success handler for http. Your articles object in Vue component is not getting any values from your http.get(..) success handler.
Inside your ready function, your http success handler should be as follows:
this.$http.get('posts').then(response => {
this.articles = response.body; // 'this' belongs to outside scope
});`
Alternatively you can also do:
var self = this; // self points to 'this' of Vue component
this.$http.get('posts').then(response => {
self.articles = response.body; // 'self' points to 'this' of outside scope
});`
Another similar issue: https://stackoverflow.com/a/40090728/654825
One more thing - it is preferable to define data as a function, as follows:
var articlesVM = new Vue({
el: '#search',
data: function() {
return {
articles: [],
searchInput: null
}
},
...
}
This ensures that your articles object is unique to this instance of the component (when you use the same component at multiple places within your app).
Edited after comment #1
The following code seems to work alright, the watch function works flawlessly:
var vm = new Vue({
el: '#search',
template: `<input v-model="searchInput" class="center-align" id="searchInput" type="text" >`,
data: {
searchInput: ""
},
watch: {
searchInput: function() {
console.log("searchInput changed to " + this.searchInput);
}
}
})
The input in template is an exact copy of your version - I have even set the id along with v-model, though I do not see the reason to set an id
Vue.js version: 2.0.3
I am unable to see any further, based on details in the question. Can you check if your code matches with the one above and see if you can get the console debugging messages?
Edited after comment #4, #5
Here is another thought which you need to verify:
Role of vue.js: Render the DOM
Role of salvattore plugin: Make the DOM layouts using CSS only
Assuming the above is true for salvattore plugin, and hopefully it does not mess with vue.js observers / getters / setters, then you can do the following: provide a time delay of about 50 ms so that vue.js completes the rendering, and then call the salvattore plugin to perform the layouts.
So your watch function needs to be as follows:
watch: {
searchInput: function (){
setTimeout(function(){
salvattore.rescanMediaQueries();
}, 50);
}
}
Alternatively you may also use Vue.nexttick() as follows:
Vue.nextTick(function () {
// DOM updated
})
The nextTick is documented here: https://vuejs.org/api/#Vue-nextTick
I do not know if you may need to provide a little bit of extra time for salvattore plugin to start the layouts, but one of the above should work out.
Let me know if it works!
I'm trying to bind a method to an on-tap attribute of a paper-button. After much testing, I've found that I can only bind a (for lack of a better word) top-level function, and not a method of an object in the template.
For example, I have a template, to which I have bound a number of objects, one of which is a user object. Object user has a bunch of methods and variables, like 'isNew' or 'reputation'. The user object also has a method 'addReputation'
I can use the object variables like this :
<template if = '{{user.new}}'><h1>{{user.name}}</h1></template>
And I can bind button taps like this:
<paper-button on-tap='{{addReputation}}'>Add Rep</paper-button>
But not like this:
<paper-button on-tap='{{user.addReputation}}'>Add Rep</paper-button>
Does anyone know why this may be?
if you set the method to a handler on your element's prototype it works. That way you can still keep things dynamic:
<script src="http://www.polymer-project.org/webcomponents.min.js"></script>
<script src="http://www.polymer-project.org/polymer.js"></script>
<polymer-element name="my-element" on-tap="{{tapHandler}}">
<template>
<style>
:host {
display: block;
}
</style>
click me
<content></content>
</template>
<script>
Polymer({
created: function() {
this.user = {
method: function() {
alert('hi');
}
};
this.tapHandler = this.user.method;
}
});
</script>
</polymer-element>
<my-element></my-element>
i'm sharing my plunk to resolve above problem. plunk link
In the template
<button on-tap="{{fncall}}" data-fnname="b">b call</button>
In the script
x.fncall = function(e) {
var target = e.target;
var fnName = target.getAttribute("data-fnname");
return x.datamodel[fnName]();
}
Polymer(x);