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.
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.
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:
inner.$$.callbacks["foo"]
holds the event handler [src]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.
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);
});
}
};
}
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 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
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 }/>