Zoeken met AJAX

Een vriend van mij vraagt me nog wel eens waar een bepaalde Bijbeltekst staat. Nu ben ik geen wandelende concordantie (index voor de Bijbel), dus vaak moet ik het antwoord schuldig blijven. Aan de andere kant bracht zijn vraag me wel op een leuk idee. Er zijn databases met alle Bijbelteksten en als je daar een goede zoekmachine opbouwt, kan mijn vriend zo zijn vraag opzoeken in die zoekmachine.

AJAX zoekmachine

In dit artikel laat ik zien hoe ik tot een mooie AJAX zoekmachine ben gekomen. Laat ik maar beginnen met een ruwe schets van de gewenste werking van de applicatie. Er komen twee invoervelden: een vrij tekstveld voor zoekwoorden en een dropdown box met de Bijbelboeken. Zodra de bezoeker een woord volledig heeft ingevuld of een Bijbelboek selecteert, worden de zoekresultaten bij gewerkt. De opgegeven keywords worden in het Bijbelvers vetgedrukt weergegeven. Verder worden er maximaal 25 resultaten opgehaald om de applicatie overzichtelijk te houden.

Opmerking:
Als je niet bekend bent met AJAX, kun je misschien eens kijken naar dit artikel over de basis van AJAX.

Events

In totaal hebben we twee events die de zoekopdracht starten. De eerste is simpel, namelijk een onchange op het tekstveld en de selectbox. Maar het zou ook mooi zijn als de tool al begint met zoeken, voordat je al je zoektermen in gevuld hebt. Het tweede event is daarom iets lastiger. Het is namelijk niet verstandig om na elke toetsaanslag een zoekopdracht uit te voeren, omdat dit een te zware operatie wordt. Daarom wil ik de zoekopdracht eigenlijk alleen aanroepen als er een volledig woord is getypt. Dit kan op twee momenten: Of de gebruiker verlaat het tekstveld (de onchange dus) of de gebruiker voert een teken in waarmee hij aangeeft dat er een woord uitgetypt is. In dit geval beschouw ik enkel een spatie als scheidingsteken tussen de woorden. Dit los ik op door een onkeyup-event op het invoerveld te zetten en vervolgens via JavaScript te checken of het laatste karakter een spatie is. In code is dat:

function checkInput(obj) {
var lastChar = obj.value.charAt(obj.value.length - 1);
if (lastChar == " "){
getZoekresultaten(obj, document.getElementById("bijbelboek"));
}
}

Zoals je ziet wordt het object zelf meegestuurd, zodat we die niet nog eens met document.getElementById() moeten ophalen. De functie getZoekresultaten() vraagt daadwerkelijk de zoekresultaten op aan een PHP-script. Deze functie heeft dus de inhoud van het tekstveld en die van de selectbox nodig. Vervolgens maken we het object aan voor de xml parser, sturen we de parameters mee naar het php script en geven we de javascript functie op die uitgevoerd moet worden na het php script. Deze functie zal dan ook de data, die als xml wordt teruggegeven, naar het scherm printen.


// Functie maakt xmlhttp object aan en roept php script aan waarin
// de zoekresultaten bepaald worden a.d.h.v. de inhoud van het zoekwoorden-veld
// en het bijbelboek veld
function getZoekresultaten(zoekwoorden, bijbelboek) {
var xmlhttp = new HTTPDataConnection();
xmlhttp.setURL("resultaten.php");
xmlhttp.setRequestAsPost(true); // set request as POST paramaters
xmlhttp.addParam("zoekwoorden", zoekwoorden.value);
xmlhttp.addParam("bijbelboek", bijbelboek.value);
xmlhttp.addParam("showXML", "1"); // Geef extra param mee via javascript, zodat het verwerkscript de output kan bepalen
xmlhttp.setFunction(verwerkInput);
xmlhttp.requestData();
}

// Deze functie verwerkt de xml output
function verwerkInput(xmlhttp) {
var dataArray = xmlhttp.getDataAsArray("object");
var output = "", tekst = "", boek = "", hoofdstuk = "", vers = "";
var count = 0;

// haal aantal resultaten op.
count = xmlhttp.getDataValue("count");
if(count != 0) {
if(count > 25) {
output ="

Er zijn meer dan 25 resultaten gevonden. Verfijn uw zoekopdracht.

n";
}
else {
output ="

Er zijn " + count + " resultaten gevonden.

n";
}

output += "

n";
for(var object in dataArray) {
if(typeof(dataArray[object]) == "object") {
tekst = dataArray[object]["tekst"];
boek = dataArray[object]["boek"];
hoofdstuk = dataArray[object]["hoofdstuk"];
vers = dataArray[object]["vers"];

output += "

";
output += "

";
output += "

";
output += "

";
}
}
output += "

" + boek + " " + hoofdstuk + ":" + vers + " " + tekst + "

n";

document.getElementById('output').innerHTML = output;
}
}

Databaseontwerp

Helaas is het zo dat er maar één Nederlandse Bijbelvertaling is waarvan de tekst vrij beschikbaar is. Dat is de Statenvertaling uit 1618/1619. Niet echt een vertaling om eens lekker weg te lezen dus. Voor dit artikel voldoet deze echter prima en ik heb deze dan ook in een database gezet. De hele Bijbel staat in één tabel genaamd ‘bijbel’ en de kolommen zien er als volgt uit:

id | testament | boek | hoofdstuk | vers | tekst

HTML code

Zoals in de JavaScript-code hierboven al te zien was, heb ik drie elementen nodig in mijn HTML:

  • Een invoerveld voor de zoekwoorden met ‘zoekwoorden’ als ID
  • Een selectbox voor het bijbelboek met ‘bijbelboek’ als ID
  • Een layer voor de output met ‘output’ als ID

In HTML wordt dat dan zoiets:

Zoekwoorden:

Bijbelboek:

// Als er informatie gepost is, zoekresultaten opvragen
if ($_POST['zoekwoorden'] ? $_POST['bijbelboek']) {
include('resultaten.php');
}
else {
echo "

Gebruik bovenstaand formulier om Bijbelteksten te zoeken.

n";
}
?>

Het submitten van de data kan nu op twee manieren: met en zonder javascript ingeschakeld. Heb je geen javascript, dan wordt het formulier gewoon gepost en wordt door de include resultaten.php de data gesubmit. In resultaten.php wordt de output als een gewone html tabel uitgespuugd.

Heb je wel javascript, dan gebeurt de dataverwerking via Ajax en dus dmv javascript. Met de onkeyup en onchange events wordt een javascript functie aangeroepen die vervolgens het request uitvoert. Ook zie je dat daar een extra (post) variabele showXML wordt geset. Als dit gebeurt dan reageert het resultaten.php script anders (zie regel 3) en wordt er ipv een html tabel, xml teruggegeven en wordt daarvoor ook een xml header aangeroepen (regel 6/7).

PHP code

De PHP-code dient a.d.h.v. de gegeven input (die via $_POST-parameters binnen komt) te bepalen welke resultaten weergegeven moeten worden. Het kan zijn dat er alleen zoekwoorden zijn ingevuld of alleen een bijbelboek, maar het zou ook allebei kunnen.

Vervolgens wordt aan de hand van de verkregen resultaten de XML opgebouwd die de volgende structuur krijgt:


In den beginne
Genesis
1
1

Aangezien de PHP-code verder niet al te spannend is, plaats ik deze maar gewoon hieronder. Er staat ook meer dan genoeg commentaar bij.

// Toon output als XML, als dmv javascript de showXML variabele met waarde '1' meegestuurd wordt
$isXML = $_GET['showXML'] == "1" ? $_POST['showXML'] == "1";

if($isXML) {
header ("Content-Type: text/xml");
echo "n”;
}

mysql_connect(“localhost”, “user”, “pwd”) or die (mysql_error());
mysql_select_db(“tools”) or die (mysql_error());

// Functie om de gevonden tekst te laten highlighten.
// Afkomstig van http://nl2.php.net/str_replace, maar
// lichtelijk verbeterd
function highlight_tekst($x, $var) {//$x is the string, $var is the text to be highlighted
if ($var != “”) {
$xtemp = “”;
$i = 0;
while($i < strlen($x)) {
if((($i + strlen($var)) <= strlen($x)) && (strcasecmp($var, substr($x, $i, strlen($var))) == 0)) {
$xtemp .= "{" . substr($x, $i , strlen($var)) . "}";
$i += strlen($var);
} else {
$xtemp .= $x{$i};
$i++;
}
}
$x = $xtemp;
}

return $x;
}

// Haal overtollige spaties weg van de input
$zoekwoorden = trim(str_replace("\'", "", str_replace('\"', "", preg_replace("/[ ]{1,100}/"," ", $_POST['zoekwoorden']))));
$bijbelboek = trim($_POST['bijbelboek']);

// continue de zoekwoorden in een array,
// zodat we het selectiecriterium
// kunnen maken
$zoekwoorden = explode(" ", $zoekwoorden);

// Doorloop de zoekwoorden
while (list($key, $val) = each($zoekwoorden)) {
if (strlen($zoekcriteria) > 0){
$zoekcriteria .= ” AND “;
}

$zoekcriteria .= “tekst LIKE ‘%” . $val . “%’n”;
}

// Indien er een boek is geselecteerd,
// deze ook meenemen in de selectie criteria
if ($bijbelboek > “”) {
if (strlen($zoekcriteria) > 0) {
$zoekcriteria .= ” AND “;
}

$zoekcriteria .= “boek = ‘” . $bijbelboek . “‘n”;
}

// Maak de selectiecriteria
$query = ” SELECT *
FROM bijbel
” . ( strlen($zoekcriteria) > 0 ? “WHERE ” . $zoekcriteria : “” ) . ”
ORDER BY id ASC”;

$result = mysql_query($query) or die (mysql_error());

$count = 0;
$continue = true;
$numOfRows = mysql_num_rows($result);
if($isXML) {
echo “n”;
echo “t” . $numOfRows . “n”;
}
else {
if($numOfRows > 25) {
echo “

Er zijn meer dan 25 resultaten gevonden. Verfijn uw zoekopdracht.

n”;
}
else {
echo “

Er ” . ($numOfRows == 1 ? “is” : “zijn”) . ” ” . $numOfRows . ” resultaten gevonden.

n”;
}
echo “

n”;
}

while ($continue && $obj = mysql_fetch_object($result)) {
reset ($zoekwoorden);
$tekst = stripslashes($obj->tekst);
$boek = stripslashes($obj->boek);
$hoofdstuk = $obj->hoofdstuk;
$vers = $obj->vers;

// Geef de zoekwoorden opvallend weer
while (list($key, $val) = each($zoekwoorden)){
// Alleen doen als het woord geen onderdeel is van de opvallende opmaak
$tekst = highlight_tekst($tekst, $val);
}
// Vervang de haken
$tekst = str_replace(“{“, ““, $tekst);
$tekst = str_replace(“}”, “
“, $tekst);

if($isXML) { // bouw de xml op
echo “tn”;
echo “ttn”;
echo “ttn”;
echo “ttn”;
echo “ttn”;
echo “
n”;
}
else {
echo “t

n”;
echo “tt

n”;
echo “tt

n”;
echo “t

n”;
}

$count++;

if($count >= 25) {
$continue = false;
}
}

// Zet het aantal resultaten in de xml
if($isXML) {
echo “n”;
}
else {
echo “

” . stripslashes($boek) . ” ” . $hoofdstuk . “:” . $vers . “ ” . $tekst . “

n”;
}

mysql_close();
?>

Voorbeeld

Met al deze code zou de applicatie moeten draaien. Een voorbeeld staat op Bible Search.

Opmerking:
De javascript code en de xml parser werd u mede mogelijk gemaakt door Ronald Everts en Arjan Snaterse.