Skip to content

FormBuilder DSL

The FormBuilder provides a fluent API for constructing forms programmatically. It uses an auto-finalize pattern where starting a new field automatically commits the previous one.

Basic Usage

dart
import 'package:vyuh_feature_forms/dsl/form_builder.dart';

final form = FormBuilder(title: 'My Form')
  .text('name', title: 'Name')
    .required()
  .text('email', title: 'Email')
    .email()
  .build();

FormBuilder Methods

Form-Level Configuration

dart
FormBuilder(title: 'Title')
  .description('Form description text')
  .hSpacing(16.0)    // Horizontal grid spacing
  .vSpacing(16.0)    // Vertical grid spacing

Field Creation Methods

Each method starts building a new field and auto-finalizes the previous one:

MethodReturnsCreates
.text(name, title:)TextFieldBuilderTextField
.number(name, title:)NumberFieldBuilderNumberField
.select(name, title:)SelectFieldBuilderSelectField
.boolean(name, title:)BooleanFieldBuilderBooleanField
.dateTime(name, title:)DateTimeFieldBuilderDateTimeField
.slider(name, title:, min:, max:, divisions:)SliderFieldBuilderSliderField
.phone(name, title:)PhoneFieldBuilderPhoneNumberField
.file(name, title:)FileFieldBuilderFilePickerField
.image(name, title:)ImageFieldBuilderImagePickerField
.formula(name, title:, expression:)FormulaFieldBuilderFormulaField
.reference(name, title:)ReferenceFieldBuilderReferenceField

Section Method

dart
.section('Section Title', (s) => s
  .text('field1', title: 'Field 1')
  .text('field2', title: 'Field 2'),
  description: 'Optional description',
  collapsible: true,
  initiallyCollapsed: false,
  rules: [When.fieldTrue('show_section').show()],
)

The callback receives a fresh FormBuilder for the section's contents.

Build Method

dart
.build(initialValues: {'name': 'Alice', 'email': 'alice@example.com'})

Returns a fully constructed Form with wired FormGroup and all validations.

Shared Field Methods

Every FieldBuilder inherits these methods:

Validation

dart
.required([String? message])
.email([String? message])
.pattern(String regex, {String? message})
.minLength(int min, {String? message})
.maxLength(int max, {String? message})
.identifier([String? message])
.asyncValidation(AsyncValidationConfiguration validation)

Rules

dart
.showWhen(When condition)
.hideWhen(When condition)
.enableWhen(When condition)
.disableWhen(When condition)

Properties

dart
.value(Object initialValue)
.placeholder(String text)
.help(String text)
.enabled(bool value)           // Static enable/disable
.visible(bool value)           // Static show/hide
.withLayout(LayoutConfiguration layout)

Derivation

dart
.derivedFrom(
  {'source_field_1', 'source_field_2'},
  transformer: (formValues) => computedValue,
  initiallyDirty: false,  // Set true in edit mode
)

Type-Specific Methods

TextFieldBuilder

dart
.text('field', title: 'Title')
  .secure()              // Password mode
  .multiline(3)          // Textarea with N lines
  .charLimit(200)        // Max character count
  .mask('###-##-####')   // Input formatter mask

NumberFieldBuilder

dart
.number('field', title: 'Title')
  .decimal()             // Allow decimals (default)
  .integerOnly()         // Integer only
  .range(min: 1, max: 100)
  .min(1)
  .max(100)

SelectFieldBuilder

dart
.select('field', title: 'Title')
  .option('value', title: 'Display')
  .options({'val1': 'Display 1', 'val2': 'Display 2'})
  .optionGroup('Group Title', [SelectOption(...)])
  .multiple()            // Multi-select mode

BooleanFieldBuilder

dart
.boolean('field', title: 'Title')
  .tristate()            // true/false/null

DateTimeFieldBuilder

dart
.dateTime('field', title: 'Title')
  .dateOnly()            // Date picker only
  .timeOnly()            // Time picker only
  .dateAndTime()         // Both (default)

SliderFieldBuilder

dart
.slider('field', title: 'Title', min: 0, max: 10, divisions: 10)

FormulaFieldBuilder

dart
.formula('field', title: 'Title', expression: 'a + b * c')

Complete Example: Course Enrollment

dart
final form = FormBuilder(title: 'Course Enrollment')
  // Student info (side-by-side)
  .text('first_name', title: 'First Name')
    .required('First name is required')
  .text('last_name', title: 'Last Name')
    .required('Last name is required')
  .text('email', title: 'Email')
    .required()
    .email()
  .phone('phone', title: 'Phone')

  // Course selection
  .select('course_id', title: 'Course')
    .option('CS101', title: 'Intro to Computer Science')
    .option('CS201', title: 'Data Structures')
    .option('CS301', title: 'Algorithms')
    .required('Please select a course')

  // Enrollment details
  .select('enrollment_type', title: 'Enrollment Type')
    .option('open', title: 'Open (Free)')
    .option('paid', title: 'Paid')
    .required()
  .dateTime('start_date', title: 'Start Date')
    .dateOnly()
    .required()

  // Conditional payment section
  .section('Payment', (s) => s
    .select('payment_method', title: 'Payment Method')
      .option('credit_card', title: 'Credit Card')
      .option('bank_transfer', title: 'Bank Transfer')
      .required('Payment method is required')
    .text('card_number', title: 'Card Number')
      .showWhen(When.fieldEquals('payment_method', 'credit_card'))
      .required()
      .pattern(r'^\d{16}$', message: 'Enter 16 digits'),
    rules: [When.fieldEquals('enrollment_type', 'paid').show()],
  )

  // Preferences
  .section('Preferences', (s) => s
    .boolean('email_updates', title: 'Email Updates')
      .help('Receive course announcements')
    .select('schedule', title: 'Schedule')
      .option('morning', title: 'Morning')
      .option('afternoon', title: 'Afternoon')
      .option('evening', title: 'Evening'),
    collapsible: true,
  )

  .build();

Auto-Finalize Pattern

The key design insight is that field builder methods are defined on both FormBuilder and FieldBuilder. When you call .text() on a TextFieldBuilder, it delegates to the parent FormBuilder, which:

  1. Finalizes the current field (calls buildField())
  2. Adds it to the items list
  3. Creates and returns the new TextFieldBuilder

This eliminates the need for explicit .done() or .end() calls between fields.

Rows and Explicit Layout

The DSL builds a sequential Form with optional sections. Field builders do not expose row spans. For explicit side-by-side layouts, create a FormRowBlock in the runtime model or use Row blocks in the visual editor; both serialize row items as { field, span } while each field still registers as a normal control in the form's single FormGroup.

Next Steps