Search code examples
vue.jsvuejs2vue-formulate

Vue.js Event passthrough triggering 'click' event and named event when I only want the named event


I'm using the VueFormulate library to generate forms from a JSON schema. Doing this I would like the ability to add any event listeners I need without having to update my generic component so I'm attempting to pass the event up using v-on="$listeners". The trouble is that I need to do some processing before passing the form object back to the client so I need to have some events specified.

For example, below when clicking the button that emits the 'save' event, the parent component ends up receiving the 'save' event but also a 'click' event. Both of which trigger the 'save' event listeners. If I remove v-on="$listeners" then the defined events work, however those specified only in the schema no longer get passed through.

// Dynamic-form.vue

<template>
  <div>
  {{ $listeners }}
    <FormulateForm
      :name="name"
      v-model="theForm"
      :schema="filteredFormSchema"
      @submit="handleSubmit"
      @save="handleSave"
      v-on="$listeners"
    >
    </FormulateForm>
  </div>
</template>

<script>
import "./vue-formulate-config"
import { cloneDeep } from "lodash";

export default {
  name: "DynamicForm",
  props: {
    formSchema: {
      type: Array,
      required: true,
    },
    formData: {
      type: Object,
      required: true,
    },
    name: {
      type: String,
      default: 'theForm'
    },
    hideOptionalFields: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      theForm: {},
      schema: [],
    };
  },
  methods: {
    handleSave: function() {
      this.$emit("save", this.generateFormData(this.theForm))
    },
    handleSubmit: function (formValues) {
      this.$emit("submit", this.generateFormData(formValues));
    },
  },
};
</script>

Below is the snippet using the component above. You can see I'm listening for some events that are not emitted directly from that component. Those are specified in the schema json and are passed through.

// some-form.vue

       <DynamicForm
          class="nested-alt p-10 rounded"
          name="some-form"
          :formSchema="k209Schema"
          :formData="form"
          @submit="handleSubmit"
          @save="saveAsDraft($event)"
          @showPreview="handleShowPreview"
          @cancel="handleCancelClick"
        ></DynamicForm>
[
      {
        name: 'preview',
        type: 'button',
        label: 'Preview',
        "input-class": 'btn btn-outline-dark btn-sm',
        '@click': 'showPreview'
      },
      {
        name: 'cancel',
        type: 'button',
        label: 'Cancel',
        "input-class": 'btn btn-outline-dark btn-sm',
        '@click': 'cancel'
      },
      {
        name: 'save',
        type: 'button',
        label: 'Save as Draft',
        "input-class": 'btn btn-outline-dark btn-sm',
        '@click': 'save'
      },
      {
        name: 'submit',
        type: 'submit',
        label: 'Send',
        "input-class": 'btn btn-info btn-sm'
      }
    ]

Solution

  • When doing this you have to be careful with the event names chosen. In this case the event name in the schema is the same event name being emitted from the handleSave() method. So what's happening is the 'save' event from VueFormulate is caught in dynamic-form.vue where @save catches it and calls that method. Additionally, the v-on="$listeners" is taking that same re-named click event (i.e. the 'save' click event) and passing it through. Now you have a save event containing the click payload as well as a save event contining your form payload from the handleSave() method.

    The fix: change the 'save' event name in one of the places. I ended up changine the schema click to be more explicit that it's a click and named it save-click.

    // some-form.vue
    
           <DynamicForm
              class="nested-alt p-10 rounded"
              name="some-form"
              :formSchema="k209Schema"
              :formData="form"
              @submit="handleSubmit"
              @save-click="saveAsDraft($event)"
              @showPreview="handleShowPreview"
              @cancel="handleCancelClick"
            ></DynamicForm>
    
    [
          {
            name: 'preview',
            type: 'button',
            label: 'Preview',
            "input-class": 'btn btn-outline-dark btn-sm',
            '@click': 'showPreview'
          },
          {
            name: 'cancel',
            type: 'button',
            label: 'Cancel',
            "input-class": 'btn btn-outline-dark btn-sm',
            '@click': 'cancel-click'
          },
          {
            name: 'save',
            type: 'button',
            label: 'Save as Draft',
            "input-class": 'btn btn-outline-dark btn-sm',
            '@click': 'save-click'
          },
          {
            name: 'submit',
            type: 'submit',
            label: 'Send',
            "input-class": 'btn btn-info btn-sm'
          }
        ]