Using XAJAX to autocomplete emails from address book

PostPosted: Sat Sep 10, 2005 6:00 pm Post subject: Reply with quote

Imagine that you are supposed to type in an email address into a form. You would typically type an email address that may be stored in your address book from a MySQL database. There are too many emails in your address book to pre-populate into a list on the page. Instead you want to start typing and have addresses that match what you’ve typed so far appear in a drop down. We will fetch the emails to populate the drop down from the server, on the fly, in the background.

I’m going to get to the guts of how to do this in a moment, but first, let’s setup a MySQL database with a table for holding emails, then, let’s prepopulate the database with 2500 email addresses.

Note: demo was broken for a few weeks, I dropped the database it was using, we’re back up now, 3/1/06.

Note: Article moved sites on Jan 27, 2008, demo no longer available. Hope to port the demo sometime.

Theoretically, you would have these in an address book of some kind, but for simplicity of this example, my table will have only emails in it:
Code:
CREATE TABLE `email` (
`id` int(10) unsigned NOT NULL auto_increment,
`email` varchar(255) NOT NULL default ”,
PRIMARY KEY (`id`),
KEY `email` (`email`)
) ENGINE=MyISAM

Ok, now let’s fill the database with some emails:
Code:
<?php
require_once(“DB.php”);
define(‘DSN’, ‘mysql://username:password@localhost/email’);
$aDB = DB::connect(DSN);
$aDB->setFetchMode(DB_FETCHMODE_ASSOC);
$domain = ‘fakedomain234234.com’;
$name[] = ‘john’;
$name[] = ‘mary’;
$name[] = ‘chris’;
$name[] = ‘bob’;
$name[] = ‘bill’;
$name[] = ‘suzy’;
$name[] = ‘tommy’;
$name[] = ‘william’;
$name[] = ‘harry’;
$name[] = ‘jennifer’;
$name[] = ‘jane’;
$name[] = ‘jack’;
$name[] = ‘keri’;
$name[] = ‘brad’;
$name[] = ‘wilson’;
$name[] = ‘berry’;
$name[] = ‘heather’;
$name[] = ‘krista’;
$name[] = ‘ralph’;
$name[] = ‘randy’;
$name[] = ‘allison’;
$name[] = ‘amanda’;
$name[] = ‘david’;
$name[] = ‘jeffrey’;
$name[] = ‘denis’;
$name[] = ‘danae’;
$name[] = ‘smith’;
$name[] = ‘johnson’;
$name[] = ‘taylor’;
$name[] = ‘schneider’;
$name[] = ‘thomas’;
$name[] = ‘cambell’;
$name[] = ‘newman’;
$name[] = ‘young’;
$name[] = ‘wright’;
$name[] = ‘hope’;
$name[] = ‘jones’;
$name[] = ‘washington’;
$name[] = ‘lincon’;
$name[] = ‘mcdonald’;
$name[] = ‘mcneil’;
$name[] = ‘brown’;
$name[] = ‘coolidge’;
$name[] = ‘carlson’;
$name[] = ‘doe’;
$name[] = ‘dilbert’;
$name[] = ‘marvin’;
$name[] = ‘carter’;
$name[] = ‘chaplin’;
$name[] = ‘silverman’;
$name[] = ‘gilmore’;
$name[] = ‘happy’;
$name[] = ‘madison’;
$name[] = ‘nolen’;

$loop = count($name);
for ($i=0; $i<$loop; $i++)
{
for ($j=0; $j<$loop; $j++)
{
$email = $name[$i].$name[$j].”@”.$domain;
$sql = “INSERT INTO email SET email=’$email'”;
$aDB->query($sql);
}
}
?>

Alright, so now our database has 2500 emails in it. We’re ready to start coding the example. First download xajax_0_1_beta4.zip from http://xajax.sourceforge.net/ and put it in a subdirectoy of your webroot, like this:
Code:
mkdir xajax
cd xajax
unzip /path/to/xajax_0_1_beta4.zip
cd ..

Now, you have a copy of the xajax.inc.php library in a subdirectoy of your webroot. Let’s setup the server. The server will register a function, fetchEmails, to be called on the fly by Javascript while the user is typing in the form. Basically, Javascript calls the function on the server, in the background, while the user is typing in the form. When the server is done calculating the result, it sends a block of javascript back to the web page for execution. Here is the server source code:

fetchEmails.server.php:
Code:
<?php
// using xajax version 0.1 beta4
// http://xajax.sourceforge.net

require_once(“DB.php”);
define(‘DSN’, ‘mysql://username:password@localhost/email’);

function fetchEmails($email, $start, $limit)
{
$aDB = DB::connect(DSN);
if ( DB::isError($aDB) )
return;
$aDB->setFetchMode(DB_FETCHMODE_ASSOC);
$objResponse = new xajaxResponse();

$email = $aDB->quote($email);
$email = ereg_replace(“^'”, “”, $email);
$email = ereg_replace(“‘$”, “”, $email);

$aSQL = “SELECT * FROM email WHERE email like ‘$email%’ ORDER BY email LIMIT “.$aDB->quote((int)$start).”, “.$aDB->quote((int)$limit);
$aResult = $aDB->query($aSQL);
if ( DB::isError($aResult) )
return;
// We now have a result set of emails, lets create the javascript
// to be executed back on the web page:
$sJS = ’emails = new Array;’.”\n”;
while ($aRow = $aResult->fetchRow() )
{
$sJS .= ’emails[emails.length] = “‘.$aRow[’email’].'”;’.”\n”;
}
$sJS .= ‘showEmails();’.”\n”;
// $sJS contains javascript that initiates an array of emails, then calls
// the function showEmails(), which is a function back on the web page.
// showEmails will do the work of creating the drop down of choices
$objResponse->addScript($sJS);
return $objResponse->getXML();
}

// This is so easy to do! Don’t you love it? Let’s register this function
// and let xajax do all the hard work. xajax has modularized the whole
// remove procedure call. It uses XML to transfer data back to the web page.
require(“xajax/xajax.inc.php”);
$xajax = new xajax();
$xajax->registerFunction(“fetchEmails”);
$xajax->processRequests();
?>

And now, to make use of it, we need to create the web page. This page has a lot of really cool javascript. Most of the javascript here is to faciliate making the dropdown work. Very little of it is actually calling back to the server to get the data. There are a lot of discussions that could arise out of other parts of this javascript code, but that would be getting off track. The focus here is: I am using xajax to retrieve data from the server, in the background, then using that data to do something useful on the page. Here’s the source code:

Code:
<?php
require(“xajax/xajax.inc.php”);
$xajax = new xajax(“fetchEmails.server.php”);
$xajax->registerFunction(“fetchEmails”);
?>
<html>
<head><title>Example XAJAX to fetch emails</title>
<?php
// output the xajax javascript. This must be called between the head tags
$xajax->printJavascript();
?>
<style>
.emailRow {
background-color: white;
border: 0px;
}
.emailRow_highlight {
background-color: #00cccc;
border: 1px outset blue;
}
</style>
<script language=”javascript”>
<!–

// INITIATE GLOBAL VARIABLES:
var emails = new Array; // will be populated by calling xajax_fetchEmails() remotely
var timer = null;
var selectedRow = -1; // for highlighting the row
var numRows = 0; // for using arrow keys to navigate the list

// This function reduces the number of XAJAX calls we make
// b/c if you are continuously typing, then the timer keeps
// getting cancelled. When you stop typing for half a second
// then we make the xajax_fetchEmails call. Genious!
function setTimer_fetchEmails(e)
{
// if I have typed a key in less than half a second
// cancel the pending xajax_fetchEmails call:
if ( timer != null )
clearTimeout(timer);

// for windows and IE compatibility
if ( e == null )
e = window.event;
var key = ( e.keyCode > 0 ? e.keyCode : e.which );

// if down or up arrow, move up or down the list instead!
if ( key == 40 ) // down
{
// down arrow clicked, let’s move down the list:
if ( selectedRow+1 < numRows )
{
highlightRow(selectedRow+1);
}
return false;
}
else if ( key == 38 ) // up
{
// up arrow clicked, let’s move up the list:
if ( selectedRow > 0 )
{
highlightRow(selectedRow-1);
}
return false;
}
else if ( key == 13 ) // ENTER KEY
{
if ( selectedRow > -1 )
{
document.getElementById(’email’).value = emails[selectedRow];
clearEmails();
}
return false;
}
else if ( key == 37 || key == 39 )
return;

// in half a second, if another key is not pressed, then
// fetchEmails() will be called. fetchEmails() makes the call
// back to the server to fetch the data.
timer = setTimeout(“fetchEmails()”, 500);
}

function fetchEmails()
{
// trim whitespace:
var email = document.getElementById(’email’).value.replace(/ +/g, “”);
// as long as we have something in the email box, call the function on the
// server to fetch the emails. The server will respond with a chunk of
// javascript to be executed, which is an array of emails and then showEmails()
if ( email.length > 0 )
xajax_fetchEmails(document.getElementById(’email’).value, 0, 15);
else
clearEmails();
}
function showEmails()
{
if ( emails.length > 0 )
{
var aDiv = document.getElementById(‘div_emails’);
var div_style = GetLayerStyle(‘div_emails’);
var leftpos = 0;
var toppos = 0;

// GET THE POSITION, THIS IS A COOL WAY TO GET THE POSITION:
aTag = document.getElementById(’email’);
do {
aTag = aTag.offsetParent;
leftpos += aTag.offsetLeft;
toppos += aTag.offsetTop;
} while (aTag.tagName != ‘BODY’);
// POSITION OF THE email FORM ELEMENT IS FOUND, BUT WE DON’T WANT
// TO PLACE THE DROP DOWN OVER THE email FORM ELEMENT, SO LET’S MOVE
// IT DOWN A TAD:
toppos += 24;
div_style.left = leftpos+’px’;
div_style.top = toppos+’px’;
// CREATE THE TABLE TO PUT IN THE HIDDEN DIV THAT WILL BECOME OUR
// DROP DOWN:
var str = ‘<table>’;
// build the rows of the drop down table
for (var i=0; i<emails.length; i++)
{
str += ‘<tr id=”emailRow’+i+'” class=”emailRow” onMouseOver=”highlightRow(‘+i+’)” onClick=”selectEmail(\”+emails[i]+’\’)”><td>’;
str += emails[i];
str += ‘</td></tr>’;
}
str += ‘</table>’;
aDiv.innerHTML = str;
numRows = emails.length;
// SHOW IT:
div_style.display = ‘inline’;
}
else {
// clear the drop down:
clearEmails();
}
}

// function to remove the drop down table and reset the global variables:
function clearEmails()
{
var aDiv = document.getElementById(‘div_emails’);
var div_style = GetLayerStyle(‘div_emails’);
div_style.display = ‘none’;
aDiv.innerHTML = ”;
numRows = 0;
selectedRow = -1;
}

// used when clicking a row from the drop down, used to populate the text element:
function selectEmail(email)
{
document.getElementById(’email’).value = email;
clearEmails();
document.getElementById(’email’).focus();
}

// loop through all the rows, unhighlighting all except the selcted row
// highlighting that row. We do this by changing the className
function highlightRow( i )
{
for (var j=0; j<numRows; j++)
{
if ( j == i )
document.getElementById(’emailRow’+j).className = ’emailRow_highlight’;
else
document.getElementById(’emailRow’+j).className = ’emailRow’;
}
selectedRow = i;
}

// neat little function for getting the style object for a named element:
function GetLayerStyle(layername) {
if (document.all) return document.all[layername].style;
else return document.getElementById(layername).style;
}

function captureEmailKeystrokes()
{
// must on onkeydown, onkeypress in IE sucks, IE doesn’t
// fire the event on arrow keys when using onkeypress …
// which tripped me up for awhile, onkeydown works fine
// in IE, but in firefox, returning false onkeydown doesn’t stop
// the event from happening. SOOOO, we need onkeydown for
// IE and onkeypress for firefox, yuck:
if ( document.all )
document.emailform.email.onkeydown = setTimer_fetchEmails;
else
document.emailform.email.onkeypress = setTimer_fetchEmails;
}

// –>
</script>

</head>
<body bgcolor=”white” onLoad=”captureEmailKeystrokes();”>

<table><tr>
<form name=”emailform”>
<table><tr>
<td>Email:</td>
<td><input type=”text” size=”50″ name=”email” id=”email” maxlength=”255″ onBlur=”setTimeout(‘clearEmails();’, 400)” AUTOCOMPLETE=”off”></td>
</tr></table>
</form>
<div id=”div_emails” style=”border: 2px inset blue; padding: 2px; position:absolute; display:none; background-color:white; z-index:100″></div>
</body>
</html>

9/11/05: I realize now that this implementation is kinda hard to port, it is not modular enough. So, trying to modularize it, I have come up with a product called dropbox. Right now, it is only client side Javascript, I haven’t yet XAJAX enabled it, which is my next logical step.

Comments are closed.