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
6
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)
View/report issues

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