/(function ($) { /** * Attaches the autocomplete behavior to all required fields. */ Drupal.behaviors.autocomplete = { attach: function (context) { var $context = $(context); var acdb = []; $context.find('input.autocomplete').once('autocomplete', function () { var uri = this.value; if (!acdb[uri]) { acdb[uri] = new Drupal.ACDB(uri); } var $input = $context.find('#' + this.id.substr(0, this.id.length - 13)) .attr('autocomplete', 'OFF') .attr('aria-autocomplete', 'list'); $context.find($input[0].form).submit(Drupal.autocompleteSubmit); $input.parents('.form-item') .attr('role', 'application') .append($('') .attr('id', $input.attr('id') + '-autocomplete-aria-live') ); new Drupal.jsAC($input, acdb[uri], $context); }); } }; /** * Prevents the form from submitting if the suggestions popup is open * and closes the suggestions popup when doing so. */ Drupal.autocompleteSubmit = function () { // NOTE: Do not return true as this is non-standard. Keep it similar to // core. If another contrib project alters this functionality, then it is // the responsibility of a sub-theme to override this method and combine // this project with the other project. return $('.form-autocomplete > .dropdown').each(function () { this.owner.hidePopup(); }).length == 0; }; /** * Highlights a suggestion. */ Drupal.jsAC.prototype.highlight = function (node) { if (this.selected) { $(this.selected).removeClass('active'); } $(node).addClass('active'); this.selected = node; $(this.ariaLive).html($(this.selected).html()); }; /** * Unhighlights a suggestion. */ Drupal.jsAC.prototype.unhighlight = function (node) { $(node).removeClass('active'); this.selected = false; $(this.ariaLive).empty(); }; /** * Positions the suggestions popup and starts a search. */ Drupal.jsAC.prototype.populatePopup = function () { var $input = $(this.input); // Show popup. if (this.popup) { $(this.popup).remove(); } this.selected = false; this.popup = $('')[0]; this.popup.owner = this; $input.parent().after(this.popup); // Do search. this.db.owner = this; this.db.search(this.input.value); }; /** * Fills the suggestion popup with any matches received. */ Drupal.jsAC.prototype.found = function (matches) { // If no value in the textfield, do not show the popup. if (!this.input.value.length) { return false; } // Prepare matches. var ul = $(''); var ac = this; ul.css({ display: 'block', right: 0 }); for (var key in matches) { $('
  • ') .html($('').html(matches[key]).on('click', function (e) { e.preventDefault(); })) .on('mousedown', function () { ac.hidePopup(this); }) .on('mouseover', function () { ac.highlight(this); }) .on('mouseout', function () { ac.unhighlight(this); }) .data('autocompleteValue', key) .appendTo(ul); } // Show popup with matches, if any. if (this.popup) { if (ul.children().length) { $(this.popup).empty().append(ul).show(); $(this.ariaLive).html(Drupal.t('Autocomplete popup')); } else { $(this.popup).css({visibility: 'hidden'}); this.hidePopup(); } } }; /** * Finds the next sibling item. */ Drupal.jsAC.prototype.findNextSibling = function (element) { var sibling = element && element.nextSibling; if (sibling && !this.validItem(sibling)) { return this.findNextSibling(sibling.nextSibling); } return sibling; }; /** * Finds the previous sibling item. */ Drupal.jsAC.prototype.findPreviousSibling = function (element) { var sibling = element && element.previousSibling; if (sibling && !this.validItem(sibling)) { return this.findPreviousSibling(sibling.previousSibling); } return sibling; }; /** * Highlights the next suggestion. */ Drupal.jsAC.prototype.selectDown = function () { var sibling = this.findNextSibling(this.selected); if (sibling) { this.highlight(sibling); } else if (this.popup) { var lis = $('li', this.popup); if (lis.length > 0) { if (this.validItem(lis[0])) { this.highlight(lis[0]); } else { this.highlight(this.findNextSibling(lis[0])); } } } }; /** * Highlights the previous suggestion. */ Drupal.jsAC.prototype.selectUp = function () { var sibling = this.findPreviousSibling(this.selected); if (sibling) { this.highlight(sibling); } else if (this.popup) { var lis = $('li', this.popup); if (lis.length > 0) { if (this.validItem(lis[lis.length - 1])) { this.highlight(lis[lis.length - 1]); } else { this.highlight(this.findPreviousSibling(lis[lis.length - 1])); } } } }; /** * Ensures the item is valid. */ Drupal.jsAC.prototype.validItem = function (element) { return !$(element).is('.dropdown-header, .divider, .disabled'); }; Drupal.jsAC.prototype.setStatus = function (status) { var $throbber = $(this.input).parent().find('.glyphicon-refresh, .autocomplete-throbber').first(); var throbbingClass = $throbber.is('.autocomplete-throbber') ? 'throbbing' : 'glyphicon-spin'; switch (status) { case 'begin': $throbber.addClass(throbbingClass); $(this.ariaLive).html(Drupal.t('Searching for matches...')); break; case 'cancel': case 'error': case 'found': $throbber.removeClass(throbbingClass); break; } }; // Save the previous autocomplete prototype. var oldPrototype = Drupal.jsAC.prototype; /** * Override the autocomplete constructor. */ Drupal.jsAC = function ($input, db, context) { var ac = this; // Context is normally passed by Drupal.behaviors.autocomplete above. However, // if a module has manually invoked this method they will likely not know // about this feature and a global fallback context to document must be used. // @see https://www.drupal.org/node/2594243 // @see https://www.drupal.org/node/2315295 this.$context = context && $(context) || $(document); this.input = $input[0]; this.ariaLive = this.$context.find('#' + this.input.id + '-autocomplete-aria-live'); this.db = db; $input .keydown(function (event) { return ac.onkeydown(this, event); }) .keyup(function (event) { ac.onkeyup(this, event); }) .blur(function () { ac.hidePopup(); ac.db.cancel(); }); }; // Restore the previous prototype. Drupal.jsAC.prototype = oldPrototype; })(jQuery); ;/*})'"*/ ;/*})'"*/