You are here

finder_autocomplete.module in Finder 7

Same filename and directory in other branches
  1. 6 modules/finder_autocomplete/finder_autocomplete.module

The finder autocomplete module.

File

modules/finder_autocomplete/finder_autocomplete.module
View source
<?php

/**
 * @file
 * The finder autocomplete module.
 */

/**
 * Implements hook_menu().
 *
 * @see hook_menu()
 */
function finder_autocomplete_menu() {
  $items = array();
  $items['finder_autocomplete/autocomplete'] = array(
    'page callback' => 'finder_autocomplete_autocomplete',
    'access arguments' => array(
      'use finder',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_finder_element_handlers().
 *
 * @see hook_finder_element_handlers()
 */
function finder_autocomplete_finder_element_handlers() {
  return array(
    'autocomplete' => array(
      '#title' => t('Autocomplete textfield'),
      '#module' => 'finder_autocomplete',
    ),
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * @see hook_form_FORM_ID_alter()
 */
function finder_autocomplete_form_finder_admin_element_edit_alter(&$form, $form_state) {
  $finder =& $form_state['storage']['finder'];
  $finder_element_id =& $form_state['storage']['finder_element_id'];
  $element =& $form_state['storage']['finder_element_defaults'];
  if ($element->element == 'autocomplete') {
    $form['settings']['form']['field_prefix'] = array(
      '#type' => 'textfield',
      '#title' => t('Field prefix'),
      '#default_value' => $element->settings['form']['field_prefix'],
      '#weight' => 150,
      '#description' => t('Displayed directly before the form input field.'),
    );
    $form['settings']['form']['field_suffix'] = array(
      '#type' => 'textfield',
      '#title' => t('Field suffix'),
      '#default_value' => $element->settings['form']['field_suffix'],
      '#weight' => 160,
      '#description' => t('Displayed directly after the form input field.'),
    );
    $form['settings']['form']['maxlength'] = array(
      '#type' => 'textfield',
      '#title' => t('Max length'),
      '#default_value' => $element->settings['form']['maxlength'],
      '#weight' => 170,
      '#description' => t('
        The maximum amount of characters to accept as input.
        <em> Using this may truncate longer field values and fail with exact matching.</em>'),
    );
    $form['settings']['form']['size'] = array(
      '#type' => 'textfield',
      '#title' => t('Size'),
      '#default_value' => $element->settings['form']['size'],
      '#weight' => 180,
      '#description' => t('Width of the textfield (in characters).  Leave blank to default to <em>60</em>'),
    );
    $form['settings']['form']['max_suggestions'] = array(
      '#type' => 'textfield',
      '#title' => t('Max suggestions'),
      '#default_value' => $element->settings['form']['max_suggestions'] ? $element->settings['form']['max_suggestions'] : 10,
      '#weight' => 190,
      '#description' => t('It is a good idea to limit the number of autocomplete suggestions that appear.'),
    );
    $operators = finder_condition_args();
    foreach ($operators as $k => $v) {
      $operators[$k] = finder_condition_args_label($v, t('autocomplete suggestions'), t('typed keywords'));
    }
    $form['settings']['form']['match'] = array(
      '#type' => 'radios',
      '#title' => t('Autocomplete matching'),
      '#default_value' => $element->settings['form']['match'] ? $element->settings['form']['match'] : 'c',
      '#options' => $operators,
      '#weight' => 190,
      '#description' => t('"Contains" is the most common autocomplete match method.  <a href="!url">Configure custom matching</a>.'),
    );
    $form['settings']['form']['autosubmit'] = array(
      '#type' => 'checkbox',
      '#title' => t('Submit upon selection'),
      '#default_value' => $element->settings['form']['autosubmit'] ? $element->settings['form']['autosubmit'] : 0,
      '#weight' => 200,
      '#description' => t('
        Normally selecting a suggested autocomplete value (with mouse click or
        enter button) does not submit the form, even if the entire value was
        typed by the user, this option shortens the workflow for single element
        finders by automatically submitting the form when the selection is
        made.'),
    );
    $form['settings']['form']['delimit_autocomplete'] = array(
      '#type' => 'textfield',
      '#title' => t('Treat delimited values as separate keywords for autocomplete suggestions'),
      '#default_value' => isset($element->settings['form']['delimit_autocomplete']) ? $element->settings['form']['delimit_autocomplete'] : '',
      '#weight' => 210,
      '#description' => t('For example, if you type a space here, the autocompleted value will be expanded into a value for each word.  Leave empty to disable this feature.'),
    );
  }
}

/**
 * Implements hook_finder_element().
 *
 * @see hook_finder_element()
 */
function finder_autocomplete_finder_element($element, &$form_element) {
  if ($element->element == 'autocomplete') {
    $form_element['#type'] = 'textfield';
    $autocomplete_path = 'finder_autocomplete/autocomplete/' . $element->finder_id . '/' . $element->finder_element_id;
    if ($element->settings['form']['autosubmit']) {
      $form_element['#theme'] = 'finder_autocomplete_textfield';
    }
    $form_element['#autocomplete_path'] = $autocomplete_path;
    $properties = array(
      'field_prefix',
      'field_suffix',
      'maxlength',
      'size',
    );
    foreach ($properties as $property) {
      if ($element->settings['form'][$property]) {
        $form_element['#' . $property] = $element->settings['form'][$property];
      }
    }
  }
}

/**
 * Menu callback; get autocomplete suggestions.
 */
function finder_autocomplete_autocomplete($finder_id, $finder_element_id, $keywords = '') {
  $choices = array();
  if ($keywords === '') {
    drupal_json_output($choices);
  }
  $keywords = array(
    $keywords,
  );
  $finder = finder_load($finder_id);
  $element =& finder_element($finder, $finder_element_id);
  $fields =& $element->settings['choices']['field'];
  foreach ($fields as $key => $field) {
    $field_info[$key] = finder_split_field($field);
    $field_names[$key] = $field_info[$key]['field'];
  }
  $pager =& $element->settings['form']['max_suggestions'];
  $match = $element->settings['form']['match'] ? $element->settings['form']['match'] : 'c';
  if (!empty($element->settings['form']['delimit_autocomplete'])) {
    foreach ($keywords as $k => $v) {
      unset($keywords[$k]);
      $exploded = explode($element->settings['form']['delimit_autocomplete'], $v);
      foreach ($exploded as $e) {
        $keywords[] = trim($e);
      }
    }
  }
  $options = finder_find($finder, array(
    $finder_element_id => $keywords,
  ), 'choices', $match, $pager);
  if ($options) {
    foreach ($options as $option) {
      $autofill = theme('finder_autocomplete_autofill', array(
        'option' => $option,
        'finder_element' => $element,
      ));
      $suggestion = theme('finder_autocomplete_suggestion', array(
        'option' => $option,
        'finder_element' => $element,
      ));
      if ($autofill && $suggestion) {
        $choices[$autofill] = $suggestion;
      }
    }
  }
  drupal_json_output($choices);
}

/**
 * Implements hook_theme().
 *
 * @see hook_theme()
 */
function finder_autocomplete_theme() {
  return array(
    'finder_autocomplete_suggestion' => array(
      'variables' => array(
        'option' => NULL,
        'finder_element' => NULL,
      ),
    ),
    'finder_autocomplete_autofill' => array(
      'variables' => array(
        'option' => NULL,
        'finder_element' => NULL,
      ),
    ),
    'finder_autocomplete_textfield' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Theme an autocomplete popup suggestion.
 *
 * This is a very useful theme function as it lets you pretty much display any
 * sort of HTML you want. By default the CSS styles will be really ugly, but
 * with a bit of work you can really make this do something special. For
 * example - if your autocomplete searches for a value that is unique to
 * different nodes (like a node title or sku perhaps) you can assume that each
 * match corresponds to a node. If you debug the $option and $element variables,
 * you will see that you have enough data there to node_load() or user_load()
 * a full object, and display it any way you like, such as teasers (using the
 * node_view() function). You can even have pictures and links in the HTML!
 * Even if you don't expect unique nodes/users/etc.. there is still a lot of
 * potential for adding stylized output here.
 * Here is some CSS to get you started on styling something like a node teaser
 * in an autocomplete popup suggestion. Note that this CSS is very general and
 * may affect other forms on your site - take care to add more specific
 * selectors.
 * <code>
 *   <style type='text/css'>
 *   #autocomplete li.selected {
 *     background: #eee;
 *     color: inherit;
 *   }
 *   #autocomplete li {
 *     white-space: normal;
 *     margin: 0;
 *   }
 *   #autocomplete ul.links,
 *   #autocomplete ul.links li {
 *     background: none;
 *   }
 *   #autocomplete p {
 *     margin: 0;
 *   }
 *   </style>
 * </code>
 *
 * @param $variables
 *   'option' - The result data.
 *   'finder_element' - The finder element object.
 */
function theme_finder_autocomplete_suggestion($variables) {
  $option =& $variables['option'];
  return $option->{$option->display_field};
}

/**
 * Theme an autocomplete autofill text value.
 *
 * This will actually set the value sent to the submit function. Be careful
 * with this if you need to use it. Getting fancy here might mean you have
 * to start hooking into the form's validate/submit operations to allow Finder
 * to process the results properly.
 *
 * @param $variables
 *   'option' - The result data.
 *   'finder_element' - The finder element object.
 */
function theme_finder_autocomplete_autofill($variables) {
  $option =& $variables['option'];
  return $option->{$option->field_name};
}

/**
 * Format an autocomplete that autosubmits.
 *
 * This was a result of user dissatisfaction with the default Drupal
 * autocomplete method and allows for some of the configuration options that
 * come with Autocomplete to function properly. The regular autocomplete
 * textfields in finder just use theme_textfield by default.
 *
 * @param $variables
 *   An array with keys:
 *   'element' - The Forms API form element.
 */
function theme_finder_autocomplete_textfield($variables) {
  $element = $variables['element'];
  $element['#attributes']['type'] = 'text';
  element_set_attributes($element, array(
    'id',
    'name',
    'value',
    'size',
    'maxlength',
  ));
  _form_set_class($element, array(
    'form-text',
  ));
  $extra = '';
  if (!empty($element['#autocomplete_path']) && drupal_valid_path($element['#autocomplete_path'])) {
    $module = 'finder_autocomplete';
    drupal_add_js(drupal_get_path('module', $module) . '/' . $module . '.js');
    $element['#attributes']['class'][] = 'form-autocomplete';
    $attributes = array();
    $attributes['type'] = 'hidden';
    $attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
    $attributes['value'] = url($element['#autocomplete_path'], array(
      'absolute' => TRUE,
    ));
    $attributes['disabled'] = 'disabled';
    $attributes['class'][] = 'finder-autocomplete';
    $extra = '<input' . drupal_attributes($attributes) . ' />';
  }
  $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
  return $output . $extra;
}

/**
 * Implements hook_finder_api().
 */
function finder_autocomplete_finderapi(&$object, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'finder_import') {
    $finder =& $object;

    // Handle custom matching.
    $custom_matching = variable_get('finder_custom_matching', array());
    foreach ($finder->elements as $feid => &$element) {
      $match =& $element->settings['advanced']['match'];
      if (is_array($match)) {
        $match_data = reset($match);
        $found_key = FALSE;
        foreach ($custom_matching as $custom_key => $custom_match) {
          if ($custom_match['operator'] == $match_data['operator'] && $custom_match['value_prefix'] == $match_data['value_prefix'] && $custom_match['value_suffix'] == $match_data['value_suffix']) {
            $found_key = $custom_key;
          }
        }
        if ($found_key) {
          $match = $found_key;
        }
        else {
          $new = NULL;
          $custom = 0;
          while (is_null($new)) {
            if (!isset($custom_matching['c' . $custom])) {
              $new = array(
                'c' . $custom,
              );
              break;
            }
            $custom++;
          }
          $custom_matching[$new] = $custom_match;
          $match = $new;
        }
      }
    }
    variable_set('finder_custom_matching', $custom_matching);
  }
  elseif ($op == 'finder_export') {
    $finder =& $object;

    // Change how match method is stored to support custom matching.
    $custom_matching = variable_get('finder_custom_matching', array());
    foreach ($finder->elements as $feid => &$element) {
      $match = $element->settings['advanced']['match'];
      if (isset($custom_matching[$match])) {
        $element->settings['advanced']['match'] = array(
          $match => $custom_matching[$match],
        );
      }
    }
  }
}

Functions

Namesort descending Description
finder_autocomplete_autocomplete Menu callback; get autocomplete suggestions.
finder_autocomplete_finderapi Implements hook_finder_api().
finder_autocomplete_finder_element Implements hook_finder_element().
finder_autocomplete_finder_element_handlers Implements hook_finder_element_handlers().
finder_autocomplete_form_finder_admin_element_edit_alter Implements hook_form_FORM_ID_alter().
finder_autocomplete_menu Implements hook_menu().
finder_autocomplete_theme Implements hook_theme().
theme_finder_autocomplete_autofill Theme an autocomplete autofill text value.
theme_finder_autocomplete_suggestion Theme an autocomplete popup suggestion.
theme_finder_autocomplete_textfield Format an autocomplete that autosubmits.