Search code examples
vue.jscomponentsvuejs3

Vue.js 3 optional v-scroll-to


Here is the code that I use.

<template>
  <div class="button-layout" :style="`margin: ${margin}; text-align: ${align};`">
    <component
      :is="buttonComponent"
      v-for="(button, index) in buttons.filter(btn => btn.url)"
      :key="button.label"
      :label="button.label"
      v-scroll-to="button.url"
      :style="`margin-left: ${index === 0 ? '' : space};`" />
    <component
      :is="buttonComponent"
      v-for="(button, index) in buttons.filter(btn => !btn.url)"
      :key="button.label"
      :label="button.label"
      :type="button.type"
      :style="`margin-left: ${index === 0 ? '' : space};`" />
  </div>
</template>

<script>
export default {
  name: "ButtonLayout",
  components: {  },
  props: {
    button: String,
    margin: String,
    align: String,
    space: String,
    buttons: Array
  },
  computed: {
    buttonComponent() {
      return () => import(`./button/${this.button}`)
    }
  }
};
</script>

I can use these two list of object structures and it works fine.

[
    { url: '#video', label: lang.video },
    { url: '#info', label: lang.info }
]
[
    { type: 'reset', label: lang.clear },
    { type: 'submit', label: lang.send }
]

As I don't like repeating my code, I tried to add dynamically the attribute type and v-scroll-to based on the first object on the list, however, it doesn't work. What would be the best way to achieve it? (See the code below)

<template>
  <div class="button-layout" :style="`margin: ${margin}; text-align: ${align};`">
    <component
      :is="buttonComponent"
      v-for="(button, index) in buttons"
      :key="button.label"
      :label="button.label"
      v-bind:[optionalDirective.directive]="button[optionalDirective.key]"
      :style="`margin-left: ${index === 0 ? '' : space};`" />
  </div>
</template>

<script>
export default {
  name: "ButtonLayout",
  components: {  },
  props: {
    button: String,
    margin: String,
    align: String,
    space: String,
    buttons: Array
  },
  computed: {
    buttonComponent() {
      return () => import(`./button/${this.button}`)
    },
    optionalDirective(){
      if(this.buttons[0].url) {
        return {
          directive: 'v-scroll-to',
          key: 'url'
        }
      } else {
        return {
          directive: 'type',
          key: 'type'
        }
      }
    }
  }
};
</script>

Solution

  • As v-scroll-to cannot be bound to v-bind, I found a little work around that actually solve my issue and avoid to duplicate the code twice. I bind the @click to a method that inspect the url value and $scollTo if needed, and it solve my problem.

    <template>
      <div class="button-layout" :style="`margin: ${margin}; text-align: ${align};`">
        <component
          :is="buttonComponent"
          v-for="(button, index) in buttons"
          :key="button.label"
          :label="button.label"
          v-bind="type(button.type)"
          @click="scrollTo(button.url)"
          :style="`margin-left: ${index === 0 ? '' : space};`"
        />
      </div>
    </template>
    
    <script>
    export default {
      name: "ButtonLayout",
      components: {},
      props: {
        button: String,
        margin: String,
        align: String,
        space: String,
        buttons: Array
      },
      methods: {
        type(type) {
          return type ? { type } : {}
        },
        scrollTo(url) {
          if (url) this.$scrollTo(url)
        }
      },
      computed: {
        buttonComponent() {
          return () => import(`./button/${this.button}`);
        }
      }
    };
    </script>