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.