ajax_example_autocomplete.inc

ajax_example_autocomplete.inc

Demonstrates usage of the Form API's autocomplete features.

This file provides three examples in increasing complexity:

  • A simple username autocomplete (usernames are unique, so little effort is required)
  • A node title autocomplete (titles are not unique, so we have to find the nid and stash it in the field)
  • A username autocomplete that updates a node title autocomplete with a changed #autocomplete_path so that the #autocomplete_path can have context (the username to use in the search).

File

ajax_example/ajax_example_autocomplete.inc

View source
<?php


/**
 * @file
 * ajax_example_autocomplete.inc
 *
 * Demonstrates usage of the Form API's autocomplete features.
 *
 * This file provides three examples in increasing complexity:
 * - A simple username autocomplete (usernames are unique, so little effort is
 *   required)
 * - A node title autocomplete (titles are not unique, so we have to find the
 *   nid and stash it in the field)
 * - A username autocomplete that updates a node title autocomplete with a
 *   changed #autocomplete_path so that the #autocomplete_path can have
 *   context (the username to use in the search).
 */

/**
 * A simple autocomplete form which just looks up usernames in the user table.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form.
 *
 * @return array
 *   Form array.
 */
function ajax_example_simple_autocomplete($form, &$form_state) {
    $form['info'] = array(
        '#markup' => '<div>' . t("This example does a simplest possible autocomplete by username. You'll need a few users on your system for it to make sense.") . '</div>',
    );
    $form['user'] = array(
        '#type' => 'textfield',
        '#title' => t('Choose a user (or a people, depending on your usage preference)'),
        // The autocomplete path is provided in hook_menu in ajax_example.module.
'#autocomplete_path' => 'examples/ajax_example/simple_user_autocomplete_callback',
    );
    return $form;
}

/**
 * This is just a copy of user_autocomplete().
 *
 * It works simply by searching usernames (and of course in Drupal usernames
 * are unique, so can be used for identifying a record.)
 *
 * The returned $matches array has
 * * key: string which will be displayed once the autocomplete is selected
 * * value: the value which will is displayed in the autocomplete pulldown.
 *
 * In the simplest cases (see user_autocomplete()) these are the same, and
 * nothing needs to be done. However, more more complicated autocompletes
 * require more work. Here we demonstrate the difference by displaying the UID
 * along with the username in the dropdown.
 *
 * In the end, though, we'll be doing something with the value that ends up in
 * the textfield, so it needs to uniquely identify the record we want to access.
 * This is demonstrated in ajax_example_unique_autocomplete().
 *
 * @param string $string
 *   The string that will be searched.
 */
function ajax_example_simple_user_autocomplete_callback($string = "") {
    $matches = array();
    if ($string) {
        $result = db_select('users')->fields('users', array(
            'name',
            'uid',
        ))
            ->condition('name', db_like($string) . '%', 'LIKE')
            ->range(0, 10)
            ->execute();
        foreach ($result as $user) {
            // In the simplest case (see user_autocomplete), the key and the value are
            // the same. Here we'll display the uid along with the username in the
            // dropdown.
            $matches[$user->name] = check_plain($user->name) . " (uid={$user->uid})";
        }
    }
    drupal_json_output($matches);
}

/**
 * An autocomplete form to look up nodes by title.
 *
 * An autocomplete form which looks up nodes by title in the node table,
 * but must keep track of the nid, because titles are certainly not guaranteed
 * to be unique.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 *
 *  * @return array
 *   Form array.
 */
function ajax_example_unique_autocomplete($form, &$form_state) {
    $form['info'] = array(
        '#markup' => '<div>' . t("This example does a node autocomplete by title. The difference between this and a username autocomplete is that the node title may not be unique, so we have to use the nid for uniqueness, placing it in a parseable location in the textfield.") . '</div>',
    );
    $form['node'] = array(
        '#type' => 'textfield',
        '#title' => t('Choose a node by title'),
        // The autocomplete path is provided in hook_menu in ajax_example.module.
'#autocomplete_path' => 'examples/ajax_example/unique_node_autocomplete_callback',
    );
    $form['actions'] = array(
        '#type' => 'actions',
    );
    $form['actions']['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Submit'),
    );
    return $form;
}

/**
 * Node title validation handler.
 *
 * Validate handler to convert our string like "Some node title [3325]" into a
 * nid.
 *
 * In case the user did not actually use the autocomplete or have a valid string
 * there, we'll try to look up a result anyway giving it our best guess.
 *
 * Since the user chose a unique node, we must now use the same one in our
 * submit handler, which means we need to look in the string for the nid.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 */
function ajax_example_unique_autocomplete_validate($form, &$form_state) {
    $title = $form_state['values']['node'];
    $matches = array();
    // This preg_match() looks for the last pattern like [33334] and if found
    // extracts the numeric portion.
    $result = preg_match('/\\[([0-9]+)\\]$/', $title, $matches);
    if ($result > 0) {
        // If $result is nonzero, we found a match and can use it as the index into
        // $matches.
        $nid = $matches[$result];
        // Verify that it's a valid nid.
        $node = node_load($nid);
        if (empty($node)) {
            form_error($form['node'], t('Sorry, no node with nid %nid can be found', array(
                '%nid' => $nid,
            )));
            return;
        }
    }
    else {
        $nid = db_select('node')->fields('node', array(
            'nid',
        ))
            ->condition('title', db_like($title) . '%', 'LIKE')
            ->range(0, 1)
            ->execute()
            ->fetchField();
    }
    // Now, if we somehow found a nid, assign it to the node. If we failed, emit
    // an error.
    if (!empty($nid)) {
        $form_state['values']['node'] = $nid;
    }
    else {
        form_error($form['node'], t('Sorry, no node starting with %title can be found', array(
            '%title' => $title,
        )));
    }
}

/**
 * Submit handler for node lookup unique autocomplete example.
 *
 * Here the nid has already been placed in $form_state['values']['node'] by the
 * validation handler.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 */
function ajax_example_unique_autocomplete_submit($form, &$form_state) {
    $node = node_load($form_state['values']['node']);
    drupal_set_message(t('You found node %nid with title %title', array(
        '%nid' => $node->nid,
        '%title' => $node->title,
    )));
}

/**
 * Autocomplete callback for nodes by title.
 *
 * Searches for a node by title, but then identifies it by nid, so the actual
 * returned value can be used later by the form.
 *
 * The returned $matches array has
 * - key: The title, with the identifying nid in brackets, like "Some node
 *   title [3325]"
 * - value: the title which will is displayed in the autocomplete pulldown.
 *
 * Note that we must use a key style that can be parsed successfully and
 * unambiguously. For example, if we might have node titles that could have
 * [3325] in them, then we'd have to use a more restrictive token.
 *
 * @param string $string
 *   The string that will be searched.
 */
function ajax_example_unique_node_autocomplete_callback($string = "") {
    $matches = array();
    if ($string) {
        $result = db_select('node')->fields('node', array(
            'nid',
            'title',
        ))
            ->condition('title', db_like($string) . '%', 'LIKE')
            ->range(0, 10)
            ->execute();
        foreach ($result as $node) {
            $matches[$node->title . " [{$node->nid}]"] = check_plain($node->title);
        }
    }
    drupal_json_output($matches);
}

/**
 * Search by title and author.
 *
 * In this example, we'll look up nodes by title, but we want only nodes that
 * have been authored by a particular user. That means that we'll have to make
 * an autocomplete function which takes a username as an argument, and use
 * #ajax to change the #autocomplete_path based on the selected user.
 *
 * Although the implementation of the validate handler may look complex, it's
 * just ambitious. The idea here is:
 * 1. Autcomplete to get a valid username.
 * 2. Use #ajax to update the node element with a #autocomplete_callback that
 *    gives the context for the username.
 * 3. Do an autcomplete on the node field that is limited by the username.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 *
 * @return array
 *   Form API array.
 */
function ajax_example_node_by_author_autocomplete($form, &$form_state) {
    $form['intro'] = array(
        '#markup' => '<div>' . t("This example uses a user autocomplete to dynamically change a node title autocomplete using #ajax.\n      This is a way to get past the fact that we have no other way to provide context to the autocomplete function.\n      It won't work very well unless you have a few users who have created some content that you can search for.") . '</div>',
    );
    $form['author'] = array(
        '#type' => 'textfield',
        '#title' => t('Choose the username that authored nodes you are interested in'),
        // Since we just need simple user lookup, we can use the simplest function
        // of them all, user_autocomplete().
'#autocomplete_path' => 'user/autocomplete',
        '#ajax' => array(
            'callback' => 'ajax_example_node_by_author_ajax_callback',
            'wrapper' => 'autocomplete-by-node-ajax-replace',
        ),
    );
    // This form element with autocomplete will be replaced by #ajax whenever the
    // author changes, allowing the search to be limited by user.
    $form['node'] = array(
        '#type' => 'textfield',
        '#title' => t('Choose a node by title'),
        '#prefix' => '<div id="autocomplete-by-node-ajax-replace">',
        '#suffix' => '</div>',
        '#disabled' => TRUE,
    );
    // When the author changes in the author field, we'll change the
    // autocomplete_path to match.
    if (!empty($form_state['values']['author'])) {
        $author = user_load_by_name($form_state['values']['author']);
        if (!empty($author)) {
            $autocomplete_path = 'examples/ajax_example/node_by_author_autocomplete/' . $author->uid;
            $form['node']['#autocomplete_path'] = $autocomplete_path;
            $form['node']['#title'] = t('Choose a node title authored by %author', array(
                '%author' => $author->name,
            ));
            $form['node']['#disabled'] = FALSE;
        }
    }
    $form['actions'] = array(
        '#type' => 'actions',
    );
    $form['actions']['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Submit'),
    );
    return $form;
}

/**
 * AJAX callback for author form element.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 *
 * @return array
 *   Form API array.
 */
function ajax_example_node_by_author_ajax_callback($form, $form_state) {
    return $form['node'];
}

/**
 * Validate handler to convert our title string into a nid.
 *
 * In case the user did not actually use the autocomplete or have a valid string
 * there, we'll try to look up a result anyway giving it our best guess.
 *
 * Since the user chose a unique node, we must now use the same one in our
 * submit handler, which means we need to look in the string for the nid.
 *
 * This handler looks complex because it's ambitious (and tries to punt and
 * find a node if they've entered a valid username and part of a title), but
 * you *could* just do a form_error() if nothing were found, forcing people to
 * use the autocomplete to look up the relevant items.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 *
 * @return array
 *   Form API array.
 */
function ajax_example_node_by_author_autocomplete_validate($form, &$form_state) {
    $title = $form_state['values']['node'];
    $author = $form_state['values']['author'];
    $matches = array();
    // We must have a valid user.
    $account = user_load_by_name($author);
    if (empty($account)) {
        form_error($form['author'], t('You must choose a valid author username'));
        return;
    }
    // This preg_match() looks for the last pattern like [33334] and if found
    // extracts the numeric portion.
    $result = preg_match('/\\[([0-9]+)\\]$/', $title, $matches);
    if ($result > 0) {
        // If $result is nonzero, we found a match and can use it as the index into
        // $matches.
        $nid = $matches[$result];
        // Verify that it's a valid nid.
        $node = node_load($nid);
        if (empty($node)) {
            form_error($form['node'], t('Sorry, no node with nid %nid can be found', array(
                '%nid' => $nid,
            )));
            return;
        }
    }
    else {
        $nid = db_select('node')->fields('node', array(
            'nid',
        ))
            ->condition('uid', $account->uid)
            ->condition('title', db_like($title) . '%', 'LIKE')
            ->range(0, 1)
            ->execute()
            ->fetchField();
    }
    // Now, if we somehow found a nid, assign it to the node. If we failed, emit
    // an error.
    if (!empty($nid)) {
        $form_state['values']['node'] = $nid;
    }
    else {
        form_error($form['node'], t('Sorry, no node starting with %title can be found', array(
            '%title' => $title,
        )));
    }
}

/**
 * Submit handler for node lookup unique autocomplete example.
 *
 * Here the nid has already been placed in $form_state['values']['node'] by the
 * validation handler.
 *
 * @param array $form
 *   Form API form.
 * @param array $form_state
 *   Form API form state.
 *
 * @return array
 *   Form API array.
 */
function ajax_example_node_by_author_autocomplete_submit($form, &$form_state) {
    $node = node_load($form_state['values']['node']);
    $account = user_load($node->uid);
    drupal_set_message(t('You found node %nid with title !title_link, authored by !user_link', array(
        '%nid' => $node->nid,
        '!title_link' => l($node->title, 'node/' . $node->nid),
        '!user_link' => theme('username', array(
            'account' => $account,
        )),
    )));
}

/**
 * Autocomplete callback for nodes by title but limited by author.
 *
 * Searches for a node by title given the passed-in author username.
 *
 * The returned $matches array has
 * - key: The title, with the identifying nid in brackets, like "Some node
 *   title [3325]"
 * - value: the title which will is displayed in the autocomplete pulldown.
 *
 * Note that we must use a key style that can be parsed successfully and
 * unambiguously. For example, if we might have node titles that could have
 * [3325] in them, then we'd have to use a more restrictive token.
 *
 * @param int $author_uid
 *   The author username to limit the search.
 * @param string $string
 *   The string that will be searched.
 */
function ajax_example_node_by_author_node_autocomplete_callback($author_uid, $string = "") {
    $matches = array();
    if ($author_uid > 0 && trim($string)) {
        $result = db_select('node')->fields('node', array(
            'nid',
            'title',
        ))
            ->condition('uid', $author_uid)
            ->condition('title', db_like($string) . '%', 'LIKE')
            ->range(0, 10)
            ->execute();
        foreach ($result as $node) {
            $matches[$node->title . " [{$node->nid}]"] = check_plain($node->title);
        }
    }
    drupal_json_output($matches);
}

Functions

Title Deprecated Summary
ajax_example_node_by_author_ajax_callback AJAX callback for author form element.
ajax_example_node_by_author_autocomplete Search by title and author.
ajax_example_node_by_author_autocomplete_submit Submit handler for node lookup unique autocomplete example.
ajax_example_node_by_author_autocomplete_validate Validate handler to convert our title string into a nid.
ajax_example_node_by_author_node_autocomplete_callback Autocomplete callback for nodes by title but limited by author.
ajax_example_simple_autocomplete A simple autocomplete form which just looks up usernames in the user table.
ajax_example_simple_user_autocomplete_callback This is just a copy of user_autocomplete().
ajax_example_unique_autocomplete An autocomplete form to look up nodes by title.
ajax_example_unique_autocomplete_submit Submit handler for node lookup unique autocomplete example.
ajax_example_unique_autocomplete_validate Node title validation handler.
ajax_example_unique_node_autocomplete_callback Autocomplete callback for nodes by title.