autocomplete_deluxe.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /**
  2. * @file:
  3. * Converts textfield to a autocomplete deluxe widget.
  4. */
  5. (function($) {
  6. Drupal.autocomplete_deluxe = Drupal.autocomplete_deluxe || {};
  7. Drupal.behaviors.autocomplete_deluxe = {
  8. attach: function(context) {
  9. var autocomplete_settings = Drupal.settings.autocomplete_deluxe;
  10. $('input.autocomplete-deluxe-form').once( function() {
  11. new Drupal.autocomplete_deluxe(this, autocomplete_settings[$(this).attr('id')]);
  12. });
  13. }
  14. };
  15. /**
  16. * Autocomplete deluxe object.
  17. */
  18. Drupal.autocomplete_deluxe = function(input, settings) {
  19. this.id = settings.input_id;
  20. this.jqObject = $('#' + this.id);
  21. this.jqObject.addClass('ui-corner-left');
  22. this.type = settings.type;
  23. this.minLength = settings.min_length;
  24. this.multiple = settings.multiple;
  25. this.opendByFocus = false;
  26. this.keyPress = false;
  27. if (settings.select_input !== undefined) {
  28. this.selectInput = $('#' + settings.select_input);
  29. this.selectInput.hide();
  30. this.jqObject.show();
  31. }
  32. this.button = $('<span>&nbsp;</span>');
  33. this.button.attr({
  34. 'tabIndex': -1,
  35. 'title': 'Show all items'
  36. });
  37. this.button.insertAfter(this.jqObject);
  38. this.button.button({
  39. icons: {
  40. primary: 'ui-icon-triangle-1-s'
  41. },
  42. text: false
  43. });
  44. // Don't round the left corners.
  45. this.button.removeClass('ui-corner-all');
  46. this.button.addClass('ui-corner-right ui-button-icon autocomplete-deluxe-button');
  47. this.jqObject.autocomplete();
  48. this.jqObject.autocomplete("option", "minLength", this.minLength);
  49. // Add a custom class, so we can style the autocomplete box without
  50. // interfering with other jquery autocomplete widgets.
  51. this.jqObject.autocomplete("widget").addClass('autocomplete-deluxe-widget');
  52. this.hasFocus = false;
  53. // Save the current autocomplete object, so it can be used in
  54. // handlers.
  55. var instance = this;
  56. // Override enter keypress for the form, but only when the input hasn't
  57. // the focus and it isn\t empty.
  58. $('form').keypress(function(event) {
  59. if (event.keyCode == 13 && instance.hasFocus && instance.jqObject.val() !== "") return false;
  60. });
  61. // Event handlers.
  62. this.jqObject.focus( function(eventObject) {
  63. // Overlay causes a second focus, wich has to be instance.catched.
  64. if (eventObject.originalEvent === undefined) {
  65. return false;
  66. }
  67. instance.open();
  68. instance.opendByFocus = true;
  69. });
  70. this.jqObject.focusin(function(){
  71. instance.hasFocus = true;
  72. })
  73. this.jqObject.focusout(function(){
  74. instance.hasFocus = false;
  75. })
  76. // Needed when the window is closed but the textfield has the focus.
  77. this.jqObject.click( function() {
  78. if (!instance.opendByFocus) {
  79. instance.toggle();
  80. }
  81. else {
  82. instance.opendByFocus = false;
  83. }
  84. });
  85. // Create the source item, set its options and elements and initialize it.
  86. switch (this.type) {
  87. case 'ajax':
  88. var uri = settings.uri;
  89. this.source = new Drupal.autocomplete_deluxe.ajaxSource(uri);
  90. break;
  91. case 'list':
  92. this.source = new Drupal.autocomplete_deluxe.listSource(settings.data, this.selectInput);
  93. break;
  94. }
  95. this.source.autocomplete = this;
  96. this.source.multiple = settings.multiple;
  97. if (this.multiple) {
  98. this.source.container = this.jqObject.parent().parent().children('.autocomplete-deluxe-values');
  99. }
  100. this.source.init();
  101. this.jqObject.autocomplete("option", "source", function(request, response) {
  102. instance.source.setResponse(request, response);
  103. });
  104. this.jqObject.bind("autocompletesearch", function(event, ui) {
  105. instance.jqObject.addClass('throbbing');
  106. return ui;
  107. });
  108. // Don't set the value input when autocomplete window has focus.
  109. this.jqObject.bind("autocompletefocus", function(event, ui) {
  110. return false;
  111. });
  112. this.jqObject.bind("autocompletechange", function(event, ui) {
  113. instance.source.change(event, ui);
  114. });
  115. this.jqObject.bind("autocompleteselect", function(event, ui) {
  116. instance.close();
  117. instance.opendByFocus = false;
  118. var ret = instance.source._select(this, ui);
  119. if (instance.keyPress === false) {
  120. this.blur();
  121. } else {
  122. instance.keyPress = false;
  123. }
  124. return ret;
  125. });
  126. this.jqObject.blur(function() {
  127. if (!instance.jqObject.autocomplete("widget").is(":visible")) {
  128. var val = instance.jqObject.val();
  129. if (val.substring(val.length, val.length - 2) == ', ') {
  130. instance.jqObject.val(val.substring(0, val.length - 2));
  131. }
  132. }
  133. });
  134. this.jqObject.keypress(function(event) {
  135. instance.keyPress = true;
  136. instance.source.keypress(event);
  137. });
  138. this.jqObject.keyup(function(event) {
  139. if (instance.multiple && event.which == 188 || event.which == 13) {
  140. instance.jqObject.val('');
  141. instance.close();
  142. }
  143. });
  144. // Since jquery autocomplete by default strips html text by using .text()
  145. // we need our own _renderItem function to display html content.
  146. this.jqObject.data("autocomplete")._renderItem = function(ul, item) {
  147. return $("<li></li>").data("item.autocomplete", item).append("<a>" + item.label + "</a>").appendTo(ul);
  148. };
  149. // Costume response callback to delete the throbbing class. Could be deleted
  150. // once the ui-autocomplete-loading css class is implemented in seven theme.
  151. this.jqObject.data("autocomplete")._response = function(content) {
  152. if (content && content.length) {
  153. content = this._normalize(content);
  154. this._suggest(content);
  155. this._trigger("open");
  156. }
  157. else {
  158. this.close();
  159. }
  160. this.element.removeClass('throbbing');
  161. };
  162. this.button.click( function() {
  163. instance.jqObject.blur();
  164. instance.toggle(true);
  165. });
  166. };
  167. /**
  168. * EscapeRegex function from jquery autocomplete, is not included in drupal.
  169. */
  170. Drupal.autocomplete_deluxe.escapeRegex = function(value) {
  171. return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/gi, "\\$&");
  172. };
  173. /**
  174. * Filter function from jquery autocomplete, is not included in drupal.
  175. */
  176. Drupal.autocomplete_deluxe.filter = function(array, term) {
  177. var matcher = new RegExp(Drupal.autocomplete_deluxe.escapeRegex(term), "i");
  178. return $.grep(array, function(value) {
  179. return matcher.test(value.label || value.value || value);
  180. });
  181. };
  182. /**
  183. * Open the autocomplete window.
  184. * @param emptySearch If true it will perform an empty search.
  185. */
  186. Drupal.autocomplete_deluxe.prototype.open = function(emptySearch) {
  187. if ( emptySearch !== undefined) {
  188. var item = this.jqObject.val();
  189. var searchFor = item.substring(0, this.minLength);
  190. }
  191. else {
  192. var searchFor = this.jqObject.val();
  193. }
  194. this.jqObject.autocomplete("search", searchFor);
  195. this.button.addClass("ui-state-focus");
  196. };
  197. /**
  198. * Close the autocomplete window.
  199. */
  200. Drupal.autocomplete_deluxe.prototype.close = function() {
  201. this.jqObject.autocomplete("close");
  202. this.button.removeClass("ui-state-focus");
  203. this.opendByFocus = false;
  204. };
  205. /**
  206. * Toggle the autcomplete window.
  207. * @param emptySearch If true and autocomplete is opening, it will perform an
  208. * empty search.
  209. */
  210. Drupal.autocomplete_deluxe.prototype.toggle = function(emptySearch) {
  211. if (this.jqObject.autocomplete("widget").is(":visible")) {
  212. this.close();
  213. }
  214. else {
  215. this.open(emptySearch);
  216. }
  217. };
  218. /**
  219. * Handles value elements for multiple entries.
  220. */
  221. Drupal.autocomplete_deluxe.value = function(value, source) {
  222. this.removeLink = $('<span class="autocomplete-deluxe-value-delete">&nbsp;</span>')
  223. this.span = $('<span class="autocomplete-deluxe-value ui-corner-all ui-button ui-state-default">' + value + '</span>');
  224. this.value = value;
  225. this.source = source;
  226. source.container.append(this.span);
  227. this.span.append(this.removeLink);
  228. var object = this;
  229. this.removeLink.click(function(){
  230. object.span.remove();
  231. object.source.removeValue(object.value)
  232. });
  233. };
  234. Drupal.autocomplete_deluxe.value.prototype.value = null;
  235. Drupal.autocomplete_deluxe.value.prototype.span = null;
  236. Drupal.autocomplete_deluxe.value.prototype.source = null;
  237. /**
  238. * Main abstract source object.
  239. */
  240. Drupal.autocomplete_deluxe.source = function() {
  241. };
  242. // Some base settings for all source objects.
  243. Drupal.autocomplete_deluxe.source.prototype.autocomplete = null;
  244. Drupal.autocomplete_deluxe.source.prototype.multiple = false;
  245. Drupal.autocomplete_deluxe.source.prototype.container = undefined;
  246. Drupal.autocomplete_deluxe.source.prototype.highlight = function(term, data) {
  247. // If no term is entered, we want to return the data as it is.
  248. if (term == '') {
  249. return data;
  250. }
  251. else {
  252. // Create a new data array, so we can keep our original data clean
  253. // (without <strong> tags).
  254. var newData = new Array();
  255. var regex = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi");
  256. for ( var i in data) {
  257. var nterm = data[i].label.replace(regex, "<strong>$1</strong>");
  258. newData.push({
  259. label: nterm,
  260. value: data[i].value
  261. });
  262. }
  263. return newData;
  264. }
  265. };
  266. /**
  267. * Sets the source element from the autocomplete deluxe object.
  268. */
  269. Drupal.autocomplete_deluxe.source.prototype.response = function(request, response, data) {
  270. response(this.highlight(request.term, data));
  271. };
  272. /**
  273. * Select super function. Super because, in contrary to the .select()
  274. * function this function will be always called, not depending on the source
  275. * objects type.
  276. */
  277. Drupal.autocomplete_deluxe.source.prototype._select = function(input, ui) {
  278. // Strip the strong tags from the label.
  279. ui.item.label = $("<span>" + ui.item.label + "</span>").text();
  280. this.select(input, ui);
  281. if (this.multiple) {
  282. this.addValue(this.autocomplete.jqObject.val());
  283. input.value = '';
  284. }
  285. return false;
  286. };
  287. /**
  288. * Initialization function for setting the default states.
  289. */
  290. Drupal.autocomplete_deluxe.source.prototype.init = function() {
  291. };
  292. /**
  293. * Select event function.
  294. */
  295. Drupal.autocomplete_deluxe.source.prototype.select = function(input, ui) {
  296. };
  297. /**
  298. * Change event function.
  299. */
  300. Drupal.autocomplete_deluxe.source.prototype.change = function(input, ui) {
  301. };
  302. /**
  303. * Keypress event function.
  304. */
  305. Drupal.autocomplete_deluxe.source.prototype.keypress = function(event) {
  306. if (this.multiple && event.which == 44 || event.which == 13) {
  307. var val = this.autocomplete.jqObject.val();
  308. if (val != '') {
  309. this.addValue(val);
  310. }
  311. }
  312. };
  313. /**
  314. * Adds a new value.
  315. */
  316. Drupal.autocomplete_deluxe.source.prototype.addValue = function(value) {
  317. new Drupal.autocomplete_deluxe.value(value, this);
  318. }
  319. /**
  320. * Will be called if a value is removed.
  321. */
  322. Drupal.autocomplete_deluxe.source.prototype.removeValue = function(value) {
  323. }
  324. /**
  325. * List Source Object
  326. *
  327. * @param data
  328. * The data for the autocomplete object.
  329. */
  330. Drupal.autocomplete_deluxe.listSource = function(data, select) {
  331. this.list = new Array();
  332. var instance = this;
  333. this.selectbox = select;
  334. jQuery.each(data, function(index, value) {
  335. instance.list.push({
  336. label: $.trim(value),
  337. value: index
  338. });
  339. });
  340. };
  341. /**
  342. * Sort function for the list entries.
  343. */
  344. Drupal.autocomplete_deluxe.listSource.sortList = function(itemA, itemB) {
  345. if (itemA.value < itemB.value)
  346. return -1;
  347. if (itemA.value > itemB.value)
  348. return 1;
  349. return 0;
  350. }
  351. // Set base class.
  352. Drupal.autocomplete_deluxe.listSource.prototype = new Drupal.autocomplete_deluxe.source();
  353. /**
  354. * Override init function,
  355. */
  356. Drupal.autocomplete_deluxe.listSource.prototype.init = function(data, select) {
  357. if (this.multiple) {
  358. var instance = this;
  359. // Add all selected(probably by #default_value) to the selected list.
  360. this.selectbox.children("option:selected").each( function() {
  361. instance.addValue($(this).text());
  362. });
  363. } else {
  364. this.autocomplete.jqObject.val(this.selectbox.children("option:selected").text());
  365. }
  366. }
  367. Drupal.autocomplete_deluxe.listSource.prototype.selectbox = null;
  368. /**
  369. * Will be called by the JQuery autocomplete source function to retrieve the
  370. * data.
  371. */
  372. Drupal.autocomplete_deluxe.listSource.prototype.setResponse = function(request, response) {
  373. var filtered = Drupal.autocomplete_deluxe.filter(this.list, request.term);
  374. this.response(request, response, filtered);
  375. };
  376. /**
  377. * Override select event function.
  378. */
  379. Drupal.autocomplete_deluxe.listSource.prototype.select = function(input, ui) {
  380. input.value = ui.item.label;
  381. if (!this.multiple) {
  382. this.selectbox.children('option:contains("' + input.value + '")').attr("selected", true);
  383. }
  384. };
  385. /**
  386. * Overrides the add new value function.
  387. */
  388. Drupal.autocomplete_deluxe.listSource.prototype.addValue = function(value) {
  389. for (var i=0; i < this.list.length; i++) {
  390. if (value == this.list[i].label) {
  391. this.selectbox.children('option:contains("' + value + '")').attr("selected", true);
  392. new Drupal.autocomplete_deluxe.value(value, this);
  393. this.list.splice(i, 1);
  394. }
  395. };
  396. };
  397. /**
  398. * Overrides the remove item event function.
  399. */
  400. Drupal.autocomplete_deluxe.listSource.prototype.removeValue = function(value) {
  401. this.selectbox.children('option:contains("' + value + '")').attr("selected", false);
  402. this.list.push({
  403. label: $.trim(value),
  404. value: this.selectbox.children('option:contains("' + value + '")').val()
  405. });
  406. this.list.sort(Drupal.autocomplete_deluxe.listSource. sortList);
  407. };
  408. /**
  409. * Ajax Source Object for selection
  410. *
  411. * @param uri
  412. * URI to server with the data.
  413. * @param dataType
  414. * If nothing is passed, json will be used as default.
  415. */
  416. Drupal.autocomplete_deluxe.ajaxSource = function(uri, dataType) {
  417. this.cache = new Array();
  418. this.uri = uri;
  419. if (dataType === undefined) {
  420. this.dataType = 'json';
  421. }
  422. else {
  423. this.dataType = dataType;
  424. }
  425. };
  426. // Set base class.
  427. Drupal.autocomplete_deluxe.ajaxSource.prototype = new Drupal.autocomplete_deluxe.source();
  428. Drupal.autocomplete_deluxe.ajaxSource.prototype.valueField = null;
  429. Drupal.autocomplete_deluxe.ajaxSource.prototype.values = null;
  430. /**
  431. * Initialization function for setting the default states.
  432. */
  433. Drupal.autocomplete_deluxe.ajaxSource.prototype.init = function() {
  434. if (this.multiple) {
  435. this.valueField = this.autocomplete.jqObject.parent().parent().children('div.autocomplete-deluxe-value-container').children().children();
  436. this.valueField.hide();
  437. this.autocomplete.jqObject.show();
  438. this.values = (this.valueField.val().split(',') != "") ? this.valueField.val().split(',') : new Array();
  439. for (var i in this.values) {
  440. if (this.values[i] != "" && this.values[i] != " ") {
  441. new Drupal.autocomplete_deluxe.value(this.values[i], this);
  442. }
  443. }
  444. }
  445. };
  446. /**
  447. * Will be called by the JQuery autocomplete source function to retrieve the
  448. * data.
  449. */
  450. Drupal.autocomplete_deluxe.ajaxSource.prototype.setResponse = function(request, response) {
  451. var instance = this;
  452. if (request.term in this.cache) {
  453. var instance = this;
  454. var terms = this.cache[request.term].filter(function(val) {
  455. for (var i in instance.values) {
  456. if (instance.values[i] == val.value) {
  457. return false;
  458. }
  459. }
  460. return true;
  461. });
  462. instance.response(request, response, (terms));
  463. return;
  464. }
  465. $.ajax({
  466. url: this.uri + '/' + request.term,
  467. dataType: this.dataType,
  468. success: function(data) {
  469. instance.response(request, response, instance.success(data, request));
  470. }
  471. });
  472. };
  473. /**
  474. * Success function for the autocomplete object.
  475. */
  476. Drupal.autocomplete_deluxe.ajaxSource.prototype.success = function(data, request) {
  477. var instance = this;
  478. this.cache[request.term] = new Array();
  479. jQuery.each(data, function(index, value) {
  480. instance.cache[request.term].push({
  481. label: value,
  482. value: index
  483. });
  484. });
  485. var terms = this.cache[request.term].filter(function(val) {
  486. for (var i in instance.values) {
  487. if (instance.values[i] == val.value) {
  488. return false;
  489. }
  490. }
  491. return true;
  492. });
  493. return terms;
  494. };
  495. /**
  496. * Override select event function.
  497. */
  498. Drupal.autocomplete_deluxe.ajaxSource.prototype.select = function(input, ui) {
  499. input.value = ui.item.value;
  500. };
  501. /**
  502. * Overrides the add new value function.
  503. */
  504. Drupal.autocomplete_deluxe.ajaxSource.prototype.addValue = function(value) {
  505. new Drupal.autocomplete_deluxe.value(value, this);
  506. this.values.push(value);
  507. this.valueField.val(this.values.join(','))
  508. }
  509. /**
  510. * Overrides the remove item event function.
  511. */
  512. Drupal.autocomplete_deluxe.ajaxSource.prototype.removeValue = function(value) {
  513. var idx = this.values.indexOf(value);
  514. if (idx >-1) {
  515. this.values.splice(idx, 1);
  516. }
  517. this.valueField.val(this.values.join(','))
  518. }
  519. })(jQuery);