Drupal 7: Auto-fill Zip code using Geocoder and Address Field modules

 

A few notes for reference on how to do this as well as some gotchas that makes me wonder if this is a good idea in the first place.  A very similar approach could be taken if you wanted to have the zip code entered and calculate the city and state from that.

Modules being used:

Jumping right into the code:

<?php
/**
 * Implements hook_form_alter
 * 
 * @param type $form
 * @param type $form_state 
 */
function hook_form_FORM_ID_alter(&$form, $form_state) {
  // Auto-fill ZIP Code once we have city and state
  // - element_children filters out properties (ie keys starting with "#")
  foreach(element_children($form['field_address'][$form['language']['#value']]) as $address_key) {
    if(is_numeric($address_key)) { // filter out "add_more" button element
      // Setup AJAX to be called whenever State drop-down change event happens
      $form['field_address'][$form['language']['#value']][$address_key]['locality_block']['administrative_area']['#ajax'] = array(
        'callback' => 'hook_zip_autofill_callback',
        'wrapper' => 'field-address-add-more-wrapper',
        'progress' => array(
          'type' => 'throbber',
          'message' => 'Determining ZIP Code... ',
        ),
      );
      
      // Geocode and fill in as #default_value for Zip code
      $zip_code_calculated = FALSE;
      if(!empty($form_state['values']) && !empty($form_state['values']['field_address'][$form_state['values']['language']][$address_key])) {
        $address = $form_state['values']['field_address'][$form_state['values']['language']][$address_key];
        if(!empty($address['thoroughfare']) && !empty($address['locality']) && !empty($address['administrative_area'])) {
          $geocode_address = $address['thoroughfare'] . ', ';
          if(!empty($address['premise'])) {
            $geocode_address .= $address['premise'] . ', ';
          }
          $geocode_address .= $address['locality'] . ', ';
          $geocode_address .= $address['administrative_area'] . ', ';
          $geocode_address .= $address['country'];
          $point = geocoder('google', $geocode_address);
          if(isset($point) && !empty($point->data) && !empty($point->data['geocoder_address_components'])) {
            foreach($point->data['geocoder_address_components'] as $component) {
              foreach($component->types as $type) {
                switch($type) {
                  case 'postal_code': // zip code
                    $form['field_address'][$form['language']['#value']][$address_key]['locality_block']['postal_code']['#value'] = $component->short_name;
                    // since we have to set the #value (see http://drupal.stackexchange.com/q/3875/6581) it will override any user input, so just disable it
                    $form['field_address'][$form['language']['#value']][$address_key]['locality_block']['postal_code']['#disabled'] = TRUE;
                    $zip_code_calculated = TRUE;
                    break;
                }
              }
            }
          }
        }
      }
      if(!$zip_code_calculated) { // allow user to edit if it wasn't explicitly calculated and set
        $form['field_address'][$form['language']['#value']][$address_key]['locality_block']['postal_code']['#disabled'] = FALSE;
      }
    }
  }
}

/**
 * Callback to autofill Zip code 
 */
function hook_zip_autofill_callback($form, $form_state) {
  return $form['field_address'][$form['language']['#value']];
}
?>

 

Some notes:

  • Probably need to use a different geocoder than Google and I think Google only allows use if to be displayed on a Google Map.
  • element_children() was a great discovery (and I think the correct answer to how to "Do a render foreach loop on a regular field")
  • There are several 'gotchas' to auto-filling ZIP code.  Not the least of which is having to use #value which prevents any user input from being saved - not ideal.  Plus, if the user goes back and changes the city / address fields it will not update unless you change the State again.  I tried adding the AJAX callback to those fields, but it just gets super clunky with way too many requests, focus changes, values being replaced, etc.
  • A good next step to this would be adding two hidden fields for each address that records the geocoded ZIP and address used to geocode against.  That way we maybe could use that value to determine if the user entered a different value (which would be preserved).  Or something like that...

That said, it is definitely nifty to have postal codes auto-filled and it removes one more barrier to having users enter travel reviews in this case.

About Us

Experienced Drupal Development from Des Moines, Iowa.

We do Drupal websites great and small – tailored to fit your needs. Reach out to us so we can begin. Our clients are in Des Moines, Iowa and across the world. If you have a large scale custom web project, we can be the reliable web partner to make those dreams a reality. But, if you need a simple brochure site or an e-commerce platform for your business, we can come alongside and make that happen too. 

 

covenantlogogrey.png