Friday, April 15, 2011

Turn a select into an autocomplete textfield in a Views exposed form

Within a Views exposed filter form, there was a select for a node reference field. The client wanted the select to be a textfield because it only required 3 characters and it would be faster for users to type them than find them in a select. I decided it would be even cooler to make it an autocomplete textfield.

I used http://drupal.org/node/179922#comment-1467932 as a starting point and made modifications.

Here's my .module code for Drupal 6.x:
<?php

// $Id: MODULE_NAME.module

/**
*@file
* Module Name
* Turns a select into an autocomplete textfield.
*
* Specific for 'NODE_NAME' nodes and the 'VIEW_NAME' view
*
*/

/**
* Implementation of hook_menu().
*/
function MODULE_NAME_menu(){
 
//Initialize the $items array.
    $items = array();
 
//Add menu for the node autocomplete textfield's #autocomplete_path.
    $items['NODE_NAME/autocomplete'] = array(
//Required. The untranslated title of the menu item.
        'title' => t('Node Title'),
//The function to call to display a web page when the user visits the path. If omitted, the parent menu item's callback will be used instead.
        'page callback' => 'NODE_NAME_autocomplete',
//An array of arguments to pass to the access callback function. Integer values pass the corresponding URL component.
        'access arguments' => array('access content'),
//Callbacks simply register a path so that the correct function is fired when the URL is accessed.
        'type' => MENU_CALLBACK
    );
 
//Return the $items array with the new menu item.
    return $items;
}

/**
* Implementation of hook_form_alter().
*/
function MODULE_NAME_form_alter(&$form, $form_state, $form_id){

//Only affect a views exposed form.
    if($form_id == 'views_exposed_form'){
  
//If a view exists, retreive form_state information.
        if (array_key_exists('view', $form_state)){
   
//If the view has a name,
            if (array_key_exists('name', $form_state['view'])){
    
//Only affect the specified view.
                if ($form_state['view']->name == 'VIEW_NAME'){
     
//Turn the node select into a textfield.
                    $form['SELECT_FIELD']['#type'] ='textfield';
//Turns the node textfield into an autocomplete textfield.     
                    $form['SELECT_FIELD']['#autocomplete_path'] ='NODE_NAME/autocomplete';
//Remove options from the node autocomplete textfield so errors aren't thrown if the user enters something that doesn't exist.
     unset($form['SELECT_FIELD']['#options']);
                }
            }
        }
    }
}

/**
* Autocomplete Function for nodes.
*/
function NODE_NAME_autocomplete($string = ''){
 
//Initialize the $matches array.
    $matches = array();
 
//Ensure that a string has been passed. 
    if ($string){
  
//Remove any extra spaces before or after the meaningful string.  
        $string = trim($string);
  
//Set the query to select all nodes that are published and match what the user entered. - use '%%%s%%' to match from anywhere in the title
  $query = "SELECT title FROM {node} WHERE type='NODE_NAME' and title like '%s%'";
//Run the query and restrict the results to the first 10.
  $result = db_query_range($query,$string,0,10);
  
//Ensure there are results returned from the query.  
  if ($result != false){
   
//Loop through the all of the results.
   while ($NODE_NAME = db_fetch_object($result)){
    
//Add the plain text select field to the $matches array with the $key set as the same.   
    $matches[$NODE_NAME->title] = check_plain($NODE_NAME->title);
   }
  }
    }

//Converts the $matches array into its Javascript equivalent and prints it to the page output.  
    print drupal_to_js($matches);
//Exit the function. 
    exit;
}

/**
* Implementation of hook_views_query_alter().
*/
function MODULE_NAME_views_query_alter(&$view, &$query){
 
//Ensure that the WHERE information exists as an array in the query which is not empty.
    if (is_array($query->where) && !empty($query->where)){

//Search the where clauses for the select field clause.
  $index_value = array_search("node_data_SELECT_FIELD.SELECT_FIELD = '%s'", $query->where[0][clauses]);

//If the select field clause was found,  
  if ($index_value != '' && $index_value != false){

//If the cooresponding argument is not blank or all,
   if($query->where[0][args][$index_value] == '' || strtolower($NODE_NAME_title) == "all"){

//remove the clause and argument because the user isn't searching by the select field.
    unset($query->where[0][clauses][$index_value]); 
    unset($query->where[0][args][$index_value]); 
   }
   else{
//Get the argument that cooresponds to the select field where clause.
    $NODE_NAME_title = strtoupper($query->where[0][args][$index_value]);
    
//Select the node id of the select field argument.
    $NODE_NAME_nid = db_result(db_query("SELECT nid FROM {node} WHERE title = '%s'",$NODE_NAME_title));
    
//If a node id was found for the select field argument,
    if($NODE_NAME_nid){

//replace the select field with the cooresponding node id in the argument.
     $query->where[0][args][$index_value] = $NODE_NAME_nid;
    }
    else{
//If the node id wasn't found, replace the select field with 0 as a nonexistent node id that will not return results.    
     $query->where[0][args][$index_value] = 0;
    }
   }
  }
 }
}
?>

1 comment:

  1. Few changes are needed for Drupal 7 in NODE_NAME_autocomplete() function:

    1.
    //Set the query to select all nodes that are published and match what the user entered. - use '%%%s%%' to match from anywhere in the title
    $query = "SELECT title FROM {node} WHERE type='NODE_NAME' and title like '%s%'";
    //Run the query and restrict the results to the first 10.
    $result = db_query_range($query,$string,0,10);

    to

    $query = "SELECT title FROM {node} WHERE type='NODE_NAME' and title LIKE :input";
    $result = db_query_range($query, 0, 10, array(':input'=>db_like($string) . '%'));


    2.
    while ($NODE_NAME = db_fetch_object($result)){

    to

    foreach($result as $project) {


    3.
    print drupal_to_js($matches);

    to

    print drupal_json_encode($matches);

    ReplyDelete