Home > Back-end >  How to display as much as item fit in one line and if all don't fit in show the number of remai
How to display as much as item fit in one line and if all don't fit in show the number of remai

Time:08-07

I have just started learning Svelte. I want to display as much as email fit in one line and show the remaining emails with a number like this: https://imgur.com/ro8eTPM

I want achieve this without modifying the code in the parent component. I just want to change the DisplayEmail component.

So far I have done this: https://imgur.com/qEeY1q7

<!-- Parent component -->
<script lang="ts">
  import DisplayEmail from 'components/DisplayEmail.svelte'

const emailList = [
    ['[email protected]', '[email protected]', '[email protected]', '[email protected]'],
  ]
</script>

<style lang="scss">
  $border-style: solid 1px #777;
  table {
    table-layout: fixed;
    border: $border-style;
    width: 100%;
    text-align: left;
  }
  th,
  td {
    border: $border-style;
    padding: 5px 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    height: 34px;
    box-sizing: border-box;
  }
  th {
    &:nth-child(1) {
      width: 20%;
    }
    &:nth-child(2) {
      width: 30%;
    }
    &:nth-child(3) {
      width: 50%;
    }
    &:nth-child(4) {
      width: 90px;
    }
    &:nth-child(5) {
      width: 70px;
    }
  }
  tbody:nth-child(even) {
    background-color: #ddd;
  }
  .align-right {
    text-align: right;
  }
</style>

<table cellspacing="0">
  <thead>
    <tr>
      <th>Sender</th>
      <th>Recipients</th>
      <th>Subject</th>
      <th >Date</th>
      <th >Time</th>
    </tr>
  </thead>
  <tbody>
    {#each emailList as emails}
      <tr>
        <td>[email protected]</td>
        <td>
          <DisplayEmail {emails} />
        </td>
        <td>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim, odio.
        </td>
        <td >n/a</td>
        <td >n/a</td>
      </tr>
    {/each}
  </tbody>
</table>
<!--DisplayEmail Component -->

<script lang="ts">
  export let emails
  let showEmail = emails.map((email: string) => email)
  let spanWidth
</script>

<span bind:offsetWidth={spanWidth}>{showEmail}</span>

CodePudding user response:

This can be a bit complicated.

For one, showing the number takes away from the available space, which could cause one more item to be truncated, so trying to simply calculate sizes and trying to figure out how many would fit would be difficult.

The easiest method probably would be to:

  • Set up a wrapper element around everything whose size changes trigger a re-fitting of the items.
  • Set up a wrapper around the items text which can be used to detect an overflow
  • Reduce the amount of items incrementally until no more overflow occurs.

The structure would be something like this:

<span  bind:this={root}>
    <span  bind:this={outer}>
        <span  bind:this={inner}>
            {emailsString}
        </span>
    </span>

    <span  bind:this={counter}
                class:show={overflow}>
             {emails.length - displayedEmailCount}
    </span>
</span>

The code to update everything could use ResizeObservers like this:

onMount(() => {
    const containerObserver = new ResizeObserver(throttle(refit));
    containerObserver.observe(root);

    const overflowObserver = new ResizeObserver(throttle(updateDisplayCount));
    overflowObserver.observe(outer);
    overflowObserver.observe(inner);
    overflowObserver.observe(counter);
    
    return () => {
        containerObserver.disconnect();
        overflowObserver.disconnect();
    }
});

async function refit() {
    // Container size changed, reset display count because potentially
    // more items could fit now.
    displayedEmailCount = emails.length;
    await tick(); // Lets Svelte update the DOM
    updateDisplayCount();
}
async function updateDisplayCount() {
    if (inner == null || outer == null)
        return;
    
    // Reduce count until items fit or no items are displayed
    while (inner.offsetWidth > outer.offsetWidth && displayedEmailCount > 0) {
        displayedEmailCount -= 1;
        await tick(); // Lets Svelte update the DOM
    }
}

$: displayedEmailCount = emails.length;
$: overflow = emails.length > displayedEmailCount;
$: emailsString = emails.slice(0, displayedEmailCount).join(', ')   overflowSuffix;
$: overflowSuffix =
    overflow == false ? '' :
    displayedEmailCount > 0 ? ', ...' : '...';

(throttle is a function that ensures that code is only executed at most once per frame. Can be a performance optimization when an API calls a function/raises an event many times per frame.)

This could still be further improved, e.g. by only resetting the display count when the container grows, not when it shrinks.

Full REPL example - Resize the splitter between code and rendered result to cause trunctation.

  • Related