jquery.tagsinput.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /*
  2. jQuery Tags Input Plugin 1.3.3 (with minor modifications for CardDavMATE)
  3. Copyright (c) 2011 XOXCO, Inc
  4. Documentation for this plugin lives here:
  5. http://xoxco.com/clickable/jquery-tags-input
  6. Licensed under the MIT license:
  7. http://www.opensource.org/licenses/mit-license.php
  8. ben@xoxco.com
  9. */
  10. (function($){
  11. var tags_settings=new Array();
  12. var tags_callbacks=new Array();
  13. String.prototype.escapeCustom=function(inputDelimiter)
  14. {
  15. var value=(this==undefined ? '' : this),
  16. output='';
  17. for(var i=0; i<value.length; i++)
  18. {
  19. if(value[i]==inputDelimiter || value[i]=='\\')
  20. output+='\\';
  21. output+=value[i];
  22. }
  23. return output;
  24. }
  25. // split and unescape values
  26. String.prototype.splitCustom=function(inputDelimiter)
  27. {
  28. var outputArray=new Array(),
  29. value=this,
  30. j=0;
  31. for(var i=0; i<value.length; i++)
  32. {
  33. if(value[i]==inputDelimiter)
  34. {
  35. if(outputArray[j]==undefined)
  36. outputArray[j]='';
  37. ++j;
  38. continue;
  39. }
  40. else if(value[i]=='\\')
  41. ++i;
  42. outputArray[j]=(outputArray[j]==undefined ? '' : outputArray[j])+value[i];
  43. }
  44. return outputArray;
  45. }
  46. $.fn.doAutosize=function(o)
  47. {
  48. var minWidth=$(this).data('minwidth'),
  49. maxWidth=$(this).data('maxwidth'),
  50. val='',
  51. input=$(this),
  52. testSubject=$('#'+$(this).data('tester_id'));
  53. if(val===(val=input.val()))
  54. return;
  55. // Enter new content into testSubject
  56. var escaped=val.replace(/\s/g,'&nbsp;'); // get proper width for values with leading spaces (or only spaces)
  57. testSubject.html(escaped);
  58. // Calculate new width + whether to change
  59. var testerWidth=testSubject.width(),
  60. newWidth=(testerWidth+o.comfortZone)>=minWidth ? testerWidth+o.comfortZone : minWidth,
  61. currentWidth=input.width(),
  62. isValidWidthChange=(newWidth<currentWidth && newWidth>=minWidth) || (newWidth>minWidth && newWidth<maxWidth);
  63. // Animate width
  64. if(isValidWidthChange)
  65. input.width(newWidth);
  66. };
  67. $.fn.resetAutosize=function(options)
  68. {
  69. // alert(JSON.stringify(options));
  70. var minWidth=$(this).data('minwidth') || options.minInputWidth || $(this).width(),
  71. maxWidth=$(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width()-options.inputPadding),
  72. val='',
  73. input=$(this),
  74. testSubject=$('<tester/>').css({
  75. position: 'absolute',
  76. top: -9999,
  77. left: -9999,
  78. width: 'auto',
  79. fontSize: input.css('fontSize'),
  80. fontFamily: input.css('fontFamily'),
  81. fontWeight: input.css('fontWeight'),
  82. letterSpacing: input.css('letterSpacing'),
  83. whiteSpace: 'nowrap'
  84. }),
  85. testerId=$(this).attr('id')+'_autosize_tester';
  86. if(!$('#'+testerId).length>0)
  87. {
  88. testSubject.attr('id', testerId);
  89. testSubject.appendTo('body');
  90. }
  91. input.data('minwidth', minWidth);
  92. input.data('maxwidth', maxWidth);
  93. input.data('tester_id', testerId);
  94. input.css('width', minWidth);
  95. };
  96. $.fn.addTag=function(value, options)
  97. {
  98. options=jQuery.extend({focus: false, callback: true, imported: false}, options);
  99. this.each(function()
  100. {
  101. var id=$(this).attr('id');
  102. if(tags_settings[id].allowDelimiterInValue==true)
  103. var tagslist=$(this).val().splitCustom(tags_settings[id].delimiter);
  104. else
  105. var tagslist=$(this).val().split(delimiter[id]);
  106. if(tagslist[0]=='')
  107. tagslist=new Array();
  108. if(options.trimInput==true)
  109. value=jQuery.trim(value);
  110. var skipTag=false;
  111. var duplicate=$(tagslist).tagExist(value);
  112. if(tags_callbacks[id] && tags_callbacks[id]['validateTag'])
  113. skipTag=!tags_callbacks[id]['validateTag'].call(this, value, options.imported, duplicate);
  114. if(!skipTag && options.unique)
  115. skipTag=duplicate;
  116. if(skipTag)
  117. $(this).parent().find('#'+id+'_tag').addClass('not_valid'); //Marks fake input as not_valid to let styling it
  118. if(value!='' && skipTag!=true)
  119. {
  120. $('<span>').addClass('tag').append(
  121. $('<span>').text(value),
  122. $('<a>', {
  123. href: '#',
  124. title: 'Removing tag',
  125. text: 'x'
  126. }).click(function(){return $('#'+id).removeTag(value)})
  127. ).insertBefore($(this).parent().find('#'+id+'_addTag'));
  128. tagslist.push(value);
  129. var tmpRef=$(this).parent().find('#'+id+'_tag');
  130. tmpRef.val('');
  131. if(options.focus)
  132. tmpRef.focus();
  133. else
  134. tmpRef.blur();
  135. $.fn.tagsInput.updateTagsField(this, tagslist);
  136. if(options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag'])
  137. {
  138. var f=tags_callbacks[id]['onAddTag'];
  139. f.call(this, value);
  140. }
  141. if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
  142. {
  143. var i=tagslist.length;
  144. var f=tags_callbacks[id]['onChange'];
  145. f.call(this, tagslist[i-1], options.imported);
  146. }
  147. }
  148. });
  149. return false;
  150. };
  151. $.fn.removeTag = function(value)
  152. {
  153. this.each(function()
  154. {
  155. var id=$(this).attr('id');
  156. if(tags_settings[id].allowDelimiterInValue==true)
  157. var old=$(this).val().splitCustom(tags_settings[id].delimiter);
  158. else
  159. var old=$(this).val().split(delimiter[id]);
  160. $(this).parent().find('#'+id+'_tagsinput .tag').remove();
  161. var str='';
  162. for(i=0; i<old.length; i++)
  163. if(old[i]!=value)
  164. str=(str=='' ? '' : str+tags_settings[id].delimiter)+(tags_settings[id].allowDelimiterInValue==true ? old[i].escapeCustom(tags_settings[id].delimiter) : old[i]);
  165. $.fn.tagsInput.importTags(this, str);
  166. if(tags_callbacks[id] && tags_callbacks[id]['onRemoveTag'])
  167. {
  168. var f=tags_callbacks[id]['onRemoveTag'];
  169. f.call(this, value);
  170. }
  171. });
  172. return false;
  173. };
  174. $.fn.tagExist=function(val)
  175. {
  176. return (jQuery.inArray(val, $(this))>=0); //true when tag exists, false when not
  177. };
  178. // clear all existing tags and import new ones from a string
  179. $.fn.importTags=function(str)
  180. {
  181. $(this).parent().find('#'+$(this).attr('id')+'_tagsinput .tag').remove();
  182. $.fn.tagsInput.importTags(this, str);
  183. }
  184. $.fn.tagsInput=function(options)
  185. {
  186. var settings=jQuery.extend({
  187. interactive: true,
  188. defaultText: 'add a tag',
  189. useNativePlaceholder:false,
  190. minChars: 0,
  191. width: '300px',
  192. height: '100px',
  193. autocomplete: {selectFirst: false},
  194. hide: true,
  195. delimiter: ',',
  196. allowDelimiterInValue: false,
  197. trimInput: true,
  198. unique: true,
  199. removeWithBackspace: true,
  200. color: '#000000',
  201. placeholderColor: '#666666',
  202. autosize: true,
  203. comfortZone: 20,
  204. inputPadding: 6*2
  205. }, options);
  206. this.each(function()
  207. {
  208. if(settings.hide)
  209. $(this).hide();
  210. var id=$(this).attr('id');
  211. var data=jQuery.extend({
  212. real_inputObj: $(this),
  213. pid: id,
  214. real_input: '#'+id,
  215. holder: '#'+id+'_tagsinput',
  216. input_wrapper: '#'+id+'_addTag',
  217. fake_input: '#'+id+'_tag'
  218. }, settings);
  219. tags_settings[id]={delimiter: data.delimiter, allowDelimiterInValue: data.allowDelimiterInValue};
  220. if(settings.onAddTag || settings.onRemoveTag || settings.onChange || settings.validateTag)
  221. {
  222. tags_callbacks[id] = new Array();
  223. tags_callbacks[id]['onAddTag'] = settings.onAddTag;
  224. tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
  225. tags_callbacks[id]['onChange'] = settings.onChange;
  226. tags_callbacks[id]['validateTag'] = settings.validateTag;
  227. }
  228. var markup='<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
  229. if(settings.interactive)
  230. markup=markup+'<div class="input_container"><input id="'+id+'_tag" type="text" value=""'+(settings.useNativePlaceholder==true ? ' placeholder="'+settings.defaultText+'" data-default=""' : ' data-default="'+settings.defaultText+'"')+' /></div>';
  231. markup=markup+'</div><div class="tags_clear"></div></div>';
  232. var tmpMarkupObj=$(markup).insertAfter(this);
  233. if(settings.width!=null)
  234. tmpMarkupObj.css('width', settings.width);
  235. if(settings.height!=null)
  236. tmpMarkupObj.css('height', settings.height);
  237. if($(this).val()!='')
  238. $.fn.tagsInput.importTags($(this), $(this).val());
  239. if(settings.interactive)
  240. {
  241. tmpMarkupObj.val(tmpMarkupObj.attr('data-default'));
  242. tmpMarkupObj.css('color', settings.placeholderColor);
  243. tmpMarkupObj.resetAutosize(settings);
  244. tmpMarkupObj.bind('click', data, function(event)
  245. {
  246. $(this).find(event.data.fake_input).focus();
  247. });
  248. tmpMarkupObj.find(data.fake_input).bind('focus', data, function(event)
  249. {
  250. if($(this).val() == $(this).attr('data-default'))
  251. $(this).val('');
  252. $(this).css('color', settings.color);
  253. });
  254. if(settings.autocomplete_url!=undefined)
  255. {
  256. var autocomplete_options={source: settings.autocomplete_url};
  257. for(var attrname in settings.autocomplete)
  258. autocomplete_options[attrname]=settings.autocomplete[attrname];
  259. if(jQuery.Autocompleter!==undefined)
  260. {
  261. tmpMarkupObj.find(data.fake_input).autocomplete(settings.autocomplete_url, settings.autocomplete);
  262. tmpMarkupObj.find(data.fake_input).bind('result', data, function(event, data, formatted)
  263. {
  264. if(data)
  265. event.data.real_inputObj.addTag(data[0] + "", {focus: true, unique: settings.unique, trimInput: settings.trimInput});
  266. });
  267. }
  268. else if(jQuery.ui.autocomplete!==undefined)
  269. {
  270. tmpMarkupObj.find(data.fake_input).autocomplete(autocomplete_options);
  271. tmpMarkupObj.find(data.fake_input).bind('autocompleteselect', data, function(event,ui)
  272. {
  273. event.data.real_inputObj.addTag(ui.item.value, {focus: true, unique: settings.unique, trimInput: settings.trimInput});
  274. return false;
  275. });
  276. }
  277. // if a user tabs out of the field, create a new tag
  278. // this is only available if autocomplete is not used.
  279. tmpMarkupObj.find(data.fake_input).bind('blur', data, function(event)
  280. {
  281. var d=$(this).attr('data-default');
  282. if($(this).val()!='' && $(this).val()!=d)
  283. {
  284. if((event.data.minChars<=$(this).val().length) && (!event.data.maxChars || (event.data.maxChars>=$(this).val().length)))
  285. event.data.real_inputObj.addTag($(this).val(), {focus: true, unique: settings.unique, trimInput: settings.trimInput});
  286. }
  287. else
  288. $(this).val($(this).attr('data-default'));
  289. $(this).css('color', settings.placeholderColor);
  290. return false;
  291. });
  292. }
  293. // if user types a comma, create a new tag
  294. tmpMarkupObj.find(data.fake_input).bind('keypress', data, function(event)
  295. {
  296. if(settings.allowDelimiterInValue==false && event.which==event.data.delimiter.charCodeAt(0) || event.which==13)
  297. {
  298. event.preventDefault();
  299. if((event.data.minChars<=$(this).val().length) && (!event.data.maxChars || (event.data.maxChars>=$(this).val().length)))
  300. event.data.real_inputObj.addTag($(event.data.fake_input).val(), {focus: true, unique: settings.unique, trimInput: settings.trimInput});
  301. $(this).resetAutosize(settings);
  302. return false;
  303. }
  304. else if(event.data.autosize)
  305. $(this).doAutosize(settings);
  306. });
  307. //Delete last tag on backspace
  308. data.removeWithBackspace && tmpMarkupObj.find(data.fake_input).bind('keydown', data, function(event)
  309. {
  310. if($(this).closest('.tagsinput').hasClass('readonly')==false && event.keyCode==8 && $(this).val()=='')
  311. {
  312. event.preventDefault();
  313. var last_tag=$(this).closest('.tagsinput').find('.tag:last').text();
  314. var id=$(this).attr('id').replace(/_tag$/, '');
  315. last_tag=last_tag.replace(/x$/, '');
  316. event.data.real_inputObj.removeTag(last_tag);
  317. $(this).trigger('focus');
  318. }
  319. });
  320. tmpMarkupObj.find(data.fake_input).blur();
  321. //Removes the not_valid class when user changes the value of the fake input
  322. if(data.unique)
  323. {
  324. tmpMarkupObj.find(data.fake_input).keydown(function(event)
  325. {
  326. if(event.keyCode==8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/))
  327. $(this).removeClass('not_valid');
  328. });
  329. }
  330. } // if settings.interactive
  331. // store settings
  332. $(this).data('tagsOptions', settings);
  333. return false;
  334. });
  335. return this;
  336. };
  337. $.fn.tagsInput.updateTagsField=function(obj, tagslist)
  338. {
  339. var id = $(obj).attr('id');
  340. if(tags_settings[id].allowDelimiterInValue==true)
  341. for(var i=0;i<tagslist.length;i++)
  342. tagslist[i]=tagslist[i].escapeCustom(tags_settings[id].delimiter);
  343. $(obj).val(tagslist.join(tags_settings[id].delimiter));
  344. };
  345. $.fn.tagsInput.importTags=function(obj, val)
  346. {
  347. var settings=jQuery.extend({
  348. trimInput: true,
  349. unique: true
  350. }, $(obj).data('tagsOptions'));
  351. $(obj).val('');
  352. var id=$(obj).attr('id');
  353. if(tags_settings[id].allowDelimiterInValue==true)
  354. var tags=val.splitCustom(tags_settings[id].delimiter);
  355. else
  356. var tags=val.split(delimiter[id]);
  357. for(var i=0; i<tags.length; i++)
  358. $(obj).addTag(tags[i], {focus: true, unique: settings.unique, trimInput: settings.trimInput, callback: false, imported: true});
  359. if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
  360. {
  361. var f=tags_callbacks[id]['onChange'];
  362. f.call(obj, tags[i], true);
  363. }
  364. };
  365. })(jQuery);