import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; import 'field.dart'; import 'form_renderer.dart'; /// A [Field] that accepts plain text. class TextField extends Field<String> { /// If `true` (default), then renderers will produce a `<textarea>` element. final bool isTextArea; /// If `true` (default), then the input will be trimmed before validation. final bool trim; TextField(String name, {String label, bool isRequired = true, this.isTextArea = false, this.trim = true}) : super(name, label: label, isRequired: isRequired); @override FutureOr<U> accept<U>(FormRenderer<U> renderer) => renderer.visitTextField(this); @override FutureOr<FieldReadResult<String>> read(RequestContext req, Map<String, dynamic> fields, Iterable<UploadedFile> files) { var value = fields[name] as String; if (trim) { value = value?.trim(); } if (value == null) { return null; } else if (trim && value.isEmpty) { return null; } else { return FieldReadResult.success(value); } } } /// A [Field] that checks simply for its presence in the given data. /// Typically used for checkboxes. class BoolField extends Field<bool> { BoolField(String name, {String label, bool isRequired = true}) : super(name, label: label, isRequired: isRequired); @override FutureOr<U> accept<U>(FormRenderer<U> renderer) => renderer.visitBoolField(this); @override FutureOr<FieldReadResult<bool>> read(RequestContext req, Map<String, dynamic> fields, Iterable<UploadedFile> files) { if (fields.containsKey(name)) { return FieldReadResult.success(true); } else { return FieldReadResult.success(false); } } } /// A [Field] that parses its value as a [num]. class NumField<T extends num> extends Field<T> { // Reuse text validation logic. TextField _textField; /// The minimum/maximum value for the field. final T min, max; /// The amount for a form field to increment by. final num step; NumField(String name, {String label, bool isRequired = true, this.max, this.min, this.step}) : super(name, label: label, isRequired: isRequired) { _textField = TextField(name, label: label, isRequired: isRequired); } @override FutureOr<U> accept<U>(FormRenderer<U> renderer) => renderer.visitNumField(this); @override Future<FieldReadResult<T>> read(RequestContext req, Map<String, dynamic> fields, Iterable<UploadedFile> files) async { var result = await _textField.read(req, fields, files); if (result == null) { return null; } else if (result.isSuccess != true) { return FieldReadResult.failure(result.errors); } else { var value = num.tryParse(result.value); if (value != null) { if (min != null && value < min) { return FieldReadResult.failure(['"$name" can be no less than $min.']); } else if (max != null && value > max) { return FieldReadResult.failure( ['"$name" can be no greater than $max.']); } else { return FieldReadResult.success(value as T); } } else { return FieldReadResult.failure(['"$name" must be a number.']); } } } } /// A [NumField] that coerces its value to a [double]. class DoubleField extends NumField<double> { DoubleField(String name, {String label, bool isRequired = true, num step, double min, double max}) : super(name, label: label, isRequired: isRequired, step: step, min: min, max: max); @override Future<FieldReadResult<double>> read(RequestContext req, Map<String, dynamic> fields, Iterable<UploadedFile> files) async { var result = await super.read(req, fields, files); if (result == null) { return null; } else if (!result.isSuccess) { return FieldReadResult.failure(result.errors); } else { return FieldReadResult.success(result.value.toDouble()); } } } /// A [NumField] that requires its value to be an [int]. /// Passing a [double] will result in an error, so [step] defaults to 1. class IntField extends NumField<int> { IntField(String name, {String label, bool isRequired = true, num step = 1, int min, int max}) : super(name, label: label, isRequired: isRequired, step: step, min: min, max: max); @override Future<FieldReadResult<int>> read(RequestContext req, Map<String, dynamic> fields, Iterable<UploadedFile> files) async { var result = await super.read(req, fields, files); if (result == null) { return null; } else if (!result.isSuccess) { return FieldReadResult.failure(result.errors); } else { var value = result.value; if (value is int) { return FieldReadResult.success(result.value); } else { return FieldReadResult.failure(['"$name" must be an integer.']); } } } }