Friday 5 December 2008

grails: Implementing Search

The ability for a User to perform searches for entries in the database is a critical requirement for most web apps. Implementing such a feature from scratch can be a rather daunting task. Thankfully there are open source solutions that alleviate the need to build your own search engine. Two of these, Lucene and Compass are bundled into a grails plug-in, enabling you to provide a powerful and flexible search capability for your Users.

Implementing search using this plugin is actually quite simple, as I will demonstrate in this post.

Step 1: Download & Install Searchable plugin

The first step is to download and install the Searchable plugin. Navigate to your project folder and issue the following command from a console window.

grails install-plugin searchable
 



Please note this assumes you are using JDK1.5+. If however, you're still using JDK1.4, then invoke the above command ensuring that the term 'searchable' is replaced with 'searchable14'.

Step 2: Modify Domain class

Open up your Domain class and add the statement 'static searchable = true '. In the example below, my domain class is called Issue and is a simplification of an actual class that I'm developing for a pet project. This flag enables the plugin to identify which records are available for search.

class Issue{

String title
String description
String status
String priority

static searchable = true

static constraints = {
title()
description()
status(inList : ["UNASSIGNED", "ASSIGNED", "CLOSED"])
priority(inList: ["TRIVIAL", "MINOR", "MAJOR", "CRITICAL", "BLOCKER"])
}

String toString(){
return title
}
}




Step 3: Create Search Textfield.

The next step is to provide a search box into which the User can enter search parameters. I do this by creating a search textfield like the one below,


<g:form url='[controller: "searchable", action: "index"]' id="searchableForm" name="searchableForm" method="get">
      <g:textField name="q" value="${params.q}" size="15"/> <input type="submit" value="Search" />

</g:form>




The textfield is embedded within a form that will pass its contents to an 'index' action on the plugins 'search' controller, either when the User clicks on the 'search' button or hits the 'enter' key.

To show what this would look like, I've knocked up a simple web app using the Issue class above. We now have something that looks like this

You can clearly see the search box at the top of the navigation bar. I've used the bootstrap class to create some test data which are shown in the table. This data will enable me to demonstrate that the search capability works and returns the expected results.

Entering a value of "CLOSED" into the search box returns the following response,

Although the plugin has successfully returned the expected results, the presentation of the data leaves a lot to be desired. Our application no longer looks consistent. Every time a User performs a search they'll end up with a page that clearly doesn't look right and they'll have to click on the browser back button to navigate somewhere else. The next step should fix this.

Step 4: Edit View for Search Results

Create a new folder 'searchable' under grails-app/views.

In this folder we want to place a copy of the view used by the plugin to display the results. The copy will subsequently be modified to generate a view that is consistent with our web application. So copy the file web-app/plugins/searchable-0.5.1/grails-app/views/searchable/index.gsp into the newly created folder. Here /web-app/ is the name of your project and searchable-0.5.1 refers to the version of the plugin that is in use. For my case, this happens to be 0.5.1. Check the version that you have downloaded and adjust accordingly.

Next we modify index.gsp in the following way,
  1. Delete all the css styling defined within the <head> tags
  2. Place the following meta-tag between the <head> tags
<meta name="layout" content="main"/>



Following these two steps should be sufficient to generate a results view that is consistent with your application. However for my application, I had to perform an extra step and rename the id's for the <header> and <main> divs. This was to prevent a conflict with css styling from my own style sheet.

The end result now looks like



This completes the tutorial, which I hope you will find useful. My intention was to enable you to get started quickly and in that respect the post has not and could not cover the full power of this fantastic plugin. That may be a task for another day.

The example code demonstrated here is available for download [compass.zip] from here. Please note that this is missing the plugin. I had to delete that in order to comply with the File Upload limit. However this shouldn't be a problem if you follow the installation instructions given above.

3 comments:

Lissandro Dittmar said...

Hello! I have a problem. I want to search only for lines where the fields 'finish' are true and 'locked' are false. How can I do this? My code is:

def search = {
def helloList
def c = Hello.createCriteria()

params.suggestQuery = true

if (params.query)
helloList = Hello.search(params.query, params)
else
helloList = c.list{
and{
eq('locked', false)
eq('finish', true)
}
}

render(view:'list', model:[helloList : helloList.results, suggestQuery : helloList.suggestedQuery])
}

Unknown said...

Excellent post. Thank you very much for sharing this info.

Unknown said...

Will this searchable plugin work with domain classes having composite id.