webapp/js/gen-form.js builds every create form, edit form, action form, and read-only record card of the web app from EntityMeta.form. webapp/js/form-drafts.js keeps unsaved input during a session, and webapp/js/customselect.js restyles every native select. This page lists the field controls, the metadata features the form honours, how values are collected, and how drafts work. The metadata classes are on metadata model.
Scope and source¶
- Defined in:
webapp/js/gen-form.js(createGenForm,makeGenFormView,mountRecordForm,buildRecordCard),webapp/js/form-drafts.js,webapp/js/customselect.js(initCustomSelects) - Count: 15 field types with a dedicated control, plus a plain text fallback (counted from the branches of
controlHtmlinwebapp/js/gen-form.js:67-200);FieldMeta.field_typeallows 19 values (src/cluster_erp/core/metadata.py:82-102)
Entry points¶
| Function | Purpose | Source |
|---|---|---|
createGenForm(moduleName, entityMeta, opts) |
Builds the form element and returns an object with loadFks, validate, showErrors, collect, commitFiles, setLocked, and draft helpers |
webapp/js/gen-form.js:287-1829 |
makeGenFormView(moduleName, entityMeta, opts) |
A pushed screen with the form, a parent picker when the create path needs {parent_id}, a document collector, and save |
webapp/js/gen-form.js:1846-2035 |
mountRecordForm(host, moduleName, entityMeta, opts) |
A read-only record card that switches to edit mode with Edit (common.edit) |
webapp/js/gen-form.js:2310 |
Field controls¶
field_type |
Control | Value sent | Source |
|---|---|---|---|
string and unknown types |
Text input; semantic: "password" gives a password input |
Text | webapp/js/gen-form.js:197-199 |
text, json |
Text area | Text | webapp/js/gen-form.js:72-74 |
integer |
Number input with min_value and max_value |
Integer | webapp/js/gen-form.js:75-79, webapp/js/gen-form.js:495-498 |
float |
Text input with decimal keypad | Number | webapp/js/gen-form.js:80-82, webapp/js/gen-form.js:499-502 |
decimal |
Text input with decimal keypad | The typed string, "0" when required and empty |
webapp/js/gen-form.js:80-82, webapp/js/gen-form.js:503-506 |
boolean |
Switch | Boolean | webapp/js/gen-form.js:83-88 |
date |
Date input | YYYY-MM-DD or null |
webapp/js/gen-form.js:89-91, webapp/js/gen-form.js:511 |
datetime |
Local date-time input | UTC ISO string | webapp/js/gen-form.js:92-94, webapp/js/gen-form.js:512-514 |
file_upload |
File button; the file is uploaded on save | Attachment id | webapp/js/gen-form.js:95-105 |
enum |
Select; an optional field starts blank | Choice value or null | webapp/js/gen-form.js:106-116 |
multi_enum |
Toggle chips | List of values | webapp/js/gen-form.js:117-127 |
foreign_key |
Select filled from source, with a quick-create + when the source names an entity type |
Id | webapp/js/gen-form.js:144-155 |
multi_foreign_key |
Toggle chips filled from source, with quick-create + |
List of ids | webapp/js/gen-form.js:128-143 |
suggest_combo |
Text input with suggestions | Text | webapp/js/gen-form.js:156-164 |
dynamic_form |
Sub-fields loaded from the driver field's selected record | Object | webapp/js/gen-form.js:165-174 |
field_list |
Repeating rows of item_fields |
List of objects | webapp/js/gen-form.js:175-184 |
string_list |
Removable chips with an input | List of strings | webapp/js/gen-form.js:185-195 |
hidden_scope_field |
No control; the value comes from the screen scope | Scope value | webapp/js/gen-form.js:390, webapp/js/gen-form.js:1775-1778 |
Decimal input stays a string from field to request. This follows the money rule on money and Decimal. The quick-create + carries the tooltip Quick create (fk.create_tooltip).
Metadata features honoured¶
| Feature | Behaviour | Source |
|---|---|---|
create_only and edit_only groups |
A create-only group is hidden when editing; an edit-only group is hidden when creating | webapp/js/gen-form.js:290-298 |
conditional_group with condition |
Shown when the controlling field's value is in the pipe-separated set, inverted by negate; a controlling field that is not on the form is read from the loaded record |
webapp/js/gen-form.js:698-722 |
weight: "detail" |
The row is hidden until the user reveals detail rows | webapp/js/gen-form.js:408-412 |
auto_generate |
Fills the field from a template when other fields match | webapp/js/gen-form.js:434, webapp/js/gen-form.js:724-760 |
source.params with {parent_id} and {field:<key>} |
The picker reloads when the named sibling changes | webapp/js/gen-form.js:816-900 |
immutable_on_edit |
Disabled on an existing record; a foreign key shows only the record's own option from a single read | webapp/js/gen-form.js:845-860, webapp/js/gen-form.js:1632 |
read_only |
Disabled and not collected | webapp/js/gen-form.js:1632, webapp/js/gen-form.js:1779 |
autofill |
Copies a field of the picked record into a sibling, optionally through another source | webapp/js/gen-form.js:1180-1240 |
nested_key |
Values are read from and written to a nested object (with a <key>_info fallback on read) |
webapp/js/gen-form.js:1650-1662, webapp/js/gen-form.js:1768-1773 |
storage: "extra" |
A tenant-defined field travels in the row's extra bag |
webapp/js/gen-form.js:1656-1657, webapp/js/gen-form.js:1785-1790 |
EntityMeta.completeness |
A completeness bar with tiers above the form | webapp/js/gen-form.js:465-480, webapp/js/gen-form.js:771-805 |
EntityMeta.composite_create |
A create posts one composite payload to the declared endpoint | webapp/js/gen-form.js:39-56, webapp/js/gen-form.js:1990-2000 |
EntityMeta.photo |
A photo picked on the create form is uploaded to the new record's photo endpoint | webapp/js/gen-form.js:2002-2012 |
EntityMeta.create_with_documents |
The create form collects documents and attaches them after creation | webapp/js/gen-form.js:1914-1920 |
Validation and collection¶
validate() checks required fields in visible groups, including required sub-fields of container fields, and returns messages built from validation.field_required_named (webapp/js/gen-form.js:1696-1762). collect() skips hidden conditional groups and read-only fields, writes nested groups into their bucket, and never clears a loaded value whose option was not found (webapp/js/gen-form.js:1764-1794). commitFiles() uploads chosen files to POST /attachments/ and writes the returned ids before collect() runs (webapp/js/gen-form.js:1795-1812). The server validates again; see errors and exceptions.
Record card¶
mountRecordForm first shows a read-only card built by buildRecordCard (webapp/js/gen-form.js:2210-2308, webapp/js/gen-form.js:2440-2484). The card shows:
- contact quick actions for
record_actions(call,message,mail) (webapp/js/gen-form.js:2058-2077,webapp/js/gen-form.js:2449-2450); - the photo header when the entity declares
photo(webapp/js/gen-form.js:2452-2460); - the Documents row (
docs.section) unlessrecord_documentsis false (webapp/js/gen-form.js:2477-2483); - navigation bar entries: Share, Edit, and Actions when
single_recordactions apply (webapp/js/gen-form.js:2319-2361); - metadata links (
RecordLinkMeta) that apply to the record; a link raisescluster:open-sectionwith parameters taken from the record (webapp/js/gen-form.js:2364-2385).
In edit mode the navigation bar shows Cancel and Save (webapp/js/gen-form.js:2318-2322).
Drafts of unsaved input¶
form-drafts.js keeps only the fields the user changed from the loaded baseline (webapp/js/gen-form.js:1255-1271, webapp/js/form-drafts.js:1-16).
| Rule | Value | Source |
|---|---|---|
| Draft key | Module, entity, record id or new, parent id, and scope, joined by a vertical bar |
webapp/js/form-drafts.js:56-61 |
| Storage | Memory, mirrored to tab session storage under cluster.session.form_drafts |
webapp/js/form-drafts.js:18-51 |
| Maximum drafts | 60; the oldest is dropped | webapp/js/form-drafts.js:20, webapp/js/form-drafts.js:94-104 |
| Fields never stored | Password fields and permission-gated fields | webapp/js/gen-form.js:2043-2052, webapp/js/form-drafts.js:69-82 |
| Cleared by | Save, Cancel, closing the app from the switcher, and logout | webapp/js/form-drafts.js:114-132, webapp/js/home.js:2539-2541 |
| Restore notice | A status line with a discard button | webapp/js/gen-form.js:1830-1844 |
When a draft is restored, fields that depend on another field are filled after their controlling field, so dependent pickers find their options (webapp/js/gen-form.js:1273-1290).
Custom selects¶
initCustomSelects() runs at boot and upgrades every current and future native select through a MutationObserver. The native element stays as the source of truth for value and change events. A search box appears from 8 options (webapp/js/customselect.js:1-17, webapp/js/customselect.js:229, webapp/js/main.js:229).
Constraints and validation¶
- The form never invents a status field. A status that changes only through actions is not on the form; a condition on it reads the loaded record (
webapp/js/gen-form.js:703-707). See actions and action sheet. - Browser tests:
web-client/_e2e_form_draft.mjs,web-client/_e2e_form_draft_matrix.mjs,web-client/_e2e_ui_forms.mjs,web-client/_e2e_party_form_fixes.mjs.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| record card | mountRecordForm |
The read-only view of a record that switches to edit mode on request. | kayıt kartı | webapp/js/gen-form.js:2310 |
| form draft | writeDraft |
The session-only copy of the fields a user changed but has not saved. | form taslağı | webapp/js/form-drafts.js:84 |
| quick create | fk.create_tooltip |
The + next to a picker that creates a referenced record without leaving the form. | Hızlı oluştur | webapp/js/gen-form.js:146-148 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.