Change record status: 
Project: 
Introduced in branch: 
9.3.x
Introduced in version: 
9.3.0
Description: 

jQuery UI has reached end of life and is being phased out of Drupal core as a result. jQuery UI autocomplete has been deprecated as part of that process. and Autocomplete functionality is now provided by a new library, @drupal/a11y_autocomplete.

Although autocomplete inputs will no longer use jQuery UI to execute their functionality, two backwards compatibility layers have been provided to make this change minimally disruptive. For the vast majority of use cases,

API backwards compatiblity

By default, autocomplete-enabled inputs include a backwards-compatible shim so the jQuery UI autocomplete API will continue to work for most use cases. For example, usage such as $('#autocomplete-input').autocomplete(options); will continue to work as expected despite jQuery UI autocomplete not being loaded. When the jQuery UI autocomplete API is used, a a JavaScript deprecation notice will be triggered.

API Backwards compatibility scope

  • All Autocomplete specific functionality is supported (i.e. everything documented in the jQuery UI Autocomplete API) This includes:
    • Options: appendTo, autoFocus, classes, delay, disabled, minLength, position, source
    • Methods: close, destroy, disable, enable, instance, option, search, widget
    • Events: change, close, create, focus, open, response, search, select
    • Extension Points: _renderItem, _renderMenu, _resizeMenu
  • Extending jQuery UI functionality viajQuery UI widget factory has limited backwards compatibility support. If a site codebase has no calls to $.widget('[a string that includes "autocomplete"]'), then it is not impacted by this backwards compatibility limitation.

    When using the widget factory to extend autocomplete, such as with calls like this $.widget( "custom.autocomplete", {foo: bar, dip: dobson});, only autocomplete-specific methods (close, destroy, disable, enable, instance, option, search, widget) will be overridden. This widget factory usage does not support widget-scoped methods such as _addClass, _create, _delay,, _destroy, _superetc. Code that attempts to use these methods will fail ungracefully in order to unambiguously convey the fact that this usage is no longer supported. If a given autocomplete implementation includes this now-unsupported use case, this will be immediately apparent in the JS console as soon as the page loads.

jQuery UI Autocomplete Options -> A11y_Autocomplete Equivalents

appendTo There is no 1:1 equivalent, and the default behavior is different. jQuery UI defaults to appending autocomplete suggestion lists to the body element, while A11y Autocomplete appends the suggestion lists to a <div data-drupal-autocomplete-wrapper> that wraps the autocomplete-enabled input.

To achieve the same results as jQuery UI appendTo in A11y_Autocomplete, override the appendList() method of the A11y_Autocomplete instance. For example, to append to the document body

autocompleteInstance.appendList = function() {
  document.body..appendChild(this.ul);
}
autoFocus Identical in A11y_Autocomplete, same option name, functionality and default value
classes There is no 1:1 equivalent, but there are many ways to achieve the same results. Our recommended approach is doing this via event listener.
document.addEventListener('drupal-autocomplete-init', (e) => {
  // Get the autocomplete instance
  const { instance } = e.detail;
  // Equivalent to setting a class via classes.ui-autocomplete
  instance.ul.classList.add('TheClassYouWantAddedToTheList');
  // Equivalent to setting a class via classes.ui-autocomplete-input
  instance.input.classList.add('TheClassYouWantAddedToTheInput');
});
delay ##PENDING##
disabled No 1:1 equivalent and not configurable via option. This option disables autocomplete functionality on an input without disabling the input. This is best accomplished in A11y_Autocomplete via event listener:
autocompleteInput.addEventListener('autocomplete-pre-search', (e) => {
  // Get the autocomplete instance if needed.
  const { instance } = e.detail;
  // Doing this is the equivalent of setting the disabled option to true;
  e.preventDefault();  
});
minLength The equivalent option in A11y_Autocomplete is minChars, and the default value is 1 in both
position There is no 1:1 equvalent. In A11y autocomplete, the positioning of the list options is most easily customized via CSS styling.
source The jQuery UI source option can be used in a variety of ways. The A11y_Autocomplete equivalent is different based on how it is used.
  • If the value of source is an array, the equivalent option in A11y_Autocomplete is list. The contents of that array become the list of items the autocomplete searches
  • If the value of source is a string, it is expected to be a URI, and the equivalent option in A11y_Autocomplete is path.
  • If the value of source is a function, first see if the function only exists to make requests from a URI and return JSON (this is a common scenario). If so, it may be possible to replace that whole function with path: 'theRequestedURI'. If it's more complex than that, it may be necessary to override the doSearch() function of the autocomplete instance.

jQuery UI Autocomplete Methods -> A11y_Autocomplete Equivalents

close autocompleteInstance.close()
destroy autocompleteInstance.close()
disable No 1:1 equivalent, best accomplished via event listener.
autocompleteInput.addEventListener('autocomplete-pre-search', (e) => {
  // Get the autocomplete instance if needed.
  const { instance } = e.detail;
  
  if (theCriteriaYouWantItToBeDisabled) {
    // PreventDefault here is the equivalent of calling disable
    e.preventDefault();  
 }

});
enable Use the same approach as disable above. The condition in the event listener will determine whether or not to disable the input.
instance There's no exact 1:1 equivalent but most properties remain available
  • $autocompleteInput.autocomplete( "instance" ).document === jQuery(document)
  • $autocompleteInput.autocomplete( "instance" ).element === jQuery(autocompleteInstance.input)
  • $autocompleteInput.autocomplete( "instance" ).menu === jQuery(autocompleteInstance.ul)
  • $autocompleteInput.autocomplete( "instance" ).liveRegion === jQuery(autocompleteInstance.liveRegion)
  • $autocompleteInput.autocomplete( "instance" ).options === jQuery(autocompleteInstance.options)

Other instance properties provided by jQuery UI Autocomplete do not have equivalents available in A11y_Autocomplete

option
  • When used without args autocompleteInstance.autocomplete('option');, the same value can be retrieved in A11y_Autocomplete directly:
    autocompleteInstance.options
  • For uses with two string args autocompleteInstance.autocomplete('option', optionName, value ) , set the values directly autocompleteInstance.options[optionName] = option
  • For uses with an object arg, use the "two string args" approach in the prior list item. This needs to be applied for each option set via the object, either via individual calls or using destructuring autocompleteInstance.options = {...autocompleteInstance.options, {optionName: value, optionName2: value }}
search No direct equivalent. It requires using JavaScript to replicate the steps of searching: focus the input, enter a value, trigger search via keyboard event:
// A11yAutocomplete expects the input to be focused when a search
// occurs, even if it's programmatically triggered.
autocompleteInstance.input.focus();

// Set the value of the input with the term to search for 
autocompleteInstance.input.value = 'valueBeingSearchedFor';

// Trigger a search. It is important to add the keydown event as an arg, otherwise <code>doSearch

can interpret this as a cancelled search attempt.
autocompleteInstance.doSearch($.Event('keydown'));

widget This provides the <ul> that contains list options as a jQuery object. The value returned by $autocompleteInstance.autocomplete( "widget" ) is equivalent to jQuery(autocompleteInstance.ul)

jQuery UI Autocomplete Events -> A11y_Autocomplete Equivalents

change autocomplete-change
close autocomplete-close
create autocomplete-created
focus autocomplete-highlight
open autocomplete-open
response autocomplete-response
search autocomplete-pre-search
select autocomplete-select

jQuery UI Autocomplete Extension Points -> A11y_Autocomplete Equivalents

_renderItem No direct equivalent, but most uses of _renderItem can be accomplished by overriding the suggestionItem()method of the autocompleteInstance. This can also be accomplished by adding an event listener that customizes the list items.
autocompleteInput.addEventListener('autocomplete-open', (e) => {
  // Get the autocomplete instance if needed.
  const { instance } = e.detail;
  // Example: dhange list item attributes
 this.ul.children.forEach((item) => {
  item.setAttribute('data-attribute-to-set', 'value being set');
});

});
_renderMenu No direct equivalent, but most uses of _renderItem can be accomplished by overriding the implementList()method of the autocompleteInstance. This can also be accomplished by adding an event listener that customizes the ul.
autocompleteInput.addEventListener('autocomplete-open', (e) => {
  // Get the autocomplete instance if needed.
  const { instance } = e.detail;
  // Example: change an attribute in the rendered list
  instance.ul.setAttribute('data-attribute-to-set', 'value being set');
});
_resizeMenu No direct equivalent. Use the same event listener approach as _renderMenu, and set sizes there:
autocompleteInput.addEventListener('autocomplete-open', (e) => {
  // Get the autocomplete instance
  const { instance } = e.detail;
  // Example: set size of the rendered list
  instance.ul.style.width = '500px';
});

This API backwards compatibility shim will be removed in Drupal 10. For sites that still require the jQuery UI autocomplete in Drupal 10, jQuery UI autocomplete is still available as a contributed module https://www.drupal.org/project/jquery_ui_autocomplete/.

Running the API shim

By default, the shim is enabled on all autocomplete-enabled inputs initialized by Drupal.autocomplete. It may be necessary for contributed projects to have to manually initialize the shim when upgrading to use the new autocomplete library. The shim will reduce the likely hood of causing regressions with other modules that are potentially working on top of the contributed project.

It is important to attach the jQuery UI shim with the assumption that it might not always exist because the jQuery UI shim will be removed in Drupal 10.

  1. Add dependency for the core/autocomplete.jqueryui.shim library.
    library:
      ...
      dependencies:
        - core/autocomplete.jqueryui.shim
    
  2. Attach jQuery UI shim after initializing the autocomplete library:
    const autocomplete = A11yAutocomplete(input, options);
    
    // Drupal.autocompleteShim is removed in Drupal 10. If it doesn't exist, the
    // shim doesn't have to be initialized.
    if (Drupal.autocompleteShim && autocomplete._internal_object) {
      Drupal.autocompleteShim.jqueryUiShimInit(autocomplete._internal_object, options);
    }
    

Markup backwards compatibility

The markup generated by A11y_Autocomplete is different than that generated by jQuery UI autocomplete. Themes declaring Stable or Stable 9 as a base theme will continue to render autocomplete inputs with markup matching jQuery UI's. For sites needing to maintain jQuery UI markup, but not able to use Stable/Stable 9 as as base theme, it is also possible to provide backwards compatible markup by copying js/autocomplete from Stable or Stable 9 (they are identical) and loading it via a custom module or theme. If your site does not include autocomplete-specific custom styling or JavaScript customizations that rely on a specific DOM structure, this layer is likely unnecessary.

Impacts: 
Site builders, administrators, editors
Module developers
Themers
Site templates, recipes and distribution developers