Creating a Keyword Faceted Search in dotCMS 2.1


Aug 06, 2012

Creating a Keyword Faceted Search in dotCMS 2.1

This blog will show you a step by step guide to create a keyword faceted search in dotCMS 2.1.

We will show you how to create a new “keywords” tag field on your content types, use the tags as meta keywords on your templates and use ElasticSearch to create a keyword faceted search.

A faceted search will display the number of hits within the search that match each tag, allowing the users to drill down by each specific keyword and refine their search results.

Click here for more information on how the Faceted Site Search works in dotCMS 2.1 click here

Let’s go over the steps:

Changes to the content types and templates

1. Create a keywords tag field

Select the content types where you want to perform the faceted site search. Add a “tags” tag field on each content type.

2. Add content

Add keywords to the “tags” field in all the content to be searched.

3. Modify your templates

Modify all the detail page templates for each content type selected, to add the following code. This code will assume there is a keywords field on your content type and will display the content as meta keywords in the HTML. You can either add this code to your Template or to a Header Container.

#if($URLMapContent && $URLMapContent.tags)
  #foreach($keyword in $URLMapContent.tags)
  <meta name="keywords" content="$strKeywords">  
  <meta name="keywords" content="$!{HTMLPAGE_KEYWORDS}">

4. View the source code

Open one detail page for each content type selected to make sure the keywords are displaying. The source code should look like this:

<meta name="keywords" content="Gas,Oil,Prices,Investment,">

Indexing the Content

1. Create new Site Search Index

Using an index name “SiteSearch”. Make it your default site search index.

2. Create a new Site Search Job

Include either the entire host, or the paths to all the detail pages for each content type selected.

In our example, we added the “tags” field to the content types: Products, News, Blogs, Events. In this case, we can choose to only include the paths to their detail pages:

/products/*, /news/*, /blog/*, /events/*

Note: To test your site search job, use a cron expression. You may with to run it every 5 minutes at first(until you are okay with the results), then change it to once a day or so.

This cron expression will run the job every 5 minutes: 0 0/5 * * * ?

3. Test Site Search

After the job has finished, test your site search by going to the “Test Site Search” tab and entering any search term.

Creating the Widget to Display the Site Search Results

1. Create a new page

Where you will display your search results. In our example we created the page: /home/site-search.html

2. Add a New Widget to Display Search Results

You can download or view the entire code on our demo site under: //shared/vtl/widgets/full-site/site-search.vtl

The code takes a search term $q and creates a lucene query adding the current host.

#if($UtilMethods.isSet($q) )
  ## QUERY
  #set($runQ  =$q.replaceAll("\"", ""))
  ## add a +
    #set($runQ = "+$runQ")
  #set($runQ = "$runQ +host:$host.identifier")

Then it calls the sitesearch api to search on the default index using the query $runQ:

## null for the first argument searches the "default" index
#set($results = $, "$!runQ ",  $start, $end))

If there are results, it returns them by looping through each result and displaying the Title, URL, Highlights, Modified Date and the number of matches in the document.

#foreach($detail in $results.results)
  <div class="resultResult">
    <div class="resultTitle"><a href="$detail.uri"<$detail.title</a></div>
    <div class="resultUrl">$detail.url</div>
    #foreach($highlight in $detail.highlights)
      <div class="resultSummary">$highlight...</div>
    <div class="resultsNum">modified: $detail.modified</div>
    <div class="resultsNum">${detail.highlights.size()} match(es) in document</div>
  #set($i =$math.add($i, 1))

3. Add Code to Display Search Facets

First we create the keywords facet query. This query is using the previously built lucene query $runQ, and also adds the keyword faceted search. Note that this is only done to get the facets. This is not filtering by keywords yet.

 ##Keywords Facet
    #set($kwFacetsQry = '  {
    "query" : { "query_string" : {"query" : "$runQ"} },
    "facets" : {
    "keywords" : { "terms" : { "field" : "keywords", "size" : 10 } }
  #set($kwFacetsQry = $render.eval($context,$kwFacetsQry))

Call the sitesearch api method getFacets. If using the default index, we can send null as the first parameter. If not, we should send the search alias.

  #set($kwFacets = $sitesearch.getFacets(null, $kwFacetsQry))

Once this runs, we have the facets in $kwFacets. We then loop through the facet entries and display each keyword’s term and count on the page.

    #if(!$UtilMethods.isSet($facet.entries()) || $facet.entries().size() == 0)
      <em>No Results</em>
      (<a href="javascript:addKeyword('')">clear</a>)
      #foreach($term in $facet.entries())
            <li><a href="javascript:addKeyword('$term.term')">$term.term</a> ($term.count)</li>
          #elseif($keyword == $term.term)
            <li><b>$term.term</b> ($term.count)</li>

Each keyword is also linked to a javascript function addKeyword that will add the term to a hidden field on the form and submit it.

  function addKeyword(term ){
    document.getElementById("keyword").value  = term;

When the page loads again it checks to see if there is a keyword in the request and adds it to the query $runQ. This will allow the drill down and refinement of the current search results.

  #if($keyword.contains(" "))
    #set($runQ = "$runQ +keywords:${esc.q}$keyword${esc.q}")
    #set($runQ = "$runQ +keywords:$keyword")

Voila! Your keyword faceted search is done!

You can see all the above code, and test this search, on the demo site at:

ElasticSearch will search all the meta fields on your HTML pages. You can perform a similar filtering process using the meta description or author tags.

<meta name="description" content="This is my content description field" />
<meta name="author" content="Maria Ahues Bouza" />

Email me if you have any questions or feedback concerning this post.

Recent Posts

Omnichannel Doesn't Mean You Have To Be On Every Channel

Delivering an omnichannel customer experience is now considered obligatory, but does omnichannel mean you should be on every channel?

Lessons Learned From Building An Alexa Skill For Students

Gettysburg College build a set of skills for their students, and this is what they learned.

Voice of the Customer: 6 Common Mistakes To Avoid

Voice of the customer programs are easy, and your customers love the customer experience you give them. Well, at least, you think they do. Here’s why VOC programs are important, and six ways to not fail at them.

You're Living In a Customer Experience Bubble (Here's How To Burst It)

Customer experience is far too important to be exclusively owned by the marketing department. Here’s how to burst your CX bubble and take part in the ultimate team sport — customer experience orchestration — in five steps.