Knockout-Validation 多个自定义异步规则
Knockout-Validation multiple custom async rules
我有一个域 属性,我想验证两件事;
- URL 存在(可达)
- URL 存在于我的本地数据库中。
为了检查这些东西,我使用 https://github.com/Knockout-Contrib/Knockout-Validation 创建了异步验证规则,并将它们都应用于我的 属性。
发生的情况是,每次来自其中一条规则的响应更早时,它会将 isValidating
属性 设置为 false
,我希望这个 属性 是true
直到我的第二个规则的响应出现。
自定义规则:
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);
});
}
使用规则:
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);
}
}
在 cshtml 中:
data-bind="text: currentPage().nextButtonText, css: {'button-overlay': currentPage().isValidating(), 'button': !currentPage().isValidating()}, click: nextAction"
我查看了敲除验证的源代码 (here),很明显不支持两个独立的异步验证器。
一旦异步规则开始 运行,isValidating
属性 就会设置为 true
并再次设置为 false
该规则结束。因此,多个异步规则冲突。
只有一种解决方案。删除第二个异步验证器。
您可以在客户端或服务器端将两个检查合并为一个。
要在客户端执行此操作,您需要编写一个验证器 运行 两个 Ajax 请求并仅在它们都返回后调用验证 callback
.
要在服务器端执行此操作,您必须依次 运行 "is reachable" 和 "is in DB" 检查,然后再对客户端做出全面响应。
我个人更愿意更改服务器端,因为
- 它使客户端代码保持整洁和易于管理
- 每次检查节省一次 HTTP 往返
- 语义上,URL 检查是 一件事情 由于不止一个原因
而失败
- 很容易让服务器发送自定义验证结果和-message
除了普通的 true
或 false
,验证插件理解这种格式的响应:
{isValid: false, message: "something is wrong"}
因此,让您的服务器发送一个 JSON 响应,其中包含适当的验证结果和错误消息,并且您的 REST 客户端下载 JSON 而不是文本。
然后您需要做的就是将服务器的响应直接传递给验证回调。
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.'
};
这里的message
只是默认值。服务器的 message
始终优先于验证规则中的设置。
是的,正如 Tomalak 指出的那样,不可能有多个异步验证器。但我在客户端解决了它,并且解决方案非常易于管理且灵活,恕我直言。
这里的技巧是将不同的异步验证器实现为常规的淘汰扩展器,并使用单个异步规则来调用它们。这是异步规则:
interface HasAsyncValidator {
asyncValidators: Validator[];
}
interface Validator {
name: string,
validator: (params: any) => boolean | PromiseLike<any>,
params: any
}
interface KnockoutObservable<T> extends HasAsyncValidator {}
ko.validation.rules["validateAsync"] = {
validator: async (value: any, paramsAccessor: () => HasAsyncValidator, callback: (result: boolean | ValidationResult) => void) => {
const params = paramsAccessor();
if (!params || !params.asyncValidators) {
callback(true);
return;
}
try {
const results = await Promise.all(params.asyncValidators.map(v => v.validator(v.params)));
const invalidResult = results.find(r => r.isValid === false);
callback(!!invalidResult ? invalidResult : true);
} catch (error) {
callback(false);
throw error;
}
},
message: 'default message',
async: true
}
如您所见,我们使用 asyncValidators 属性 扩展了 observable,它保留了所有已注册的验证器。留给规则的只是调用验证器(如果有的话),然后将结果传递给敲除验证回调。
这是验证器作为常规扩展器的示例:
ko.extenders["validationRule"] = (target: any, option: any) => {
const validatorObj: Validator = {
name: "validationRule",
params: option,
validator: async (): Promise<boolean | ValidationResult> => {
const unwrappedValue = ko.unwrap(target);
const result = await callServer();
return {
isValid: result.isValid,
message: result.message
};
}
}
addOrUpdateAsyncValidator(target, validatorObj);
};
function addOrUpdateAsyncValidator(target: HasAsyncValidator, validatorObj: Validator) {
target.asyncValidators = target.asyncValidators || [];
const existingRule = target.asyncValidators.find(v => v.name == validatorObj.name);
!!existingRule
? existingRule!.params = validatorObj.params
: target.asyncValidators.push(validatorObj);
}
请注意,每个验证器都必须将自己注册到 asyncValidators 属性 on observable。
这个解决方案的用法非常简单:
let value = ko.observable();
value.extend({ validationRule: true, validateAsync: () => value });
请注意,我们应该将值访问器而不是值本身传递给 validateAsync。这是必需的,这样异步规则就不会错过稍后可以添加的验证器。
我有一个域 属性,我想验证两件事;
- URL 存在(可达)
- URL 存在于我的本地数据库中。
为了检查这些东西,我使用 https://github.com/Knockout-Contrib/Knockout-Validation 创建了异步验证规则,并将它们都应用于我的 属性。
发生的情况是,每次来自其中一条规则的响应更早时,它会将 isValidating
属性 设置为 false
,我希望这个 属性 是true
直到我的第二个规则的响应出现。
自定义规则:
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); }); }
使用规则:
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); } }
在 cshtml 中:
data-bind="text: currentPage().nextButtonText, css: {'button-overlay': currentPage().isValidating(), 'button': !currentPage().isValidating()}, click: nextAction"
我查看了敲除验证的源代码 (here),很明显不支持两个独立的异步验证器。
一旦异步规则开始 运行,isValidating
属性 就会设置为 true
并再次设置为 false
该规则结束。因此,多个异步规则冲突。
只有一种解决方案。删除第二个异步验证器。
您可以在客户端或服务器端将两个检查合并为一个。
要在客户端执行此操作,您需要编写一个验证器 运行 两个 Ajax 请求并仅在它们都返回后调用验证 callback
.
要在服务器端执行此操作,您必须依次 运行 "is reachable" 和 "is in DB" 检查,然后再对客户端做出全面响应。
我个人更愿意更改服务器端,因为
- 它使客户端代码保持整洁和易于管理
- 每次检查节省一次 HTTP 往返
- 语义上,URL 检查是 一件事情 由于不止一个原因 而失败
- 很容易让服务器发送自定义验证结果和-message
除了普通的 true
或 false
,验证插件理解这种格式的响应:
{isValid: false, message: "something is wrong"}
因此,让您的服务器发送一个 JSON 响应,其中包含适当的验证结果和错误消息,并且您的 REST 客户端下载 JSON 而不是文本。
然后您需要做的就是将服务器的响应直接传递给验证回调。
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.'
};
这里的message
只是默认值。服务器的 message
始终优先于验证规则中的设置。
是的,正如 Tomalak 指出的那样,不可能有多个异步验证器。但我在客户端解决了它,并且解决方案非常易于管理且灵活,恕我直言。 这里的技巧是将不同的异步验证器实现为常规的淘汰扩展器,并使用单个异步规则来调用它们。这是异步规则:
interface HasAsyncValidator {
asyncValidators: Validator[];
}
interface Validator {
name: string,
validator: (params: any) => boolean | PromiseLike<any>,
params: any
}
interface KnockoutObservable<T> extends HasAsyncValidator {}
ko.validation.rules["validateAsync"] = {
validator: async (value: any, paramsAccessor: () => HasAsyncValidator, callback: (result: boolean | ValidationResult) => void) => {
const params = paramsAccessor();
if (!params || !params.asyncValidators) {
callback(true);
return;
}
try {
const results = await Promise.all(params.asyncValidators.map(v => v.validator(v.params)));
const invalidResult = results.find(r => r.isValid === false);
callback(!!invalidResult ? invalidResult : true);
} catch (error) {
callback(false);
throw error;
}
},
message: 'default message',
async: true
}
如您所见,我们使用 asyncValidators 属性 扩展了 observable,它保留了所有已注册的验证器。留给规则的只是调用验证器(如果有的话),然后将结果传递给敲除验证回调。 这是验证器作为常规扩展器的示例:
ko.extenders["validationRule"] = (target: any, option: any) => {
const validatorObj: Validator = {
name: "validationRule",
params: option,
validator: async (): Promise<boolean | ValidationResult> => {
const unwrappedValue = ko.unwrap(target);
const result = await callServer();
return {
isValid: result.isValid,
message: result.message
};
}
}
addOrUpdateAsyncValidator(target, validatorObj);
};
function addOrUpdateAsyncValidator(target: HasAsyncValidator, validatorObj: Validator) {
target.asyncValidators = target.asyncValidators || [];
const existingRule = target.asyncValidators.find(v => v.name == validatorObj.name);
!!existingRule
? existingRule!.params = validatorObj.params
: target.asyncValidators.push(validatorObj);
}
请注意,每个验证器都必须将自己注册到 asyncValidators 属性 on observable。
这个解决方案的用法非常简单:
let value = ko.observable();
value.extend({ validationRule: true, validateAsync: () => value });
请注意,我们应该将值访问器而不是值本身传递给 validateAsync。这是必需的,这样异步规则就不会错过稍后可以添加的验证器。