{
  "id": "TD04",
  "title": "Side-by-side comparison",
  "topic": "tables",
  "use": "Two to five records stand side by side with the same criteria — the health insurers an employee could join, compared by contribution, sick pay and contact. The records are the columns and the criteria the rows, so every value stands on one line with the values it competes with, and the comparison ends in a decision under the table. Where the number of records varies from case to case, or the criteria differ from record to record, a fixed table does not hold; then the records are picked from a list first.",
  "decisions": [
    "The records are the columns and the criteria the rows: every value stands on one line with the values it is compared to, and each criterion has one label and one place for every record.",
    "Two to five columns. Choose health insurers says so in its subtitle and counts the columns in use: from six on every column is too narrow to read, and one column compares nothing.",
    "Outside two to five, Build comparison is unavailable and the footer says what to change: a comparison that cannot be read is not built.",
    "A row whose values differ stands in semibold, and a row that is the same for every record says so under its criterion: the eye goes to the differences, and the rows that decide nothing can be passed over.",
    "The column headers stay in view while the page scrolls: a value read without the name of its record belongs to nobody.",
    "The comparison ends in a decision under the table, where reading it ends, not in the toolbar: the line names the insurer with the lowest total contribution, and Adopt writes it to the employee the comparison was opened for. Without that button the comparison is information, and the result is carried elsewhere by hand.",
    "Adopt shows that it is saving and takes no second press meanwhile, and the result is confirmed in a toast: what changes is the employee's record, which is not on this screen.",
    "Cancel leaves the comparison as it was, without asking: the choice is a few ticks, and nothing that was typed is lost."
  ],
  "try": [
    {
      "do": "Look along the row Total contribution",
      "then": "Barmer has the lowest, 15.90%, and the line under the table names it and offers Adopt Barmer"
    },
    {
      "do": "Look at General contribution rate and Electronic reporting",
      "then": "their criterion says Same for all and their values stand in regular weight; the rows whose values differ stand in semibold"
    },
    {
      "do": "Press Adopt Barmer twice in a row",
      "then": "the button shows it is saving, the second press adopts nothing, and one toast says Barmer adopted for Luca Berger"
    },
    {
      "do": "Press Change selection",
      "then": "the dialog Choose health insurers has the three compared insurers ticked and counts 3 of 5 possible columns used"
    },
    {
      "do": "Tick IKK Südwest, DAK-Gesundheit and hkk Krankenkasse",
      "then": "the count says 6 chosen, Build comparison is unavailable, and the footer says Choose five at most"
    },
    {
      "do": "Uncheck IKK Südwest and press Build comparison",
      "then": "five insurers stand side by side, the toolbar counts 5 of 8 health insurers, and the line under the table now offers Adopt hkk Krankenkasse"
    },
    {
      "do": "Press Change selection and uncheck all but AOK Rheinland-Pfalz/Saarland",
      "then": "Build comparison is unavailable, and the footer says Choose at least two health insurers"
    },
    {
      "do": "Tick IKK Südwest and press Build comparison",
      "then": "two insurers remain, and six of the eight criteria say Same for both"
    },
    {
      "do": "Press Change selection, tick another insurer and press Cancel",
      "then": "the dialog closes without a question, and the comparison is as it was"
    }
  ],
  "revision": 1,
  "changes": [],
  "reviewed": false,
  "source": "<style>\n  /* Equal columns for the records, whatever their names; the criterion column keeps its width. */\n  #td04-table {\n    table-layout: fixed;\n  }\n\n  #td04-table thead th:first-child {\n    inline-size: 12rem;\n  }\n\n  /* The header row carries its own opaque background, so the rows pass under it. */\n  #td04-table thead th {\n    position: sticky;\n    inset-block-start: 0;\n    z-index: 1;\n  }\n</style>\n\n<div class=\"ed-stack ed-gap-4\">\n  <!-- Demo only, not part of the pattern. The screen's title, and whom the comparison is for, come from the screen around the pattern; SC01 places them. -->\n\n  <ed-toolbar label=\"Health insurers\">\n    <ed-button id=\"td04-print\" appearance=\"plain\">\n      <ed-icon slot=\"start\" name=\"print\"></ed-icon>\n      Print comparison\n    </ed-button>\n    <ed-button slot=\"view\" id=\"td04-change\" appearance=\"plain\">\n      <ed-icon slot=\"start\" name=\"edit\"></ed-icon>\n      Change selection\n    </ed-button>\n    <span id=\"td04-count\" slot=\"meta\" role=\"status\"></span>\n  </ed-toolbar>\n\n  <table id=\"td04-table\" aria-label=\"Health insurers compared\">\n    <thead></thead>\n    <tbody></tbody>\n  </table>\n\n  <div class=\"ed-flank:end ed-gap-3 ed-align-items-center\">\n    <p id=\"td04-best\"></p>\n    <ed-button id=\"td04-adopt\">\n      <ed-icon slot=\"start\" name=\"check\"></ed-icon>\n      <span id=\"td04-adopt-label\"></span>\n    </ed-button>\n  </div>\n\n  <ed-dialog\n    id=\"td04-dialog\"\n    label=\"Choose health insurers\"\n    sublabel=\"Five columns at most — beyond that the comparison can no longer be read\"\n    style=\"--ed-dialog-width: 32rem\"\n  >\n    <div class=\"ed-stack ed-gap-4\">\n      <p id=\"td04-capacity\" class=\"ed-text-tabular\" aria-live=\"polite\"></p>\n      <ed-checkbox-group id=\"td04-choice\" label=\"Health insurers\"></ed-checkbox-group>\n    </div>\n    <ed-button slot=\"footer\" id=\"td04-build\">Build comparison</ed-button>\n    <ed-button slot=\"footer\" appearance=\"plain\" data-dialog=\"close\">Cancel</ed-button>\n    <span slot=\"footer\" id=\"td04-reason\" class=\"ed-caption-s ed-align-self-center\"></span>\n  </ed-dialog>\n\n  <ed-toast-stack id=\"td04-toasts\"></ed-toast-stack>\n</div>\n\n<!-- Demo only, not part of the pattern. The product supplies CRITERIA, the rows of the comparison in their fixed order as { key, label, percent }, percent true where the value is a rate; funds, every health insurer that can be compared, as { id, name, generalRate, additionalRate, totalRate, sickPay, deductible, electronicReporting, noticePeriod, localContact }, rates in percent; compared, the ids of the insurers the comparison opens with; employee, the employee it was opened for, as { name }; and adoptFund(fund), which writes the insurer to that employee and resolves once it is saved. -->\n\n<script type=\"module\">\n  const table = document.getElementById('td04-table');\n  const count = document.getElementById('td04-count');\n  const best = document.getElementById('td04-best');\n  const adoptLabel = document.getElementById('td04-adopt-label');\n  const dialog = document.getElementById('td04-dialog');\n  const choice = document.getElementById('td04-choice');\n  const capacity = document.getElementById('td04-capacity');\n  const build = document.getElementById('td04-build');\n  const reason = document.getElementById('td04-reason');\n  const toasts = document.getElementById('td04-toasts');\n\n  // One column compares nothing; from six on, every column is too narrow to read.\n  const FEWEST = 2;\n  const MOST = 5;\n\n  const percent = new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2 });\n  const show = (criterion, fund) => (criterion.percent ? percent.format(fund[criterion.key] / 100) : fund[criterion.key]);\n  const byId = new Map(funds.map(fund => [fund.id, fund]));\n\n  let shown = compared.map(id => byId.get(id));\n\n  const lowestTotal = () => shown.reduce((lowest, fund) => (fund.totalRate < lowest.totalRate ? fund : lowest));\n\n  function cell(tag, text, attributes = {}) {\n    const element = document.createElement(tag);\n    for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value);\n    element.append(text);\n    return element;\n  }\n\n  // A row whose values differ stands in semibold; one that is the same for all says so in words, so\n  // the distinction does not rest on the weight alone.\n  function row(criterion) {\n    const values = shown.map(fund => show(criterion, fund));\n    const same = values.every(value => value === values[0]);\n    const header = cell('th', criterion.label, { scope: 'row' });\n    if (same) header.append(cell('div', shown.length === 2 ? 'Same for both' : 'Same for all', { class: 'ed-caption-s' }));\n    const element = document.createElement('tr');\n    element.append(header, ...values.map(value => cell('td', value, same ? {} : { class: 'ed-font-weight-semibold' })));\n    return element;\n  }\n\n  function render() {\n    const head = document.createElement('tr');\n    head.append(cell('th', 'Criterion', { scope: 'col' }), ...shown.map(fund => cell('th', fund.name, { scope: 'col' })));\n    table.tHead.replaceChildren(head);\n    table.tBodies[0].replaceChildren(...CRITERIA.map(row));\n\n    count.textContent = `${shown.length} of ${funds.length} health insurers · ${CRITERIA.length} criteria`;\n    const lowest = lowestTotal();\n    best.textContent = `Lowest total contribution: ${lowest.name}, ${percent.format(lowest.totalRate / 100)}`;\n    adoptLabel.textContent = `Adopt ${lowest.name}`;\n  }\n\n  // The choice lists every insurer in the product's order, and the columns keep that order.\n  const boxes = funds.map(fund => {\n    const box = document.createElement('ed-checkbox');\n    box.value = fund.id;\n    box.hint = `Additional contribution ${percent.format(fund.additionalRate / 100)}`;\n    box.textContent = fund.name;\n    return box;\n  });\n  choice.append(...boxes);\n\n  const chosen = () => boxes.filter(box => box.checked).map(box => byId.get(box.value));\n\n  function update() {\n    const number = chosen().length;\n    capacity.textContent =\n      number > MOST\n        ? `${number} chosen, ${MOST} columns possible · from six on, every column is too narrow`\n        : `${number} of ${MOST} possible columns used · from six on, every column is too narrow`;\n    build.disabled = number < FEWEST || number > MOST;\n    reason.textContent = number < FEWEST ? 'Choose at least two health insurers.' : number > MOST ? 'Choose five at most.' : '';\n  }\n\n  document.getElementById('td04-change').addEventListener('click', () => {\n    for (const box of boxes) box.checked = shown.includes(byId.get(box.value));\n    update();\n    dialog.open = true;\n  });\n\n  choice.addEventListener('change', update);\n\n  build.addEventListener('click', () => {\n    shown = chosen();\n    dialog.open = false;\n    render();\n  });\n\n  document.getElementById('td04-print').addEventListener('click', () => window.print());\n\n  // The comparison ends where it was opened: the insurer goes to the employee's record, which is not\n  // on this screen, so the toast is the confirmation.\n  // A second press while the first is saving would write the insurer twice.\n  const adopt = document.getElementById('td04-adopt');\n  adopt.addEventListener('click', async () => {\n    if (adopt.loading) return;\n    const fund = lowestTotal();\n    adopt.loading = true;\n    await adoptFund(fund);\n    adopt.loading = false;\n    toasts.create({ variant: 'success', title: `${fund.name} adopted for ${employee.name}` });\n  });\n\n  render();\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/tables/td04-side-by-side-comparison/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/tables/"
}
