| applyTo | **/Forms/**/*.{4DForm,4dm} |
|---|
- Use form class-driven architecture: keep logic in form classes, and delegate from
method.4dmand object methods. - Keep
.4DFormschema-compliant; move advanced visual blending to assets. - For PNG light/dark + HiDPI, always provide 4 variants:
xxx.png,xxx_dark.png,xxx@2x.png,xxx@2x_dark.png. - Prefer CSS theming for native objects when dark mode/theme adaptation is required.
- Initialize full form state in class constructor before loading data.
- Use
currentItemSourcefor listbox selection; do not useLISTBOX Get table source. - Use async state objects with
running,progress.value,progress.message, and error field. - During async actions, show progress UI and hide trigger actions consistently.
- Use naming groups with
@suffix for bulk visibility control. - Keep styling and business logic separated.
4D forms must follow the schema defined in the formsSchema.json file located in the .github/.instructions directory. This schema defines the structure and valid properties for form.4DForm files, including object types, properties, events, and styling options.
- IMPORTANT: 4D form rendering does not reliably support transparency for native object
fillandstrokevalues. - AVOID semi-transparent native colors (
rgba(...)) for liquid/frosted effects on form objects. - RECOMMENDED PATTERN: Build layered effects with
pictureobjects using PNG/SVG assets. - WHEN USING SVG ASSETS for adaptive themes; include
@media (prefers-color-scheme: dark)in SVG styles when dark mode behavior is required. - WHEN USING PNG ASSETS WITH LIGHT/DARK + HIDPI SUPPORT: Provide all four files for each image base name:
xxx.png(light, 1x)xxx_dark.png(dark, 1x)xxx@2x.png(light, 2x/Retina)xxx@2x_dark.png(dark, 2x/Retina)
- KEEP SCHEMA COMPLIANCE: Use schema-valid properties in
.4DFormand move advanced visual blending into image assets.
This project uses a Form Class-driven architecture where each form has a dedicated class that acts as a complete view controller:
- Every form declares its class in
form.4DForm:"formClass": "formMenu" - Form classes (e.g.,
formMenu.4dm) contain ALL form logic as properties and methods - Form methods (
method.4dm) delegate immediately to the form class:Form.formEventHandler(Form event code) - Object methods delegate to form class handlers:
Form.btnGeneratePeopleEventHandler(Form event code)
// File: Sources/Classes/formVectorize.4dm
property providersGen : Object // Dropdown data: {values: Collection; index: Integer}
property modelsGen : Object // Dynamic model list based on provider
property actions : Object // State management for async operations
property AIText : Text // Real-time streaming text content
Class constructor()
// Initialize all form state in constructor
This.providersGen:={values: []; index: 0}
This.actions:={embedding: {running: 0; progress: {value: 0; message: ""}}}
// Load data from database and populate dropdowns
var $providers:=ds.providerSettings.providersAvailable("embedding")
This.providersGen.values:=$providers.extract("name")
//MARK: - Form & form objects event handlers
Function formEventHandler($formEventCode : Integer)
Case of
: ($formEventCode=On Load)
OBJECT SET VISIBLE(*; "progress@"; False)
Function btnGeneratePeopleEventHandler($formEventCode : Integer)
Case of
: ($formEventCode=On Clicked)
This.actions.generatingPeople.running:=1
// Start async operation with Form reference
cs.AI_PeopleGenerator.me.generatePeopleAsync(params; Form)
//MARK: - Form actions callback functions
Function terminateGeneratePeople()
// Handle completion of async operations
OBJECT SET VISIBLE(*; "progress@"; False)
//MARK: - Computed properties
Function get embeddingDateTime() : Text
return String(This.actions.embedding.info.embeddingDate; "dd/MM/yyyy")// In form.4DForm - bind directly to form class properties
{
"dataSource": "Form.providersEmb", // Dropdown lists
"dataSource": "Form.actions.embedding.progress.value", // Progress bars
"dataSource": "Form.actions.embedding.running", // Spinners (0/1)
"dataSource": "ds.person.all().length" // Database queries
}// Always use this structure for dropdowns
This.providersEmb:={values: []; index: 0}
// Dynamic model loading based on provider selection
Function providersEmbListEventHandler($formEventCode : Integer)
Case of
: ($formEventCode=On Data Change)
This.modelsEmb:=This.setModelList(This.providersEmb; "embedding")
Function setModelList($providerList : Object; $kind : Text) : Object
var $provider:=ds.providerSettings.query("name = :1"; $providerList.currentValue).first()
var $models:=$provider.embeddingModels.models
return {values: $models.extract("model"); index: 0}// Form class property for selected item
property currentCountry : Object
property filteredData : Collection
// Form constructor initialization
Class constructor()
This.currentCountry:=Null
This.filteredData:=[]// In form.4DForm - CRITICAL: Use currentItemSource for selection binding
{
"type": "listbox",
"dataSource": "Form.countryData",
"currentItemSource": "Form.currentCountry", // THIS IS ESSENTIAL
"listboxType": "collection",
"method": "ObjectMethods/listboxCountry.4dm"
}// Event handler using the bound current item
Function listboxCountryEventHandler($formEventCode : Integer)
Case of
: ($formEventCode=On Selection Change)
If (This.currentCountry#Null)
// Access selected item properties directly
This.filteredData:=This.allData.query("country = :1"; This.currentCountry.country)
Else
This.filteredData:=[]
End if
End case
// AVOID: Never use LISTBOX Get table source - causes syntax errors
// WRONG: $selectedRow:=LISTBOX Get table source(*; "listboxName")
// RIGHT: Use currentItemSource binding and This.currentItem// Standard pattern for long-running operations
This.actions:={
embedding: {
running: 0; // 0=stopped, 1=running (for spinner)
progress: {value: 0; message: ""}; // Progress bar + status text
status: "Missing" // Overall status: "Missing"/"Done"/"In progress"
}
}
// Start async operation
Function btnVectorizeEventHandler($formEventCode : Integer)
This.actions.embedding.running:=1
OBJECT SET VISIBLE(*; "progress@"; True) // Show progress UI
OBJECT SET VISIBLE(*; "btnVectorize"; False) // Hide trigger button
// Use CALL WORKER for background processing with Form reference
CALL WORKER("embedding-worker"; Formula(cs.AI_PersonVectorizer.me.vectorize($1)); Form)// Parent form (menu) manages subforms via OBJECT SET SUBFORM
Function tabMenuEventHandler($formEventCode : Integer)
Case of
: (This.menu.currentValue="Data Gen & Embeddings 🪄")
OBJECT SET SUBFORM(*; "Subform"; "vectorize")
// Child forms communicate back via EXECUTE METHOD IN SUBFORM
Function terminateGeneratePeople()
If (Current form name="menu") // Called from subform
EXECUTE METHOD IN SUBFORM("Subform"; Formula(Form.terminateGeneratePeople()); *)
Else // Direct form usage
OBJECT SET VISIBLE(*; "progress@"; False)- Upward: Child forms call parent via
EXECUTE METHOD IN SUBFORM - Downward: Parent controls child via
OBJECT SET SUBFORM - Async callbacks: Always check
Current form nameand delegate appropriately
// Use object naming convention with @ wildcard for groups
OBJECT SET VISIBLE(*; "embedding@"; True) // Show all embedding-related objects
OBJECT SET VISIBLE(*; "btn@"; False) // Hide all buttons during operation
// Progress components always include:
// - spinner (running: 0/1)
// - progress bar (value: 0-100)
// - status text (message: Text)// Real-time text streaming (e.g., AI generation)
Function progressGeneratePeople($input : Object)
If (Not(Undefined($input.AIText)))
Form.AIText+=$input.AIText
// Auto-scroll to bottom
HIGHLIGHT TEXT(*; "InputAIText"; Length(Form.AIText); Length(Form.AIText))
GOTO OBJECT(*; "InputAIText")Function btnAskMeEventHandler($formEventCode : Integer)
If (This.modelsGen.currentValue="")
ALERT("Please select a model first")
return
End if
// Proceed with operation...// Always include error state in actions object
This.actions.embedding.error:=""
// Handle errors in progress callbacks
Function progressVectorizing($progress : Object)
If (Not(Undefined($progress.error)))
This.actions.embedding.error:=$progress.error
This.actions.embedding.running:=0 // Stop operation// Start worker with form reference for callbacks
CALL WORKER(String(Session.id)+"-embedding"; Formula(cs.AI_PersonVectorizer.me.vectorizePeople($1; $2; $3; $4)); $provider; $model; $recomputeAll; Form)
// Worker calls back to form methods for progress updates
Function progressCallback($progress : Object)
// Worker implementation calls: $form.progressVectorizing($progress)- Progress groups: Use
@suffix (e.g.,embedding@,btn@) for bulk visibility control - Consistent naming:
btnAction,providersTypeList,progressMessage,statusSpinner
- ALWAYS include constructor with complete state initialization
- ALWAYS use MARK comments:
//MARK: - Form & form objects event handlers - ALWAYS delegate from form/object methods to form class handlers
- NEVER put business logic directly in form methods
- ALWAYS use
currentItemSourcebinding for listbox selection handling - ALWAYS declare a property for the selected item (e.g.,
property currentCountry : Object) - ALWAYS initialize the selection property to
Nullin constructor - NEVER use
LISTBOX Get table sourceor similar commands - they cause syntax errors - ACCESS selected item via
This.currentItemin event handlers - PATTERN:
"currentItemSource": "Form.currentItem"in form.4DForm
- ALWAYS use string format with unit for
rowHeight:"rowHeight": "32px"(NOT numeric32) - NUMERIC VALUES FAIL: Using
"rowHeight": 40causes automatic row height calculation - STRING FORMAT WORKS: Using
"rowHeight": "32px"enforces fixed row height - OPTIONAL: Add
"verticalAlign": "middle"to center content within rows - TRUNCATION: Use
"truncateMode": "withEllipsis"on columns to prevent text wrapping - RESIZING: Use
"resizingMode": "legacy"for traditional sizing behavior - EXAMPLE:
{ "type": "listbox", "rowHeight": "32px", "verticalAlign": "middle", "columns": [ { "name": "ColName", "truncateMode": "withEllipsis" } ] }
- ALWAYS load data in the constructor, not in form events
- INITIALIZE collections to empty arrays
[]to prevent null reference errors - LOAD data synchronously in constructor for immediate display
- AVOID loading data in On Load form event - forms appear empty initially
- PATTERN: Initialize properties first, then load data in same constructor call
- EXAMPLE:
This.countries:=[]thenThis.loadCountryData()
- ALWAYS use
running: 0/1for spinner data source - ALWAYS include progress object with
valueandmessage - ALWAYS hide action buttons and show progress UI during operations
- ALWAYS handle both direct form and subform execution contexts
- USE
Form.propertysyntax for form class properties - USE
ds.table.query()for direct database binding - AVOID global variables - everything goes through form class properties
- DELEGATE all communication through form class methods
- CHECK
Current form namein shared callback functions - USE
EXECUTE METHOD IN SUBFORMfor parent-child communication
Establish a consistent visual design system with defined color schemes and typography:
// Organize colors by function, not specific values
// Header/Title sections
"headerBackground": "#colorValue"
// Informational states
"infoNormal": "#colorValue" // Normal status/info cards
"infoWarning": "#colorValue" // Warning/missing states
"infoSuccess": "#colorValue" // Success/completed states
// Background hierarchy
"contentBackground": "#colorValue"
"containerBackground": "#colorValue"
"borderColor": "#colorValue"
// Interactive elements
"inputBackground": "#colorValue"
"inputBorder": "#colorValue"// Establish consistent font hierarchy
"fontFamily": "PreferredFont" // Choose primary font for all text
"titleSize": number // Page titles/headers
"sectionSize": number // Section titles
"bodySize": number // Body text, inputs, buttons
"smallSize": number // List details, small labels// Standard info card structure - define your own consistent styling
{
"type": "rectangle",
"borderRadius": "consistentValue",
"stroke": "borderColor",
"fill": "normalStateColor", // For normal state
"fill": "warningStateColor" // For error/warning state
}// Consistent header styling across forms
{
"type": "rectangle",
"fill": "headerBackgroundColor",
"stroke": "transparent",
"height": "standardHeaderHeight"
}// Standard input appearance
{
"fontFamily": "primaryFont",
"fontSize": "bodyTextSize",
"borderStyle": "none", // Remove default borders for custom styling
"fill": "transparent", // Transparent background
"hideFocusRing": true // Clean focus appearance
}
// Rounded input containers
{
"borderRadius": "inputContainerRadius", // For text input containers
"borderRadius": "largeContainerRadius" // For larger interaction areas
}// Establish consistent spacing measurements for your project
"top": "standardTopMargin", "left": "standardLeftMargin" // Content positioning
"width": "sidebarWidth", "height": "sidebarHeight" // Sidebar dimensions
"width": "formWidth", "height": "formHeight" // Form dimensions
// Button sizing standards
"width": "actionButtonWidth", "height": "actionButtonHeight" // Primary action buttons
"width": "iconButtonWidth", "height": "iconButtonHeight" // Icon/small buttons
// Progress elements
"height": "progressBarHeight" // Progress indicators
"height": "statusTextHeight" // Status messages- Page titles: Use largest font size, consistent positioning
- Section titles: Secondary font size, consistent alignment
- Content areas: Use rounded rectangles with consistent border radius
- Separator lines: Consistent color and width throughout
- Action buttons: Consistent styling and visual identification patterns
- Limited CSS support: 4D forms support CSS2 syntax but adapted for 4D-specific needs
- StyleSheet files: Use
/SOURCES/styleSheets.css,/SOURCES/styleSheets_mac.css,/SOURCES/styleSheets_windows.css - Form-specific CSS: Can be specified via
"css"property in form.4DForm for custom stylesheets - Priority order: JSON properties override CSS unless
!importantis used - CSS classes: Objects can use
"class"property to apply CSS class styles - shared_css.json: Used for Qodly Pages, not 4D forms
/* Object type selector */
button {
font-family: Helvetica Neue;
font-size: 20px;
}
/* Object name selector */
#okButton {
font-family: Helvetica Neue;
font-size: 20px;
}
/* Class selector */
.okButtons {
font-family: Helvetica Neue;
text-align: center;
}
/* Universal selector */
* {
fill: gray;
}
/* Attribute selector */
[borderStyle] {
stroke: purple;
}- CSS property keys: Must match the property names defined in
formsSchema.json(e.g.,fill,stroke,borderStyle,fontSize) — not arbitrary CSS properties - Attribute mapping: 4D names (
fill,stroke) or CSS names (background-color,color) both accepted - Path syntax:
icon: url("/RESOURCES/Images/Buttons/edit.png"); - Color formats: CSS color names, hex values,
rgb()function
4D forms support two media queries that can be combined:
/* System color scheme */
@media (prefers-color-scheme: dark) { }
@media (prefers-color-scheme: light) { }
/* 4D form theme — possible values: win-classic, mac-classic, fluent-ui, liquid-glass */
@media (form-theme: liquid-glass) { }
@media (form-theme: fluent-ui) { }
@media (form-theme: mac-classic) { }
@media (form-theme: win-classic) { }
/* Combined */
@media (form-theme: liquid-glass) and (prefers-color-scheme: dark) { }
@media (form-theme: fluent-ui) and (prefers-color-scheme: light) { }/* Default (all themes, light mode) */
.panelCard {
fill: #FFFFFF;
stroke: #F0F4F9;
}
@media (prefers-color-scheme: dark) {
.panelCard {
fill: #1a2332;
stroke: #2a3f4f;
}
}
@media (form-theme: liquid-glass) and (prefers-color-scheme: light) {
.panelCard {
fill: #F8FAFC;
stroke: #E2E8F0;
}
}
@media (form-theme: liquid-glass) and (prefers-color-scheme: dark) {
.panelCard {
fill: #1e2d3d;
stroke: #334155;
}
}Then in form.4DForm, assign the class to objects:
"Rectangle2": {
"type": "rectangle",
"class": "panelCard"
}- CSS files are preferred when theming or dark mode adaptation is needed for native objects
- Create CSS files when the user requests it:
/SOURCES/styleSheets.css(cross-platform),/SOURCES/styleSheets_mac.css(macOS only),/SOURCES/styleSheets_windows.css(Windows only) - Inline JSON is acceptable for one-off or non-themed values (positions, sizes, non-color properties)
- Web areas: Used for complex interactive components (styling is separate topic)
// Consistent progress UI pattern across all forms
"progressBar": {
"type": "progress",
"height": "progressBarHeight",
"max": 100,
"dataSource": "Form.actions.operation.progress.value"
},
"statusMessage": {
"fontSize": "bodyTextSize",
"fontFamily": "primaryFont",
"borderStyle": "none",
"fill": "transparent"
},
"spinner": {
"type": "spinner",
"width": "spinnerWidth", "height": "spinnerHeight",
"dataSource": "Form.actions.operation.running"
}// UI state transitions using object naming conventions
OBJECT SET VISIBLE(*; "operationGroup@"; True) // Show progress group
OBJECT SET VISIBLE(*; "btn@"; False) // Hide action buttons
// Color-coded status indication in form classes
Case of
: (This.status="Missing")
$color:="warningColor" // For missing/error states
: (This.status="Done")
$color:="successColor" // For success states
: (This.status="In progress")
$color:="processingColor" // For processing states
End case- Main panel: Primary content area for complex interactions
- Side panel: Secondary content browser (listbox + subform)
- Separators: Consistent dividing lines between sections
- Input areas: Grouped input controls with consistent styling
- Info cards: Grid of status rectangles with state-based colors
- Action areas: Grouped controls by function with consistent placement
- Progress overlays: Temporary UI that replaces action controls during operations
- Search controls: Consistent positioning and spacing
- Results area: Full-width data display with custom styling
- Filter sidebar: Secondary content in consistent container styling
- ESTABLISH a consistent color palette for your project
- ASSIGN specific colors to states (error/warning, success, normal, disabled)
- USE semantic naming for colors rather than direct hex values
- AVOID introducing new colors without updating the design system
- CHOOSE a primary font family for the entire application
- DEFINE a clear hierarchy with consistent font sizes
- MAINTAIN consistent font sizes within component types
- CONSIDER readability across different screen sizes
- USE borderRadius consistently for modern appearance
- ESTABLISH standard spacing and sizing conventions
- MAINTAIN consistent margins and padding throughout forms
- GROUP related elements visually with background containers
- STANDARDIZE button appearances and behaviors
- USE consistent progress indication patterns (spinner + progress bar + status text)
- IMPLEMENT clear visual feedback for user interactions
- CONSIDER accessibility requirements for interactive elements
- CREATE reusable styling patterns for common components
- DOCUMENT your design system decisions for team consistency
- USE consistent object naming conventions for easy maintenance
- MAINTAIN separation between styling and business logic
This design system ensures consistent, professional appearance across all forms while maintaining the modern look expected in contemporary applications.