Stephan-v
Stephan-v

Reputation: 20289

Renderless Vue component with a click listener

I have read this post which goes in depth about renderless components:

https://adamwathan.me/renderless-components-in-vuejs/

A renderless component would pretty much look like this:

export default {
  render() {
    return this.$scopedSlots.default({})
  },
}

Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.

In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.

How would I go about adding a click listener to the element that is being passed into the slot?

Upvotes: 5

Views: 986

Answers (2)

Richard Matsen
Richard Matsen

Reputation: 23463

Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.

See createElements Arguments, the second arg is the object to enhance

A data object corresponding to the attributes you would use in a template. Optional.

console.clear()
Vue.component('renderless', {
  render(createElement) {
    var vNode = this.$scopedSlots.default()[0]
    var children  = vNode.children || vNode.text
    const clone = createElement(
      vNode.tag, 
      {
        ...vNode.data, 
        on: { click: () => alert('clicked') }
      },
      children
    )
    return clone
  },
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<div id="app">
  <renderless>
    <button type="button" slot-scope="{props}">Click me</button>
  </renderless>
</div>

Upvotes: 4

miqh
miqh

Reputation: 3664

Here's one way to go about this.

Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.

Vue.component('renderless-action-wrapper', {
  props: ['action'],
  render() {
    return this.$scopedSlots.default({
      action: this.action,
    });
  },
});

Then another component which uses the aforementioned wrapper would enclose a customisable slot with a @click handler, which invokes the action that is passed in when triggered.

Vue.component('clickable', {
  props: ['action'],
  template: `
    <renderless-action-wrapper :action="action">
      <span slot-scope="{ url, action }">
        <span @click="action()">
          <slot name="action"></slot>
        </span>
      </span>
    </renderless-action-wrapper>
  `,
});

Finally, wire up the specialised version of the wrapper.

<clickable :action="doAjaxRequest">
  <button type="button" slot="action">Button</button>
</clickable>

Here's a live example of the above suggestion you can play around with.

Upvotes: 0

Related Questions