Steps to reproduce:

  1. Visit a form that allows to edit an address field.
  2. I first encountered it on the commerce checkout form, but it also happens when editing a commerce shipping profile.
  3. Use browser autocomplete (Chromium) to fill in an address where the country is different than whichever country is preselected.

Expected behavior:
All fields are filled in as from the browser autocomplete.

Actual behavior:
- Fields are filled in correctly.
- Ajax reload triggers
- Fields are wiped empty, except the country.

Technical explanation

Two effects:
- The ajax request is sent before all the form elements are filled in by the browser autocomplete.
- Even if the recent form data would be sent with the ajax request, addressfield_standard_country_validate() still clears the city and postal code (dependent_locality, locality, administrative_area, postal_code), if country has changed.

Solution

Two changes:
- Add a small delay (e.g. one millisecond) before firing the ajax request, to allow the browser autofill to complete before collecting form data for ajax.
- Somehow prevent the reset of form elements that were (recently) modified on client side.

Implementation

I don't know. Does Drupal #ajax support adding a delay like this?

Comments

donquixote created an issue. See original summary.

donquixote’s picture

I don't know. Does Drupal #ajax support adding a delay like this?

I don't see a straightforward solution with Drupal #ajax.

So here is an idea:
Define a new event name "change_delayed".
Bind a "change" behavior to country field, using custom js file, which waits a microsecond and then fires the "change_delayed" event.
Register the "#ajax" behavior with "event" => "change_delayed".

This allows to have our delay without reinventing all the Drupal ajax mechanics.

donquixote’s picture

A different idea I had before was to create an additional country element which copies the value of country with a delay, and then register the #ajax behavior on this delayed country field. But I think this would add unnecessary clutter.

donquixote’s picture

I played around with delay as in #2.
It does preserve the street and name values.

However, the postal code and city are wiped empty.
The $_POST does contain the complete address including postal code and city.
However, $form_state['input']['commerce_customer_address']['und'][0] only contains thoroughfare, premise, country, organisation_name and name_line.

This means an ajax delay can only be a half-solution.

donquixote’s picture

So, turns out it is addressfield itself which clears the postal code and city elements.

In addressfield_standard_country_validate():

    $address = drupal_array_get_nested_value($form_state['values'], $parents);

    // Clear the country-specific field values.
    $country_specific_data = array(
      'dependent_locality' => '',
      'locality' => '',
      'administrative_area' => '',
      'postal_code' => '',
    );
    $address = array_diff_key($address, $country_specific_data);

    drupal_array_set_nested_value($form_state['values'], $parents, $address);
    drupal_array_set_nested_value($form_state['input'], $parents, $address);
donquixote’s picture

Another idea would be to remember values on client side.

donquixote’s picture

I wonder if the browser autocomplete always fills the country first?

donquixote’s picture

Another look at addressfield_standard_country_validate():

/**
 * Element validate callback: rebuilds the form on country change.
 */
function addressfield_standard_country_validate($element, &$form_state) {

  if ($element['#default_value'] != $element['#value']) {

    $parents = $element['#parents'];
    array_pop($parents);
    $address = drupal_array_get_nested_value($form_state['values'], $parents);

    // Clear the country-specific field values.
    $country_specific_data = array(
      'dependent_locality' => '',
      'locality' => '',
      'administrative_area' => '',
      'postal_code' => '',
    );
    $address = array_diff_key($address, $country_specific_data);

    drupal_array_set_nested_value($form_state['values'], $parents, $address);
    drupal_array_set_nested_value($form_state['input'], $parents, $address);

    $form_state['rebuild'] = TRUE;
  }
}

The function checks whether the #default_value for country is different from #value for country.
Maybe it should do the equivalent check for other elements:
Only reset those fields where #default_value and #value are the same.

This would be in addition to the ajax delay.

donquixote’s picture

Maybe it should do the equivalent check for other elements:
Only reset those fields where #default_value and #value are the same.

Problem: How do we determine the #default_value of those fields, from within addressfield_standard_country_validate()?
Maybe instead we should register a validation handler on a parent element, so that it has access to each child element's #default_value?
Can we assume that the array structure is consistent?

Next problem: Even if $element['locality']['#default_value'] === $element['locality']['#value'], we cannot be sure that this postal code has to be deleted when changing the country.

Thanks to addressfield_field_widget_form() calling $input_address = drupal_array_get_nested_value($form_state['input'], $parents); the #default_value is not the original value, but just the previous state of the form.

E.g. what happens if the user fills in the other fields before changing the country?

Usual case:
- User loads page with country = NL, locality = Amsterdam
- User sets locality = Berlin
- User sets country = DE
- Ajax request fires, finds that country and locality have changed -> so nothing is emptied, which is good.

But:
- User loads page with country = NL, locality = Amsterdam
- User sets locality = Berlin
- Some other ajax request happens, which sets [locality][#default_value] = Berlin
- User sets country = DE
- Ajax request fires, finds that country has changed, but locality is still the same -> locality is emptied, which is not good.

Maybe this is acceptable and to be expected, as long as the country change was initiated manually by the user?
(as opposed to a browser autofill)

donquixote’s picture

Issue summary: View changes