Knockout-Validation多个自定义异步规则

时间:2017-06-28 16:29:44

标签: javascript validation knockout.js knockout-validation

我有域属性,我想验证两件事;

  1. 网址存在(可访问)
  2. 我的本地数据库中存在URL。
  3. 为了检查我使用https://github.com/Knockout-Contrib/Knockout-Validation创建的异步验证规则,并在我的属性上应用它们。

    每次来自其中一个规则的响应更早出现并将isValidating属性设置为false时,我希望此属性为true,直到我的响应为止第二条规则来了。

    1. 自定义规则:

       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);
      });
      }
      
    2. 使用规则:

      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);   
          }
      }
      
    3. 在cshtml中:

       data-bind="text: currentPage().nextButtonText, css: {'button-overlay': currentPage().isValidating(), 'button': !currentPage().isValidating()}, click: nextAction"
      

2 个答案:

答案 0 :(得分:1)

我查看了敲除验证的源代码(here),很明显不支持两个独立的异步验证器。

一旦异步规则开始运行,isValidating属性就会设置为true,并且一旦该规则完成,就会再次设置为false。因此,多个异步规则发生冲突。

只有一种解决方案。删除第二个异步验证器。

您可以将两个支票折叠为客户端或服务器端的支票。

要在客户端执行此操作,您需要编写一个运行两个Ajax请求的验证程序,并且只有在它们都返回后才会调用验证callback

要在服务器端执行此操作,您必须在向客户端发出总体响应之前连续运行“is reachable”和“is in DB”检查。

我个人更喜欢更改服务器端,因为

  1. 它使客户端代码保持整洁和易于管理
  2. 每次检查会保存一次HTTP往返
  3. 从语义上讲,URL检查一件因多种原因而失败
  4. 很容易让服务器发送自定义验证结果和-message
  5. 除了普通truefalse之外,验证插件还能理解以下格式的响应:

    {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始终优先于验证规则中的设置。

答案 1 :(得分:0)

是的,正如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属性。

此解决方案的用法非常简单:

let value = ko.observable();
value.extend({ validationRule: true, validateAsync: () => value });

请注意,我们应该将值访问器传递给validateAsync而不是值本身。这是必需的,因此异步规则不会错过可以在以后添加的验证器。