Class ModalDef

java.lang.Object
org.apache.juneau.console.widgets.ModalDef
All Implemented Interfaces:
Widget

@BeanType(properties="contractVersion,title,fields,form,idempotencyKey,selfTargeted,barSlot,keepOpenOnSubmit,childActions,chrome,steps") public class ModalDef extends Object implements Widget
The declarative modal (dialog) a present=dialog row action opens — the payload the modal-open confirmation fetch returns.
A widget contract driven by a view-module runtime

This bean is a general widget contract and lives in this module, but the browser runtime that paints it ships with the rich-view module (it is part of that module's juneau-views.js monolith, which is not split apart here). So a page that opens one of these dialogs loads the view runtime; nothing in this module reaches back to it.

Safe-by-construction confirmation body

The confirmation body is typed structured fields (fields: incident #, title, service, current status — the confirmation field set), server-rendered against the current, authoritative record and bound to the target identifier. The juneau-views.js runtime paints each field's value with textContent — NEVER innerHTML, never raw markup, never the non-table panel-content sink. Live/remote data is attacker-influenceable (anyone who can title an incident or post in a watched thread) and this origin holds the CSRF token, so an HTML sink here would be stored-XSS → token-theft → arbitrary-write; typed fields painted with textContent close that off by construction.

The idempotency key rides here

idempotencyKey is a server-minted key value, minted at modal-open and bound to (action, targetId). The runtime carries it verbatim on the submit so a double-click / re-submit / browser retry all collapse to one effect; the server checks that binding on submit and answers a mismatch with a named refusal, never a replayed success. The minting/binding helper itself is a table-and-row-action concern and stays in the rich-view module, which is why this field is a plain String rather than that helper's type.

selfTargeted — the opt-in for a key that targets itself

A dialog opened where there is no row (a ribbon-hosted dialog whose submit creates the thing it is about) has no artifact id to bind its key to at modal-open. The rich-view module's key helper can mint a key whose bound target is its own value; selfTargeted is the signal that tells the runtime to send that value as the submitted target instead of the row's id.

The signal has to ride on this bean, because it cannot be inferred from the wire: idempotencyKey is an opaque string either way, indistinguishable in shape from an artifact-bound key's value. It is therefore an explicit, additive, per-dialog opt-in — not a blanket runtime precedence rule. A dialog that leaves this unset behaves exactly as it does today, including one that sets idempotencyKey for an artifact-bound reason: it keeps sending the row's real id, which is what its own mint call bound.

Set it only on a modal whose key came from the self-targeted mint helper. Setting it on a modal carrying an artifact-bound key silently discards the real target and makes the server's binding check vacuous.

keepOpenOnSubmit — the opt-in for an in-dialog result receipt

By default the runtime closes a dialog at confirm-click time and then submits, so every dialog is gone before its result exists. keepOpenOnSubmit suppresses that close, and the runtime instead paints the write's result into the dialog that is already open — a receipt.

A receipt host is non-submittable by construction, not by convention: the runtime paints it with a distinct function that creates no confirm control at all, and it refuses to paint one whose payload carries a form. It is reached only after a committed write (an ActionResult whose outcome is success), so a result host can never appear for a write that did not happen. A follow-up read-only form source is named by that result's resultForm.

The opt-in has to live here rather than on the action bean, because the confirm handler must decide whether to close before any response exists. A dialog synthesized from a confirm string alone (no form-source URL) therefore cannot opt in — a receipt-bearing action must serve a real ModalDef.

Additive, and named degradation, accepted: an older runtime ignoring this flag closes the dialog and drops the receipt. The write still happens; the receipt is simply not shown.

childActions — the dialog-scoped child-action catalog

A type="action" form input names an action id, and the runtime resolves it against the enclosing view's row-action catalog. A stacked step reached from inside an already-open dialog (a Review step in a wizard) has nothing legal to resolve against there: it is not a row action, and publishing it as one would surface it in every row's action menu. childActions is that missing catalog, scoped to one open dialog.

Scoping is structural, not a flag some menu builder has to remember to filter on: this catalog rides on a per-open payload, so the row-action menu and the ribbon resolver never see it and there is no exclusion logic to rot. Resolution precedence is likewise fail-safe: the existing row-action check runs first and unchanged, so a served payload can never shadow — and thereby bypass the gating of — a declared row action.

chrome and steps — dialog styling and a built-in wizard

chrome names a dialog chrome the page script registered with the runtime; the runtime hands that chrome the freshly built dialog parts so it can restyle them. An unregistered name is ignored and the default chrome is kept.

steps turns the dialog into a wizard: the runtime shows one ModalDef.Step at a time with Back and Next buttons, and the last step's button is the dialog's own confirm. Both are additive-only and omitted from the wire when unset, so neither bumps CONTRACT_VERSION.

Not a new row-action wire field

This is the response a row action's form-source URL returns, not a new field on the frozen row-action wire schema: the modal's declaration lives in this separately-served payload so the row-action contract stays frozen. The same reasoning is why keepOpenOnSubmit and childActions land here rather than on RowAction / RibbonAction, which stay frozen and untouched.

Example:

ModalDef modal = ModalDef.create("Acknowledge this incident?") .field("Incident", incidentNumber) .field("Title", incidentTitle) .field("Service", serviceName) .field("Current status", currentStatus) .form(FormDef.ofTemplate("servlet:/incidents/ack-form.ftl")) .idempotencyKey(keyValue);

See Also:
The third named bar-slot host

barSlot is the third named BarSlot attachment — alongside the rich-view module's page and row-detail hosts (that module never depends on this one, so they are not linked here). Unlike those two — Java-only fields a server-side emitter paints directly, omitted from their wire — this modal itself is the wire payload the modal-open confirmation fetch returns, with no separate server-rendered pass the dialog could ride into. So this field is carried on the wire (additive-only: null when unset, and its presence does not bump CONTRACT_VERSION), and the juneau-views.js runtime paints the region and its dynamic-count sidecar client-side from the JSON, the same way it already paints typed confirmation fields and form controls — textContent only, never innerHTML. The dialog anchors its own region immediately after the title (the ANCHOR_DIALOG_TITLE placement in juneau-views.js), owning that placement itself rather than leaning on the shared strip builder, exactly as the row-detail host owns its own ribbon-trailing relocation.

Since:
10.0.0
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    One entry in a dialog's childActions catalog — a stacked step a type="action" input inside this dialog's form may open.
    static class 
    A single typed confirmation field: a label and a value, painted client-side with textContent (never innerHTML).
    static class 
    One page of a steps wizard: an id, an optional title and confirm-button label, and the typed display fields painted for that page.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    Optional additive bar slot anchored to this dialog's title — the third named BarSlot attachment (see the class Javadoc for how this host differs from the other two).
    The dialog-scoped catalog of stacked child actions a type="action" input in form may open (see the class Javadoc); omitted from the wire when unset or empty.
    The name of a dialog chrome the page script registered with the runtime, which restyles this dialog's parts (see the class Javadoc); omitted from the wire when unset (the default chrome).
    static final String
    The frozen modal contract version.
    The frozen modal contract version discriminator.
    static final String
    The ModalDef.Field.kind token for a monospaced, copyable value (an id, a URL, a generated token).
    static final String
    The ModalDef.Field.kind token for an ordinary text value — the default when the field declares none.
    The typed confirmation fields, in display order; omitted from the wire when none are declared.
    The optional input form; omitted from the wire when unset (a confirm-only modal).
    The server-minted idempotency key value the runtime carries on the submit; omitted from the wire when unset.
    Whether the runtime must not close this dialog at confirm-click time, so the write's result can be painted into it as a receipt (see the class Javadoc); omitted from the wire when unset or false.
    Whether idempotencyKey is a self-targeted key whose own value the runtime must send as the submitted target (see the class Javadoc); omitted from the wire when unset or false.
    The wizard steps the runtime shows one at a time with Back and Next (see the class Javadoc); omitted from the wire when unset or empty.
    The modal title / confirmation prompt.
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    Declares the additive bar slot anchored to this dialog's title.
    The serving-path hook every app @RestGet that returns a ModalDef should invoke.
    Adds one entry to this dialog's child-action catalog.
    chrome(String value)
    Selects the dialog chrome the page script registered under the given name.
    codeField(String label, String value)
    Adds one typed confirmation field displayed as monospaced, copyable code.
    static ModalDef
    create(String title)
    Starts a new ModalDef with the specified title / confirmation prompt.
    field(String label, String value)
    Adds one typed confirmation field.
    form(FormDef value)
    Sets the optional input form.
    Sets the server-minted idempotency key value carried on the submit.
    keepOpenOnSubmit(boolean value)
    Declares that the runtime must keep this dialog open across its submit so the result can be painted into it as a receipt (see the class Javadoc).
    selfTargeted(boolean value)
    Declares that idempotencyKey is a self-targeted key — the runtime sends the key's own value as the submitted target rather than a row id.
    Adds one wizard step.
    void
    Fail-closed bean validation.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • CONTRACT_VERSION

      public static final String CONTRACT_VERSION
      The frozen modal contract version. Bumped only on a breaking wire change to this modal contract.

      Moves in lockstep with FormDef.CONTRACT_VERSION and the runtime's baked-in literal: the runtime compares one literal against both this version and the nested form's, so bumping any two of the three makes every form-bearing dialog refuse to open.

      See Also:
    • FIELD_KIND_TEXT

      public static final String FIELD_KIND_TEXT
      The ModalDef.Field.kind token for an ordinary text value — the default when the field declares none.
      See Also:
    • FIELD_KIND_CODE

      public static final String FIELD_KIND_CODE
      The ModalDef.Field.kind token for a monospaced, copyable value (an id, a URL, a generated token).
      See Also:
    • contractVersion

      The frozen modal contract version discriminator.

      Defaults to CONTRACT_VERSION on every modal, confirm-only or form-bearing, from the instant it is constructed — the guarantee does not depend on checked() being called. Guaranteed present is not the same as guaranteed valid: presence is this field's whole job, structural well-formedness is validate()'s (see checked()).

    • title

      public String title
      The modal title / confirmation prompt.
    • fields

      The typed confirmation fields, in display order; omitted from the wire when none are declared.
    • form

      public FormDef form
      The optional input form; omitted from the wire when unset (a confirm-only modal).
    • idempotencyKey

      The server-minted idempotency key value the runtime carries on the submit; omitted from the wire when unset. The value is minted (and its (action, targetId) binding re-checked on submit) by the rich-view module's idempotency-key helper.
    • selfTargeted

      Whether idempotencyKey is a self-targeted key whose own value the runtime must send as the submitted target (see the class Javadoc); omitted from the wire when unset or false.

      Additive-only: like barSlot, its presence does not bump CONTRACT_VERSION.

    • barSlot

      public BarSlot barSlot
      Optional additive bar slot anchored to this dialog's title — the third named BarSlot attachment (see the class Javadoc for how this host differs from the other two). Omitted from the wire when unset; painted by the runtime immediately after title, ahead of fields and form.
    • keepOpenOnSubmit

      Whether the runtime must not close this dialog at confirm-click time, so the write's result can be painted into it as a receipt (see the class Javadoc); omitted from the wire when unset or false.

      Additive-only: like barSlot and selfTargeted, its presence does not bump CONTRACT_VERSION.

    • childActions

      The dialog-scoped catalog of stacked child actions a type="action" input in form may open (see the class Javadoc); omitted from the wire when unset or empty.
    • chrome

      public String chrome
      The name of a dialog chrome the page script registered with the runtime, which restyles this dialog's parts (see the class Javadoc); omitted from the wire when unset (the default chrome).

      Additive-only: like barSlot, its presence does not bump CONTRACT_VERSION.

    • steps

      The wizard steps the runtime shows one at a time with Back and Next (see the class Javadoc); omitted from the wire when unset or empty.

      Additive-only: like barSlot, its presence does not bump CONTRACT_VERSION.

  • Constructor Details

  • Method Details

    • create

      public static ModalDef create(String title)
      Starts a new ModalDef with the specified title / confirmation prompt.
      Parameters:
      title - The modal title / confirmation prompt. Must not be null or blank.
      Returns:
      A new mutable ModalDef to chain builder calls on.
      Throws:
      IllegalArgumentException - If title is null or blank.
    • field

      public ModalDef field(String label, String value)
      Adds one typed confirmation field.
      Parameters:
      label - The field label. Must not be null or blank.
      value - The field value (read back from the current record). Can be null.
      Returns:
      This object.
    • codeField

      public ModalDef codeField(String label, String value)
      Adds one typed confirmation field displayed as monospaced, copyable code.
      Parameters:
      label - The field label. Must not be null or blank.
      value - The field value. Can be null.
      Returns:
      This object.
    • childAction

      Adds one entry to this dialog's child-action catalog.
      Parameters:
      value - The child action. Must not be null.
      Returns:
      This object.
      Throws:
      IllegalArgumentException - If value is null.
    • chrome

      public ModalDef chrome(String value)
      Selects the dialog chrome the page script registered under the given name.
      Parameters:
      value - The chrome name. Can be null to unset (the default chrome); a non-null value must not be blank.
      Returns:
      This object.
    • step

      public ModalDef step(ModalDef.Step value)
      Adds one wizard step.
      Parameters:
      value - The step. Must not be null.
      Returns:
      This object.
      Throws:
      IllegalArgumentException - If value is null.
    • keepOpenOnSubmit

      public ModalDef keepOpenOnSubmit(boolean value)
      Declares that the runtime must keep this dialog open across its submit so the result can be painted into it as a receipt (see the class Javadoc).

      Passing false clears the flag rather than serializing an explicit false: the wire carries this opt-in only when it is on, so an un-opted-in dialog's payload is byte-identical to one that never set it.

      Parameters:
      value - true to opt in; false (or unset) keeps the default close-then-submit behaviour.
      Returns:
      This object.
    • form

      public ModalDef form(FormDef value)
      Sets the optional input form.
      Parameters:
      value - The form. Can be null to unset (a confirm-only modal).
      Returns:
      This object.
    • idempotencyKey

      public ModalDef idempotencyKey(String value)
      Sets the server-minted idempotency key value carried on the submit.
      Parameters:
      value - The key value. Can be null to unset.
      Returns:
      This object.
    • selfTargeted

      public ModalDef selfTargeted(boolean value)
      Declares that idempotencyKey is a self-targeted key — the runtime sends the key's own value as the submitted target rather than a row id.

      Set this only alongside a key minted by the rich-view module's self-targeted mint helper; pairing it with an artifact-bound key discards that key's real target. See the class Javadoc.

      Passing false clears the flag rather than serializing an explicit false: the wire carries this opt-in only when it is on, so an un-opted-in dialog's payload is byte-identical to one that never set it.

      Parameters:
      value - true to opt in; false (or unset) keeps the default row-id behaviour.
      Returns:
      This object.
    • barSlot

      public ModalDef barSlot(BarSlot value)
      Declares the additive bar slot anchored to this dialog's title.

      See barSlot — a third named host for the same BarSlot bean, not a re-use of the rich-view module's page or row-detail hosts.

      Parameters:
      value - The bar slot. Can be null (no dialog bar slot).
      Returns:
      This object.
    • validate

      public void validate()
      Fail-closed bean validation.

      Requires a non-blank title and a non-blank label on each ModalDef.Field; when a form is present it delegates to FormDef.validate(). Does not require contractVersion to be set to CONTRACT_VERSION (or set at all) — a modal whose version was explicitly cleared, or that arrived from a foreign producer that never set one, must not false-refuse on that alone. Version-presence and structural well-formedness are deliberately independent properties (design note, §5.4).

      Also rejects, fail-closed at serve time rather than silently on the wire: a ModalDef.Field.kind outside the text/code allowlist; a blank, duplicate or unlabelled ModalDef.ChildAction id; ModalDef.ChildAction.carryDrafts on a modal that declares no form (nothing to collect from); and ModalDef.ChildAction.carryDrafts on a child that declares no ModalDef.ChildAction.form (nowhere to put them — the runtime would take its confirm-only branch, issue no GET, and the drafts would vanish silently).

      Specified by:
      validate in interface Widget
      Throws:
      IllegalArgumentException - If this modal is not well-formed.
    • checked

      public ModalDef checked()
      The serving-path hook every app @RestGet that returns a ModalDef should invoke.

      contractVersion is guaranteed present on every modal from construction, so this is no longer required for versioning. It stays strongly recommended for one remaining duty and is redundant-but-harmless for another:

      • Non-REST producers (a direct Json.of(...) serialization, or any consumer marshalling a modal outside a REST response) have no other gate: calling this is the only fail-closed structural validation they get, and skipping it means a malformed modal/form serializes as contract-valid with no complaint.
      • REST responses are validated independently: WidgetsMixin.WidgetValidationProcessor calls validate() on every REST response that is a Widget, fail-closed with a 500, whether or not the producing endpoint called this method. Calling it anyway is safe (validation is idempotent) and remains the only gate for the non-REST case above.
      When a form is present it also re-stamps CONTRACT_VERSION on the form and recurses into FormDef.checked().
      Returns:
      This object.
      Throws:
      IllegalArgumentException - If this modal (or its form) is not well-formed.