Creating a Custom Facet in Coveo Hive – Part Three

This is the 3rd (and most likely final) blog post in the Creating a Custom Facet in Coveo Hive series. If you’ve completed parts one and two, you’ve got a working custom facet that can be interacted with and can modify the current query and state, and update the search results that appear on the page. The last big piece of functionality, in my opinion, is setting up the facet breadcrumbs. This step is totally optional, and you should check with project management to make sure it falls within scope and the client requested to implement.

I struggled on this piece greatly because I wasn’t able to find any documentation on how to do this for a custom (Hive) facet. I had to contact Coveo Support, who then referred me to a Typescript file in their GitHub repositories which had the code for setting up breadcrumbs (albeit in a language I wasn’t too familiar with). I didn’t quite understand how the code worked, even after getting past the language barriers, and I wasn’t sure which parts I actually needed in my code. Thus, I created this blog post to answer some of those questions, for those that are in my same shoes at some point in the future.

What Functionality Do We Need?

Before thinking about exactly what code is required to implement breadcrumbs, it may help to think about what our goals are, on a high level. If you are unfamiliar with Coveo breadcrumbs, they are pieces of markup that are generated and added to the DOM when a Coveo facet value changes (an option is checked, a value is submitted, etc). By default they appear above the search results. This is often termed the “breadcrumb bar”.

breadcrumb bar

In the above example, I have a facet named Sites, which has an option named Blog, which was selected and thus, the breadcrumbs were populated. When a breadcrumb is clicked, it is removed from the DOM. In addition, if there is at least one breadcrumb in the DOM, a Clear button will appear near the breadcrumbs.

As you can see, there are two primary pieces of functionality that we will need to implement:

  1. Populating breadcrumbs – when we interact with our custom facet and update the query, we also want to add or update breadcrumbs.
  2. Clearing breadcrumbs – when the user decides to clear one breadcrumb or all, we need to handle the removal.

However, there is an important step that comes before this.

First, Let’s Register Our Events

Similar to how we did things in the previous blog post, we will first need to register the Coveo events before writing our custom code. So, in the constructor of our custom Coveo component class, where you’ve been calling your functions to register other events, you’ll want to add a line here:

 function AOPADateRangePickerFacet(element, options, bindings) {
     ...
     this.registerBreadcrumbEvents(this);
 }

Then, your function registerBreadcrumbEvents would look like this:

    AOPADateRangePickerFacet.prototype.registerBreadcrumbEvents = function (coveoClass) {
        Coveo.$(coveoClass.bindings.searchInterface.element).on('populateBreadcrumb', function (e, args) {
            coveoClass.handlePopulateBreadcrumb(args, coveoClass.bindings.searchInterface.element, coveoClass);
        });
        Coveo.$(coveoClass.bindings.searchInterface.element).on('clearBreadcrumb', function (e, args) {
            coveoClass.handleClearBreadcrumb(coveoClass);
        });
    };

Finally, you’ll want to create those two new class functions to actually do the heavy lifting. For now, they’ll be empty:

AOPADateRangePickerFacet.prototype.handlePopulateBreadcrumb = function (args, searchInterface, coveoClass) {
}

AOPADateRangePickerFacet.prototype.handleClearBreadcrumb = function (coveoClass) {
}

Populating Breadcrumbs

Now that the Coveo events have been registered and are being handled, let’s get into some in-depth coding. We have a few scenarios to consider for populating breadcrumbs:

  • Check if breadcrumb exists in the DOM.
    • Breadrcumb doesn’t exist, create it.
    • Breadcrumb does exist, update it.

There aren’t any out of the box functions or methods that Coveo provides to add a breadcrumb, update a breadcrumb, so the following code will be heavy jQuery development and DOM manipulation. First, we need to start with creating a new breadcrumb.

Creating a New Breadcrumb

While checking for breadcrumbs will always come before creating them, it’s important we write the code to create one beforehand. I’ll explain: in the create code, we will be adding various classes to the elements we are generating and adding to the DOM. We’re going to need to use those classes when we check for breadcrumbs, so let’s define those classes now.

I would recommend creating a new namespaced class function to handle adding a new breadcrumb. I’m going to name it addNewBreadcrumb. In this function, I pass in the following things:

  • args from the handlePopulateBreadcrumbs function.
  • date value to be added to the breadcrumb.
  • breadcrumbItemClass which is a class that I will add for every new breadcrumb item.
  • breadcrumbValueClass which is a class I will add for each breadcrumb value.
  • breadcrumbLabel – I do not believe my code is using this.
  • coveoClass – a reference to the Coveo component class

In addition, my addNewBreadcrumb class will also handle its click event, which will occur when a user clicks the breadcrumb item. We’ll get into this in more detail in the Clearing Breadcrumbs section. Here is the code for this new function:

    AOPADateRangePickerFacet.prototype.addNewBreadcrumb = function (args, date, breadcrumbItemClass, breadcrumbValueClass, breadcrumbLabel, coveoClass) {
        // Check for coveo-breadcrumb-items div and create if not there
        var itemsDiv = '';
        if ($('.coveo-breadcrumb-items').length < 1) {
            itemsDiv = document.createElement('div');
            Coveo.$(itemsDiv).addClass('coveo-breadcrumb-items');
        }

        var elem = document.createElement('div');
        Coveo.$(elem).addClass('coveo-facet-breadcrumb coveo-breadcrumb-item ' + breadcrumbItemClass);

        var title = document.createElement('span');
        Coveo.$(title).addClass('coveo-facet-breadcrumb-title');
        Coveo.$(title).text('Date Selection:');
        elem.appendChild(title);

        var values = document.createElement('span');
        Coveo.$(values).addClass('coveo-facet-breadcrumb-values');

        var value = document.createElement('span');
        Coveo.$(value).addClass('coveo-facet-breadcrumb-value coveo-selected ' + breadcrumbValueClass);
        Coveo.$(value).attr('title', date);

        var caption = document.createElement('span');
        Coveo.$(caption).addClass('coveo-facet-breadcrumb-caption');
        Coveo.$(caption).text(date);

        var clearSVG = ``;
        var clearIcon = document.createElement('span');
        Coveo.$(clearIcon).addClass('coveo-facet-breadcrumb-clear');
        Coveo.$(clearIcon).html(clearSVG);

        value.append(caption);
        value.append(clearIcon);
        values.append(value);
        elem.appendChild(values);

        if ($('.coveo-breadcrumb-items').length  -1) {
                console.log('clear start date click');
                coveoClass.clearStartDateBreadcrumb(coveoClass);
            } else {
                console.log('clear end date click');
                coveoClass.clearEndDateBreadcrumb(coveoClass);
            }
        });

        return elem;
    };

I’ll go through this long and complex code step by step:

  1. First we check if the container div for our new breadcrumb items exists in the DOM, and create it if not.
  2. Then, we go ahead and start creating the DOM elements to make our breadcrumb appear. I got most of these class names from looking at generated markup of breadcrumbs for other OOTB facets.
  3. Next, I copied the SVG markup, again from a breadcrumb of another facet, put it in here and added it to the DOM.
  4. Then, we append elements to their appropriate parent in the DOM.
  5. Finally, we have some code to handle the breadcrumb click event; again, will get into that more later.

Check if Breadcrumb Exists

If you’ve added the Breadcrumbs module to your search page, there should be a div above your search results. It is actually contained within the Coveo results section div (coveo-result-column). It’s hidden by default, until you interact with a facet to make breadcrumbs appear. Anyway, the breadcrumbs div should have a class of CoveoBreadcrumb and using this we can easily see if it has any breadcrumbs inside it.

Let’s head back to the handlePopulateBreadcrumb function and do our check:

    AOPADateRangePickerFacet.prototype.handlePopulateBreadcrumb = function (args, searchInterface, coveoClass) {
        var breadcrumbItem = '';
        var startDate = $(coveoClass.startDateField).val();
        var endDate = $(coveoClass.endDateField).val();
        var calendarBreadcrumbItemClass = 'breadcrumb-item-calendar';
        var startDateBreadcrumbValueClass = 'breadcrumb-value-start-date';
        var endDateBreadcrumbValueClass = 'breadcrumb-value-end-date'

        if (startDate.length) {
            if ($('.CoveoBreadcrumb').find('div.' + calendarBreadcrumbItemClass).length) {
                breadcrumbItem = coveoClass.checkExistingAndUpdateBreadcrumb(startDate, calendarBreadcrumbItemClass, startDateBreadcrumbValueClass, coveoClass);
            } else {
                breadcrumbItem = coveoClass.addNewBreadcrumb(args, startDate, calendarBreadcrumbItemClass, startDateBreadcrumbValueClass ,'Start Date', coveoClass);
            }
        }

        if (endDate.length) {
            if ($('.CoveoBreadcrumb').find('div.' + calendarBreadcrumbItemClass).length) {
                breadcrumbItem = coveoClass.checkExistingAndUpdateBreadcrumb(endDate, calendarBreadcrumbItemClass, endDateBreadcrumbValueClass, coveoClass);
            } else {
                breadcrumbItem = coveoClass.addNewBreadcrumb(args, endDate, calendarBreadcrumbItemClass, endDateBreadcrumbValueClass, 'End Date', coveoClass);
            }
        }
...

This code is getting the values of two class (“global”) properties, my start date and end date (entered by the user), and also defines some class names to be used for checking for breadcrumbs and adding or updating them. Before I check if a breadcrumb exists, I do a length value on my inputs to make sure the properties are not empty (this is a second layer of protection on top of the field validation that was present earlier in my code). If that passes, I check the breadcrumb container to see if it has any breadcrumbs; if so, update, otherwise add new.

Updating a Breadcrumb

Here, I get the breadcrumb item and try to find the span which would normally contain its value. Depending on whether that span is found, different code is needed. Here is the full code for the checkExistingAndUpdateBreadcrumb function:

    AOPADateRangePickerFacet.prototype.checkExistingAndUpdateBreadcrumb = function (date, breadcrumbItemClass, breadcrumbValueClass, coveoClass) {
        var breadcrumbItem = $('.CoveoBreadcrumb').find('div.' + breadcrumbItemClass);
        var valueSpan = breadcrumbItem.find('.coveo-facet-breadcrumb-value.' + breadcrumbValueClass);
        if (valueSpan.length) {
            valueSpan.attr('title', date);
            var captionSpan = valueSpan.find('span.coveo-facet-breadcrumb-caption');
            if (captionSpan.length) {
                captionSpan.text(date);
            }
        } else {
            // Add the new value.
            var values = breadcrumbItem.find('.coveo-facet-breadcrumb-values');
            var value = coveoClass.createNewBreadcrumbValue(date, breadcrumbValueClass);
            if (breadcrumbValueClass.indexOf('start-date') > -1) {
                // Adding start date so put it in front.
                values.prepend(value);
            } else {
                values.append(value);
            }

            // Bind click event of new value span
            Coveo.$(value).on('click', function () {
                if (breadcrumbValueClass.indexOf('start-date') > -1) {
                    console.log('clear start date click');
                    coveoClass.clearStartDateBreadcrumb(coveoClass);
                } else {
                    console.log('clear end date click');
                    coveoClass.clearEndDateBreadcrumb(coveoClass);
                }
            });
        }

        return breadcrumbItem[0];
    };

Similar to the add function, we need to bind the click event. Additionally, here’s the code for the createNewBreadcrumbValue function. I believe I meant for this to be reusable between the above (update) function and the add function, but it looks like that never happened. I encourage you to reuse as much code as possible to make it easier to read and understand.

    AOPADateRangePickerFacet.prototype.createNewBreadcrumbValue = function (date, breadcrumbValueClass) {
        var value = document.createElement('span');
        Coveo.$(value).addClass('coveo-facet-breadcrumb-value coveo-selected ' + breadcrumbValueClass);
        Coveo.$(value).attr('title', date);

        var caption = document.createElement('span');
        Coveo.$(caption).addClass('coveo-facet-breadcrumb-caption');
        Coveo.$(caption).text(date);

        var clearSVG = ``;
        var clearIcon = document.createElement('span');
        Coveo.$(clearIcon).addClass('coveo-facet-breadcrumb-clear');
        Coveo.$(clearIcon).html(clearSVG);

        value.append(caption);
        value.append(clearIcon);

        return value;
    };

Finishing Up Populating Breadcrumbs

Head back to the handlePopulateBreadcrumb function. We’ve got a couple things more to do. We need to check if the breadcrumb bar itself is invisible, and if so, make it visible. Then, send back the breadcrumbs we’ve added or updated to the args.breadcrumbs argument of the event.

        ...
        // Make breadcrumb div visible if currently invisible.
        if ($('.CoveoBreadcrumb').is(':hidden') && $('.CoveoBreadcrumb').children().length > 0) {
            $('.CoveoBreadcrumb').show();
        }

        if (breadcrumbItem !== '') {
            args.breadcrumbs = [{ element: breadcrumbItem }];
        }
    }

Clearing Breadcrumbs

Take a second and relax. The longer part of this process is pretty much over (minus testing of course). What I’m trying to say is, clearing the breadcrumbs requires much, much less code than populating them. In our handleClearBreadcrumb function that we created earlier, we can put something like this:

    AOPADateRangePickerFacet.prototype.handleClearBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.startDateField).val('');
        $(coveoClass.endDateField).val('');

        // Clear out state
        coveoClass.clearState(coveoClass);
    }

Essentially we need to do two things. First, clear out any values entered by the user in our custom facet. Then, clear out any values for our custom facet in Coveo state:

    AOPADateRangePickerFacet.prototype.clearState = function (coveoClass) {
        coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', []);
        coveoClass.bindings.queryController.executeQuery();
    }

Your breadcrumbs should respond to the state change by clearing out automatically. At this point, the Clear button should work and clear out all breadcrumbs, including ones generated by your custom facet.

There’s one more piece to this – remember the click events? Code for those is above in the update and add functions. In my case, I had to do some special clearing logic; here is the code those click events are running:

    AOPADateRangePickerFacet.prototype.clearStartDateBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.startDateField).val('');

        // Clear out state
        coveoClass.clearStartDateState(coveoClass);

        $('.breadcrumb-value-start-date').remove();
    }

    AOPADateRangePickerFacet.prototype.clearEndDateBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.endDateField).val('');

        // Clear out state
        coveoClass.clearEndDateState(coveoClass);

        $('.breadcrumb-value-end-date').remove();
    }
    AOPADateRangePickerFacet.prototype.clearStartDateState = function (coveoClass) {
        var resultDateValue = coveoClass.bindings.searchInterface.element.Coveostate.attributes["f:@aoparesultdate:range"];
        if (resultDateValue !== undefined && resultDateValue.length > 0) {
            var startDateUnix = resultDateValue[0];
            var endDateUnix = resultDateValue[1];
            var newArray = (endDateUnix === 0 ? [] : [0, endDateUnix]);

            coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', newArray);
            coveoClass.bindings.queryController.executeQuery();
        }
    }

    AOPADateRangePickerFacet.prototype.clearEndDateState = function (coveoClass) {
        var resultDateValue = coveoClass.bindings.searchInterface.element.Coveostate.attributes["f:@aoparesultdate:range"];
        if (resultDateValue !== undefined && resultDateValue.length > 0) {
            var startDateUnix = resultDateValue[0];
            var endDateUnix = resultDateValue[1];
            var newArray = (startDateUnix === 0 ? [] : [startDateUnix, 0]);

            coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', newArray);
            coveoClass.bindings.queryController.executeQuery();
        }
    }

Again, in my case I had to handle date ranges, so here you code could be much different, but all this is doing is clearing either the start or end date. The logic is, if I click the state date breadcrumb item, I only want to clear the start date from the Coveo state, so I would pass something like [0, XX.XXXXX (end date Unix timestamp)] to make sure my end date breadcrumb does not go away.

Status Check

You should now have a custom facet with breadcrumbs that are populated and can be cleared, either completely via the Clear button, or individually via clicking one.

Full Javascript

Here is the full, final Javascript. Unfortunately, it won’t be in the same order as I went through it. If you’re looking for the custom component markup, please see the first post in this series.

var AOPADateRangePickerFacet = (function (_super) {
    // TODO: Convert these global variables to this.variable at some point
    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);
            }
        })
    };

    AOPADateRangePickerFacet.prototype.registerStateAttribute = function (coveoClass) {
        Coveo.$(this.bindings.searchInterface.element).on("afterComponentsInitialization", function (e) {
            coveoClass.bindings.searchInterface.element.Coveostate.registerNewAttribute("f:@aoparesultdate:range", []);
        });
    };

    AOPADateRangePickerFacet.prototype.registerBuildingQueryEvent = function (coveoClass) {
        Coveo.$(this.bindings.searchInterface.element).on('buildingQuery', function (e, args) {
            coveoClass.handleBuildingQuery(e, args, coveoClass);
        });
    };

    AOPADateRangePickerFacet.prototype.registerBreadcrumbEvents = function (coveoClass) {
        Coveo.$(coveoClass.bindings.searchInterface.element).on('populateBreadcrumb', function (e, args) {
            coveoClass.handlePopulateBreadcrumb(args, coveoClass.bindings.searchInterface.element, coveoClass);
        });
        Coveo.$(coveoClass.bindings.searchInterface.element).on('clearBreadcrumb', function (e, args) {
            coveoClass.handleClearBreadcrumb(coveoClass);
        });
    };

    /* Custom Functions */
    AOPADateRangePickerFacet.prototype.processQueryChange = function (coveoClass) {
        // Update state
        var startDateUnix = (StartDate.length === 0 ? 0 : (new Date(StartDate).getTime()));
        var endDateUnix = (EndDate.length === 0 ? 0 : (new Date(EndDate).getTime()));
        var searchInterface = coveoClass.bindings.searchInterface.element;
        var newArray = (startDateUnix === 0 && endDateUnix === 0 ? [] : [startDateUnix, endDateUnix]);
        if (searchInterface.Coveostate !== undefined) {
            searchInterface.Coveostate.set('f:@aoparesultdate:range', newArray);
        }

        coveoClass.bindings.queryController.executeQuery();

        this.bindings.usageAnalytics.logSearchEvent({
            name: 'facetSelect',
            type: 'facet'
        });
    };

    AOPADateRangePickerFacet.prototype.handleBuildingQuery = function (e, args, coveoClass) {
        var dateField = $(coveoClass.startDateField).closest('div.CoveoAOPADateRangePickerFacet').attr('data-field');
        var newDateSet = coveoClass.getNewDateSet(coveoClass);
        if (newDateSet) {
            NewDateSet = newDateSet;
            StartDate = $(coveoClass.startDateField).val();
            EndDate = $(coveoClass.endDateField).val();

            // This becomes '@aoparesultdate>=2018/1/1' for example
            // check state for our custom field
            var resultDateValue = coveoClass.bindings.searchInterface.element.Coveostate.attributes["f:@aoparesultdate:range"];
            if (resultDateValue !== undefined && resultDateValue.length > 0) {
                var expressionString = dateField + newDateSet;
                args.queryBuilder.advancedExpression.add(expressionString);
            }
        }
    };

    AOPADateRangePickerFacet.prototype.handlePopulateBreadcrumb = function (args, searchInterface, coveoClass) {
        var breadcrumbItem = '';
        var startDate = $(coveoClass.startDateField).val();
        var endDate = $(coveoClass.endDateField).val();
        var calendarBreadcrumbItemClass = 'breadcrumb-item-calendar';
        var startDateBreadcrumbValueClass = 'breadcrumb-value-start-date';
        var endDateBreadcrumbValueClass = 'breadcrumb-value-end-date'

        if (startDate.length) {
            if ($('.CoveoBreadcrumb').find('div.' + calendarBreadcrumbItemClass).length) {
                breadcrumbItem = coveoClass.checkExistingAndUpdateBreadcrumb(startDate, calendarBreadcrumbItemClass, startDateBreadcrumbValueClass, coveoClass);
            } else {
                breadcrumbItem = coveoClass.addNewBreadcrumb(args, startDate, calendarBreadcrumbItemClass, startDateBreadcrumbValueClass ,'Start Date', coveoClass);
            }
        }

        if (endDate.length) {
            if ($('.CoveoBreadcrumb').find('div.' + calendarBreadcrumbItemClass).length) {
                breadcrumbItem = coveoClass.checkExistingAndUpdateBreadcrumb(endDate, calendarBreadcrumbItemClass, endDateBreadcrumbValueClass, coveoClass);
            } else {
                breadcrumbItem = coveoClass.addNewBreadcrumb(args, endDate, calendarBreadcrumbItemClass, endDateBreadcrumbValueClass, 'End Date', coveoClass);
            }
        }

        // Make breadcrumb div visible if currently invisible.
        if ($('.CoveoBreadcrumb').is(':hidden') && $('.CoveoBreadcrumb').children().length > 0) {
            $('.CoveoBreadcrumb').show();
        }

        if (breadcrumbItem !== '') {
            args.breadcrumbs = [{ element: breadcrumbItem }];
        }
    }

    AOPADateRangePickerFacet.prototype.handleClearBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.startDateField).val('');
        $(coveoClass.endDateField).val('');

        // Clear out state
        coveoClass.clearState(coveoClass);
    }

    AOPADateRangePickerFacet.prototype.getNewDateSet = function (coveoClass) {
        var startDateVal = $(coveoClass.startDateField).val();
        var endDateVal = $(coveoClass.endDateField).val();
        var newDateSet = '';
        var startDateYear = '';
        var startDateMonth = '';
        var startDateDay = '';
        var endDateYear = '';
        var endDateMonth = '';
        var endDateDay = '';
        var startDate = '';
        var endDate = '';

        // Reformat dates. Since Coveo JS framework seems to only accept in format yyyy/mm/dd?
        if (startDateVal.length) {
            startDate = new Date(startDateVal);
            startDateYear = startDate.getFullYear();
            startDateMonth = startDate.getMonth() + 1;
            startDateDay = startDate.getDate();

            if (startDateMonth.toString().length < 2) startDateMonth = '0' + startDateMonth;
            if (startDateDay.toString().length < 2) startDateDay = '0' + startDateDay;
        };

        if (endDateVal.length) {
            endDate = new Date(endDateVal);
            endDateYear = endDate.getFullYear();
            endDateMonth = endDate.getMonth() + 1;
            endDateDay = endDate.getDate();

            if (endDateMonth.toString().length < 2) endDateMonth = '0' + endDateMonth;
            if (endDateDay.toString().length =' + reformattedStartDate;
        } else if (!startDateVal.length && endDateVal.length) {
            newDateSet = ' -1) {
                // Adding start date so put it in front.
                values.prepend(value);
            } else {
                values.append(value);
            }

            // Bind click event of new value span
            Coveo.$(value).on('click', function () {
                if (breadcrumbValueClass.indexOf('start-date') > -1) {
                    console.log('clear start date click');
                    coveoClass.clearStartDateBreadcrumb(coveoClass);
                } else {
                    console.log('clear end date click');
                    coveoClass.clearEndDateBreadcrumb(coveoClass);
                }
            });
        }

        return breadcrumbItem[0];
    };

    AOPADateRangePickerFacet.prototype.addNewBreadcrumb = function (args, date, breadcrumbItemClass, breadcrumbValueClass, breadcrumbLabel, coveoClass) {
        // Check for coveo-breadcrumb-items div and create if not there
        var itemsDiv = '';
        if ($('.coveo-breadcrumb-items').length < 1) {
            itemsDiv = document.createElement('div');
            Coveo.$(itemsDiv).addClass('coveo-breadcrumb-items');
        }

        var elem = document.createElement('div');
        Coveo.$(elem).addClass('coveo-facet-breadcrumb coveo-breadcrumb-item ' + breadcrumbItemClass);

        var title = document.createElement('span');
        Coveo.$(title).addClass('coveo-facet-breadcrumb-title');
        Coveo.$(title).text('Date Selection:');
        elem.appendChild(title);

        var values = document.createElement('span');
        Coveo.$(values).addClass('coveo-facet-breadcrumb-values');

        var value = document.createElement('span');
        Coveo.$(value).addClass('coveo-facet-breadcrumb-value coveo-selected ' + breadcrumbValueClass);
        Coveo.$(value).attr('title', date);

        var caption = document.createElement('span');
        Coveo.$(caption).addClass('coveo-facet-breadcrumb-caption');
        Coveo.$(caption).text(date);

        var clearSVG = ``;
        var clearIcon = document.createElement('span');
        Coveo.$(clearIcon).addClass('coveo-facet-breadcrumb-clear');
        Coveo.$(clearIcon).html(clearSVG);

        value.append(caption);
        value.append(clearIcon);
        values.append(value);
        elem.appendChild(values);

        if ($('.coveo-breadcrumb-items').length  -1) {
                console.log('clear start date click');
                coveoClass.clearStartDateBreadcrumb(coveoClass);
            } else {
                console.log('clear end date click');
                coveoClass.clearEndDateBreadcrumb(coveoClass);
            }
        });

        return elem;
    };

    AOPADateRangePickerFacet.prototype.clearStartDateBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.startDateField).val('');

        // Clear out state
        coveoClass.clearStartDateState(coveoClass);

        $('.breadcrumb-value-start-date').remove();
    }

    AOPADateRangePickerFacet.prototype.clearEndDateBreadcrumb = function (coveoClass) {
        // Clear out textboxes
        $(coveoClass.endDateField).val('');

        // Clear out state
        coveoClass.clearEndDateState(coveoClass);

        $('.breadcrumb-value-end-date').remove();
    }

    AOPADateRangePickerFacet.prototype.clearState = function (coveoClass) {
        coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', []);
        coveoClass.bindings.queryController.executeQuery();
    }

    AOPADateRangePickerFacet.prototype.clearStartDateState = function (coveoClass) {
        var resultDateValue = coveoClass.bindings.searchInterface.element.Coveostate.attributes["f:@aoparesultdate:range"];
        if (resultDateValue !== undefined && resultDateValue.length > 0) {
            var startDateUnix = resultDateValue[0];
            var endDateUnix = resultDateValue[1];
            var newArray = (endDateUnix === 0 ? [] : [0, endDateUnix]);

            coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', newArray);
            coveoClass.bindings.queryController.executeQuery();
        }
    }

    AOPADateRangePickerFacet.prototype.clearEndDateState = function (coveoClass) {
        var resultDateValue = coveoClass.bindings.searchInterface.element.Coveostate.attributes["f:@aoparesultdate:range"];
        if (resultDateValue !== undefined && resultDateValue.length > 0) {
            var startDateUnix = resultDateValue[0];
            var endDateUnix = resultDateValue[1];
            var newArray = (startDateUnix === 0 ? [] : [startDateUnix, 0]);

            coveoClass.bindings.searchInterface.element.Coveostate.set('f:@aoparesultdate:range', newArray);
            coveoClass.bindings.queryController.executeQuery();
        }
    }

    AOPADateRangePickerFacet.prototype.createNewBreadcrumbValue = function (date, breadcrumbValueClass) {
        var value = document.createElement('span');
        Coveo.$(value).addClass('coveo-facet-breadcrumb-value coveo-selected ' + breadcrumbValueClass);
        Coveo.$(value).attr('title', date);

        var caption = document.createElement('span');
        Coveo.$(caption).addClass('coveo-facet-breadcrumb-caption');
        Coveo.$(caption).text(date);

        var clearSVG = ``;
        var clearIcon = document.createElement('span');
        Coveo.$(clearIcon).addClass('coveo-facet-breadcrumb-clear');
        Coveo.$(clearIcon).html(clearSVG);

        value.append(caption);
        value.append(clearIcon);

        return value;
    };

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

Completion

coveo-hive-date-selection-facet-final-appearance

coveo-hive-date-selection-breadcrumb-final-appearnce

Congratulations, you’ve built your own custom facet using Coveo for Sitecore Hive Framework! This blog post turned out way longer than I thought it would be, but I hope this helped you or at least sent you in the right direction. If anything is inaccurate, missing or not working, leave a comment and I will do my best to fix and respond. Thank you!

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