Creating a Custom Facet in Coveo Hive – Part One

Recently I was tasked with creating a custom facet for a client’s Coveo for Sitecore implementation. I was implementing Coveo Hive, which worked well and it was very simple to add components to the page in a modularized fashion. After reviewing the proof of concept search page, the client realized they didn’t like the date slider component at all, and asked that we instead have a facet with two fields, start date and end date, and each field would have a calendar picker.

I quickly found out this was not available as an OOTB component. I would have to learn how to create a custom component and allow it to be added to a search page. I would have to manage state, facet appearance and breadcrumbs for the new facet. Due to the complexity of this task, I will be creating a blog post series for each major part. In this first blog post, I will go through the basic setup of your custom facet and at the end of this post, you should be able to see and interact with your facet to some extent. Final code will be provided at the end of the final blog post in this series. Let’s get started!

It Starts With Javascript (Or Typescript)

When I first started working on this, Coveo’s documentation suggested creating a custom component using Javascript. It looks like they’ve updated their documentation. You’ll want to start here and pick one of the two paths. I went the JS path, so my code will be different from yours if you choose the TS path.

Creating a New Javascript Class

If you choose the Javascript path, steps 1-3 of this document explains what you need to do to get started with the scripting piece. You will have to create a Javascript class that handles all the functionality the component will be responsible for. Your code should 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;

    // Bind events here
  }

  CustomSearchbox.ID = 'CustomSearchbox';
  Coveo.Initialization.registerAutoCreateComponent(CustomSearchbox);
})(Coveo.Component);

It’s important to note that the component must be named the same everywhere, or the component will not work correctly. So if you decide to rename CustomSearchbox to MyCustomFacet, be sure to update that naming everywhere. Note that you have the option to register your component as a lazy component, so that it is only initialized when you have the component on your page (see the link in the document for more information).

Integrating the Custom Component with Coveo Hive

Next, you’ll want to tell Coveo for Sitecore all about your brand new component. There are multiple things that need to be done here, so I’ll divide this part into two sections.

Basic Setup – Template, Layout, & Markup

You’ll want to refer to this documentation for this section. Per the document, Coveo Hive uses data sources to give properties to components. Think about what properties you want developers or other users to manage for your component. These will become fields on your component item in Sitecore that will be readable in your view and manageable in the scripting of your component.

In my case, I didn’t need any properties, but you still need to create a template anyway. So to truly set up your custom component to work with Coveo Hive, you’ll have to go through the steps in this document, including creating a data sources template, a model and rendering item in Sitecore, along with a model class and and your new .cshtml file for your component.

During this process, make sure to put your custom views, model classes, templates, rendering items and model items in a “Custom” folder under Coveo Hive in each respective area of your solution and Sitecore: .../Coveo Hive/Custom/Facets/YourCustomComponent, as this is a Coveo best practice.

Once you’ve done that and built/deployed the new code, you may run into a few hiccups:

  • When you create the properties class, you need to inherit from IModelProperties. This is a Coveo class in the Coveo.UI.Components.dll, so you’ll need to add a reference to that.
  • You might get an error opening the search page in the Experience Editor: "Coveo.UI.Components.Partials does not contain a definition for 'EDIT_TITLE'". I verified this is actually a problem by opening the Coveo.UI.Components.dll file in Telerik JustDecompile and finding that EDIT_TITLE was not present in the Partials class. If you get this error as well, you’ll want to remove the line from the view that references EDIT_TITLE. I am still not sure what purpose that line serves.
  • You may get a null reference exception on the line in your view where you loop through the Model.RawProperties. In my case, I had actually added a new property named RawProperties to my new model class, but shouldn’t have, as the inherited model class has that property.

Your view code should look something like this now. If not, please fill in code that is missing. What you’ve created is the container that wraps around your custom facet:

custom facet container v2

To complete your view, you just need to enter your custom markup for your component. You can go ahead and fill that in now.

Update Your Script

Now that you’ve added your custom markup with the facet container, you’ll need to add your custom scripting logic. In the area where I had // Bind events here in the initial script, you’ll want to start binding all of the custom events for your facet. You’ll also want to add your properties, functions and methods to the Javascript class to support those events.

In my case, I had two calendar pickers and needed to update the current Coveo query whenever my start date or end date changed. We’ll get more into modifying the query, state and other things later in the next blog post, but to visualize these script changes, here is some of my script’s code:

AOPADateRangePickerFacet is the name of my custom Javascript class, and as a result is used in all methods and functions in the class. Your name will be different.

var AOPADateRangePickerFacet = (function (_super) {
    // You can put global variables here, but it is not recommended IMO.
    // Use this.variables instead.
    var NewDateSet = '';
    var StartDate = '';
    var EndDate = '';
    function AOPADateRangePickerFacet(element, options, bindings) {
        this.type = 'AOPADateRangePickerFacet';
        Coveo.Component.bindComponentToElement(element, this);
        this.element = element;
        this.options = options;
        this.bindings = bindings;
        this.startDateField = this.element.querySelector('#startDatePicker');
        this.endDateField = this.element.querySelector('#endDatePicker');

        // Initialization
        this.startDatePickerInit(this);
        this.endDatePickerInit(this);
        this.startDatePickerChange(this);
        this.endDatePickerChange(this);
        this.registerStateAttribute(this);
        this.registerBuildingQueryEvent(this);
        this.registerBreadcrumbEvents(this);
    }

    /* Initialization */
    AOPADateRangePickerFacet.prototype.startDatePickerInit = function (coveoClass) {
        $(coveoClass.startDateField).datepicker({
            beforeShowDay: function (date) {
                var endDate = $(coveoClass.endDateField).val();
                if (endDate.length) {
                    if (date = new Date(startDate)) {
                        return [true];
                    } else {
                        return [false];
                    }
                } else return [true];
            }
        });
    };

    AOPADateRangePickerFacet.prototype.startDatePickerChange = function (coveoClass) {
        $(coveoClass.startDateField).on('change paste keyup', function (ev) {
            var start = $(coveoClass.startDateField).val();
            if (start.length) {
                if (Date.parse(start)) {
                    $(coveoClass.startDateField).datepicker('hide');
                    // Add to query and reprocess query
                    console.log('Start date parsed');
                    StartDate = start;
                    coveoClass.processQueryChange(coveoClass);
                } else {
                    console.log('Start date invalid');
                }
            } else {
                // The value in the field changed is now an empty string. Clear the state.
                coveoClass.clearStartDateBreadcrumb(coveoClass);
            }
        });
    };

    AOPADateRangePickerFacet.prototype.endDatePickerChange = function (coveoClass) {
        $(coveoClass.endDateField).on('change paste keyup', function (ev) {
            var end = $(coveoClass.endDateField).val();
            if (end.length) {
                if (Date.parse(end)) {
                    $(coveoClass.endDateField).datepicker('hide');
                    // Add to query and reprocess query
                    console.log('End date parsed');
                    EndDate = end;
                    coveoClass.processQueryChange(coveoClass);
                } else {
                    console.log('End date invalid');
                }
            } else {
                // The value in the field changed is now an empty string. Clear the state.
                coveoClass.clearEndDateBreadcrumb(coveoClass);
            }
        })
    };

...

In my example, I am providing the class itself (coveoClass) to each function so I can reference this.variables anywhere I need them. Notice that I encapsulate my this.variable calls in a jQuery wrapper so that I can use jQuery features like .datepicker(). This code is just a small portion of the full script file; keep reading and following along and I will provide the full script code at the end of the final blog post in this series.

Let’s Make Your Facet Available via the Experience Editor

The next step in integrating your component with Coveo Hive is adding it to a placeholder, so it can be inserted as a facet in the Experience Editor. You may have noticed the “Select a Rendering” window which lets you select which type of facet you would like to enter into your search page.

select a rendering window

We need to get out custom facet here so we can select it and add it to the search page; but how? By adding it to the placeholder! Of course, the facets on a search page are contained within a placeholder. Under Layout/Placeholder Settings/Coveo Hive/Facets, click the Facets placeholder item and add your custom component rendering item to the list in the Allowed Controls field. Save the item and voila, you should now see your custom component in the list to select! Once your facet is added and you click Save in the Experience Editor, the facet item will be created under your Coveo Global Parameters/Global Facets folder.

Status Check

If you did everything correctly, you should see your facet after adding it in the Experience Editor. It should be visible and should have your custom markup in it. If you do not see your facet, right click the facet above or below it and and locate the markup for the custom facet. If the div for your facet has a class named coveo-facet-empty, please see my blog post on this for information and steps to resolve.

The Next Step

Now that your facet is visible and can be interacted with somewhat, you are ready to move on to the next step. In the next (2nd) blog post, I will go over how to have your custom facet update the current Coveo query and Coveo state. In the 3rd blog and possibly final blog post in this series, I will go over how to add and manage breadcrumbs for your custom facet. Please post questions, issues or comments so that I can improve the blog posts!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s