Use a REST asset to access templated results

Background

This section provides information on using a REST asset to nest a set of search results within a standard page.

This is the most common method used to integrate between the DXP Content Management and Search capabilities and involves configuring a Freemarker template in DXP Search that returns a well-formed HTML fragment (a chunk of HTML code that you can safely nest within a <div>). DXP Content Management takes the user’s query and constructs a call to DXP Search, making the HTTP request on the user’s behalf. DXP Search returns the HTML fragment to the CMS, which is then nested inside a standard page on your website. You manage the styling, overall page structure (headers, footers, navigation etc.) from within your CMS.

nest the search inside a squiz matrix search page

This method of integration is achieved using a REST asset.

When embedding the search the whole return from DXP Search should be nested in a single REST asset.

If you need dynamic DXP Content Management content to appear within the search results section you can include some DXP Content Management snippets code within the Freemarker template so that these are expanded when the CMS renders the page.

When using this method of integration:

  • Ensure the X-Forwarded-For HTTP header is set containing the user’s remote address (otherwise the search analytics will show all search result traffic as coming from your CMS).

  • The search return is encoded in UTF-8 and the CMS should also be configured to return content as UTF-8 so that nesting of this content doesn’t result in incorrectly encoded characters.

  • Caching of the search results page should be disabled.

  • Set the ui.modern.search_link, ui.modern.click_link and the ui_cache_link configuration values within DXP Search to set appropriate links for inclusion in the search results output - this will ensure correct links are returned within the search interface.

There is a performance cost when using this nesting method to display your search results as you are adding the DXP Content Management page render and access time on top of the time taken by Funnelback to run and build the search results.

Advantages and disadvantages

Advantages

  • Centralized control of CMS-managed CSS, JS, wrapping markup.

  • You make use of the pre-build and tested Freemarker macros to template the search results.

  • DXP Content Management keywords embedded in FTL responses are translated.

  • The DXP Content Management can be used to provide authenticated access to the search results, using the same user configuration that exists for the rest of the website.

Disadvantages

  • Response time has added overhead of the CMS access and rendering.

  • Search result templating is stored within the search configuration - changes to search results markup must be made within DXP Search.

  • The search results templating must be written using the Freemarker templating language.

Tutorials

Part 1: Configure a Funnelback template for REST integration

This involves configuring a Freemarker template for your search results within a results page that you have set up within the DXP search to handle the search of your DXP Content Management site.

Before you start

Steps

  1. Within the DXP console, select the search capability. This opens up the search administration dashboard where you can manage your searches.

  2. Locate the search package that you created, and then click on the Results pages tab. Create a results page for your search. We’ll call it Website search but you can name it whatever you like. Once this is created you will be redirected to the results page management screen for the Website search that you just created.

  3. Select edit results page templates from the templates panel and click the Add new button. Create a new template called rest.ftl.

    configure rest template 01
    configure rest template 02
  4. Click on the 'rest.ftl' item in the directory listing. The template editor screen opens where you can edit the template. Cut and paste the code below into the template window, then click the Save and publish button.

    This template code implements a very basic search results template that includes the search box, search results, pagination, spelling suggestions, best bets, curator messages and related searches.

    <#ftl output_format="HTML" encoding="utf-8" />
    <#import "/web/templates/modernui/funnelback_classic.ftl" as s/>
    <#import "/web/templates/modernui/funnelback.ftl" as fb/>
    
    <div id="dxp-search-container">
      <#if error?? || (response.resultPacket.error)??>
        // If there is a search error print it to the browser console
        <script>console.log(<@fb.ErrorMessage />);</script>
      </#if>
    
      <#-- Search Box -->
      <form class="navbar-form navbar-left form-inline" action="${question.currentProfileConfig.get("ui.modern.search_link")}" method="GET"/>
        <input type="hidden" name="collection" value="${question.collection.id!}"/>
        <@s.IfDefCGI name="enc"><input type="hidden" name="enc" value="${question.inputParameters["enc"]?first!}"/></@s.IfDefCGI>
        <@s.IfDefCGI name="form"><input type="hidden" name="form" value="${question.form!}"/></@s.IfDefCGI>
        <@s.IfDefCGI name="scope"><input type="hidden" name="scope" value="${question.inputParameters["scope"]?first!}"/></@s.IfDefCGI>
        <@s.IfDefCGI name="lang"><input type="hidden" name="lang" value="${question.inputParameters["lang"]?first!}"/></@s.IfDefCGI>
        <@s.IfDefCGI name="profile"><input type="hidden" name="profile" value="${question.profile!}"/></@s.IfDefCGI>
        <@s.IfDefCGI name="sort"><input type="hidden" name="sort" value="${question.inputParameters["sort"]?first!}"/></@s.IfDefCGI>
        <input required name="query" id="query" title="Search query" type="text" value="${question.originalQuery!}" placeholder="Search <@s.cfg>service_name</@s.cfg>"/>
        <input type="submit" value="Search" />
      </form>
    
      <#-- Display the following only after a query is submitted -->
      <@s.AfterSearchOnly>
          <#-- Display best bets -->
          <#assign curatorAdvertPresent = false />
          <#list response.curator.exhibits as exhibit>
              <#if exhibit.titleHtml?? && exhibit.linkUrl??>
                  <#assign curatorAdvertPresent = true />
                  <#break>
              </#if>
          </#list>
    
          <#if curatorAdvertPresent >
            <ol id="dxp-search-best-bets">
              <#list response.curator.exhibits as exhibit>
                <#if exhibit.titleHtml?? && exhibit.linkUrl??>
                  <li>
                    <h4><a href="${exhibit.linkUrl}"><@s.boldicize>${exhibit.titleHtml?no_esc}</@s.boldicize></a></h4>
                    <#if exhibit.displayUrl??><cite class="text-success">${exhibit.displayUrl}</cite></#if>
                    <#if exhibit.descriptionHtml??><p><@s.boldicize>${exhibit.descriptionHtml?no_esc}</@s.boldicize></p></#if>
                  </li>
                </#if>
              </#list>
            </ol>
          </#if>
    
          <#-- Search result set summary -->
          <div id="dxp-search-result-count">
            <#if response.resultPacket.resultsSummary.totalMatching == 0>
              0 search results for <strong><@s.QueryClean /></strong>
            </#if>
            <#if response.resultPacket.resultsSummary.totalMatching != 0>
              ${response.resultPacket.resultsSummary.currStart} -
              ${response.resultPacket.resultsSummary.currEnd} of
              ${response.resultPacket.resultsSummary.totalMatching?string.number}
              search results for <@s.QueryClean />
            </#if>
          </div>
    
            <#-- Display spelling suggestion message -->
            <@s.CheckSpelling prefix="<div>Did you mean: " suffix="?</div>" />
    
            <#-- Display (non-best bets) curator messages -->
            <#if (response.curator.exhibits)!?size gt 0>
              <#list response.curator.exhibits as exhibit>
                <#if exhibit.messageHtml??>
                  <div>
                    ${exhibit.messageHtml?no_esc}
                  </div>
                </#if>
              </#list>
            </#if>
    
            <#-- Display the search results -->
            <h2>Search results</h2>
    
            <#-- No results message -->
            <#if response.resultPacket.resultsSummary.totalMatching == 0>
                <h3>No search results</h3>
                <p>Your search for <strong>${question.originalQuery!}</strong> did not return any results. Please ensure that you:</p>
                <ul>
                  <li>are not using any advanced search operators like + - | " etc.</li>
                  <li>expect this document to exist within the set of search results.</li>
                  <li>have permission to see any documents that may match your query</li>
                </ul>
            </#if>
    
            <ol id="search-results" start="${response.resultPacket.resultsSummary.currStart}">
              <@s.Results>
                <#if s.result.class.simpleName != "TierBar">
                  <li data-fb-result="${s.result.indexUrl}">
                    <#-- Search result title -->
                    <div><a href="${s.result.clickTrackingUrl}" title="${s.result.liveUrl}"><@s.boldicize><@s.Truncate length=140>${s.result.title}</@s.Truncate></@s.boldicize></a></div>
                    <#-- Search result URL -->
                    <cite data-url="${s.result.displayUrl}">${s.result.displayUrl}</cite>
    
                    <#-- Search result description - print description metadata, fallback to auto-generated summary -->
                    <#if s.result.listMetadata?keys?seq_contains("c")>
                      <p>
                        <#if s.result.date??>${s.result.date?date?string("d MMM yyyy")}:</#if>
                        <@s.boldicize>${s.result.listMetadata["c"]?first!}</@s.boldicize>
                      </p>
                    <#elseif s.result.summary??>
                      <p>
                        <#if s.result.date??>${s.result.date?date?string("d MMM yyyy")}:</#if>
                        <@s.boldicize>${s.result.summary}</@s.boldicize>
                      </p>
                    </#if>
                  </li>
                </#if>
              </@s.Results>
            </ol>
    
            <#-- Search results pagination -->
            <div class="dxp-search-pagination">
              <ul>
                <@fb.Prev><li><a href="${fb.prevUrl}" rel="prev">Prev</a></li></@fb.Prev>
                <@fb.Page numPages=5><li <#if fb.pageCurrent> class="active"</#if>><a href="${fb.pageUrl}">${fb.pageNumber}</a></li></@fb.Page>
                <@fb.Next><li><a href="${fb.nextUrl}" rel="next">Next</a></li></@fb.Next>
              </ul>
            </div>
          </div>
    
          <#-- Related searches (contextual navigation) -->
          <@s.ContextualNavigation>
            <@s.ClusterNavLayout />
            <@s.NoClustersFound />
            <@s.ClusterLayout>
              <div id="dxp-search-contextual-navigation">
                <h3>Related searches</h3>
                <ul>
                  <@s.Category name="type">
                    <@s.Clusters><li><a href="${s.cluster.href}">${s.cluster.label?replace("...", " "+s.contextualNavigation.searchTerm)}</a></li></@s.Clusters>
                  </@s.Category>
                  <@s.Category name="topic">
                    <@s.Clusters><li><a href="${s.cluster.href}">${s.cluster.label?replace("...", " "+s.contextualNavigation.searchTerm)}</a></li></@s.Clusters>
                  </@s.Category>
                </ul>
              </div>
            </@s.ClusterLayout>
          </@s.ContextualNavigation>
      </@s.AfterSearchOnly>
    
    </div>
  5. Return to the results page management screen (select the results page name in the breadcrumb trail) then enter a search into the search box.

  6. You should see a set of search results returned in a very basic html page, without any real formatting. It should look similar to:

    configure rest template 03
  7. Copy the URL of the search results page and save this - you’ll need it when you configure your REST asset.

  8. View the source code for the HTMl and observe that the template is returning a chunk of html, rather than a full html file (it’s just returning a <div> containing all the search results.)

Your basic template is now created. The next step is to configure a REST asset in DXP Content Management to call this search results page.

Part 2: Configure a REST asset within your CMS website

  1. Return to the DXP console and select the content management capability.

  2. Locate your website in the asset map.

  3. Create a folder for all your search-related assets if you don’t already have one. At the root of your site create a new folder asset named DXP Search Integration.

  4. Create a new REST resource by right-clicking on the DXP Search Integration folder and selecting Create new…​  Web Services  REST resource from the popup context menu.

    create rest asset 01
  5. Set the following details in the popup window, then click the Create button.

    • Name: Site search integration

    • Link type: Hidden link

    create rest asset 02
  6. The popup window will update and should provide confirmation that the asset was created successfully. Click the edit button to configure the REST resource asset.

    create rest asset 03
  7. Update the configuration in the HTTP request section:

    • Method: GET

    • URL(s): Paste the URL that you noted down earlier and then edit this. It can be set to remove any variables that should be hard-coded and not repeated (typically 'collection', 'profile' and 'form'):

      Your URL will look something similar to this:

      https://dxp-au-admin.funnelback.squiz.cloud/s/search.html?collection=exampleorg%7Esp-site-search&form=rest&profile=site-search&query=example

      Extract the following information from your URL:

      • The search hostname (including the protocol. Update the hostname so that the -admin is replaced with -search (e.g. the hostname above would be updated to https://dxp-au-search.funnelback.squiz.cloud).

      • Extract the value of the collection parameter. In the above example this is exampleorg%7Esp-site-search.

        This value selects the search package to use when calling the search.
      • Extract the value of the profile parameter. In the above example this is site-search. If your profile value contains a _preview suffix remove this.

        This value selects the results page to use when calling the search.
      • Extract the value of the form parameter. In the above example this is rest.

        This value selects the template to use when calling the search.

      Construct a URL that has the following format and add this to the URL(s) field.:

      <SEARCH-HOSTNAME>/s/search.html?%globals_server_query_string^replace:profile=<PROFILE-ID>:^replace:collection=<COLLECTION-ID>:^replace:form=<TEMPLATE-ID>:^replace:&+:&%&collection=<COLLECTION-ID>&profile=<PROFILE-ID>&form=<TEMPLATE-ID>

      Using the example values we collected above would result in a URL like the following being set:

      https://dxp-au-search.funnelback.squiz.cloud/s/search.html?%globals_server_query_string^replace:profile=site-search:^replace:collection=exampleorg%7Esp-site-search:^replace:form=rest:^replace:&+:&%&collection=exampleorg%7Esp-site-search&profile=site-search&form=rest

      This configures the REST asset to the search.html endpoint for the DXP search, and passes through the set of parameters that accompanied the query. The keyword replacements ensure that the collection, profile and form parameters can’t be changed by editing the URL, and that they are not duplicated.

      You should ensure that any parameters you add to the request (when the REST asset URL is called) to override other values (such as collection, profile but also any other parameters like userkeys) should be removed from the globals_server_query_string using an appropriate replacement modifier. This ensures that you won’t get duplicate parameters added to requests resulting in something like http://search.example.com/search?query=example*&collection=example~search&profile=search&userkeys=john&userkeys=john&userkeys=john&userkeys=john&userkeys=john
    • Cache options: Never cache

    • Request headers: Configure this to pass the remote user’s IP address to the DXP search so that the correct information is recorded in your search analytics:

      X-Forwarded-For:%globals_server_remote_addr%
  8. Under Output options:

    • Replace keywords in output: Enable this to support the processing of DXP Content Management keywords (such as snippets) present in the search results HTML response if you wish to nest DXP Content Management snippets in your Freemarker template.

    • Error response: Set this to something helpful like:

      Search service is currently unavailable.
  9. Click the save button to save the REST asset configuration.

Part 3: Nesting the REST asset

This tutorial show you the steps required to create your search results page in DXP Content Management and link it to the REST asst you just created.

  1. Create a standard page (typically at the site’s root) in DXP Content Management called search: then click the create button.

    • Page name: Search

    • Link type: Menu link

  2. Edit the page and change to the contents screen, then edit the properties of the component in the page where you would like the search results to appear by clicking on the button. This will open a panel on the side of your screen. Set the following properties:

    • Component type: Nested content

  3. Click the save button to update your component properties. The component will update and provide a field where you can link the REST asset you created in the previous step.

  4. Enter the Asset ID of the REST asset you created into the Nested content field then click the save button.

  5. Click on the URLs tab and make a note of the URL of the search page, you will need this in the next step.

Part 4: Configuring the DXP search URLs

  1. Return to the DXP search configuration screen, locate your search package and then select the results pages tab. Click on the results page that you are using for your search.

  2. Select the edit results page configuration option from the customize panel.

  3. Set the following configuration keys by clicking the add new button.

    Parameter key Value

    ui.modern.search_link

    Set this to the CMS search page URL. e.g. http://www.example.com/search. This setting ensures links within the UI such as faceted navigation and pagination links call the search via the CMS search page.

    ui.modern.click_link

    Set this to the absolute URL of the DXP Search click endpoint. e.g. https://dxp-au-admin.funnelback.squiz.cloud/s/redirect. This setting ensures the click tracking works correctly with the CMS search page.

    ui.integration_url

    Set this to the CMS search page URL. e.g. http://www.example.com/search. This setting ensures the search boxes within the administration dashboard call the search via the CMS search page.

    If you want to make use of the cache pages you will also need to set the ui_cache_link, but this currently needs to be set in the parent search package configuration.
  4. Click the save all button to update the search configuration.

This completes the setup steps for a basic search.

Part 5: Testing and accessing the REST asset

  1. Visit your search results page in DXP Content Management, using a test query: https://www.example.com/search?query=!showall

  2. Test clicking through a result, using pagination and cached copy links (if applicable).

  3. You can add a search box to your template using a standard HTML search form using GET similar to the code below:

    <form action="/search" method="get"/> (1)
      <input type="search" name="query" value=""/>
      <input type="submit"/>
      (2)
    </form>
    1 the form action should point to the standard page that nests your search.
    2 add additional input fields if you need to pass in other search options (such as a sort parameter).
The search form must call the search using a HTTP GET request. If you attempt to use a HTTP POST parts of the search will not function correctly.