Implementing a Custom Component in JavaScript

When possible, it is recommended to create your component in TypeScript (see Step 9 - Advanced Integration with Custom TypeScript Component - Configuration).

For more information on why it is considered a good practice, see Creating Custom Components.

When using the Coveo JavaScript Search Framework, you will sometimes want to add your own custom components (see Creating Custom Components).

In this tutorial, you will be adding a rudimentary custom search box to your page using pure JavaScript. This component will execute a query every time a user presses the Enter key. It will also have a configurable option to enable search as you type, meaning that a query will be sent every time the user types something when the option is enabled.

Step 1 - Create Your Constructor

First, you need to declare a constructor function that the framework will call to instantiate your custom search box component:

var CustomSearchbox = (function(_super) {
  function CustomSearchbox(element, options, bindings) {
    this.type = 'CustomSearchBox';
    Coveo.Component.bindComponentToElement(element, this);
    this.element = element;
    this.options = options;
    this.bindings = bindings;
  }
})(Coveo.Component);

The framework will automatically pass the following arguments when it calls this function:

  1. element: the DOM element on which the component is being instantiated.
  2. options: the options that were passed for the component in the init top-level function call.

    The options argument contains options passed as follows:

    Coveo.init(document.querySelector('#search'), {
      CustomSearchbox: {
        'foo': 'bar'
      }
    })
    

    It does not contain options passed as data- attribute values in the component markup (e.g., div class='CustomSearchbox' data-foo='bar'></div>)

  3. bindings: an object that references different singletons which are used by the framework. This object has the following properties:

    • root: the DOM element on which the main SearchInterface component is bound.
    • queryStateModel: the State object instance, which can be used to read/write in the state of the search interface.
    • queryController: the QueryController object instance, which can be used to execute queries in the search interface.
    • searchInterface : the main SearchInterface component instance.
    • usageAnalytics : the Analytics component instance, which can log various analytics events.

Step 2 - Add the Component ID

In order for your component to work, it needs an ID property that uniquely identifies the component. This property allows the Coveo JavaScript Search Framework to recognize an element with the CoveoCustomSearchBox class to be instantiated as a component.

Add the following line after the constructor function:

CustomSearchbox.ID = 'CustomSearchbox';

Step 3 - Initialize the Component

In order for your component to be registered and initialized alongside the Coveo JavaScript Search Framework, you need to call your constructor in the registerAutoCreateComponent function.

Add the following line after the constructor function:

Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);

Your code should now look like this:

CustomComponent.js

var CustomSearchbox = (function(_super) {
  function CustomSearchbox(element, options, bindings) {
    this.type = 'CustomSearchBox';
    Coveo.Component.bindComponentToElement(element, this);
    this.element = element;
    this.options = options;
    this.bindings = bindings;
  }
 
  CustomSearchbox.ID = 'CustomSearchbox';
  Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);
})(Coveo.Component);

Coveo JavaScript Search Framework 2.2900.23 - July 2017

When using the Coveo JavaScript Search Framework 2.x, you can also register your component as a lazy component. This way, your code is only initialized when you have the component on your page (see Lazy Versus Eager Component Loading).

Step 4 - Add a Function to Trigger a Query When the Enter Key is Pressed

As of now, your component is able to be successfully initialized, but it does not do anything.

Since you are creating a search box, a good function to add would be to trigger a query when the Enter key is pressed in the search box.

  1. In the constructor, add an event listener on the keyup event. Make it so that it calls a handleKeyUp function.

    this.element.addEventListener('keyup', (e)=> this.handleKeyUp(e));
    
  2. Create the handleKeyUp function. It should only detect if the released key was Enter, and call the executeNewQuery function when it is.

    CustomSearchbox.prototype.handleKeyUp = function(e) {
      if (e.key == 'Enter') {
        this.executeNewQuery();
      }
    }
    
  3. Create the executeNewQuery function.
    The function should read the value entered in your component, and set it as the new query.

    CustomSearchbox.prototype.executeNewQuery = function() {
      this.bindings.queryStateModel.set('q', this.element.value);
      this.bindings.queryController.executeQuery();
    }
    
  4. In  the executeNewQuery function, send a Usage Analytics event when the query is performed. This is so you can track what your users search (see Why You Should Add Usage Analytics Events).

    CustomSearchbox.prototype.executeNewQuery = function() {
      this.bindings.queryStateModel.set('q', this.element.value);
      this.bindings.usageAnalytics.logSearchEvent({
        name : 'submitSearchbox',
        type : 'CustomSearchbox'
      });
      this.bindings.queryController.executeQuery();
    }
    

Your code should now look like this:

var CustomSearchbox = (function(_super) {
  function CustomSearchbox(element, options, bindings) {
    this.type = 'CustomSearchBox';
    Coveo.Component.bindComponentToElement(element, this);
    this.element = element;
    this.options = options;
    this.bindings = bindings;
    this.element.addEventListener('keyup', (e) => this.handleKeyUp(e));
  }
  CustomSearchbox.prototype.handleKeyUp = function(e) {
    if (e.key == 'Enter') {
      this.executeNewQuery();
    }
  }
  CustomSearchbox.prototype.executeNewQuery = function() {
    this.bindings.queryStateModel.set('q', this.element.value);
    this.bindings.usageAnalytics.logSearchEvent({
      name: 'submitSearchbox',
      type: 'CustomSearchbox'
    });
    this.bindings.queryController.executeQuery();
  }
  CustomSearchbox.ID = 'CustomSearchbox';
  Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);
})(Coveo.Component);

Why You Should Add Usage Analytics Events

To get consistent data and reporting inside Coveo Usage Analytics, it is mandatory that every search event be logged to the service. Otherwise, the framework will log a warning every time a query is submitted without first having logged a search event.

If you do not log any search events when you trigger a query, in the your browser console, you will most probably see a warning like this one:

A search was triggered, but no analytics event was logged. If you wish to have consistent analytics data, consider logging a search event using the methods provided by the framework

In a Google Chrome browser, the warning looks like this:

Step 5 - Add a Search as you Type Option

You can add options to your custom component. Those options can be modified using the data- HTML attribute (see ComponentOptions).

A nice option to have on a search box is the search as you type function, which triggers a query while the user is typing, allowing them to preview the results while typing their query.

  1. In the constructor, change the this.options = options line to initialize your other options (see ComponentOptions - initComponentOptions).

    this.options = Coveo.ComponentOptions.initComponentOptions(element, CustomSearchbox, options);
    
  2. Declare a searchAsYouType option on your component. You want this option to be a Boolean (see ComponentOptions - buildBooleanOption). For this example, you can make its default value false.

    CustomSearchbox.options = {
      searchAsYouType : Coveo.ComponentOptions.buildBooleanOption({defaultValue: false})
    }
    
  3. Change your handleKeyUp function so that, when searchAsYouType is enabled, a query is performed every time a key is released instead of only when the Enter key is.

    CustomSearchbox.prototype.handleKeyUp = function(e) {
      if (this.options.searchAsYouType) {
        this.executeNewQuery();
      } else if (e.key == 'Enter') {
        this.executeNewQuery();
      }
    }
    

Your code should now look like this:

var CustomSearchbox = (function(_super) {
  function CustomSearchbox(element, options, bindings) {
    this.type = 'CustomSearchBox';
    Coveo.Component.bindComponentToElement(element, this);
    this.element = element;
    this.options = Coveo.ComponentOptions.initComponentOptions(element, CustomSearchbox, options);
    this.bindings = bindings;
    this.element.addEventListener('keyup', (e) => this.handleKeyUp(e));
  }
  CustomSearchbox.prototype.handleKeyUp = function(e) {
    if (this.options.searchAsYouType) {
      this.executeNewQuery();
    } else if (e.key == 'Enter') {
      this.executeNewQuery();
    }
  }
  CustomSearchbox.prototype.executeNewQuery = function() {
    this.bindings.queryStateModel.set('q', this.element.value);
    this.bindings.usageAnalytics.logSearchEvent({
      name : 'submitSearchbox',
      type : 'CustomSearchbox'  
    });
    this.bindings.queryController.executeQuery();
  }
  CustomSearchbox.options = {
    searchAsYouType : Coveo.ComponentOptions.buildBooleanOption({defaultValue: false})
  }
  CustomSearchbox.ID = 'CustomSearchbox';
  Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);
})(Coveo.Component);

You can now add data-search-as-you-type='true' to your CoveoCustomSearchbox element to enable your option.

<input class="CoveoCustomSearchbox" data-search-as-you-type='true'></input>

Step 6 - Add the Newly Created Component in a Search Page

Now that you have completed your rudimentary custom search box, you are ready to add it to your page:

</head>
<script>
  // You could of course instead insert a script resource in the header of your search page.
  var CustomSearchbox = (function(_super) {
    function CustomSearchbox(element, options, bindings) {
      this.type = 'CustomSearchBox';
      Coveo.Component.bindComponentToElement(element, this);
      this.element = element;
      this.options = Coveo.ComponentOptions.initComponentOptions(element, CustomSearchbox, options);
      this.bindings = bindings;
      this.element.addEventListener('keyup', (e) => this.handleKeyUp(e));
    }
    CustomSearchbox.prototype.handleKeyUp = function(e) {
      if (this.options.searchAsYouType) {
        this.executeNewQuery();
      } else if (e.key == 'Enter') {
        this.executeNewQuery();
      }
    }
    CustomSearchbox.prototype.executeNewQuery = function() {
      this.bindings.queryStateModel.set('q', this.element.value);
      this.bindings.usageAnalytics.logSearchEvent({
        name: 'submitSearchbox',
        type: 'CustomSearchbox'
      });
      this.bindings.queryController.executeQuery();
    }
    CustomSearchbox.options = {
      searchAsYouType: Coveo.ComponentOptions.buildBooleanOption({ defaultValue: false })
    }
    CustomSearchbox.ID = 'CustomSearchbox';
    Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);
  })(Coveo.Component);
</script>
[...]
<!-- The class CoveoCustomSearchbox corresponds to the ID of the component -->
<body class='CoveoSearchInterface'>
  <input class='CoveoCustomSearchbox' data-search-as-you-type='true'></input>
</body>