How do I invoke custom constraint validation as pa

2019-07-17 07:12发布

  • The following is based on testing with Chrome 31
  • TL;DR: How come the code in this fiddle works on attempted submit, but doesn't in this one on manual invocation? Is there a better way of doing it?

In answering a question asking for a way of validating an <input type="file"> against its accept attribute (the native behaviour will work with the resulting file dialog to present a filter by default, but it isn't enforced), I wanted to see what options were available to integrate this custom validation into HTML5's scripted Constraint validation.

My initial skim-reading of the API led me to overwrite the native checkValidity method, in the belief that this would be called by default whenever the browser saw fit to validate the code (jsFiddle):

void function enhanceFileInputTypeValidityCheck(){
    var inputPrototype      = document.createElement( 'input' ).constructor.prototype;
    var nativeCheckValidity = inputPrototype.checkValidity;

    // This is my custom validation function
    function validateFileInputType( input ){
        var MIMEtype = new RegExp( input.accept.replace( '*', '.\*' ) );

        return Array.prototype.every.call( input.files, function passesAcceptedFormat( file ){
            return MIMEtype.test( file.type );
        } );
    }

    // My attempt at getting the behaviour to happen 
    inputPrototype.checkValidity = function enhancedCheckValidity(){
        if( this.type === 'file' &&  this.accept && this.files && this.files.length ){
            if( !validateFileInputType( this ) ){
                this.setCustomValidity( 'Please only submit files of type ' + this.accept );

                return false;
            }
        }

        return nativeCheckValidity.apply( this );
    }
}();

This effectively enhances checkValidity to return false when my conditions aren't met, and falls through to native behaviour if they are, but setCustomValidity isn't working as expected: it doesn't throw any exceptions, but doesn't display any error message either. Crucially, my return false in checkValidity doesn't prevent the form from submitting.

After some fiddling I ended up with this event handler, which along with the code posted above finally achieves the desired aims — namely, preventing submission according to the custom criteria and feeding back to the user (jsFiddle):

$( 'input' ).on( 'change input', function( event ){
    this.checkValidity();
} );

I'm mystified as to how this is happening: according to logging the event type, change will execute checkValidity, but won't display the message; meanwhile attepting to submit the form does implement my setCustomValidity, but won't log the event type: however the implication is that this is an input event, since that's the only other event the handler is listening for — but (and this is where it gets really weird), unbinding from change (which wasn't working anyway) leaving only input as a trigger for my imperative callback doesn't prevent submission at all anymore.

I'm still at a loss for the 'correct' way of binding my custom validation into the native validation flow, since binding to change and event to trigger expected behaviour when neither of those events is actually taking place just seems far too hacky to be intended usage.

Bonus points for explaining any of the following:

  • Why don't I get any feedback from my custom validation console.log when invoking checkValidity on an empty input?
  • Why don't I get any feedback from the console.log in the event handler bound to input && change?
  • How come executing code as a callback to the change event doesn't trigger setCustomValidity?
  • What event — if not input — is actually triggering my setCustomValidity?
  • Could I produce the same desired end result without overriding checkValidity?

3条回答
唯我独甜
2楼-- · 2019-07-17 07:40

I'll try to summarize my experience:

  1. The custom validation checks should be performed both on user data input and on form submission button click.
  2. You need using both the setCustomValidity and the checkValidity methods from the HTML5 form validation API.
  3. For validation on user data input, you add to the input field element an input event listener that invokes setCustomValidity on the input field. In the argument of setCustomValidity you invoke a suitable check function that returns an empty string if the user input is valid, and a constraint violation message otherwise.
  4. For validation on form submission button click, you invoke checkValidity on the form element and continue the submission only if this returns true.

See also my answer to a similar SO question or try my example solution.

查看更多
淡お忘
3楼-- · 2019-07-17 07:52

As far as I am aware, the checkValidity method calls the browser's internal validation, rather than being what the browser's internal validation calls. Thus if you override it the browser won't notice or care.

In my experience, all that is necessary to implement custom validity is to bind to the change event and then call setCustomValidity with the right value. For example,

myinput.addEventListener('change', function(e) {
    if ( ! myCustomValidityCheck() )
        this.setCustomValidity( 'Uh oh, that didn\'t work!' );
    else
        this.setCustomValidity( '' );
})

Whimsical JSFiddle example

查看更多
Emotional °昔
4楼-- · 2019-07-17 07:55

Here is a solution for your problems http://jsfiddle.net/trixta/E7VtD/

  1. Use change isntead of input event. input event is great, but does not apply to input[type=file].
  2. You should not override checkValidity
  3. Use setCustomValidity on dom ready + every change
  4. use reportValidity, if available, to report errors or mimic reportValditity by clicking on a submit button

Here is a simple method to make setCustomValidity simpler:

//setCustomValidity is stupid so we improve by adding setCustomValidityRule
$.fn.setCustomValidityRule = function (type, rule) {
    var testRule = function () {
        //only if it's valid or we have set the message our own
        if ($(this).is(':valid') || $.data(this, 'customErrorType' + type)) {
            var message = rule(this);
            this.setCustomValidity(message);
            if (message) {
                $.data(this, 'customErrorType' + type, message)
            } else {
                $.removeData(this, 'customErrorType' + type);
            }
        }
    };
    return this
    //invoke initially
    .each(testRule)
    //and on every change
    .on('customchange.customerror' + type + ' change.customerror' + type, testRule);
};

And here is a method to make reportValidity work in current browsers:

$.fn.reportValidity = function () {
    return this.each(function () {
        var form;
        if (this.reportValidity) {
            this.reportValidity();
        } else {
            form = $($.prop(this, 'form')).add(this).filter('form');
            //checkvalidity is obtrusive sometimes so we use :invalid
            if($(':invalid', form).length){
            $('<input type="submit" />')
                .css({
                    position: 'absolute',
                    left: '-99999px'
                })
                .appendTo(form)
                .click()
                .remove();
            }
        }
    });
};
查看更多
登录 后发表回答