Postal Code Textbox (zip code)

As part of a new UI library, I have created a Postal Code (Zip Code) Textbox in a manner that follows the guidelines found in the Webgrown Solutions UI Manifesto. The Postal Code Textbox supports the following features:

The Demo

The Markup

Just a basic input element, with a normal class attribute and some custom data (data-*) attributes to allow feature customization. Once the Script stuff is in place on a website, any page can implement the Postal Code Textbox by simply setting the input element's class attribute to "postalCodeTextbox" and setting the desired optional custom data attributes.

That sounds like a DRY solution to me (see UI Manifesto).

 
<div class="form legendLook horizontalFormLayout">
    <div id="hdivFormTest1ConfirmationMessage" class="confirmationMessageWrapper hide"></div>
            
    <label for="txtUsdZipCode">US Zip Code:</label>
    <input type="text" id="txtUsdZipCode" class="postalCodeTextbox" 
        required 
        data-countryIsoCode="US"
        data-allowExtended="true"
        data-formGroup="formAuthenticate" 
        name="Value" />

    <label for="txtCanadaPostalCode">Canada Postal Code:</label>
    <input type="text" id="txtCanadaPostalCode" class="postalCodeTextbox" 
        required 
        data-countryIsoCode="CA"
        data-formGroup="formAuthenticate" 
        name="_" />          

    <div class="formButtonWrapper clearFix">
        <input type="submit" class="ajaxFormPost buttonStrong" value="Submit"
            data-formGroup="formAuthenticate" 
            data-serviceRequestUrl="..." 
            data-successFunction="successFormTest1"
            data-confirmActionMessage=""
            data-processingMessage=""
            data-overlayWindowWhileProcessing="true" />
    </div>
</div>

The Script

function formatPostalCode(value, countryIsoCode, allowExtended, event) {
    var requestingElement = $("#" + event.target.id);

    //8  = Backspace
    //9  = Tab (so will stay highlighted)
    //16 = Reverse Tab - Shift+Tab (so will stay highlighted)
    if (getKeycode(event) != 8 && getKeycode(event) != 9 && getKeycode(event) != 16) {
        switch (countryIsoCode.toUpperCase()) {
            case "CA":
                //Build and assign the "pattern", if not already there.
                if (!isAttrDefined(requestingElement.attr("pattern")) || requestingElement.attr("pattern").length == 0) {
                    var patternValue = "/^((A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|X|Y)(\\d{1})(A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|W|X|Y|Z) (\\d{1})(A|B|C|E|G|H|J|K|L|M|N|P|R|S|T|V|W|X|Y|Z)(\\d{1}))$/";
                    requestingElement.attr("pattern", patternValue);
                }

                //Assign the "maxlength", if not already there.
                if (!isAttrDefined(requestingElement.attr("maxlength")) || requestingElement.attr("maxlength").length == 0) {
                    requestingElement.attr("maxlength", "7");
                }

                //http://www.infinitegravity.ca/postalcodeformat.htm (ANA NAN)
                var unformattedPostalCode = value.replace(/\s/g, "");
                var formattedPostalCode = "";

                if (unformattedPostalCode.length > 0) {
                    var validFirstCharacterOfFSA = new Array("A", "B", "C", "E", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "V", "X", "Y");

                    for (var i = 0; i <= (validFirstCharacterOfFSA.length - 1); i++) {  //the (<= & -1) is needed to not get messed up with Packer 3.1 compression
                        if (validFirstCharacterOfFSA[i] == unformattedPostalCode.charAt(0).toUpperCase()) {
                            formattedPostalCode += validFirstCharacterOfFSA[i];
                        }
                    }

                    if (formattedPostalCode.length == 1 && unformattedPostalCode.length > 1) {
                        if (!isNaN(unformattedPostalCode.charAt(1))) {
                            formattedPostalCode += unformattedPostalCode.charAt(1);
                        }
                    }

                    if (formattedPostalCode.length == 2 && unformattedPostalCode.length > 2) {
                        //http://en.wikipedia.org/wiki/Canadian_postal_code
                        //No postal code includes the letters D, F, I, O, Q, or U, as the OCR equipment used in automated sorting could easily confuse them with other letters and digits, especially when they are rendered as cursive handwriting. The letters W and Z are used, but are not currently used as the first letter.

                        //Added W and Z to First list
                        var validThirdCharacterOfFSA = new Array("A", "B", "C", "E", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "V", "W", "X", "Y", "Z");

                        for (var j = 0; j <= (validThirdCharacterOfFSA.length - 1); j++) { //the (<= & -1) is needed to not get messed up with Packer 3.1 compression
                            if (validThirdCharacterOfFSA[j] == unformattedPostalCode.charAt(2).toUpperCase()) {
                                formattedPostalCode += validThirdCharacterOfFSA[j];
                            }
                        }
                    }

                    if (formattedPostalCode.length == 3) {
                        formattedPostalCode += " ";
                    }

                    if (formattedPostalCode.length == 4 && unformattedPostalCode.length > 3) {
                        if (!isNaN(unformattedPostalCode.charAt(3))) {
                            formattedPostalCode += unformattedPostalCode.charAt(3);
                        }
                    }

                    if (formattedPostalCode.length == 5 && unformattedPostalCode.length > 4) {
                        //http://en.wikipedia.org/wiki/Canadian_postal_code
                        //No postal code includes the letters D, F, I, O, Q, or U, as the OCR equipment used in automated sorting could easily confuse them with other letters and digits, especially when they are rendered as cursive handwriting. The letters W and Z are used, but are not currently used as the first letter.

                        //Added W and Z to First list
                        var validSecondCharacterOfLDU = new Array("A", "B", "C", "E", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "V", "W", "X", "Y", "Z");

                        for (var j = 0; j <= (validSecondCharacterOfLDU.length - 1); j++) { //the (<= & -1) is needed to not get messed up with Packer 3.1 compression
                            if (validSecondCharacterOfLDU[j] == unformattedPostalCode.charAt(4).toUpperCase()) {
                                formattedPostalCode += validSecondCharacterOfLDU[j];
                            }
                        }
                    }

                    if (formattedPostalCode.length == 6 && unformattedPostalCode.length > 5) {
                        if (!isNaN(unformattedPostalCode.charAt(5))) {
                            formattedPostalCode += unformattedPostalCode.charAt(5);
                        }
                    }
                }

                return formattedPostalCode;
                break;
            default: //US
                //Build and assign the "pattern", if not already there.
                if (!isAttrDefined(requestingElement.attr("pattern")) || requestingElement.attr("pattern").length == 0) {
                    var patternValue = "/^(((\\d{5}))|((\\d{5})-(\\d{4})))$/";
                    requestingElement.attr("pattern", patternValue);
                }

                //Assign the "maxlength", if not already there.
                if (!isAttrDefined(requestingElement.attr("maxlength")) || requestingElement.attr("maxlength").length == 0) {
                    if (allowExtended) {
                        requestingElement.attr("maxlength", "10");
                    }
                    else {
                        requestingElement.attr("maxlength", "5");
                    }
                }

                var unformattedPostalCode = makeNumeric(value, false, null, null);
                var formattedPostalCode = "";

                formattedPostalCode += unformattedPostalCode.charAt(0) + unformattedPostalCode.charAt(1) + unformattedPostalCode.charAt(2) + unformattedPostalCode.charAt(3) + unformattedPostalCode.charAt(4);

                if ((formattedPostalCode.length == 5 && getKeycode(event) == 109) || (unformattedPostalCode.length > 5)) {
                    formattedPostalCode += "-"
                }

                if (unformattedPostalCode.length > 5 && allowExtended) {
                    formattedPostalCode += unformattedPostalCode.charAt(5) + unformattedPostalCode.charAt(6) + unformattedPostalCode.charAt(7) + unformattedPostalCode.charAt(8);
                }

                return formattedPostalCode;
                break;
        }
    }
    else {
        return value;
    }
}

//Enable Postal Code Textbox(es)
$(parentElementSelector + " input.postalCodeTextbox").each(function () {
    $(this).keyup(function (event) {
        var countryIsoCode = ((!isAttrDefined($(this).attr("data-countryIsoCode")) || $(this).attr("data-countryIsoCode").length == 0) ? $("#hfGlobalCountryIsoCode").val() : $(this).attr("data-countryIsoCode"));
        var allowExtended = (($(this).attr("data-allowExtended") == "true") ? true : false);
        $(this).val(formatPostalCode($(this).val(), countryIsoCode, allowExtended, event));
    });
});

The CSS

 
    Not applicable.