/* jQuery Tags Input Plugin 2.0 Copyright (c) 2012 XOXCO, Inc Documentation for this plugin lives here: http://xoxco.com/clickable/jquery-tags-input Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php ben@xoxco.com GOALS FOR 2.0 Fix identifier issue - should work with any valid jquery selector Make internationalization friendly add tag input validation make sure autocomplete works with newest jquery */ (function($) { var internal_counter = 0; // this is used to create unique ids for each tag input // automatically size the input to show the entire value $.fn.doAutosize = function(o){ var minWidth = $(this).data('minwidth'), maxWidth = $(this).data('maxwidth'), val = '', input = $(this), testSubject = $('#'+$(this).data('tester_id')); if (val === (val = input.val())) {return;} // Enter new content into testSubject var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>'); testSubject.html(escaped); // Calculate new width + whether to change var testerWidth = testSubject.width(), newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth, currentWidth = input.width(), isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth) || (newWidth > minWidth && newWidth < maxWidth); // Animate width if (isValidWidthChange) { input.width(newWidth); } }; $.fn.resetAutosize = function(options){ var minWidth = $(this).data('minwidth') || options.minInputWidth || $(this).width(), maxWidth = $(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width() - options.inputPadding), val = '', input = $(this), testSubject = $('').css({ position: 'absolute', top: -9999, left: -9999, width: 'auto', fontSize: input.css('fontSize'), fontFamily: input.css('fontFamily'), fontWeight: input.css('fontWeight'), letterSpacing: input.css('letterSpacing'), whiteSpace: 'nowrap' }), testerId = $(this).attr('id')+'_autosize_tester'; if(! $('#'+testerId).length > 0){ testSubject.attr('id', testerId); testSubject.appendTo('body'); } input.data('minwidth', minWidth); input.data('maxwidth', maxWidth); input.data('tester_id', testerId); input.css('width', minWidth); }; $.fn.addTag = function(value,options) { options = jQuery.extend({focus:false,callback:true},options); this.each(function() { var settings = $(this).data('settings'); var tagslist = $(this).val().split(settings.delimiter); if (tagslist[0] == '') { tagslist = new Array(); } value = jQuery.trim(value); var skipTag = false; if (options.unique) { skipTag = $(this).tagExist(value); if(skipTag == true) { //Marks fake input as not_valid to let styling it $(settings.fake_input).addClass('not_valid'); if (settings.onError) { var f = settings.onError; f.call(this,'duplicate'); } } } if (settings.validateTag) { var f = settings.validateTag; if (!f.call(this,value)) { skipTag = true; $(settings.fake_input).addClass('not_valid'); if (settings.onError) { var f = settings.onError; f.call(this,'validation'); } } } if (value !='' && skipTag != true) { $('').addClass('tag').append( $('').text(value).append('  '), $('', { href : '#', title : settings.removeText, text : 'x' }).click(function () { return $(settings.real_input).removeTag(escape(value)); }) ).insertBefore(settings.input_wrapper); tagslist.push(value); $(settings.fake_input).val(''); if (options.focus) { $(settings.fake_input).focus(); } else { $(settings.fake_input).blur(); } $.fn.tagsInput.updateTagsField(this,tagslist); if (settings.onAddTag) { var f = settings.onAddTag; f.call(this, value); } if(settings.onChange) { var i = tagslist.length; var f = settings.onChange; f.call(this, $(this), tagslist[i-1]); } } }); return false; }; $.fn.removeTag = function(value) { value = unescape(value); this.each(function() { var settings = $(this).data('settings'); var old = $(this).val().split(settings.delimiter); $(settings.holder + ' .tag').remove(); str = ''; for (i=0; i< old.length; i++) { if (old[i]!=value) { str = str + settings.delimiter +old[i]; } } $.fn.tagsInput.importTags(this,str); if (settings.onRemoveTag) { var f = settings.onRemoveTag; f.call(this, value); } }); return false; }; $.fn.tagExist = function(val) { var settings = $(this).data('settings'); var tagslist = $(this).val().split(settings.delimiter); return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not }; // clear all existing tags and import new ones from a string $.fn.importTags = function(str) { var settings = $(this).data('settings'); $(settings.holder + ' .tag').remove(); $.fn.tagsInput.importTags(this,str); } $.fn.tagsInput = function(options) { var settings = jQuery.extend({ interactive:true, defaultText:'add a tag', removeText:'Remove Tag', minChars:0, width:'auto', minHeight:'100px', height: 'auto', autocomplete: null, autocompleteselect: null, 'hide':true, 'delimiter':',', 'unique':true, removeWithBackspace:true, placeholderColor:'#666666', autosize: true, comfortZone: 20, inputPadding: 6*2, onChange: null, // events and handlers onAddTag: null, onRemoveTag: null, validateTag: null, onError: null },options); this.each(function() { if (settings.hide) { $(this).hide(); } var id = internal_counter++; var data = jQuery.extend({ pid:id, real_input: this, holder: '#'+id+'_tagsinput', input_wrapper: '#'+id+'_addTag', fake_input: '#'+id+'_tag' },settings); $(this).addClass('tagsinput_processed'); var markup = '
'; if (settings.interactive) { markup = markup + ''; } markup = markup + '
'; $(markup).insertAfter(this); $(data.holder).css('width',settings.width); $(data.holder).css('height',settings.height); $(data.holder).css('min-height',settings.minHeight); // store the settings with the DOM object so we can use them in other functions $(this).data('settings',data); if ($(this).val()!='') { $.fn.tagsInput.importTags($(this),$(this).val()); } if (settings.interactive) { $(data.fake_input).val($(data.fake_input).attr('data-default')); $(data.fake_input).css('color',settings.placeholderColor); $(data.fake_input).resetAutosize(settings); // if the user clicks anywhere in the tag box, focus the input field $(data.holder).on('click',data,function(event) { $(event.data.fake_input).focus(); }); // if the user clicks in the input field… $(data.fake_input).on('focus',data,function(event) { // empty the field if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) { $(event.data.fake_input).val(''); } // set the field to the active color $(event.data.fake_input).css('color','#000000'); }); // if an autocomplete has been specified, do some stuff… if (settings.autocomplete) { // pass in the autocomplete settings to the autocomplete plugin // provided by jquery UI $(data.fake_input).autocomplete(settings.autocomplete); // if an autocompleteselect handler has been passed in, use that. // otherwise, use our own. if (settings.autocompleteselect) { $(data.fake_input).on('autocompleteselect',data,settings.autocompleteselect); } else { $(data.fake_input).on('autocompleteselect',data,function(event,ui) { $(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)}); return false; }); } // track whether or not the autocomplete is open so we can avoid adding duplicate tags $(data.fake_input).on('autocompleteopen',data,function() { $(this).data('autocompleteopen',true); return false; }); $(data.fake_input).on('autocompleteclose',data,function() { $(this).data('autocompleteopen',null); return false; }); } // if the user tabs out of the field // add the tag that was being typed // UNLESS the autocompleter is open // and then set back to default. $(data.fake_input).on('blur',data,function(event) { if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=event.data.defaultText) { if (!$(this).data('autocompleteopen')) { if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) ) $(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)}); } else { // autocomplete is open, do not add tag because // this will be handled by the autocompleteselect event. } } else { $(event.data.fake_input).val(event.data.defaultText); $(event.data.fake_input).css('color',event.data.placeholderColor); $(event.data.fake_input).removeClass('not_valid'); } return false; }); // if user types a comma, create a new tag $(data.fake_input).on('keypress',data,function(event) { if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) { event.preventDefault(); if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) ) $(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)}); $(event.data.fake_input).resetAutosize(settings); return false; } else if (event.data.autosize) { $(event.data.fake_input).doAutosize(settings); } }); //Delete last tag on backspace data.removeWithBackspace && $(data.fake_input).on('keydown',data, function(event) { if(event.keyCode == 8 && $(this).val() == '') { event.preventDefault(); var last_tag = $(this).closest('.tagsinput').find('.tag:last').text(); last_tag = last_tag.replace(/[\s]+x$/, ''); $(data.real_input).removeTag(escape(last_tag)); $(this).trigger('focus'); } }); $(data.fake_input).blur(); //Removes the not_valid class when user changes the value of the fake input if(data.unique || data.validateTag) { $(data.fake_input).keydown(function(event){ if(event.keyCode == 8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)) { $(this).removeClass('not_valid'); } }); } } // if settings.interactive }); return this; }; $.fn.tagsInput.updateTagsField = function(obj,tagslist) { var settings = $(obj).data('settings'); $(obj).val(tagslist.join(settings.delimiter)); }; $.fn.tagsInput.importTags = function(obj,val) { var settings = $(obj).data('settings'); $(obj).val(''); var tags = val.split(settings.delimiter); for (i=0; i