Search code examples
javascriptjquerymaskedinput

jQuery maskedinput triggers completed twice on paste


I'm facing this bug with jQuery maskedinput.

Pasting a value on an masked input triggers the completed event twice. This behavior can be seen on their oficial website.

Open this link and click on Demo. Select Product Key input and type aa-999-a999. After that, try to paste the same value in the field.

There are some other issues related to NOT triggering the event on paste, but none of them helped me with this bug...

Dows anyone know why or had a similar problem?

Thanks in advance.


Solution

  • So I've tried a simplest case like this (using this for maskedinput.js, though note that I edit it, below, and this for jquery-1.11.2.js):

    <html>
    <body>
        <script type="text/javascript" src="./jquery-1.11.2.js"></script>
        <script type="text/javascript" src="./jquery.maskedinput.js"></script>
    
        <script type="text/javascript">
        jQuery(function($) {
              $.mask.definitions['~']='[+-]';
              $("#tin").mask("99-9999999",{completed:function(){alert("Now you typed: "+this.val());}});
           });
        </script>
    
        <table border="0">
        <tbody>
            <tr>
                <td>Tax ID</td>
                <td><input type="text" tabindex="5" id="tin"></td>
                <td>99-9999999</td>
            </tr>
        </tbody>
        </table>
    </body>
    </html>
    

    And I edited the maskedinput source a bit to see when and from where stuff fires. I'll paste.ee it here, but here are the main parts...

    • Find the .on("blur.mask")... line
    • Note that it's reusing the same function for blur, keydown, input, and paste.
    • Add a console.log to tell you what specific event is firing each time to debug pasting.

    Here's what I did (note that if you're using IE8-, you need to "protect" the console.log call or run it with Dev Tools open):

    }).on("blur.mask", blurEvent).on("keydown.mask", keydownEvent).on("keypress.mask", keypressEvent).on("input.mask", function() {
        console.log("mask called: " + arguments[0].type); // <<< New debugging line.
        input.prop("readonly") || setTimeout(function() {
            var pos = checkVal(!0);
            input.caret(pos);
            tryFireCompleted("mask");
        }, 0);
    }), chrome && android && input.off("input.mask").on("input.mask", androidInputEvent),
    

    When you paste stuff in, two event handlers are called for the "same" action.

    mask called: paste
    mask called: input
    

    Those both eventually call tryFireCompleted, which fires off the .completed function we set up in this line of our own code, above:

    $("#tin").mask("99-9999999",{completed:function(){alert("Now you typed: "+this.val());}});
    

    If you remove the paste.mask event handler, that problem will go away. Here's the crucial part:

    .on("input.mask paste.mask",
    ... changes into...
    .on("input.mask",
    ... so paste.mask is gone. (And before releasing to production, remove the console.log line, of course.)

    Now that might work all by itself. Pastes were firing off input, so having paste too might be overkill. But I'd check around more before I used that as a permanent fix. I did try right-clicking a paste with the mouse, which was fine with this change. Not sure when paste is fired and input isn't.

    If there is an edge case where paste is called and input isn't (or a case where you really would want tryFireCompleted to fire twice), you'll need to find a way to suppress the second tryFireCompleted with more convoluted code.

    Without a completed function, though, firing off twice wouldn't be an obvious problem, I don't think, which explains why this might have slipped through. Good find.