" );
currentCategory = item.category;
}
that._renderItemData( ul, item );
});
}
});
$.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','semicolon'],
//custom regex for splitting data
seperatorKeys: ['comma','semicolon'],
//array method for setting initial tags
initialTags:[],
defaultType: 'none', //default type for newly added tags
//minimum length of tags
minLength:1,
//minimum length of typed text before triggering jQuery-ui Autocomplete
autocompleteMinLength: 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,
editable:false,
//color to highlight text when a duplicate tag is entered
highlightOnExistColor:'#0F0',
// The place holder to be shown in the
// tagit field.
placeholder:'Enter tags...',
// input name
fieldName:'',
// The input width of the text field, If the placeholder is
// too long or too short this value can be adjusted to fit it in.
inputWidth:250,
//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],
semicolon:[59,186]
},
_sortable:{
sorting:-1
},
_idEditing: false,
//initialization function
_create:function () {
var self = this;
this.tagsArray = [];
this.timer = null;
this.lastKey = 0;
//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())});
});
pushRegex = function(list, key,regex){
if ($.inArray(key, self.options.seperatorKeys) != -1){
regexes.push(regex);
}
};
//setup split according to the trigger keys
self._splitAt = null;
var regexes = [];
pushRegex(regexes, 'space', /\ /);
pushRegex(regexes, 'semicolon', /;/);
pushRegex(regexes, 'comma', /,/);
var regexString = $.map(regexes,function(x){
return x.source;
}).join('|');
self._splitAt = new RegExp(regexString,"g");
//alert(self._splitAt);
//add the html input
this.element.html('');
this.input = this.element.find(".tagit-input");
this.input.autoGrowInput();
//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 {
if (!self._isEditing) { //focus default input if we're not editing existing tag at the moment
self.input.focus();
}
if (self.options.emptySearch && $(e.target).hasClass('tagit-input') && self.input.val() == '' && self.input.catcomplete != undefined) {
self.input.catcomplete('search');
}
if (self.options.editable && $(e.target).hasClass('tagit-label')) {
self._edit(e.target);
}
}
});
//setup autocomplete handler
var os = this.options.select;
var ml = this.options.minLength;
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({ label: ui.item.value});
else
self._addTag({ label: ui.item.label, value: ui.item.value, type: ui.item.category});
}
return false;
},
this.options.focus = function (event, ui) {
if (ui.item.label !== undefined && /^key/.test(event.originalEvent.type)) {
self.input.val(ui.item.label);
self.input.data('value', ui.item.value);
return false;
}
};
this.options.autoFocus = !this.options.allowNewTags;
this.options.minLength = this.options.autocompleteMinLength;
this.input.catcomplete(this.options);
this.options.minLength = ml;
this.options.select = os;
self.isKeyEventProcessed = false;
this.input.keyup(function(e) {
self.isKeyEventProcessed = false;
});
this.input.keydown(function (e) {
self._processKeyEvent(e);
});
//setup keydown handler
this.input.keypress(function (e) {
self._processKeyEvent(e);
});
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({ label: self.currentLabel, value: self.currentValue, type: self.options.defaultType});
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);
}
},
_processKeyEvent: function(e) {
if (this.isKeyEventProcessed) {
return; //don't process key events twice
}
var pressedKey = e.which || e.keyCode || e.charCode;
var lastLi = this.element.children(".tagit-choice:last");
this.isKeyEventProcessed = true;
if (pressedKey == this._keys.backspace) {
return this._backspace(lastLi);
}
if (this._isInitKey(pressedKey) && !(this._isTabKey(pressedKey) && this.value == '' && !this.input.data('autoCompleteTag'))) {
e.preventDefault();
this.input.data('autoCompleteTag', false);
if (!this.options.allowNewTags || (this.options.maxTags !== undefined && this.tagsArray.length == this.options.maxTags)) {
this.input.val("");
}
else if (this.options.allowNewTags && this.input.val().length >= this.options.minLength) {
this._addTag({ label: this.input.val(), type: this.options.defaultType});
}
}
if (this.options.maxLength !== undefined && this.input.val().length > this.options.maxLength) {
e.preventDefault();
this.input.val(this.input.val().substring(0, this.options.maxLength));
}
if (lastLi.hasClass('selected'))
lastLi.removeClass('selected');
this.lastKey = pressedKey;
},
_postEdit: function(element, editInput, initialValue) {
var finishEditing = $.proxy(function() {
editInput.remove();
$(element).removeClass('hidden');
$(element).parent().removeClass('edited');
this._isEditing = false;
}, this);
return function() {
var initialTagIndex = $(element).parent().index();
var initialTag = this.tagsArray[initialTagIndex];
//try to add new and if success - remove old
var newValue = editInput.val();
if (this._splitAt && newValue.search(this._splitAt) > 0) {
newValue = newValue.split(this._splitAt)[0]; //use only first value part - no splitters in edit
}
if (newValue == initialValue) {
finishEditing();
} else if (this._addTag({ label: newValue})) {
//else attempt to add new tag and if succeeded - remove old element and edit box
initialTag.element.remove();
this._popTag(initialTag);
var lastTagIndex = this.tagsArray.length - 1;
if (lastTagIndex != initialTagIndex) {
var lastTag = this.tagsArray[lastTagIndex];
//visually move tag to the old place
lastTag.element.insertBefore(this.tagsArray[initialTagIndex].element);
this._moveTag(this.tagsArray.length - 1, initialTagIndex); //move element from last to old place
if(this.options.tagsChanged) { //fire an update
var tag = this.tagsArray[initialTagIndex];
this.options.tagsChanged(tag.value, 'moved', tag.element);
}
}
finishEditing();
}
}
},
_edit: function(element) {
this._isEditing = true;
var initialValue = $(element).text();
var editInput = $('');
editInput.val(initialValue);
$(element).parent().addClass('edited');
editInput.addClass('tagit-edit');
editInput.css('width', $(element).outerWidth());
$(element).addClass('hidden');
editInput.blur($.proxy(this._postEdit(element, editInput, initialValue), this));
editInput.keypress($.proxy(function(e) {
var pressedKey = e.which || e.keyCode || e.charCode;
if (this._isInitKey(pressedKey)) {
editInput.blur();
}
}, this));
$(element).before(editInput);
editInput[0].select();
},
_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? (tag.value || tag.label) : null, 'popped', tag);
return;
},
_addTag:function (newTag) {
if (newTag.label === undefined) { //set label to value to simplify possible use-cases, eg. if we send data from server where title isn't set
newTag.label = newTag.value;
}
this.input.catcomplete('close').val("");
//are we trying to add a tag that should be split?
if (this._splitAt && newTag.label.search(this._splitAt) > 0) {
var result = newTag.label.split(this._splitAt);
for (var i = 0; i < result.length; i++)
this._addTag({ label: result[i], value: newTag.value});
return;
}
newTag.label = newTag.label.replace(/,+$/, "").trim();
if (newTag.label == "")
return false;
//escape < > and &
newTag.label = newTag.label.replace(/&/g, "&").replace(//g, ">");
var tagExists = this._exists(newTag.label, newTag.value);
if (tagExists !== false) {
this._highlightExisting(tagExists);
return false;
}
var tag = this.tag(newTag.label, newTag.value, newTag.type);
tag.element = $('