I have a domain property and I want to validate two things;
In order to check these things I created to async validation rules using https://github.com/Knockout-Contrib/Knockout-Validation and applied both of them on my property.
What happens is that each time the response from one of the rules comes earlier and it sets isValidating
property to false
and I want this property to be true
until the response from my second rule came.
Custom rules:
export function enableCustomValidators() {
(ko.validation.rules as any)["urlValidationServicePath"] = {
async: true,
validator: function (url: string, baseUrl: string, callback: any) {
getRequest(url, baseUrl, callback, "true");
},
message: 'You must enter a reachable domain.',
},
(ko.validation.rules as any)["customerValidationServicePath"] = {
async: true,
validator: function (url: string, baseUrl: string, callback: any) {
getRequest(url, baseUrl, callback, "false");
},
message: "This url already exists in our system. Please contact us at hello@ve.com",
}
ko.validation.registerExtenders();
}
function getRequest(url: string, baseUrl: string, callback: any, method: string) {
var restClient = new RestClient();
restClient.downloadString(baseUrl.concat(url), (responseText) => {
method === "true" ? callback(responseText === "true" ? true : false) :
callback(responseText === "true" ? false : true);
});
}
Using of the rules:
export class CompanySetupVM extends BasePageVM {
public websiteUrl: KnockoutObservable<string> = ko.observable(undefined);
public isValidating: KnockoutObservable<boolean> = ko.observable(false);
public constructor() {
this.websiteUrl.extend({
required: {
params: true,
message: CompanySetupVM.ErrorMessageNullWebsiteUrl
},
urlValidationServicePath: CompanySetupVM.DomainValidationPath,
customerValidationServicePath: CompanySetupVM.CustomerValidationPath
});
this.isValidating = ko.computed(() => this.websiteUrl.isValidating(), this);
}
}
In cshtml:
data-bind="text: currentPage().nextButtonText, css: {'button-overlay': currentPage().isValidating(), 'button': !currentPage().isValidating()}, click: nextAction"
I've looked at the source code of knockout validation (here) and it's pretty clear that two independent async validators are not supported.
The isValidating
property is set to true
as soon as an async rule is begins to run and set to false
again as soon as that rule finishes. Therefore, multiple async rules clash.
There is only one solution. Remove the second async validator.
You can collapse the two checks into one either on the client side or on the server side.
To do it on the client side, you would need to write a validator that runs two Ajax requests and invokes the validation callback
only after both of them have returned.
To do it on the server side, you would have to run the "is reachable" and "is in DB" checks in succession before giving an overall response to the client.
Personally I would prefer changing the server side, because
Besides plain true
or false
, the validation plugin understands responses in this format:
{isValid: false, message: "something is wrong"}
So make your server send a JSON response with the appropriate validation result and error message and your REST client download JSON instead of text.
Then all you need to do is pass the server's response directly to the validation callback.
ko.validation.rules.urlValidationServicePath = {
async: true,
validator: function (url, baseUrl, callback) {
restClient.downloadJSON(baseUrl.concat(url), callback);
},
message: 'The URL you entered is not valid.'
};
Here the message
is only a default. The server's message
always takes precedence over the setting in the validation rule.