Who this is for. The front-end developer building a form layout on the JS model, or debugging one. Multi-step forms explains the model and when to choose it; this page is the contract.
The file
Served by every site at /assets/forms/js-forms.js, with its stylesheet at /assets/forms/js-forms.css, which defines the js-hidden class that hides the steps not shown. It is the platform's, updated with the platform; do not copy it into the theme. Load it on the pages that carry a JS-model form, with the page's nonce (the snippet).
It starts itself once. When the page has loaded (or at once, if it is loaded later), it sets up every .coredna-js-form already in the page. Render the form with the page; the script sets up every form present when it loads.
The markup it expects
| Element | Role |
|---|---|
.coredna-js-form | The container, one per form. The callbacks and data-endpoint are attributes on it |
input[name="_formId"] | Inside the container, holding the form id. Required: without it the script logs "_formId input is required" and leaves the form alone |
.form-step | One per step, in order. The script numbers them from 1 into data-step. The last .form-step is the result step, shown after a successful submit |
.form-step form | One per step but the result step. Submitting a step's form moves to the next step; submitting the last form sends the whole form |
.navigate-previous | A control that goes back one step |
.go-to-step with data-step="n" | A control that goes to step n, counted from 1 |
data-skipped="true" on a step | Navigation passes over it. js-forms-conditionals.js sets it for a step hidden by a rule |
.submit-error-message | Inside a step: shown when a submit fails. %message% in it is replaced by the error's message; otherwise its content is replaced |
input[name="_redirectUrl"] | Optional. After a successful submit the browser goes to its value instead of showing the result step |
Because each step is a real <form>, the browser checks required, pattern and the input types before the script sees the submit, so a step with an empty required field does not advance.
Attributes on the container
| Attribute | Value |
|---|---|
data-endpoint | Where to send the form. The default is /cdna-api/forms/submission/FORM_ID; FORM_ID in the value is replaced by the form id |
data-start-callback | The name of a function on window, below |
data-navigate-callback | |
data-submit-modifier-callback | |
data-submit-callback | |
data-submit-result-callback |
The callbacks
Each attribute names a global function, window[name]; a name that is not a function is ignored. The start callback is looked up once, when the script sets the form up, so it must exist by then. The others are looked up each time they are needed, so a script loaded later can set them; js-payment-element.js does. Every callback receives one object with formContainer, formId, currentStep and currentStepNumber, plus:
| Callback | When | Also receives | Return |
|---|---|---|---|
data-start-callback | The first keystroke or change in any field | event | Ignored |
data-navigate-callback | Before moving to another step | data (every answer so far), requestedStep, requestedStepNumber | false, or a promise of false, stays on the step |
data-submit-modifier-callback | When the last form is submitted, before anything is sent | data, resultStep, resultStepNumber, event | The data to send, or a promise of it. Throwing shows the error's message in .submit-error-message |
data-submit-callback | After the modifier, before sending | The same, with the modified data | false, or a promise of false, cancels |
data-submit-result-callback | When the answer arrives | data, resultStep, resultStepNumber, xhr | Truthy lets the script handle the answer; falsy means you have |
A cancelled submit leaves the buttons disabled. The script disables the last step's submit buttons as soon as it is submitted, and only an error it handles itself enables them again. A modifier that returns nothing, a submit callback that returns false and a submit callback that throws all stop the submit with the buttons still disabled: enable them again in your callback before stopping.