Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emit event from content in slot to parent

I'm trying to build a flexible carousel control that allows inner content elements to force changing a slide, aswell as the carousel controls itself to change slides

A sample structure in my page looks like

<my-carousel>
  <div class="slide">
    <button @click="$emit('next')">Next</button>
  </div>

  <div class="slide">
    <button @click="$emit('close')">Close</button>
  </div>
</my-carousel>

The template for my carousel is like

<div class="carousel">
  <div class="slides" ref="slides">
    <slot></slot>
  </div> 
  <footer>
   <!-- other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

And script like

...
created() {
 this.$on('next', this.next)
}
...

Accessing the slides etc is no problem, however using $emit will not work and I can't seem to find a simple solution for this problem.

I want to component to be easily reusable without having to use

  • central event bus
  • hardcoded slides within a carousel
  • implement the next slide methods on page level and pass the current index to the control (as I'd have to do this every time I use the carousel)
like image 248
Frnak Avatar asked Jun 20 '18 07:06

Frnak


2 Answers

Slots are compiled against the parent component scope, therefore events you emit from the slot will only be received by the component the template belongs to.

If you want interaction between the carousel and slides, you can use a scoped slot instead which allows you to expose data and methods from the carousel to the slot.

Assuming your carousel component has next and close methods:

Carousel template:

<div class="carousel">
  <div class="slides" ref="slides">
    <slot :next="next" :close="close"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example usage:

<my-carousel v-slot="scope">
  <div class="slide">
    <button @click="scope.next">Next</button>
  </div>

  <div class="slide">
    <button @click="scope.close">Close</button>
  </div>
</my-carousel>
like image 106
Decade Moon Avatar answered Nov 07 '22 07:11

Decade Moon


My Solution

Just create an event listener component (e.g. "EventListener") and all it does is render the default slot like so:

EventListener.vue

export default {
    name: 'EventListener'
    render() {
        return this.$slots.default;
    }
}

Now use this <event-listener> component and wrap it on your <slot>. Child components inside the slot should emit events to the parent like so: this.$parent.$emit('myevent').

Attach your custom events to the <event-listener @myevent="handleEvent"> component.

Carousel template:

<div class="carousel">
  <event-listener @next="handleNext" @close="handleClose">
     <div class="slides" ref="slides">
       <slot></slot>
     </div> 
  </event-listener>
  <footer>
   <!-- other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example:

<my-carousel>
  <div class="slide">
    <button @click="$parent.$emit('next')">Next</button>
  </div>

  </div class="slide">
    <button @click="$parent.$emit('close')">Close</button>
  </div>
</my-carousel>

Note: The <event-listener> component must only have one child vnode. It cannot be the <slot>, so we just wrapped it on the div instead.

like image 10
Daniel Ramirez Avatar answered Nov 07 '22 07:11

Daniel Ramirez