Hey,

For a Drupal 7 module I'm working on, I'm trying to figure a way for my autocomplete_path function for a textfield to return additional information back to the hook_form function it was called from.

If including the additional info in the return (which I have a strong feeling) is not possible, is there a way to define variables in a scope to make this work decently for a per user per form basis?

The reason I want to do this is that I'm getting the auto complete info via querying another site for musical information and returning "artist - song" for the textfield but would like the function to also return a song ID (hidden form the user) that I could use back in the form show additional information about the song (e.g. album image.)

Any help is appreciated.

Thanks!
-mr

Comments

Jaypan’s picture

There are two ways I can think to do this:
1) Include the ID in the text.
2) On submit, query the external site once more using the data that you have, and get the ID that way.

mr1’s picture

Thanks for the quick response. I have considered these but have realized the following problems with them:

1) The id is a 12 digit number and I can't bring myself to put it in the text.

2) The external site I'm querying may not return the correct information on a second query based off of the returned text for the first as it takes into account other factors in addition to the actual query such as popularity. Ideally I would love to know exactly what the user selected the first time instead of hoping the second query returns it.

Thanks again for your response. I appreciate you trying to help me.

Jaypan’s picture

Ok, another way you can do it is like this. When generating the array that is turned into the autocomplete values, you generally set the key and the value to the same:

$values[$something->result1] = $something->result1;
$values[$something->result2] = $something->result2;
$values[$something->result3] = $something->result3;

Instead, you can split up the ID and the value. Something like this:

$values[$something->id1] = $something->result1;
$values[$something->id2] = $something->result2;
$values[$something->id3] = $something->result3;

This way the key is the ID. The only problem with this method is that I believe it will insert the ID into the textfield when the user selects the text, rather than inserting the result. So it doesn't look the cleanest, but it will work.

If this is no good for you, then the only other solution really is building your own autocomplete I think.

mr1’s picture

Yeah.. Looks like I'll have to pick my poison with this one.. Thanks again for trying to help.

I actually feel better knowing that there's no hidden option (4) that I couldn't find documented anywhere :)

migrations’s picture

I have the same question as MR. It one of the most default things to submit an ID to a hidden field on a autocomplete field. Is there any workarround available?

Jaypan’s picture

Read the whole thread.

phreestilr’s picture

@Jay Matwichuk

What you mentioned in comment #4019358 is exactly what I am trying to do, but I can't seem to get it to work correctly. When the user types in the autocomplete field, the suggestions include a user picture, the username, and the user's location. When one of the suggestions is selected, I would like just the username to be inserted into the textfield. I tried setting the key to the username in the way you suggested above, but it does not change what is inserted into the textfield.

Any ideas on how to do this?

Thanks!

Adam S’s picture

1. Create a widget that is an autocomplete text field

/**
 * Implementation of hook_widget().
 */
function openlayers_geocoder_widget(&$form, &$form_state, $field, $items, $delta = 0) {

  drupal_add_css(drupal_get_path('module', 'openlayers_geocoder') .'/openlayers_geocoder.css');
  drupal_add_js(drupal_get_path('module', 'openlayers_geocoder') .'/js/openlayers_geocoder.js');

  $element = array();
  $element = module_invoke('openlayers_cck', 'widget', $form, $form_state, $field, $items, $delta);

  $element['openlayers_geocoder_query'] = array(
    '#title' => t('Search address'),
    '#type' => 'textfield',
    '#autocomplete_path' => 'openlayers/geocoder/response',
    '#weight' => isset($field['widget']['position']) ? $field['widget']['position'] : OPENLAYERS_GEOCODER_POSITION_BELOW,
    '#attributes' => array(
      'geoautocomplete' => TRUE,
      'fieldname' => $field['field_name'],
      'dashed' => str_replace('_', '-', $field['field_name']),
    ),
  );


  $element['#type'] = 'openlayers_wkt_widget';
  $element['#default_value'] = $items;

  return $element;
}

2. Create menu for autocomplete.

  $items['openlayers/geocoder/response'] = array(
    'page callback' => 'openlayers_geocoder_get_response',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

3. Create a callback that queires the third party system and returns multiple options for the autocomplete text field

/**
 * Query Google geocoding web service.
 * 
 * @param $address
 *    Address or location name
 * @return
 *    Array of placemarks
 */
function openlayers_geocoder_response($address) {

  $locations = $args = array();

  // The address that you want to geocode.
  $args['address'] = str_replace(' ', '+', $address);

  // The language in which to return results. If "language" is not supplied,
  // the geocoder will attempt to use the native language of the domain
  // from which the request is sent wherever possible.
  $language = language_default();
  $args['language'] = $language->language;

  // Response encoding.
  $args['oe'] = 'utf-8';

  //  Indicates whether or not the geocoding request comes from a device with a location sensor. This value must be either true or false.
  $args['sensor'] = 'false';

  //The textual latitude/longitude value for which you wish to obtain the closest, human-readable address.
  // $args['latlng'] = '40.714224,-73.961452';

  // The bounding box of the viewport within which to bias geocode results more prominently.
  // $args['bounds'] = '';

  // The region code, specified as a ccTLD ("top-level domain") two-character value.
  // $args['region'] = '';

  $query = http_build_query($args, '', '&');
  
  if (function_exists("curl_init")) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, GOOGLE_GEOCODER_URL . $query);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
    curl_close($ch);  
  }
  else {
    $result = file_get_contents(GOOGLE_GEOCODER_URL . $query);
  }
  
  $response = json_decode($result);

  if ($response->status == GOOGLE_GEOCODER_STATUS_OK) {
    foreach ($response->results as $result) {
      $location = $components = array();
      foreach ($result->address_components as $component) {
        $key = $component->types[0];
        $components[$key] = $component->long_name;
        if ($key == 'country') {
          $components['country_code'] = $component->short_name;
        }
      }
      $components['street_address'] = $location['address'] = $result->formatted_address;
      $location['components'] = $components;
      $location['location'] = (array) $result->geometry->location;
      $location['bounds'] = (array) $result->geometry->viewport;
      $locations[] = $location;
    }
  }
  return $locations;
}

4. Extend the Drupal autocomplete server side code to process query.

Drupal.behaviors.openlayers_geocoder = function (context) {

  if (Drupal.jsAC) {
    /**
   * Hides the autocomplete suggestions
   */
    Drupal.jsAC.prototype.hidePopup = function (keycode) {
      // Select item if the right key or mousebutton was pressed
      if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
        this.input.value = this.selected.autocompleteValue;
      }
      // Hide popup
      var popup = this.popup;
      if (popup) {
        this.popup = null;
        $(popup).fadeOut('fast', function() {
          $(popup).remove();
        });
        // Add-on for OpenLayer Geocoder module
        if ($(this.input).attr('geoautocomplete')) {
          geocoder = new Drupal.Geocoder(this);
          geocoder.process(this.input.value);
        }
      }
      this.selected = false;
    
    };
  
  }
  
};

5.Add js script to make Ajax query

/**
 * Performs a search
 */
Drupal.Geocoder.prototype.process = function (query) {
  
  var fieldname = $(this.data.input).attr('fieldname');
  var dashed = $(this.data.input).attr('dashed');
  var formid = $("input[name=form_id]").val();
  var contenttype = formid.replace("_node_form", "");
  
  var data = {
    query:query,
    fieldname:fieldname,
    content_type:contenttype
  };

  $.ajax({
    type: 'POST',
    url: this.data.db.uri + '/process',
    data: data,
    dataType: 'json',
    success: function(point) {

      if (point.longitude && point.latitude) {

        var data = $('#openlayers-cck-widget-map-' + fieldname).data('openlayers');
        if (!data.map.displayProjection) {
          data.map.displayProjection = 4326;
        }
        var displayProjection = new OpenLayers.Projection('EPSG:' + data.map.displayProjection);
        var projection = new OpenLayers.Projection('EPSG:' + data.map.projection);
        var vectorLayer = data.openlayers.getLayersBy('drupalID', "openlayers_drawfeatures_layer");
        var geometry = new OpenLayers.Geometry.Point(point.longitude, point.latitude).transform(displayProjection, projection);
        var bounds = new OpenLayers.Bounds(point.box.west, point.box.south, point.box.east, point.box.north).transform(displayProjection, projection);

        //Remove all points, unless CCK widget settings prevent it.
        if (point.keep_points) {
          data.openlayers.setCenter(new OpenLayers.LonLat(point.longitude, point.latitude).transform(displayProjection, projection));
        }
        else {
          vectorLayer[0].removeFeatures(vectorLayer[0].features);
          data.openlayers.zoomToExtent(bounds);
          // Adding CCK fields autocompletion
          if (point.fields) {
            jQuery.each(point.fields, function () {
              $(this.type + "[name*='" + this.name + "']").attr('value', this.value);
              if (!this.override) {
                $(this.type + "[name*='" + this.name + "']").attr('readonly', 'TRUE').addClass('readonly');
              }
            });
          }
        }
        
        //Add point to map.
        vectorLayer[0].addFeatures([new OpenLayers.Feature.Vector(geometry)]);
      }
    }
  });

}

6. Query the third party service again! Acquire all information and map each piece to each CCK field for the ajax call to the server to change the 'value' attribute of each text field.

/**
 * Process a successful response returning location
 * coordinates and replacement tokens for filed completion
 */
function openlayers_geocoder_process_response() {

  $location['latitude'] = 0;
  $location['longitude'] = 0;
  $location['box'] = array();
  $location['fields'] = array();
  $location['keep_points'] = false;

  $query = $_POST['query'];
  $fieldname = $_POST['fieldname'];
  $contenttype = $_POST['content_type'];

  if ($response = openlayers_geocoder_response($query)) {

    $result = array_shift($response);
    $location['latitude'] = $result['location']['lat'];
    $location['longitude'] = $result['location']['lng'];
    $location['box']['north'] = $result['bounds']['northeast']->lat;
    $location['box']['east'] = $result['bounds']['northeast']->lng;
    $location['box']['south'] = $result['bounds']['southwest']->lat;
    $location['box']['west'] = $result['bounds']['southwest']->lng;

    $field = content_fields($fieldname, $contenttype);
    if ($field && $field['widget']['type'] == 'openlayers_geocoder_widget') {
      if (isset($field['widget']['keep_points'])) {
        $location['keep_points'] = $field['widget']['keep_points'];
      }

      if (module_exists('token')) {
        $fields = openlayers_geocoder_widget_parse_settings($field['widget']);
        foreach ($fields as $name => $settings) {
          if ($settings['enable']) {
            $location['fields'][$name]['value'] = token_replace($settings['content'], 'geocoder', $result);
            $location['fields'][$name]['name'] = $name;
            $location['fields'][$name]['override'] = $settings['override'];
            $location['fields'][$name]['type'] = $settings['type'];
          }
        }
      }
    }
  }
  drupal_json($location);
}
Jaypan’s picture

Nice little tutorial!

migrations’s picture

Nice tutorial, many thanks (and always good to see OL code ;))

mr1’s picture

Wow. Thanks.. That is an awesome tutorial.. You should add that to the Drupal Docs :)