#Pass props to <script>

24 messages · Page 1 of 1 (latest)

spring crater
#

In a previous conversation with you, you created a custom DownloadButton component that gets passed a props ID to have a unique ID.

---
import Button from '~/components/Button.svelte'
const id = Astro.props.id
const href = '/downloads'
---
<script>
    import { UAParser } from 'ua-parser-js'
    import { latestLinks } from '~/config'
    class DownloadButton extends HTMLElement {
        constructor() {
            super()
            // Your existing script here, modified to use `this` instead of `document`
            // to only affect this instance of the button.
            const parser = UAParser(navigator.userAgent)
            const os = parser.os.name.toLowerCase()
            const isMacOS = ['macos'].includes(os)
            const isWindows = ['windows'].includes(os)
            const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
            if (isMacOS) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.macOS())
            } else if (isWindows) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.windows())
            } else if (isLinux) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.linux())
            }
        }
    }
    customElements.define('download-button', DownloadButton)
</script>
<download-button {id} {href}>
    <Button {href}>Download</Button></download-button
>

The site is static, and so the {id} querySelector won't work unless the <script> gets passed the id props. Can you do that? I assume maybe a Map needs to get used so that all the IDs get set?

cloud stumpBOT
#

Hi @spring crater. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

In Astro, the code in the frontmatter between the --- fences runs on the server and is not available in the browser. To send variables from the server to the client, you can use data-* attributes to store the value of variables in your HTML output. Scripts, including custom elements, can then read these attributes using an element’s dataset property once your HTML loads in the browser.

In your case, you can store the id prop in a data-id attribute, so the custom element can read this.dataset.id and get the value of the prop in the browser.

Here's how you can modify your code:

#
---
import Button from '~/components/Button.svelte'
const id = Astro.props.id
const href = '/downloads'
---
<script>
    import { UAParser } from 'ua-parser-js'
    import { latestLinks } from '~/config'
    class DownloadButton extends HTMLElement {
        constructor() {
            super()
            // Read the id from the data attribute.
            const id = this.dataset.id;
            // Your existing script here, modified to use `this` instead of `document`
            // to only affect this instance of the button.
            const parser = UAParser(navigator.userAgent)
            const os = parser.os.name.toLowerCase()
            const isMacOS = ['macos'].includes(os)
            const isWindows = ['windows'].includes(os)
            const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
            if (isMacOS) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.macOS())
            } else if (isWindows) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.windows())
            } else if (isLinux) {
                this.querySelector(`#${id}`)?.setAttribute('href', latestLinks.linux())
            }
        }
    }
    customElements.define('download-button', DownloadButton)
</script>
<download-button data-id={id} {href}>
    <Button {href}>Download</Button></download-button
>

This way, each instance of your DownloadButton component will have its own unique id that can be used in the client-side script.

For more information, you can refer to the Astro documentation on passing frontmatter variables to scripts (https://docs.astro.build/en/guides/client-side-scripts/).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@spring crater kapa.ai is still learning and improving, please let us know how it did by reacting below

spring crater
#

@cloud stump href is still only returning /download on all the buttons. It seems like data-id isn't working? Or maybe the querySelector?

cloud stumpBOT
#

Let me check the knowledge base for you. One moment! :books:

#

The issue might be due to the timing of when the script is running. The constructor of a custom element runs when an instance of the element is created, but before it's attached to the DOM. At this point, the inner content of the element might not be fully parsed and available yet, which could be why querySelector isn't finding the button.

To solve this, you can move your code to the connectedCallback method, which runs when the element is attached to the DOM. At this point, the inner content should be fully available.

Here's how you can modify your code:

#
---
import Button from '~/components/Button.svelte'
const id = Astro.props.id
const href = '/downloads'
---
<script>
    import {{ UAParser }} from 'ua-parser-js'
    import {{ latestLinks }} from '~/config'
    class DownloadButton extends HTMLElement {{
        connectedCallback() {{
            // Read the id from the data attribute.
            const id = this.dataset.id;
            // Your existing script here, modified to use `this` instead of `document`
            // to only affect this instance of the button.
            const parser = UAParser(navigator.userAgent)
            const os = parser.os.name.toLowerCase()
            const isMacOS = ['macos'].includes(os)
            const isWindows = ['windows'].includes(os)
            const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
            if (isMacOS) {{
                this.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.macOS())
            }} else if (isWindows) {{
                this.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.windows())
            }} else if (isLinux) {{
                this.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.linux())
            }}
        }}
    }}
    customElements.define('download-button', DownloadButton)
</script>
<download-button data-id={{id}} {{href}}>
    <Button {{href}}>Download</Button></download-button
>

This should ensure that the href attribute is correctly set on each button when the page loads.

For more information, you can refer to the MDN documentation on using custom elements (https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@spring crater kapa.ai is still learning and improving, please let us know how it did by reacting below

spring crater
#

@cloud stump still doesn't work. Maybe moving the querySelector should be moved out of the custom element?

cloud stumpBOT
#

Let me check the knowledge base for you. One moment! :books:

#

I apologize for the confusion. The issue might be due to the fact that the Button component is a Svelte component, and its rendering might not be complete when the connectedCallback of the custom element runs.

Instead of using a custom element, you could use a regular script and run your code when the document is fully loaded. This way, you can be sure that all components have been rendered and are available in the DOM.

Here's how you can modify your code:

---
import Button from '~/components/Button.svelte'
const id = Astro.props.id
const href = '/downloads'
---
<Button id={{id}} {{href}}>Download</Button>
<script>
    import {{ UAParser }} from 'ua-parser-js'
    import {{ latestLinks }} from '~/config'
    window.addEventListener('DOMContentLoaded', (event) => {
        // Read the id from the data attribute.
        const id = '{{id}}';
        // Your existing script here, modified to use `document` instead of `this`
        // to affect the button with the given id.
        const parser = UAParser(navigator.userAgent)
        const os = parser.os.name.toLowerCase()
        const isMacOS = ['macos'].includes(os)
        const isWindows = ['windows'].includes(os)
        const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
        if (isMacOS) {
            document.querySelector(`#${id}`)?.setAttribute('href', latestLinks.macOS())
        } else if (isWindows) {
            document.querySelector(`#${id}`)?.setAttribute('href', latestLinks.windows())
        } else if (isLinux) {
            document.querySelector(`#${id}`)?.setAttribute('href', latestLinks.linux())
        }
    });
</script>

This should ensure that the href attribute is correctly set on each button when the page loads.

#

@spring crater kapa.ai is still learning and improving, please let us know how it did by reacting below

spring crater
#

@cloud stump thanks - the data-attribute needs to be passed to the script somehow??

cloud stumpBOT
#

Let me check the knowledge base for you. One moment! :books:

#

Yes, you're correct. The data-* attribute can be used to pass data from the server-side to the client-side. In your case, you can use a data-id attribute to pass the id prop to the client-side script.

Here's how you can modify your code:

---
import Button from '~/components/Button.svelte'
const id = Astro.props.id
const href = '/downloads'
---
<Button id={{id}} data-id={{id}} {{href}}>Download</Button>
<script>
    import {{ UAParser }} from 'ua-parser-js'
    import {{ latestLinks }} from '~/config'
    window.addEventListener('DOMContentLoaded', (event) => {{
        // Read the id from the data attribute.
        const id = document.querySelector(`#${{id}}`).dataset.id;
        // Your existing script here, modified to use `document` instead of `this`
        // to affect the button with the given id.
        const parser = UAParser(navigator.userAgent)
        const os = parser.os.name.toLowerCase()
        const isMacOS = ['macos'].includes(os)
        const isWindows = ['windows'].includes(os)
        const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
        if (isMacOS) {{
            document.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.macOS())
        }} else if (isWindows) {{
            document.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.windows())
        }} else if (isLinux) {{
            document.querySelector(`#${{id}}`)?.setAttribute('href', latestLinks.linux())
        }}
    }});
</script>

This way, each instance of your Button component will have its own unique id that can be used in the client-side script.

#

@spring crater kapa.ai is still learning and improving, please let us know how it did by reacting below

spring crater
#

@cloud stump This also doesn't seem to work. Maybe we should move all the logic to the Button.svelte and that gets called directly instead:

<script lang="ts">
    import { twMerge } from 'tailwind-merge'

    let className = ''

    export let href: string | undefined = undefined
    export let type: 'submit' | 'reset' | 'button' | undefined = 'button'

    export { className as class }
</script>

<svelte:element
    this={href ? 'a' : 'button'}
    on:click|stopPropagation
    on:keydown
    {href}
    id='download'
    type={!href ? type : undefined}
    role={href ? 'link' : 'button'}
    rel={href && href?.startsWith('http') ? 'noopener nofollow' : href ? 'prefetch' : undefined}
    target={href && href?.startsWith('http') ? '_blank' : '_self'}
    class={twMerge(
        'whitespace-nowrap rounded-full bg-gold px-7 py-4 font-montserrat text-lg dark:text-deepsea font-semibold no-underline transition-all hover:scale-105 hover:bg-sand lg:text-xl',
        className
    )}
    {...$$restProps}
>
    <slot />
</svelte:element>
cloud stumpBOT
#

Let me check the knowledge base for you. One moment! :books:

#

If you want to move the logic to the Button.svelte component, you can do so by adding a mounted lifecycle hook in the Svelte component. This hook runs after the component is first rendered in the DOM, ensuring that the component and its content are fully available.

Here's how you can modify your Button.svelte component:

<script lang="ts">
    import { onMount } from 'svelte';
    import { UAParser } from 'ua-parser-js';
    import { latestLinks } from '~/config';
    import { twMerge } from 'tailwind-merge'

    let className = ''

    export let href: string | undefined = undefined
    export let type: 'submit' | 'reset' | 'button' | undefined = 'button'

    export { className as class }

    onMount(() => {
        // Your existing script here, modified to use `document` instead of `this`
        // to affect the button with the given id.
        const parser = new UAParser(navigator.userAgent)
        const os = parser.getOS().name.toLowerCase()
        const isMacOS = ['macos'].includes(os)
        const isWindows = ['windows'].includes(os)
        const isLinux = ['ubuntu', 'linux', 'unix', 'gnu', 'fedora'].includes(os)
        if (isMacOS) {
            document.querySelector('#download')?.setAttribute('href', latestLinks.macOS())
        } else if (isWindows) {
            document.querySelector('#download')?.setAttribute('href', latestLinks.windows())
        } else if (isLinux) {
            document.querySelector('#download')?.setAttribute('href', latestLinks.linux())
        }
    });
</script>
#

<svelte:element
this={href ? 'a' : 'button'}
on:click|stopPropagation
on:keydown
{href}
id='download'
type={!href ? type : undefined}
role={href ? 'link' : 'button'}
rel={href && href?.startsWith('http') ? 'noopener nofollow' : href ? 'prefetch' : undefined}
target={href && href?.startsWith('http') ? '_blank' : '_self'}
class={twMerge(
'whitespace-nowrap rounded-full bg-gold px-7 py-4 font-montserrat text-lg dark:text-deepsea font-semibold no-underline transition-all hover:scale-105 hover:bg-sand lg:text-xl',
className
)}
{...$$restProps}

<slot />

</svelte:element>


This way, each instance of your `Button` component will have its own unique `id` that can be used in the client-side script.

For more information, you can refer to the **Svelte documentation on lifecycle hooks** (<https://svelte.dev/tutorial/onmount>).
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------