0
votes

I have an array of non-homogeneous objects that are each rendered in a loop using a <svelte:component this={type}> component, and I would like to group adjacent things that are of the same type within a div.

For example, I have some code similar to this:

<script>
let things = [
  {type: A, content: "One"},
  {type: B, content: "Two"},
  {type: B, content: "Three"},
  {type: A, content: "Four"}
];
</script>

{#each things as thing, i}
  {()=>someMagicHere1(things, thing, i)}
  <svelte:component this={thing.type}>
  {()=>someMagicHere2(things, thing, i)}
{/each}

And I want the output to group the things like so:

<div class="group type-A">
  <div class="thing type-A">One</div>
</div>
<div class="group type-B">
  <div class="thing type-B">Two</div>
  <div class="thing type-B">Three</div>
</div>
<div class="group type-A">
  <div class="thing type-A">Four</div>
</div>

In the things array, the things are not sorted (actually, they are, but by date, unrelated to their type), but the idea is to visually group together the ones that are the same type. Ideally, I'd be able to group only certain types (like group all of type A, but type B's would remain separate), but I feel like I would be able to derive a separate solution if I could group at all. There are also more than two types; this is just a minimal sample.

In Svelte, the individual A and B components can't have partial HTML elements like this inside, because Svelte won't allow conditionals around unclosed elements:

{#if groupStart}
<div class="group">
{/if}

From within each someMagicHereX() I could output some HTML with {@html customTags} to get the DOM output that I want, but then I lose the style encapsulation and other Svelte component benefits.

What I'd really like is a more "sveltian" solution. Perhaps I need to create something new with use? Anyone have any good ideas?

Update: A key feature I seem to have left out is that any controls must ultimately bind to the original dataset. So even if the data is transformed somehow, the original data must be updated at runtime and vice-versa on any bound controls.

1

1 Answers

1
votes

Not absolutely sure if the following satisfies what you are looking for, as I can't tell from your question if you want to keep the order of values untouched or if you want to reorder them by group first.

The idea is to re-arrange your data in order to loop through it in the way you want to. It's always easier to manipulate data in order to fit a layout than the other way around.

The following would turn your initial array into an array with the following structure:

[
  {
    cssClass: 'type-A',
    values: [ /* all objects of type 'A' */ ],
  },
  {
    cssClass: 'type-B',
    values: [ /* all objects of type 'B' */ ],
  },
  // etc.
]

The data transformation is pretty straightforward:

let groups = things.reduce((curr, val) => {
  let group = curr.find(g => g.cssClass === `type-${val.type}`)
  if (group)
    group.values.push(val)
  } else {
    curr.push({ cssClass: `type-${val.type}`, values: [ val ] }) 
  }
  return curr
}, [])

With this new data structure available, it's fairly easy to achieve the layout you had in mind:

{#each groups as group}
  <div class="group {group.cssClass}">
    {#each group.values as value}
      <div class="thing {group.cssClass}">
        {value.content}
      </div>
    {/each}
  </div>
{/each}

Demo REPL

Edit: If you prioritize the order of objects as it stands in your initial array, the data transformation would be slightly different. Basically, you'd want to create a new 'group' every time the type changes.

Something like the following would do the trick (note that the svelte #each structure would remain the same, only the data transformation changes):

let groups = things.reduce((curr, val) => {
  let group = curr.length ? curr[curr.length - 1] : undefined 
  if (group && group.cssClass === `type-${val.type}`) {
    group.values.push(val)
  } else {
    curr.push({ cssClass: `type-${val.type}`, values: [ val ] }) 
  }
  return curr
}, [])

Option 2 Demo REPL