【JQuery】input format 输入框内容格式化

2023-01-27  本文已影响0人  冰麟轻武

1. 背景

先看一张图


类似这样的功能相信绝大部分人都遇到过,用vue之类响应式框架来搞很简单,但是老项目基于JQuery的就似乎没看到过什么现成的组件是可以拿来就用的,所以只能自己搞一个

2. 思路

类似Excel的设置单元格格式,这里就是设置输入框格式

  1. 响应焦点移入/移出事件
  2. 用起来要方便、直观
  3. 扩展要简单

3. 编码

3.1. 响应焦点移入/移出事件

要支持所有input,包括动态产生的,所以要使用on文档)来绑定

$(document).on("blur", ":input", e => {
    const jq = $(e.target);
    // 处理格式化逻辑
});
$(document).on("focus", ":input", e => {
    const jq = $(e.target);
    // 处理反格式化逻辑
});

3.2. 用起来要方便、直观

使用input自定义属性来定义格式,格式化支持多种规则顺序执行,反格式化支持单一规则
格式化:对应format属性和blur事件
反格式化:对应unformat属性和focus事件

<!-- 格式化:去空格 -->
<input type="text" format="trim">
<!-- 格式化:去空格 → 转数字 → 保留4位小数 → 加千分位符号 → 转为百分比 -->
<!-- 反格式化:百分比转数字 -->
<input type="text" format="trim|number|float:4|thous|percent" unformat="percent">

3.3. 扩展要简单

使用$.formats来定义所有格式化规则,$.unformats来定义所有反格式化规则

$.extend($.formats, {
    /** 
     * 格式化规则:去空格
     * @param {JQuery} jquery对象
     * @param {String|null} 表示input输入的值或经过其他格式化规则处理后的值
     * @param {String} 表示格式化规则参数 
     * @returns {String|undefined} 表示格式化之后的值, 如返回 undefined 表示无法处理
     */
    trim(jq, value, args) { return value == null ? value : value.trim() },
    /** 
     * 格式化规则:保留小数位
     * @param {JQuery} jquery对象
     * @param {String|null} 表示input输入的值或经过其他格式化规则处理后的值
     * @param {String} 表示保留几位小数, 默认2 <input format='float:2' >
     * @returns {String|undefined} 表示格式化之后的值, 如返回 undefined 表示无法处理
     */
    float(jq, value, args) { isFinite(value) && Number(value).toFixed(args == "" ?  2 : args) },
    number(){...},
    thous(){...},
    percent(){...},
    ['.']: "float",  // 别名规则,方便调用
    [',']: "thous",
    ['%']: "percent",
});

别名规则可以用别名调用,如:

<input type="text" format="trim|number|float:4|thous|percent" unformat="percent" />
<input type="text" format="trim|number|.:4|,|%" unformat="%" />

3.4. 封装JQuery插件函数

function handler(functions, text) {
    const colon = text.indexOf(":");
    const arg = colon < 0 ? "" : text.substring(colon + 1);
    const name = colon < 0 ? text : text.substring(0, colon);
    let func = functions[name];
    while (func instanceof Function === false) {
        if (func == null) {
            return (_, value) => value;
        }
        func = functions[func];
    }
    return (jq, value) => {
        try {
            var ret = func.apply(functions, [jq, value, arg]);
            if (ret !== undefined) {
                return ret;
            }
            return value;
        } catch (e) {
            console.error("format/unformat [" + name + "] error:", e);
            return value;
        }
    }
}
$.fn.extend({
    format() {
        if (!this.is("[format]")) {
            return this.val();
        }
        const formats = (this.attr("format") || "").split("|");
        let value = this.val();
        for (var i = 0; i < formats.length; i++) {
            value = handler($.formats, formats[i])(this, value);
        }
        return value;
    },
    unformat() {
        const unformat = this.attr("unformat");
        if (!unformat) {
            return this.val();
        }
        return handler($.unformats, unformat)(this, this.val());
    },
});

3.5. 完善各种规则

4. 完整代码

    (function () {
            /**
             * 字符串转数字
             * @para
             */
            function parseNumber(str) {
                if (str == null || str === "") {
                    return null;
                }
                if (typeof str === "number" && !isFinite(str)) {
                    return str;
                }
                str = str.toString().trim().replace(/([, _](?=\d{3}))|(^[^\d+-]+\s?)|((\s?[\D]+$))/g, "");
                if (!/^[+-]?[0-9]+(\.([0-9])*)?(e[+-]\d+)?$/.test(str)) {
                    return null;
                }
                var number = parseFloat(str);
                if (isNaN(number) || !isFinite(number)) {
                    return null;
                }
                return number;
            }

            /**
            * 移动小数点
            * @param {Number} number 数字
            * @param {Number} move 小数点移动位数
            */
            function movePoint(number, move) {

                move = parseInt(move);
                if (isNaN(move) || move < -20 || move > 20) {
                    throw Error("move不能大于20或小于-20")
                }

                var str = overflowString(number, 20);
                var point = str.indexOf(".");
                var arr = str.split("");
                arr.splice(point, 1);
                arr.splice(point + move, 0, ".");
                return parseNumber(arr.join(""));
            }

            /**
             * 字符串前后补0,方便后续操作
             * @param {Number} number 需要补0的数字
             * @param {Number|String|null} zeroLength 前后补0的个数
             */
            function overflowString(number, zeroLength) {
                zeroLength = parseNumber(zeroLength) || 10;
                if (zeroLength < 0) {
                    zeroLength = 10;
                }
                var symbol = number < 0 ? '-' : "";
                number = Math.abs(number);
                // 将20位以内的科学计数法数字转为纯数字
                var str = number.toLocaleString("zh-CN", { maximumFractionDigits: 20 }).replace(/[,]/g, "");
                var zero = new Array(zeroLength).fill("0").join("");
                var point = str.indexOf(".") < 0 ? "." : ""; // 添加小数点
                return symbol + zero + str + point + zero;
            }

            if ($.formats == null) {
                $.formats = {};
            }

            $.extend($.formats, {
                trim(_, value, args) {
                    if (!value) {
                        return value;
                    }
                    switch ((args || "all").trim().toLowerCase()) {
                        case "right":
                        case "r":
                            return value.trimRight();
                        case "left":
                        case "l":
                            return value.trimLeft();
                        default:
                            return value.trim();
                    }
                },
                number(ele, value, def) {
                    const number = parseNumber(value);
                    if (number == null) {
                        return ele[0].lastNumber || def || "";
                    }
                    ele[0].lastNumber = number;
                    return number;
                },
                float(_, value, digits) {
                    const number = parseNumber(value);
                    if (number != null) {
                        return number.toFixed(digits);
                    }
                },
                money(_, value, space) {
                    space = parseNumber(space);
                    if (space > 0) {
                        return "$" + new Array(space + 1).join(" ") + value;
                    }
                    return "$" + value;
                },
                thous(_, value, flag) {
                    const number = parseNumber(value);
                    if (number != null) {
                        const digits = value.toString().length - value.toString().indexOf(".") - 1;
                        const ret = number.toLocaleString("zh-CN", { maximumFractionDigits: 20, minimumFractionDigits: digits });
                        if (flag) {
                            return ret.replace(',', flag);
                        }
                    }
                },
                percent(jq, value) {
                    const number = parseNumber(value);
                    if (number != null) {
                        return jq.val().endsWith("%") ? number + "%" : movePoint(number, 2) + "%";
                    }
                },
                milli(_, value) {
                    const number = parseNumber(value);
                    if (number != null) {
                        return movePoint(number, 3) + "‰";
                    }
                },
                ['$']: "money",
                [',']: "thous",
                ['%']: "percent",
                ['‰']: "milli",
            });

            if ($.unformats == null) {
                $.unformats = {};
            }

            $.extend($.unformats, {
                number(_, value, arg) {
                    const number = parseNumber(value);
                    if (number == null) {
                        return (arg + "") === "true" ? null : str;
                    }
                    return number;
                },
                percent(_, value, arg) {
                    const number = parseNumber(value);
                    if (number == null) {
                        return value;
                    }
                    return value.endsWith("%") ? movePoint(number, -2) : number;
                },
                milli(_, value, arg) {
                    const number = parseNumber(value);
                    if (number == null) {
                        return value;
                    }
                    return value.endsWith("‰") ? movePoint(number, -3) : number;
                },
                ['%']: "percent",
                ['‰']: "milli",
            });


            if ($.fn.format) {
                return;
            }

            function handler(functions, text) {
                const colon = text.indexOf(":");
                const arg = colon < 0 ? "" : text.substring(colon + 1);
                const name = colon < 0 ? text : text.substring(0, colon);
                let func = functions[name];
                while (func instanceof Function === false) {
                    if (func == null) {
                        return (_, value) => value;
                    }
                    func = functions[func];
                }
                return (jq, value) => {
                    try {
                        var ret = func.apply(functions, [jq, value, arg]);
                        if (ret !== undefined) {
                            return ret;
                        }
                        return value;
                    } catch (e) {
                        console.error("format/unformat [" + name + "] error:", e);
                        return value;
                    }
                }
            }

            $.fn.extend({
                format() {
                    if (!this.is("[format]")) {
                        return this.val();
                    }
                    const formats = (this.attr("format") || "").split("|");
                    let value = this.val();
                    for (var i = 0; i < formats.length; i++) {
                        value = handler($.formats, formats[i])(this, value);
                    }
                    return value;
                },
                unformat() {
                    const unformat = this.attr("unformat");
                    if (!unformat) {
                        return this.val();
                    }
                    return handler($.unformats, unformat)(this, this.val());
                },
            });


            $(document).on("blur", ":input[format]", e => {
                const jq = $(e.target);
                const value = jq.format();
                if (value != jq.val()) {
                    jq.val(value);
                }
            });

            $(document).on("focus", ":input[unformat]", e => {
                const jq = $(e.target);
                const value = jq.unformat();
                if (value != jq.val()) {
                    jq.val(value);
                }
            });
        })();
   

5. demo

JQuery input format demo源码预览- JSRUN

上一篇下一篇

猜你喜欢

热点阅读