📄 pbForm: Fast and Reliable Form Handling
pbForm is a ready-to-use class for convenient client-side AJAX form validation and submission. It integrates with pbFetch and automatically handles errors, messages, and redirects.
✨ Key Features
- Intercepts form submission: prevents the default submit and sends the data via
pbFetchusingFormData. - Handles field errors: if the API returns validation errors (e.g.
{ errors: { email: "Invalid email" } }), the class adds CSS classes to the fields and displays the messages. - Shows where to look: scrolls the page to the first field that failed validation and focuses it.
- Supports custom elements: use
data-error="name"anddata-custom="name"for flexible markup. - Auto-clears errors: removes stale errors on re-entry or after a successful submission.
- Disables the button and shows a spinner for the duration of the request.
- Redirect support: if the server returns a
redirect, the form sends the user there automatically. - reCAPTCHA v3 support: the token is requested by the class itself, right before submission.
- Customizable CSS classes: error and message classes can be set through constructor options or system settings.
⚙️ How to Use
1️⃣ Add the pb-form Attribute to the Form
<form action="/api/register" method="POST" pb-form>
<input name="email">
<span data-error="email"></span>
<button type="submit">Submit</button>
</form>2️⃣ Initialize the Class
With the pageblocks_load_scripts system setting enabled, everything starts automatically. If you need to do it manually:
<script src="/assets/components/pageblocks/js/web/pb.fetch.v300.js"></script>
<script src="/assets/components/pageblocks/js/web/pb.message.v300.js"></script>
<script src="/assets/components/pageblocks/js/web/pb.form.v300.js"></script>
<script>
pbFetch.init();
new pbForm('form[pb-form]', {
errorClass: 'is-invalid',
errorMessageClass: 'invalid-feedback'
});
</script>Options:
errorClass: CSS class for invalid fields.errorMessageClass: CSS class for the error message block.
By default they are pulled from system settings:
pageblocks_field_error— class for a field with an error (default:is-invalid).pageblocks_field_msg_error— class for the error text (default:invalid-feedback).
An empty setting won't break the form
If the setting is cleared, the constructor receives an empty string, and classList.add('') throws token must not be empty, taking the whole handler down with it. For that case pbForm falls back to its own defaults.
Handlers are attached once to document, not to each individual form. That is why forms added to the DOM later (loaded over AJAX, rendered in a modal) work without re-initialization.
3️⃣ Real-World Example with Bootstrap 5
<form class="border rounded-4 p-5" action="/login" method="post" pb-form>
<input type="hidden" name="_token" value="{csrf_token}">
<input type="hidden" name="honeypot" value="">
{if $success_message}
<p class="text-center text-success" pb-message>{$success_message}</p>
{elseif $error_message}
<p class="text-center text-danger text-error" pb-message>{$error_message}</p>
{else}
<p class="text-center d-none" pb-message></p>
{/if}
<div class="form-group mb-3">
<label for="username" class="mb-2">Username</label>
<input type="text"
name="username"
id="username"
class="form-control{if $errors.username} is-invalid{/if}"
value="{$old_input.username}">
<span class="invalid-feedback" data-error="username">{$errors.username}</span>
</div>
<div class="form-group mb-3">
<label for="password" class="mb-2">Password</label>
<input type="password"
name="password"
id="password"
class="form-control{if $errors.password} is-invalid{/if}">
<span class="invalid-feedback" data-error="password">{$errors.password}</span>
</div>
<button type="submit" class="btn btn-dark w-100">
<span class="spinner spinner-border spinner-border-sm"
pb-spinner
aria-hidden="true"
style="display:none">
</span>
<span role="status">Login</span>
</button>
</form>🔍 Key Details
- ✅
<p pb-message>is used to display the general message about success or failure. - ✅ Field errors use
data-error="name"and the CSS classes defined in system settings (field_errorandfield_msg_error). - ✅ The submit button is disabled automatically for the duration of the request, and a spinner with the
pb-spinnerattribute inside it indicates loading.
🎯 Scrolling to the First Error
A long form gets submitted from its bottom, and when a field near the top fails, all the user sees is "Validation failed" — with no idea where to look. So after parsing the response pbForm takes the first field from errors, smoothly scrolls the page to it (block: 'center') and focuses it.
The order of the fields is set by the server: the highlighted field is the first key of the errors object, not the first field in the markup.
Choices works
The Choices library hides the original <select> inside its own container, and the browser silently refuses both to scroll to it and to focus it. pbForm accounts for this: it finds the .choices wrapper, scrolls to it and focuses the inner input.choices__input.
For the same reason the highlight is cleared not only on input, but on change as well: picking a value in a select fires no input event, and the error class used to stay on an already corrected field until the next submission.
🧹 What Happens After Success
- If the response contains a non-empty
redirectfield — the browser navigates there, and that's it. - Otherwise the form is cleared with
form.reset(). - All error classes and per-field error texts are removed, and the general message block is emptied and hidden again.
The general message is cleared in the context of the form itself — the same way it gets there in the first place. That is why the [pb-message] block must live inside the form: outside it, pbForm can neither show a message nor clear one.
Clearing can be turned off — for example when the form is filled in repeatedly (adding line items, searching):
<form action="/api/items" method="post" pb-form data-noclear>
…
</form>🛡️ reCAPTCHA v3
It's enough to put a hidden field in the form — pbForm obtains the token itself, in the before callback, i.e. right before submission (the token lives two minutes, so it cannot be fetched in advance):
<form action="/api/feedback" method="post" pb-form data-action="feedback">
<input type="hidden" name="g-recaptcha-response" data-key="{$recaptcha_public_key}">
…
</form>data-key— the site's public key, from thepageblocks_recaptcha_public_keysystem setting.data-actionon the form — the reCAPTCHA action name; defaults toform.
Google's script is loaded automatically as long as the public key setting is filled in. Once the request finishes the field value is reset, so a repeated submission doesn't go out with a burnt token.
🏷️ Error Markup
| Attribute | What it does |
|---|---|
data-error="name" | Element that receives the error text for the name field; gets errorMessageClass. |
data-custom="name" | Extra element that gets errorClass along with the field itself. |
pb-spinner | Indicator inside the button; shown for the duration of the request. |
pb-message | The form's general message block (see pbMessage). |
data-custom is needed where the real <input> is hidden or replaced — file uploads, a custom checkbox, the same Choices. An error class on an invisible field tells the user nothing, so it is mirrored onto the visible wrapper:
<div class="dropzone" data-custom="photo">
<input type="file" name="photo" class="d-none">
<span>Drop a file here</span>
</div>
<span class="invalid-feedback" data-error="photo"></span>The error class is applied to all fields with that name — a group of name="tags[]" checkboxes is highlighted as a whole.
🔧 Individual Forms with Different Classes
You can initialize forms with different selectors and your own error classes:
new pbForm('.custom-ajax-form', {
errorClass: 'has-error',
errorMessageClass: 'field-error'
});🗂 Default System Settings
| Setting | Default Value | Description |
|---|---|---|
pageblocks_field_error | is-invalid | CSS class for invalid fields |
pageblocks_field_msg_error | invalid-feedback | CSS class for error message blocks |
pageblocks_msg_success | text-success | Classes for a general success message |
pageblocks_msg_error | text-error,text-danger | Classes for a general error message |
pageblocks_hidden_class | d-none | Class for hiding message blocks |
📡 What It Expects from the Server
The request goes out as POST to form.action with expect: 'json'. Three response fields are parsed:
{
"message": "Request sent",
"redirect": "/thanks",
"errors": {
"email": "Invalid email",
"phone": ["Phone is required"]
}
}errors— a "field → message" object; the value can be a string or an array, in which case the first element is used. Read only on an error HTTP status (422and similar).redirect— where to go after success.message— the general message, displayed bypbMessage(this is handled bypbFetch, not bypbFormitself).
✅ Summary
pbForm is a simple and flexible foundation for AJAX forms with auto-validation and error handling. Add the pb-form attribute, configure the system settings, and the form is ready to work without any extra JS.