改造jQuery-Tagit 插件支持中文全角的逗号和空格

来源:互联网 发布:python 开发环境 编辑:程序博客网 时间:2024/05/21 22:22

jQuery 的 tagit 插件效果还是不错的,今天用到该插件但发现不能自定义标签分隔符,只能是英文半角逗号或空格,于是想改造下


效果:


先研究了一番插件的代码,发现并不能通过插件自身的扩展方法来实现,

标签输入框是插件自己生成的,所以本来想在外部绑定 keydown 事件但由于事件绑定先后顺序的问题不能实现,只能修改代码了:

改动不多,主要是增加了三个事件绑定在插件原来的 keydown 事件之前绑定一个自定义的 keydown 以及 blur 事件处理标签内容的过滤,以及 keyup 后模拟键盘的半角逗号按键回调触发原来的 keydown 事件,从而不会太多改动插件代码,不妨碍以后升级

这里还有一个问题,涉及到 keydown、keypress、keyup 顺序和效率的问题,简单说说:keydown是指键盘开始往下按下,此时 input 控件并没有得到最终的按键字符,只有一个响应事件 keyCode,而且此时如果是用的中文输入法则 keyCode 始终都是 229,也就不能通过该事件来判断全角中文的输入字符了,但这个效率应该是很高的,处理效果会很流畅,keypress 也是差不多的




代码:

/** jQuery UI Tag-it!** @version v2.0 (06/2011)** Copyright 2011, Levy Carneiro Jr.* Released under the MIT license.* http://aehlke.github.com/tag-it/LICENSE** Homepage:*   http://aehlke.github.com/tag-it/** Authors:*   Levy Carneiro Jr.*   Martin Rehfeld*   Tobias Schmidt*   Skylar Challand*   Alex Ehlke** Maintainer:*   Alex Ehlke - Twitter: @aehlke** Dependencies:*   jQuery v1.4+*   jQuery UI v1.8+*/(function($) {    $.widget('ui.tagit', {        options: {            allowDuplicates   : false,            caseSensitive     : true,            fieldName         : 'tags',            placeholderText   : null,   // Sets `placeholder` attr on input field.            readOnly          : false,  // Disables editing.            removeConfirmation: false,  // Require confirmation to remove tags.            tagLimit          : null,   // Max number of tags allowed (null for unlimited).            // Used for autocomplete, unless you override `autocomplete.source`.            availableTags     : [],            // Use to override or add any options to the autocomplete widget.            //            // By default, autocomplete.source will map to availableTags,            // unless overridden.            autocomplete: {},            // Shows autocomplete before the user even types anything.            showAutocompleteOnFocus: false,            // When enabled, quotes are unneccesary for inputting multi-word tags.            allowSpaces: false,            // The below options are for using a single field instead of several            // for our form values.            //            // When enabled, will use a single hidden field for the form,            // rather than one per tag. It will delimit tags in the field            // with singleFieldDelimiter.            //            // The easiest way to use singleField is to just instantiate tag-it            // on an INPUT element, in which case singleField is automatically            // set to true, and singleFieldNode is set to that element. This            // way, you don't need to fiddle with these options.            singleField: false,            // This is just used when preloading data from the field, and for            // populating the field with delimited tags as the user adds them.            singleFieldDelimiter: ',',            // Set this to an input DOM node to use an existing form field.            // Any text in it will be erased on init. But it will be            // populated with the text of tags as they are created,            // delimited by singleFieldDelimiter.            //            // If this is not set, we create an input node for it,            // with the name given in settings.fieldName.            singleFieldNode: null,            // Whether to animate tag removals or not.            animate: true,            // Optionally set a tabindex attribute on the input that gets            // created for tag-it.            tabIndex: null,            // Event callbacks.            beforeTagAdded      : null,            afterTagAdded       : null,            beforeTagRemoved    : null,            afterTagRemoved     : null,            onTagClicked        : null,            onTagLimitExceeded  : null,            // DEPRECATED:            //            // /!\ These event callbacks are deprecated and WILL BE REMOVED at some            // point in the future. They're here for backwards-compatibility.            // Use the above before/after event callbacks instead.            onTagAdded  : null,            onTagRemoved: null,            // `autocomplete.source` is the replacement for tagSource.            tagSource: null            // Do not use the above deprecated options.        },        _create: function() {            // for handling static scoping inside callbacks            var that = this;            // There are 2 kinds of DOM nodes this widget can be instantiated on:            //     1. UL, OL, or some element containing either of these.            //     2. INPUT, in which case 'singleField' is overridden to true,            //        a UL is created and the INPUT is hidden.            if (this.element.is('input')) {                this.tagList = $('<ul></ul>').insertAfter(this.element);                this.options.singleField = true;                this.options.singleFieldNode = this.element;                this.element.addClass('tagit-hidden-field');            } else {                this.tagList = this.element.find('ul, ol').andSelf().last();            }            this.tagInput = $('<input type="text" />').addClass('ui-widget-content');            if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled');            if (this.options.tabIndex) {                this.tagInput.attr('tabindex', this.options.tabIndex);            }            if (this.options.placeholderText) {                this.tagInput.attr('placeholder', this.options.placeholderText);            }            if (!this.options.autocomplete.source) {                this.options.autocomplete.source = function(search, showChoices) {                    var filter = search.term.toLowerCase();                    var choices = $.grep(this.options.availableTags, function(element) {                        // Only match autocomplete options that begin with the search term.                        // (Case insensitive.)                        return (element.toLowerCase().indexOf(filter) === 0);                    });                    if (!this.options.allowDuplicates) {                        choices = this._subtractArray(choices, this.assignedTags());                    }                    showChoices(choices);                };            }            if (this.options.showAutocompleteOnFocus) {                this.tagInput.focus(function(event, ui) {                    that._showAutocomplete();                });                if (typeof this.options.autocomplete.minLength === 'undefined') {                    this.options.autocomplete.minLength = 0;                }            }            // Bind autocomplete.source callback functions to this context.            if ($.isFunction(this.options.autocomplete.source)) {                this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this);            }            // DEPRECATED.            if ($.isFunction(this.options.tagSource)) {                this.options.tagSource = $.proxy(this.options.tagSource, this);            }            this.tagList                .addClass('tagit')                .addClass('ui-widget ui-widget-content ui-corner-all')                // Create the input field.                .append($('<li class="tagit-new"></li>').append(this.tagInput))                .click(function(e) {                    var target = $(e.target);                    if (target.hasClass('tagit-label')) {                        var tag = target.closest('.tagit-choice');                        if (!tag.hasClass('removed')) {                            that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)});                        }                    } else {                        // Sets the focus() to the input field, if the user                        // clicks anywhere inside the UL. This is needed                        // because the input field needs to be of a small size.                        that.tagInput.focus();                    }                });            // Single field support.            var addedExistingFromSingleFieldNode = false;            if (this.options.singleField) {                if (this.options.singleFieldNode) {                    // Add existing tags from the input field.                    var node = $(this.options.singleFieldNode);                    var tags = node.val().split(this.options.singleFieldDelimiter);                    node.val('');                    $.each(tags, function(index, tag) {                        that.createTag(tag, null, true);                        addedExistingFromSingleFieldNode = true;                    });                } else {                    // Create our single field input after our list.                    this.options.singleFieldNode = $('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');                    this.tagList.after(this.options.singleFieldNode);                }            }            // Add existing tags from the list, if any.            if (!addedExistingFromSingleFieldNode) {                this.tagList.children('li').each(function() {                    if (!$(this).hasClass('tagit-new')) {                        that.createTag($(this).text(), $(this).attr('class'), true);                        $(this).remove();                    }                });            }            // 增加键盘输入事件,支持全角下的(,)逗号和( )空格            // 注意顺序必须在插件绑定前绑定自定义事件            this.tagInput.on('keydown blur', function(){                var val = that.tagInput.val();                val = val.replace(/(,| )+/g, '');                that.tagInput.val(val);            }).keyup(function() {                var val = $.trim(that.tagInput.val());                var lastChar = val ? val.substr(-1, 1) : '';                if (lastChar == ',' || lastChar == ' ') {                    that.tagInput.trigger('keydown', { keyCode:$.ui.keyCode.COMMA });                }            });            // Events.            this.tagInput                .keydown(function(event, opt) {                    // 增加外部模拟逗号键盘输入事件                    if (typeof opt == 'object' && typeof opt.keyCode == 'number') {                        event.which = opt.keyCode;                        event.shiftKey = false;                    }                    // Backspace is not detected within a keypress, so it must use keydown.                    if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {                        var tag = that._lastTag();                        if (!that.options.removeConfirmation || tag.hasClass('remove')) {                            // When backspace is pressed, the last tag is deleted.                            that.removeTag(tag);                        } else if (that.options.removeConfirmation) {                            tag.addClass('remove ui-state-highlight');                        }                    } else if (that.options.removeConfirmation) {                        that._lastTag().removeClass('remove ui-state-highlight');                    }                    // Comma/Space/Enter are all valid delimiters for new tags,                    // except when there is an open quote or if setting allowSpaces = true.                    // Tab will also create a tag, unless the tag input is empty,                    // in which case it isn't caught.                    if (                        (event.which === $.ui.keyCode.COMMA && event.shiftKey === false) ||                        event.which === $.ui.keyCode.ENTER ||                        (                            event.which == $.ui.keyCode.TAB &&                            that.tagInput.val() !== ''                        ) ||                        (                            event.which == $.ui.keyCode.SPACE &&                            that.options.allowSpaces !== true &&                            (                                $.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||                                (                                    $.trim(that.tagInput.val()).charAt(0) == '"' &&                                    $.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' &&                                    $.trim(that.tagInput.val()).length - 1 !== 0                                )                            )                        )                    ) {                        // Enter submits the form if there's no text in the input.                        if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) {                            event.preventDefault();                        }                        // Autocomplete will create its own tag from a selection and close automatically.                        if (!(that.options.autocomplete.autoFocus && that.tagInput.data('autocomplete-open'))) {                            that.tagInput.autocomplete('close');                            that.createTag(that._cleanedInput());                        }                    }                }).blur(function(e){                    // Create a tag when the element loses focus.                    // If autocomplete is enabled and suggestion was clicked, don't add it.                    if (!that.tagInput.data('autocomplete-open')) {                        that.createTag(that._cleanedInput());                    }                });            // Autocomplete.            if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) {                var autocompleteOptions = {                    select: function(event, ui) {                        that.createTag(ui.item.value);                        // Preventing the tag input to be updated with the chosen value.                        return false;                    }                };                $.extend(autocompleteOptions, this.options.autocomplete);                // tagSource is deprecated, but takes precedence here since autocomplete.source is set by default,                // while tagSource is left null by default.                autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source;                this.tagInput.autocomplete(autocompleteOptions).bind('autocompleteopen.tagit', function(event, ui) {                    that.tagInput.data('autocomplete-open', true);                }).bind('autocompleteclose.tagit', function(event, ui) {                    that.tagInput.data('autocomplete-open', false);                });                this.tagInput.autocomplete('widget').addClass('tagit-autocomplete');            }        },        destroy: function() {            $.Widget.prototype.destroy.call(this);            this.element.unbind('.tagit');            this.tagList.unbind('.tagit');            this.tagInput.removeData('autocomplete-open');            this.tagList.removeClass([                'tagit',                'ui-widget',                'ui-widget-content',                'ui-corner-all',                'tagit-hidden-field'            ].join(' '));            if (this.element.is('input')) {                this.element.removeClass('tagit-hidden-field');                this.tagList.remove();            } else {                this.element.children('li').each(function() {                    if ($(this).hasClass('tagit-new')) {                        $(this).remove();                    } else {                        $(this).removeClass([                            'tagit-choice',                            'ui-widget-content',                            'ui-state-default',                            'ui-state-highlight',                            'ui-corner-all',                            'remove',                            'tagit-choice-editable',                            'tagit-choice-read-only'                        ].join(' '));                        $(this).text($(this).children('.tagit-label').text());                    }                });                if (this.singleFieldNode) {                    this.singleFieldNode.remove();                }            }            return this;        },        _cleanedInput: function() {            // Returns the contents of the tag input, cleaned and ready to be passed to createTag            return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1'));        },        _lastTag: function() {            return this.tagList.find('.tagit-choice:last:not(.removed)');        },        _tags: function() {            return this.tagList.find('.tagit-choice:not(.removed)');        },        assignedTags: function() {            // Returns an array of tag string values            var that = this;            var tags = [];            if (this.options.singleField) {                tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);                if (tags[0] === '') {                    tags = [];                }            } else {                this._tags().each(function() {                    tags.push(that.tagLabel(this));                });            }            return tags;        },        _updateSingleTagsField: function(tags) {            // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter            $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change');        },        _subtractArray: function(a1, a2) {            var result = [];            for (var i = 0; i < a1.length; i++) {                if ($.inArray(a1[i], a2) == -1) {                    result.push(a1[i]);                }            }            return result;        },        tagLabel: function(tag) {            // Returns the tag's string label.            if (this.options.singleField) {                return $(tag).find('.tagit-label:first').text();            } else {                return $(tag).find('input:first').val();            }        },        _showAutocomplete: function() {            this.tagInput.autocomplete('search', '');        },        _findTagByLabel: function(name) {            var that = this;            var tag = null;            this._tags().each(function(i) {                if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) {                    tag = $(this);                    return false;                }            });            return tag;        },        _isNew: function(name) {            return !this._findTagByLabel(name);        },        _formatStr: function(str) {            if (this.options.caseSensitive) {                return str;            }            return $.trim(str.toLowerCase());        },        _effectExists: function(name) {            return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name])));        },        createTag: function(value, additionalClass, duringInitialization) {            var that = this;            value = $.trim(value);            if(this.options.preprocessTag) {                value = this.options.preprocessTag(value);            }            if (value === '') {                return false;            }            if (!this.options.allowDuplicates && !this._isNew(value)) {                var existingTag = this._findTagByLabel(value);                if (this._trigger('onTagExists', null, {                    existingTag: existingTag,                    duringInitialization: duringInitialization                }) !== false) {                    if (this._effectExists('highlight')) {                        existingTag.effect('highlight');                    }                }                return false;            }            if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) {                this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization});                return false;            }            var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);            // Create tag.            var tag = $('<li></li>')                .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')                .addClass(additionalClass)                .append(label);            if (this.options.readOnly){                tag.addClass('tagit-choice-read-only');            } else {                tag.addClass('tagit-choice-editable');                // Button for removing the tag.                var removeTagIcon = $('<span></span>')                    .addClass('ui-icon ui-icon-close');                var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X                    .addClass('tagit-close')                    .append(removeTagIcon)                    .click(function(e) {                        // Removes a tag when the little 'x' is clicked.                        that.removeTag(tag);                    });                tag.append(removeTag);            }            // Unless options.singleField is set, each tag has a hidden input field inline.            if (!this.options.singleField) {                var escapedValue = label.html();                tag.append('<input type="hidden" value="' + escapedValue + '" name="' + this.options.fieldName + '" class="tagit-hidden-field" />');            }            if (this._trigger('beforeTagAdded', null, {                tag: tag,                tagLabel: this.tagLabel(tag),                duringInitialization: duringInitialization            }) === false) {                return;            }            if (this.options.singleField) {                var tags = this.assignedTags();                tags.push(value);                this._updateSingleTagsField(tags);            }            // DEPRECATED.            this._trigger('onTagAdded', null, tag);            this.tagInput.val('');            // Insert tag.            this.tagInput.parent().before(tag);            this._trigger('afterTagAdded', null, {                tag: tag,                tagLabel: this.tagLabel(tag),                duringInitialization: duringInitialization            });            if (this.options.showAutocompleteOnFocus && !duringInitialization) {                setTimeout(function () { that._showAutocomplete(); }, 0);            }        },        removeTag: function(tag, animate) {            animate = typeof animate === 'undefined' ? this.options.animate : animate;            tag = $(tag);            // DEPRECATED.            this._trigger('onTagRemoved', null, tag);            if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) {                return;            }            if (this.options.singleField) {                var tags = this.assignedTags();                var removedTagLabel = this.tagLabel(tag);                tags = $.grep(tags, function(el){                    return el != removedTagLabel;                });                this._updateSingleTagsField(tags);            }            if (animate) {                tag.addClass('removed'); // Excludes this tag from _tags.                var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast'];                var thisTag = this;                hide_args.push(function() {                    tag.remove();                    thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)});                });                tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue();            } else {                tag.remove();                this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)});            }        },        removeTagByLabel: function(tagLabel, animate) {            var toRemove = this._findTagByLabel(tagLabel);            if (!toRemove) {                throw "No such tag exists with the name '" + tagLabel + "'";            }            this.removeTag(toRemove, animate);        },        removeAll: function() {            // Removes all tags.            var that = this;            this._tags().each(function(index, tag) {                that.removeTag(tag, false);            });        }    });})(jQuery);


使用方法很简单的就不多说了,下面是相关网址介绍:

https://github.com/aehlke/tag-it


需要配置 jQuery-ui 一起使用的:

http://jqueryui.com/download/






效果:

0 0
原创粉丝点击