PageSourceSearch

https://samainsider.org.za/lib/pkp/js/lib/jquery/plugins/jquery.tag-it.js

js samainsider.org.za collected 2026-10-03 02:07:14 UTC 12,452 bytes, 400 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			itemName		  : 'item',
32			fieldName		 : 'tags',
33			availableTags	 : [],
34			tagSource		 : null,
35			removeConfirmation: false,
36			caseSensitive	 : true,
37
38			// When enabled, quotes are not neccesary
39			// for inputting multi-word tags.
40			allowSpaces: false,
41
42			// Whether to animate tag removals or not.
43			animate: true,
44
45			// The below options are for using a single field instead of several
46			// for our form values.
47			//
48			// When enabled, will use a single hidden field for the form,
49			// rather than one per tag. It will delimit tags in the field
50			// with singleFieldDelimiter.
51			//
52			// The easiest way to use singleField is to just instantiate tag-it
53			// on an INPUT element, in which case singleField is automatically
54			// set to true, and singleFieldNode is set to that element. This
55			// way, you don't need to fiddle with these options.
56			singleField: false,
57
58			singleFieldDelimiter: ',',
59
60			// Set this to an input DOM node to use an existing form field.
61			// Any text in it will be erased on init. But it will be
62			// populated with the text of tags as they are created,
63			// delimited by singleFieldDelimiter.
64			//
65			// If this is not set, we create an input node for it,
66			// with the name given in settings.fieldName,
67			// ignoring settings.itemName.
68			singleFieldNode: null,
69
70			// Optionally set a tabindex attribute on the input that gets
71			// created for tag-it.
72			tabIndex: null,
73
74
75			// Event callbacks.
76			onTagAdded  : null,
77			onTagRemoved: null,
78			onTagClicked: null
79		},
80
81
82		_create: function() {
83			// for handling static scoping inside callbacks
84			var that = this;
85
86			// There are 2 kinds of DOM nodes this widget can be instantiated on:
87			//	 1. UL, OL, or some element containing either of these.
88			//	 2. INPUT, in which case 'singleField' is overridden to true,
89			//		a UL is created and the INPUT is hidden.
90			if (this.element.is('input')) {
91				this.tagList = $('<ul></ul>').insertAfter(this.element);
92				this.options.singleField = true;
93				this.options.singleFieldNode = this.element;
94				this.element.css('display', 'none');
95			} else {
96				this.tagList = this.element.find('ul, ol').andSelf().last();
97			}
98
99			this._tagInput = $('<input type="text" />').addClass('ui-widget-content');
100			if (this.options.tabIndex) {
101				this._tagInput.attr('tabindex', this.options.tabIndex);
102			}
103
104			this.options.tagSource = this.options.tagSource || function(search, showChoices) {
105				var filter = search.term.toLowerCase();
106				var choices = $.grep(this.options.availableTags, function(element) {
107					// Only match autocomplete options that begin with the search term.
108					// (Case insensitive.)
109					return (element.toLowerCase().indexOf(filter) === 0);
110				});
111				showChoices(this._subtractArray(choices, this.assignedTags()));
112			};
113
114			// Bind tagSource callback functions to this context.
115			if ($.isFunction(this.options.tagSource)) {
116				this.options.tagSource = $.proxy(this.options.tagSource, this);
117			}
118
119			this.tagList
120				.addClass('tagit')
121				.addClass('ui-widget ui-widget-content ui-corner-all')
122				// Create the input field.
123				.append($('<li class="tagit-new"></li>').append(this._tagInput))
124				.click(function(e) {
125					var target = $(e.target);
126					if (target.hasClass('tagit-label')) {
127						that._trigger('onTagClicked', e, target.closest('.tagit-choice'));
128					} else {
129						// Sets the focus() to the input field, if the user
130						// clicks anywhere inside the UL. This is needed
131						// because the input field needs to be of a small size.
132						that._tagInput.focus();
133					}
134				});
135
136			// Add existing tags from the list, if any.
137			this.tagList.children('li').each(function() {
138				if (!$(this).hasClass('tagit-new')) {
139					that.createTag($(this).html(), $(this).attr('class'), true);
140					$(this).remove();
141				}
142			});
143
144			// Single field support.
145			if (this.options.singleField) {
146				if (this.options.singleFieldNode) {
147					// Add existing tags from the input field.
148					var node = $(this.options.singleFieldNode);
149					var tags = node.val().split(this.options.singleFieldDelimiter);
150					node.val('');
151					$.each(tags, function(index, tag) {
152						that.createTag(tag, null, false);
153					});
154				} else {
155					// Create our single field input after our list.
156					this.options.singleFieldNode = this.tagList.after('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');
157				}
158			}
159
160			// Events.
161			this._tagInput
162				.keydown(function(event) {
163					// Backspace is not detected within a keypress, so it must use keydown.
164					if (event.which == $.ui.keyCode.BACKSPACE && that._tagInput.val() === '') {
165						var tag = that._lastTag();
166						if (!that.options.removeConfirmation || tag.hasClass('remove')) {
167							// When backspace is pressed, the last tag is deleted.
168							that.removeTag(tag);
169						} else if (that.options.removeConfirmation) {
170							tag.addClass('remove ui-state-highlight');
171						}
172					} else if (that.options.removeConfirmation) {
173						that._lastTag().removeClass('remove ui-state-highlight');
174					}
175
176					// Comma/Space/Enter are all valid delimiters for new tags,
177					// except when there is an open quote or if setting allowSpaces = true.
178					// Tab will also create a tag, unless the tag input is empty, in which case it isn't caught.
179					if (
180						event.which == $.ui.keyCode.COMMA ||
181						event.which == $.ui.keyCode.ENTER ||
182						(
183							event.which == $.ui.keyCode.TAB &&
184							that._tagInput.val() !== ''
185						) ||
186						(
187							event.which == $.ui.keyCode.SPACE &&
188							that.options.allowSpaces !== true &&
189							(
190								$.trim(that._tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
191								(
192									$.trim(that._tagInput.val()).charAt(0) == '"' &&
193									$.trim(that._tagInput.val()).charAt($.trim(that._tagInput.val()).length - 1) == '"' &&
194									$.trim(that._tagInput.val()).length - 1 !== 0
195								)
196							)
197						)
198					) {
199						event.preventDefault();
200						that.createTag(that._cleanedInput(), null, false);
201
202						// The autocomplete doesn't close automatically when TAB is pressed.
203						// So let's ensure that it closes.
204						that._tagInput.autocomplete('close');
205					}
206				}).blur(function(e){
207					// Create a tag when the element loses focus (unless it's empty).
208					that.createTag(that._cleanedInput(), null, false);
209				});
210
211
212			// Autocomplete.
213			this._autocompleteVal = null;
214			this._selectAction = function(event, ui) {
215				if (that._tagInput.val() == that._autocompleteVal) {
216					that.createTag(that._autocompleteVal, null, true);
217					// Preventing the tag input to be updated with the chosen value.
218					that.tagList.children('.tagit-new').children('input').val('');
219					return false;
220				}
221			//	if (that._tagInput.val() === '') {
222
223			//	}
224			}
225			if (this.options.availableTags || this.options.tagSource) {
226				this._tagInput.autocomplete({
227					source: this.options.tagSource,
228					close: this._selectAction,
229					select: this._selectAction,
230					minLength: 2,
231					autoFocus: true,
232					focus: function(event, ui) {
233						that._autocompleteVal = ui.item.value;
234						$(this).val(that._unescapeHTML(ui.item.value));
235						return false;
236					}
237				});
238			}
239		},
240
241		_cleanedInput: function() {
242			// Returns the contents of the tag input, cleaned and ready to be passed to createTag
243			return $.trim(this._tagInput.val().replace(/^"(.*)"$/, '$1'));
244		},
245
246		_lastTag: function() {
247			return this.tagList.children('.tagit-choice:last');
248		},
249
250		assignedTags: function() {
251			// Returns an array of tag string values
252			var that = this;
253			var tags = [];
254			if (this.options.singleField) {
255				tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
256				if (tags[0] === '') {
257					tags = [];
258				}
259			} else {
260				this.tagList.children('.tagit-choice').each(function() {
261					tags.push(that.tagLabel(this));
262				});
263			}
264			return tags;
265		},
266
267		_updateSingleTagsField: function(tags) {
268			// Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
269			$(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter));
270		},
271
272		_subtractArray: function(a1, a2) {
273			var result = [];
274			for (var i = 0; i < a1.length; i++) {
275				if ($.inArray(a1[i], a2) == -1) {
276					result.push(a1[i]);
277				}
278			}
279			return result;
280		},
281
282		tagLabel: function(tag) {
283			// Returns the tag's string label.
284			if (this.options.singleField) {
285				return $(tag).children('.tagit-label').text();
286			} else {
287				return $(tag).children('input').val();
288			}
289		},
290
291		_isNew: function(value) {
292			var that = this;
293			var isNew = true;
294			this.tagList.children('.tagit-choice').each(function(i) {
295				if (that._formatStr(urlEncode(value)) == that._formatStr(that.tagLabel(this))) {
296					isNew = false;
297					return false;
298				}
299			});
300			return isNew;
301		},
302
303		_formatStr: function(str) {
304			if (this.options.caseSensitive) {
305				return str;
306			}
307			return $.trim(str.toLowerCase());
308		},
309
310		_unescapeHTML: function(str) {
311			return $('<div/>').html(str).text();
312		},
313
314		createTag: function(value, additionalClass, unescape) {
315			// Unescape potentially HTML-escaped value
316			if(unescape) value = this._unescapeHTML(value);
317
318			var that = this;
319			// Automatically trims the value of leading and trailing whitespace.
320			value = $.trim(value);
321
322			if (!this._isNew(value) || value === '') {
323				return false;
324			}
325
326			var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
327
328			// Create tag.
329			var tag = $('<li></li>')
330				.addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
331				.addClass(additionalClass)
332				.append(label);
333
334			// Button for removing the tag.
335			var removeTagIcon = $('<span></span>')
336				.addClass('ui-icon ui-icon-close');
337			var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
338				.addClass('tagit-close')
339				.append(removeTagIcon)
340				.click(function(e) {
341					// Removes a tag when the little 'x' is clicked.
342					that.removeTag(tag);
343				});
344			tag.append(removeTag);
345
346			// Unless options.singleField is set, each tag has a hidden input field inline.
347			if (this.options.singleField) {
348				var tags = this.assignedTags();
349				tags.push(value);
350				this._updateSingleTagsField(tags);
351			} else {
352				var escapedValue = urlEncode(value);
353				tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.itemName + '[' + this.options.fieldName + '][]" />');
354			}
355
356			this._trigger('onTagAdded', null, tag);
357
358			// Cleaning the input.
359			this._tagInput.val('');
360
361			// insert tag
362			this._tagInput.parent().before(tag);
363		},
364
365		removeTag: function(tag, animate) {
366			animate = animate || this.options.animate;
367
368			tag = $(tag);
369
370			this._trigger('onTagRemoved', null, tag);
371
372			if (this.options.singleField) {
373				var tags = this.assignedTags();
374				var removedTagLabel = this.tagLabel(tag);
375				tags = $.grep(tags, function(el){
376					return el != removedTagLabel;
377				});
378				this._updateSingleTagsField(tags);
379			}
380			// Animate the removal.
381			if (animate) {
382				tag.fadeOut('fast').hide('blind', {direction: 'horizontal'}, 'fast', function(){
383					tag.remove();
384				}).dequeue();
385			} else {
386				tag.remove();
387			}
388		},
389
390		removeAll: function() {
391			// Removes all tags.
392			var that = this;
393			this.tagList.children('.tagit-choice').each(function(index, tag) {
394				that.removeTag(tag, false);
395			});
396		}
397
398	});
399
400})(jQuery);

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.