Validation (The M Project 1.x) - mwaylabs/The-M-Project GitHub Wiki
Introduction
Validation is one of the features in the framework that helps app developers to have consistent data inside the application. Simply said, it validates data against a certain definition that is defined by the developer. Validation can take place in the view as well as in the model. For example in the view, the app developer might want to have a form not be submitted until all necessary input in the textfields is given and that the input of the user in an email field is syntactically an email adress. In MVC the model is the gatekeeper to persistence. the model represents and persists the application's business data. With this task in mind it makes sense that models validate themselves before persisting into storage. The other way around this means, all data that is persisted by the model is valid and all model objects fetched from storage are valid objects.
Who's doing the job?
The Validation is done by the so called Validators which are instances of the prototype object M.Validator
. There are several predefined validators shipped with the framework that can be used out of the box:
M.DateValidator
: Checks if it is possible to create a date from the value by using it withM.Date.create()
, which means ifYES
then it is a date.M.EmailValidator
: Checks if the given string satisfies the email adress pattern.M.NotMinusValidator
: Checks if a value is a negative number. Works with strings as well as with numbers.NumberValidator
: Checks if a value represents a number. Works with strings as well as with numbers. (Object type validation can be done withtypeof()
, e.g. (typeof(1) === 'number'
).M.PhoneValidator
: Checks if a value represents a phone number.M.PresenceValidator
: Checks if a value is given.M.UrlValidator
: Checks if a value is a valid URL.
How to use a validator with a model?
The model prototype object defines a boolean property usesValidation
which defaults to YES
. That means the model is validated before it is saved. But what validators are used? The developer needs to name them on model definition. Let's assume we have a model Contact
with the following properties:
firstName
lastName
email
age
Our model definition now looks like this (our app is named Contacts):
Contacts.Contact = M.Model.create({
__name__: 'Contact' // generated by model generator
firstName: M.Model.attr('String', {
isRequired: YES
},
lastName: M.Model.attr('String', {
isRequired: YES
},
email: M.Model.attr('String', {
validators: [M.EmailValidator]
},
age: M.Model.attr('Integer', {
validators: [M.NumberValidator, M.NotMinusValidator]
}
}, M.DataProviderWebSql.configure({
dbName: 'contacts_db'
}));
As you can see, validators are named for each property by setting an array of validators to the validators property in the parameter object passed to the attr
method of the model. This method returns an object of M.ModelAttribute
that contains all this information we pass here. Here, email and age are validated with the named validators. Naturally, if no validators are set, no validation is performed for this property. It seems that the model properties firstName
and lastName
are not validated because they don't have any validators passed. At first sight this is true. At the second sight we see the property isRequired
set to YES
. Isn't this also a validation target? Yes, it is: all model properties that have isRequired
set to YES
automatically get the M.PresenceValidator
added to their validators array. Using isRequired
is just a more readable way for this very important property constraint. The property is set to NO
by default. Not setting it to YES
and naming the M.PresenceValidator
in the validators array has the same impact as setting the property to YES
.
How to use a validator with a view?
Instead of validating on the model's side, it is also possible to use validation with a view. Since model validation is performed right before saving it, a validation in the view's side can prevent you from creating an invalid record at all. Other frameworks and technologies typically would call this input validation.
Using validation with a view is basically the same as with a model: You need to set the validators
property and specify the validators you want to use – either some of the predefined validators or your own. To simplify the validation process itself, you have to use a wrapper view element called M.FormView
. This wrapper then can be used to easily trigger the validation on all of the M.FormView
's child views. So for example a single M.TextFieldView
with a M.PresenceValidator
would be defined like this:
form: M.FormView.design({
firstName: M.TextFieldView.design({
name: 'firstName',
label: 'Firstname',
validators: [M.PresenceValidator.customize({
msg: 'Firstname is required'
})]
})
});
Once you have specified some validators for your views that are wrapped inside an M.FormView
container, you can simply trigger the validation out of a controller by calling the validate()
method of M.FormView
. So let's say you submit your input elements on a button click to a controller method. Then this method could look something like:
readInput: function() {
if(!M.ViewManager.getView('page', 'form').validate()) {
if(M.Validator.validationErrors) {
M.ViewManager.getView('page', 'form').showErrors();
return;
}
}
// if the validation returns true, do what ever you want to do with your input data...
}
As you can see, the validate()
method of M.FormView
returns either true
or false
. If it returns true
, everything is fine! If false
is returned, at least one validation went wrong. Then you can use the validationErrors
property of M.Validator
to get further knowledge about which validation(s) went wrong. This property is a simple array containing information about every single validation that returned false
. A even more simple solution is to use the showErrors()
of your M.FormView
. This will automatically show an M.AlertDialogView
, that displays the specified error messages for all invalid validations.
If you need a more custom behaviour, you will have to iterate through mentioned validationErrors
property of M.Validator
. Such an error, e.g. for the M.TextFieldView
of our example, will look like:
msg: "Firstname is required"
validator: "PRESENCE"
viewId: "m_01"
So you will know which view caused the validation error (viewId
), which validator caused the error (validator
) and what message was defined (msg
). This allows you to implement your own custom and even M.FormView
specific behaviour for handling validation errors, since every M.FormView
can be handled differently.
How do I write my own validator?
Naturally, the framework cannot deliver validators for every imaginable task. Luckily it is easily possible to write your own validators. Basically all you have to do is extend M.Validator
.
M.MyOwnValidator = M.Validator.extend({
validate: function(obj) {
// your validation code here...
}
});
The one thing you need to implement inside your validator is a function named validate
which takes an object as parameter (contains the value that's going to be validated) and returns a boolean (YES
/ NO
). Save your code to a file and put it into a validators directory inside your application (next to controllers, views, ...). Alternatively it is possible to define your own validators inline with the model definition:
zip: M.Model.attr('String', {
validators: [M.NumberValidator, {
validate: function(obj) { if obj.value.length > 5) { return NO; } return YES; }
}]
}
This validator checks if the string provided for the zip code property has more than 5 characters (we will not discuss if this makes sense here) and returns NO (validation failed) if it is more than five and YES (validation passed) if not.
How can I customize a given validator?
It is possible to customize an existing validator by using the customize
function that every validator has. Just pass it the parameters you want to change inside the validator: it is also possible to pass another validate
function.
zip: M.Model.attr('String', {
validators: [M.NumberValidator.customize({
msg: 'Not a valid zip code. Please enter another one.' // defines your own message that is written to the error buffer
})]
}
You have to weigh whether to write your own validator or customize an existing one. Surely, customizing an existing one only changes the validator for the one property, not for all properties where the validator is used.