Spiral 2 – how to validate data with Dart

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:

  1. The HTML5 control only does its checks when the Create Account button is clicked on; it would be better if the user is alerted earlier, preferably after filling in each field.
  2. The Birth Date value is checked to be a correct DateTime value, but a value in the future is accepted.
  3. The Gender field will gladly accept other values than M or F.
  4. The Balance field accepts a negative number.

So HTML5 doesn't give us full validation possibilities; to remedy and supplement that, we must add code validation, see bank_terminal_s2.dart:

Note

For code files of this section, refer to chapter 6ank_terminal_s2 in the code bundle.

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!

Validation in the model

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;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.190.211