So I've got a textfield with #autocomplete_route_name and #ajax set.

    $form['dept_filter'] = array(
      '#type' => 'textfield',
      '#title' => t('Department Name'),
      '#autocomplete_route_name' => 'mymod.dept_filter_autocomplete',
      '#description' => t('Department Filter'),
      '#default_value' => $_SESSION['dept_filter'],
      '#ajax' => array(
        'callback' => 'Drupal\mymod\Controller\DeptDashController::deptFilterSelect',
        'event' => 'blur',
      ),
    );

If I set #ajax['event'] to 'blur', I can get it to call my DeptDashController::deptFilterSelect callback on blur, but I actually want that to fire when something is selected in the autocomplete.

I think because '#autocomplete_route_name' is set, it's overriding the 'change' event.

So is there another event I can use to fire my callback when an option is selected from the autocomplete?

I've scanned through both documentation and code, and haven't yet come up with anything that works the way I want yet. I may have actually discovered some bugs!

Events I've tried, with result:
'change': Fires if I change the text and tab away, but NOT after I select an autocomplete option, even after tabbing away.
'blur': Default functionality - fires callback on blur, then returns cursor to textbox, so anything I click on will blur and fire the callback again.
'select': Fires callback in an infinite loop as soon as I type anything into the box.

Any help would be appreciated!

Comments

millenniumtree created an issue. See original summary.

millenniumtree’s picture

Issue summary: View changes
millenniumtree’s picture

OK, I found the event I needed.
From here: https://api.jqueryui.com/autocomplete/

The events are 'autocompleteselect' and 'autocompleteclose'.

'autocompleteselect' doesn't quite work as I expected. It submits the search string that you entered, NOT the selected option.
'autocompleteclose' submits EITHER the selected option, or if the dialog was closed without selecting an option, the search string.

So the one that works is autocompleteclose, with a little server-side validation.

      '#ajax' => array(
        'callback' => 'Drupal\mymod\Controller\DeptDashController::deptFilterSelect',
        'event' => 'autocompleteclose',
        'progress' => FALSE,
      ),

Then, in my DeptDashController, I have to verify a number was passed in the selected option.

  public function deptFilterSelect(Request $request) {
    $dept_filter = $request['dept_filter']['#value'];
    if($dept_filter) {
      $ex = explode(':', $dept_filter);
      $dept_id = intval($ex[0]);
      if($dept_id) {
        $_SESSION['dept_filter'] = $dept_filter;
        $_SESSION['dept_id'] = $dept_id;
      }
    }
    $response = new AjaxResponse();
    return $response;
  }

It'd be fantastic if these options were documented somewhere. The ONLY reference to autocompleteselect that I found was in core/misc/autocomplete.js, in a comment. There was no reference to autocompleteclose at all.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

blainelang’s picture

Thanks very much @ millenniumtree for finding that event 'autocompleteclose' as it's exactly what I needed.

millenniumtree’s picture

Status: Active » Closed (works as designed)

Closing.

rpayanm’s picture

Thank you @millenniumtree, it works!

dustinyoder’s picture

I modified ajax.js to track if an autocompleteselect fires before the autocompleteclose and only does the ajax on autocompleteclose when autocompleteselect has fired first. This allows passing the selected value back without triggering the ajax call every time the popup closes. I am not recommending hacking core, but wasn't sure how to do this otherwise. I think it should be possible from a module.

I would also suggest making this only happen when a certain property is passed along in the elementSettings. So maybe you would need to form_alter the autocomplete element and in the #ajax property where you change the event, you could have a 'required_event' or something like that where you could put the 'autocompleteselect' in. Then core could maybe require a preceding event before ajaxing on the current event. Maybe even some validation stuff could be added there.

Here is my hacked Drupal 8 code from ajax.js. This does seem to do the trick for me. autocompleteclose now works like autocompleteselect and also does pass the current value.

Just wanted to post this for anyone researching this topic. I think this is a reasonable approach if it would be cleaned up more.

    if(elementSettings.event === "autocompleteclose") {
      $(ajax.element).on("autocompleteselect", function (event) {
        elementSettings.wasSelect = true;
      });
    }

    $(ajax.element).on(elementSettings.event, function (event) {
      // skip the ajax update if autocompleteclose event, but we have no record of an autocompleteselect preceding it
      if(elementSettings.event === "autocompleteclose") {
        if(!elementSettings.hasOwnProperty("wasSelect") || !elementSettings.wasSelect) {
          return false;
        }
        elementSettings.wasSelect = false;  // clear for next event
      }
      // console.log("ajax event firing ", event);
      if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {
        throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {
          '!url': ajax.url
        }));
      }
      return ajax.eventResponse(this, event);
    });