Clone on Gitea
Introduction
Explained by ducks
FAQ
Getting Started
Components
Component Basics
Auto Initialization From DOM
Templates
Children and Parents
Roles
Strictness
Fields
What is a Field
Binding Fields to DOM
Reading and Writing Fields
Value Casting
Flags
Events
Overview
Event Handlers
DOM Events
Custom Events
Propagation and Flow
Advanced Event Control
Beyond Components
States
Overview
StateEngine
DisplayStateEngine
Field Matchers
Ordering and priority
Debugging
Advanced usage
Ajax
ComponentUI
VirtualForm
Unit Testing
Helpers
Overview
Cookies
Arrays

VirtualForm

VirtualForm programmatically builds and submits a hidden HTML <form>. It exists for situations where Ajax is the wrong tool. Where Ajax is for API-style requests. VirtualForm is for browser-native navigation flows. Use VirtualForm when you want:

  • Real browser navigation.
  • Full page reloads.
  • Standard form submission semantics.
  • Compatibility with backend method override conventions (for example, DELETE via _method).
  • To let other libraries intercept the native submit event.

The generated form is appended to document.body, has data-qite-virtual-form="1" for identification and is styled with display: none

Basic usage

The simplest form is:

new VirtualForm("/users/1", { name: "John" }).submit();

Where:

  • The 1st argument is string and uss URL for form action
  • The 2nd argument is an object map or [{ name, value }] which represents field names and values.
  • The 3rd argument is a map of configuration options.

It's usually desirable to set configuration defaults for all forms:

VirtualForm.configure({ method: "POST", csrf: "Rails" });

This modifies static defaults used by future instances, but per-instance options provided via the 3rd argument to new VirtualForm() always override global configuration.

Submitting

Calling .submit() does more than form.submit(). Why? In browsers, form.submit() bypasses the submit event entirely, which means there's no preventDefault() interception, which in turn means it's harder to test and integrate with global listeners. For that reason VirtualForm.submit() does the following:

  1. Builds the form.
  2. Appends it to document.body
  3. Dispatches a cancelable "submit" event manually.
  4. If not prevented, performs the real submission.

If any listener calls preventDefault(), navigation is skipped and the form element is returned. This makes VirtualForm safe for tests and integration with event-driven code.

HTTP methods and method override

Browsers only support GET and POST in <form method="...">. If you pass a different method:

new VirtualForm("/users/1", {}, { method: "DELETE" }).submit();

the form is still submitted as POST, but a hidden _method field is added with value "DELETE". This matches common Rails and other backend framework conventions. The idea is that you can easily create links or buttons which, when clicked, should submit a request like "Delete profile" or "Sign out", but that don't technically require a visible form on screen, because from user's perspective it is clear what the action should do without providing additional information.

CSRF support

VirtualForm supports CSRF in two ways.

Template-based:

new VirtualForm("/users", {}, {
    method: "POST",
    csrf: "Rails"
}).submit();

When csrf is provided, Qite resolves how to read the token (via a special csrf getter function), and what hidden field name to use.

Manual override:

new VirtualForm("/users", {}, {
    csrf_getter: () => "...token...",
    csrf_form_field_name: "authenticity_token"
}).submit();

If both csrf_getter and csrf_form_field_name are present, the getter is called at submission time and a hidden field is added automatically. If the getter returns null or undefined, no CSRF field is added.

This mirrors the way CSRF is handled by the Ajax module.

Field handling

Fields may be:

  1. An object map: { name: "John", age: 30 }
  2. Or an array of pairs: [{ name: "user[name]", value: "John" }]

Implicit rules apply:

  • null or undefined values are ignored.
  • All values are converted to strings.
  • Hidden <input type="hidden"> elements are generated for each field.

Practical patterns

Redirect after client-side validation:

if (this.isValid()) {
    new VirtualForm("/checkout", this.fields.all).submit();
}

Logout link that must perform POST:

new VirtualForm("/logout", {}, {
    method: "DELETE",
    csrf: "Rails"
}).submit();

Intercept submission globally:

document.addEventListener("submit", (e) => {
    if (e.target.dataset.qiteVirtualForm === "1") {
        // inspect, log, or cancel
    }
});

VirtualForm intentionally does very little: there's not validation, retries, managing state or history.

VirtualForm simply gives you a clean, testable way to trigger real browser form navigation with modern configuration and CSRF support. Use it when you want the browser to take over and use Ajax when you want to stay in control.