parttio / textfieldformatter

TextField Formatter Vaadin add-on
Apache License 2.0
13 stars 9 forks source link

On Vaadin 23.3.X - Phone number validation issues #66

Closed oilhands closed 1 year ago

oilhands commented 1 year ago

Phone number validation issues when values are cleared by the user after having entered prior text. When the user does this the validation input is the prior text rather than the empty string which the user finished up on.

public static final int PHONE_BLOCKS[] = {0, 3, 3, 4}; public static final String PHONE_DELIMITERS[] = {"(", ") ", "-"};

TextField tollFreePhone = new TextField();

new CustomStringBlockFormatter(PHONE_BLOCKS, PHONE_DELIMITERS, null, null, true).extend(tollFreePhone);

oilhands commented 1 year ago

To be clear on this the clearing of the text needs to be done by using the space key.

oilhands commented 1 year ago

After downloading the source code and reviewing our implementation, I'm not 100% sure that my original analysis is correct. There is a validator we have on the field in addition to the formatter extension. What I was seeing was a war between this other validator and the formatter. Space key and providing non-numeric input when the valid value is blanked out (select all the valid text first) causes the issue. Our validator method then gets goofy values like space and letter x to begin with. Then as I enter numeric values our validator fires erratically with partial inputs until the full phone number is entered and the field departed.

oilhands commented 1 year ago

My guess is that this is a Cleave issue (cleave-esm.js) in combination with the server side representation.

It strips the non-numeric input below and uses that in the display. But there is a disconnect between it's value on the client side and the representation on the server side. The client browser value is the stripped empty string. This includes space, single non-numeric key press, and copy/pasting non-numeric text strings. The client side is the empty string and the backend server side is the space or non-numeric character or the pasted string of non-numeric values. This disconnect between the two representations then creates all kinds of goofy behavior.

onInput: function (value) { var owner = this, pps = owner.properties, Util = Cleave.Util; // case 1: delete one more character "4" // 1234| -> hit backspace -> 123| // case 2: last character is not delimiter which is: // 12|34 -> hit backspace -> 1|34* // note: no need to apply this for numeral mode var postDelimiterAfter = Util.getPostDelimiter(value, pps.delimiter, pps.delimiters); if (!pps.numeral && pps.postDelimiterBackspace && !postDelimiterAfter) { value = Util.headStr(value, value.length - pps.postDelimiterBackspace.length); }

    // phone formatter
    if (pps.phone) {
        if (pps.prefix && (!pps.noImmediatePrefix || value.length)) {
            pps.result = pps.prefix + pps.phoneFormatter.format(value).slice(pps.prefix.length);
        } else {
            pps.result = pps.phoneFormatter.format(value);
        }
        owner.updateValueState();

        return;
    }

    // numeral formatter
    if (pps.numeral) {
        // Do not show prefix when noImmediatePrefix is specified
        // This mostly because we need to show user the native input placeholder
        if (pps.prefix && pps.noImmediatePrefix && value.length === 0) {
            pps.result = '';
        } else {
            pps.result = pps.numeralFormatter.format(value);
        }
        owner.updateValueState();

        return;
    }

    // date
    if (pps.date) {
        value = pps.dateFormatter.getValidatedDate(value);
    }

    // time
    if (pps.time) {
        value = pps.timeFormatter.getValidatedTime(value);
    }

    // strip delimiters
    value = Util.stripDelimiters(value, pps.delimiter, pps.delimiters);

    // strip prefix
    value = Util.getPrefixStrippedValue(value, pps.prefix, pps.prefixLength, pps.result, pps.delimiter, pps.delimiters, pps.noImmediatePrefix, pps.tailPrefix, pps.signBeforePrefix);

    // strip non-numeric characters
    value = pps.numericOnly ? Util.strip(value, /[^\d]/g) : value;

    // convert case
    value = pps.uppercase ? value.toUpperCase() : value;
    value = pps.lowercase ? value.toLowerCase() : value;

    // prevent from showing prefix when no immediate option enabled with empty input value
    if (pps.prefix) {
        if (pps.tailPrefix) {
            value = value + pps.prefix;
        } else {
            value = pps.prefix + value;
        }

        // no blocks specified, no need to do formatting
        if (pps.blocksLength === 0) {
            pps.result = value;
            owner.updateValueState();

            return;
        }
    }

    // update credit card props
    if (pps.creditCard) {
        owner.updateCreditCardPropsByValue(value);
    }

    // strip over length characters
    value = Util.headStr(value, pps.maxLength);

    // apply blocks
    pps.result = Util.getFormattedValue(
        value,
        pps.blocks, pps.blocksLength,
        pps.delimiter, pps.delimiters, pps.delimiterLazyShow
    );

    owner.updateValueState();
},