Using Laravel Blade templates with Vue JS and event listeners

Viewed 892

I am using Laraval custom components, for example, I have:

components/input.blade.php

<input type="text" class="rounded">

Note: this is a simple example, the key part here is that I want to reuse the HTML generated by the blade template.

Now I can create a Vue component with a scoped slot:

js/components/Search.vue

<template>
  <div>
    <slot name="search-input" />
  </div>
</template>
<script>
  export default {
    methods: {
      focus() {
        console.log('focus input');
      }
    }
  }
</script>

// ... and registering the component
// among others 

Vue.component('search', require('js/components/Search'));
new Vue({
  el: '#vue_app'
});

Now great, I can use my component as follows:

views/somepage.blade.php

<search>
  <template v-slot:search>
    <x-input>
  </template>
</search>

All works as expected, however as you can see I have a focus method, which I would like to be triggered when focusing on the blade input.

Now I can't do this:

js/components/Search.vue

<template>
  <div>
    <slot name="search-input" @focus="focus" />
  </div>
</template>
<script>
  export default {
    methods: {
      focus() {
        console.log('focus input');
      }
    }
  }
</script>

But I can do this:

js/components/Search.vue

<template>
  <div>
    <div ref="search-container"><slot name="search-input" /></div>
  </div>
</template>
<script>
  export default {
    methods: {
      mounted() {
          this.$refs['search-container'].querySelector('input').addEventListener('focus', this.focus)
      },
      focus() {
        console.log('focus input');
      }
    }
  }
</script>

But I am not sure that using addEventListener is the best approach within a Vue component.

Is there a better way to achieve what I am looking to do?

2 Answers

Not sure exactly of the issue, maybe

this.$refs['search-container'].querySelector('input').addEventListener('focus', this.focus)

is somehow async (lifecycle-wise) and that you may need to wrap it into a nextTick

this.$nextTick(() => {
  this.$refs['search-container'].querySelector('input').addEventListener('focus', this.focus)
})

Btw, IMO there is no issue with this way of adding a listener.

Also, maybe try @focus.native="focus" as suggested here: https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components
Because maybe of hydration, it may take some time for the element to be available as a Vue node, so sticking it a native focus may be the solution.

Not sure that those may work but those are the 2 ideas that I have.

My preferred solution was to utilise scoped slots and their slot props to pass methods. An end solution would look like:

views/somepage.blade.php

<search>
  <template v-slot:default>
    <x-input @focus="slot.methods.focus">
  </template>
</search>

You can see above I am referencing the slot props variable slot to access the variables that I define below. Notice I pass a methods object. Calling the object methods is just a preference of mine. I decided to add both methods and props when necessary.

js/components/Search.vue

<template>
  <div>
    <slot :methods="{ focus }" />
  </div>
</template>
<script>
  export default {
    methods: {
      focus() {
        console.log('focus input');
      }
    }
  }
</script>
Related