Insights

Using Coveo With RequireJS And Sitecore 7

Part 1: Making Coveo AMD-compatible

Fishtank is a strong advocate of the AMD (Asynchronous Module Definition) JavaScript API. We're also very enthusiastic about Coveo Enterpise Search. So when the opportunity presented itself, it was a no brainer for us to adapt their excellent JS Search UI to be AMD-friendly. Here's what we were aiming to accomplish:

  • Increase page-speed by loading all JavaScript asynchronously
  • Simplify implementation by explicitly declaring dependencies
  • Re-use implementation code / customizations through modules (across multiple Coveo instances)
  • Share site's existing libraries with Coveo (jquery, handlerbars, etc)
  • And many other benefits

Setting Up RequireJS To Load Coveo

RequireJS is our AMD loader of choice. We'll use requirejs-setup.js to configure Coveo for RequireJS.

We're using the concepts of shims and dependencies in RequireJS to make everything play nice. A shim magically make non-AMD JS libraries AMD-friendly. And deps declare a modules dependencies that will be loaded first.


// file: /scripts/require-setup.js

// This is written to load before RequireJS. 
// When RequireJS loads it will use the "require" variable as a config.

// This is using the April 2014 release of Coveo's JS UI

var require = {

	"shim": {
        "handlebars": { "exports": "Handlebars" },
        "coveojs": {
            "exports": "Coveo",
            "deps": ["jquery", "handlebars", "underscore", "coveo-culture-en", "coveo-expressionutils", "coveo-l10n", "coveo-jstz", "coveo-jqueryjsonp", "coveo-jquerycolorbox"]
        },
        "underscore" : { "exports": "_"},
        "coveo-jqueryjsonp": ["jquery"],
        "coveo-jquerycolorbox": ["jquery"]
    },
    
	"paths": {        
		
		// Libraries used throughout the site, including by Coveo 
		
		/* 
		  Note - Coveo will install with it"s own jQuery, 
		  Handlebars and Underscore libraries.  We"re replacing them
		  with our site's existing libraries.  
		*/
		"jquery": "/scripts/vendors/jquery-1.10.2.min",
        "handlebars": "/scripts/vendors/handlebars-v1.3.0",
        "underscore": "/scripts/vendors/underscore-1.5.2",

        // Libraries used exclusively by Coveo.  
		
		/*
		  Note - Prefixed with "coveo-" for clarity.  We've also left these
		  JS files in their default location as installed by Coveo for Sitecore.
		  Change the paths appropriately to reflect your JS file's location.
		*/        
		"coveojs": "/Coveo/js/CoveoJsSearch",
        "coveo-globalize": "/Coveo/js/globalize",
		"coveo-culture-en" : "/Coveo/js/culture/en",
        "coveo-expressionutils": "/Coveo/js/expressionutils",
        "coveo-l10n": "/Coveo/js/l10n",
        "coveo-jstz": "/Coveo/js/jstz",
        "coveo-jqueryjsonp": "/Coveo/js/jquery.jsonp-2.4.0",
        "coveo-jquerycolorbox": "/Coveo/js/jquery.colorbox-min",
    }
};

This file is the most important piece in this post.

We turned CoveoJsSearch.js into the coveojs module and declared its dependencies under deps. Note that jQuery plugins like jquerycolorbox declare jquery as their dependency.

Now calling require(["coveojs"], function(Coveo){ ... }) will load the coveojs and its 10 supporting JavaScript libraries asynchronously.

Moving Coveo's JavaScript Into A Module

We've made Coveo's JavaScript AMD-friendly but not it's implementation code.

Next step is porting Coveo's in-line JavaScript found in CoveoSearch.ascx into a module. In Sitecore 7, Coveo populates JavaScript variables using server data. The following is an partial-excerpt of CoveoSearch.ascx from Coveo's April 2014 release. Note the code-behind values mixed with the in-line JavaScript.


<!-- CoveoSearch.ascx -->

<script type="text/javascript">    
	
	// Excerpt
	
	var indexSourceName = "<%= IndexSourceName %>";
    var restEndpointUri = "<%= Parameters["RestUri"] %>";
    var indexSourceName = "<%= IndexSourceName %>";
    var hiddenExpression = "<%= HiddenExpression %>";
    var boostExpressions = "<%= BoostExpressions %>";
    var externalSources = new Array();
    <% foreach (string value in ExternalContentSources) { %>
        externalSources.push('<%=value%>');
    <% } %>
	
	// Excerpt
	
	<% if (FilterCulture) { %>
		args.queryBuilder.constantExpression.add(getFilterExpression(externalSources, '<%= ToCoveoFieldName("language") %>', '<%= LanguageName %>'));
	<% }
		if (LatestOnly) { %>
		args.queryBuilder.constantExpression.add(getFilterExpression(externalSources, '<%= ToCoveoFieldName("_latestVersion") %>', '1'));
	<% } %>

</script>

To move this in-line JavaScript into a module, we need access to these .NET values.

My solution - create a #CoveoOptions element and store all the Coveo values I need to access as encoded JSON in data-attribute. Then use $('#CoveoOptions').data('options') and JSON.parse() to access them inside my Coveo implementation module.

Pulling It All Together

Here is a simplified HTML page.


<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Fishtank + Coveo + AMD JS</title>
		
		<!-- Scripts -->		
		<script src="/scripts/require-setup.js"></script>
		<script src="//requirejs.org/docs/release/2.1.14/minified/require.js"></script>
		<script src="/scripts/app.js"></script>

	</head>
	<body>

		<div id="CoveoOptions" 
			  data-options="{
			  'culture' : '<%= CultureName %>',
			  'restEndpoint' : '<%= Parameters["RestUri"] %>',
			  'indexSourceName' : '<%= IndexSourceName %>',
			  'hiddenExpression' : '<%= HiddenExpression %>',
			  'boostExpression' : '<%= BoostExpressions %>',
			  'externalSources' : '<%= String.Join(",", ExternalContentSources) %>',
			  'enableClientSideLogging' : '< %=  EnableClientSideLogging %>',
			  'filterCulture' : '<%=  FilterCulture %>',
			  'latestOnly': '<%= LatestOnly %>',
			  'languageName': '<%= LanguageName %>',
			  'databaseName' : '<%= Sitecore.Context.Database.Name.ToLower() %>',
			  'fields' : {
				'language': '<%= ToCoveoFieldName("language") %>',
				'_path': '<%= ToCoveoFieldName("_path") %>',
				'_latestVersion': '<%= ToCoveoFieldName("_latestVersion") %>',
				'templateid': '<%= ToCoveoFieldName("templateId") %>',
				'databasename': '<%= ToCoveoFieldName("databasename") %>'
			  }    
			}"	 
		></div>
		
		<div id="Search" class="CoveoSearchInterface">		
			<!-- COVEO IMPLEMENTATION GOES HERE -->		
		</div>
		
	</body>
</html>

This is the /scripts/app.js referenced above. It loads the coveojs module and any other modules it needs.


// file: /scripts/app.js

require(['coveojs'], function (Coveo) {
    
	var $ = Coveo.$;
	
	// Execute our code on DOM-ready
	$(function() {
		
		// Get our server-side options`into our JS
		var options = JSON.parse(
			$('#CoveoOptions').data('options')
		);
		
		// Our 
		$('#search').coveo('init', {
			SearchInterface: {
				autoTriggerQuery: false,
				hideUntilFirstQuery: false,
				enableHistory: true,
				//We're using our 'options'
				hiddenExpression: getHiddenExpression(
						options.indexSourceName, options.externalSources, options.hiddenExpression, options.boostExpressions
					)
			}
		});

	});
});

What's Coming In Part 2

I hope this helped get you started.

In Part 2 we'll stub out a Sitecore + Coveo + RequireJS project with the following:

  • Implementation module
  • Customization module
  • CoveoHelper module
  • Dynamically loading languages w/ JavaScript
  • GitHub repository

Please leave feel free to leave questions and comments.

This post was authored using Markdown for Sitecore.

👋 Hey Sitecore Enthusiasts!

Sign up to our bi-weekly newsletter for a bite-sized curation of valuable insight from the Sitecore community.

What’s in it for you?

  • Stay up-to-date with the latest Sitecore news
  • New to Sitecore? Learn tips and tricks to help you navigate this powerful tool
  • Sitecore pro? Expand your skill set and discover troubleshooting tips
  • Browse open careers and opportunities
  • Get a chance to be featured in upcoming editions
  • Learn our secret handshake
  • And more!
Sitecore Snack a newsletter by Fishtank Consulting
 

Meet Dan Cruickshank

President | Sitecore MVP x 11

Dan is the founder of Fishtank. He's a multi-time Sitecore MVP and Coveo MVP award winner. Outside of technology, he is widely considered to be a top 3 father (routinely receiving "Father of the Year" accolades from his family) and past his prime on the basketball court.

Connect with Dan