Argle
Argle

Reputation: 324

Dividing lists in Svelte

I had a recent urge to play with reading and formatting a GEDCOM (Genealogy) file and I'm using Svelte to accomplish this. But I'm stuck on a problem I'm not quite sure the best way to do it. Extracted from the GEDCOM file is a collection of names sorted by surnames. Let's say it's this:

Adams, John
Anderson, Neo
Cash, Johnny
Clapton, Eric
Cross, Christopher
Denver, John

The real list is quite a bit bigger and needs indicators for the letters of the alphabet. Essentially, I want to have the following as output:

Names starting with A:
Adams, John; Anderson, Neo;

Names starting with C:
Cash, Johnny; Clapton, Eric; Cross, Christopher;

Names starting with D:
Denver, John;

I have no problem spewing out the list. Svelte makes that easy enough. What I haven't figured out yet is how to interject the breaks. Each thing I've tried or thought about seems to be flawed. As far as I can tell, there's no good way to simply alter a variable as a loop progresses and there doesn't seem to be a way to get the previous item in the loop. Any suggestions for an approach? I currently have this, but where to begin injecting the header?

    {#each data.sort(compare) as n }
        {#if n.ReverseName }
            {n.ReverseName}  
        {/if}
    {/each}

Upvotes: 0

Views: 48

Answers (1)

Thomas Hennes
Thomas Hennes

Reputation: 9979

First of all, you want to ask yourself, "what is the format of the data output I want to obtain?". Considering your question and your objective to be able to loop through the output, you'd probably want the following structure:

output = [
  [array of names starting with A, if any],
  [array of names starting with B, if any],
  etc.
]

I believe a relatively straightforward way to achieve this is with a reducer:

// start with sorting the input array so names are
// already properly sorted before being input to our reducer
const output = input.sort().reduce((acc, current) => {
  // if the accumulator is an empty array,
  // return the current value in a new sub array
  // (the very first name pushed into the very first sub array)
  if (acc.length === 0) {
    return [ [ current ] ];
  }
  // otherwise, if the initial of the first name in
  // the current (i.e. last created) sub array matches
  // the initial of the current value, add that current
  // value to the current sub array
  const [ currentSub ] = acc.slice(-1);
  if (currentSub[0].charAt(0) === current.charAt(0)) {
    return [ ...acc.slice(0, -1), [ ...currentSub, current ] ]; 
  }
  // else add a new sub array and initialise it with the current value
  return [ ...acc, [ current ] ];
}, []); // initialise with an empty array

The output array will have the desired shape [ [names starting with A, if any], [names starting with B, if any], ... ] and all that's left is for you to iterate through that output array:

{#each output as letterArray}
  <p>Names starting with <strong>{letterArray[0].charAt(0)}</strong>:</p>
  <p>{letterArray.join('; ')}</p>
{/each}

Or if you want to iterate over individual names, add an inner each block that iterates over letterArray. Many possibilities exist once your data is shaped the way you want it to be.

Demo REPL

Edit:

If input is dynamic (i.e. it is updated through the life of the component, either via a fetch or because it is received as a prop that might get updated), you can keep output automatically updated by turning its declaration into reactive code:

$: output = input.sort().reduce(...same code as above...)

Upvotes: 1

Related Questions