v-on with no argument expects an object value - vue.js

I am new to Vue. So I gotta tell ya, what I am doing here is working, but I am also get a dev tools error.
[Vue warn]: v-on with no argument expects an object value.
Now, I get WHY it is telling me this, but when I try to fix it in a couple of ways, I get a number of errors. What I am wondering is how would I resolve this properly. I also want to stress what the code is doing IS working as expected on the frontend. Just not proper Vue I would like to resolve
TEMPLATE
<div v-if="message.type === 'message'">
<chat-message v-on="playMessageNotificationSound()"
:message="message"/>
</div>
<div v-else-if="message.type === 'notification'">
<div v-show="this.$store.state.notifications.enterLeave === 'true'">
<chat-notification v-on="playNotificationSound()" :message="message" />
</div>
</div>
SCRIPT
methods: {
playMessageNotificationSound() {
if (this.$store.state.notifications.messagesound === 'true' ) {
var audio = new Audio('https://chat-cdn.levelupchat.com/chat-sounds/whisper.mp3');
audio.volume = 0.4;
audio.play();
}
},
playNotificationSound () {
if (this.$store.state.notifications.enterLeaveSound === 'true' ) {
var audio = new Audio('https://chat-cdn.levelupchat.com/chat-sounds/message.mp3');
audio.volume = 0.4;
audio.play();
}
},

v-on is a directive to listen to DOM events and run some JavaScript when they’re triggered.
You can't use it empty instead need to bind an event handler .
For example
<button v-on:click="say('hi')">Say hi</button>
where
methods: {
say: function (message) {
alert(message)
}
}
For more info : https://v2.vuejs.org/v2/guide/events.html#Listening-to-Events

The v-on directive is intended for binding event listeners, but you're not actually trying to listen to any events, so you're misusing the v-on directive, leading to the warning you observed.
When Vue loads <chat-message v-on="playNotificationSound()">, it calls playNotificationSound(), expecting a return value to assign to v-on. Since playNotificationSound() is called when chat-message and chat-notification is rendered, it only appears to work as intended (the sound coincides with the chat message/notification).
However, there are no events to be handled here, so v-on should not be used. The call to playNotificationSound() should perhaps be moved into a watcher on message.type:
export default {
watch: {
'message.type'(messageType) {
if (messageType === 'message') {
this.playMessageNotificationSound()
} else if (messageType === 'notification' && this.$store.state.notifications.enterLeave === 'true') {
this.playNotificationSound()
}
}
}
}

Related

Is it possible to get referenced element ie. this.$ref inside dateClick function in fullCalendar?

I am using vuejs for this project. In FullCalendar(v5.3.0), I've been trying to get the timeGridDay when a particular date is clicked in month view (default view) with the selected date being the day to be shown.
I used ref to reference to FullCalendar component. (Only relevant parts are shown here)
<template>
<div class="main">
<div class="calendar-holder col-10 section">
<FullCalendar ref="fullcalendar" :options="options" />
</div>
</div>
</template>
And then I tried to use dateClick method to get to the timeGridDay view when a date is clicked.
data(){
return {
calendarApi: null,
options: {
plugins: [
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
listPlugin
],
initialView: 'dayGridMonth',
dateClick: function(info) {
if(confirm('View Schedule?')){
let calendarApi = this.$ref.fullcalendar.getApi();
calendarApi.fullCalendar('changeView', 'timeGridDay', info.date);
}
}
}
}
}
which didn't work. When I checked the console it said
TypeError: this.$ref is undefined
but it wasn't undefined when I tried the same code inside mounted hook (initializing calendarApi within data)
mounted(){
this.calendarApi = this.$refs.fullcalendar.getApi();
console.log(this.calendarApi)
}
which returned an object. But I couldn't fetch that calendarApi object inside dateClick method. I tried:
dateClick: function(info) {
if(confirm('View Schedule?')){
this.calendarApi.fullCalendar('changeView', 'timeGridDay', info.date);
}
}
didn't work. console gave this error:
TypeError: this.calendarApi is undefined
Is there any way I can get around this problem? I tried to get the calendarApi object from another method as well, but the console said the method is not defined. Thanks in advance.
I've solved it by changing the dateClick funtion to:
dateClick: (info) => {
if(confirm('View Schedule?')){
let calendarApi = this.$refs.fullcalendar.getApi();
calendarApi.changeView('timeGridDay', info.date);
}
}
You can find more on this link: https://fullcalendar.io/docs/Calendar-changeView

Ternary operator truthy or falsy on v-bind vuejs

I'm in a project using VueJS 2
I want to define a certain value with a v-bind using a ternary operator. My value is either truthy (an object) or falsy (undefined)
Here is the code :
<b-icon
class="select-icon"
pack="fas"
:icon="selectedUsers[user.objectID] ? 'circle' : 'check-circle'"
size="is-medium"
type="is-primary"
/>
This is to define which icon should appear. With a direct true or false it work, but not with truthy or falsy. Is there a way to bypass that ?
I tried transforming it to a Boolean with !!selectedUsers[user.objectID])
and loggin it with :
console.log(!!this.selectedUsers[user.objectID]);
but not working
Here is the console.log() :
EDIT : here is the code that should mutate my selectedUsers, i think this is where i'm doing something wrong, i'll check if the render is triggered or not
methods: {
selectUser(user) {
console.log(user);
if (this.selectedUsers[user.objectID]) {
delete this.selectedUsers[user.objectID];
} else {
this.selectedUsers[user.objectID] = user;
}
console.log('this.selectedUsers: ', this.selectedUsers);
console.log(!!this.selectedUsers[user.objectID]);
},
},
Mutation should be last approach, it makes mess when series of calling functions happenend.
Why not you just use computed?
From the code, it seems that you want to achieve value 'circle' OR 'check-circle'
<b-icon
class="select-icon"
pack="fas"
v-bind:icon="check"
size="is-medium"
type="is-primary"
/>
computed: {
check(){
return selectedUsers[user.objectID] ? 'circle' : 'check-circle';
}
}
I hope I understand what you're trying to achieve. If not, please give some more references. I will try my best to solve it.
After some search, it appears that nested object not declared into the data element of VueJS will not be watch, so if changed will not trigger rendering.
For them to be reactive, we have to set or delete them with specific vue methods :
methods: {
selectUser(user) {
if (this.selectedUsers[user.objectID]) {
this.$delete(this.selectedUsers, user.objectID);
} else {
this.$set(this.selectedUsers, user.objectID, user);
}
},
With this code the rendering is triggered. I was not aware of this specificity.
To read more on this, check this official documentation

How can I update the msg variable in the following code?

I have written the following code in a single Vue component. I am very new to Vue. I want to know how can I update the msg variable in the following and pass the updated value to the template in this Vue component:
<template>
<div class="dropzone">
<form id="dropzone" method='POST' enctype="multipart/form-data" v-on:drop="upload" #dragover.prevent>
<my-vue-component v-model="msg" ref="markdownEditor" />
</form>
</div>
</template>
<script>
export default {
data: () => {
return {
msg: 'zyx'
}
},
methods: {
upload: (e) => {
self.msg = '123'
}
}
}
</script>
I am really stuck and none of the links that google redirects me to are out of my reach. Because all of them either talk about new Vue({}) or dont provide much on export default.
Am I doing something wrong here?
I have also tried this.msg, but then I get the error saying msg of undefined...
Change this:
upload: (e) => {
self.msg = '123'
}
to this:
upload (e) {
this.msg = '123'
}
Note that upload (e) { is short-hand for upload: function (e) {.
The key changes are:
Use this instead of self.
Don't use an arrow function because that binds this to the surrounding scope and in this case that's the global scope. What you want is for this to be bound to your component instance (which is actually a Vue instance) so you need to use a normal function. Vue tries to ensure the binding is correct but it can't do that with an arrow function because it is already bound.
The config options that appear in a component like this are almost all the same as the config options that you would pass to new Vue, so if you see examples that are using the other one it will rarely make any difference. Usually it is pretty obvious when a config setting doesn't make sense for one or the other.

How can I capture click event on custom directive on Vue.js?

I am trying to learn Vue.js and came to an practice example where I need to implement a custom directive whice works lice 'v-on'.
This means that i need to capture the click event on my custom directive and call a method.
The template i was thinking of.
<template>
<h1 v-my-on:click="alertMe">Click</h1>
</template>
The problem is i don't know how to capture the click event in the custom directive. Excuse the clumsy code below.
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
bind(el, binding, vnode) {
console.log('bind');
el.addEventListener('click',()=>{
console.log('bind');
vnode.context.$emit('click');
});
},
}
}
}
</script>
Can anyone help me understand how this works? I didn't manage to find any example of something similar.
After some more searching i came to this solution:
<template>
<h1 v-my-on:click="alertMe">Click me!</h1>
</template>
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
// Add Event Listener on mounted.
bind(el, binding) {
el.addEventListener(binding.arg, binding.value);
},
// Remove Event Listener on destroy.
unbind(el, binding) {
el.removeEventListener(binding.arg, binding.value);
}
}
}
}
</script>
The solution you found is, as far as I can tell, the very best solution for what you are looking for. However, for those who don't know much about Vue.JS I thought I'd give a quick explanation. I'd also suggest you check out the official Vue documentation for Custom Directives or my Medium article on the concepts.
This is the code that Vlad came to and I would support:
<template>
<h1 v-my-on:click="alertMe">Click me!</h1>
</template>
<script>
export default {
methods: {
alertMe() {
alert('The Alert!');
}
},
directives: {
'my-on': {
bind(el, binding) {
let type = binding.arg;
let myFunction = binding.value;
el.addEventListener(type, myFunction);
}
}
}
}
</script>
In short, Vue Directives are called on the lifecyle of the element they are attached to, based on the directive object definition. In the example the function defined is called "bind" so the directive will call that function when the element is bound into the DOM.
This function receives the element it's attached to "el" and the different content of the directive usage in the template "binding". In the binding usage in the template, the value after the colon ":" is the "arg" which in this example is the string literal "click". The value inside of the quotes '""' is the "value" which in this case is the object reference to the function "alertMe".
The vars that are defined by getting binding.arg and binding.value (with their respective content) can then be used to create an event listener contained inside of the element "el" that the directive is used on (el is modifiable). So, when the element is created and bound, this new event listener is created on the "click" event defined by "arg" and it will call the "alertMe" function defined by "value".
Because the modification is contained inside the element, you don't have to worry about cleaning up on unbind, because the listener will be destroyed when the element is destroyed.
And that is a basic description of what is happening in the suggested code. To see more about directives and how to use them, follow the suggested links. Hopefully that helps!
You need to register a listener for the event being emitted within your directive.
// emit a custom event
// binding.expression is alertMe
vnode.context.$emit(binding.expression);
// listen for the event
export default {
created(){
this.$on('alertMe', event => {
this.alertMe()
})
},
....
}
This is not calling the method alertMe, rather passing alertMe through to the directive as the binding expression:
<h1 v-my-on:click="alertMe">Click</h1>
#Vlad has an excellent solution!
May I also add an important point: if you wanna pass parameters to your callback, it will confuse you by the way Vue handles your expression. In short, for custom directives, whatever in between quotation marks gets evaluated and the resulted value is passed in (hence, you can get it via binding.value (duh!), while for built-in directives, at least for v-on, the contents between quotation marks get evaluated later on, when event is fired.
Maybe this is best demonstrated with a comparison between custom directive and the built-in v-on directive. suppose you have a "my-on" directive written exactly as what #Vlad does, and you use it side by side with v-on:
built-in:
<h1 v-on:click="myAlert('haha')"> Click me!</h1>
It works as expected, when button is clicked, alert window pops up.
customized:
<h1 v-my-on:click="myAlert('haha')">Click me!</h1>
As soon as button is displayed, the alert window pops up, and when you click on it, the event is fired but nothing visible happens. This is because "myAlert('haha')" is evaluated as soon as binding(?), hence the alert window, and its value gets passed to your directive(undefined or whatever), cuz its value is not a function, nothing seems to happen.
now, the workaround is to have whatever in between the quotation marks returns a function upon evaluation, eg v-my-on:click="() => {myAlert('haha')}"
Hope it helps.
References:
https://stackoverflow.com/a/61734142/1356473
https://github.com/vuejs/vue/issues/5588
As #Vlad said it worked for me:
el.addEventListener('click',()=>{
console.log('bind');
vnode.context.$emit('click');
Here's my directive:
Vue.directive('showMenu', {
bind: function (el, binding, vnode) {
el.addEventListener('click', () => {
console.log('bind')
setTimeout(() => {
this.$emit('toggleDrawer')
}, 1000)
})
}
})
Thanks dude!
Seems like addEventListener works only for native events
To catch events fired with Vue inside the directive use $on
newdirective: {
bind(el, key, vnode){
vnode.componentInstance.$on('event-fired-from-component', someFunction)
},
....
}
You can put this code either inside your component or mixin under directives section like this
directives: {...}
And then connect it to the component you want to receive this event from
<some-component
v-newdirective
></some-component>

How to trigger event in all sibling components except current component in vuejs?

I have a reusable component that does inline editing for data.
So a page has 10 fields that can be edited inline,
<editfield :value="field" v-for="field in fieldslist"></editfield>
Each of them have a data field called "editing" that sets as true or false as the user clicks on it. Everytime a field is set to editing an event editing-another-field is emitted using event bus.
edit(){
this.editing = true;
EventBus.$emit('editing-another-field');
}
I added the event listener when the component is created
created(){
EventBus.$on('editing-another-field', ()=>{ this.editing = false;});
}
The problem I am facing is it is triggering the event even in the currennt component being edited.
How can I mention that updated value of editing in all the other sibling components except the current component.
Why not pass the current component as an event argument and use that to check if the event originated from this component or another one.
edit() {
this.editing = true;
EventBus.$emit('editing-another-field', this);
}
created() {
EventBus.$on('editing-another-field', source => {
if (source !== this) {
this.editing = false;
}
});
}
Or you can do it like this (it is important to unregister the event listener when the component is destroyed to avoid a memory leak):
edit() {
EventBus.$emit('editing-field', this);
}
created() {
this.editingFieldHandler = vm => {
this.editing = vm === this;
};
EventBus.$on('editing-field', this.editingFieldHandler);
}
destroyed() {
EventBus.$off('editing-field', this.editingFieldHandler);
}
Otherwise you can emit the event first and then set this.editing to true.
Are you sure you want an event bus? This brings up bad memories of JQuery ;-) I think it would be cleaner to limit yourself to a tree of parents and children. Thinking MVVM, formLockedBy is a perfectly valid and sensible property to store on the parent and pass to the children.
The solution below, running here, shows a form with two fields. The fields are both instances of modal-component. The parent manages the formLockedBy property. The child fields look at this property to know to disable themselves. When the user starts typing in a field, the field emits an editing event and formLockedBy gets set. Similarly, when a field emits a save or cancel event, the parent clears formLockedBy and the other input(s) spring back to life.
Note the advantages...
Only the parent listens for events.
The identifier stored in formLockedBy is just the string name of the field. This is much safer than passing and storing a reference to the Vue component. If you don't like this, you might consider adding a safe id to the object proto.
No surprises. The full list of events the parent will react to is declared in the tag instantiating the child. The child specifies in props everything it needs from the parent.
HTML
<div id="example">
<modal-input name='First Name'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
<modal-input name='Address'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
</div>
JS
Vue.component('modal-input', {
template: `<div>
{{name}} :
<input :name='name' type="text" v-on:keydown="active" :disabled="formLockedBy && formLockedBy != name"/>
<span v-if="editing && formLockedBy == name">
<input type="button" value="Save" v-on:click="$emit('save');editing=false;"></input>
<input type="button" value="Cancel" v-on:click="$emit('cancel');editing=false;"></input>
</span>
</div>`,
data : function(){
return {editing:false};
},
props: ['name','formLockedBy'],
methods : {
active : function(event){
if(!this.editing){
this.editing = true;
this.$emit('editing',{field:this.name})
}
return true;
}
}
});
// create a root instance
new Vue({
el: '#example',
data: {
formLockedBy : null
},
methods : {
fieldActive : function(args){
this.formLockedBy = args.field;
}
}
})