1
votes

I want to use Tabulator in my project, which does not yet have an integration with Svelte. It is a very complete data table library that I have already used before. But when I tried to create the typical column with action buttons (edit, remove ...) I have always used their Custom Formatters that returns html as a string.

The function that formats the column returns something like this:

return '<ul class="actions_row"><li><custom-button on:click={handleClick}>Edit</custom-button></li></ul>';

<custom-button /> is a custom element created using <svelte:options> and added to the project through index.html (<script src="lib/CustomButton.js"></script>).

The custom button is shown in the table, but it is not able to forward events. It seems as the component could not communicate outside its own scope.

3

3 Answers

1
votes

The string returned by your custom formater will be processed as "normal" HTML, so Svelte syntax is not available anymore... Namely on:click is Svelte syntax and won't be processed by the browser.

I'm no custom element expert, but unfortunately, according to others, what you're trying to do is impossible. That is, you can't register custom events listeners on custom elements from the HTML alone. You would necessarily have to do it from JS. Something like this:

    <script>
      document.querySelector('custom-button')
        .addEventListener('my-event', e => {
          console.log(e.detail)
        })
    </script>

Furthermore, beware that events from custom elements in Svelte currently suffer some limitations (see this issue).

So, in order to have custom events for your custom elements, you'd have to use some kind of workaround. For example, here's a component that would trigger the listener in the above snippet:

<svelte:options tag="custom-button" />

<script>
  import { onMount } from 'svelte';

  let el

  onMount(() => {
    const interval = setInterval(() => {
      let event = new CustomEvent('my-event', {
          detail: {time: Date.now()},
          bubbles: true,
          composed: true, // needed for the event to traverse beyond shadow dom
      })
      el.dispatchEvent(event)
    }, 1000)

    return () => {
      clearInterval(interval)
    }
  })
</script>

<button bind:this={el}><slot /></button>

Note that such code will have limited support with older browsers.

All that being said, for just precisely the case in your example, you could apparently make it work by registering your event with native browser onclick instead of Svelte's on:click. I'm still no CE expert, but my guess is the browser processes standard events that are available on all native elements like click or keydown on CE elements too...

So, it appears this should work for you:

return '<ul class="actions_row"><li><custom-button onclick="handleClick">Edit</custom-button></li></ul>';

Notice: onclick="handleClick" instead of on:click={handleClick}. You're now in standard browser land, so usual rules apply, the same way as if you were using a normal <button>... handleClick has to be available in scope, etc.

0
votes

The reason this is happening is because you are returning an HTML string from the formatter, which means that any JS bindings such as click event handlers will have been stripped out as part of turning it into a string. (the context of the click function binding will have been lost as the function will only be valid in the function in which the string was written not where it was parsed)

In order to keep the bindings you should return the Node Object itself representing the button instead of just its HTML, that way any event bindings added to it by your other library will remain intact.

0
votes

I am dispatching events using the below code from the custom component.

<script>
    import { get_current_component } from "svelte/internal";
    const thisComponent = get_current_component();
    
      // Standard function for dispatching custom events
      const dispatchWcEvent = (name, detail) => {
        thisComponent.dispatchEvent(new CustomEvent(name, {
          detail,
          composed: true, // propagate across the shadow DOM
        }));
      };
    
       function handleClose(event) {
           event.preventDefault();
           // dispatching collpase event
           dispatchWcEvent("collapse", "tooltip")
       }
<script>

<svelte:options tag="my-component"></svelte:options>
    
<span class="icon-cross" on:click={event => handleClose(event)}>X</span>

Catching custom event in Real DOM ( js file not a part of svelte project)

let shadow = document.querySelectorAll("my-component");
// if you have single occurence then you can skip the foreach loop and use querySelector

shadow.forEach(function(elem) {
  elem.addEventListener("collapse", e => {
    alert('event handled');
  });
});