DEV Community 👩‍💻👨‍💻

Richard Leddy
Richard Leddy

Posted on

Svelte Nested Tabs

Svelte is Pretty Cool

Maybe something worth talking about.

What this is About

svelte-nested-tabs

Experience

I got some stuff up in a hurry. And, that may have been to do with Svelte.

I decided to make a blogging platform, one with music and videos among other things. I decided that I might have several entry pages, depending on the kind of media that is being presented. So, I needed to make grids I could just drop stuff into.

So, I made a Svelte component that I am calling The grid of things. grid-of-things. I figured that if you want an Internet of Things, you might want to see them, sometimes in a grid.

One cool thing about Svelte is that you can have a slot. A slot is called out in HTML within a component that you might use in your web page. It goes something like this:

... Bunches of HTML
<!- suddenly you are here -->
<slot>
Oh! My gosh! Someone forget to put a value in!
</slot>
Enter fullscreen mode Exit fullscreen mode

So, if that is part of a component, says "Surprises.svelte". Then when the application uses it, it can put in a value from the application. For example:

<script>
import * as everything from "Surprises.svelte"

// do JavaScript stuff.

</script>
<!-- now for the HMTL part of the app -->
<div>
<Surprises {...parameters_in_an_object} >
I did not forget to put values in!
</Surprises>
<span class="stylish">So there!</span>
</div
<style> 
  .stylish { font-style: cursive }
</style>

Enter fullscreen mode Exit fullscreen mode

It's like the Svelte components have attitude or something.

So, it's all great fun.

I was piking down the Svelte road, until one day...

I Needed Nested Tabs

Strange! A little hard to come by. Well, maybe its in some ultra large library that finishes loading on your page for that date you have with Rip Van Winkle. But, how about something simple?

Was making another little web app for sending message back and forth in a public P2P file system. It's doesn't have much purpose except replacing email with end to end encrypted contact mail that never touches a mail server. But, other than that it's just a page that needs nested tabs.

You can find that software in the copious-world repository called, interplanetary-contact.

I am going to make some changes to the code there. Maybe I will call it "intergalactic-contact" later on. I am going to start using UCWIDs and derived keys. But, that is for another conversation. Right now, I am worried about nested tabs.

Tabs and Panels

OK. You probably have a clue as to what I am talking about. But, just in case:

  • A tab is a likely a button that you will see in a row at the top of a display area.

  • A panel is like a box ('div' in HTML-ese) that contains information to be displayed when one of the tab buttons is clicked.

Here is a general idea:

<div class="tab-row">
<button on:click={activate_me}>tab 1</button>
<button on:click={activate_me}>tab 2</button>
<button on:click={activate_me}>tab 3</button>
</div>
<div id="tab1-panel"  class="panel"></div>
<div id="tab2-panel"  class="panel"></div>
<div id="tab3-panel"  class="panel"></div>
Enter fullscreen mode Exit fullscreen mode

So, the panels are visible one at a time, depending on the button that got clicked.

I Looked Around

I looked around and found some pretty good components that will make great tabs if you just use one layer. I used one, and just decided that all the page could be under separate tabs. If I recall (just months ago) I was hoping to keep the message lists under one tab with tabs for kinds of messages nested below it. I made some compromises.

I started wondering if maybe Svelte was just too tricky to work with. I started looking around again. I found nested tabs that look wonderful and are in vanilla JS or use jQuery. OK. But, I was hoping to keep things organized with in one scheme, not many.

I tried slots. So, in my grid-of-things component, there is a single slot that is replicated many times. In fact, it is the same object allowing in different data. But, all the data has the same structure. So, each presentation is similar, but it might have a different picture, SVG, or a different link to some media.

At first I thought named slots might work. But, really name slots are just like slots without names. They allow a datum to be parsed into them for a one view. Again the structure is expected to be the same. I tried using a formula for the name, but I got a message in the compiler saying "slot names cannot be dynamic". So, that is actually blocked. I was imaging an application loop going through a list of tabs somehow generating a list of matched slots in the components.

The Nested Tabs Need Different Components in Each Panel

Something more like this:

<div class="tab-row">
<button on:click={activate_me}>tab 1</button>
<button on:click={activate_me}>tab 2</button>
<button on:click={activate_me}>tab 3</button>
</div>
<div id="tab1-panel"  class="panel">{stuff_from_ace[1]}</div>
<div id="tab2-panel"  class="panel">{stuff_from_acme[0]}</div>
<div id="tab3-panel"  class="panel">{stuff_from_the_bay[4]}</div>
Enter fullscreen mode Exit fullscreen mode

Like that. One for each provider - say. Or, maybe they are all the same type. But, what you want to do is generate the panels in a for loop. The same for the buttons. Here is the Svelte way.

<div class="tab-row">
{#each tab_list as tab (tab.id)}
<button on:click={activate_me}>tab.name</button>
{/each}
</div>
{#each tab_list as tab (tab.id)}
<div id={tab.id) >
{tab.content}
</div>
{/each}

Enter fullscreen mode Exit fullscreen mode

So, right there where it says tab.content, you want something like a slot. But, you can't have a slot. At least you can't have a different slot at each point in the loop.

There is a Way

Svelte does have a trick up its sleeve, it's called svelte:component.

What you do with this is use it where you might put a slot. And, you tell it what component to put there. For example:

<svelte:component this={component} {...parameters} />
Enter fullscreen mode Exit fullscreen mode

Here, you bind "this" to the component to say what it's type is. And, you can pass parameters in a generic way, using the ellipsis. So, as long as you can pass a component to the tabs component, where the passed component is one imported into the Svelte app calling the nested tab component, you can put anything into the tab's panel. The parameters object would be passed as well. So, you get you data straight in the application and send it down.

For my Tabs component, I put in a loop to get my components lined up.

{#each tab_list as tab (tab.id) }
{#if active === tab.id }
<div class="tab-panel" style={(tab.style && tab.style.panel) ? tab.style.panel : (style && style.panel ? style.panel : "") }>
    {#if (tab_panels[tab.id] !== undefined) && tab_panels[tab.id] }
    <svelte:component this={tab_panels[tab.id].component} {...(tab_panels[tab.id].parameters)} />
    {:else}
        {@html tab.content}
    {/if}
</div>
{/if}
{/each}

Enter fullscreen mode Exit fullscreen mode

So, you see, I passed in a list of tabs, tab|_list and a map of tab|_panels. The tab|_panels have fields pointing to the component class, and field point to parameters to parameters that go into the component.

It Needs to be Said : the Component Parameter may be a Tabs Component

So, there you have it.

The application can now decide what it wants inside a Tabs component.

<script>
import Tabs from "svelte-nested-tabs"

</script>
<!-- now for the HMTL part of the app -->
<main>
    <h1>Hello {name}!</h1>
    <div class="tab-container" >
        <Tabs {...tab_def} />
    </div>  
</main>

Enter fullscreen mode Exit fullscreen mode

The innocuous variable, tab_def has a longish definition. One you might want to grab from a DB or something. Here is a version of one that does one nested tab.

let tab_list_def = {
        "tab_list" : [
            { 
                "id" : "l1_1",
                "name" : "tutorial",
                "content" : "not much"
            },
            { 
                "id" : "l1_2",
                "name" : "example",
                "content" : "won't see this"
            },
            { 
                "id" : "l1_3",
                "name" : "colors",
                "content" : "superfluous..."
            }
        ],
        "tab_panels" : {
            "l1_1" : false,
            "l1_2" : {
                "component" : Tabs,
                "parameters" : {
                    "tab_list" : [
                                    { 
                                        "id" : "l2_1",
                                        "name" : "jumping jack",
                                        "content" : "bouncy"
                                    },
                                    { 
                                        "id" : "l2_2",
                                        "name" : "nick nack",
                                        "content" : "likes momentos"
                                    }
                                ],
                    "tab_panels" : {
                        "l2_1" : false,
                        "l2_2" : false
                    },
                }
            }
        }
    }

Enter fullscreen mode Exit fullscreen mode

It looks longer than it is. But, you can load parts and put them together, and you don't have it in your code. Notice that tab_list_def.tab_panels.l1_2.component is a Tabs component, the same from the import statement.

Conclusion

You can customize it, too. There is room for improvement there if you want to help out.

You can find out more by checking out the repository: svelte-nested-tabs

And, you can get a working example from my brand new (not yet rewritten readme) example repository. svelte-examples. The example uses the styling capabilities.

Not bad for an afternoon! Gotta go!.

Hope you enjoy!

Thanks. Bye.

Top comments (0)

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.