1
votes

From an exernal source (for example api), I get a component definition:

{
    "component": "b-btn",
    "content": "Button",
    "attr": {
        "title": "Edit"
    },
    "events": {
        "click": "doSomething"
    }
}

This definition is used with a dynamic component:

<component :is="item.component" v-bind="item.attr">
    {{ item.content }}
</component>

and it works as expected (a vue-bootstrap button is shown, with the title 'Edit' and a button text 'Button').

Now I want to add events as well. Since VueJS 2.4 (https://vuejs.org/v2/api/#v-on), you can define events in object syntax like <button v-on="{ mousedown: doThis, mouseup: doThat }"></button>, I thought, just adding v-on="item.events" might work:

<component :is="item.component" v-bind="item.attr" v-on="item.events">
    {{ item.content }}
</component>

But it doesn't, since the value of the object property click is a string ("doSomething") and not a callable.

// Just to make it clear
{"click": "doSomething"} != {"click": doSomething}

Is there any way to bind dynamic events (from JSON) to a component?

1
You could do this["doSomething"](), but is "doSomething" already defined in the method's object of the Vue instance? Or where is it defined? - Ricky
Thanks for feedback. Yes, doSomething is definied on the vue instance. - bernhardh

1 Answers

2
votes

Yes, it is possible. You have to check programmatically, if a function with the given name exists in your component, and if so use that function instead of the string. Therefore you need to manipulate your JSON. I built a test case in CodeSandbox where I use a computed field which updates the events property by checking if a function exists with the given name for each key in the events object and replaces the value with the function.

Say my child component has an event clicked which fires upon clicking on a button.

My parent component looks like this:

data() {
return {
  item: {
    component: "TestComponent",
    content: "Button",
    attr: {
      title: "Edit"
    },
    events: {
      clicked: "alertText"
    }
  }
};
},
computed: {
  componentItem() {
    let item = this.item;
    if (item.hasOwnProperty("events")) {
      let events = item.events;
      for (let i in events) {
        if (events.hasOwnProperty(i)) {
          let functionName = events[i];
          if (
            this.hasOwnProperty(functionName) &&
            typeof this[functionName] === "function"
          ) {
            // function exists
            item.events[i] = this[functionName];
          }
        }
      }
    }
    return item;
  }
},

methods: {
  alertText() {
    console.log("I was clicked");
  }
}

See a working example here: https://codesandbox.io/s/oo9zj8qy9y