Leveraging the Coveo JavaScript Search Framework

The Coveo™ JavaScript Search Framework offers a large collection of components (e.g., Searchbox, ResultList, Facet, etc.) and utilities to help you create, customize, and maintain end-user interfaces relying on the Coveo Platform. Among other things, JavaScript Search Framework end-user interfaces can automatically handle building and sending queries, displaying query results, and logging appropriate usage analytics events.

If you decide not to use the JavaScript Search Framework to achieve a search integration with the Coveo Platform, but rather choose to use the Coveo Cloud APIs directly, make sure you log the right usage analytics events. If you don’t, machine learning and usage analytics reports won’t be usable (see Implement a Search Interface and Log Usage Analytics Events).

This article provides a high-level overview of the crucial steps and elements required when using the JavaScript Search Framework to design your search interface.

To use the Coveo JavaScript Search Framework, you will most likely want to include a content delivery network (CDN) link in the <head> of the page, (see JavaScript Search Framework CDN Links).

<head>
  <!-- ... -->
  <link rel="stylesheet" href="https://static.cloud.coveo.com/searchui/v2.7610/css/CoveoFullSearch.css"/>
  <script src="https://static.cloud.coveo.com/searchui/v2.7610/js/CoveoJsSearch.Lazy.min.js"></script>
  <script src="https://static.cloud.coveo.com/searchui/v2.7610/js/templates/templates.js"></script>
  <!-- ... -->
</head>

If for some reason you prefer not to use a CDN link, you can download the project, notably by using npm (see Download the JavaScript Search Framework).

Regardless of the installation method you employ, you can either load components lazily or eagerly. When employing lazy loading, your search page only loads the code of the components that are actually required. Although lazy loading adds a layer of complexity to your search page, it translates into linear time gains, especially significant in simple search pages (see Lazy Versus Eager Component Loading). The above is an example of lazy loading.

Connect Your Search Page to Your Coveo Organization

To connect your search page to your Coveo organization, create and configure a corresponding endpoint SearchEndpoint (see Setting Up a New Endpoint). This endpoint tells the framework where to send queries and where to log usage analytics events. Typically, the access token you configure with your search endpoint contains this information (see Authenticating).

<head>
  <!-- ... -->
  <script>
    document.addEventListener("DOMContentLoaded", function () {
      let organizationId = "mycoveoorganization";
      let accessToken = "XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
      Coveo.SearchEndpoint.configureCloudV2Endpoint(organizationId, accessToken);
      Coveo.init(document.body);
    })
  </script>
  <!-- ... -->
</head>

Use Components to Build Your Search Interface

The Coveo JavaScript Search Framework is based on a set of components (see JavaScript Search Framework - Components). Use the desired components as the HTML building blocks of your search interface (see Reference Documentation). The framework JavaScript and CSS files then process that HTML to build your dynamic search page. Most components expose options which can be set directly in their markup configuration (see Configuring Components).

The following screen capture contains a minimal example of what the framework can help you accomplish, using only the few dozen lines of HTML below.

jsui-overview

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My Basic Search Page</title>
    <link rel="stylesheet" href="https://static.cloud.coveo.com/searchui/v2.4609/css/CoveoFullSearch.css"/>
    <script src="https://static.cloud.coveo.com/searchui/v2.4609/js/CoveoJsSearch.Lazy.min.js"></script>
    <script src="https://static.cloud.coveo.com/searchui/v2.4609/js/templates/templates.js"></script>
    <script>
    document.addEventListener("DOMContentLoaded", function () {
      let organizationId = "mycoveoorganization";
      let accessToken = "XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
      Coveo.SearchEndpoint.configureCloudV2Endpoint(organizationId, accessToken);
      Coveo.init(document.body);
    })
  </script>
  </head>
  <body id="search" class="CoveoSearchInterface" data-results-per-page="3">
    <div class="coveo-tab-section">
      <a class="CoveoTab" data-id="All" data-caption="All Content"></a>
    </div>
    <div class="coveo-search-section">
      <div class="CoveoSearchbox"></div>
    </div>
    <div class="coveo-main-section">
      <div class="coveo-facet-column">
        <div class="CoveoFacet" data-title="Type" data-field="@objecttype"></div>
      </div>
      <div class="coveo-results-column">
        <div class="CoveoResultList"></div>
        <div class="CoveoPager"></div>
      </div>
    </div>
  </body>
</html>

Include an Analytics Component

In addition to interactive components such as the Searchbox and ResultList, you should include an Analytics component (CoveoAnalytics class) in your search interface. While this component doesn’t render anything visible on its own, it allows the framework to automatically log appropriate usage analytics events whenever end users interact with your search interface.

The Analytics component also exposes the searchHub option, which determines the originLevel1 to use for all usage analytics events, and the searchHub to use for all search requests originating from the search interface.

By default, Coveo Machine Learning (Coveo ML) models use the originLevel1/ searchHub value as a filter. Therefore, setting the searchHub option to a meaningful value is extremely important, especially if your solution comprises many search interfaces (e.g., a community search page , an internal search page, etc.). Otherwise, the output of your Coveo ML models won’t be properly scoped (e.g., users on your community search page may see recommendations intended for your internal search page and vice versa). Using a distinctive searchHub value for each of your search interfaces is also highly practical for usage analytics reporting.

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="CoveoAnalytics" data-search-hub="communitySearch"></div>
  <!-- ... -->
</body>

You can also use the JavaScript Search Framework to modify the standard events logged by the Analytics components, or to log these events on your own (see Log Usage Analytics Events).

If your Coveo organization is in the HIPAA environment, set the endpoint option of your Analytics component to https://usageanalyticshipaa.cloud.coveo.com.

<div class="CoveoAnalytics" data-endpoint="https://usageanalyticshipaa.cloud.coveo.com"></div>

A search box is an essential component in most search interfaces. To create a standard search box, include, and optionally configure, a Searchbox component in your search page.

HTML:

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-search-section">
    <!-- ... -->
    <div class="CoveoSearchbox"></div>
  </div>
  <!-- ... -->
</body>

End result:

jsui-search-box

Provide Query Suggestions

You may want to offer query suggestions to your end users as they type in your search box. There are three main advantages to this:

  1. It’s faster than typing full keywords.

  2. It prevents misspelling.

  3. It can help formulate queries based on previous end-user input.

By default, the enableOmnibox option is set to true. This makes the searchbox an Omnibox which displays query suggestions by default, provided that a Coveo ML Query Suggestions (QS) model is properly configured. To set up such a model, see Create a Query Suggestion Model.

You may also want to enable partial matching of queries. This feature enables the index to return items which match only part of the words in the basic query expression (see Taking Advantage of Keywords Partial Matches).

Create a Result List

A result list allows end users to view and interact with rendered query result items.

Result Templating

The main ingredients in a result list are the result items, which are formatted using result templates.

Simple HTML list result template example

More specifically, result templates transform the raw search results (which the Coveo REST Search API returns in JSON format) into an HTML representation. You can customize the way your search interface displays search results using a template engine.

When creating your own templates, you will probably want to leverage result template helpers.

The JavaScript Search Framework supports the following template engines:

  • HTML templates (recommended)

    Coveo designed these templates to be easily customizable using the Interface Editor. While they’re not as flexible as Underscore templates, since they’re structured using rows and cells, HTML templates are easier to edit for less technical users and easier to maintain in general. Despite their ease of use, HTML templates can do anything that Underscore templates can.

    <script id="myTemplate" class="result-template"
          type="text/html">
    <div class="coveo-result-frame">
      <div class="coveo-result-row">
        <div class="coveo-result-cell"
             style="<!-- ...some styling... -->">
          <span class="CoveoIcon"></span>
          <div class="CoveoQuickview"></div>
        </div>
        <div class="coveo-result-cell"
             style="padding-left:15px;">
          <div class="coveo-result-row">
            <div class="coveo-result-cell">
              <a class="CoveoResultLink"
                 style="font-size:18px;"></a>
            </div>
            <div class="coveo-result-cell"
                 style="<!-- ...some styling... -->">
              <span class="CoveoFieldValue"
                    data-field="@date"
                    data-helper="date"></span>
            </div>
          </div>
        </div>
      </div>
    </div>
    </script>
    
  • Underscore templates

    These are very flexible, in the sense that they can execute arbitrary JavaScript code (see Underscore.js). However, they can’t be edited using the Interface Editor, and they can become hard to read and difficult to maintain.

    <script id="myTemplate" class="result-template"
          type="text/underscore">
    // Use a Coveo helper to format the date and time,
    // referencing the raw.sysdate property of each result.
    <div class="my-date"><%= dateTime(raw.sysdate) %></div>
    // Output the excerpt of the result in a div.
    <div class="my-excerpt"><%= excerpt %></div>
    </script>
    

When there are multiple templates in a given result layout (i.e., in a given ResultList component), the first template whose conditions are satisfied by a query result is used. You can use a template with no conditions as a default template by including it as the last template in its parent layout.

When certain query result items have a logical parent-child relationship with one another, you may want to improve user experience by grouping those results and rendering a hierarchical representation of this group (see Folding Results).

Paging

A search interface typically contains a Pager component, which allows the end user to navigate through result pages.

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-main-section">
    <!-- ... -->
    <div class="coveo-results-column">
      <!-- ... -->
      <div class="CoveoPager"></div>
    </div>
    <!-- ... -->
  </div>
  <!-- ... -->
</body>

This example renders a paging interface as follows:

jsui-pager

Sorting

A search interface may contain one or several Sort components allowing end users to select the sort criterion to apply to the query result items.

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-main-section">
    <!-- ... -->
    <div class="coveo-results-column">
      <!-- ... -->
      <div class="coveo-results-header">
        <!-- ... -->
          <div class="coveo-sort-section">
            <span class="CoveoSort" data-sort-criteria="relevancy"
                  data-caption="Relevance"></span>
            <span class="CoveoSort"
                  data-sort-criteria="date descending,date ascending"
                  data-caption="Date"></span>
          </div>
        <!-- ... -->
      </div>
    </div>
    <!-- ... -->
  </div>
  <!-- ... -->
</body>

This example renders a sort interface as follows:

jsui-sort

Switching Layouts

A search interface may contain more than one ResultList component so that end users can select the result layout with which they’re most comfortable (i.e., list, card, or table). Each layout uses its own set of result templates.

<body id="search" class="CoveoSearchInterface">
  <!-- ...  -->
  <div class="coveo-main-section">
    <!-- ... -->
    <div class="coveo-results-column">
      <!-- ... -->
      <div class="coveo-results-header">
        <div class="coveo-summary-section">
          <!-- ... -->
        </div>
        <div class="coveo-result-layout-section">
          <span class="CoveoResultLayoutSelector"></span>
        </div>
        <div class="coveo-sort-section">
          <!-- ... -->
        </div>
        <!-- ... -->
      </div>
      <!-- ... -->
      <div class="CoveoResultList" data-layout="list">
        <!-- ...result templates... -->
      </div>
      <div class="CoveoResultList" data-layout="card">
        <!-- ...result templates... -->
      </div>
      <div class="CoveoResultList" data-layout="table">
        <!-- ...result templates... -->
      </div>
      <!-- ... -->
    </div>
    <!-- ... -->
  </div>
  <!-- ... -->
</body>

This example renders a result layout selector in the result header of the search interface:

Result Layout Selector

Create Facets

A search page typically includes facets which allow the end user to select dynamic filter values to narrow down a query to a specific subset of result items (see Facet component).

HTML:

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-main-section">
    <!-- ... -->
    <div class="coveo-facet-column">
      <div class="CoveoFacet" data-title="Type" data-field="@objecttype"></div>
      <div class="CoveoFacet" data-title="File Type" data-field="@filetype"></div>
    <!-- ... -->
  </div>
  <!-- ... -->
</body>

End result:

jsui-facets

By default, facet value captions correspond directly to the related facet field values in the current result set. If those field values are cryptic or in any way not easily readable, you may want to use client-side code to normalize them (see Normalize Facet Value Captions). However, it’s often preferable to modify field values as early as possible during the indexing process (see Understanding the Indexing Process).

Create Tabs

A search page is sometimes divided into tabs which essentially operate as sub-search pages. When an end user selects a tab, this tab typically enforces a static filter expression on queries, but may also have other effects such as routing queries through a specific query pipeline, modifying the maximum age of cached query result sets, or toggling the visibility of certain elements in the search interface (see Tab).

HTML:

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-tab-section">
    <a class="CoveoTab" data-id="All" data-caption="All Content"></a>
    <a class="CoveoTab" data-id="Email" data-caption="Email" data-expression="@mailbox" data-sort="date descending"></a>
  </div>
  <!-- ... -->
</body>

End result:

jsui-tabs

Display Components Only on Specific Tabs

A common way to provide end users with a more personalized search experience is to toggle components depending on the currently selected tab. This can be done by adding data-tab and/or data-tab-not options on any relevant HTML element inside your user interface (see Use Components Only on Specific Tabs).

In the search page with the following markup, the Author facet only appears when the Email tab is selected.

<body id="search" class="CoveoSearchInterface">
  <!-- ... -->
  <div class="coveo-main-section">
    <!-- ... -->
    <div class="coveo-facet-column">
      <div class="CoveoFacet" data-title="Author" data-field="@author" data-tab="Email"></div>
      <!-- ... -->
    </div>
  </div>
</body>

Customize Your Search Page

After you have covered the above fundamentals, you may want to further enhance end-user experience by employing some of the more advanced features of the framework.

The default look of the framework may surprise you by its simplicity, but it’s based on industry leading practices and data shared by Coveo clients. The JavaScript Search Framework components are designed to provide the best-in-class end-user experience, hence we recommend that you consult with your site UX specialist early in the project. Don’t forget that the components are built for efficiency. This approach optimizes the search experience without imposing any specific style.

The default Sort component displays the sort options next to each other. A drop down menu may seem like a good idea, however this increases the amount of clicks, hides options, and makes toggling a complex manipulation.

recommended-not-recommended-sort

  • Although the interface is customizable, we recommend that you refer to the Coveo Community when modifying the standard behavior of a Coveo component.

  • In an attempt to provide the best search experience possible, the components are designed to be easily accessible by end users. You may want to further include ARIA landmarks in your page to ease the experience of end users requiring assistive technology such as screen readers (see Add ARIA Landmarks).

Never change the existing CSS and JS files provided by Coveo. Always change the behavior in your own custom CSS and JS files.

Language

By default, the JavaScript Search Framework creates search pages in English. If you want to create a page in a different language, you typically only have to reference the proper culture file (located in the js/culture of the project) in your HTML page (see Change the Language of Your Search Interface).

Styling

You can define CSS rules to alter the visual appearance of the various JavaScript Search Framework components in any way you need. Create new CSS files containing the styling modifications your project requires, and reference those files in your search page (see Styling the Coveo JavaScript Search Framework).

Never change the existing CSS files provided by Coveo, or your changes will be overwritten when upgrading to a newer version of the JavaScript Search Framework.

  • You should always re-test your CSS customizations when upgrading to a newer version of the JavaScript Search Framework.

  • The JavaScript Search Framework relies on two sets of HTML classes. An element with a class name starting with Coveo (e.g., CoveoFacet) is bound to a component instance. Class names starting with coveo (e.g., coveo-search-section) are used for styling purposes only.

  • Code sample of CSS styling:
.coveo-custom .CoveoFacetRange,
.coveo-custom .CoveoFacet,
.coveo-custom .coveo-facet-header,
.coveo-custom .coveo-facet-footer
{
  border-radius: 0px;
  border: none;  
}
  • The following are a few examples of facet styling:

Coveo solutions facet styling

You may want to have a look at the CSS files of the Coveo Demo.

Build New Components

If you need to build a new component, a pure JavaScript solution will often be enough (see Implement a Custom Component in JavaScript). Is is however recommended to use TypeScript (see TypeScript Versus JavaScript).

Since the Coveo JavaScript Search Framework is a fully open source project, you can fork its repository on GitHub. Using TypeScript, you can then code your own custom features and components, and eventually create pull requests to have your contributions reviewed, and possibly merged into the main project. Advanced programming knowledge is required.

Be aware that pull requests will be refused if not properly tested (see Create Custom Components).

Use Standard Form Controls

If you want to add form controls such as check boxes, radio buttons, etc., to your search interface, consider using the standard JavaScript Search Framework form controls (see Use Standard Form Controls). These standard form controls are implemented as classes which come equipped out of the box with useful methods (e.g., see Checkbox). Additionally, using these controls ensures your search interface is uniform with the controls used by the JavaScript Search Framework components themselves.

In addition to using the JavaScript Search Framework to build a search page, you may want to use it to include a search box in every page of your website. This search box, called a standalone search box, would redirect end users to your main search page (see Create a Standalone Search Box).

<script type="text/javascript">
  document.addEventListener("DOMContentLoaded", function() {
    // ...
    var root = Coveo.$$(document).find("#searchbox");
    Coveo.initSearchbox(root, "https://support.coveo.com/s/search/All/Home/%40uri");
  });
</script>

When implementing a standalone search box in a web site or application, use lazy component loading to minimize loading time (see Lazy Versus Eager Component Loading).

To preserve a uniform end-user experience, many web design specialists recommend using the same search box across a given site. This implies using the standalone search box in the full search page as well. However, as the standalone search box is a redirection, if you only deploy in the full search page, it will completely reload the page. This is both inefficient and bad user experience.

The leading practice is to load the standalone search box as an external component when in the full search page (see Initializing - Embedded in the Main Search Page).

Get More Information

  • To get further acquainted with the Framework, you can read the JavaScript Search Framework Tutorials. Unless you plan to design your own custom components, you can skip the 9th and 10th tutorials.

  • It’s also possible to explore the Interface Editor to build a simple hosted search page in the Administration Console (see Use the Interface Editor and Manage Hosted Search Pages). With the interface editor, you can drag and drop components to design a search interface graphically rather than programmatically, as in the full JavaScript Search Framework.

    You would normally not use a hosted search page in a real application. The Interface Editor uses an old version of the Framework and it’s significantly more restricted than the Framework. Also, you probably want to keep your end users on your website rather than redirecting them to search.cloud.coveo.com.

What’s Next?

The next section of this guide explains how you can improve the relevance of your search interface (see Tuning Relevance).

Recommended Articles