Build an autocomplete search tool for PoolParty
For one of my projects I wanted to emulate the situation where a content author or editor needs to search a PoolParty project for matching concepts in order to tag their content. In an earlier article I showed how to create a SPARQL query to do this. However, the application was a bit primitive (hey, I'm not a developer, remember).
I've always wanted to work out how to do autocomplete search boxes (those that update results as you type), and it seemed to me that I could revisit my old primitive code and develop it into something a bit more slick. Now that I've done this, I thought I'd share it, because from everything I've seen on the web while researching this, clear and commented code is the exception rather than the rule.
So, here goes. As usual, I'll give a walk-through of the application and the code, and then you can find the complete source at the end of this article.
Note that to run this application you will need to plug in your own PoolParty SPARQL endpoint; the code here just has a placeholder: "[project]". It should be fairly easy to implement for your server, but if you need some help in doing this then please use the contact form to get in touch, and I'll be pleased to help you out.
A quick run-through of the application
On loading the application (via index.html) the user sees a simple search box.

The user then starts to type in the search text. In this case she will be searching the Medical Subject Headings taxonomy (MeSH 2016), and so the search term is medically-related; she's looking for concepts about coagulation of blood. Typing in coag results in the immediate appearance of a dropdown list of matching concepts, with the search string highlighted in the retrieved concepts.

The user can then click on the best match (Blood Coagulation Factors). In this demo example the skos:prefLabel and the URI are pasted into the page below.

If the user wants to choose further matches she can clear the search box and start typing again.
In a real life case that label and URI would probably be written as a tag into a database (or preferably as a triple into a graph database).
Notice that the page never re-loads. The search is carried out using the autocomplete functions in jquery-ui. Notice also that although this is using a search form, there is no need for a submit button. Each keystroke in the search box sends a search, retrieves the results and reworks the results display.
Working through the code
The application has two code files; index.html and search.php. The first builds the interface, dispatches the search request and formats the response. The second carries out the search and sends back the response.
On loading index.html the script loads the usual libraries for bootstrap css, jquery and jquery-ui. There is also a specific local style that sets the width for the dropdown list box.
<!doctype html> |
The remainder of the html is in a div element that contains a form and a div that will display the search results.
<div class="col-sm-6 col-sm-offset-3"> |
Now to the jQuery code that initiates the search and processes the display of results. There is a lot going on here, so let's take it a bit at a time. The overall function runs the jquery-ui autocomplete function on the form element above (which has an id of "autocomplete"). This points to the php script search.php, which is where the remote search will be conducted. Note that this function waits until the set number of characters has been typed, and then invokes the search function in search.php for each character typed. I will show search.php shortly; for now, all that matters is that it takes an incoming text string (see query_content in the listing above), runs a query and returns the response.
The results contain two pieces of information for each concept found in PoolParty; the search label (defined in the SPARQL query, covered below) which is either a skos:prefLabel or the skos:altLabel, and the concept URI. These are packaged up as json and the individual values can be extracted using ui.item.value (for the human-readable label) and ui.item.uri (for the URI). These values are then used to build up the collection of results displayed in the list box, but in the process they are also highlighted (the _renderItem function). This replaces the label string with a new string which has a highlight around the text matched from the searchbox. This in turn is using the JavaScript RegExp object, and the "gi" means global (find all matches) and case-insensitive searching. The results are assembled as list items in an unordered list. This is displayed as a dropdown list. When an item in the list is clicked (the select event, triggered when the search completes; that is, every time search.php sends back data) the program adds a piece of html to the results div, showing the ui.item.value and the ui.item.uri (together with its anchor tag).
<script> |
Let's now turn to search.php. This is a small script that takes in the data passed from the form in index.html (note that the jquery autocomplete tools do the form dispatch as a GET, and the payload is called "term").
The program uses the EasyRDF library to manage the request sent to the SPARQL endpoint and the corresponding response. We'll get to that shortly.
When you dispatch a form in html you send off an array of form data. Each item in the array is identified by its form id. In this case, the value that is dispatched is the text typed into the form, and this is sent from index.html to search.php as a GET, and the payload is called "term" (this is defined in jquery ui autocomplete).
In search.php the _GET array is picked up and the array member called "term" is sent to the runPPQuery function. The response from that function is an array. Each item of that array is an object which contains a large number of properties. We're only interested in two of those properties; the searchLabel (which comes from either skos:prefLabel or skos:altLabel) and the concept (which is the URI). These are then added to a new associative array called $return_array, and that is converted to json using json_encode. The final echo is just to provide a handle for the index.html program to pick up the json.
<?php |
The real final piece of code is the runPPQuery function. This is where the script formulates a SPARQL query to send to PoolParty.
Every taxonomy project in a PoolParty instance has a SPARQL endpoint. This is basically a web location to which you can send a query. The endpoint responds with a package of results.
Here, you can see the entire function. First we create a new SPARQL endpoint object, based on a project in a PoolParty server. Next comes the SPARQL query. I'm not covering how to build SPARQL queries, because there are plenty of resources on the web and anyway it's not a core part of this article. Suffice to say that the query takes the search string and sends off a query asking for any concepts that contain that string in either the skos:prefLabel or the skos:altLabel. Since a PoolParty concept must have one prefLabel and may have any number of altLabels, this maximises the chances of getting back a reasonable match.
Notice, too that we are limiting the results to 20, though you can change this or leave it off (though prepare for many results and a performance hit). Like the regular expression above, this one is case-insensitive ("i").
The results come back in $qresult, which is returned to the code that called the function (see above).
function runPPQuery($the_searchstring) { |
That brings me to the end of this technical article. I hope you find it useful in building your own PoolParty search solutions. Below you will find the complete code for both the index.html and search.php scripts.
index.html
<!doctype html> |
search.php
<?php |