Skip to content

Enrollment Form

A complete course enrollment form demonstrating field types, validation, conditional logic, sections, and layout.

Requirements

  • Student name, email, phone (required)
  • Course selection from predefined list
  • Enrollment type: open (free) or paid
  • Payment method appears only for paid enrollment
  • Start date with future date constraint
  • Preferences section (collapsible)
  • Explicit row layout for side-by-side fields

Complete Implementation

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

Form buildEnrollmentForm({Map<String, dynamic>? initialValues}) {
  return FormBuilder(title: 'Course Enrollment')
    .description('Enroll in a course at the LMS')

    // ── Student Information ───────────────────────────────
    .section('Student Information', (s) => s
      .text('first_name', title: 'First Name')
        .required('First name is required')
        .minLength(2, message: 'At least 2 characters')

      .text('last_name', title: 'Last Name')
        .required('Last name is required')
        .minLength(2, message: 'At least 2 characters')

      .text('email', title: 'Email Address')
        .required('Email is required')
        .email('Please enter a valid email')

      .phone('phone', title: 'Phone Number')
        .required('Phone number is required')
        ,
      description: 'Your contact details',
    )

    // ── Course Selection ──────────────────────────────────
    .section('Course Details', (s) => s
      .select('course_id', title: 'Course')
        .option('FLUTTER101', title: 'Flutter Fundamentals')
        .option('DART201', title: 'Advanced Dart')
        .option('MOBILE301', title: 'Mobile Architecture')
        .option('UIDESIGN', title: 'UI Design Principles')
        .option('TESTING', title: 'Testing & Quality')
        .required('Please select a course')
      .select('enrollment_type', title: 'Enrollment Type')
        .option('open', title: 'Open Enrollment (Free)')
        .option('paid', title: 'Paid Enrollment')
        .required('Please select enrollment type')

      .dateTime('start_date', title: 'Preferred Start Date')
        .dateOnly()
        .required('Start date is required')
        ,
    )

    // ── Payment (conditional) ────────────────────────────
    .section('Payment Information', (s) => s
      .select('payment_method', title: 'Payment Method')
        .option('credit_card', title: 'Credit Card')
        .option('bank_transfer', title: 'Bank Transfer')
        .option('invoice', title: 'Invoice')
        .required('Payment method is required')

      .text('billing_email', title: 'Billing Email')
        .email()
        .placeholder('Leave blank to use student email')

      .text('card_number', title: 'Card Number')
        .showWhen(When.fieldEquals('payment_method', 'credit_card'))
        .required('Card number is required')
        .pattern(r'^\d{16}$', message: 'Enter 16 digits')

      .text('card_expiry', title: 'Expiry (MM/YY)')
        .showWhen(When.fieldEquals('payment_method', 'credit_card'))
        .required()
        .pattern(r'^\d{2}/\d{2}$', message: 'Format: MM/YY')

      .text('card_cvv', title: 'CVV')
        .showWhen(When.fieldEquals('payment_method', 'credit_card'))
        .required()
        .secure()
        .pattern(r'^\d{3,4}$', message: '3 or 4 digits')

      .text('bank_name', title: 'Bank Name')
        .showWhen(When.fieldEquals('payment_method', 'bank_transfer'))
        .required()

      .text('account_number', title: 'Account Number')
        .showWhen(When.fieldEquals('payment_method', 'bank_transfer'))
        .required()

      .text('company_name', title: 'Company Name')
        .showWhen(When.fieldEquals('payment_method', 'invoice'))
        .required()

      .text('purchase_order', title: 'PO Number')
        .showWhen(When.fieldEquals('payment_method', 'invoice'))
        ,
      rules: [When.fieldEquals('enrollment_type', 'paid').show()],
    )

    // ── Preferences (collapsible) ────────────────────────
    .section('Preferences', (s) => s
      .boolean('email_notifications', title: 'Email Notifications')
        .help('Receive course updates and schedule changes')
      .boolean('sms_notifications', title: 'SMS Notifications')
        .help('Receive text message reminders')
      .select('schedule_preference', title: 'Schedule Preference')
        .option('morning', title: 'Morning (9am - 12pm)')
        .option('afternoon', title: 'Afternoon (1pm - 5pm)')
        .option('evening', title: 'Evening (6pm - 9pm)')
        .option('weekend', title: 'Weekend')
      .text('special_requirements', title: 'Special Requirements')
        .multiline(3)
        .placeholder('Dietary needs, accessibility, etc.'),
      collapsible: true,
      description: 'Optional preferences for your enrollment',
    )

    // ── Terms ────────────────────────────────────────────
    .boolean('accept_terms', title: 'I accept the terms and conditions')
      .required('You must accept the terms')

    .build(initialValues: initialValues);
}

Usage

Creating a New Enrollment

dart
class EnrollmentPage extends StatefulWidget {
  @override
  State<EnrollmentPage> createState() => _EnrollmentPageState();
}

class _EnrollmentPageState extends State<EnrollmentPage> {
  late final Form form;

  @override
  void initState() {
    super.initState();
    form = buildEnrollmentForm();
  }

  @override
  void dispose() {
    form.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Course Enrollment')),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            vyuh.content.buildContent(context, form),
            SizedBox(height: 24),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              spacing: 12,
              children: [
                OutlinedButton(
                  onPressed: () => form.resetForm(),
                  child: Text('Reset'),
                ),
                ElevatedButton(
                  onPressed: _submit,
                  child: Text('Enroll'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _submit() async {
    if (!form.validate()) return;

    // Check soft validation breaches
    final breaches = form.collectSoftBreaches();
    if (breaches.isNotEmpty) {
      final proceed = await _confirmBreaches(breaches);
      if (!proceed) return;
    }

    // Run async validations
    final asyncValid = await form.validateAllAsync();
    if (!asyncValid) return;

    // Submit
    final values = form.currentValues;
    await api.createEnrollment(values);
  }
}

Editing an Existing Enrollment

dart
final existingEnrollment = await api.getEnrollment(id);

form = buildEnrollmentForm(initialValues: {
  'first_name': existingEnrollment.firstName,
  'last_name': existingEnrollment.lastName,
  'email': existingEnrollment.email,
  'phone': existingEnrollment.phone,
  'course_id': existingEnrollment.courseId,
  'enrollment_type': existingEnrollment.type,
  'start_date': existingEnrollment.startDate,
  'payment_method': existingEnrollment.paymentMethod,
});

What This Demonstrates

FeatureUsage
Sections4 logical groups with titles and descriptions
Collapsible sectionPreferences section
Conditional sectionPayment visible for paid enrollment
Conditional fieldsCard/bank/invoice fields per payment method
Compound conditionsNested visibility within conditional section
ValidationRequired, email, pattern, min length
Row layoutSide-by-side fields through row blocks and row item spans
Secure fieldCVV field
Initial valuesPre-population for edit mode
Soft validationBreach collection before submit
Async validationServer-side checks before submit

Next Steps