PageSourceSearch

https://www.harikulscience.com/modules/flexi/js/jquery.tag-it/jquery.tag-it.js

js harikulscience.com collected 2026-10-07 22:38:46 UTC 22,517 bytes, 551 lines download raw bytes

1/*
2* jQuery UI Tag-it!
3*
4* @version v2.0 (06/2011)
5*
6* Copyright 2011, Levy Carneiro Jr.
7* Released under the MIT license.
8* http://aehlke.github.com/tag-it/LICENSE
9*
10* Homepage:
11*   http://aehlke.github.com/tag-it/
12*
13* Authors:
14*   Levy Carneiro Jr.
15*   Martin Rehfeld
16*   Tobias Schmidt
17*   Skylar Challand
18*   Alex Ehlke
19*
20* Maintainer:
21*   Alex Ehlke - Twitter: @aehlke
22*
23* Dependencies:
24*   jQuery v1.4+
25*   jQuery UI v1.8+
26*/
27(function($) {
28
29    $.widget('ui.tagit', {
30        options: {
31            allowDuplicates   : false,
32            caseSensitive     : true,
33            fieldName         : 'tags',
34            placeholderText   : null,   // Sets `placeholder` attr on input field.
35            readOnly          : false,  // Disables editing.
36            removeConfirmation: false,  // Require confirmation to remove tags.
37            tagLimit          : null,   // Max number of tags allowed (null for unlimited).
38
39            // Used for autocomplete, unless you override `autocomplete.source`.
40            availableTags     : [],
41
42            // Use to override or add any options to the autocomplete widget.
43            //
44            // By default, autocomplete.source will map to availableTags,
45            // unless overridden.
46            autocomplete: {},
47
48            // Shows autocomplete before the user even types anything.
49            showAutocompleteOnFocus: false,
50
51            // When enabled, quotes are unneccesary for inputting multi-word tags.
52            allowSpaces: false,
53
54            // The below options are for using a single field instead of several
55            // for our form values.
56            //
57            // When enabled, will use a single hidden field for the form,
58            // rather than one per tag. It will delimit tags in the field
59            // with singleFieldDelimiter.
60            //
61            // The easiest way to use singleField is to just instantiate tag-it
62            // on an INPUT element, in which case singleField is automatically
63            // set to true, and singleFieldNode is set to that element. This
64            // way, you don't need to fiddle with these options.
65            singleField: false,
66
67            // This is just used when preloading data from the field, and for
68            // populating the field with delimited tags as the user adds them.
69            singleFieldDelimiter: ',',
70
71            // Set this to an input DOM node to use an existing form field.
72            // Any text in it will be erased on init. But it will be
73            // populated with the text of tags as they are created,
74            // delimited by singleFieldDelimiter.
75            //
76            // If this is not set, we create an input node for it,
77            // with the name given in settings.fieldName.
78            singleFieldNode: null,
79
80            // Whether to animate tag removals or not.
81            animate: true,
82
83            // Optionally set a tabindex attribute on the input that gets
84            // created for tag-it.
85            tabIndex: null,
86
87            // Event callbacks.
88            beforeTagAdded      : null,
89            afterTagAdded       : null,
90
91            beforeTagRemoved    : null,
92            afterTagRemoved     : null,
93
94            onTagClicked        : null,
95            onTagLimitExceeded  : null,
96
97
98            // DEPRECATED:
99            //
100            // /!\ These event callbacks are deprecated and WILL BE REMOVED at some
101            // point in the future. They're here for backwards-compatibility.
102            // Use the above before/after event callbacks instead.
103            onTagAdded  : null,
104            onTagRemoved: null,
105            // `autocomplete.source` is the replacement for tagSource.
106            tagSource: null
107            // Do not use the above deprecated options.
108        },
109
110        _create: function() {
111            // for handling static scoping inside callbacks
112            var that = this;
113
114            // There are 2 kinds of DOM nodes this widget can be instantiated on:
115            //     1. UL, OL, or some element containing either of these.
116            //     2. INPUT, in which case 'singleField' is overridden to true,
117            //        a UL is created and the INPUT is hidden.
118            if (this.element.is('input')) {
119                this.tagList = $('<ul></ul>').insertAfter(this.element);
120                this.options.singleField = true;
121                this.options.singleFieldNode = this.element;
122                this.element.css('display', 'none');
123            } else {
124                this.tagList = this.element.find('ul, ol').andSelf().last();
125            }
126
127            this.tagInput = $('<input type="text" />').addClass('ui-widget-content');
128
129            if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled');
130
131            if (this.options.tabIndex) {
132                this.tagInput.attr('tabindex', this.options.tabIndex);
133            }
134
135            if (this.options.placeholderText) {
136                this.tagInput.attr('placeholder', this.options.placeholderText);
137            }
138
139            if (!this.options.autocomplete.source) {
140                this.options.autocomplete.source = function(search, showChoices) {
141                    var filter = search.term.toLowerCase();
142                    var choices = $.grep(this.options.availableTags, function(element) {
143                        // Only match autocomplete options that begin with the search term.
144                        // (Case insensitive.)
145                        return (element.toLowerCase().indexOf(filter) === 0);
146                    });
147                    showChoices(this._subtractArray(choices, this.assignedTags()));
148                };
149            }
150
151            if (this.options.showAutocompleteOnFocus) {
152                this.tagInput.focus(function(event, ui) {
153                    that._showAutocomplete();
154                });
155
156                if (typeof this.options.autocomplete.minLength === 'undefined') {
157                    this.options.autocomplete.minLength = 0;
158                }
159            }
160
161            // Bind autocomplete.source callback functions to this context.
162            if ($.isFunction(this.options.autocomplete.source)) {
163                this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this);
164            }
165
166            // DEPRECATED.
167            if ($.isFunction(this.options.tagSource)) {
168                this.options.tagSource = $.proxy(this.options.tagSource, this);
169            }
170
171            this.tagList
172                .addClass('tagit')
173                .addClass('ui-widget ui-widget-content ui-corner-all')
174                // Create the input field.
175                .append($('<li class="tagit-new"></li>').append(this.tagInput))
176                .click(function(e) {
177                    var target = $(e.target);
178                    if (target.hasClass('tagit-label')) {
179                        var tag = target.closest('.tagit-choice');
180                        if (!tag.hasClass('removed')) {
181                            that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)});
182                        }
183                    } else {
184                        // Sets the focus() to the input field, if the user
185                        // clicks anywhere inside the UL. This is needed
186                        // because the input field needs to be of a small size.
187                        that.tagInput.focus();
188                    }
189                });
190
191            // Single field support.
192            var addedExistingFromSingleFieldNode = false;
193            if (this.options.singleField) {
194                if (this.options.singleFieldNode) {
195                    // Add existing tags from the input field.
196                    var node = $(this.options.singleFieldNode);
197                    var tags = node.val().split(this.options.singleFieldDelimiter);
198                    node.val('');
199                    $.each(tags, function(index, tag) {
200                        that.createTag(tag, null, true);
201                        addedExistingFromSingleFieldNode = true;
202                    });
203                } else {
204                    // Create our single field input after our list.
205                    this.options.singleFieldNode = $('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');
206                    this.tagList.after(this.options.singleFieldNode);
207                }
208            }
209
210            // Add existing tags from the list, if any.
211            if (!addedExistingFromSingleFieldNode) {
212                this.tagList.children('li').each(function() {
213                    if (!$(this).hasClass('tagit-new')) {
214                        that.createTag($(this).text(), $(this).attr('class'), true);
215                        $(this).remove();
216                    }
217                });
218            }
219
220            // Events.
221            this.tagInput
222                .keydown(function(event) {
223                    // Backspace is not detected within a keypress, so it must use keydown.
224                    if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {
225                        var tag = that._lastTag();
226                        if (!that.options.removeConfirmation || tag.hasClass('remove')) {
227                            // When backspace is pressed, the last tag is deleted.
228                            that.removeTag(tag);
229                        } else if (that.options.removeConfirmation) {
230                            tag.addClass('remove ui-state-highlight');
231                        }
232                    } else if (that.options.removeConfirmation) {
233                        that._lastTag().removeClass('remove ui-state-highlight');
234                    }
235
236                    // Comma/Space/Enter are all valid delimiters for new tags,
237                    // except when there is an open quote or if setting allowSpaces = true.
238                    // Tab will also create a tag, unless the tag input is empty,
239                    // in which case it isn't caught.
240                    if (
241                        // NOTE: modified by VelaEasy on 2013-05-09
242                        // Google Chrome returns the same event.keyCode for ',' (comma) and 'ม' (mor-mar in Thai) in 'keydown' event.
243                        // And this prevents creating a tag with 'ม', so let's use 'keypress' here to fix this problem.
244
245                        /* event.which === $.ui.keyCode.COMMA || */
246                        event.which === $.ui.keyCode.ENTER ||
247                        (
248                            event.which == $.ui.keyCode.TAB &&
249                            that.tagInput.val() !== ''
250                        ) ||
251                        (
252                            event.which == $.ui.keyCode.SPACE &&
253                            that.options.allowSpaces !== true &&
254                            (
255                                $.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
256                                (
257                                    $.trim(that.tagInput.val()).charAt(0) == '"' &&
258                                    $.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' &&
259                                    $.trim(that.tagInput.val()).length - 1 !== 0
260                                )
261                            )
262                        )
263                    ) {
264                        // Enter submits the form if there's no text in the input.
265                        if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) {
266                            event.preventDefault();
267                        }
268
269                        that.createTag(that._cleanedInput());
270
271                        // The autocomplete doesn't close automatically when TAB is pressed.
272                        // So let's ensure that it closes.
273                        that.tagInput.autocomplete('close');
274                    }
275                }).keypress(function(event) {
276                    // NOTE: modified by VelaEasy on 2013-05-09
277                    // Google Chrome returns the same event.keyCode for ',' (comma) and 'ม' (mor-mar in Thai) in 'keydown' event.
278                    // And this prevents creating a tag with 'ม', so let's use 'keypress' here to fix this problem.
279                    if (String.fromCharCode(event.charCode) == ',') {
280                        that.createTag(that._cleanedInput());
281                        event.preventDefault();
282                    }
283                }).blur(function(e){
284                    // Create a tag when the element loses focus.
285                    // If autocomplete is enabled and suggestion was clicked, don't add it.
286                    if (!that.tagInput.data('autocomplete-open')) {
287                        that.createTag(that._cleanedInput());
288                    }
289                });
290
291            // Autocomplete.
292            if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) {
293                var autocompleteOptions = {
294                    select: function(event, ui) {
295                        that.createTag(ui.item.value);
296                        // Preventing the tag input to be updated with the chosen value.
297                        return false;
298                    }
299                };
300                $.extend(autocompleteOptions, this.options.autocomplete);
301
302                // tagSource is deprecated, but takes precedence here since autocomplete.source is set by default,
303                // while tagSource is left null by default.
304                autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source;
305
306                this.tagInput.autocomplete(autocompleteOptions).bind('autocompleteopen', function(event, ui) {
307                    that.tagInput.data('autocomplete-open', true);
308                }).bind('autocompleteclose', function(event, ui) {
309                    that.tagInput.data('autocomplete-open', false)
310                });
311            }
312        },
313
314        _cleanedInput: function() {
315            // Returns the contents of the tag input, cleaned and ready to be passed to createTag
316            return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1'));
317        },
318
319        _lastTag: function() {
320            return this.tagList.find('.tagit-choice:last:not(.removed)');
321        },
322
323        _tags: function() {
324            return this.tagList.find('.tagit-choice:not(.removed)');
325        },
326
327        assignedTags: function() {
328            // Returns an array of tag string values
329            var that = this;
330            var tags = [];
331            if (this.options.singleField) {
332                tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
333                if (tags[0] === '') {
334                    tags = [];
335                }
336            } else {
337                this._tags().each(function() {
338                    tags.push(that.tagLabel(this));
339                });
340            }
341            return tags;
342        },
343
344        _updateSingleTagsField: function(tags) {
345            // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
346            $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change');
347        },
348
349        _subtractArray: function(a1, a2) {
350            var result = [];
351            for (var i = 0; i < a1.length; i++) {
352                if ($.inArray(a1[i], a2) == -1) {
353                    result.push(a1[i]);
354                }
355            }
356            return result;
357        },
358
359        tagLabel: function(tag) {
360            // Returns the tag's string label.
361            if (this.options.singleField) {
362                return $(tag).find('.tagit-label:first').text();
363            } else {
364                return $(tag).find('input:first').val();
365            }
366        },
367
368        _showAutocomplete: function() {
369            this.tagInput.autocomplete('search', '');
370        },
371
372        _findTagByLabel: function(name) {
373            var that = this;
374            var tag = null;
375            this._tags().each(function(i) {
376                if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) {
377                    tag = $(this);
378                    return false;
379                }
380            });
381            return tag;
382        },
383
384        _isNew: function(name) {
385            return !this._findTagByLabel(name);
386        },
387
388        _formatStr: function(str) {
389            if (this.options.caseSensitive) {
390                return str;
391            }
392            return $.trim(str.toLowerCase());
393        },
394
395        _effectExists: function(name) {
396            return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name])));
397        },
398
399        createTag: function(value, additionalClass, duringInitialization) {
400            var that = this;
401
402            value = $.trim(value);
403
404            if(this.options.preprocessTag) {
405                value = this.options.preprocessTag(value);
406            }
407
408            if (value === '') {
409                return false;
410            }
411
412            if (!this.options.allowDuplicates && !this._isNew(value)) {
413                var existingTag = this._findTagByLabel(value);
414                if (this._trigger('onTagExists', null, {
415                    existingTag: existingTag,
416                    duringInitialization: duringInitialization
417                }) !== false) {
418                    if (this._effectExists('highlight')) {
419                        existingTag.effect('highlight');
420                    }
421                }
422                return false;
423            }
424
425            if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) {
426                this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization});
427                return false;
428            }
429
430            var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
431
432            // Create tag.
433            var tag = $('<li></li>')
434                .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
435                .addClass(additionalClass)
436                .append(label);
437
438            if (this.options.readOnly){
439                tag.addClass('tagit-choice-read-only');
440            } else {
441                tag.addClass('tagit-choice-editable');
442                // Button for removing the tag.
443                var removeTagIcon = $('<span></span>')
444                    .addClass('ui-icon ui-icon-close');
445                var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
446                    .addClass('tagit-close')
447                    .append(removeTagIcon)
448                    .click(function(e) {
449                        // Removes a tag when the little 'x' is clicked.
450                        that.removeTag(tag);
451                    });
452                tag.append(removeTag);
453            }
454
455            // Unless options.singleField is set, each tag has a hidden input field inline.
456            if (!this.options.singleField) {
457                var escapedValue = label.html();
458                tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.fieldName + '" />');
459            }
460
461            if (this._trigger('beforeTagAdded', null, {
462                tag: tag,
463                tagLabel: this.tagLabel(tag),
464                duringInitialization: duringInitialization
465            }) === false) {
466                return;
467            }
468
469            if (this.options.singleField) {
470                var tags = this.assignedTags();
471                tags.push(value);
472                this._updateSingleTagsField(tags);
473            }
474
475            // DEPRECATED.
476            this._trigger('onTagAdded', null, tag);
477
478            this.tagInput.val('');
479
480            // Insert tag.
481            this.tagInput.parent().before(tag);
482
483            this._trigger('afterTagAdded', null, {
484                tag: tag,
485                tagLabel: this.tagLabel(tag),
486                duringInitialization: duringInitialization
487            });
488
489            if (this.options.showAutocompleteOnFocus && !duringInitialization) {
490                setTimeout(function () { that._showAutocomplete(); }, 0);
491            }
492        },
493
494        removeTag: function(tag, animate) {
495            animate = typeof animate === 'undefined' ? this.options.animate : animate;
496
497            tag = $(tag);
498
499            // DEPRECATED.
500            this._trigger('onTagRemoved', null, tag);
501
502            if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) {
503                return;
504            }
505
506            if (this.options.singleField) {
507                var tags = this.assignedTags();
508                var removedTagLabel = this.tagLabel(tag);
509                tags = $.grep(tags, function(el){
510                    return el != removedTagLabel;
511                });
512                this._updateSingleTagsField(tags);
513            }
514
515            if (animate) {
516                tag.addClass('removed'); // Excludes this tag from _tags.
517                var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast'];
518
519                var thisTag = this;
520                hide_args.push(function() {
521                    tag.remove();
522                    thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)});
523                });
524
525                tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue();
526            } else {
527                tag.remove();
528                this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)});
529            }
530
531        },
532
533        removeTagByLabel: function(tagLabel, animate) {
534            var toRemove = this._findTagByLabel(tagLabel);
535            if (!toRemove) {
536                throw "No such tag exists with the name '" + tagLabel + "'";
537            }
538            this.removeTag(toRemove, animate);
539        },
540
541        removeAll: function() {
542            // Removes all tags.
543            var that = this;
544            this._tags().each(function(index, tag) {
545                that.removeTag(tag, false);
546            });
547        }
548
549    });
550})(jQuery);
551

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.