If you have tested the Spiral 1 – the power of HTML5 forms version thoroughly, you would have come across some things that could be improved:
DateTime
value, but a value in the future is accepted.M
or F
.So HTML5 doesn't give us full validation possibilities; to remedy and supplement that, we must add code validation, see bank_terminal_s2.dart
:
InputElement name, address, email, birth_date, gender; InputElement number, balance, pin_code; void main() { // bind variables to DOM elements: name = querySelector('#name'), address = querySelector('#address'), email = querySelector('#email'), birth_date = querySelector('#birth_date'), gender = querySelector('#gender'), number = querySelector('#number'), balance = querySelector('#balance'), pin_code = querySelector('#pin_code'), lbl_error = querySelector('#error'), lbl_error.text = ""; lbl_error.style..color = "red"; // attach event handlers: // checks for not empty in onBlur event: name.onBlur.listen(notEmpty); (1) email.onBlur.listen(notEmpty); number.onBlur.listen(notEmpty); pin_code.onBlur.listen(notEmpty); // other checks: birth_date.onChange.listen(notInFuture); (2) birth_date.onBlur.listen(notInFuture); (3) gender.onChange.listen(wrongGender); (4) balance.onChange.listen(nonNegative); (5) balance.onBlur.listen(nonNegative); (6) } notEmpty(Event e) { InputElement inel = e.currentTarget as InputElement; var input = inel.value; if (input == null || input.isEmpty) { lbl_error.text = "You must fill in the field ${inel.id}!"; inel.focus(); } } notInFuture(Event e) { DateTime birthDate; try { birthDate = DateTime.parse(birth_date.value); } on ArgumentError catch(e) { (7) lbl_error.text = "This is not a valid date!"; birth_date.focus(); return; } DateTime now = new DateTime.now(); if (!birthDate.isBefore(now)) { lbl_error.text = "The birth date cannot be in the future!"; birth_date.focus(); } } wrongGender(Event e) { var sex = gender.value; if (sex != 'M' && sex != 'F') { lbl_error.text = "The gender must be either M (male) or F (female)!"; gender.focus(); } } nonNegative(Event e) { num input; try { input = int.parse(balance.value); } on ArgumentError catch(e) { lbl_error.text = "This is not a valid balance!"; balance.focus(); return; } if (input < 0) { lbl_error.text = "The balance cannot be negative!"; balance.focus(); } }
When the user leaves a field, the blur
event fires; so, to check whether a value was given in the field, use the onBlur
event handler. For example, for the name
field in line (1)
we show an alert window and use the focus
method to put the cursor back in the field. Notice that the notEmpty
method can be used for any input field. Controlling a given value is best done in the onChange
event handler, as we do for birth_date
in line (2)
; we use a try
construct to catch
the ArgumentError
exception that occurs in line (7)
when no date is given or selected (leave the method with return in the error case, so that no further processing is done). To ensure that the user cannot leave the field, call the same method in onBlur
(in line (3)
). For checking gender and balance, we also use the
onChange
handlers (lines (4)
and (5)
). Check the e-mail address with a regular expression pattern in an onChange
event handler as an exercise. Now we are ready to store the data; we add the following line after line (6)
:
btn_create.onClick.listen(storeData);
In the method storeData
, we first create the objects:
storeData(Event e) { // creating the objects: Person p = new Person(name.value, address.value, email.value, gender.value, DateTime.parse(birth_date.value)); BankAccount bac = new BankAccount(p, number.value, double.parse(balance.value), int.parse(pin_code.value));}
Notice that we have to perform data conversions here; the value from the balance
and pin_code
Element objects is always a string!
For a more robust application, we should also include validations in the model classes themselves, that way, they are independent of the front-end graphical interface and could be re-used in another application (provided we place them in their own library to import in this application, instead of using the part construct). We illustrate this with the number
field in BankAccount
(the class code in this spiral also contains the validations for the other fields). First, we make the field private by prefixing it with an _
:
String _number;
Then, we make a get
and set
method for this property; it is in the setter that we can validate the incoming value:
String get number => _number; set number(value) { if (value == null || value.isEmpty) throw new ArgumentError("No number value is given"); (1) var exp = new RegExp(r"[0-9]{3}-[0-9]{7}-[0-9]{2}"); (2) if (exp.hasMatch(value)) _number = value; (3) }
In the model classes, we don't have the possibility to show an error window, but we can throw an error or exception in line (1)
. To catch this error, we have to change the code in storeData
, where the
BankAccount
object is created:
try { BankAccount bac = new BankAccount(p, number.value, double.parse(balance.value), int.parse(pin_code.value)); } catch(e) { window.alert(e.toString()); }
To test the particular format, we make use of the RegExp
class in the built in dart:core
library. We construct an object exp of this class by passing the regular expression as a raw string in line (2)
; then, we use hasMatch
to test whether the value conforms to the format of exp
(line (3)
).
Using this mechanism, it turns out that we cannot use the short version of the constructors any more, because a setter can only be called from within the constructor body; so the BankAccount
constructor changes to:
BankAccount(this.owner, number, balance, pin_code): date_created = new DateTime.now() { this.number = number; this.balance = balance; this.pin_code = pin_code; }
18.227.190.211