Integrate Algolia Autocomplete widget in WebHelp transformation
How to add Algolia autocomplete to the template and put it into Webhelp output?
We'll replace the search form from WebHelp and put an autocomplete from Algolia.
- In the template root directory create a "xslt" folder
-
Create custom.xsl file in "xslt" directory
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:whc="http://www.oxygenxml.com/webhelp/components" xmlns="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xs" version="2.0"> <xsl:import href="labels-show.xsl"/> <!-- Generate <div id="autocomplete"> for Algolia autocomplete integration. --> <xsl:template match="whc:webhelp_search_input" mode="copy_template"> <!-- EXM-36737 - Context node used for messages localization --> <xsl:param name="i18n_context" tunnel="yes" as="element()*"/> <div> <xsl:call-template name="generateComponentClassAttribute"> <xsl:with-param name="compClass">wh_search_input</xsl:with-param> </xsl:call-template> <!-- Copy attributes --> <xsl:copy-of select="@* except @class"/> <xsl:variable name="localizedSearch"> <xsl:choose> <xsl:when test="exists($i18n_context)"> <xsl:for-each select="$i18n_context[1]"> <xsl:call-template name="getWebhelpString"> <xsl:with-param name="stringName" select="'webhelp.search'"/> </xsl:call-template> </xsl:for-each> </xsl:when> <xsl:otherwise>Search</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="localizedSearchQuery"> <xsl:choose> <xsl:when test="exists($i18n_context)"> <xsl:for-each select="$i18n_context[1]"> <xsl:call-template name="getWebhelpString"> <xsl:with-param name="stringName" select="'search.query'"/> </xsl:call-template> </xsl:for-each> </xsl:when> <xsl:otherwise>Search query</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="search_comp_output"> <form id="searchForm" method="get" role="search" action="{concat($PATH2PROJ, 'search', $OUTEXT)}"> <div id="autocomplete"> <!--<input type="search" placeholder="{$localizedSearch} " class="wh_search_textfield" id="textToSearch" name="searchQuery" aria-label="{$localizedSearchQuery}" required="required"/> <button type="submit" class="wh_search_button" aria-label="{$localizedSearch}"><span class="search_input_text"><xsl:value-of select="$localizedSearch"/></span></button>--> </div> </form> </xsl:variable> <xsl:call-template name="outputComponentContent"> <xsl:with-param name="compContent" select="$search_comp_output"/> <xsl:with-param name="compName" select="local-name()"/> </xsl:call-template> </div> </xsl:template> </xsl:stylesheet>
-
At the end of our main.js file add this:
const navigateToSearch = (state) => { const path = document.querySelector('meta[name="wh-path2root"]').content + "search.html?searchQuery=" + state.collections[0].items[state.activeItemId].title; window.location = path; }; // If container with id autocomplete is present in the DOM then replace it with Algolia autocomplete. if (document.getElementById("autocomplete")) { autocomplete({ id: "webhelp-algolia-search", container: "#autocomplete", placeholder: "Search", initialState: { query: window.location.href.includes("search.html?searchQuery=") ? decodeURI( window.location.href.substring( window.location.href.indexOf("=") + 1, window.location.href.length ) ) : "", }, // Actions to perform when user submits the query. onSubmit(state) { // Check if it's not empty if (state.query != "") { if (state.activeItemId == null) { const path = document.querySelector('meta[name="wh-path2root"]').content + "search.html?searchQuery=" + state.state.query; window.location = path; } else { navigateToSearch(state); } } }, // Actions to perform to get suggestions for user. getSources({ query }) { return [ { sourceId: "topics", // Return URL of the selected item. getItemUrl({ item }) { return item.objectID; }, // Get suggestions. getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: indexName, query, params: { hitsPerPage: 5, attributesToSnippet: ["title:10", "content:30"], snippetEllipsisText: "…", }, }, ], }); }, // HTML template that is used in order to display suggestions. templates: { item({ item, components, html, state }) { return html`<div class="aa-ItemWrapper" onclick="${() => { navigateToSearch(state); }}" > <div class="aa-ItemContent"> <div class="aa-ItemContentBody"> <div class="aa-ItemContentTitle"> ${components.Highlight({ hit: item, attribute: "title", })} </div> <div class="aa-ItemContentDescription"> ${components.Snippet({ hit: item, attribute: "content", })} </div> </div> </div> </div>`; }, }, }, ]; }, // Navigator that handles user redirections when only keyboard(arrows and enter button) is used. navigator: { navigate({ state }) { navigateToSearch(state); }, }, }); }
-
It should look like this:
import { autocomplete, getAlgoliaResults } from "@algolia/autocomplete-js"; import "@algolia/autocomplete-theme-classic"; import algoliaConfig from "./../algolia-config.json"; // Check if disableWebHelpDefaultSearchEngine() method is present. if (WebHelpAPI.disableWebHelpDefaultSearchEngine) { WebHelpAPI.disableWebHelpDefaultSearchEngine(); } // Connect to Algolia App with Search-only API key. const algoliasearch = require("algoliasearch"); const searchClient = algoliasearch( algoliaConfig.appId, algoliaConfig.searchOnlyKey ); const indexName = algoliaConfig.indexName; // Create a object that implements performSearchOperation() and onPageChangedHandler() methods so it can be used by WebHelp. const algoliaSearch = { // Method that is called when Submit is performed. performSearchOperation(query, successHandler, errorHandler) { // Search for hits for the given query. let result; if (query.includes("label:")) { let tag = query.split(":")[query.split(":").length - 1]; let facetFilters = `_tags:${tag}`; result = searchClient .initIndex(indexName) .search("", { facetFilters: [facetFilters] }); } else { result = searchClient.initIndex(indexName).search(query); } result .then((obj) => { // Extract data from Promise and create a SearchMeta object with extracted data. const meta = new WebHelpAPI.SearchMeta( "Algolia", obj.nbHits, obj.page, obj.hitsPerPage, obj.nbPages, query, false, false, false, false, false, false ); // Extract data from Promise and create SearxhDocument object with extracted data. const documents = obj.hits.map((it) => { return new WebHelpAPI.SearchDocument( it.objectID, it.title, it._snippetResult.content.value, [], 0, [], it._highlightResult.content.matchedWords ); }); // Pass the extracted data to SearchResult so it can be displayed by WebHelp. successHandler(new WebHelpAPI.SearchResult(meta, documents)); }) .catch((error) => { console.log(error); }); }, // Actions to do when page of results is changed. onPageChangedHandler(pageToShow, query, successHandler, searchFailed) { // Get results on the next page using the given by user query. const result = searchClient.initIndex(indexName).search(query, { page: pageToShow, }); result .then((obj) => { const meta = new WebHelpAPI.SearchMeta( "Algolia", obj.nbHits, obj.page, obj.hitsPerPage, obj.nbPages, query, false, false, false, false, false, false ); const documents = obj.hits.map((it) => { return new WebHelpAPI.SearchDocument( it.objectID, it.title, it._snippetResult.content.value, [], 0, [], it._highlightResult.content.matchedWords ); }); successHandler(new WebHelpAPI.SearchResult(meta, documents)); }) .catch((error) => { console.log(error); }); }, }; // Check if setCustomSearchEngine() method is present in order to change it to Algolia engine. if (WebHelpAPI.setCustomSearchEngine) { WebHelpAPI.setCustomSearchEngine(algoliaSearch); } const navigateToSearch = (state) => { const path = document.querySelector('meta[name="wh-path2root"]').content + "search.html?searchQuery=" + state.collections[0].items[state.activeItemId].title; window.location = path; }; // If container with id autocomplete is present in the DOM then replace it with Algolia autocomplete. if (document.getElementById("autocomplete")) { autocomplete({ id: "webhelp-algolia-search", container: "#autocomplete", placeholder: "Search", initialState: { query: window.location.href.includes("search.html?searchQuery=") ? decodeURI( window.location.href.substring( window.location.href.indexOf("=") + 1, window.location.href.length ) ) : "", }, // Actions to perform when user submits the query. onSubmit(state) { // Check if it's not empty if (state.query != "") { if (state.activeItemId == null) { const path = document.querySelector('meta[name="wh-path2root"]').content + "search.html?searchQuery=" + state.state.query; window.location = path; } else { navigateToSearch(state); } } }, // Actions to perform to get suggestions for user. getSources({ query }) { return [ { sourceId: "topics", // Return URL of the selected item. getItemUrl({ item }) { return item.objectID; }, // Get suggestions. getItems() { return getAlgoliaResults({ searchClient, queries: [ { indexName: indexName, query, params: { hitsPerPage: 5, attributesToSnippet: ["title:10", "content:30"], snippetEllipsisText: "…", }, }, ], }); }, // HTML template that is used in order to display suggestions. templates: { item({ item, components, html, state }) { return html`<div class="aa-ItemWrapper" onclick="${() => { navigateToSearch(state); }}" > <div class="aa-ItemContent"> <div class="aa-ItemContentBody"> <div class="aa-ItemContentTitle"> ${components.Highlight({ hit: item, attribute: "title", })} </div> <div class="aa-ItemContentDescription"> ${components.Snippet({ hit: item, attribute: "content", })} </div> </div> </div> </div>`; }, }, }, ]; }, // Navigator that handles user redirections when only keyboard(arrows and enter button) is used. navigator: { navigate({ state }) { navigateToSearch(state); }, }, }); }
- All you have to do now is to generate your documentation and enjoy the results!