{
  "id": "F01",
  "title": "Standard form",
  "topic": "forms",
  "use": "Our forms follow a twelve-column grid. Every form has buttons that complete the entry or discard it.",
  "decisions": [
    "Up to one or two screen heights a form is this pattern; above that it is F02 with its navigation aid: the height is what the user scrolls, and twelve fields with long labels and hints are taller than twenty short ones.",
    "The rows sit on the twelve-column grid, and the fields size themselves in steps on the form's own width: on a narrow form short fields widen to half a row, and no row holds more than two.",
    "Creating and editing are one pattern, not two: the same sections, rows and columns, so whoever created the record finds every field in the same place when editing it. They differ in four things — the start (empty or filled), the closing label (Create client or Save changes), what a discard protects (the entries, or the changes it names), and the way back (to the list, or to the record).",
    "The closing actions sit in a toolbar that holds at the top while the form scrolls, so they stay in reach however far down the user is.",
    "The closing actions are disabled until a value differs from where the form started, and again once that value is typed back: the buttons show whether there is anything to save, and while they are disabled an ed-tooltip on each says why: Nothing has changed. The tooltips stand outside the bar, since in the action slot they would count as actions.",
    "Saving checks the required fields only when pressed: the first missing field says what is missing and takes the focus, and nothing is saved.",
    "Discarding asks first in a warning dialog that names what is lost — the changed fields when editing, the entries when creating — because it cannot be undone."
  ],
  "try": [
    {
      "do": "Change the street, then type the old value back in",
      "then": "Save changes and Discard changes become available with the change and are disabled again once nothing differs from what was saved; pointed at while disabled, each says “Nothing has changed”"
    },
    {
      "do": "Empty Company name and press Save changes",
      "then": "the field says what is missing and takes the focus, and nothing is saved"
    },
    {
      "do": "Change two fields and press Discard changes",
      "then": "a warning names both fields first; Discard changes puts them back to their saved values, Keep editing leaves the change"
    },
    {
      "do": "Change Company name and press Save changes",
      "then": "a toast confirms, the page title shows the new name, and both actions are disabled again"
    },
    {
      "do": "Switch the case to Create",
      "then": "the same form starts empty, the actions read Create client and Discard entries, and the way back leads to the client list instead of the client"
    },
    {
      "do": "Fill in the required fields and press Create client",
      "then": "a toast confirms the new client, and the empty form is ready for the next one"
    },
    {
      "do": "Scroll down",
      "then": "the toolbar holds at the top while the way back and the title scroll away"
    },
    {
      "do": "Open the pattern in a new tab and narrow the window",
      "then": "the short fields widen to half the row, and no row holds more than two fields"
    }
  ],
  "revision": 2,
  "changes": [
    {
      "revision": 2,
      "change": "Why Save changes and Discard changes are disabled is an ed-tooltip on each, outside the toolbar, not the buttons' title; it shows only while the button is disabled."
    }
  ],
  "reviewed": true,
  "source": "<style>\n  .f01-toolbar {\n    position: sticky;\n    inset-block-start: 0;\n    z-index: 4;\n    background: var(--ed-color-surface-default);\n  }\n</style>\n\n<div class=\"ed-stack ed-gap-4\">\n\n  <div class=\"ed-stack ed-gap-2\">\n    <p><a id=\"f01-back\" href=\"#\">Back to client 104721</a></p>\n    <div class=\"ed-cluster ed-gap-3 ed-align-items-baseline\">\n      <hgroup class=\"ed-stack ed-gap-0\">\n        <h1 id=\"f01-title\">Bergmann &amp; Sons Vehicle Engineering GmbH &amp; Co. KG</h1>\n        <p id=\"f01-subtitle\" class=\"ed-caption-m\">Client 104721 · Saarbrücken office</p>\n      </hgroup>\n      <ed-badge id=\"f01-badge\" variant=\"success\">Active</ed-badge>\n    </div>\n  </div>\n\n  <div class=\"f01-toolbar ed-py-2\">\n    <ed-toolbar id=\"f01-toolbar\" label=\"Client 104721\">\n      <ed-button id=\"f01-save\" disabled><ed-icon slot=\"start\" name=\"save\"></ed-icon><span id=\"f01-save-label\">Save changes</span></ed-button>\n      <ed-divider orientation=\"vertical\"></ed-divider>\n      <ed-button id=\"f01-discard\" appearance=\"plain\" disabled><ed-icon slot=\"start\" name=\"undo\"></ed-icon><span id=\"f01-discard-label\">Discard changes</span></ed-button>\n    </ed-toolbar>\n    <ed-tooltip for=\"f01-save\">Nothing has changed</ed-tooltip>\n    <ed-tooltip for=\"f01-discard\">Nothing has changed</ed-tooltip>\n  </div>\n\n  <form id=\"f01-form\" class=\"ed-stack ed-gap-8\" style=\"max-inline-size: 64rem\">\n    <section class=\"ed-stack ed-gap-4\">\n      <div class=\"ed-stack ed-gap-1\">\n        <h2>Client</h2>\n        <p class=\"ed-caption-m\">Company name and legal form appear in letters and reports exactly as they stand here.</p>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-input class=\"ed-span-sm-6 ed-span-md-3\" name=\"number\" label=\"Client number\" value=\"104721\" hint=\"Assigned on April 1, 2019; it cannot be changed\" readonly></ed-input>\n        <ed-input id=\"f01-company\" class=\"ed-span-sm-6 ed-span-md-9\" name=\"company\" label=\"Company name\" value=\"Bergmann &amp; Sons Vehicle Engineering GmbH &amp; Co. KG\" required></ed-input>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-select class=\"ed-span-sm-6\" name=\"legal-form\" label=\"Legal form\" value=\"gmbh-co-kg\" required>\n          <ed-option value=\"sole\">Sole proprietorship</ed-option>\n          <ed-option value=\"ek\">e. K.</ed-option>\n          <ed-option value=\"gbr\">GbR</ed-option>\n          <ed-option value=\"ohg\">OHG</ed-option>\n          <ed-option value=\"kg\">KG</ed-option>\n          <ed-option value=\"gmbh\">GmbH</ed-option>\n          <ed-option value=\"gmbh-co-kg\">GmbH &amp; Co. KG</ed-option>\n          <ed-option value=\"ug\">UG (haftungsbeschränkt)</ed-option>\n          <ed-option value=\"ag\">AG</ed-option>\n          <ed-option value=\"eg\">eG</ed-option>\n        </ed-select>\n        <ed-combobox class=\"ed-span-sm-6\" name=\"industry\" label=\"Industry\" value=\"45203\" hint=\"WZ 2008\">\n          <ed-option value=\"45203\">45.20.3 — Maintenance and repair of motor vehicles</ed-option>\n          <ed-option value=\"45111\">45.11.1 — Sale of motor vehicles</ed-option>\n          <ed-option value=\"46690\">46.69.0 — Wholesale of machinery</ed-option>\n          <ed-option value=\"25620\">25.62.0 — Machining of metals</ed-option>\n          <ed-option value=\"71121\">71.12.1 — Engineering offices for overall building design</ed-option>\n        </ed-combobox>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-input class=\"ed-span-sm-6\" name=\"register\" label=\"Commercial register\" value=\"HRA 8241\"></ed-input>\n        <ed-select class=\"ed-span-sm-6\" name=\"court\" label=\"Register court\" value=\"saarbruecken\">\n          <ed-option value=\"saarbruecken\">Saarbrücken</ed-option>\n          <ed-option value=\"saarlouis\">Saarlouis</ed-option>\n          <ed-option value=\"homburg\">Homburg</ed-option>\n          <ed-option value=\"neunkirchen\">Neunkirchen</ed-option>\n        </ed-select>\n      </div>\n    </section>\n\n    <section class=\"ed-stack ed-gap-4\">\n      <div class=\"ed-stack ed-gap-1\">\n        <h2>Address</h2>\n        <p class=\"ed-caption-m\">The registered office moved in September. The old address stays in this year's reports until the end of the year.</p>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-input class=\"ed-span-sm-6 ed-span-md-9\" name=\"street\" label=\"Street\" value=\"Halbergstraße\" required></ed-input>\n        <ed-input class=\"ed-span-sm-6 ed-span-md-3\" name=\"house-number\" label=\"House number\" value=\"112\" required></ed-input>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-input class=\"ed-span-sm-6 ed-span-md-3\" name=\"postcode\" label=\"Postcode\" value=\"66121\" required></ed-input>\n        <ed-input class=\"ed-span-sm-6 ed-span-md-9\" name=\"city\" label=\"City\" value=\"Saarbrücken\" required></ed-input>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-select class=\"ed-span-sm-6 ed-span-md-3\" name=\"country\" label=\"Country\" value=\"de\" required>\n          <ed-option value=\"de\">Germany</ed-option>\n          <ed-option value=\"fr\">France</ed-option>\n          <ed-option value=\"lu\">Luxembourg</ed-option>\n          <ed-option value=\"be\">Belgium</ed-option>\n          <ed-option value=\"at\">Austria</ed-option>\n          <ed-option value=\"ch\">Switzerland</ed-option>\n        </ed-select>\n      </div>\n    </section>\n\n    <section class=\"ed-stack ed-gap-4\">\n      <div class=\"ed-stack ed-gap-1\">\n        <h2>Service</h2>\n        <p class=\"ed-caption-m\">A change of clerk applies from the next payroll period; open tasks stay with the current clerk.</p>\n      </div>\n      <div class=\"ed-grid\">\n        <ed-combobox class=\"ed-span-md-4\" name=\"clerk\" label=\"Clerk\" value=\"sb\" required>\n          <ed-option value=\"sb\">Sophie Brandt</ed-option>\n          <ed-option value=\"aj\">Andrea Jansen</ed-option>\n          <ed-option value=\"tr\">Thorsten Reinke</ed-option>\n          <ed-option value=\"mh\">Marie-Luise Hoffmann-Grote</ed-option>\n        </ed-combobox>\n        <ed-date-picker class=\"ed-span-md-4\" name=\"client-since\" label=\"Client since\" value=\"2019-04-01\" required readonly></ed-date-picker>\n        <ed-select class=\"ed-span-md-4\" name=\"payroll-cycle\" label=\"Payroll cycle\" value=\"monthly\" required>\n          <ed-option value=\"monthly\">Monthly payroll</ed-option>\n          <ed-option value=\"semi-monthly\">Semi-monthly payroll</ed-option>\n          <ed-option value=\"weekly\">Weekly payroll</ed-option>\n          <ed-option value=\"none\">No payroll</ed-option>\n        </ed-select>\n      </div>\n    </section>\n  </form>\n</div>\n\n<ed-alert-dialog id=\"f01-confirm-discard\" light-dismiss type=\"warning\" label=\"Discard your changes?\" confirm-text=\"Discard changes\" cancel-text=\"Keep editing\">\n  <span id=\"f01-discard-text\"></span>\n</ed-alert-dialog>\n\n<ed-toast-stack id=\"f01-toasts\"></ed-toast-stack>\n\n<!-- Demo only, not part of the pattern. The product supplies openedCase(): 'edit' for an existing client, 'create' for a new one, from its route; and afterCreate(), which goes back to the client list once a client is created, where the demo opens the empty form again. The demo switches between both cases on one page and keeps the entries of each; a product opens the form in one case and leaves out onCaseSwitch(). -->\n\n<script type=\"module\">\n  await Promise.all(['ed-input', 'ed-select', 'ed-combobox', 'ed-option', 'ed-date-picker'].map(tag => customElements.whenDefined(tag)));\n\n  const form = document.getElementById('f01-form');\n  const back = document.getElementById('f01-back');\n  const title = document.getElementById('f01-title');\n  const subtitle = document.getElementById('f01-subtitle');\n  const badge = document.getElementById('f01-badge');\n  const toolbar = document.getElementById('f01-toolbar');\n  const company = document.getElementById('f01-company');\n  const save = document.getElementById('f01-save');\n  const saveLabel = document.getElementById('f01-save-label');\n  const discard = document.getElementById('f01-discard');\n  const discardLabel = document.getElementById('f01-discard-label');\n  const confirmDiscard = document.getElementById('f01-confirm-discard');\n  const discardText = document.getElementById('f01-discard-text');\n  const toasts = document.getElementById('f01-toasts');\n  // The named controls, as FormData takes them: an ed-input lists its own inner <input> in\n  // form.elements as well, which has no name.\n  const fields = [...form.elements].filter(field => field.name);\n  const number = fields.find(field => field.name === 'number');\n  const since = fields.find(field => field.name === 'client-since');\n  // The record as last saved, and the values the open case started from: the record when editing,\n  // the empty form when creating.\n  const stored = new Map();\n  const start = new Map();\n  const creating = () => openedCase() === 'create';\n  const names = labels => (labels.length < 2 ? labels.join('') : `${labels.slice(0, -1).join(', ')} and ${labels.at(-1)}`);\n\n  const update = () => {\n    const changed = fields.some(field => field.value !== start.get(field));\n    for (const action of [save, discard]) {\n      action.disabled = !changed;\n      document.querySelector(`ed-tooltip[for=\"${action.id}\"]`).disabled = changed;\n    }\n  };\n\n  const open = async () => {\n    const create = creating();\n    for (const field of fields) field.value = create ? '' : stored.get(field);\n    if (create) since.value = new Date().toISOString().slice(0, 10);\n    number.hint = create ? 'Assigned when the client is created' : 'Assigned on April 1, 2019; it cannot be changed';\n    title.textContent = create ? 'New client' : company.value;\n    subtitle.textContent = create ? 'Saarbrücken office' : 'Client 104721 · Saarbrücken office';\n    badge.hidden = create;\n    back.textContent = create ? 'Back to the client list' : 'Back to client 104721';\n    toolbar.label = create ? 'New client' : 'Client 104721';\n    saveLabel.textContent = create ? 'Create client' : 'Save changes';\n    discardLabel.textContent = create ? 'Discard entries' : 'Discard changes';\n    await Promise.all(fields.map(field => field.updateComplete));\n    for (const field of fields) start.set(field, field.value);\n    update();\n  };\n\n  await Promise.all(fields.map(field => field.updateComplete));\n  for (const field of fields) stored.set(field, field.value);\n  await open();\n  onCaseSwitch(open);\n\n  form.addEventListener('input', update);\n  form.addEventListener('change', update);\n\n  save.addEventListener('click', async () => {\n    if (!form.reportValidity()) return;\n    if (creating()) {\n      toasts.create({ variant: 'success', title: `${company.value} created` });\n      await afterCreate(open);\n      return;\n    }\n    for (const field of fields) stored.set(field, field.value);\n    title.textContent = company.value;\n    await open();\n    toasts.create({ variant: 'success', title: 'Client 104721 saved' });\n  });\n\n  // The warning names what is lost: the changed fields when editing, the entries when creating.\n  discard.addEventListener('click', () => {\n    if (creating()) {\n      confirmDiscard.label = 'Discard the new client?';\n      confirmDiscard.confirmText = 'Discard entries';\n      discardText.textContent = 'Everything entered for the new client is lost.';\n    } else {\n      const changed = fields.filter(field => field.value !== start.get(field)).map(field => field.label);\n      confirmDiscard.label = 'Discard your changes?';\n      confirmDiscard.confirmText = 'Discard changes';\n      discardText.textContent = `${names(changed)} ${changed.length === 1 ? 'goes back to the value' : 'go back to the values'} last saved for client 104721. The changes cannot be restored.`;\n    }\n    confirmDiscard.open = true;\n  });\n\n  confirmDiscard.addEventListener('ed-alert-dialog-confirm', () => {\n    for (const field of fields) field.value = start.get(field);\n    update();\n  });\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/forms/f01-standard-form/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/forms/"
}
