Search code examples
javascripttypescripttogglesveltesveltekit

Passing Svelte value to Javascript onclick event


I have a Svelte store in which i have an ASCII-Logo. Here I wanted to define a button spanning two Symbols (e.g. "-."). Because I didn't want to split up my ASCII into two parts and inserting the button into my +page.svelte between just inserting those two parts from my store.

Currently I have it setup so that the ASCII-Logo contains my necessary Javascript, but now I have the problem of not being able to retrieve any values from my Svelte "runtime" because I have this completely seperated Javascript in the Svelte store embedded into the ASCII.

Example:

// src/lib/stores.ts

const ascii =
`//||<button class="hiddenButton" onclick="{
          let elems = document.getElementsByClassName('hiddenImage');
          for (const elem of elems) {
              elem.classList.toggle('off')
          };
}">-.</button>||\\`;

Is there a way to retrieve values from my Svelte Typescript in my Javascript which is being loaded from a Svelte store? Or should I just accept that splitting up the ASCII is the way to go?

The actual behaviour I want to achieve is the button toggling between the ASCII and an image that replaces the ASCII which I currently handle by adding a class which has display: none as its CSS. That already works, but if I switch themes and I am currently viewing the image instead of the ASCII it disappears because of being hidden by default. To account for this I added a boolean off = true storing if the image should be visible or not. But i can't retrieve this value in my Javascript embedded into the ASCII, which (I believe) results in the behaviour that I

  1. Toggle to reveal image and hide ASCII; works.
  2. Toggle to hide image and reveal ASCII; reveals ASCII, but image isn't toggled off.

Here's my Code broken down to the Problem.

// src/routes/+page.svelte

<script lang="ts">
    import { ascii } from '$lib/stores';

    $: off = true;

    let togglePicture = () => {
        let elems = document.getElementsByClassName('hiddenImage')!;
        for (const elem of elems) {
            elem.classList.toggle('off')
        }
        off = !off;
    };
</script>

<style>
    :global(.off) {
        display: none;
    }
</style>

<p class="hiddenImage">{@html ascii}</p><button on:click={togglePicture}>
<img src='/pics/favicons/dark-favicon.ico' alt="Dark Logo" class="pixelLogo hiddenImage{off === true ? " off" : null}">

Solution

  • You should avoid DOM manipulation like this, setting classes both via the markup and in JS only leads to confusion and mistakes.

    If the display should be exclusive to either ASCII or image, the whole situation can be handled with an #if/:else based on a single flag.

    You just have to toggle said flag when the button is clicked and if the flag should persist, you have to store it somewhere. For toggling you can just delegate the logic via bubbling, i.e. the ASCII contains a button, but you handle its click in the Svelte code. E.g.

    <script>
        const ascii = 'hello <button type=button data-button>b</button> world';
    
        let showImage = false;
        let container;
    
        function onClick(e) {
            const button = e.target.closest('[data-button]');
            if (button == null || container.contains(button) == false)
                    return;
    
            showImage = !showImage;
        }
    </script>
    
    <!-- Events of interest come from interactive elements
         svelte-ignore a11y-click-events-have-key-events
                       a11y-no-static-element-interactions -->
    <div bind:this={container} class="container" on:click={onClick}>
        {#if showImage}
            <button type=button data-button>[Image here]</button>
        {:else}
            {@html ascii}
        {/if}
    </div>
    

    REPL