You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jquery.tagedit.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*
  2. * Tagedit - jQuery Plugin
  3. * The Plugin can be used to edit tags from a database the easy way
  4. *
  5. * Examples and documentation at: tagedit.webwork-albrecht.de
  6. *
  7. * License:
  8. * This work is licensed under a MIT License
  9. *
  10. * @licstart The following is the entire license notice for the
  11. * JavaScript code in this file.
  12. *
  13. * Copyright (c) 2010 Oliver Albrecht <info@webwork-albrecht.de>
  14. * Copyright (c) 2014 Thomas Brüderli <thomas@roundcube.net>
  15. *
  16. * Licensed under the MIT licenses
  17. *
  18. * Permission is hereby granted, free of charge, to any person obtaining
  19. * a copy of this software and associated documentation files (the
  20. * "Software"), to deal in the Software without restriction, including
  21. * without limitation the rights to use, copy, modify, merge, publish,
  22. * distribute, sublicense, and/or sell copies of the Software, and to
  23. * permit persons to whom the Software is furnished to do so, subject to
  24. * the following conditions:
  25. *
  26. * The above copyright notice and this permission notice shall be
  27. * included in all copies or substantial portions of the Software.
  28. *
  29. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  30. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  31. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  32. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  33. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  34. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  35. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  36. *
  37. * @licend The above is the entire license notice
  38. * for the JavaScript code in this file.
  39. *
  40. * @author Oliver Albrecht Mial: info@webwork-albrecht.de Twitter: @webworka
  41. * @version 1.5.2 (06/2014)
  42. * Requires: jQuery v1.4+, jQueryUI v1.8+, jQuerry.autoGrowInput
  43. *
  44. * Example of usage:
  45. *
  46. * $( "input.tag" ).tagedit();
  47. *
  48. * Possible options:
  49. *
  50. * autocompleteURL: '', // url for a autocompletion
  51. * deleteEmptyItems: true, // Deletes items with empty value
  52. * deletedPostfix: '-d', // will be put to the Items that are marked as delete
  53. * addedPostfix: '-a', // will be put to the Items that are choosem from the database
  54. * additionalListClass: '', // put a classname here if the wrapper ul shoud receive a special class
  55. * allowEdit: true, // Switch on/off edit entries
  56. * allowDelete: true, // Switch on/off deletion of entries. Will be ignored if allowEdit = false
  57. * allowAdd: true, // switch on/off the creation of new entries
  58. * direction: 'ltr' // Sets the writing direction for Outputs and Inputs
  59. * animSpeed: 500 // Sets the animation speed for effects
  60. * autocompleteOptions: {}, // Setting Options for the jquery UI Autocomplete (http://jqueryui.com/demos/autocomplete/)
  61. * breakKeyCodes: [ 13, 44 ], // Sets the characters to break on to parse the tags (defaults: return, comma)
  62. * checkNewEntriesCaseSensitive: false, // If there is a new Entry, it is checked against the autocompletion list. This Flag controlls if the check is (in-)casesensitive
  63. * texts: { // some texts
  64. * removeLinkTitle: 'Remove from list.',
  65. * saveEditLinkTitle: 'Save changes.',
  66. * deleteLinkTitle: 'Delete this tag from database.',
  67. * deleteConfirmation: 'Are you sure to delete this entry?',
  68. * deletedElementTitle: 'This Element will be deleted.',
  69. * breakEditLinkTitle: 'Cancel'
  70. * }
  71. */
  72. (function($) {
  73. $.fn.tagedit = function(options) {
  74. /**
  75. * Merge Options with defaults
  76. */
  77. options = $.extend(true, {
  78. // default options here
  79. autocompleteURL: null,
  80. checkToDeleteURL: null,
  81. deletedPostfix: '-d',
  82. addedPostfix: '-a',
  83. additionalListClass: '',
  84. allowEdit: true,
  85. allowDelete: true,
  86. allowAdd: true,
  87. direction: 'ltr',
  88. animSpeed: 500,
  89. autocompleteOptions: {
  90. select: function( event, ui ) {
  91. $(this).val(ui.item.value).trigger('transformToTag', [ui.item.id]);
  92. return false;
  93. }
  94. },
  95. breakKeyCodes: [ 13, 44 ],
  96. checkNewEntriesCaseSensitive: false,
  97. texts: {
  98. removeLinkTitle: 'Remove from list.',
  99. saveEditLinkTitle: 'Save changes.',
  100. deleteLinkTitle: 'Delete this tag from database.',
  101. deleteConfirmation: 'Are you sure to delete this entry?',
  102. deletedElementTitle: 'This Element will be deleted.',
  103. breakEditLinkTitle: 'Cancel',
  104. forceDeleteConfirmation: 'There are more records using this tag, are you sure do you want to remove it?'
  105. },
  106. tabindex: false
  107. }, options || {});
  108. // no action if there are no elements
  109. if(this.length == 0) {
  110. return;
  111. }
  112. // set the autocompleteOptions source
  113. if(options.autocompleteURL) {
  114. options.autocompleteOptions.source = options.autocompleteURL;
  115. }
  116. // Set the direction of the inputs
  117. var direction= this.attr('dir');
  118. if(direction && direction.length > 0) {
  119. options.direction = this.attr('dir');
  120. }
  121. var elements = this;
  122. var focusItem = null;
  123. var baseNameRegexp = new RegExp("^(.*)\\[([0-9]*?("+options.deletedPostfix+"|"+options.addedPostfix+")?)?\]$", "i");
  124. var baseName = elements.eq(0).attr('name').match(baseNameRegexp);
  125. if(baseName && baseName.length == 4) {
  126. baseName = baseName[1];
  127. }
  128. else {
  129. // Elementname does not match the expected format, exit
  130. alert('elementname dows not match the expected format (regexp: '+baseNameRegexp+')')
  131. return;
  132. }
  133. // read tabindex from source element
  134. var ti;
  135. if (!options.tabindex && (ti = elements.eq(0).attr('tabindex')))
  136. options.tabindex = ti;
  137. // init elements
  138. inputsToList();
  139. /**
  140. * Creates the tageditinput from a list of textinputs
  141. *
  142. */
  143. function inputsToList() {
  144. var html = '<ul class="tagedit-list '+options.additionalListClass+'">';
  145. elements.each(function(i) {
  146. var element_name = $(this).attr('name').match(baseNameRegexp);
  147. if(element_name && element_name.length == 4 && (options.deleteEmptyItems == false || $(this).val().length > 0)) {
  148. if(element_name[1].length > 0) {
  149. var elementId = typeof element_name[2] != 'undefined'? element_name[2]: '',
  150. domId = 'tagedit-' + baseName + '-' + (elementId || i);
  151. html += '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
  152. html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
  153. html += '<input type="hidden" name="'+baseName+'['+elementId+']" value="'+$(this).val()+'" />';
  154. if (options.allowDelete)
  155. html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
  156. html += '</li>';
  157. }
  158. }
  159. });
  160. // replace Elements with the list and save the list in the local variable elements
  161. elements.last().after(html)
  162. var newList = elements.last().next();
  163. elements.remove();
  164. elements = newList;
  165. // Check if some of the elementshav to be marked as deleted
  166. if(options.deletedPostfix.length > 0) {
  167. elements.find('input[name$="'+options.deletedPostfix+'\]"]').each(function() {
  168. markAsDeleted($(this).parent());
  169. });
  170. }
  171. // put an input field at the End
  172. // Put an empty element at the end
  173. html = '<li class="tagedit-listelement tagedit-listelement-new">';
  174. if (options.allowAdd)
  175. html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
  176. html += '</li>';
  177. html += '</ul>';
  178. elements
  179. .append(html)
  180. .attr('tabindex', options.tabindex) // set tabindex to <ul> to recieve focus
  181. // Set function on the input
  182. .find('#tagedit-input')
  183. .attr('tabindex', options.tabindex)
  184. .each(function() {
  185. $(this).autoGrowInput({comfortZone: 15, minWidth: 15, maxWidth: 20000});
  186. // Event is triggert in case of choosing an item from the autocomplete, or finish the input
  187. $(this).bind('transformToTag', function(event, id) {
  188. var oldValue = (typeof id != 'undefined' && (id.length > 0 || id > 0));
  189. var checkAutocomplete = oldValue == true || options.autocompleteOptions.noCheck ? false : true;
  190. // check if the Value ist new
  191. var isNewResult = isNew($(this).val(), checkAutocomplete);
  192. if(isNewResult[0] === true || (isNewResult[0] === false && typeof isNewResult[1] == 'string')) {
  193. if(oldValue == false && typeof isNewResult[1] == 'string') {
  194. oldValue = true;
  195. id = isNewResult[1];
  196. }
  197. if(options.allowAdd == true || oldValue) {
  198. var domId = 'tagedit-' + baseName + '-' + id;
  199. // Make a new tag in front the input
  200. html = '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
  201. html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
  202. var name = oldValue? baseName + '['+id+options.addedPostfix+']' : baseName + '[]';
  203. html += '<input type="hidden" name="'+name+'" value="'+$(this).val()+'" />';
  204. html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
  205. html += '</li>';
  206. $(this).parent().before(html);
  207. }
  208. }
  209. $(this).val('');
  210. // close autocomplete
  211. if(options.autocompleteOptions.source) {
  212. if($(this).is(':ui-autocomplete'))
  213. $(this).autocomplete( "close" );
  214. }
  215. })
  216. .keydown(function(event) {
  217. var code = event.keyCode > 0? event.keyCode : event.which;
  218. switch(code) {
  219. case 46:
  220. if (!focusItem)
  221. break;
  222. case 8: // BACKSPACE
  223. if(focusItem) {
  224. focusItem.fadeOut(options.animSpeed, function() {
  225. $(this).remove();
  226. })
  227. unfocusItem();
  228. event.preventDefault();
  229. return false;
  230. }
  231. else if($(this).val().length == 0) {
  232. // delete Last Tag
  233. var elementToRemove = elements.find('li.tagedit-listelement-old').last();
  234. elementToRemove.fadeOut(options.animSpeed, function() {elementToRemove.remove();})
  235. event.preventDefault();
  236. return false;
  237. }
  238. break;
  239. case 9: // TAB
  240. if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
  241. $(this).trigger('transformToTag');
  242. event.preventDefault();
  243. return false;
  244. }
  245. break;
  246. case 37: // LEFT
  247. case 39: // RIGHT
  248. if($(this).val().length == 0) {
  249. // select previous Tag
  250. var inc = code == 37 ? -1 : 1,
  251. items = elements.find('li.tagedit-listelement-old')
  252. x = items.length, next = 0;
  253. items.each(function(i, elem) {
  254. if ($(elem).hasClass('tagedit-listelement-focus')) {
  255. x = i;
  256. return true;
  257. }
  258. });
  259. unfocusItem();
  260. next = Math.max(0, x + inc);
  261. if (items.get(next)) {
  262. focusItem = items.eq(next).addClass('tagedit-listelement-focus');
  263. $(this).attr('aria-activedescendant', focusItem.attr('aria-labelledby'))
  264. if(options.autocompleteOptions.source != false) {
  265. $(this).autocomplete('close').autocomplete('disable');
  266. }
  267. }
  268. event.preventDefault();
  269. return false;
  270. }
  271. break;
  272. default:
  273. // ignore input if an item is focused
  274. if (focusItem !== null) {
  275. event.preventDefault();
  276. event.bubble = false;
  277. return false;
  278. }
  279. }
  280. return true;
  281. })
  282. .keypress(function(event) {
  283. var code = event.keyCode > 0? event.keyCode : event.which;
  284. if($.inArray(code, options.breakKeyCodes) > -1) {
  285. if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
  286. $(this).trigger('transformToTag');
  287. }
  288. event.preventDefault();
  289. return false;
  290. }
  291. else if($(this).val().length > 0){
  292. unfocusItem();
  293. }
  294. return true;
  295. })
  296. .bind('paste', function(e){
  297. var that = $(this);
  298. if (e.type == 'paste'){
  299. setTimeout(function(){
  300. that.trigger('transformToTag');
  301. }, 1);
  302. }
  303. })
  304. .blur(function() {
  305. if($(this).val().length == 0) {
  306. // disable the field to prevent sending with the form
  307. $(this).attr('disabled', 'disabled').addClass('tagedit-input-disabled');
  308. }
  309. else {
  310. // Delete entry after a timeout
  311. var input = $(this);
  312. $(this).data('blurtimer', window.setTimeout(function() {input.val('');}, 500));
  313. }
  314. unfocusItem();
  315. // restore tabindex when widget looses focus
  316. if (options.tabindex)
  317. elements.attr('tabindex', options.tabindex);
  318. })
  319. .focus(function() {
  320. window.clearTimeout($(this).data('blurtimer'));
  321. // remove tabindex on <ul> because #tagedit-input now has it
  322. elements.attr('tabindex', '-1');
  323. });
  324. if(options.autocompleteOptions.source != false) {
  325. $(this).autocomplete(options.autocompleteOptions);
  326. }
  327. })
  328. .end()
  329. .click(function(event) {
  330. switch(event.target.tagName) {
  331. case 'A':
  332. $(event.target).parent().fadeOut(options.animSpeed, function() {
  333. $(event.target).parent().remove();
  334. elements.find('#tagedit-input').focus();
  335. });
  336. break;
  337. case 'INPUT':
  338. case 'SPAN':
  339. case 'LI':
  340. if($(event.target).hasClass('tagedit-listelement-deleted') == false &&
  341. $(event.target).parent('li').hasClass('tagedit-listelement-deleted') == false) {
  342. // Don't edit an deleted Items
  343. return doEdit(event);
  344. }
  345. default:
  346. $(this).find('#tagedit-input')
  347. .removeAttr('disabled')
  348. .removeClass('tagedit-input-disabled')
  349. .focus();
  350. }
  351. return false;
  352. })
  353. // forward focus event (on tabbing through the form)
  354. .focus(function(e){ $(this).click(); })
  355. }
  356. /**
  357. * Remove class and reference to currently focused tag item
  358. */
  359. function unfocusItem() {
  360. if(focusItem){
  361. if(options.autocompleteOptions.source != false) {
  362. elements.find('#tagedit-input').autocomplete('enable');
  363. }
  364. focusItem.removeClass('tagedit-listelement-focus');
  365. focusItem = null;
  366. elements.find('#tagedit-input').removeAttr('aria-activedescendant');
  367. }
  368. }
  369. /**
  370. * Sets all Actions and events for editing an Existing Tag.
  371. *
  372. * @param event {object} The original Event that was given
  373. * return {boolean}
  374. */
  375. function doEdit(event) {
  376. if(options.allowEdit == false) {
  377. // Do nothing
  378. return;
  379. }
  380. var element = event.target.tagName == 'SPAN'? $(event.target).parent() : $(event.target);
  381. var closeTimer = null;
  382. // Event that is fired if the User finishes the edit of a tag
  383. element.bind('finishEdit', function(event, doReset) {
  384. window.clearTimeout(closeTimer);
  385. var textfield = $(this).find(':text');
  386. var isNewResult = isNew(textfield.val(), true);
  387. if(textfield.val().length > 0 && (typeof doReset == 'undefined' || doReset === false) && (isNewResult[0] == true)) {
  388. // This is a new Value and we do not want to do a reset. Set the new value
  389. $(this).find(':hidden').val(textfield.val());
  390. $(this).find('span').html(textfield.val());
  391. }
  392. textfield.remove();
  393. $(this).find('a.tagedit-save, a.tagedit-break, a.tagedit-delete').remove(); // Workaround. This normaly has to be done by autogrow Plugin
  394. $(this).removeClass('tagedit-listelement-edit').unbind('finishEdit');
  395. return false;
  396. });
  397. var hidden = element.find(':hidden');
  398. html = '<input type="text" name="tmpinput" autocomplete="off" value="'+hidden.val()+'" class="tagedit-edit-input" dir="'+options.direction+'"/>';
  399. html += '<a class="tagedit-save" title="'+options.texts.saveEditLinkTitle+'">o</a>';
  400. html += '<a class="tagedit-break" title="'+options.texts.breakEditLinkTitle+'">x</a>';
  401. // If the Element is one from the Database, it can be deleted
  402. if(options.allowDelete == true && element.find(':hidden').length > 0 &&
  403. typeof element.find(':hidden').attr('name').match(baseNameRegexp)[3] != 'undefined') {
  404. html += '<a class="tagedit-delete" title="'+options.texts.deleteLinkTitle+'">d</a>';
  405. }
  406. hidden.after(html);
  407. element
  408. .addClass('tagedit-listelement-edit')
  409. .find('a.tagedit-save')
  410. .click(function() {
  411. $(this).parent().trigger('finishEdit');
  412. return false;
  413. })
  414. .end()
  415. .find('a.tagedit-break')
  416. .click(function() {
  417. $(this).parent().trigger('finishEdit', [true]);
  418. return false;
  419. })
  420. .end()
  421. .find('a.tagedit-delete')
  422. .click(function() {
  423. window.clearTimeout(closeTimer);
  424. if(confirm(options.texts.deleteConfirmation)) {
  425. var canDelete = checkToDelete($(this).parent());
  426. if (!canDelete && confirm(options.texts.forceDeleteConfirmation)) {
  427. markAsDeleted($(this).parent());
  428. }
  429. if(canDelete) {
  430. markAsDeleted($(this).parent());
  431. }
  432. $(this).parent().find(':text').trigger('finishEdit', [true]);
  433. }
  434. else {
  435. $(this).parent().find(':text').trigger('finishEdit', [true]);
  436. }
  437. return false;
  438. })
  439. .end()
  440. .find(':text')
  441. .focus()
  442. .autoGrowInput({comfortZone: 10, minWidth: 15, maxWidth: 20000})
  443. .keypress(function(event) {
  444. switch(event.keyCode) {
  445. case 13: // RETURN
  446. event.preventDefault();
  447. $(this).parent().trigger('finishEdit');
  448. return false;
  449. case 27: // ESC
  450. event.preventDefault();
  451. $(this).parent().trigger('finishEdit', [true]);
  452. return false;
  453. }
  454. return true;
  455. })
  456. .blur(function() {
  457. var that = $(this);
  458. closeTimer = window.setTimeout(function() {that.parent().trigger('finishEdit', [true])}, 500);
  459. });
  460. }
  461. /**
  462. * Verifies if the tag select to be deleted is used by other records using an Ajax request.
  463. *
  464. * @param element
  465. * @returns {boolean}
  466. */
  467. function checkToDelete(element) {
  468. // if no URL is provide will not verify
  469. if(options.checkToDeleteURL === null) {
  470. return false;
  471. }
  472. var inputName = element.find('input:hidden').attr('name');
  473. var idPattern = new RegExp('\\d');
  474. var tagId = inputName.match(idPattern);
  475. var checkResult = false;
  476. $.ajax({
  477. async : false,
  478. url : options.checkToDeleteURL,
  479. dataType: 'json',
  480. type : 'POST',
  481. data : { 'tagId' : tagId},
  482. complete: function (XMLHttpRequest, textStatus) {
  483. // Expected JSON Object: { "success": Boolean, "allowDelete": Boolean}
  484. var result = $.parseJSON(XMLHttpRequest.responseText);
  485. if(result.success === true){
  486. checkResult = result.allowDelete;
  487. }
  488. }
  489. });
  490. return checkResult;
  491. }
  492. /**
  493. * Marks a single Tag as deleted.
  494. *
  495. * @param element {object}
  496. */
  497. function markAsDeleted(element) {
  498. element
  499. .trigger('finishEdit', [true])
  500. .addClass('tagedit-listelement-deleted')
  501. .attr('title', options.deletedElementTitle);
  502. element.find(':hidden').each(function() {
  503. var nameEndRegexp = new RegExp('('+options.addedPostfix+'|'+options.deletedPostfix+')?\]');
  504. var name = $(this).attr('name').replace(nameEndRegexp, options.deletedPostfix+']');
  505. $(this).attr('name', name);
  506. });
  507. }
  508. /**
  509. * Checks if a tag is already choosen.
  510. *
  511. * @param value {string}
  512. * @param checkAutocomplete {boolean} optional Check also the autocomplet values
  513. * @returns {Array} First item is a boolean, telling if the item should be put to the list, second is optional the ID from autocomplete list
  514. */
  515. function isNew(value, checkAutocomplete) {
  516. checkAutocomplete = typeof checkAutocomplete == 'undefined'? false : checkAutocomplete;
  517. var autoCompleteId = null;
  518. var compareValue = options.checkNewEntriesCaseSensitive == true? value : value.toLowerCase();
  519. var isNew = true;
  520. elements.find('li.tagedit-listelement-old input:hidden').each(function() {
  521. var elementValue = options.checkNewEntriesCaseSensitive == true? $(this).val() : $(this).val().toLowerCase();
  522. if(elementValue == compareValue) {
  523. isNew = false;
  524. }
  525. });
  526. if (isNew == true && checkAutocomplete == true && options.autocompleteOptions.source != false) {
  527. var result = [];
  528. if ($.isArray(options.autocompleteOptions.source)) {
  529. result = options.autocompleteOptions.source;
  530. }
  531. else if ($.isFunction(options.autocompleteOptions.source)) {
  532. options.autocompleteOptions.source({term: value}, function (data) {result = data});
  533. }
  534. else if (typeof options.autocompleteOptions.source === "string") {
  535. // Check also autocomplete values
  536. var autocompleteURL = options.autocompleteOptions.source;
  537. if (autocompleteURL.match(/\?/)) {
  538. autocompleteURL += '&';
  539. } else {
  540. autocompleteURL += '?';
  541. }
  542. autocompleteURL += 'term=' + value;
  543. $.ajax({
  544. async: false,
  545. url: autocompleteURL,
  546. dataType: 'json',
  547. complete: function (XMLHttpRequest, textStatus) {
  548. result = $.parseJSON(XMLHttpRequest.responseText);
  549. }
  550. });
  551. }
  552. // If there is an entry for that already in the autocomplete, don't use it (Check could be case sensitive or not)
  553. for (var i = 0; i < result.length; i++) {
  554. var resultValue = result[i].label? result[i].label : result[i];
  555. var label = options.checkNewEntriesCaseSensitive == true? resultValue : resultValue.toLowerCase();
  556. if (label == compareValue) {
  557. isNew = false;
  558. autoCompleteId = typeof result[i] == 'string' ? i : result[i].id;
  559. break;
  560. }
  561. }
  562. }
  563. return new Array(isNew, autoCompleteId);
  564. }
  565. }
  566. })(jQuery);
  567. (function($){
  568. // jQuery autoGrowInput plugin by James Padolsey
  569. // See related thread: http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
  570. $.fn.autoGrowInput = function(o) {
  571. o = $.extend({
  572. maxWidth: 1000,
  573. minWidth: 0,
  574. comfortZone: 70
  575. }, o);
  576. this.filter('input:text').each(function(){
  577. var minWidth = o.minWidth || $(this).width(),
  578. val = '',
  579. input = $(this),
  580. testSubject = $('<tester/>').css({
  581. position: 'absolute',
  582. top: -9999,
  583. left: -9999,
  584. width: 'auto',
  585. fontSize: input.css('fontSize'),
  586. fontFamily: input.css('fontFamily'),
  587. fontWeight: input.css('fontWeight'),
  588. letterSpacing: input.css('letterSpacing'),
  589. whiteSpace: 'nowrap'
  590. }),
  591. check = function() {
  592. if (val === (val = input.val())) {return;}
  593. // Enter new content into testSubject
  594. var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,'&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  595. testSubject.html(escaped);
  596. // Calculate new width + whether to change
  597. var testerWidth = testSubject.width(),
  598. newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
  599. currentWidth = input.width(),
  600. isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
  601. || (newWidth > minWidth && newWidth < o.maxWidth);
  602. // Animate width
  603. if (isValidWidthChange) {
  604. input.width(newWidth);
  605. }
  606. };
  607. testSubject.insertAfter(input);
  608. $(this).bind('keyup keydown blur update', check);
  609. check();
  610. });
  611. return this;
  612. };
  613. })(jQuery);