We now need to plug the Elasticsearch query that was built in the previous blog into a JavaScript client that has been loaded on the search page as described in another earlier blog. So a few lines of JavaScript will be required here, and it will need to work on my three environments:
- Sandbox Drupal site connecting to local Elasticsearch server.
- Sandbox static site connecting to local Elasticsearch server.
- Production static site connecting to production Elasticsearch server.
The JavaScript will use the elasticsearch.js API library which can be downloaded from here. We need to make our theme aware of our JavaScript files and we only wanted them loading on the node search page route. Our JavaScript file will be called beezee_elastic.js, which is the name of my theme (beezee8) and elastic obviously.
beezee8.info.yml
libraries:
- 'beezee8/elastic-library'
beezee8.libraries.yml
elastic-library: js: js/elasticsearch-js/elasticsearch.min.js: {} js/beezee/beezee_elastic.js: {}
beezee8.theme
<?php
/**
* @param $variables
*/
function beezee8_preprocess_page(&$variables) {
// If we are on the search page, load the JS search client api
// and our implementation to Elasticsearch
if (\Drupal::routeMatch()->getRouteName() == 'search.view_node_search') {
$variables['#attached']['library'][] = 'beezee8/elastic-library';
}
}
?>
beezee_elastic.js
(function ($, Drupal) { var elastic_once; function BeezeeElastic() { if (!elastic_once) { elastic_once = true; // Get the params from the url var pageURL = window.location.search.substring(1); var URLVariables = pageURL.split('&'); var keys; i = 0; while (i < URLVariables.length) { var ParameterName = URLVariables[i].split('='); if (ParameterName[0] == 'keys') { keys = ParameterName[1]; } i++; } // Did we get anything from the URL? If not bail, otherwise do the search. // We shouldn't get to here so don't show a message to user. if (!keys) { console.log('Invalid keys'); return; } // Add the search key to the h1 selector $("h1").append(Drupal.t(" for ") + keys); // Endpoint defined during instantiation of API Client var client = new elasticsearch.Client({ host: 'http://meedjum.test:9200/' /*,*/ /* log: 'trace' */ }); client.search({ index: 'elasticsearch_index_badzilla_meedjum_staticcontent', body: { query: { function_score: { query: { query_string: { query: keys, fields: [ "title", "*body", "term" ], default_operator: "OR" } }, functions: [ { linear: { "created": { origin: "now", offset: "365d", scale: "1460d", decay: 0.5 } } } ] } }, highlight: { number_of_fragments: 1, pre_tags: ["<strong>"], post_tags: ["</strong>"], fragment_size: 400, no_match_size: 400, phrase_limit: 1, fields: { "*body": {}, "title": {}, "term": {} } }, size: 10 } }, response); /* Process the response from Elastic */ function response(err, resp, status) { // Are we logged in? If so our jQuery calls will be slightly different due to additional DOM stuff // for non anonymous // These are our targets for injecting the content on the page. Markup is Bootstrap 3 var h1_find; var h2_find; if ($(".user-logged-in").length) { h1_find = "h1 + nav:last-child"; h2_find = "h1 + nav + h2:last-child"; } else { h1_find = "h1"; h2_find = "h1 + h2:last-child"; } var hits = resp.hits.hits; if (resp.hits.total == 0) { $(h1_find).after("<h3>" + Drupal.t("Your search yielded no results.") + "</h3>"); } else { $(h1_find).after("<h2>" + Drupal.t("Search results") + "</h2>"); var inject = "<ol>"; $.each(hits, function (key, value) { // Url, title and Body var prefix = "<li><h3>"; var suffix = "</li>"; var title = "<a href=\"" + value._source.url[0] + "\">" + value.highlight.title[0] + "</a></h3>"; // do we have a book body or a paragraph body? var body; if (typeof value.highlight.blog_body != 'undefined') { body = value.highlight.blog_body[0]; } else { body = value.highlight.body; } body = "<p>" + body + "</p>"; inject += prefix + title + body + suffix; // Add the tag if (typeof value.highlight.term != 'undefined') { inject += "<p>" + Drupal.t("Tag:") + " " + value.highlight.term + "</p>"; } // Author and date var gb_obj = new Date(value._source.created[0] * 1000); var gb_date = ('0' + gb_obj.getDate()).slice(-2) + "/" + ('0' + (gb_obj.getMonth() + 1)).slice(-2) + "/" + gb_obj.getFullYear() + " " + "-" + " " + gb_obj.getHours() + ":" + gb_obj.getMinutes(); inject += "<p>" + value._source.author[0] + " " + "-" + " " + gb_date; }); inject += "</ol>"; $(h2_find).after(inject); } } } } Drupal.behaviors.beezee_elastic = { attach: function (context, settings) { BeezeeElastic(); } }; })(jQuery, Drupal);
I'd like to think the code is self-explanatory, but here are a few words for additional clarification.
The first block of code is processing the URL params. We are expecting the ?keys={search_phrase}. The search phrase needs to be injected into the Elasticsearch query later.
The client object is returned from me instantiating the Elasticsearch API. I needed a parameter of the Elasticsearch endpoint which is currently hardcoded. It will work for my two sandbox environments (Drupal and static) but not for production. I will have to come up with a solution to rewrite the endpoint during the deployment process!
The client.search call is our query created earlier, and I have added the search term from the URL. The Elasticsearch API provides two mechanisms for this call - it can return a promise or invoke a callback. I opted for the latter.
The response function is the callback and it handles the Elastic response. My first task is to set the selectors where I will inject the results - note that the DOM is different whether I am logged into my sandbox Drupal or not - so my injection anchor points are different dependent upon this circumstance.
I add the title, body text, tag, author and published date to my output, concatenating them together, and then inject them into the content area of the screen around ordered list tags.
The next result is very similar visually to the default Drupal core search with the addition of my blog tags, although of course it can be styled differently if wanted.

The script works well on both the Drupal and static sites in sandbox. The screenshot above shows a search on the word nginx. At the moment I haven't built in a paginator, but that could come along later.