5

Is it possible to dispatch a svelte event (created with createEventDispatcher) with a target object like a native browser event?

I.e. receiving on the handler side event.target.value instead of event.detail.

Paolo
  • 20,112
  • 21
  • 72
  • 113
LongHike
  • 4,016
  • 4
  • 37
  • 76

4 Answers4

5

Yes, it's possible but involve some hacking.

You can take a look into the svelte source code to see how event dispatching works. I'll outline the key parts as below.

Let's say we have two components, Inner and Outer.

<!-- Inner.svelte -->
<script>
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
const dispatchFoo = () => dispatch('foo', 'bar')
</script>

<button on:click={dispatchFoo}>send</button>

<!-- Outer.svelte -->
<script>
import Inner from './Inner.svelte'
const handleFoo = (e) => { console.log('receive foo', e) }
</script>

<Inner on:foo={handleFoo}></Inner>

If you think about it, the event handler handleFoo is created in Outer, but it's actually register on Inner.

Check the compiled JS code you'll see:

  1. inner.$$.callbacks["foo"] holds the event handler [src]
  2. when you click the button and call dispatch, it just read the inner.$$.callbacks["foo"] for potential handlers, and if found, call them with CustomEvent as argument [src]

The only way to set customEvent.target is by dispatch that custom event using element.dispatchEvent(customEvent). But element.dispatchEvent is not used through the whole process.

Solution (hack)

Write your own createEventDispatcher.

import { get_current_component } from 'svelte/internal'

function createEventDispatcher() {
  const component = get_current_component();
  return (type, target, detail) => {
    const callbacks = component.$$.callbacks[type];
    if (callbacks) {
      const event = new CustomEvent(type, { detail });
      // the key is to call `dispatchEvent` manually to set `event.target`
      target.dispatchEvent(event);
      callbacks.slice().forEach((fn) => {
        fn.call(component, event);
      });
    }
  };
}

Svelte REPL

hackape
  • 18,643
  • 2
  • 29
  • 57
  • Great elaboration on the subject. Yes, it's quite a hack, but with a lot of insights. Thanks! – LongHike Apr 07 '21 at 12:25
  • One nice thing about svelte is that it compiles to plain old javascript. If you understand the internal, you can almost always hack your way around. – hackape Apr 07 '21 at 12:29
0

Modified from hackape's answer

import { get_current_component } from 'svelte/internal'

function createEventDispatcher() {
  const component = get_current_component(bubbles = true);
  return (type, target, detail) => {
    const callbacks = component.$$.callbacks[type];
    if (callbacks) {
      const event = new CustomEvent(type, { bubbles, detail });
      // the key is to call `dispatchEvent` manually to set `event.target`
      target.dispatchEvent(event);
      /* You have already raised an event, you should not repeat the callbacks to avoid duplication
        callbacks.slice().forEach((fn) => {
          fn.call(component, event);
        });
      */
    }
  };
}
user5354671
  • 311
  • 4
  • 4
0

user5354671 Commenting out fn.call(...) wont invoke the callback on the outer component.

The target.dispatchEvent() only serves for the purpose of adding the target on that event object

Aayush
  • 1
  • 2
0

You may want to pass the target explicitly:

// Component.svelte
<script>
  import { createEventDispatcher } from 'svelte'
  const payload = 'foo'

  function onClick (event) {
    const { target } = event
    dispatch('myEvent', { target, payload })
  }
</script>

<button on:click={ onClick }></button>

And then, somewhere:

<script>
  import Component from 'Component.svelte'

  function handler (event) {
    const { target, payload } = event.detail
  }
</script>

<Component on:myEvent={ handler }/>
Dmitry
  • 1,556
  • 1
  • 14
  • 26