Autocomplete Example

We recently added an autocomplete function to our Form class. It's a little more complicated than the rest of the field types, so we felt an example was in order. This is that example. It's nothing fancy, just a list of animals that can be selected.

Many autocomplete functions have the box appearing below the text field. That has some advantages, but it can also make things complicated. We therefore chose to include a separate dropdown to the right of the text field. Both can be styled independently. This example doesn't get into that, though. See the main Form class tutorial for that.

Animal
 

Here's the code that generated that example:

require_once 'form.class.php';
$form = new Form(array('use_sessions'=>false));

$fields = array(
	'animal' => array('type'=>'autocomplete', 'required'=>false, 'caption'=>'Animal', 'cleanup_function'=>false, 'error_function'=>false)
);

$data = $form->process(array('fields'=>$fields,'cleanup_function'=>'standard_cleanup'));
if ($form->success) {
	echo '<p>Success. The following values were submitted:</p>';
	echo '<ul>';
	foreach ($data as $key => $value) {
		echo '<li>'.$key.' = '.htmlspecialchars($value).'</li>';
	}
	echo '</ul>';
} else {
	echo $form->error_message;
	echo $form->start(array('action'=>https://inkplant.com/code/autocomplete-example));
	echo '<table class="standard">';
	$args = array(
		'name'=>'animal',
		'response_url'=>'autocomplete.php',
		'response_source'=>'autocomplete-test.php',
		'min_text_length'=>0,
		'add_option_value'=>50,
		'add_option_caption'=>'Penelope the purple Turtle',
		'remove_option'=>7,
	);
	echo '<tr><th>Animal</th><td>'.$form->autocomplete($args).'</td></tr>';
	echo '<tr><th>&nbsp;</th><td>'.$form->submit(array('caption'=>'Submit')).'</td></tr>';
	echo '</table>';
	echo $form->end();
}

The response_url is the URL of a page that generates the options to send back via the autocomplete Ajax call. It's something that you'll host on your own server. You can simply copy and paste the following code and save it as autocomplete.php (it's what we're using here) or tweak it to suit your own purposes:

<?php

//open up cached options file
$key = 'source';
if (array_key_exists($key,$_REQUEST) && (is_string($_REQUEST[$key]))) {
	$source = trim(strip_tags($_REQUEST[$key]));
} else {
	die('<!-- Error: No options source file specified. -->');
}

if (!file_exists($source)) { die('<!-- Error: Specified options source file does not exist. -->'); }

require $source;

//a bit of error checking once that's loaded
if (empty($options) || (!is_array($options))) { die('<!-- Error: No options array specified. -->'); }
elseif (empty($optionslc) || (!is_array($optionslc))) { die('<!-- Error: No lowercase options array specified. -->'); }
elseif (count($options) < 1) { die('<!-- Options array is empty. -->'); }
elseif (count($options) != count($optionslc)) { die('<!-- Options array and lowercase options array don\'t match. -->'); }

//look to see if there are items to add or remove, or a value to select
foreach (array('add_option_value','add_option_caption','remove_option','selected_value','action') as $key) {
	if (array_key_exists($key,$_REQUEST) && ((is_string($_REQUEST[$key])) || (is_numeric($_REQUEST[$key])))) { $$key = trim(strip_tags($_REQUEST[$key])); } else { $$key = false; }
}
if (($add_option_value) || ($add_option_caption)) {
	$options[$add_option_value] = $add_option_caption;
	$optionslc[$add_option_value] = strtolower($add_option_caption);
}
if ($remove_option) { unset($options[$remove_option]); }

if ($action == 'return_caption') { //just return the caption of selected value, nothing else
	die($options[$selected_value]);
}

//now compare user's input with what's stored
$key = 'str';
if (array_key_exists($key,$_REQUEST) && (is_string($_REQUEST[$key]) || is_numeric($_REQUEST[$key]))) {
	$str = trim(strip_tags($_REQUEST[$key]));
	$strlc = strtolower($str);
} else {
	$str = false;
	$strlc = false;
}

if ($str) {
	foreach ($optionslc as $key => $value) { //we search lowercase array
		if (strpos($value,$strlc) === false) {
			unset($options[$key]); //...but delete from original array
		}
	}
}

//now display original (not lowercase) array
echo '<option value=""> </option>'; //empty spot

if (count($options) > 0) {
	foreach ($options as $key => $value) {
		if ($key == $selected_value) { $sel = ' selected'; } else { $sel = ''; }
		echo '<option value="'.$key.'"'.$sel.'>'.$value.'</option>';
	}
} else {
	echo '<!-- No options returned. -->';
}

?>

The response_source is the URL of the data page. Your response_url (autocomplete.php) will call it as an include to load up the data you want to serve. Here's the example we're using here, autocomplete-test.php:

<?php

$options = array(
'1'=>'Billy the blue Bear',
'6'=>'Cincy the dark blue Tiger',
'11'=>'Clifford the light blue Dog',
'5'=>'Jingles the light blue Cat',
'2'=>'Lucy the light blue Cat',
'9'=>'Odysseus the dark blue Hamster',
'10'=>'Rex the blue Lion',
'4'=>'Rover the blue Dog',
'8'=>'Sebastian the light blue Hamster',
'7'=>'Simba the blue Lion',
'12'=>'Steve the dark blue Bear',
'3'=>'Vito the dark blue Dog',
);

$optionslc = array(
'1'=>'billy the blue bear',
'6'=>'cincy the dark blue tiger',
'11'=>'clifford the light blue dog',
'5'=>'jingles the light blue cat',
'2'=>'lucy the light blue cat',
'9'=>'odysseus the dark blue hamster',
'10'=>'rex the blue lion',
'4'=>'rover the blue dog',
'8'=>'sebastian the light blue hamster',
'7'=>'simba the blue lion',
'12'=>'steve the dark blue bear',
'3'=>'vito the dark blue dog',
);

?>

To play around with the min_text_length feature, add ?min= and an integer from 1-10 to the URL. This feature keeps the options from being displayed unless there are a certain number of characters entered. It is particularly useful when dealing with large datasets (that you don't want to deliver in their entirety on page load).

jQuery is used, so be sure to call it within the <head> section of your page:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>

Finally, if you're looking for an easy way to generate your response_source file, you can start with this (it's what we used to build autocomplete-test.php):

$table = 'animals';
$orderby = '`name`';
$idcol = 'id';
$file = 'autocomplete-test.php';

function option_value($row) {
	return $row['name'].' the '.$row['color'].' '.$row['animal_type'];
}

require 'save-autocomplete.inc.php';

The contents of save-autocomplete.inc.php are here:

<?php

//you can either provide an $options array or table, orderby, etc. to generate it

//error checking
if (empty($options) || (!is_array($options))) {
	if (empty($table)) { $error = 'No table set.'; }
	elseif (empty($orderby)) { $error = 'No order by set.'; }
	elseif (empty($idcol)) { $error = 'No ID column set.'; }
	elseif (!function_exists('option_value')) { $error = 'No option value function defined.'; }
}
elseif (empty($file)) { $error = 'No destination file set.'; }
//elseif (!file_exists($file)) { $error = 'Destination file does not exist.'; }
//elseif (!is_writeable($file)) { $error = 'Destination file does is not writeable.'; }
if ($error) { die('Error: '.$error); }

//build $options array if it doesn't already exist
if (empty($options) || (!is_array($options))) {
	$options = array();
	$result = dbQuery('SELECT * FROM `'.$table.'` ORDER BY '.$orderby);
	while ($row = dbGetRow($result)) { $options[$row[$idcol]] = option_value($row); }
}

//generate a lowercase version for searching
$optionslc = array();
foreach ($options as $key => $value) {
	$optionslc[$key] = strtolower($value);
}

//export arrays
$lb = "\n";
$php = '<?php'.$lb.$lb;
foreach (array('options','optionslc') as $array) {
	$php .= '$'.$array.' = array('.$lb;
	foreach (${$array} as $key => $value) {
		$php .= '\''.$key.'\'=>\''.str_replace("'","\'",$value).'\','.$lb;
	}
	$php .= ');'.$lb.$lb;
}
$php .= '?>';

//save file
$fp = fopen($file, 'w') or die('Error: Could not open destination file.');
fwrite($fp, $php) or die('Error: Could not write to destination file.');
fclose($fp);

if (empty($quiet)) {
	echo '<p>'.number_format(strlen($php)).' characters were written to '.$file.'.</p>';
}

?>

Hopefully that gets you on your way. But if you're super lost at this point, you should probably go back and start at the Form helper class instructions and then come back to this.


Comments

Loading…

This post was first published on June 11th, 2015 and last updated on June 15th, 2015 by Robert James Reese in the following categories: Ajax, Helpers, jQuery, and PHP. Before using any of the code or other content in this post, you must read and agree to our terms of use.