Add event directive to svelte html expression?

Viewed 829

I want to add a custom button through an html expression in svelte. Through the docs here, I can do that using the @html tag.

{@html 
`
 <button>
   click me
 </button>
`
}

I would like to now add a directive event handler to the html string, namely a locally defined function that will be called when button is clicked. What I have tried so far:

{@html 
`
 <button
   on:click="${()=>doSomething()}">
   click me
 </button>
`
}

However, as I expected it didn't work. I was thinking there could be another way to this, but couldn't come up with one so far

1 Answers

Conceptually, using the @html directive is equivalent to doing el.innerHTML = myHtmlString.

Said otherwise, in the context of your HTML string, you're completely out of Svelte's realm (and completely in native browser's realm).

on:click is special Svelte syntax, the browser won't process that. So if you want events in you HTML string, you need to go back to raw HTML/JS. That is, use onclick instead.

Of course, the semantics of onclick are completely different from that of on:click. To onclick, you can only pass a string, that will be eval'd when the event happens (whereas with on:click, you pass a function reference, that will be called when the event happens).

To make this very clear, let's recall Svelte's syntax:

<button on:click={doSomething}>Click me</button>

In contrast, in raw HTML, this would look like:

{@html '<button onclick="doSomething()">Click click!</button>'}

See the important difference: with onclick, you need to call the doSomething() function, or nothing will happen on click.

Now, extra fun: this doSomething function needs to be available in scope... That is, you have to attach it to window:

<script context="module">
  // note: using context=module to avoid adding this function to window
  //       multiple times
  window.doSomething = () => { ... }
</script>
{@html '<button onclick="doSomething()">Click click!</button>'}

This is ugly... You could do something like this instead, to avoid polluting the global scope:

<script>
  import { onMount } from 'svelte'

    const doSomething = () => { ... }

    onMount(() => {
        const btn = document.querySelector('#grab-me')
        btn.addEventListener('click', doSomething)
    })
</script>

{@html '<button id="grab-me">Click click!</button>'}

Now we're polluting DOM ids... We could further avoid that by wrapping with an element that Svelte controls, to which we could grab a reference with bind:this={el}...

To sum it up, dynamic elements in @html strings are best avoided because you lose all the benefits of Svelte inside them. @html is best fitted for rendering static HTML content.

Why do you want to put your button in a string, instead of letting Svelte manage it?

If you really need going done this road, you might also be interested in this dirty trick, that I absolutely don't recommend to use (I would never use it in my own code)... But this might give you further perspective on what you're trying to achieve.

Related