Joseph Pernerstorfer
Joseph Pernerstorfer

Reputation: 424

Using anonymous function as an event handler in Vue component

I have been looking around and found that the most common solution to my problem is to set a variable with the instance of the app/component and then using this variable to change the data (Example 1, Example 2).

My issue is that I cannot use arrow functions (I am only allowed to use ES5 comptible JS, not my call) and that my function does not have a "previous" part to set the app instance.

var theApp = new Vue({
    el: '#the-app',
    data: {
        selectedView: 1,
        myDynamicButtonsArray: [
            {
                text: 'Change view!!'
                onClick: function () {
                    // here this.selectedView should be changed
                    this.selectedView = 2;
                }
            },
            {
                text: 'My other button'
                onClick: function () {
                    // Here something completely unrelated should happen
                }
            }
        ]
    }
});

The I loop over myDynamicButtonsArray and load a myButton component that executes the onClick when clicked.

<my-button v-for="button in myDynamicButtonsArray" v-bind="button"></my-button>

The problem is that when I execute this.selectedView = 2; the this is not refering to the app instance but the function instance where it is being executed, makes sense.

I have tried setting the value like this:

theApp._data.selectedView = 2;

But I am not sure if this is the right approach.

The reason why I am not using the $emit is because there are many different functions to be executed, and emiting a button-clicked and then executing a common function with a giant switch so it does one thing or another depending on what button was pressed does not seem like a viable solution:

// MyButton component template
<button @onClick="this.$emit('button-clicked', id)">
    {{ text }}
</button>

// App html
<my-button 
    v-for="button in myDynamicButtonsArray" 
    v-bind="button"
    @button-clicked="buttonClicked"
>
</my-button>

// App object
var theApp = new Vue({
    el: '#the-app',
    data: {
        selectedView: 1,
        myDynamicButtonsArray: [
            {
                text: 'Change view!!',
                id: 1
            },
            {
                text: 'My other button',
                id: 2
            }
        ]
    },
    methods: {
        buttonClicked: function(id) {
            switch(id) {
                case 1:
                    this.selectedView = 2; 
                    // I am not even sure if I can use 'this' the way I want here;
                    break;
                case 2:
                    // And so on for all my buttons
            }
        }
    }
});

And emitting a different event per button does also not seem viable:

// MyButton component template
<button @onClick="this.$emit(customEventString)">
    {{ text }}
</button>

// App html
<my-button 
    v-for="button in myDynamicButtonsArray" 
    v-bind="button"
    @custom-event-1="customEvent1"
    @custom-event-2="customEvent2"
>
</my-button>

// App object
var theApp = new Vue({
    el: '#the-app',
    data: {
        selectedView: 1,
        myDynamicButtonsArray: [
            {
                text: 'Change view!!',
                customEventString: 'custom-event-1'
            },
            {
                text: 'My other button',
                customEventString: 'custom-event-2'
            }
        ]
    },
    methods: {
        customEvent1: function() {
            this.selectedView = 2; 
            // I am not even sure if I can use 'this' the way I want here;
        },
        customEvent2: function() {
            // Do other things
        }
    }
});

My question is, which is the right approach:

Upvotes: 1

Views: 2114

Answers (2)

Helder Lucas
Helder Lucas

Reputation: 3343

First you need to return a function off your data property and second bind the this keyword to the function

Example:

const MyButton = Vue.component('my-button', {
  template: '<button v-text="text" @click="onClick"></button>',
  props: ['text', 'onClick']
});

new Vue({
  el: '#the-app',
    
  components: { MyButton },
  
  data: function() {
    return {
      selectedView: 1,
      buttons: [
        {
          text: 'Change view!!',
          onClick: function () {
            console.log('btn 1 clicked')
            // here this.selectedView should be changed
            this.selectedView = 2;
          }.bind(this)
        },
        {
          text: 'My other button',
          onClick: function () {
            console.log('btn 2 clicked')
            // Here something completely unrelated should happen
          }
        }
      ]
    }
  }
});
<div id="the-app">
  {{ selectedView }}
  <my-button v-for="(button, index) in buttons" :key="index" v-bind="button" />
</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

Upvotes: 2

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37763

You can change what this means for any function using bind()

<button
      v-for="button in myDynamicButtonsArray"
      @click="bindToMe(button.onClick)()"
      :key="button.text"
    >{{ button.text }}</button>
methods: {
    bindToMe(handler) {
      return handler.bind(this);
    }
  }

Just note about syntax inside @click - instead of passing handler directly, we are calling a function which returns original handler but with this bound to the current Vue instance....

Demo

Upvotes: 1

Related Questions