Validation - MVVM Approach
End-user data entry errors are common and it's the developer's responsibility to prevent associated issues. DevExtreme includes a powerful and flexible validation engine and a number of readily usable validation rules. You can choose from standard restrictions (such as "required","stringLength", etc.) or implement your own logic with any level of detail, including remote validation.
Like the rest in DevExtreme, data validation is consistent across all of our shipping themes.
The DevExtreme validation engine comes with a powerful and straightforward API. This enables you to check validation rules at any points. In addition, you are free to use a custom validation engine but indicate an invalid validation result in a UI using the DevExtreme mechanisms. And vise versa, you can use the DevExtreme validation engine, but indicate an invalid validation result in a custom manner.
Read below to learn how to perform the tasks that can be performed using the DevExtreme Validation.
Similar article is available for the jQuery approach.
Associate Editor with Validator
To validate an editor value, associate either the UI editor or the underlying view or application model field with the Validator widget.
To validate a DevExtreme editor, the latter should be associated with the Validator widget.
The Validator widget validates the editor's value against a predefined set of rules. To define the rules to be checked, assign an array of rules to the validator's validationRules configuration option.
var validatorOptions = { validationRules: [ { type: 'required', message: 'login is required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' } ] }
As you can see, validation rules have the type, message and type-specific fields. The type field specifies the rule type, the message field specifies the message to be displayed if a rule is not satisfied. Rule type specific fields are required to specify additional conditions for validation. You can see a full structure of each predefined rule type in the Validation Rules Reference section.
To associate an editor with a validator, apply the corresponding validator attribute to the element that is bound to the required editor (see the code below).
AngularJS
<div dx-text-box="{ value: login, placeholder: 'Login' }", dx-validator="{ validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div>
Knockout
<div data-bind="dxTextBox: { value: login, placeholder: 'Login' }, dxValidator: { validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div>
Validate Editor Values
The editors that are associated with the Validator widget are automatically validated on the event specified by their valueChangeEvent option. You can also validate several editors at once. For this purpose, there are validation methods that validate a validation group. So, you should associate editors with validation groups.
Define Validation Groups
Add the editors that should be validated together to the div element of the ValidationGroup widget.
AngularJS
<div id="sampleGroup" dx-validation-group="{}"> <div dx-text-box="{ value: login, placeholder: 'Login' }", dx-validator="{ validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div dx-text-box="{ value: password, mode: 'password' }", dx-validator="{ validationRules: [{ type: 'required, message: 'Password is required' }] }"> </div> </div>
Knockout
<div id="sampleGroup" data-bind="dxValidationGroup : {}" > <div data-bind="dxTextBox: { value: login, placeholder: 'Login' }, dxValidator: { validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div data-bind="dxTextBox: { value: password, mode: 'password' }, dxValidator: { validationRules: [{ type: 'required', message: 'Password is required' }] }"> </div> </div>
Validate Groups
To validate a group, use one of the following approaches.
At a button's click event
Associate a Button widget with the required validation group. In this instance, the associated group will come in the validationGroup field of the parameter passed to a button's click event handler. Call the validate() method of the button's validation group.To associate the Button widget with a validation group, create the ValidationGroup widget and add the Button widget inside of the element representing the validation group.
NOTEIf you are going to use a button to validate the view or application model's validation group, do not associate the button with a validation group. The validation group will come in the parameter object of the click event handler.AngularJS
HTMLJavaScript<div id="sampleGroup" dx-validation-group="{}"> <div dx-text-box="{ value: login, placeholder: 'Login' }", dx-validator="{ validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div dx-text-box="{ value: password, mode: 'password' }", dx-validator="{ validationRules: [{ type: 'required', message: 'Password is required' }] }"> </div> <div dx-button="{ text: 'Validate and submit', onClick: validateAndSubmit }"></div> </div>
function Controller($scope) { $scope.login = ''; $scope.password = ''; $scope.validateAndSubmit = function(params) { var result = params.validationGroup.validate($scope); if (result.isValid) { DevExpress.ui.notify({ message: "You are registered", position: { my: "left top", at: "left top", offset: 15 } }, "success", 3000); } } }
Knockout
HTMLJavaScript<div id="sampleGroup" data-bind="dxValidationGroup : {}" > <div data-bind="dxTextBox: { value: login, placeholder: 'Login' }, dxValidator: { validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div data-bind="dxTextBox: { value: password, mode: 'password' }, dxValidator: { validationRules: [{ type: 'required', message: 'Password is required' }] }"> </div> <div data-bind="dxButton: { text: 'Validate and submit', onClick: validateAndSubmit }"></div> </div>
var viewModel = { login: ko.observable(""), password: ko.observable(""), validateAndSubmit: function(params) { var result = params.validationGroup.validate(); if (result.isValid) { DevExpress.ui.notify({ message: "You are registered", position: { my: "left top", at: "left top", offset: 15 } }, "success", 3000); } } }; ko.applyBindings(viewModel);
At any place
Call the DevExpress.validationEngine.validateGroup(group) method passing the required validation group as a parameter.JavaScriptDevExpress.validationEngine.validateGroup($("#sampleGroup").dxValidationGroup("instance"));
Whenever you force validation for a group when an editor value has not changed since the last validation, validation will not be performed repeatedly for this editor. For example, if an editor value has not changed since it was validated on a value change, it will not be validated on a button click when a validation group is validated. However, some rules have the reevaluate option, which allows you to change this behavior and check the required rules no matter the target value changed.
Display Validation Errors
The DevExtreme Validation comes with an integrated UI part. First, each editor is highlighted to indicate that it has an invalid value when a validation rule is not satisfied during validation.
Second, a summary on all the validation rules that are not satisfied in a validation group is displayed by the ValidationSummary widget. This widget has an item collection that is populated each time a validation error occurs in the validation group with which the widget is associated. The summary items are displayed using the default item template that is based on the messages specified for the failed validation rules. You can use a custom item template, as in any collection widget in DevExtreme.
To associate the ValidationSummary widget with a validation group, add the ValidationSummary widget inside of the element representing the ValidationGroup widget.
AngularJS
<div id="sampleGroup" dx-validation-group="{}"> <div dx-text-box="{ value: login, placeholder: 'Login' }", dx-validator="{ validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div dx-text-box="{ value: password, mode: 'password' }", dx-validator="{ validationRules: [{ type: 'required', message: 'Password is required' }] }"> </div> <div dx-validation-summary="{ }"></div> <div dx-button="{ text: 'Validate and submit', onClick: validateAndSubmit }"></div> </div>
Knockout
<div id="sampleGroup" data-bind="dxValidationGroup : {}" > <div data-bind="dxTextBox: { value: login, placeholder: 'Login' }, dxValidator: { validationRules: [{ type: 'required' }, { type: 'pattern', pattern: '^[a-zA-Z]+$', message: 'Do not use digits.' }] }"> </div> <div data-bind="dxTextBox: { value: password, mode: 'password' }, dxValidator: { validationRules: [{ type: 'required', message: 'Password is required' }] }"> </div> <div data-bind="dxValidationSummary: { }"></div> <div data-bind="dxButton: { text: 'Validate and submit', onClick: validateAndSubmit }"></div> </div>
Remote Scenarios
When validation requires a request to a server, some time may pass until a response from the server comes. Thus, validation should be performed with a delay. Therefore, we recommend that you use the 'custom' rule for such remote scenarios. The 'custom' rule allows you to define a function where you can validate the editor value yourself.
Below, you can see an example where two fields must be validated. One of these fields demands a request to a server. Thus, this field will be validated within the validationCallback function of a 'custom' rule.
To emulate a request to a server, a validation will be performed after the specified timeout. While waiting for the specified timeout, the editor value can be considered either valid or invalid (this depends on the value returned by the validationCallback function). Choose the scenario that is most appropriate for your application.
AngularJS
<div ng-controller="demoController"> <div dx-text-box="{ value: login, placeholder: 'Login' }", dx-validator="{ validationRules: [ { type: 'required', message: 'Login is required' }, { type: 'custom', validationCallback: checkLoginAvailable, message: 'This login is not available, please choose another one.' } ] }"> </div> <div dx-text-box="{ value: password, mode: 'password', placeholder: 'Password' }" dx-validator="{ validationRules: [ { type: 'required', message: 'Password can not be empty.' } ] }"> </div> <div dx-validation-summary="{ }"></div> <div dx-button="{ text: 'Register', onClick: register }"></div> </div>
angular.module('DemoApp', ['dx']) .controller('demoController', function ($scope) { $scope.login = ""; $scope.password = ""; $scope.register = function (params) { var result = params.validationGroup.validate(); if (result.isValid) { DevExpress.ui.notify({ message: "You are logged in as " + $scope.login, position: { my: "left top", at: "left top" } }, "success", 3000); } }; $scope.checkLoginAvailable = function (params) { // Emulates response from a server setTimeout(function () { // Checks if username is already used if (params.value === "Alex") { params.rule.isValid = false; params.validator.validate(); } else { params.rule.isValid = true; params.validator.validate(); } }, 1000); // While waiting for server response // the editor value is considered invalid return false; } });
Knockout
<div data-bind="dxTextBox: { value: login, placeholder: 'Login' }, dxValidator: { validationRules: [ { type: 'required', message: 'Login is required' }, { type: 'custom', validationCallback: checkLoginAvailable, message: 'This login is not available, please choose another one.' } ] }"> </div> <img style="position: absolute; right:10px; top: 9px;" data-bind="visible: loginRemoteCheckTimeout" src="../Images/preloader.gif" /> <div data-bind="dxTextBox: { value: password, mode: 'password', placeholder: 'Password' }, dxValidator: { validationRules: [ { type: 'required', message: 'Password can not be empty.' } ] }"> </div> <div data-bind="dxValidationSummary: { }"></div> <div data-bind="dxButton: { text: 'Register', onClick: register }"></div>
var viewModel = { login: ko.observable(""), password: ko.observable(""), register: function (params) { var result = params.validationGroup.validate(); if (result.isValid) { DevExpress.ui.notify({ message: "You are logged in as " + this.login(), position: { my: "left top", at: "left top" } }, "success", 3000); } }, checkLoginAvailable: function (params) { // Emulates response from a server setTimeout(function () { // Checks if username is already used if (params.value === "Alex") { params.rule.isValid = false; params.validator.validate(); } else { params.rule.isValid = true; params.validator.validate(); } }, 1000); // While waiting for server response // the editor value is considered invalid return false; } } ko.applyBindings(viewModel);
Validate View Model
You may want to validate the ViewModel object rather than the UI editors. This will allow you to separate validation logic from the UI, reuse it between multiple views and easily unit-test the implementation.
Associate ViewModel Members with a Validator
To validate a ViewModel's observable member, the latter should be associated with the dxValidator object.
The dxValidator object validates the ViewModel's members against a predefined set of rules. To define the rules to be checked, assign an array of rules to the validator's validationRules field.
dxValidator = { validationRules: [{ type: "required", message: "login is required" //optional }, { type: "pattern", pattern: "^[a-zA-Z]+$", message: "Do not use digits." //optional }] };
You can see a full structure of each predefined rule type in the Validation Rules Reference section.
Generally, the structure of the dxValidator object repeats the structure of the Validator widget's configuration object.
To associate a ViewModel's observable member with a dxValidatior object, extend this member with an object that has the dxValidator field set to the dxValidator object.
$(function() { var viewModel = { login: ko.observable("").extend({ dxValidator: { validationRules: [{ type: 'required', message: 'Login is required' }] } }), password: ko.observable("").extend({ dxValidator: { name: "Password", validationRules: [{ type: 'required' }] } }) }; ko.applyBindings(viewModel); });
Validate ViewModel Members
A ViewModel for its validated members serves as a validation group for validated fields. However, while validation groups are registered for validation automatically, ViewModel objects should be registered manually. For this purpose, use the DevExpress.validationEngine.registerModelForValidation(model) method.
See Also
var viewModel = { //... }; DevExpress.validationEngine.registerModelForValidation(viewModel); ko.applyBindings(viewModel);
To validate a registered ViewModel object, call the DevExpress.validationEngine.validateModel(model) method.
var viewModel = { //... validateAndLogin: function(params) { var result = DevExpress.validationEngine.validateModel(this); if(result.isValid) { DevExpress.ui.notify({ message: "You are logged in as " + this.login(), position: { my: "left top", at: "left top" } }, "success", 3000); } } }; }; DevExpress.validationEngine.registerModelForValidation(viewModel); ko.applyBindings(viewModel);
Display Validation Errors
Since ViewModel fields are related to some editors in a UI, validation errors found in the ViewModel fields should be shown for the related editors. Thus, set the isValid and validationError configuration options of the target editors to the corresponding fields of the ViewModel's validators.
<div data-bind="dxTextBox: { value: login, placeholder: 'Login', isValid: login.dxValidator.isValid, validationError: login.dxValidator.validationError }"> </div> <div data-bind="dxTextBox: { value: password, mode: 'password', placeholder: 'Password', isValid: password.dxValidator.isValid, validationError: password.dxValidator.validationError }"> </div>
In addition, you can use the ValidationSummary widget to display a summary of validation errors. Add this widget to the view that is bound to the validated ViewModel.
<div data-bind="dxValidationSummary: { }"></div>
Validate Custom Editors
There can be scenarios when you cannot associate the Validator widget with an editor, because this editor is not a DevExtreme editor or a validated value is a concatenation of several DevExtreme editor values. In this instance, create the Validator widget and specify an adapter for it using the adapter configuration option. An adapter is required to "tell" the Validator what and when to validate, and how to apply the validation result.
By default, when you do not specify the adapter option, a default adapter is used. The default adapter does the following.
- Specifies the current value of the associated editor as the getValue function's return value.
- Fires the validationRequestsCallbacks passed to the validator when the event specified by the editor's valueChangeEvent is raised.
- Sets the editor's isValid and validationError options to the values provided by the validator's validation result.
- Sets the editor's value to undefined.
- Focuses the validated editor.
AngularJS
<div ng-controller="demoController"> <div dx-text-box="{ bindingOptions: { value: 'contactEmail'}, mode: 'email', placeholder: 'Email' }"></div> <div dx-text-box="{ bindingOptions: { value: 'contactPhone'}, placeholder: 'Phone number' }"></div> <div dx-validator="{ validationRules: [{ type: 'required', message: 'Specify your email or phone number!'}], adapter: phoneAndEmailAdapter }"></div> <div dx-validation-summary="{}"></div> <div dx-button="{text: 'Contact me', onClick: contactMeClick}"></div> </div>
angular.module('DemoApp', ['dx']); .controller("demoController", function ($scope) { var $callbacks = $.Callbacks(); $scope.contactPhone = ''; $scope.contactEmail = ''; $scope.validationResultStyle = { 'border-width': '0px' }; $scope.contactMeClick = function (p) { var result = p.validationGroup.validate(); if (result.isValid) { DevExpress.ui.notify({ message: "We will contact you when we have information for you.", position: { my: "right top", at: "right top", offset: "-10 10" } }, "success", 3000); } }; $scope.phoneAndEmailAdapter = { getValue: function () { return $scope.contactPhone + $scope.contactEmail; }, applyValidationResults: function (result) { $scope.validationResultStyle['border-width'] = result.isValid ? "0px" : "1px"; }, validationRequestsCallbacks: $callbacks } });
Knockout
<div data-bind="dxTextBox: { value: contactEmail, mode: 'email', placeholder: 'Email' }"></div> <div data-bind="dxTextBox: { value: contactPhone, placeholder: 'Phone number' }"></div> <div data-bind="dxValidator: { adapter: { getValue: getContactPhoneAndEmail, validationRequestsCallbacks: contactInfoValidationRequests, applyValidationResults: applyContactValidation }, validationRules: [{ type: 'required', message: 'Specify your email or phone number!' }] }"> </div> <div data-bind="if: showRequestContactInfo " style="color: #000971">Please, let us contact you!</div> <div data-bind="dxValidationSummary: { }"></div> <div data-bind="dxButton: { text: 'Contact me', onClick: contactMeClick }"></div>
var viewModel = { contactEmail: ko.observable(""), contactPhone: ko.observable(""), showRequestContactInfo: ko.observable(false), contactMeClick: function (params) { var result = params.validationGroup.validate(); if (result.isValid) { DevExpress.ui.notify({ message: "We will contact you when we have information for you.", position: { my: "right top", at: "right top", offset: "-10 10" } }, "success", 3000); } }, getContactPhoneAndEmail: function () { return viewModel.contactPhoneAndEmail(); } }; viewModel.contactPhoneAndEmail = ko.computed(function () { return this.contactEmail() + this.contactPhone(); }, viewModel); viewModel.applyContactValidation = function (result) { viewModel.showRequestContactInfo(!result.isValid); }; viewModel.contactInfoValidationRequests = $.Callbacks(); viewModel.contactInfoChangedHandler = function () { viewModel.contactInfoValidationRequests.fire(); }; viewModel.contactPhoneAndEmail.subscribe(viewModel.contactInfoChangedHandler) ko.applyBindings(viewModel);
Use Custom Validation Engine
If you need to use a custom validation engine for DevExtreme editors, but want to display the validation result using the DevExtreme UI implementation, specify the following options for the validated DevExtreme editors.
isValid
Specify whether the current editor value is valid against the validation rules that you checked using your custom validation engine.validationError
Specify an object that has the message field. This field should be a string that defines the error that occurred during validation.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
We appreciate your feedback.