I am looking for any possible improvements on this tagging widget.
I ask for special attention to be paid to my blur handler in the event of a autocomplete option being specified but any optimizations or critiques are welcome.
Demo: http://webspirited.com/tagit Js File: http://webspirited.com/tagit/js/tagit.js Css File: http://webspirited.com/tagit/css/tagit.css
Code:
(function($) {
$.widget("ui.tagit", {
// default options
options: {
tagSource: [],
triggerKeys: ['enter', 'space', 'comma', 'tab'],
initialTags: [],
minLength: 1
},
_keys: {
backspace: 8,
enter: 13,
space: 32,
comma: 44,
tab: 9
},
//initialization function
_create: function() {
var self = this;
this.tagsArray = [];
//store reference to the ul
this.element = this.element;
//add class "tagit" for theming
this.element.addClass("tagit");
//add any initial tags added through html to the array
this.element.children('li').each(function() {
self.options.initialTags.push($(this).text());
});
//add the html input
this.element.html('<li class="tagit-new"><input class="tagit-input" type="text" /></li>');
this.input = this.element.find(".tagit-input");
//setup click handler
$(this.element).click(function(e) {
if (e.target.tagName == 'A') {
// Removes a tag when the little 'x' is clicked.
$(e.target).parent().remove();
self._popTag();
}
else {
self.input.focus();
}
});
//setup autcomplete handler
this.options.appendTo = this.element;
this.options.source = this.options.tagSource;
this.options.select = function(event, ui) {
self._removeTag();
self._addTag(ui.item.value);
return false;
}
this.input.autocomplete(this.options);
//setup keydown handler
this.input.keydown(function(e) {
var lastLi = self.element.children(".tagit-choice:last");
if (e.which == self._keys.backspace)
return self._backspace(lastLi);
if (self._isInitKey(e.which)) {
e.preventDefault();
if ($(this).val().length >= self.options.minLength)
self._addTag($(this).val());
}
if (lastLi.hasClass('selected'))
lastLi.removeClass('selected');
self.lastKey = e.which;
});
//setup blur handler
this.input.blur(function(e) {
self._addTag($(this).val());
$(this).val('');
return false;
});
//define missing trim function for strings
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
};
this._initialTags();
},
_popTag: function() {
return this.tagsArray.pop();
}
,
_addTag: function(value) {
this.input.val("");
value = value.replace(/,+$/, "");
value = value.trim();
if (value == "" || this._exists(value))
return false;
var tag = "";
tag = '<li class="tagit-choice">' + value + '<a class="tagit-close">x</a></li>';
$(tag).insertBefore(this.input.parent());
this.input.val("");
this.tagsArray.push(value);
}
,
_exists: function(value) {
if (this.tagsArray.length == 0 || $.inArray(value, this.tagsArray) == -1)
return false;
return true;
}
,
_isInitKey : function(keyCode) {
var keyName = "";
for (var key in this._keys)
if (this._keys[key] == keyCode)
keyName = key
if ($.inArray(keyName, this.options.triggerKeys) != -1)
return true;
return false;
}
,
_removeTag: function() {
this._popTag();
this.element.children(".tagit-choice:last").remove();
}
,
_backspace: function(li) {
if (this.input.val() == "") {
// When backspace is pressed, the last tag is deleted.
if (this.lastKey == this._keys.backspace) {
this._popTag();
li.remove();
this.lastKey = null;
} else {
li.addClass('selected');
this.lastKey = this._keys.backspace;
}
}
return true;
}
,
_initialTags: function() {
if (this.options.initialTags.length != 0) {
for (var i in this.options.initialTags)
if (!this._exists(this.options.initialTags[i]))
this._addTag(this.options.initialTags[i]);
}
}
,
tags: function() {
return this.tagsArray;
}
,
destroy: function() {
$.Widget.prototype.destroy.apply(this, arguments); // default destroy
this.tagsArray = [];
}
});
})(jQuery);