platform/lib/src/common_fields.dart
2019-10-16 21:09:01 -04:00

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.']);
}
}
}
}