完全模拟textarea的maxlength属性

2012-03-28 21:25

完全模拟textarea的maxlength属性

by army

at 2012-03-28 13:25:58

original http://army8735.org/2012/03/28/1057.html

现代浏览器中,textarea的maxlength属性已经被浏览器内置支持,然而ie系列仍旧我行我素。

在低版本ie(ie8-)中,可以通过对propertychange的侦听来检测输入,从而模拟maxlength属性。然而ie9很奇怪,支持了input事件,但是删除、剪切、粘帖却不触发input!

幸运的是,ie9还可以侦听cut、paste这样的操作,于是大概模拟兼容主流浏览器的代码如下(使用jQuery):

define(['jQuery', 'Event'], function($, Event) {
    var ie9 = $.browser.msie && $.browser.version == '9.0';
    function virtualTextareaMaxlength(item) {
        var max = parseInt(item.attr('maxlength')),
            v = item.val();
        if(!isNaN(max) && v.length > max) {
            var i,
                bookmark,
                oS = document.selection.createRange(),
                oR = document.body.createTextRange();
            oR.moveToElementText(item[0]);
            bookmark = oS.getBookmark();
            for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i++)
                //ie的换行是\r\n,算2个字符长度
                if(v.charAt(i) == '\n')
                    i++;
            item.val(v.substr(0, Math.min(max, i - 1)) + v.substr(i, Math.min(max, v.length)));
            //模拟光标位置
            if(v.length != i) {
                var range = item[0].createTextRange();
                range.collapse(true);
                range.moveEnd('character', i - 1);
                range.moveStart('character', i - 1);
                range.select();
            }
        }
    }
    function cb(self, item) {
        var v = item.val();
        self.trigger('input', [v.length, parseInt(item.attr('maxlength'))]);
    }
    return Event.extend(function(item) {
        Event.call(this);
        if($.type(item) == 'string')
            item = $(item);
        var self = this;
        if(window.addEventListener) {
            item.bind('input', function() {
                if(ie9)
                    virtualTextareaMaxlength(item);
                cb(self, item);
            });
            //ie9对于delete、backspace、剪切、粘帖不支持,需hack
            if(ie9)
                item.bind('keydown cut paste', function(e) {
                    switch(e.type) {
                        case 'keydown':
                            if(e.keyCode != 46 && e.keyCode != 8)
                                return;
                        default:
                            setTimeout(function() {
                                virtualTextareaMaxlength(item);
                                cb(self, item);
                            }, 0);
                    }
                });
        }
        else
            item.bind('propertychange', function() {
                virtualTextareaMaxlength(item);
                cb(self, item);
            });
    });
});

依赖的Event模块:

define('Event', ['jQuery', 'Class'], function($, Class) {
    var Klass = Class(function() {
        this.dispatcher1 = $({});
    }).methods({
        bind: function() {
            var self = this,
                args = Array.prototype.slice.call(arguments, 0),
                cb = args.pop();
                cb2 = function() {
                    var as = Array.prototype.slice.call(arguments, 0);
                    as.shift();
                    cb.apply(self, as);
                };
                args.push(cb2);
            this.dispatcher1.bind.apply(this.dispatcher1, args);
            return self;
        }
    });
    ['unbind', 'trigger'].forEach(function(k){
        Klass.prototype[k] = function() {
            this.dispatcher1[k].apply(this.dispatcher1, Array.prototype.slice.call(arguments, 0));
            return this;
        }
    });
    Klass.statics({
        mix: function() {
            Array.prototype.slice.call(arguments, 0).forEach(function(o) {
                var e = new Klass,
                    mix = {};
                Object.keys(Klass.prototype).forEach(function(k) {
                    mix[k] = function() {
                        e[k].apply(e, Array.prototype.slice.call(arguments, 0));
                    }
                });
                $.extend(o, mix);
            });
            return arguments[0];
        }
    });
    return Klass;
});

依赖的Class模块:

define('Class', function() {
    function inheritPrototype(subType, superType) {
        var prototype = Object.create(superType.prototype);
        prototype.constructor = subType;
        subType.prototype = prototype;
        //继承static变量
        Object.keys(superType).forEach(function(k) {
            if(!subType.hasOwnProperty(k))
                subType[k] = superType[k];
        });
        return subType;
    }
    function wrap(fn) {
        fn.extend = function(sub) {
            inheritPrototype(sub, fn);
            return wrap(sub);
        }
        fn.methods = function(o) {
            Object.keys(o).forEach(function(k) {
                fn.prototype[k] = o[k];
            });
            return fn;
        };
        fn.statics = function(o) {
            Object.keys(o).forEach(function(k) {
                fn[k] = o[k];
            });
            return fn;
        };
        return fn;
    }
    function klass(cons) {
        return wrap(cons);
    }
    klass.extend = inheritPrototype;
    return klass;
});