/* * INFORMATION * --------------------------- * Owner: jquery.webspirited.com * Developer: Matthew Hailwood * --------------------------- */ (function ($) { $.widget("ui.tagit", { // default options options:{ //Maps directly to the jQuery-ui Autocomplete option tagSource:[], //What keys should trigger the completion of a tag triggerKeys:['enter', 'space', 'comma', 'tab'], //array method for setting initial tags initialTags:[], //minimum length of tags minLength:1, //should an html select be rendered to allow for normal form submission select:false, //if false only tags from `tagSource` are able to be entered allowNewTags:true, //should tag and Tag be treated as identical caseSensitive:false, //should tags be drag-and-drop sortable? //true: entire tag is draggable //'handle': a handle is rendered which is draggable sortable:false, //color to highlight text when a duplicate tag is entered highlightOnExistColor:'#0F0', //empty search on focus emptySearch:true, //callback function for when tags are changed //tagValue: value of tag that was changed //action e.g. removed, added, sorted tagsChanged:function (tagValue, action, element) { ; }, maxTags:undefined, //should 'paste' event trigger 'blur', thus potentially adding a new tag // (true for backwards compatibility) blurOnPaste:true }, _splitAt:/\ |,/g, _existingAtIndex:0, _keys:{ backspace:[8], enter:[13], space:[32], comma:[44, 188], tab:[9] }, _sortable:{ sorting:-1 }, //initialization function _create:function () { var self = this; this.tagsArray = []; this.timer = null; //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 () { var tag = $(this); var tagValue = tag.attr('tagValue') || tag.data('value'); self.options.initialTags.push({label:tag.text(), value:(tagValue ? tagValue : tag.text())}); }); //setup split according to the trigger keys self._splitAt = null; if ($.inArray('space', self.options.triggerKeys) > 0 && $.inArray('comma', self.options.triggerKeys) > 0) self._splitAt = /\ |,/g; else if ($.inArray('space', self.options.triggerKeys) > 0) self._splitAt = /\ /g; else if ($.inArray('comma', self.options.triggerKeys) > 0) self._splitAt = /,/g; //add the html input this.element.html('
'); this.input = this.element.find(".tagit-input"); //setup click handler $(this.element).click(function (e) { if ($(e.target).hasClass('tagit-close')) { // Removes a tag when the little 'x' is clicked. var parent = $(e.target).parent(); var tag = self.tagsArray[parent.index()]; tag.element.remove(); self._popTag(tag); } else { self.input.focus(); if (self.options.emptySearch && $(e.target).hasClass('tagit-input') && self.input.val() == '' && self.input.autocomplete != undefined) { self.input.autocomplete('search'); } } }); //setup autocomplete handler var os = this.options.select; this.options.appendTo = this.element; this.options.source = this.options.tagSource; this.options.select = function (event, ui) { self.input.data('autoCompleteTag', true); clearTimeout(self.timer); if (self.options.maxTags !== undefined && self.tagsArray.length == self.options.maxTags) { self.input.val(""); } else { if (ui.item.label === undefined) self._addTag(ui.item.value); else self._addTag(ui.item.label, ui.item.value); } return false; }, this.options.focus = function (event, ui) { if (ui.item.label !== undefined && /^key/.test(event.originalEvent.originalEvent.type)) { self.input.val(ui.item.label); self.input.data('value', ui.item.value); return false; } }; this.options.autoFocus = !this.options.allowNewTags; this.input.autocomplete(this.options); this.options.select = os; //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) && !(self._isTabKey(e.which) && this.value == '' && !self.input.data('autoCompleteTag'))) { e.preventDefault(); self.input.data('autoCompleteTag', false); if (!self.options.allowNewTags || (self.options.maxTags !== undefined && self.tagsArray.length == self.options.maxTags)) { self.input.val(""); } else if (self.options.allowNewTags && $(this).val().length >= self.options.minLength) { self._addTag($(this).val()); } } if (self.options.maxLength !== undefined && self.input.val().length == self.options.maxLength) { e.preventDefault(); } if (lastLi.hasClass('selected')) lastLi.removeClass('selected'); self.lastKey = e.which; }); this.input.bind("paste", function (e) { if (self.options.blurOnPaste) { var input = $(this); self.timer = setTimeout(function () { input.blur(); }, 0); } }); //setup blur handler this.input.blur(function (e) { self.currentLabel = $(this).val(); self.currentValue = $(this).data('value'); if (self.options.allowNewTags) { self.timer = setTimeout(function () { self._addTag(self.currentLabel, self.currentValue); self.currentValue = ''; self.currentLabel = ''; }, 400); } $(this).val('').removeData('value'); return false; }); //define missing trim function for strings if (!String.prototype.trim) { String.prototype.trim = function () { return this.replace(/^\s+|\s+$/g, ''); }; } if (this.options.select) { this.select = $(''); this.element.after(this.select); } this._initialTags(); //setup sortable handler if (self.options.sortable !== false) { var soptions = { items:'.tagit-choice', containment:'parent', opacity: 0.6, tolerance: 'pointer', start:function (event, ui) { self._sortable.tag = $(ui.item); self._sortable.origIndex = self._sortable.tag.index(); }, update:function (event, ui) { self._sortable.newIndex = self._sortable.tag.index(); self._moveTag(self._sortable.origIndex, self._sortable.newIndex); if(self.options.tagsChanged){ var tag = self.tagsArray[self._sortable.newIndex]; self.options.tagsChanged(tag.value, 'moved', tag.element); } } }; if (self.options.sortable == 'handle') { soptions.handle = 'a.ui-icon'; soptions.cursor = 'move'; } self.element.sortable(soptions); } }, _popSelect:function (tag) { $('option:eq(' + tag.index + ')', this.select).remove(); this.select.change(); }, _addSelect:function (tag) { this.select.append(''); this.select.change(); }, _popTag:function (tag) { //are we removing the last tag or a specific tag? if (tag === undefined) tag = this.tagsArray.pop(); else this.tagsArray.splice(tag.index, 1); //maintain the indexes for (var ind in this.tagsArray) this.tagsArray[ind].index = ind; if (this.options.select) this._popSelect(tag); if (this.options.tagsChanged) this.options.tagsChanged(tag.value || tag.label, 'popped', tag); return; }, _addTag:function (label, value) { this.input.autocomplete('close').val(""); //are we trying to add a tag that should be split? if (this._splitAt && label.search(this._splitAt) > 0) { var result = label.split(this._splitAt); for (var i = 0; i < result.length; i++) this._addTag(result[i], value); return; } label = label.replace(/,+$/, "").trim(); if (label == "") return false; var tagExists = this._exists(label, value); if (tagExists !== false) { this._highlightExisting(tagExists); return false; } var tag = this.tag(label, value); tag.element = $('