Expand my Community achievements bar.

Dive into Adobe Summit 2024! Explore curated list of AEM sessions & labs, register, connect with experts, ask questions, engage, and share insights. Don't miss the excitement.
SOLVED

Can you make a search component return all pages in the search path?

Avatar

Former Community Member

I need to develop a page where a list of job openings is displayed. To be able to do some filtering on the jobs being displayed I decided to try to implement this using a search component which searches in search path .../jobs. This works, but now I realize that I am unable to show a list of all jobs when the page loads for the first time and no search has been performed yet. I tried to figure out if I could get all pages underneath ../jobs/... to show up by editing the query ?q=<querystring> to something like ?q=*. But this doesn't work. So now I am stuck.

Another approach could be to use the list component. This way you can easily list up all pages underneath the ../jobs url, but this list is always the same. So you can't filter it further I think. Maybe it could be possible to add an input field on the page in which one could enter a query string and try something like

List list = (List)request.getAttribute("list");

list.setQuery(queryString);

But even if this works, the list comprises of objects of type Page and it would be nice to have at least an excerpt of the job description for each job opening in the list. Which is something a search result has.

I would greatly appreciate any tips on this matter!

1 Accepted Solution

Avatar

Correct answer by
Level 7

It is possible to do the search if you want to. Here are some pieces from an example of how i solved it. I have a page that works as a search/display for certain resources and the page calls itself with a querystring "?q=something" when the user searches in the search field. But to start with, it shows them all..

1) Start of with initializing the search query, which might be empty in your case the first time someone enters the page

<% String searchTerm = ""; String searchPath = "put the root-search-path here in the way you prefer" boolean searched = true; // If there has been a search or not if(slingRequest.getRequestParameter("q") != null ){ searchTerm = slingRequest.getRequestParameter("q").getString("UTF-8"); //Get the queryparameter (q in this case) }else{ searched = false; } searchTerm = "*" + searchTerm + "*"; //Add some wildcards if you like %>



2) Initialize a search with the searchterm fetched above
 

<% HashMap<String, String> map = new HashMap<String, String>(); map.put("path", searchPath); map.put("type", "nt:unstructured"); map.put("property", "sling:resourceType"); map.put("property.value", "cq:page"); map.put("group.p.or", "true"); // combine this group with OR map.put("group.1_fulltext", fulltextSearchTerm); map.put("group.1_fulltext.relPath", "@yourNode/jcr:description"); map.put("group.2_fulltext", fulltextSearchTerm); map.put("group.2_fulltext.relPath", "@yourNode/jcr:title"); map.put("orderby","SOME_ORDER_CRITERIA"); map.put("p.limit", "LIMIT_IF_YOU_WANT"); // same as query.setHitsPerPage(20) QueryBuilder builder = resource.getResourceResolver().adaptTo(QueryBuilder.class); Session session = resource.getResourceResolver().adaptTo(Session.class); Query query = builder.createQuery(PredicateGroup.create(map), session); SearchResult result = query.getResult(); // Do the query List<Hit> hits = result.getHits(); // Get all the hits int nrOfResults = hits.size(); //Get the number of results %>


3) Loop through your results and display/present them in the way you like:
 

<% for (Hit hit: hits) { Resource myJob = hit.getResource(); if(myJob != null){ //Do something with your resource and present it nicely } } %>



Now this was just a short example of how to solve this and you might have to modify the queries and such according to you needs.
In my query I had a somewhat complicated node structure and I searched both the description and title on a node located unded the page itself.

You could of course incorporate some kind of search / filtering into your own component that extends the OOB list component. It depends on what you prefer. But somehow the example of a search component page seems a bit simpler if you are going to do a free text search amongst the properties in a resource . Hope things will work out!

/Johan

View solution in original post

3 Replies

Avatar

Level 7

Ahh sorry my bad there it should be  -> map.put("p.limit", "20"); and that will work fine.

The thing in my example was that i did not make use of the actual search/result in the way you decided to proceed.
I was doing a query that resulted in a collection of hits where each hit corresponded to a component node. I extraced the resource from each hit and made use of a special .jsp file connected to the component resource that i wanted to show as the result.
This is an example of how it can be set up.
1) Make use of special jsp pages
 i) MyComponent.jsp <- standard jsp page for the component
 ii) MyComponentSearched <- presenting the component in a search result
This method, including the components search-jsp is one way of displaying in the search results.

2) Or simply extract the information out of each resource in the loop and present it that way

 

<%for (Hit hit: hits) { Resource myResource = hit.getResource(); ValueMap eventProperties = myResource.adaptTo(ValueMap.class); String myTitle = eventProperties.get("mycomponent/jcr:title", "notitlefound"); String myText = eventProperties.get("mycomponent/jcr:description", "nottextfound"); %> <div class="searchhit"> <div class="title"><%=myTitle%></div> <div class="text"><%=myText%></div> </div> <%}%>

But as you probably have noticed, you get a lot of nice features when using the built-in search results/queries like pagination etc. that you will otherwise have to do yourself. Since I did not use the OOB features I also had to implement my own pagination which was not that difficult once you have set the number of results/page and counted how many results you got in total. After that you could use the query.setStart(start) as you already have there to start the presentation from the corresponding result.

Also, to come to terms with the build in search results, you could take a look at the system console and customize the excerpt properties for the "Day CQ Query Builder". 

Avatar

Former Community Member

Thank you very much for your response! smiley It put me in the right direction although I had to make some changes to make it compile (apparently you even need to import standard Java classes like HashMap and Map. I borrowed some code from the Query Builder documentation as well, but there seem to be some errors in there too.

I had copied this code from there

map.put("p.offset", 0);
map.put("p.limit", 20);

But this doesn't work as Map<String, String> doesn't allow adding int values.

For displaying the search results I borrowed the code from the search component rendering script. Oddily enough I could not simply use this. Apparently the Search results generated from the search component and the ones generated by a query are different object classes?

I had to work with http://dev.day.com/docs/en/cq/current/javadoc/com/day/cq/search/result/Hit.html and this doesn't support properties like URL and similarURL. So these made my page crash initially. So replaced those with 'path'. But then my links didn't work as they didn't end in '.html'. So I hardcoded this suffix after each path. Commented out the similarURL part as I didn't need it.

The excerpt value apparently comes from the Description you enter in the Page properties in the siteadmin. But there strange things happened as well. When I enter a description longer than 151 characters it would print only the last 55 characters??? What is this all about? (I am working in version 5.6.0)

 

 

So my resulting code looks like:

<%--

  Job Overview component.

  Shows all jobs, but also allows to enter a search term

--%><%
%>
<%@include file="/libs/foundation/global.jsp" %><%
%><%@ page import="java.util.Map,
                   java.util.HashMap,
                   com.day.cq.search.Query,
                   com.day.cq.search.PredicateGroup,
                   com.day.cq.search.QueryBuilder,
                   com.day.cq.search.result.SearchResult" %><%
%><cq:setContentBundle/><%

//Start of with initializing the search query, which might be empty in your case the first time someone enters the page
String searchTerm = "";
String searchPath =  currentPage.getPath();
boolean searched = false; // If there has been a search or not
if (slingRequest.getRequestParameter("q") != null ) {
    searchTerm = slingRequest.getRequestParameter("q").getString("UTF-8"); //Get the queryparameter (q in this case)
    searched = true;
}
//searchTerm = "*" + searchTerm + "*"; //Add some wildcards if you like

//Initialize a search query with the searchterm fetched above
Map<String, String> map = new HashMap<String, String>();
map.put("path", searchPath);
map.put("type", "cq:Page");
map.put("group.p.or", "true"); // combine this group with OR
map.put("group.1_fulltext", searchTerm);
map.put("group.1_fulltext.relPath", "jcr:content");
//map.put("orderby","SOME_ORDER_CRITERIA");
//map.put("p.offset", 0); THIS DOESN"T WORK AS YOU CAN'T ADD STRING, INT INTO A STRING, STRING Map// same as query.setStart(0)
//map.put("p.limit", 20); // same as query.setHitsPerPage(20)

QueryBuilder builder = resource.getResourceResolver().adaptTo(QueryBuilder.class);
Session session = resource.getResourceResolver().adaptTo(Session.class);
Query query = builder.createQuery(PredicateGroup.create(map), session);

query.setStart(0);
query.setHitsPerPage(20);

SearchResult result = query.getResult(); // Execute the query

pageContext.setAttribute("searchTerm", searchTerm);
pageContext.setAttribute("searchResult", result);
%>
<center>
    <form action="${currentPage.path}.html">
        <input size="41" maxlength="2048" name="q" value="${fn:escapeXml(searchTerm)}"/>
        <input value="<fmt:message key="searchButtonText"/>" type="submit" />
    </form>
</center>

               <br/>
            <c:set var="result" value="${searchResult}"/>
            <c:choose>
                <c:when test="${empty result && empty searchTerm}">
                </c:when>
                <c:when test="${empty result.hits}">
                    <%--
                    <c:if test="${result.spellcheck != null}">
                      <p><fmt:message key="spellcheckText"/> <a href="<c:url value="${currentPage.path}.html"><c:param name="q" value="${result.spellcheck}"/></c:url>"><b><c:out value="${result.spellcheck}"/></b></a></p>
                    </c:if>
                    --%>
                    <fmt:message key="noResultsText">
                        <h1 class="page-title"><fmt:param value="${fn:escapeXml(searchTerm)}"/></h1>
                    </fmt:message>
                </c:when>
                  <c:otherwise>
                    <h1 class="page-title">Results ${result.startIndex + 1} - ${result.startIndex + fn:length(result.hits)} of ${result.totalMatches} for <b>${fn:escapeXml(searchTerm)}</b>. (${result.executionTime} seconds)</h1>
                       <br/>

                    <div class="content clearfix">
                          <c:forEach var="hit" items="${result.hits}" varStatus="status">
                            <div>
                                <h2><a href="${hit.path}.html" title="${hit.title}">${hit.title}</a></h2>
                                <div class="content clearfix">
                                    <p>${hit.excerpt}</p>
                                </div>
                                <p class="links">
                                    <a class="button-default" href="${hit.path}.html" title="${hit.title}">Continue Reading</a>
                                </p>
                                <div class="hiturl"> ${hit.path}.html<c:if test="${!empty hit.properties['cq:lastModified']}"> - <c:catch><fmt:formatDate value="${hit.properties['cq:lastModified'].time}" dateStyle="medium"/></c:catch></c:if><%-- DON'T KNOW WHAT TO DO WITH THIS AS similarURL doesn't exist on this Hit object - <a href="${hit.similarURL}"><fmt:message key="similarPagesText"/></a> --%></div>
                                <hr/>
                            </div>
                          </c:forEach>
                    </div>
                      <br/>   

                  <c:if test="${fn:length(result.resultPages) > 1}">
                    <div class="pagination">
                        <fmt:message key="resultPagesText"/>
                        <c:if test="${result.previousPage != null}">
                          <a href="${result.previousPage.URL}"><fmt:message key="previousText"/></a>
                        </c:if>
                        <c:forEach var="page" items="${result.resultPages}">
                          <c:choose>
                            <c:when test="${page.currentPage}">${page.index + 1}</c:when>
                            <c:otherwise>
                              <a href="${page.URL}">${page.index + 1}</a>
                            </c:otherwise>
                          </c:choose>
                        </c:forEach>
                        <c:if test="${result.nextPage != null}">
                          <a href="${result.nextPage.URL}"><fmt:message key="nextText"/></a>
                        </c:if>
                    </div>
                  </c:if>
              </c:otherwise>
            </c:choose>

 

Greetz,

Ivo

Avatar

Correct answer by
Level 7

It is possible to do the search if you want to. Here are some pieces from an example of how i solved it. I have a page that works as a search/display for certain resources and the page calls itself with a querystring "?q=something" when the user searches in the search field. But to start with, it shows them all..

1) Start of with initializing the search query, which might be empty in your case the first time someone enters the page

<% String searchTerm = ""; String searchPath = "put the root-search-path here in the way you prefer" boolean searched = true; // If there has been a search or not if(slingRequest.getRequestParameter("q") != null ){ searchTerm = slingRequest.getRequestParameter("q").getString("UTF-8"); //Get the queryparameter (q in this case) }else{ searched = false; } searchTerm = "*" + searchTerm + "*"; //Add some wildcards if you like %>



2) Initialize a search with the searchterm fetched above
 

<% HashMap<String, String> map = new HashMap<String, String>(); map.put("path", searchPath); map.put("type", "nt:unstructured"); map.put("property", "sling:resourceType"); map.put("property.value", "cq:page"); map.put("group.p.or", "true"); // combine this group with OR map.put("group.1_fulltext", fulltextSearchTerm); map.put("group.1_fulltext.relPath", "@yourNode/jcr:description"); map.put("group.2_fulltext", fulltextSearchTerm); map.put("group.2_fulltext.relPath", "@yourNode/jcr:title"); map.put("orderby","SOME_ORDER_CRITERIA"); map.put("p.limit", "LIMIT_IF_YOU_WANT"); // same as query.setHitsPerPage(20) QueryBuilder builder = resource.getResourceResolver().adaptTo(QueryBuilder.class); Session session = resource.getResourceResolver().adaptTo(Session.class); Query query = builder.createQuery(PredicateGroup.create(map), session); SearchResult result = query.getResult(); // Do the query List<Hit> hits = result.getHits(); // Get all the hits int nrOfResults = hits.size(); //Get the number of results %>


3) Loop through your results and display/present them in the way you like:
 

<% for (Hit hit: hits) { Resource myJob = hit.getResource(); if(myJob != null){ //Do something with your resource and present it nicely } } %>



Now this was just a short example of how to solve this and you might have to modify the queries and such according to you needs.
In my query I had a somewhat complicated node structure and I searched both the description and title on a node located unded the page itself.

You could of course incorporate some kind of search / filtering into your own component that extends the OOB list component. It depends on what you prefer. But somehow the example of a search component page seems a bit simpler if you are going to do a free text search amongst the properties in a resource . Hope things will work out!

/Johan