简体   繁体   English

提交表单之前使用ajax验证字段

[英]Validate a field with ajax before submitting form

I've just started creating a form validator, and came across a puzzling problem. 我刚刚开始创建表单验证器,遇到一个令人费解的问题。 If I'm validating a simple field (say, password), I can do that on the client side alone (not that I mean I won't validate it on the server). 如果我要验证一个简单字段(例如密码),则可以仅在客户端执行此操作(不是说我不会在服务器上对其进行验证)。 What I mean is I can do the validation on the client machine without any external checks. 我的意思是,我可以在客户端计算机上进行验证,而无需任何外部检查。

If I'm validating a more complex field (username), this requires external check. 如果我要验证更复杂的字段(用户名),则需要进行外部检查。 For example, in a registration form, I want to validate the username (make sure it doesn't exist). 例如,在注册表格中,我要验证用户名(确保用户名不存在)。 To do that I'd have to make an ajax call. 为此,我必须进行ajax调用。 This complicates things a little. 这使事情变得有些复杂。 Examine the code below. 检查下面的代码。

FormValidator.prototype.validate = function validateForm() {
    this.errors = {};
    for (var fieldName in this.fields) {
        var field = this.fields[fieldName];

        if (field.hasOwnProperty("required") && field.required && field.elem.value.length == 0) {
            this.addError(fieldName, "This field is required.");
            break;
        }

        if (field.hasOwnProperty("minLength") && field.elem.value.length < field.minLength) {
            this.addError(fieldName, "Input length should not be less than  " + field.minLength + " characters.");
            break;
        }

        if (field.hasOwnProperty("maxLength") && field.elem.value.length > field.maxLength) {
            this.addError(fieldName, "Input length should not be greater than" + field.maxLength + " characters.");
            break;
        }

        if (field.hasOwnProperty("ajax")) {
            // FormValidator can't possibly know what the call will return, so we can't add the error here
            // it has to be done manually
            field.ajax(this, field, fieldName);
        }
    }
    if (this.errors.length != 0) {
        // warn the user
        console.log(this.errors);
    }
};

var fv = new FormValidator(document.forms[0]);

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax      : function (fv, field, fieldName) {
        ajax("http://localhost/surec/scripts/user_check.php?field=login_name&value=" + field.elem.value, {
            success : function () {
                var response = JSON.parse(this.response);
                // manually adding the error
                if (!response.error && response.exists) {
                    fv.addError(fieldName, "This username is taken.");
                }
            },
            // async: false,
        });
    },
});

// called on form submit
// fv.validate();

The moment fv.validate() is called (assuming the user entered a taken username), validate() won't do anything to warn the user, because the ajax call is asynchronous. 调用fv.validate()的那一刻(假设​​用户输入了采用的用户名), validate()不会采取任何措施来警告用户,因为ajax调用是异步的。 When the error check is done if (this.errors.length != 0) { , errors will be empty. 当错误检查完成时, if (this.errors.length != 0) { ,错误将为空。 It'll be populated when the ajax call is done, and then it's too late. ajax调用完成后将填充它,然后为时已晚。

To fix this, I can make the ajax call synchronous. 为了解决这个问题,我可以使ajax调用同步。 This solves the problem, but I'm not sure about using a synchronous call. 这解决了问题,但是我不确定使用同步调用。 Is this approach valid or is there an alternative approach that I can take? 这种方法有效吗?或者我可以采用其他方法吗?


Update : I've started looking into Promise, and I think I'm getting the hang of it. 更新 :我已经开始研究Promise了,我想我已经掌握了它。 I've made it work to some point, so I still need some help. 我已经使其工作到一定程度,所以我仍然需要一些帮助。

Graphical depiction of what I'm trying to do would be something like this: 我要尝试做的图形描述如下:

在此处输入图片说明

I've created an async loop function that I'll be using to loop the fields: 我创建了一个异步循环函数,将用于循环这些字段:

async function asyncForEach(array, callback) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
    }
}

and the current state of the validate function: 以及validate函数的当前状态:

FormValidator.prototype.validate = async function validateForm() {
    this.errors = {};
    var self = this;

    await asyncForEach(self.fields.keys(), async function (fieldName) {
        var field = self.fields[fieldName];

        if (field.hasOwnProperty("required") && field.required && field.elem.value.length == 0) {
            self.addError(fieldName, "This field is required.");
            // break;
        }

        if (field.hasOwnProperty("minLength") && field.elem.value.length < field.minLength) {
            self.addError(fieldName, "Input length should not be less than " + field.minLength + " characters.");
            // break;
        }

        if (field.hasOwnProperty("maxLength") && field.elem.value.length > field.maxLength) {
            self.addError(fieldName, "Input length should not be greater than " + field.maxLength + " characters.");
            // break;
        }

        if (field.hasOwnProperty("ajax")) {
            // FormValidator can't possibly know what the call will return, so we can't add the error here
            // it has to be done manually
            await field.ajax(self, field, fieldName);
        }

    });

    if (self.errors.length() != 0) {
        // warn the user
        console.log("errors: ", self.errors);
    }
};

This appears to work (in a test case). 这似乎可行(在测试用例中)。 See below. 见下文。 I'm not using an ajax function, but faked it (using a 1 second delay). 我没有使用ajax函数,而是伪造了它(使用了1秒的延迟)。 console.log("errors: ", self.errors); in fv.validate() runs after the external check (fake ajax) is done (1s delay). fv.validate() ,在完成外部检查(假ajax)(延迟1 fv.validate()后运行。

var fv = new FormValidator(document.forms[0]);

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax : async function (fv, field, fieldName) {
        // assume delay is ajax
        await delay(1000);
        // and on success (and login_name collision) we add an error
        fv.addError(fieldName, "This username is taken.");
    },
});

var delay = (ms) => new Promise(r => setTimeout(r, ms));

fv.validate();

Now, I need to rewrite the external check method ( ajax ) and make it work with this code. 现在,我需要重写外部检查方法( ajax )并使其与此代码一起使用。 I've tried a few combinations (probably nonsense, using trial-error), but couldn't make it work. 我尝试了几种组合(可能是废话,使用试验错误),但无法使其正常工作。 How should I proceed? 我应该如何进行?

FormValidator.js (GitHub repo) FormValidator.js (GitHub存储库)

Here one possible implementation with promises. 这里有一个可能的实现承诺。 It's a simplified example that just focuses on managing an array of errors where one is the result of an ajax call 这是一个简化的示例,仅专注于管理一系列错误,其中一个错误是ajax调用的结果

 // errors is an array of promises const errors = ["Too long"] const remoteErrorCheck = $.ajax({type:"GET", url:'https://api.stackexchange.com/2.2/info?site=stackoverflow'}).then(function(data){ // here you have access to the result of ajax request, inside data return Promise.resolve("error or ok") // you want to return a promise to put in the array of promises }) // now put the promise from the ajax request into the array of promises errors.push(remoteErrorCheck) // when all the promises are resolved, then do something with the results Promise.all(errors).then((a)=>{ console.log(a) }) 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 

Note that Promise.all waits for ajax to complete before executing. 请注意, Promise.all在执行之前等待ajax完成。 Strings are treated by Promise.all as already resolved promises. Promise.all将字符串视为已解决的Promise.all

Since remoteErrorCheck already has a .then also that one is executed and since it returns a promise, the Promise of Promise is squashed into just one promise, and that's why you can access the error or ok string in the array of errors 因为remoteErrorCheck已经有一个remoteErrorCheck .then又执行了一个.then并且由于它返回了一个Promise of Promise ,所以Promise of Promise被压缩为一个Promise of Promise ,这就是为什么您可以访问error or ok错误数组中的error or ok字符串的原因

This needs to be adapted for your case (objects instead of strings, validation function inside the then should be passed from outside, ...) but it should be a good starting point 这需要针对您的情况进行调整(对象而不是字符串, then从内部传递验证函数, then从外部传递...),但这应该是一个很好的起点

I've managed to solve the problem. 我设法解决了这个问题。 Instead of using a custom ajax function/lib, I've decided to use fetch . 我决定不使用自定义ajax函数/ lib,而是使用fetch

Using it alone (without await) didn't work, so I had to add it. 单独使用(不等待)无法正常工作,因此我不得不添加它。 And now it works. 现在就可以了。

fv.addField("login_name", {
    type      : "text",
    minLength : 4,
    maxLength : 32,
    required  : true,
    ajax : async function (fv, field, fieldName) {
        await fetch("http://localhost/surec/scripts/user_check.php?field=login_name&value=" + field.elem.value)
        .then(function (response) {
            return response.json();
        }).then(function (response) {
            if (!response.error && response.exists) {
                fv.addError(fieldName, "This username is taken.");
            }
        });
    },
});

Although this works at the moment, I'm not sure if this will cause problems in the future. 尽管目前可以使用,但我不确定将来是否会引起问题。 If it does, I'll update this QA. 如果是这样,我将更新此质量检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM