Step 3 - Understanding the Event System

The event system is core to the Coveo JavaScript Search Framework (see Events). It is used both internally by out-of-the-box components, and externally by extension code written for specific implementations.

All out-of-the-box components of the Coveo JavaScript Search Framework use the event system as demonstrated in this tutorial. Thus, by taking full advantage of this system, any external code you write can gain access to everything “core” components can do.

In this step of the tutorial, you will learn more about the major categories of events, how to attach event handlers, and the order in which certain events are triggered. Exercises at the end of this section will also help you gain mastery over these concepts.

If you significantly modified the ./bin/index.html page in previous steps of this tutorial, you can undo these changes by rebuilding the project. To do so, run the following command line from the top of the tutorial project folder:

npm run build

Available Events

A list of all available events in the Coveo JavaScript Search Framework can be found in the reference documentation (see classes AdvancedSearchEvents, BreadcrumbEvents, ComponentEvents, InitializationEvents, and QueryEvents).

More information on events and event handlers can also be found in the Reference section of this site (see Events).

Right now, you should not try to learn all of the different events or what each handler can expect to receive as an argument. You should rather focus on understanding the major categories into which events can fall, and how these categories of events can be used to implement certain specific features.

  • Query events should be used by any code aimed at interacting with the query. This can mean modifying the query while it is being built, or rendering the results when the query returns (see Query Events)
    Query events are probably the most important events in the Coveo JavaScript Search Framework, and they are the most widely used and interacted with. 
  • Initialization events are typically used when a piece of code needs to be executed at a precise moment during initialization; i.e., when the Coveo.init function is executed (see Initialization Events).
  • ResultList events are generally used to interact with the rendering of results in the result templates (see ResultList Events).

Other categories of events exist. However, Query, Initialization, and ResultList events are the most important and frequently used ones.

Attach an Event Handler

To attach an event handler, you have to target the element to which you want to add a handler and specify the event string and the function that should be executed.

The target of an event will almost always be the root of your search interface.

Attaching an event handler in the Coveo JavaScript Search Framework is no different from binding a click handler on a button in “standard” web development.

The following examples demonstrate how you could add a handler to modify the query while it is being built. 

You will learn more about modifying the query in the next step of this tutorial (see Step 4 - Modifying the Query). For now, these examples focus on demonstrating how to attach event handlers.

  • Using the standard “pure” JavaScript way to bind an event handler (see EventTarget.addEventListener()):

      <script>
        document.addEventListener('DOMContentLoaded', function () {
          Coveo.SearchEndpoint.configureSampleEndpoint();
          var root = document.body;   
          root.addEventListener('buildingQuery', function(e) {
            e.detail.queryBuilder.expression.add('foobar');
          });
          Coveo.init(root);
        })
      </script>
    
  • Using the Dom helper class:

      <script>
        document.addEventListener('DOMContentLoaded', function () {
          Coveo.SearchEndpoint.configureSampleEndpoint();
          var root = document.body;   
          Coveo.$$(root).on('buildingQuery', function(e, args) {
            args.queryBuilder.expression.add('foobar');
          });
          Coveo.init(root);
        })
      </script>
    
  • Using jQuery:

      <script>
        document.addEventListener('DOMContentLoaded', function () { 
          Coveo.SearchEndpoint.configureSampleEndpoint();
          var root = document.body;   
          $(root).on('buildingQuery', function(e, args) {
            args.queryBuilder.expression.add('foobar');
          });
          Coveo.init(root);
        })
      </script>
    

Q: Why three different ways to bind an event?

A: The way you choose to bind an event is up to you.

Some people are used to jQuery, while some others prefer the native JavaScript way with addEventListener. Internally, the Coveo JavaScript Search Framework uses the Dom helper class.

All three methods are perfectly equivalent, the only difference being that a native handler will receive the event argument in the detail property of the CustomEvent (see CustomEvent) instead of a second argument.

The examples from this tutorial will use the Dom helper class.

Triggered Events Examples

The following examples show in what order the Query and Initialization events are triggered.

Query Events

Consider the following code excerpt:

Query events

<script>
    // Assume that a search interface exists and that it is correctly configured.
    // First, the root of the interface is targeted. In this example, it is the body of the document.
    var root = document.body;
    // Then, the query is executed.
    Coveo.executeQuery(root);
</script>

When the Coveo.executeQuery(root) line is executed, the following events are triggered, in order:

  1. newQuery:  This event informs all components and external code that a new query is being triggered. This can be used to update the appearance of a component, for instance to add a Loading... image.
  2. buildingQuery: This event is typically used when all components contribute their part in building the outgoing query.
  3. doneBuildingQuery: This is triggered when the query is done being built.
  4. duringQuery: This is triggered when the query has been sent from the browser as an XMLHTTPRequest.
  5. From here on, two scenarios are possible:
    1. The query is executed successfully on the Search API:
      1. preprocessResults: This event allows external code to modify the results before they are rendered. This can be used to modify the results content or adjust the index data on the client side.

      2. noResults: This is triggered when the query was executed successfully, but the results set is empty.
      3. querySuccess: This is triggered when the query was executed successfully, and the results can be manipulated and rendered. Note that if the noResults event was triggered, querySuccess will still be triggered.
        This is typically where the ResultList component will render itself. 
      4. deferredQuerySuccess: This event is triggered when the query was executed successfully, and the Facet components are ready to render themselves.
        For performance reasons, facets are always rendered after the result list.
    2. queryError: This is triggered when the query was executed on the Search API, but it returned an error. This error could be a network error, an authentication error, an invalid query, etc.
      If there was a query error, the preprocessResults, noResults, querySuccess, and deferredQuerySuccess events will not be triggered.

Initialization Events

Consider the following code excerpt:

Initialization event

<script>
  document.addEventListener('DOMContentLoaded', function () {
    Coveo.SearchEndpoint.configureSampleEndpoint();
    var root = document.body;
    Coveo.init(root);
  })
</script>

When the Coveo.init(root) line is executed, the following events are triggered (in order):

  1. beforeInitialization: This is triggered only once before all components inside the search interface have been initialized (i.e., before any component constructor function has been called).
  2. afterComponentsInitialization: This is triggered only once, when the constructors of all components have been executed.

  3. afterInitialization:This is triggered when the UI is fully initialized. Concretely, this means that the first query is launched, and that any state coming from the URL has been applied (e.g., http://mysearchinterface#q=myQuery).

  4. If your interface is configured to automatically trigger a query on initialization (see the autoTriggerQuery option, which is set to true by default), all Query events will be triggered in the same order as in the Query Events example.

Component constructors are mentioned in this tutorial because it is important that you understand this concept.

As stated before, the “core” components of the Coveo JavaScript Search Framework use the event system explained in this tutorial.

This means that all components in your interface will also bind event handlers during the execution of their constructor.

For example, if you need to execute a handler on the buildingQuery event and you want your handler to execute after the “core” components, you will have to bind your handler during the afterComponentsInitialization event.

If you would rather want your handler to execute before the “core” components, you will have to bind your handler before the afterComponentsInitialization event.

Exercises

  • All exercises in this section should be done by modifying the tutorial search page under ./bin/index.html (the exercises should not be done by modifying the original search page under ./pages/index.html ).
  • To actually see the results in your demo page, you must be running a webpack-dev-server (See Step 0 - Environment Setup).
  1. After the query was executed successfully, render a very simple unordered list of all found item titles.

    Solution

     <head>
      
       [ ... ]
       <script>
         document.addEventListener('DOMContentLoaded', function () {
           Coveo.SearchEndpoint.configureSampleEndpointV2();
           var root = document.body;
           // Adding the event listener before the 'Coveo.init(root)' line ensures that it will be bound before the first query
           // is executed. It will thus execute correctly on the first query.
           //
           // Note that the Dom helper class is used in this solution. 'args' is an object matching the corresponding reference
           // documentation:
           // https://coveo.github.io/search-ui/classes/queryevents.html#querysuccess
           // https://coveo.github.io/search-ui/interfaces/iquerysuccesseventargs.html
           //
           // This might seem like a lot of information for now, so you can instead simply inspect the variable value in a
           // modern web browser console to explore the available data.
           //
           Coveo.$$(root).on('querySuccess', function(e, args) {
             var results = args.results.results;
             var listElement = document.querySelector('ul');
             listElement.innerHTML = '';
             results.map(function(result) {
               var liElement = document.createElement('li');
               liElement.innerText = result.title;
               listElement.appendChild(liElement);
             })
           })
           Coveo.init(root);
         })
       </script>
     </head>
         
     <body id="search" class='CoveoSearchInterface' data-enable-history="true">
       <!-- The following unordered list should be added. -->
       <ul></ul>
         
       [ ... ]
     </body>
    
  2. After the query is done being built, read the expression from the search box. If it matches the keyword "foo", add an advanced query expression for the keyword "bar".

    Solution (JavaScript)

     <script>
       document.addEventListener('DOMContentLoaded', function () {
         Coveo.SearchEndpoint.configureSampleEndpoint();
         var root = document.body;
         Coveo.$$(root).on('doneBuildingQuery', function(e, args) {
           var queryBuilder = args.queryBuilder;
           var expression = queryBuilder.expression.build();
           if(expression != null && expression.match(/foo/i)) {
             queryBuilder.advancedExpression.add('bar');
           }
         });
         Coveo.init(root);
       })
     </script>
    

What’s Next?

You should now proceed to Step 4 - Modifying the Query.