-
Hello, Recent svelte adopter (and so far mostly enjoyer), I'm trying to find a way to create svelte components from javascript code. Note that I'm using it in SPA mode, so I do not have access to The feature I'm trying to achieve, is on some user action (in this case a large file upload) create a card displaying progress Here a very simple case using pure html: <script lang="ts">
function btnClick(){
let card = document.createElement('div');
let cardcontent = document.createElement('p');
cardcontent.innerText = "Pending Action ...";
card.appendChild(cardcontent);
let cardbar = document.getElementById("mycards")!;
cardbar.appendChild(card);
}
</script>
<button type"button" onclick={btnClick()}>
click me
</button>
<div id="mycards"></div> In my case I'm using flowbite-svelte, so the idea would be to create a svelte component. Which doesn't work with Browsing the docs, using import { Card } from 'flowbite-svelte';
function btnClick(){
let cardbar = document.getElementById("mycards")!;
let new_card = mount(Card, {target: cardbar});
} It works for the card, but now it's not obvious to me how to set the content, I've tried a few things Idea 1:I thought about using mount all the way down. however it only accepts svelte components as input so elements created with Idea 2:Given my understanding on how svelte works. The content is actually a snippet labelled function btnClick(){
let cardbar = document.getElementById("mycards")!;
const content = `<p>Pending Action ...</p>`
const cardcontent = createRawSnippet(() => {
return {
render: () => content,
setup: (node) => {
$effect(() => {
node.textContent = content;
});
}
}}
);
let new_card = mount(Card, {
target: cardbar,
props: {
class: "action-card",
children: cardcontent,
},
})
} Which appears to be correct for my JS linters, but does not display the content inside the card. I'm not sure this Idea 3 (aka 0'):That card is just a Best, |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
It depends on what the content is and where it is supposed to come from. You can e.g. reference snippets defined in the markup and snippets that don't use local state can also be exported from the module script. That may be easier than working with One can also import components and pass those as props & add them in the receiving component's markup. Regarding the approach using Though if you create a wrapper, you could also add an element inside the card and expose that; this way you can add DOM elements directly to the component. <!-- CardWrapper.svelte -->
<script>
import Card from './Card.svelte';
const { ...rest } = $props();
let contents;
export { contents }
</script>
<Card {...rest}>
<div bind:this={contents}></div>
</Card> <script>
import { mount } from 'svelte';
import CardWrapper from './CardWrapper.svelte';
let cards;
async function btnClick() {
const content = document.createElement('p');
content.textContent = 'Pending Action...';
const card = await mount(CardWrapper, {
target: cards,
props: { class: "action-card" },
});
card.contents.append(content);
}
</script>
<button onclick={btnClick}>Add</button>
<div bind:this={cards} ></div> |
Beta Was this translation helpful? Give feedback.
-
I think this could be accomplished much more idiomatically with an array. Basic exampleThe most basic example is simply iterating over the array and showing static content. <script>
let uploads = $state([])
async function handleClick() {
const id = Date.now()
const filename = id + '.txt'
let upload = { id, filename }
uploads.push(upload)
}
</script>
<button onclick={handleClick}>Add</button>
{#each uploads as upload (upload.id)}
<div>
<p>Pending action...</p>
<p>{upload.filename}</p>
</div>
{/each} With mutationsOnce the value is captured in state, it can be updated directly. You can update, add, or remove from the array to control what components are rendered. <script>
let uploads = $state([])
async function handleClick() {
const id = Date.now()
const filename = id + '.txt'
let upload = { id, filename, progress: 0 }
uploads.push(upload)
// Once the value is added to the state array, it gets proxified.
// Afterwards, mutating the proxified value will cause updates.
// You can retreive the proxified version of the inserted value by doing something like this.
upload = uploads.find(u => u.id === id) || uploads.at(-1)
// This should cause an update for example.
upload.progress += 10
// Example of updating state.
const interval = setInterval(() => {
// You can also look up the upload by ID every time for the most safety.
// This is equivalent with using the proxified state retrieved right before if it is still present in the array.
const current = uploads.find(u => u.id === id)
if (current) {
current.progress += 10
if (current.progress >= 100) {
clearInterval(interval)
// You could also remove the upload altogether.
uploads = uploads.filter(u => u.id !== id)
}
}
}, 1_000)
// If you want to be extra careful for this example, clean up the intervals.
upload.interval = interval
}
$effect(() => {
return () => {
// If you want to be extra careful for this example, clean up the intervals.
uploads.forEach((upload) => {
clearInterval(upload.interval)
})
}
})
</script>
<button onclick={handleClick}>Add</button>
{#each uploads as upload (upload.id)}
<div>
<p>Pending action...</p>
<p>{upload.filename}</p>
<p>{upload.progress}</p>
</div>
{/each} With custom componentsIf you also want dynamic components for each upload, you could capture that in the array. <script>
import { Card } from 'flowbite-svelte'
import MyComponent from './my-component.svelte'
let uploads = $state([])
// Dummy value.
let i = 0;
async function handleClick() {
const id = Date.now()
const filename = id + '.txt'
let upload = { id, filename, progress: 0 }
// Just an example, not representative of actual practices.
i++
if (i % 3 === 1) {
upload.component = Card
}
if (i % 3 === 2) {
upload.component = MyComponent
}
uploads.push(upload)
}
</script>
<button onclick={handleClick}>Add</button>
{#each uploads as upload (upload.id)}
{#if upload.component}
<upload.component>
<p>Pending action...</p>
<p>{upload.filename}</p>
<p>{upload.progress}</p>
</upload.component>
{:else}
<div>
<p>Pending action...</p>
<p>{upload.filename}</p>
<p>{upload.progress}</p>
</div>
{/if}
{/each} |
Beta Was this translation helpful? Give feedback.
It depends on what the content is and where it is supposed to come from. You can e.g. reference snippets defined in the markup and snippets that don't use local state can also be exported from the module script. That may be easier than working with
createRawSnippet
which is not really meant for general use.One can also import components and pass those as props & add them in the receiving component's markup.
Regarding the approach using
createRawSnippet
, that works if theCard
actually renderschildren
.If that is not the case, the library may still be using
<slot />
.You could create a wrapper component as a workaround (so you have
<Card {...rest}>{@render children()}</Card>
).Though if …