combobox.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*!
  2. * Copyright Ben Olson (https://github.com/bseth99/jquery-ui-extensions)
  3. * jQuery UI ComboBox @VERSION
  4. *
  5. * Adapted from Jörn Zaefferer original implementation at
  6. * http://www.learningjquery.com/2010/06/a-jquery-ui-combobox-under-the-hood
  7. *
  8. * And the demo at
  9. * http://jqueryui.com/autocomplete/#combobox
  10. *
  11. * Permission is hereby granted, free of charge, to any person
  12. * obtaining a copy of this software and associated documentation
  13. * files (the "Software"), to deal in the Software without
  14. * restriction, including without limitation the rights to use,
  15. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  16. * copies of the Software, and to permit persons to whom the
  17. * Software is furnished to do so, subject to the following
  18. * conditions:
  19. *
  20. * The above copyright notice and this permission notice shall be
  21. * included in all copies or substantial portions of the Software.
  22. *
  23. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  24. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  25. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  26. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  27. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  28. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  29. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  30. * OTHER DEALINGS IN THE SOFTWARE.
  31. *
  32. */
  33. (function( $, undefined ) {
  34. $.widget( "ui.combobox", {
  35. version: "@VERSION",
  36. widgetEventPrefix: "combobox",
  37. uiCombo: null,
  38. uiInput: null,
  39. _wasOpen: false,
  40. _create: function() {
  41. var self = this,
  42. select = this.element.hide(),
  43. input, wrapper;
  44. input = this.uiInput =
  45. $( "<input />" )
  46. .insertAfter(select)
  47. .addClass("ui-widget ui-widget-content ui-corner-left ui-combobox-input")
  48. .val( select.children(':selected').text() )
  49. .attr('tabindex', select.attr( 'tabindex'));
  50. wrapper = this.uiCombo =
  51. input.wrap( '<span>' )
  52. .parent()
  53. .addClass( 'ui-combobox' )
  54. .insertAfter( select );
  55. input
  56. .autocomplete({
  57. delay: 0,
  58. minLength: 0,
  59. appendTo: wrapper,
  60. source: $.proxy( this, "_linkSelectList" )
  61. });
  62. $( "<button>" )
  63. .attr( "tabIndex", -1 )
  64. .attr( "type", "button" )
  65. .insertAfter( input )
  66. .button({
  67. icons: {
  68. primary: "ui-icon-triangle-1-s"
  69. },
  70. text: false
  71. })
  72. .removeClass( "ui-corner-all" )
  73. .addClass( "ui-corner-right ui-button-icon ui-combobox-button" );
  74. // Our items have HTML tags. The default rendering uses text()
  75. // to set the content of the <a> tag. We need html().
  76. input.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
  77. return $( "<li>" )
  78. .append( $( "<a>" ).html( item.label ) )
  79. .appendTo( ul );
  80. };
  81. this._on( this._events );
  82. },
  83. _linkSelectList: function( request, response ) {
  84. var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), 'i' );
  85. response( this.element.children('option').map(function() {
  86. var text = $( this ).text();
  87. if ( this.value && ( !request.term || matcher.test(text) ) ) {
  88. var optionData = {
  89. label: text,
  90. value: text,
  91. option: this
  92. };
  93. if (request.term) {
  94. optionData.label = text.replace(
  95. new RegExp(
  96. "(?![^&;]+;)(?!<[^<>]*)(" +
  97. $.ui.autocomplete.escapeRegex(request.term) +
  98. ")(?![^<>]*>)(?![^&;]+;)", "gi"),
  99. "<strong>$1</strong>");
  100. }
  101. return optionData;
  102. }
  103. })
  104. );
  105. },
  106. _events: {
  107. "autocompletechange input" : function(event, ui) {
  108. var $el = $(event.currentTarget);
  109. var changedOption = ui.item ? ui.item.option : null;
  110. if ( !ui.item ) {
  111. var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $el.val() ) + "$", "i" ),
  112. valid = false,
  113. matchContains = null,
  114. iContains = 0,
  115. iSelectCtr = -1,
  116. iSelected = -1,
  117. optContains = null;
  118. if (this.options.autofillsinglematch) {
  119. matchContains = new RegExp($.ui.autocomplete.escapeRegex($el.val()), "i");
  120. }
  121. this.element.children( "option" ).each(function() {
  122. var t = $(this).text();
  123. if ( t.match( matcher ) ) {
  124. this.selected = valid = true;
  125. return false;
  126. }
  127. if (matchContains) {
  128. // look for items containing the value
  129. iSelectCtr++;
  130. if (t.match(matchContains)) {
  131. iContains++;
  132. optContains = $(this);
  133. iSelected = iSelectCtr;
  134. }
  135. }
  136. });
  137. if ( !valid ) {
  138. // autofill option: if there is just one match, then select the matched option
  139. if (iContains == 1) {
  140. changedOption = optContains[0];
  141. changedOption.selected = true;
  142. var t2 = optContains.text();
  143. $el.val(t2);
  144. $el.data('ui-autocomplete').term = t2;
  145. this.element.prop('selectedIndex', iSelected);
  146. console.log("Found single match with '" + t2 + "'");
  147. } else {
  148. // remove invalid value, as it didn't match anything
  149. $el.val( '' );
  150. // Internally, term must change before another search is performed
  151. // if the same search is performed again, the menu won't be shown
  152. // because the value didn't actually change via a keyboard event
  153. $el.data( 'ui-autocomplete' ).term = '';
  154. this.element.prop('selectedIndex', -1);
  155. }
  156. }
  157. }
  158. this._trigger( "change", event, {
  159. item: changedOption
  160. });
  161. },
  162. "autocompleteselect input": function( event, ui ) {
  163. ui.item.option.selected = true;
  164. this._trigger( "select", event, {
  165. item: ui.item.option
  166. });
  167. },
  168. "autocompleteopen input": function ( event, ui ) {
  169. this.uiCombo.children('.ui-autocomplete')
  170. .outerWidth(this.uiCombo.outerWidth(true));
  171. },
  172. "mousedown .ui-combobox-button" : function ( event ) {
  173. this._wasOpen = this.uiInput.autocomplete("widget").is(":visible");
  174. },
  175. "click .ui-combobox-button" : function( event ) {
  176. this.uiInput.focus();
  177. // close if already visible
  178. if (this._wasOpen)
  179. return;
  180. // pass empty string as value to search for, displaying all results
  181. this.uiInput.autocomplete("search", "");
  182. }
  183. },
  184. value: function ( newVal ) {
  185. var select = this.element,
  186. valid = false,
  187. selected;
  188. if ( !arguments.length ) {
  189. selected = select.children( ":selected" );
  190. return selected.length > 0 ? selected.val() : null;
  191. }
  192. select.prop('selectedIndex', -1);
  193. select.children('option').each(function() {
  194. if ( this.value == newVal ) {
  195. this.selected = valid = true;
  196. return false;
  197. }
  198. });
  199. if ( valid ) {
  200. this.uiInput.val(select.children(':selected').text());
  201. } else {
  202. this.uiInput.val( "" );
  203. this.element.prop('selectedIndex', -1);
  204. }
  205. },
  206. _destroy: function () {
  207. this.element.show();
  208. this.uiCombo.replaceWith( this.element );
  209. },
  210. widget: function () {
  211. return this.uiCombo;
  212. },
  213. _getCreateEventData: function() {
  214. return {
  215. select: this.element,
  216. combo: this.uiCombo,
  217. input: this.uiInput
  218. };
  219. }
  220. });
  221. }(jQuery));