1(function ($) { 2 3 /** 4 * Attaches the autocomplete behavior to all required fields. 5 */ 6 Drupal.behaviors.autocomplete = { 7 attach: function (context) { 8 var $context = $(context); 9 var acdb = []; 10 $context.find('input.autocomplete').once('autocomplete', function () { 11 var uri = this.value; 12 if (!acdb[uri]) { 13 acdb[uri] = new Drupal.ACDB(uri); 14 } 15 var $input = $context.find('#' + this.id.substr(0, this.id.length - 13)) 16 .attr('autocomplete', 'OFF') 17 .attr('aria-autocomplete', 'list'); 18 $context.find($input[0].form).submit(Drupal.autocompleteSubmit); 19 $input.parents('.form-item') 20 .attr('role', 'application') 21 .append($('<span class="element-invisible" aria-live="assertive"></span>') 22 .attr('id', $input.attr('id') + '-autocomplete-aria-live') 23 ); 24 new Drupal.jsAC($input, acdb[uri], $context); 25 }); 26 } 27 }; 28 29 /** 30 * Prevents the form from submitting if the suggestions popup is open 31 * and closes the suggestions popup when doing so. 32 */ 33 Drupal.autocompleteSubmit = function () { 34 $('.form-autocomplete > .dropdown').each(function () { 35 this.owner.hidePopup(); 36 }); 37 38 // Always return true to make it possible to submit even when there was an 39 // autocomplete suggestion list open. 40 return true; 41 }; 42 43 /** 44 * Highlights a suggestion. 45 */ 46 Drupal.jsAC.prototype.highlight = function (node) { 47 if (this.selected) { 48 $(this.selected).removeClass('active'); 49 } 50 $(node).addClass('active'); 51 this.selected = node; 52 $(this.ariaLive).html($(this.selected).html()); 53 }; 54 55 /** 56 * Unhighlights a suggestion. 57 */ 58 Drupal.jsAC.prototype.unhighlight = function (node) { 59 $(node).removeClass('active'); 60 this.selected = false; 61 $(this.ariaLive).empty(); 62 }; 63 64 /** 65 * Positions the suggestions popup and starts a search. 66 */ 67 Drupal.jsAC.prototype.populatePopup = function () { 68 var $input = $(this.input); 69 // Show popup. 70 if (this.popup) { 71 $(this.popup).remove(); 72 } 73 this.selected = false; 74 this.popup = $('<div class="dropdown"></div>')[0]; 75 this.popup.owner = this; 76 $input.parent().after(this.popup); 77 78 // Do search. 79 this.db.owner = this; 80 this.db.search(this.input.value); 81 }; 82 83 /** 84 * Fills the suggestion popup with any matches received. 85 */ 86 Drupal.jsAC.prototype.found = function (matches) { 87 // If no value in the textfield, do not show the popup. 88 if (!this.input.value.length) { 89 return false; 90 } 91 92 // Prepare matches. 93 var ul = $('<ul class="dropdown-menu"></ul>'); 94 var ac = this; 95 ul.css({ 96 display: 'block', 97 right: 0 98 }); 99 for (var key in matches) { 100 $('<li></li>') 101 .html($('<a href="#"></a>').html(matches[key]).on('click', function (e) { 102 e.preventDefault(); 103 })) 104 .on('mousedown', function () { 105 ac.hidePopup(this); 106 }) 107 .on('mouseover', function () { 108 ac.highlight(this); 109 }) 110 .on('mouseout', function () { 111 ac.unhighlight(this); 112 }) 113 .data('autocompleteValue', key) 114 .appendTo(ul); 115 } 116 117 // Show popup with matches, if any. 118 if (this.popup) { 119 if (ul.children().length) { 120 $(this.popup).empty().append(ul).show(); 121 $(this.ariaLive).html(Drupal.t('Autocomplete popup')); 122 } 123 else { 124 $(this.popup).css({visibility: 'hidden'}); 125 this.hidePopup(); 126 } 127 } 128 }; 129 130 /** 131 * Finds the next sibling item. 132 */ 133 Drupal.jsAC.prototype.findNextSibling = function (element) { 134 var sibling = element && element.nextSibling; 135 if (sibling && !this.validItem(sibling)) { 136 return this.findNextSibling(sibling.nextSibling); 137 } 138 return sibling; 139 }; 140 141 /** 142 * Finds the previous sibling item. 143 */ 144 Drupal.jsAC.prototype.findPreviousSibling = function (element) { 145 var sibling = element && element.previousSibling; 146 if (sibling && !this.validItem(sibling)) { 147 return this.findPreviousSibling(sibling.previousSibling); 148 } 149 return sibling; 150 }; 151 152 /** 153 * Highlights the next suggestion. 154 */ 155 Drupal.jsAC.prototype.selectDown = function () { 156 var sibling = this.findNextSibling(this.selected); 157 if (sibling) { 158 this.highlight(sibling); 159 } 160 else if (this.popup) { 161 var lis = $('li', this.popup); 162 if (lis.length > 0) { 163 if (this.validItem(lis[0])) { 164 this.highlight(lis[0]); 165 } 166 else { 167 this.highlight(this.findNextSibling(lis[0])); 168 } 169 } 170 } 171 }; 172 173 /** 174 * Highlights the previous suggestion. 175 */ 176 Drupal.jsAC.prototype.selectUp = function () { 177 var sibling = this.findPreviousSibling(this.selected); 178 if (sibling) { 179 this.highlight(sibling); 180 } 181 else if (this.popup) { 182 var lis = $('li', this.popup); 183 if (lis.length > 0) { 184 if (this.validItem(lis[lis.length - 1])) { 185 this.highlight(lis[lis.length - 1]); 186 } 187 else { 188 this.highlight(this.findPreviousSibling(lis[lis.length - 1])); 189 } 190 } 191 } 192 }; 193 194 /** 195 * Ensures the item is valid. 196 */ 197 Drupal.jsAC.prototype.validItem = function (element) { 198 return !$(element).is('.dropdown-header, .divider, .disabled'); 199 }; 200 201 Drupal.jsAC.prototype.setStatus = function (status) { 202 var $throbber = $(this.input).parent().find('.glyphicon-refresh, .autocomplete-throbber').first(); 203 var throbbingClass = $throbber.is('.autocomplete-throbber') ? 'throbbing' : 'glyphicon-spin'; 204 switch (status) { 205 case 'begin': 206 $throbber.addClass(throbbingClass); 207 $(this.ariaLive).html(Drupal.t('Searching for matches...')); 208 break; 209 case 'cancel': 210 case 'error': 211 case 'found': 212 $throbber.removeClass(throbbingClass); 213 break; 214 } 215 }; 216 217 // Save the previous autocomplete prototype. 218 var oldPrototype = Drupal.jsAC.prototype; 219 220 /** 221 * Override the autocomplete constructor. 222 */ 223 Drupal.jsAC = function ($input, db, context) { 224 var ac = this; 225 226 // Context is normally passed by Drupal.behaviors.autocomplete above. However, 227 // if a module has manually invoked this method they will likely not know 228 // about this feature and a global fallback context to document must be used. 229 // @see https://www.drupal.org/node/2594243 230 // @see https://www.drupal.org/node/2315295 231 this.$context = context && $(context) || $(document); 232 233 this.input = $input[0]; 234 this.ariaLive = this.$context.find('#' + this.input.id + '-autocomplete-aria-live'); 235 this.db = db; 236 $input 237 .keydown(function (event) { 238 return ac.onkeydown(this, event); 239 }) 240 .keyup(function (event) { 241 ac.onkeyup(this, event); 242 }) 243 .blur(function () { 244 ac.hidePopup(); 245 ac.db.cancel(); 246 }); 247 }; 248 249 // Restore the previous prototype. 250 Drupal.jsAC.prototype = oldPrototype; 251 252})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.