{
  "id": "BE04",
  "title": "Selection in the dialog",
  "topic": "bulk-editing",
  "use": "The bulk edit that carries its selection in the dialog. Above the list stands an action that works at once; the dialog then asks in two sections — first \"Selection\", where nobody is preselected and the records are found in a combobox, then \"Change values\" with everything that can be changed. Take this pattern when the list is too long to tick off. Whoever picks single records in the list takes BE02.",
  "decisions": [
    "The dialog has two sections, each with a heading: Selection and Change values answer two different questions — whom does it reach, and what becomes of them.",
    "Nobody is selected when the dialog opens: the accent button is unavailable and the footer says what is missing. 'All' preselected is the one default that must not be confirmed by accident.",
    "People are chosen in a combobox, not in a list with checkboxes: among hundreds of people typing is the shortest way to three. The list has no checkboxes, because two ways to select would be two truths about the same set.",
    "The personnel number comes first in the option text, then name, department and location: names repeat, and the number tells them apart. It stays visible when a chip is shortened.",
    "Every changeable field starts at Leave unchanged, so a field with a preset value cannot be written along by accident.",
    "The number on the accent button counts what really changes, with the exceptions taken off.",
    "Exceptions depend on what is changed: a lead role keeps its department until the lead is filled again, while location and role still change. Changing only the location has no exception."
  ],
  "try": [
    {
      "do": "Choose Change employees",
      "then": "the dialog opens with an empty selection, the accent button is unavailable, and the footer says why"
    },
    {
      "do": "Type Brandt or 10260 into the combobox",
      "then": "the search covers name and personnel number, and the match becomes a chip"
    },
    {
      "do": "Pick two or three people",
      "then": "the footer counts along, and the number is the one that really gets changed"
    },
    {
      "do": "Under Change values, set only Location",
      "then": "the other two fields stay on Leave unchanged and write nothing"
    },
    {
      "do": "Pick a team lead and change Department",
      "then": "the warning names the exception and its reason, and the number in the footer drops"
    },
    {
      "do": "Try the same case with only a change of Location",
      "then": "no exception, since the rule hangs on the department"
    },
    {
      "do": "Click beside the dialog",
      "then": "a question asks before the entries are discarded, and Keep editing goes back with everyone and every value kept"
    },
    {
      "do": "Choose the accent button",
      "then": "the list stays where it was, and the message names each exception with personnel number and department, as a list to copy"
    }
  ],
  "revision": 1,
  "changes": [],
  "reviewed": true,
  "source": "<!-- Demo only, not part of the pattern. The table takes the height the screen leaves it; here it is set to fit the frame. -->\n\n<div class=\"ed-stack ed-gap-5\">\n  <!-- Demo only, not part of the pattern. The screen's title, and whose data it shows, come from the screen around the pattern; SC01 places them. -->\n\n  <ed-data-grid id=\"be04-grid\" label=\"Employees\" row-key=\"pnr\" with-search data-ed-preload=\"ed-format-date\">\n    <ed-button slot=\"toolbar-actions\" id=\"be04-open\">\n      <ed-icon slot=\"start\" name=\"edit\"></ed-icon>\n      Change employees\n    </ed-button>\n  </ed-data-grid>\n\n  <ed-notification id=\"be04-result\" variant=\"warning\" closable hidden>\n    <span id=\"be04-result-title\"></span>\n    <span slot=\"description\" id=\"be04-result-text\"></span>\n    <ul slot=\"description\" id=\"be04-kept\"></ul>\n    <ed-copy-button slot=\"action\" id=\"be04-copy\">\n      <ed-button appearance=\"plain\" density=\"compact\">\n        <ed-icon slot=\"start\" name=\"copy\"></ed-icon>\n        Copy list\n      </ed-button>\n    </ed-copy-button>\n  </ed-notification>\n</div>\n\n<ed-dialog id=\"be04-dialog\" light-dismiss label=\"Change employees\" style=\"--ed-dialog-width: 46rem\">\n  <div class=\"ed-stack ed-gap-5\">\n    <section class=\"ed-stack ed-gap-4\">\n      <div class=\"ed-stack ed-gap-1\">\n        <h3>Selection</h3>\n        <p class=\"ed-caption-s\">Nobody is selected when the dialog opens. Type a name or a personnel number — the matches then stand as chips in the field.</p>\n      </div>\n      <div class=\"ed-stack ed-gap-1\">\n        <ed-combobox id=\"be04-who\" label=\"Employees\" multiple with-clear max-options-visible=\"4\" placeholder=\"Enter a name or personnel number\"></ed-combobox>\n        <p id=\"be04-count\" class=\"ed-caption-s ed-text-tabular\" aria-live=\"polite\"></p>\n      </div>\n    </section>\n\n    <ed-divider></ed-divider>\n\n    <section class=\"ed-stack ed-gap-4\">\n      <div class=\"ed-stack ed-gap-1\">\n        <h3>Change values</h3>\n        <p class=\"ed-caption-s\">Every field starts at Leave unchanged. Whatever stays there is not written.</p>\n      </div>\n      <div class=\"ed-grid ed-gap-4\" style=\"--ed-grid-min-column-size: 14rem\">\n        <ed-select name=\"department\" label=\"Department\" value=\"unchanged\">\n          <ed-option value=\"unchanged\">Leave unchanged</ed-option>\n        </ed-select>\n        <ed-select name=\"location\" label=\"Location\" value=\"unchanged\">\n          <ed-option value=\"unchanged\">Leave unchanged</ed-option>\n        </ed-select>\n        <ed-combobox name=\"role\" label=\"Role\" value=\"unchanged\">\n          <ed-option value=\"unchanged\">Leave unchanged</ed-option>\n        </ed-combobox>\n      </div>\n      <ed-notification id=\"be04-exceptions\" variant=\"warning\" hidden>\n        <span id=\"be04-exceptions-title\"></span>\n        <span slot=\"description\">A lead role changes department only once the lead is filled again. Everything else in this change is set for them.</span>\n      </ed-notification>\n    </section>\n  </div>\n\n  <ed-button slot=\"footer\" id=\"be04-apply\"></ed-button>\n  <ed-button slot=\"footer\" appearance=\"outlined\" data-dialog=\"close\">Close without changes</ed-button>\n  <span slot=\"footer\" id=\"be04-reason\" class=\"ed-caption-s ed-align-self-center\"></span>\n</ed-dialog>\n\n<ed-alert-dialog id=\"be04-discard\" light-dismiss type=\"warning\" label=\"Discard your entries?\" confirm-text=\"Discard entries\" cancel-text=\"Keep editing\">\n  The people and values you chose are not changed.\n</ed-alert-dialog>\n\n<ed-toast-stack id=\"be04-toasts\"></ed-toast-stack>\n\n<!-- Demo only, not part of the pattern. The product supplies employees, the staff register as { pnr, name, department, role, location, start } with the start date as YYYY-MM-DD; and fieldValues, the values each changeable field offers, as lists under department, location and role. -->\n\n<script type=\"module\">\n  const day = value =>\n    Object.assign(document.createElement('ed-format-date'), { date: value, month: 'short', day: 'numeric', year: 'numeric' });\n  const count = (n, noun) => `${n} ${noun}${n === 1 ? '' : 's'}`;\n  const people = n => (n === 1 ? '1 person' : `${n} people`);\n\n  const byNumber = new Map(employees.map(person => [person.pnr, person]));\n  const isLead = person => / lead$/.test(person.role);\n\n  const grid = document.getElementById('be04-grid');\n  const open = document.getElementById('be04-open');\n  const dialog = document.getElementById('be04-dialog');\n  const who = document.getElementById('be04-who');\n  const chosenCount = document.getElementById('be04-count');\n  const fields = [...dialog.querySelectorAll('ed-select[name], ed-combobox[name]')];\n  const exceptionsNote = document.getElementById('be04-exceptions');\n  const exceptionsTitle = document.getElementById('be04-exceptions-title');\n  const reason = document.getElementById('be04-reason');\n  const apply = document.getElementById('be04-apply');\n  const result = document.getElementById('be04-result');\n  const resultTitle = document.getElementById('be04-result-title');\n  const resultText = document.getElementById('be04-result-text');\n  const kept = document.getElementById('be04-kept');\n  const copy = document.getElementById('be04-copy');\n  const toasts = document.getElementById('be04-toasts');\n  const discard = document.getElementById('be04-discard');\n  // Set once closing is meant to lose nothing, or the user has agreed to discard the entries.\n  let allowClose = false;\n  const entered = () => [who.value ?? []].flat().length > 0 || fields.some(field => field.value !== 'unchanged');\n\n  grid.columns = [\n    { field: 'pnr', label: 'Personnel no.', rowheader: true, pinned: 'left', width: 176 },\n    { field: 'name', label: 'Name', flex: 2, minWidth: 208 },\n    { field: 'department', label: 'Department', width: 176 },\n    { field: 'role', label: 'Role', width: 208 },\n    { field: 'location', label: 'Location', width: 144 },\n    { field: 'start', label: 'Start date', width: 140, pinned: 'right', sortFn: 'datetime', formatter: day }\n  ];\n  grid.data = employees;\n\n  for (const field of fields) {\n    for (const value of fieldValues[field.getAttribute('name')]) {\n      const option = Object.assign(document.createElement('ed-option'), { textContent: value });\n      option.setAttribute('value', value);\n      field.append(option);\n    }\n  }\n  for (const person of employees) {\n    const option = Object.assign(document.createElement('ed-option'), {\n      textContent: `${person.pnr} · ${person.name} — ${person.department}, ${person.location}`\n    });\n    option.setAttribute('value', person.pnr);\n    who.append(option);\n  }\n\n  const chosen = () => [who.value ?? []].flat().map(pnr => byNumber.get(pnr)).filter(Boolean);\n  const changes = () => Object.fromEntries(fields.filter(field => field.value !== 'unchanged').map(field => [field.name, field.value]));\n  // A lead role keeps its department until the lead is filled again; location and role still change.\n  const exceptionsOf = (selection, change) =>\n    change.department ? selection.filter(person => isLead(person) && person.department !== change.department) : [];\n\n  function update() {\n    const selection = chosen();\n    const change = changes();\n    const exceptions = exceptionsOf(selection, change);\n    const effective = selection.length - exceptions.length;\n    const nothing = Object.keys(change).length === 0;\n\n    chosenCount.textContent = selection.length ? `${people(selection.length)} selected` : 'Nobody selected';\n    exceptionsNote.hidden = !exceptions.length;\n    exceptionsNote.open = exceptions.length > 0;\n    exceptionsTitle.textContent = exceptions.length === 1 ? '1 person keeps their department' : `${exceptions.length} people keep their department`;\n\n    // The number counts the result, not the selection: the exceptions are taken off.\n    apply.textContent = `Change ${people(Math.max(0, effective))}`;\n    apply.disabled = selection.length === 0 || nothing || effective === 0;\n    reason.textContent = !selection.length\n      ? 'Select at least one person.'\n      : nothing\n        ? 'Set at least one value.'\n        : effective === 0\n          ? 'This change reaches nobody in the selection.'\n          : '';\n  }\n\n  open.addEventListener('click', () => {\n    allowClose = false;\n    who.value = [];\n    for (const field of fields) field.value = 'unchanged';\n    update();\n    dialog.open = true;\n  });\n\n  for (const control of [who, ...fields]) {\n    control.addEventListener('change', update);\n  }\n\n  apply.addEventListener('click', () => {\n    const selection = chosen();\n    const change = changes();\n    const exceptions = exceptionsOf(selection, change);\n    let changed = 0;\n\n    for (const person of selection) {\n      const kept = exceptions.includes(person);\n      for (const [key, value] of Object.entries(change)) {\n        if (!(key === 'department' && kept)) person[key] = value;\n      }\n      if (!kept || Object.keys(change).some(key => key !== 'department')) changed += 1;\n    }\n    allowClose = true;\n    dialog.open = false;\n    grid.data = employees;\n\n    result.hidden = !exceptions.length;\n    result.open = exceptions.length > 0;\n    if (!exceptions.length) {\n      toasts.create({ variant: 'success', duration: 4000, title: `${count(changed, 'employee')} changed` });\n      return;\n    }\n    resultTitle.textContent = `${changed} of ${count(selection.length, 'employee')} changed`;\n    resultText.textContent = `${people(exceptions.length)} with a lead role ${exceptions.length === 1 ? 'keeps' : 'keep'} their department:`;\n    // The names, not only a count: whoever follows up has to know whom it concerns.\n    kept.replaceChildren(\n      ...exceptions.map(person =>\n        Object.assign(document.createElement('li'), { textContent: `${person.pnr} · ${person.name} — stays in ${person.department}` })\n      )\n    );\n    copy.value = exceptions.map(person => `${person.pnr}\\t${person.name}\\t${person.department}`).join('\\n');\n  });\n\n  result.addEventListener('ed-after-hide', () => {\n    result.hidden = true;\n  });\n\n  // Every way of closing — Close without changes, Escape, a click beside — asks once someone or a\n  // value is chosen.\n  dialog.addEventListener('ed-hide', event => {\n    if (event.target !== dialog || allowClose || !entered()) return;\n    event.preventDefault();\n    discard.open = true;\n  });\n\n  discard.addEventListener('ed-alert-dialog-confirm', () => {\n    allowClose = true;\n  });\n\n  discard.addEventListener('ed-after-hide', () => {\n    if (allowClose) dialog.open = false;\n  });\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/bulk-editing/be04-selection-in-the-dialog/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/bulk-editing/"
}
