Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

v-on click, add handler only if condition has been met

After some research the following suggestion by Mr. Evan You was found: https://github.com/vuejs/vue/issues/7349#issuecomment-354937350

So without any hesitation I gave it a try:

Component template

<template>
  <div v-on='{ click: dataType === `section` ? toggleSectionElements : null }'>
    ... magic 
  </div>
<template>

JS Logic

<script>
export default {
  name: `product-section`,
  props: [`section`, `sectionName`, `depth`],
  methods: {
    toggleSectionElements() {
      ... magic 
    }
  },
  computed: {
    dataType() {
      if (this.$props.section.sections || this.$props.depth === 0) {
        return `section`
      } else {
        return `element`
      }
    }
  }
}
</script>

But for described case it results in error during rendering:

[Vue warn]: Invalid handler for event "click": got null

Can someone please suggest what has been done wrong? :thinking:

Update
The way Data Model looks like:

DataModel: {
  mainSectionA: {
    sections: {
      sectionA: {
        sections: {
          elementA: { values: { ... } },     
          elementB: { values: { ... } }
        }
        values: { ... }
      }
      sectionB: {
        elementA: { values: { ... } },
        elementB: { values: { ... } }
      }
    },
    values: { ... }
  },
  mainSectionB: {
    sections: {
      elementA: { values: { ... } },
      elementB: { values: { ... } },  
      elementC: { values: { ... } },
      ... elements
    },
    values: { ... }
  }
}
like image 422
volna Avatar asked Feb 23 '19 08:02

volna


2 Answers

Instead of polluting your template with ternary logic, you should actually perform the check inside the click handler instead. It not only makes your template more readable, but also makes maintaining the code easier since all logic has been abstracted and delegated to the event handler's callback instead.

Quick solution

Therefore the quick solution is to actually ensure that the toggleSectionElements() will only work when a correct dataType is present. This can be achieved by using a guard clause:

toggleSectionElements() {
  // Guard clause to prevent further code execution
  if (this.dataType() !== 'section')
    return;

  // Magic here
}

Even better, is that if separate handlers should be assigned to each dataType: you can then create a factory function for that purpose:

methods: {
  // This is just a factory function
  toggleElements() {
    switch (this.dataType()) {
      case 'section':
        return this.toggleSectionElements;
      case 'element':
        // Something else...
    }
  },
  toggleSectionElements() {
    // Magic for section element
  }
}

Suggestion: using atomic components

Since it might be costly to bind click event handlers to elements that end up doing nothing, you can also break down your component to be more atomic. The collection element will be responsible of receiving an array of "section" or "element", and each "section"/"element" will have its own component, something like this:

  • You have a collection component, say <my-collection>, that holds all "section" and "element" components
  • "section" component will use the <my-section> component
  • "element" component will use the <my-element> component

This is when VueJS becomes really powerful: you can use dynamic component inside <my-collection> to determine which component to use depending on the dataType encountered.

This is done by running a v-for through the collection, and then using v-bind:is="..." to determine whether a specific collection item should be using "section" or "element". I understand that this is probably going to go out of scope of your original question, but it's a worthwhile design to consider:

const collectionComponent = Vue.component('my-collection', {
  template: '#my-collection-component',
  data: function() {
    return {
      collection: [{
        dataType: 'section',
        description: 'Hello I am section 1'
      }, {
        dataType: 'element',
        description: 'Hello I am element 1'
      }, {
        dataType: 'section',
        description: 'Hello I am section 2'
      }, {
        dataType: 'element',
        description: 'Hello I am element 2'
      }]
    }
  },
  methods: {
    componentToUse(dataType) {
      return 'my-' + dataType;
    }
  }
});

const sectionComponent = Vue.component('my-section', {
  template: '#my-section-component',
  props: ['itemData'],
  methods: {
    toggle() {
      console.log('Doing some magic.');
    }
  }
});

const elementComponent = Vue.component('my-element', {
  template: '#my-element-component',
  props: ['itemData']
});

new Vue({
  el: '#app'
});
.box {
  border: 1px solid #999;
  cursor: pointer;
  margin: 10px;
  padding: 10px;
}

.box:hover {
  background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <my-collection />
</div>

<script type="text/x-template" id="my-collection-component">
  <div>
    <component
      v-for="(item, i) in collection"
      v-bind:key="i"
      v-bind:is="componentToUse(item.dataType)"
      v-bind:itemData="item" />
  </div>
</script>

<script type="text/x-template" id="my-section-component">
  <div @click="toggle" class="box">
    <h1>{{ itemData.dataType }}</h1>
    <p>{{ itemData.description }}</p>
    <p>Clicking on me will invoke a section-specific logic</p>
  </div>
</script>

<script type="text/x-template" id="my-element-component">
  <div class="box">
    <h1>{{ itemData.dataType }}</h1>
    <p>{{ itemData.description }}</p>
    <p>Clicking on me will do nothing</p>
  </div>
</script>
like image 60
Terry Avatar answered Sep 19 '22 11:09

Terry


Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"
like image 40
Patel Pratik Avatar answered Sep 18 '22 11:09

Patel Pratik