Initial Redmine tooling and local plugin forks
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) 2011-2013 Kirill Bezrukov
|
||||
|
||||
var noteFileFieldCount = 1;
|
||||
|
||||
function addNoteFileField() {
|
||||
if (noteFileFieldCount >= 10) return false
|
||||
noteFileFieldCount++;
|
||||
var f = document.createElement("input");
|
||||
f.type = "file";
|
||||
f.name = "note_attachments[" + noteFileFieldCount + "][file]";
|
||||
f.size = 30;
|
||||
var d = document.createElement("input");
|
||||
d.type = "text";
|
||||
d.name = "note_attachments[" + noteFileFieldCount + "][description]";
|
||||
d.size = 60;
|
||||
|
||||
p = document.getElementById("note_attachments_fields");
|
||||
p.appendChild(document.createElement("br"));
|
||||
p.appendChild(f);
|
||||
p.appendChild(d);
|
||||
}
|
||||
|
||||
function updateCustomForm(url, form) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'post',
|
||||
data: $(form).serialize()
|
||||
});
|
||||
}
|
||||
|
||||
function toggleContact(event, element)
|
||||
{
|
||||
if (event.shiftKey==1)
|
||||
{
|
||||
if (element.checked) {
|
||||
checkAllContacts($$('.contacts.index td.checkbox input'));
|
||||
}
|
||||
else
|
||||
{
|
||||
uncheckAllContacts($$('.contacts.index td.checkbox input'));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Element.up(element, 'tr').toggleClassName('context-menu-selection');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Observ field function
|
||||
|
||||
(function( $ ){
|
||||
|
||||
jQuery.fn.observe_field = function(frequency, callback) {
|
||||
|
||||
frequency = frequency * 100; // translate to milliseconds
|
||||
|
||||
return this.each(function(){
|
||||
var $this = $(this);
|
||||
var prev = $this.val();
|
||||
|
||||
var check = function() {
|
||||
if(removed()){ // if removed clear the interval and don't fire the callback
|
||||
if(ti) clearInterval(ti);
|
||||
return;
|
||||
}
|
||||
|
||||
var val = $this.val();
|
||||
if(prev != val){
|
||||
prev = val;
|
||||
$this.map(callback); // invokes the callback on $this
|
||||
}
|
||||
};
|
||||
|
||||
var removed = function() {
|
||||
return $this.closest('html').length == 0
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
if(ti){
|
||||
clearInterval(ti);
|
||||
ti = setInterval(check, frequency);
|
||||
}
|
||||
};
|
||||
|
||||
check();
|
||||
var ti = setInterval(check, frequency); // invoke check periodically
|
||||
|
||||
// reset counter after user interaction
|
||||
$this.bind('keyup click mousemove', reset); //mousemove is for selects
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$.fn.insertAtCaret = function (myValue) {
|
||||
|
||||
return this.each(function() {
|
||||
|
||||
//IE support
|
||||
if (document.selection) {
|
||||
|
||||
this.focus();
|
||||
sel = document.selection.createRange();
|
||||
sel.text = myValue;
|
||||
this.focus();
|
||||
|
||||
} else if (this.selectionStart || this.selectionStart == '0') {
|
||||
|
||||
//MOZILLA / NETSCAPE support
|
||||
var startPos = this.selectionStart;
|
||||
var endPos = this.selectionEnd;
|
||||
var scrollTop = this.scrollTop;
|
||||
this.value = this.value.substring(0, startPos)+ myValue+ this.value.substring(endPos,this.value.length);
|
||||
this.focus();
|
||||
this.selectionStart = startPos + myValue.length;
|
||||
this.selectionEnd = startPos + myValue.length;
|
||||
this.scrollTop = scrollTop;
|
||||
|
||||
} else {
|
||||
|
||||
this.value += myValue;
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
})( jQuery );
|
||||
|
||||
function setupDeferredTabs(url) {
|
||||
$('body').on('click', '.tab-header', function(e){
|
||||
tab = $(e.target);
|
||||
$('.tab-placeholder').removeClass('active');
|
||||
name = tab.data('name');
|
||||
partial = tab.data('partial');
|
||||
placeholder = $('#tab-placeholder-' + name);
|
||||
placeholder.addClass('active');
|
||||
|
||||
if (!placeholder.is('.loaded')) {
|
||||
url = url
|
||||
$.ajax(url, {
|
||||
data: {tab_name: name, partial: partial},
|
||||
complete: function(){
|
||||
placeholder.addClass('loaded')
|
||||
//replaces current URL with the "href" attribute of the current link
|
||||
//(only triggered if supported by browser)
|
||||
if ("replaceState" in window.history) {
|
||||
window.history.replaceState(null, document.title, tab.attr('href'));
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
dataType: 'script'
|
||||
})
|
||||
}
|
||||
else {
|
||||
if ("replaceState" in window.history) {
|
||||
window.history.replaceState(null, document.title, tab.attr('href'));
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function selectAllOptions(id) {
|
||||
var select = $('#'+id);
|
||||
select.children('option').attr('selected', true);
|
||||
}
|
||||
|
||||
|
||||
function submit_query_form(id) {
|
||||
selectAllOptions("selected_columns");
|
||||
$('#'+id).submit();
|
||||
}
|
||||
|
||||
//replaces redmine default method showTab() beacuse of compatibility Redmine 3.1+
|
||||
function showContactTab(name, url) {
|
||||
$('div#content .tab-content').hide();
|
||||
$('div.tabs a').removeClass('selected');
|
||||
$('#tab-content-' + name).show();
|
||||
$('#tab-' + name).addClass('selected');
|
||||
if ("replaceState" in window.history) {
|
||||
window.history.replaceState(null, document.title, url);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tooglePriceField() {
|
||||
$('#deal_price').prop( "disabled", $('.line:visible').size() > 0 );
|
||||
}
|
||||
|
||||
function toggleCRMIssuesSelection(el) {
|
||||
var boxes = $(el).parents('form').find('input[type=checkbox]');
|
||||
var all_checked = true;
|
||||
boxes.each(function(){ if (!$(this).prop('checked')) { all_checked = false; } });
|
||||
boxes.each(function(){
|
||||
if (all_checked) {
|
||||
$(this).removeAttr('checked');
|
||||
$(this).parents('tr').removeClass('context-menu-selection');
|
||||
} else if (!$(this).prop('checked')) {
|
||||
$(this).prop('checked', true);
|
||||
$(this).parents('tr').addClass('context-menu-selection');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toogleDealItems(el) {
|
||||
$(el).closest('p').hide();
|
||||
$('.deal_items').show();
|
||||
}
|
||||
|
||||
function uploadAvatar(element) {
|
||||
var fileSpan = $('<span>', { id: 'attachments_0'});
|
||||
$('#contact_data #attachments_fields').html('');
|
||||
fileSpan.append(
|
||||
$('<input>', { type: 'hidden', class: 'filename', name: 'attachments[0][filename]'} ).val(element.files[0].name),
|
||||
$('<input>', { type: 'hidden', class: 'description', name: 'attachments[0][description]' } ).val('avatar'),
|
||||
$('<input>', { type: 'hidden', class: 'token', name: 'attachments[0][token]' } )
|
||||
).appendTo('#contact_data #attachments_fields');
|
||||
ajaxUpload(element.files[0], 0, fileSpan, element);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
function initDealSelect2(id, url, placeholder) {
|
||||
$(function () {
|
||||
$('select#' + id).select2({
|
||||
ajax: {
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return { q: params.term };
|
||||
},
|
||||
processResults: function (data, params) {
|
||||
return { results: data };
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
placeholder: placeholder,
|
||||
allowClear: true,
|
||||
minimumInputLength: 0,
|
||||
width: '60%',
|
||||
templateResult: function (option) {
|
||||
return $('<span>' + option.avatar + ' ' + option.text + '</span>');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
function initContactsAutocomplete(fieldName, sourceUrl, selectUrl) {
|
||||
var fieldId = '#' + fieldName.split('[').join('_').split('__').join('_').split(']').join('');
|
||||
var spanId = fieldId + '_selected_contact';
|
||||
var linkId = fieldId + '_edit_link';
|
||||
|
||||
function selectContact( contact ) {
|
||||
if (!selectUrl || !selectUrl.length) {
|
||||
$(spanId).text( contact.name );
|
||||
$(spanId).show();
|
||||
$(spanId).scrollTop( 0 );
|
||||
$(fieldId).hide();
|
||||
$(fieldId).val( contact.id );
|
||||
$(linkId).show();
|
||||
$(fieldId + '_add_link').hide();
|
||||
} else {
|
||||
$.ajax({
|
||||
url: selectUrl,
|
||||
type: 'POST',
|
||||
data: {id: contact.id}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(fieldId).autocomplete({
|
||||
source: sourceUrl,
|
||||
search: function(){$(this).addClass('ajax-loading');},
|
||||
response: function(){$(this).removeClass('ajax-loading');},
|
||||
change: function(event,ui){
|
||||
$(this).val((ui.item ? ui.item.id : ''));
|
||||
},
|
||||
select: function( event, ui ) {
|
||||
selectContact( ui.item ?
|
||||
ui.item:
|
||||
'Nothing selected, input was ' + this.value);
|
||||
return false;
|
||||
},
|
||||
minLength: 0
|
||||
})
|
||||
// .focus(function(){$(this).autocomplete("search");})
|
||||
.data('ui-autocomplete')._renderItem = function( ul, item ) {
|
||||
return $('<li>')
|
||||
.append('<a>' + item.avatar + ' ' + item.name + (item.company.length != 0 ? ' (' + item.company + ') ' : '') + '</a>')
|
||||
.appendTo( ul );
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
var oldToggleFilter = window.toggleFilter;
|
||||
|
||||
window.toggleFilter = function(field) {
|
||||
oldToggleFilter(field);
|
||||
return transform_to_select2(field);
|
||||
}
|
||||
|
||||
function filterFormatState (opt) {
|
||||
var $opt = $('<span>' + opt.avatar + ' ' + opt.text + '</span>');
|
||||
return $opt;
|
||||
};
|
||||
|
||||
function transform_to_select2(field){
|
||||
field_format = availableFilters[field]['field_format'];
|
||||
field = field.replace('.', '_');
|
||||
initialized_select2 = $('#tr_' + field + ' .values .select2');
|
||||
if (initialized_select2.size() == 0 && $.inArray(field_format, field_formats) >= 0) {
|
||||
$('#tr_' + field + ' .toggle-multiselect').hide();
|
||||
$('#tr_' + field + ' .values .value').attr('multiple', 'multiple');
|
||||
$('#tr_' + field + ' .values .value').select2({
|
||||
ajax: {
|
||||
url: contact_filter_urls[field_format],
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return { q: params.term };
|
||||
},
|
||||
processResults: function (data, params) {
|
||||
return { results: data };
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
placeholder: ' ',
|
||||
minimumInputLength: 1,
|
||||
width: '60%',
|
||||
templateResult: filterFormatState
|
||||
}).on('select2:open', function (e) {
|
||||
$(this).parent('span').find('.select2-search__field').val(' ').trigger($.Event('input', { which: 13 })).val('');
|
||||
});
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Really Simple Color Picker in jQuery
|
||||
*
|
||||
* Licensed under the MIT (MIT-LICENSE.txt) licenses.
|
||||
*
|
||||
* Copyright (c) 2008-2012
|
||||
* Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/(function(a){var b,c,d=0,e={control:a('<div class="colorPicker-picker"> </div>'),palette:a('<div id="colorPicker_palette" class="colorPicker-palette" />'),swatch:a('<div class="colorPicker-swatch"> </div>'),hexLabel:a('<label for="colorPicker_hex">Hex</label>'),hexField:a('<input type="text" id="colorPicker_hex" />')},f="transparent",g;a.fn.colorPicker=function(b){return this.each(function(){var c=a(this),g=a.extend({},a.fn.colorPicker.defaults,b),h=a.fn.colorPicker.toHex(c.val().length>0?c.val():g.pickerDefault),i=e.control.clone(),j=e.palette.clone().attr("id","colorPicker_palette-"+d),k=e.hexLabel.clone(),l=e.hexField.clone(),m=j[0].id,n;a.each(g.colors,function(b){n=e.swatch.clone(),g.colors[b]===f?(n.addClass(f).text("X"),a.fn.colorPicker.bindPalette(l,n,f)):(n.css("background-color","#"+this),a.fn.colorPicker.bindPalette(l,n)),n.appendTo(j)}),k.attr("for","colorPicker_hex-"+d),l.attr({id:"colorPicker_hex-"+d,value:h}),l.bind("keydown",function(b){if(b.keyCode===13){var d=a.fn.colorPicker.toHex(a(this).val());a.fn.colorPicker.changeColor(d?d:c.val())}b.keyCode===27&&a.fn.colorPicker.hidePalette()}),l.bind("keyup",function(b){var d=a.fn.colorPicker.toHex(a(b.target).val());a.fn.colorPicker.previewColor(d?d:c.val())}),a('<div class="colorPicker_hexWrap" />').append(k).appendTo(j),j.find(".colorPicker_hexWrap").append(l),a("body").append(j),j.hide(),i.css("background-color",h),i.bind("click",function(){a.fn.colorPicker.togglePalette(a("#"+m),a(this))}),b&&b.onColorChange?i.data("onColorChange",b.onColorChange):i.data("onColorChange",function(){}),c.after(i),c.bind("change",function(){c.next(".colorPicker-picker").css("background-color",a.fn.colorPicker.toHex(a(this).val()))}),c.val(h).hide(),d++})},a.extend(!0,a.fn.colorPicker,{toHex:function(a){if(a.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i))return a.charAt(0)==="#"?a:"#"+a;if(!a.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/))return!1;var b=[parseInt(RegExp.$1,10),parseInt(RegExp.$2,10),parseInt(RegExp.$3,10)],c=function(a){if(a.length<2)for(var b=0,c=2-a.length;b<c;b++)a="0"+a;return a};if(b.length===3){var d=c(b[0].toString(16)),e=c(b[1].toString(16)),f=c(b[2].toString(16));return"#"+d+e+f}},checkMouse:function(d,e){var f=c,g=a(d.target).parents("#"+f.attr("id")).length;if(d.target===a(f)[0]||d.target===b[0]||g>0)return;a.fn.colorPicker.hidePalette()},hidePalette:function(){a(document).unbind("mousedown",a.fn.colorPicker.checkMouse),a(".colorPicker-palette").hide()},showPalette:function(c){var d=b.prev("input").val();c.css({top:b.offset().top+b.outerHeight(),left:b.offset().left}),a("#color_value").val(d),c.show(),a(document).bind("mousedown",a.fn.colorPicker.checkMouse)},togglePalette:function(d,e){e&&(b=e),c=d,c.is(":visible")?a.fn.colorPicker.hidePalette():a.fn.colorPicker.showPalette(d)},changeColor:function(c){b.css("background-color",c),b.prev("input").val(c).change(),a.fn.colorPicker.hidePalette(),b.data("onColorChange").call(b,a(b).prev("input").attr("id"),c)},previewColor:function(a){b.css("background-color",a)},bindPalette:function(c,d,e){e=e?e:a.fn.colorPicker.toHex(d.css("background-color")),d.bind({click:function(b){g=e,a.fn.colorPicker.changeColor(e)},mouseover:function(b){g=c.val(),a(this).css("border-color","#598FEF"),c.val(e),a.fn.colorPicker.previewColor(e)},mouseout:function(d){a(this).css("border-color","#000"),c.val(b.css("background-color")),c.val(g),a.fn.colorPicker.previewColor(g)}})}}),a.fn.colorPicker.defaults={pickerDefault:"FFFFFF",colors:["000000","993300","333300","000080","333399","333333","800000","FF6600","808000","008000","008080","0000FF","666699","808080","FF0000","FF9900","99CC00","339966","33CCCC","3366FF","800080","999999","FF00FF","FFCC00","FFFF00","00FF00","00FFFF","00CCFF","993366","C0C0C0","FF99CC","FFCC99","FFFF99","CCFFFF","99CCFF","FFFFFF"],addColors:[]}})(jQuery)
|
||||
+588
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
* jQuery UI Tag-it!
|
||||
*
|
||||
* @version v2.0 (06/2011)
|
||||
*
|
||||
* Copyright 2011, Levy Carneiro Jr.
|
||||
* Released under the MIT license.
|
||||
* http://aehlke.github.com/tag-it/LICENSE
|
||||
*
|
||||
* Homepage:
|
||||
* http://aehlke.github.com/tag-it/
|
||||
*
|
||||
* Authors:
|
||||
* Levy Carneiro Jr.
|
||||
* Martin Rehfeld
|
||||
* Tobias Schmidt
|
||||
* Skylar Challand
|
||||
* Alex Ehlke
|
||||
*
|
||||
* Maintainer:
|
||||
* Alex Ehlke - Twitter: @aehlke
|
||||
*
|
||||
* Dependencies:
|
||||
* jQuery v1.4+
|
||||
* jQuery UI v1.8+
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
$.widget('ui.tagit', {
|
||||
options: {
|
||||
allowDuplicates : false,
|
||||
caseSensitive : true,
|
||||
fieldName : 'tags',
|
||||
placeholderText : null, // Sets `placeholder` attr on input field.
|
||||
readOnly : false, // Disables editing.
|
||||
removeConfirmation: false, // Require confirmation to remove tags.
|
||||
tagLimit : null, // Max number of tags allowed (null for unlimited).
|
||||
|
||||
// Used for autocomplete, unless you override `autocomplete.source`.
|
||||
availableTags : [],
|
||||
|
||||
// Use to override or add any options to the autocomplete widget.
|
||||
//
|
||||
// By default, autocomplete.source will map to availableTags,
|
||||
// unless overridden.
|
||||
autocomplete: {},
|
||||
|
||||
// Shows autocomplete before the user even types anything.
|
||||
showAutocompleteOnFocus: false,
|
||||
|
||||
// When enabled, quotes are unneccesary for inputting multi-word tags.
|
||||
allowSpaces: false,
|
||||
|
||||
// The below options are for using a single field instead of several
|
||||
// for our form values.
|
||||
//
|
||||
// When enabled, will use a single hidden field for the form,
|
||||
// rather than one per tag. It will delimit tags in the field
|
||||
// with singleFieldDelimiter.
|
||||
//
|
||||
// The easiest way to use singleField is to just instantiate tag-it
|
||||
// on an INPUT element, in which case singleField is automatically
|
||||
// set to true, and singleFieldNode is set to that element. This
|
||||
// way, you don't need to fiddle with these options.
|
||||
singleField: false,
|
||||
|
||||
// This is just used when preloading data from the field, and for
|
||||
// populating the field with delimited tags as the user adds them.
|
||||
singleFieldDelimiter: ',',
|
||||
|
||||
// Set this to an input DOM node to use an existing form field.
|
||||
// Any text in it will be erased on init. But it will be
|
||||
// populated with the text of tags as they are created,
|
||||
// delimited by singleFieldDelimiter.
|
||||
//
|
||||
// If this is not set, we create an input node for it,
|
||||
// with the name given in settings.fieldName.
|
||||
singleFieldNode: null,
|
||||
|
||||
// Whether to animate tag removals or not.
|
||||
animate: true,
|
||||
|
||||
// Optionally set a tabindex attribute on the input that gets
|
||||
// created for tag-it.
|
||||
tabIndex: null,
|
||||
|
||||
// Event callbacks.
|
||||
beforeTagAdded : null,
|
||||
afterTagAdded : null,
|
||||
|
||||
beforeTagRemoved : null,
|
||||
afterTagRemoved : null,
|
||||
|
||||
onTagClicked : null,
|
||||
onTagLimitExceeded : null,
|
||||
|
||||
|
||||
// DEPRECATED:
|
||||
//
|
||||
// /!\ These event callbacks are deprecated and WILL BE REMOVED at some
|
||||
// point in the future. They're here for backwards-compatibility.
|
||||
// Use the above before/after event callbacks instead.
|
||||
onTagAdded : null,
|
||||
onTagRemoved: null,
|
||||
// `autocomplete.source` is the replacement for tagSource.
|
||||
tagSource: null
|
||||
// Do not use the above deprecated options.
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
// for handling static scoping inside callbacks
|
||||
var that = this;
|
||||
|
||||
// There are 2 kinds of DOM nodes this widget can be instantiated on:
|
||||
// 1. UL, OL, or some element containing either of these.
|
||||
// 2. INPUT, in which case 'singleField' is overridden to true,
|
||||
// a UL is created and the INPUT is hidden.
|
||||
if (this.element.is('input')) {
|
||||
this.tagList = $('<ul></ul>').insertAfter(this.element);
|
||||
this.options.singleField = true;
|
||||
this.options.singleFieldNode = this.element;
|
||||
this.element.addClass('tagit-hidden-field');
|
||||
} else {
|
||||
this.tagList = this.element.find('ul, ol').andSelf().last();
|
||||
}
|
||||
|
||||
this.tagInput = $('<input type="text" />').addClass('ui-widget-content');
|
||||
|
||||
if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled');
|
||||
|
||||
if (this.options.tabIndex) {
|
||||
this.tagInput.attr('tabindex', this.options.tabIndex);
|
||||
}
|
||||
|
||||
if (this.options.placeholderText) {
|
||||
this.tagInput.attr('placeholder', this.options.placeholderText);
|
||||
}
|
||||
|
||||
if (!this.options.autocomplete.source) {
|
||||
this.options.autocomplete.source = function(search, showChoices) {
|
||||
var filter = search.term.toLowerCase();
|
||||
var choices = $.grep(this.options.availableTags, function(element) {
|
||||
// Only match autocomplete options that begin with the search term.
|
||||
// (Case insensitive.)
|
||||
return (element.toLowerCase().indexOf(filter) === 0);
|
||||
});
|
||||
if (!this.options.allowDuplicates) {
|
||||
choices = this._subtractArray(choices, this.assignedTags());
|
||||
}
|
||||
showChoices(choices);
|
||||
};
|
||||
}
|
||||
|
||||
if (this.options.showAutocompleteOnFocus) {
|
||||
this.tagInput.focus(function(event, ui) {
|
||||
that._showAutocomplete();
|
||||
});
|
||||
|
||||
if (typeof this.options.autocomplete.minLength === 'undefined') {
|
||||
this.options.autocomplete.minLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Bind autocomplete.source callback functions to this context.
|
||||
if ($.isFunction(this.options.autocomplete.source)) {
|
||||
this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this);
|
||||
}
|
||||
|
||||
// DEPRECATED.
|
||||
if ($.isFunction(this.options.tagSource)) {
|
||||
this.options.tagSource = $.proxy(this.options.tagSource, this);
|
||||
}
|
||||
|
||||
this.tagList
|
||||
.addClass('tagit')
|
||||
.addClass('ui-widget ui-widget-content ui-corner-all')
|
||||
// Create the input field.
|
||||
.append($('<li class="tagit-new"></li>').append(this.tagInput))
|
||||
.click(function(e) {
|
||||
var target = $(e.target);
|
||||
if (target.hasClass('tagit-label')) {
|
||||
var tag = target.closest('.tagit-choice');
|
||||
if (!tag.hasClass('removed')) {
|
||||
that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)});
|
||||
}
|
||||
} else {
|
||||
// Sets the focus() to the input field, if the user
|
||||
// clicks anywhere inside the UL. This is needed
|
||||
// because the input field needs to be of a small size.
|
||||
that.tagInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Single field support.
|
||||
var addedExistingFromSingleFieldNode = false;
|
||||
if (this.options.singleField) {
|
||||
if (this.options.singleFieldNode) {
|
||||
// Add existing tags from the input field.
|
||||
var node = $(this.options.singleFieldNode);
|
||||
var tags = node.val().split(this.options.singleFieldDelimiter);
|
||||
node.val('');
|
||||
$.each(tags, function(index, tag) {
|
||||
that.createTag(tag, null, true);
|
||||
addedExistingFromSingleFieldNode = true;
|
||||
});
|
||||
} else {
|
||||
// Create our single field input after our list.
|
||||
this.options.singleFieldNode = $('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');
|
||||
this.tagList.after(this.options.singleFieldNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Add existing tags from the list, if any.
|
||||
if (!addedExistingFromSingleFieldNode) {
|
||||
this.tagList.children('li').each(function() {
|
||||
if (!$(this).hasClass('tagit-new')) {
|
||||
that.createTag($(this).text(), $(this).attr('class'), true);
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Events.
|
||||
this.tagInput
|
||||
.keydown(function(event) {
|
||||
// Backspace is not detected within a keypress, so it must use keydown.
|
||||
if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {
|
||||
var tag = that._lastTag();
|
||||
if (!that.options.removeConfirmation || tag.hasClass('remove')) {
|
||||
// When backspace is pressed, the last tag is deleted.
|
||||
that.removeTag(tag);
|
||||
} else if (that.options.removeConfirmation) {
|
||||
tag.addClass('remove ui-state-highlight');
|
||||
}
|
||||
} else if (that.options.removeConfirmation) {
|
||||
that._lastTag().removeClass('remove ui-state-highlight');
|
||||
}
|
||||
|
||||
// Comma/Space/Enter are all valid delimiters for new tags,
|
||||
// except when there is an open quote or if setting allowSpaces = true.
|
||||
// Tab will also create a tag, unless the tag input is empty,
|
||||
// in which case it isn't caught.
|
||||
if (
|
||||
(event.which === $.ui.keyCode.ENTER)
|
||||
||
|
||||
(event.which == $.ui.keyCode.TAB && that.tagInput.val() !== '')
|
||||
||
|
||||
(
|
||||
event.which == $.ui.keyCode.SPACE &&
|
||||
that.options.allowSpaces !== true &&
|
||||
(
|
||||
$.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
|
||||
(
|
||||
$.trim(that.tagInput.val()).charAt(0) == '"' &&
|
||||
$.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' &&
|
||||
$.trim(that.tagInput.val()).length - 1 !== 0
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
// Enter submits the form if there's no text in the input.
|
||||
if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Autocomplete will create its own tag from a selection and close automatically.
|
||||
if (!(that.options.autocomplete.autoFocus && that.tagInput.data('autocomplete-open'))) {
|
||||
that.tagInput.autocomplete('close');
|
||||
that.createTag(that._cleanedInput());
|
||||
}
|
||||
}
|
||||
}).blur(function(e){
|
||||
// Create a tag when the element loses focus.
|
||||
// If autocomplete is enabled and suggestion was clicked, don't add it.
|
||||
if (!that.tagInput.data('autocomplete-open')) {
|
||||
that.createTag(that._cleanedInput());
|
||||
}
|
||||
});
|
||||
|
||||
// Autocomplete.
|
||||
if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) {
|
||||
var autocompleteOptions = {
|
||||
select: function(event, ui) {
|
||||
that.createTag(ui.item.value);
|
||||
// Preventing the tag input to be updated with the chosen value.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
$.extend(autocompleteOptions, this.options.autocomplete);
|
||||
|
||||
// tagSource is deprecated, but takes precedence here since autocomplete.source is set by default,
|
||||
// while tagSource is left null by default.
|
||||
autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source;
|
||||
|
||||
this.tagInput.autocomplete(autocompleteOptions).bind('autocompleteopen.tagit', function(event, ui) {
|
||||
that.tagInput.data('autocomplete-open', true);
|
||||
}).bind('autocompleteclose.tagit', function(event, ui) {
|
||||
that.tagInput.data('autocomplete-open', false);
|
||||
});
|
||||
|
||||
this.tagInput.autocomplete('widget').addClass('tagit-autocomplete');
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
$.Widget.prototype.destroy.call(this);
|
||||
|
||||
this.element.unbind('.tagit');
|
||||
this.tagList.unbind('.tagit');
|
||||
|
||||
this.tagInput.removeData('autocomplete-open');
|
||||
|
||||
this.tagList.removeClass([
|
||||
'tagit',
|
||||
'ui-widget',
|
||||
'ui-widget-content',
|
||||
'ui-corner-all',
|
||||
'tagit-hidden-field'
|
||||
].join(' '));
|
||||
|
||||
if (this.element.is('input')) {
|
||||
this.element.removeClass('tagit-hidden-field');
|
||||
this.tagList.remove();
|
||||
} else {
|
||||
this.element.children('li').each(function() {
|
||||
if ($(this).hasClass('tagit-new')) {
|
||||
$(this).remove();
|
||||
} else {
|
||||
$(this).removeClass([
|
||||
'tagit-choice',
|
||||
'ui-widget-content',
|
||||
'ui-state-default',
|
||||
'ui-state-highlight',
|
||||
'ui-corner-all',
|
||||
'remove',
|
||||
'tagit-choice-editable',
|
||||
'tagit-choice-read-only'
|
||||
].join(' '));
|
||||
|
||||
$(this).text($(this).children('.tagit-label').text());
|
||||
}
|
||||
});
|
||||
|
||||
if (this.singleFieldNode) {
|
||||
this.singleFieldNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_cleanedInput: function() {
|
||||
// Returns the contents of the tag input, cleaned and ready to be passed to createTag
|
||||
return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1'));
|
||||
},
|
||||
|
||||
_lastTag: function() {
|
||||
return this.tagList.find('.tagit-choice:last:not(.removed)');
|
||||
},
|
||||
|
||||
_tags: function() {
|
||||
return this.tagList.find('.tagit-choice:not(.removed)');
|
||||
},
|
||||
|
||||
assignedTags: function() {
|
||||
// Returns an array of tag string values
|
||||
var that = this;
|
||||
var tags = [];
|
||||
if (this.options.singleField) {
|
||||
tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
|
||||
if (tags[0] === '') {
|
||||
tags = [];
|
||||
}
|
||||
} else {
|
||||
this._tags().each(function() {
|
||||
tags.push(that.tagLabel(this));
|
||||
});
|
||||
}
|
||||
return tags;
|
||||
},
|
||||
|
||||
_updateSingleTagsField: function(tags) {
|
||||
// Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
|
||||
$(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change');
|
||||
},
|
||||
|
||||
_subtractArray: function(a1, a2) {
|
||||
var result = [];
|
||||
for (var i = 0; i < a1.length; i++) {
|
||||
if ($.inArray(a1[i], a2) == -1) {
|
||||
result.push(a1[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
tagLabel: function(tag) {
|
||||
// Returns the tag's string label.
|
||||
if (this.options.singleField) {
|
||||
return $(tag).find('.tagit-label:first').text();
|
||||
} else {
|
||||
return $(tag).find('input:first').val();
|
||||
}
|
||||
},
|
||||
|
||||
_showAutocomplete: function() {
|
||||
this.tagInput.autocomplete('search', '');
|
||||
},
|
||||
|
||||
_findTagByLabel: function(name) {
|
||||
var that = this;
|
||||
var tag = null;
|
||||
this._tags().each(function(i) {
|
||||
if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) {
|
||||
tag = $(this);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return tag;
|
||||
},
|
||||
|
||||
_isNew: function(name) {
|
||||
return !this._findTagByLabel(name);
|
||||
},
|
||||
|
||||
_formatStr: function(str) {
|
||||
if (this.options.caseSensitive) {
|
||||
return str;
|
||||
}
|
||||
return $.trim(str.toLowerCase());
|
||||
},
|
||||
|
||||
_effectExists: function(name) {
|
||||
return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name])));
|
||||
},
|
||||
|
||||
createTag: function(value, additionalClass, duringInitialization) {
|
||||
var that = this;
|
||||
|
||||
value = $.trim(value);
|
||||
|
||||
if(this.options.preprocessTag) {
|
||||
value = this.options.preprocessTag(value);
|
||||
}
|
||||
|
||||
if (value === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.options.allowDuplicates && !this._isNew(value)) {
|
||||
var existingTag = this._findTagByLabel(value);
|
||||
if (this._trigger('onTagExists', null, {
|
||||
existingTag: existingTag,
|
||||
duringInitialization: duringInitialization
|
||||
}) !== false) {
|
||||
if (this._effectExists('highlight')) {
|
||||
existingTag.effect('highlight');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) {
|
||||
this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization});
|
||||
return false;
|
||||
}
|
||||
|
||||
var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
|
||||
|
||||
// Create tag.
|
||||
var tag = $('<li></li>')
|
||||
.addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
|
||||
.addClass(additionalClass)
|
||||
.append(label);
|
||||
|
||||
if (this.options.readOnly){
|
||||
tag.addClass('tagit-choice-read-only');
|
||||
} else {
|
||||
tag.addClass('tagit-choice-editable');
|
||||
// Button for removing the tag.
|
||||
var removeTagIcon = $('<span></span>')
|
||||
.addClass('ui-icon ui-icon-close');
|
||||
var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
|
||||
.addClass('tagit-close')
|
||||
.append(removeTagIcon)
|
||||
.click(function(e) {
|
||||
// Removes a tag when the little 'x' is clicked.
|
||||
that.removeTag(tag);
|
||||
});
|
||||
tag.append(removeTag);
|
||||
}
|
||||
|
||||
// Unless options.singleField is set, each tag has a hidden input field inline.
|
||||
if (!this.options.singleField) {
|
||||
var escapedValue = label.html();
|
||||
tag.append('<input type="hidden" value="' + escapedValue + '" name="' + this.options.fieldName + '" class="tagit-hidden-field" />');
|
||||
}
|
||||
|
||||
if (this._trigger('beforeTagAdded', null, {
|
||||
tag: tag,
|
||||
tagLabel: this.tagLabel(tag),
|
||||
duringInitialization: duringInitialization
|
||||
}) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.singleField) {
|
||||
var tags = this.assignedTags();
|
||||
tags.push(value);
|
||||
this._updateSingleTagsField(tags);
|
||||
}
|
||||
|
||||
// DEPRECATED.
|
||||
this._trigger('onTagAdded', null, tag);
|
||||
|
||||
this.tagInput.val('');
|
||||
|
||||
// Insert tag.
|
||||
this.tagInput.parent().before(tag);
|
||||
|
||||
this._trigger('afterTagAdded', null, {
|
||||
tag: tag,
|
||||
tagLabel: this.tagLabel(tag),
|
||||
duringInitialization: duringInitialization
|
||||
});
|
||||
|
||||
if (this.options.showAutocompleteOnFocus && !duringInitialization) {
|
||||
setTimeout(function () { that._showAutocomplete(); }, 0);
|
||||
}
|
||||
},
|
||||
|
||||
removeTag: function(tag, animate) {
|
||||
animate = typeof animate === 'undefined' ? this.options.animate : animate;
|
||||
|
||||
tag = $(tag);
|
||||
|
||||
// DEPRECATED.
|
||||
this._trigger('onTagRemoved', null, tag);
|
||||
|
||||
if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.singleField) {
|
||||
var tags = this.assignedTags();
|
||||
var removedTagLabel = this.tagLabel(tag);
|
||||
tags = $.grep(tags, function(el){
|
||||
return el != removedTagLabel;
|
||||
});
|
||||
this._updateSingleTagsField(tags);
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
tag.addClass('removed'); // Excludes this tag from _tags.
|
||||
var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast'];
|
||||
|
||||
var thisTag = this;
|
||||
hide_args.push(function() {
|
||||
tag.remove();
|
||||
thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)});
|
||||
});
|
||||
|
||||
tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue();
|
||||
} else {
|
||||
tag.remove();
|
||||
this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
removeTagByLabel: function(tagLabel, animate) {
|
||||
var toRemove = this._findTagByLabel(tagLabel);
|
||||
if (!toRemove) {
|
||||
throw "No such tag exists with the name '" + tagLabel + "'";
|
||||
}
|
||||
this.removeTag(toRemove, animate);
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
// Removes all tags.
|
||||
var that = this;
|
||||
this._tags().each(function(index, tag) {
|
||||
that.removeTag(tag, false);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user