Skip to main content

jaspr_forms 0.1.1 copy "jaspr_forms: ^0.1.1" to clipboard
jaspr_forms: ^0.1.1 copied to clipboard

Form validation and management for Jaspr. Declarative validators, field state tracking, and form submission with support for all 3 rendering modes (static/server/client).

example/lib/main.dart

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_forms/jaspr_forms.dart';

void main() {
  runApp(App());
}

class App extends StatelessComponent {
  const App({super.key});

  @override
  Component build(BuildContext context) {
    return div(classes: 'container', [
      h1([.text('Jaspr Forms Example')]),
      p([.text('A registration form demonstrating validation with jaspr_forms.')]),
      const RegistrationForm(),
    ]);
  }
}

@client
class RegistrationForm extends StatefulComponent {
  const RegistrationForm();

  @override
  State<RegistrationForm> createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  String? _submitted;

  @override
  Component build(BuildContext context) {
    return FormBuilder(
      validators: {
        'username': [
          Required(message: 'Username is required').call,
          MinLength(3, message: 'Username must be at least {min} characters').call,
          Custom((v) => v.contains(' ') ? 'Username cannot contain spaces' : null).call,
        ],
        'email': [Required(message: 'Email is required').call, Email(message: 'Enter a valid email address').call],
        'password': [
          Required(message: 'Password is required').call,
          MinLength(8, message: 'Password must be at least {min} characters').call,
          Custom((v) {
            if (!RegExp(r'[A-Z]').hasMatch(v)) return 'Password must contain an uppercase letter';
            if (!RegExp(r'[0-9]').hasMatch(v)) return 'Password must contain a number';
            return null;
          }).call,
        ],
        'age': [
          Required(message: 'Age is required').call,
          Custom((v) {
            final n = int.tryParse(v);
            if (n == null) return 'Age must be a number';
            if (n < 18) return 'You must be at least 18 years old';
            if (n > 120) return 'Please enter a valid age';
            return null;
          }).call,
        ],
        'bio': [MaxLength(200, message: 'Bio must be at most {max} characters').call],
      },
      initialValues: {'bio': ''},
      onSubmit: (values) async {
        await Future.delayed(const Duration(milliseconds: 800));
        _submitted = values.toString();
      },
      onChanged: (_) {},
      children: [
        _Field(name: 'username', label: 'Username', inputType: InputType.text),
        _Field(name: 'email', label: 'Email', inputType: InputType.email),
        _Field(name: 'password', label: 'Password', inputType: InputType.password),
        _Field(name: 'age', label: 'Age', inputType: InputType.number),
        _Field(name: 'bio', label: 'Bio', isTextarea: true),
        div(classes: 'btn-row', [
          button(classes: 'primary', disabled: context.form.isSubmitting, onClick: () => context.form.submit(), [
            .text(context.form.isSubmitting ? 'Submitting...' : 'Register'),
          ]),
          button(
            classes: 'secondary',
            onClick: () {
              context.form.reset();
              _submitted = null;
              setState(() {});
            },
            [.text('Reset')],
          ),
        ]),
        if (_submitted != null)
          div(classes: 'success-card', [
            h3([.text('Registration Successful')]),
            pre([.text(_submitted!)]),
          ]),
      ],
    );
  }
}

class _Field extends StatefulComponent {
  final String name;
  final String label;
  final InputType? inputType;
  final bool isTextarea;

  const _Field({required this.name, required this.label, this.inputType, this.isTextarea = false});

  @override
  State<_Field> createState() => _FieldState();
}

class _FieldState extends State<_Field> {
  String _value = '';

  @override
  Component build(BuildContext context) {
    final error = context.form.field(component.name)?.error;

    final attrs = <String, String>{if (component.inputType != null) 'type': component.inputType!.name, 'placeholder': component.label};

    return div(classes: 'form-group', [
      label([.text(component.label)]),
      if (component.isTextarea)
        textarea(
          [],
          classes: error != null ? 'error' : null,
          attributes: attrs,
          events: {
            'input': (e) {
              final v = (e.target as dynamic).value as String? ?? '';
              _value = v;
              context.form.updateField(component.name, v);
            },
          },
        )
      else
        input(
          type: component.inputType ?? InputType.text,
          classes: error != null ? 'error' : null,
          value: _value,
          onInput: (v) {
            final str = v.toString();
            _value = str;
            context.form.updateField(component.name, str);
          },
        ),
      if (error != null) div(classes: 'error-text', [.text(error)]),
    ]);
  }
}
0
likes
150
points
46
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Form validation and management for Jaspr. Declarative validators, field state tracking, and form submission with support for all 3 rendering modes (static/server/client).

Homepage
Repository (GitLab)

Topics

#jaspr #web #form #validation

Funding

Consider supporting this project:

ko-fi.com

License

GPL-3.0 (license)

Dependencies

jaspr

More

Packages that depend on jaspr_forms