164 lines
4.9 KiB
Dart
164 lines
4.9 KiB
Dart
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.']);
|
|
}
|
|
}
|
|
}
|
|
}
|