apw
apw

Reputation: 51

Drupal 7 Form API - custom autocomplete filter

Using the D7 form API, I need to create a hierarchical set of filters, each having a dependency of the previous filter(s). When a selection is made on one of the filters, all filters down the line are automatically updated based on the selection(s) up the chain. Seems straight forward enough, but there is a little bit more than that.

For a example, lets say I have a database with +75k product records I want to display.

Obviously I can't show all options in each select-multi, so here is how I envisioned it working:

  1. Page loads, all filters are empty
  2. Each filter has a little text input above it. Typing that that field auto-completes, but the results are dumped into the select-multi input.
  3. User selects an option(s) from the list that has just been populated.
  4. All filters down the line automatically populate, refined by the selection(s) up the chain.

I've been looking at the D7 form api, and I can't find any mention of this sort of functionality. I know there are #ajax and #state callbacks, but the text input auto-complete populating the select-multi and triggering events down the chain is a little fuzzy.

If it helps, I've built this entire functionality already using jQuery. My goal now is to port it over into Drupal, using the proper form API.

Upvotes: 1

Views: 1160

Answers (2)

Umar
Umar

Reputation: 21

I know it has been long time, but would still share for the sake of others looking for a solution. You will have to implement your own flavour of each autocomplete function as per the need. The important thing to note being the autocomplete_path that is setup by using values from previous filter. Rest should be self explanatory.

// hook_menu would look something like this
function hook_menu() {
  $menu = array();
  $menu['hierarchical_filter'] = array(
    'title' => 'Autocomplete Ajax Cascading Form',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('hierarchical_filter_form'),
    'access callback' => TRUE,
  );
  $menu['country_list'] = array(
    'title' => 'Country List autocomplete',
    'page callback' => 'country_list_autocomplete',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $menu['state_list'] = array(
    'title' => 'State List autocomplete',
    'page callback' => 'state_list_autocomplete',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $menu['city_list'] = array(
    'title' => 'City List autocomplete',
    'page callback' => 'city_list_autocomplete',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $menu;
}
function country_list_autocomplete($string='') {
  $values = array('United States' => 'United States', 'South Africa' => 'South Africa', 'Russian Federation' => 'Russian Federation', 'Singapore' => 'Singapore', 'China' => 'China');
  $matches = array_filter($values, function($item) use($string) {
    if(stripos($item, $string) !== FALSE) return TRUE;
    return FALSE;
  });
  drupal_json_output($matches);
}
function state_list_autocomplete($country_code, $state='') {
  $values = array('South Africa' => array('Mpumalanga' => 'Mpumalanga', 'Gauteng' => 'Gauteng', 'Limpopo' => 'Limpopo', 'Northern Cape' => 'Northern Cape'),
    'United States' => array('Alabama'=>'Alabama', 'Arizona'=>'Arizona'));
  $matches = array_filter($values[$country_code], function($item) use($state) {
    if(stripos($item, $state) !== FALSE) return TRUE;
    return FALSE;
  });
  drupal_json_output($matches);
}
function city_list_autocomplete($state, $city='') {
  $values = array('northern cape' => array('Barkly West' => 'Barkly West', 'Campbell' => 'Campbell', 'Delportshoop' => 'Delportshoop'),
    'Alabama' => array('Butler'=>'Butler', 'Calera'=>'Calera', 'Helena'=>'Helena'));
  $matches = array_filter($values[$state], function($item) use($city) {
    if(stripos($item, $city) !== FALSE) return TRUE;
    return FALSE;
  });
  drupal_json_output($matches);
}
function hierarchical_filter_form($form, &$form_state) {
  $form = array();
  $form['country_list'] = array(
    '#type' => 'textfield',
    '#title' => 'Choose Country',
    '#autocomplete_path' => 'country_list',
    '#ajax' => array(
      'callback' => 'country_callback',
      'wrapper' => 'states_wrapper',
    ),
  );
  $form['state_list'] = array(
    '#type' => 'textfield',
    '#title' => 'Choose State',
    '#prefix' => '<div id="states_wrapper">',
    '#suffix' => '</div>',
  );
  if(isset($form_state['values']['country_list'])) {
    $form['state_list']['#autocomplete_path'] = 'state_list/'.$form_state['values']['country_list'];
    $form['state_list']['#ajax'] = array(
      'callback' => 'state_callback',
      'wrapper' => 'city_wrapper',
    );
  }
  $form['city_list'] = array(
    '#type' => 'textfield',
    '#title' => 'Choose City',
    '#prefix' => '<div id="city_wrapper">',
    '#suffix' => '</div>',
  );
  if(isset($form_state['values']['state_list'])) {
    $form['city_list']['#autocomplete_path'] = 'city_list/'.$form_state['values']['state_list'];
  }
  return $form;
}
function country_callback($form, &$form_state) {
  $commands = array();
  // On changing country, make sure state and city fields are reset
  $form['state_list']['#value'] = '';
  $form['city_list']['#value'] = '';
  $commands[] = ajax_command_replace('#states_wrapper', drupal_render($form['state_list']));
  $commands[] = ajax_command_replace('#city_wrapper', drupal_render($form['city_list']));
  return array('#type' => 'ajax', '#commands' => $commands);
}
function state_callback($form, &$form_state) {
  // On changing state, make sure city field is reset
  $form['city_list']['#value'] = '';
  return $form['city_list'];
}

Hope this will be helpful to someone.

Upvotes: 2

Eyal
Eyal

Reputation: 788

Create a forum function lets say my_module_filters_form than add a menu callback using hook_menu

function my_module_menu() {
  $items['my_module/ccallback' = array(
    // missing a menu item type (forgot the syntax)
    'callback function' => 'drupal_get_form',
    'callbsck arguments' => array('my_module_filters_form'),
    'access callback' => TRUE
  );
}

each time a change is made in the HTML form do an ajax call to that callback and replace the HTML with your output.

Upvotes: 0

Related Questions