Querying and Searching

October 8, 2024

Queries and searches are two very different concepts, but both will follow the same flow: provide parameters and iterate on results. This tutorial will provide practical usage examples of both concepts as well as references to relevant documentation entries.

Before you begin

Jahia modules rely on Java and Maven. The key requirements are as follows:

What you will learn

Query

  • A query is a question asked in a structured format (in Jahia's case, the JCR SQL2 syntax), expecting results strictly matching the request's parameters. Queries are executed by asking the JCR SQL API available from JSPs, Java code and GraphQL endpoints.Queries are most often used to aggregate content and build lists and menus.

Search

  • Search is designed to gather inputs from a human (or any other entity capable of typing on a keyboard!), and aimed at returning results ordered by relevance. Parameters are not strictly enforced to allow for typos and gathering results not matching all terms at once. Search results include fancy features, such as keyword highlighting and 'did you mean...' recommendation.

Queries

  • This query tutorial will guide you through the querying and displaying of a list of pages on a specific site in a JSP. This query can be adapted for any other type of objects.

Query syntax

  • This tutorial will not cover the syntax of the JCR SQL2 grammar. Instead, you can refer to the JCR SQL2 cheat sheet that provides plenty of examples.

Querying in a JSP

  • Jahia provides the JSTL functions to run queries, using the JCR SQL2 syntax. Here is an example of using the <jcr:sql library:
<jcr:sql var="pages" sql="select * from [jnt:page] as page where isdescendantnode(page,['${renderContext.site.path}'])"/>

The query will select all the objects of type jnt:page under the current site, and store an iterator (a list) of results in the variable pages.

Iterating on results

Results stored by jcr:sql are stored in an iterator who will return a list with the accessor .nodes

<c:forEach items="${pages.nodes}" var="node">
<template:module node="${node}" view="link" />
</c:forEach>

In the previous piece of code, we are storing each page in a variable node, and displaying its view link, which is a simple HTML link to the page.

Querying best practices

Queries can be resource intensive when not optimized. Some easy optimizations can make a big difference:

  • Restrict the path under which the query is made using isdescendantnode
  • Query exactly the required nodetype. Instead of gathering all nodes of type jnt:content, and then filtering on the type of each result afterwards, create a mixin applied to all types of nodes you want to search for, and query this mixin.

Searches

Searching is an experience composed of two steps: gathering inputs, and displaying the results. Since searching is targeted at humans, the search experience is a key aspect to be taken into consideration.

Search form

The following code displays a vanilla HTML form to perform searches with the following parameters:

  • The search is going to be performed on the current site only
  • The search is going to be performed on the current user locale (language) only
  • Only results (hits) matching all words of the input will be returned
<c:url value="${currentNode.properties.result.node.url}" var="searchUrl"/>
<s:form method="post" action="${searchUrl}" role="search">
 <div class="search_form">
  <s:term match="all_words" id="searchTerm" searchIn="siteContent,tags" class="form-control" placeholder="Search term"/>
  <s:site value="${renderContext.site.name}" includeReferencesFrom="systemsite" display="false"/>
  <s:language value="${renderContext.mainResource.locale}" display="false"/>
  <span class="input-group-btn">
   <button type="button" onclick="document.searchForm.submit()" value="Search">
   </button>
  </span>
 </div>
</s:form>

The variable ${currentNode.properties.result} is expected to contain a reference to the search result's page. The search form content type should contain a property defined as follows:

[jnt:customSearchForm] > jnt:content, jmix:structuredContent
 - result (weakreference, type=['picker=page'])

 

When putting this content on a page via composer, the following field should be displayed:

image1.png

The search results page is automatically created under the home page and can be picked as follows:

image2.png

 

For more information about the taglib options, please refer to the Search and Queries documentation.

Search results

Search results are extracted by the function s:results and put on a Map object.

<s:results var="resultsHits" approxCountVar="listApproxSize">
 <c:set target="${moduleMap}" property="listTotalSize" value="${count}" />
 <c:set target="${moduleMap}" property="resultsHits" value="${resultsHits}" />
 <c:set target="${moduleMap}" property="listApproxSize" value="${listApproxSize}" />
</s:results>
<s:resultIterator begin="${moduleMap.begin}" end="${moduleMap.end}" varStatus="status" hits="${moduleMap['resultsHits']}">
 <li>
  <%--<span>${status.index+1}.</span>--%>
  <%@ include file="searchHit.jspf" %>
 </li>
</s:resultIterator>

The code above will store the search results as well as some metadata on the moduleMap, and iterate through each result. The moduleMap is a Hashmap used by various Jahia modules to store and fetch data between views. In this instance, the s:result taglib stores the search results in the moduleMap and the current view passes it to s:resultIterator to display each result individually.

The searchHit.jspf's source code can be found here, and forked according to your needs. This file needs to be stored in the same folder as your search result view file.

Congratulations!  You have have learnt how to use the search and queries API!