zoukankan      html  css  js  c++  java
  • 改造jQuery-Tagit 插件支持中文全角的逗号和空格

    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/






    效果:

  • 相关阅读:
    delphi RTTI 四 获取类属性列表
    delphi 控件编辑器
    delphi 属性编辑器
    des加密delphi与c#
    delphi c#语法转换
    电脑组装DIY
    .net DLL 注册 regasm delphi调用
    自助机调试过程
    delphi面向对象 继承窗体
    E2040 Declaration terminated incorrectly
  • 原文地址:https://www.cnblogs.com/zhouzme/p/5758423.html
Copyright © 2011-2022 走看看