@searchstax-inc/searchstudio-ux-js
v1.0.6
Published
Library to build Site Search page
Downloads
546
Readme
sitesearch-ux-js
Library to build Site Search page
changelog
changelog is documented at CHANGELOG.md
Installation
npm install following package
npm install --save @searchstax-inc/searchstudio-ux-js
Usage
After importing Searchstax class a new instance needs to be created:
const searchstax = new Searchstax();
Initialization
Initialization object needs to be of type: ISearchstaxConfig
interface ISearchstaxConfig {
sessionId?: string; // session id that is used for tracking. if not defined random value will be generated
language: string; // language code. Example: 'en'
searchURL: string; // Site Search select endpoint
suggesterURL: string; //Site Search suggest endpoint
trackApiKey: string; // Api key used for tracking events
searchAuth: string; // Authentication value. based on authType it's either a token value or basic auth value
authType: "token" | "basic"; // Type of authentication
autoCorrect?: boolean; // if set to true it will autoCorrect misspelled words. Default is false
router?: IRouterConfig; // optional object containing router settings
analyticsBaseUrl?: string; // url for analytics calls
hooks?: {
// optional object that provides various hook options
beforeSearch?: (props: ISearchObject) => ISearchObject | null; // this function gets called before firing search. searchProps are being passed as a property and can be modified, if passed along further search will execute with modified properties, if null is returned then event gets canceled and search never fires.
afterSearch?: (results: ISearchstaxParsedResult[]) => ISearchstaxParsedResult[]; // this function gets called after firing search and before rendering. It needs to return array of results that are either modified or untouched.
};
}
Initialization example
searchstax.initialize({
language: "en",
searchURL: "",
suggesterURL: "",
trackApiKey: "",
searchAuth: "",
sessionId: "yourSessionId",
authType: "basic",
router: {
enabled: true,
routeName: "searchstax",
title: (result: ISearchObject) => {
return "Search results for: " + result.query;
},
ignoredKeys: [],
},
analyticsBaseUrl: 'https://analytics-us-east.searchstax.com',
hooks: {
beforeSearch: function (props: ISearchObject) {
const propsCopy = { ...props };
return propsCopy;
},
afterSearch: function (results: ISearchstaxParsedResult[]) {
const copy = [...results];
return copy;
},
}
});
Initial layout
Our base theme is designed with this layout in mind but it is optional as all widgets have id parameters and can be attached to any element.
<div class="searchstax-page-layout-container">
<div id="searchstax-input-container"></div>
<div class="search-details-container">
<div id="search-feedback-container"></div>
<div id="search-sorting-container"></div>
</div>
<div class="searchstax-page-layout-facet-result-container">
<div class="searchstax-page-layout-facet-container">
<div id="searchstax-facets-container"></div>
</div>
<div class="searchstax-page-layout-result-container">
<div id="searchstax-external-promotions-layout-container"></div>
<div id="searchstax-results-container"></div>
<div id="searchstax-related-searches-container"></div>
<div id="searchstax-pagination-container"></div>
</div>
</div>
</div>
widgets
Following widgets are available:
advanced data flow subscriptions
Following widgets are available:
Answer Widget
Initialization properties
a. id of container where widget will be rendered
b. Answer widget config object of type: ISearchstaxAnswerConfig
example of answer widget initialization with minimum options
searchstax.addAnswerWidget("searchstax-answer-container", {});
example of answer widget initialization with various options
searchstax.addAnswerWidget("searchstax-answer-container", {
templates: {
main: {
template: `
{{#shouldShowAnswer}}
<div class="searchstax-answer-container">
<div class="searchstax-answer-title">Answer</div>
<div class="searchstax-answer-description">
{{answer}}
</div>
</div>
{{/shouldShowAnswer}}
`,
answerContainerId: ``,
},
},
});
Input Widget
Initialization properties
a. id of container where widget will be rendered
b. Input widget config object of type: ISearchstaxSearchInputConfig
example of input widget initialization with minimum options
searchstax.addSearchInputWidget("searchstax-input-container", {
suggestAfterMinChars: 3,
});
example of input widget initialization with various options
searchstax.addSearchInputWidget("searchstax-input-container", {
suggestAfterMinChars: 3,
hooks: {
afterAutosuggest: function (result: ISearchstaxSuggestResponse) {
const copy = { ...result };
return copy;
},
beforeAutosuggest: function (props: ISearchstaxSuggestProps) {
// gets suggestProps, if passed along further autosuggest will execute, if null then event gets canceled
// props can be modified and passed along
const propsCopy = { ...props };
// propsCopy.term = propsCopy.term + '222';
return propsCopy;
},
},
templates: {
mainTemplate: {
template: `
<div class="searchstax-search-input-container">
<div class="searchstax-search-input-wrapper">
<input type="text" id="searchstax-search-input" class="searchstax-search-input" placeholder="SEARCH FOR..." aria-label="Search" />
<button class="searchstax-spinner-icon" id="searchstax-search-input-action-button" aria-label="search" role="button"></button>
</div>
</div>
`,
searchInputId: "searchstax-search-input"
}
autosuggestItemTemplate: {
template: `
<div class="searchstax-autosuggest-item-term-container">{{{term}}}</div>
`,
}
},
});
Result Widget
Initialization properties
a. id of container where widget will be rendered
b. Result widget config object of type: ISearchstaxSearchResultsConfig
example of results widget initialization with minimum options
searchstax.addSearchResultsWidget("searchstax-results-container", {});
example of result widget initialization with various options
searchstax.addSearchResultsWidget("searchstax-results-container", {
templates: {
mainTemplate: {
template: `
<div class="searchstax-search-results-container">
<div class="searchstax-search-results" id="searchstax-search-results"></div>
</div>
`,
searchResultsContainerId: "searchstax-search-results",
},
searchResultTemplate: {
template: `
<div class="searchstax-search-result {{#thumbnail}} has-thumbnail {{/thumbnail}}">
<a href="{{url}}" data-searchstax-unique-result-id="{{uniqueId}}" class="searchstax-result-item-link searchstax-result-item-link-wrapping" tabindex="0">
{{#promoted}}
<div class="searchstax-search-result-promoted"></div>
{{/promoted}}
{{#ribbon}}
<div class="searchstax-search-result-ribbon">
{{{ribbon}}}
</div>
{{/ribbon}}
{{#thumbnail}}
<img src="{{thumbnail}}" alt="" class="searchstax-thumbnail">
{{/thumbnail}}
<div class="searchstax-search-result-title-container">
<h5 class="searchstax-search-result-title">{{{title}}}</h5>
</div>
{{#paths}}
<p class="searchstax-search-result-common">
{{paths}}
</p>
{{/paths}}
{{#description}}
<p class="searchstax-search-result-description searchstax-search-result-common">
{{{description}}}
</p>
{{/description}}
{{#unmappedFields}}
{{#isImage}}
<div class="searchstax-search-result-image-container">
<img src="{{value}}" alt="" class="searchstax-result-image">
</div>
{{/isImage}}
{{^isImage}}
<p class="searchstax-search-result-common">
{{{value}}}
</p>
{{/isImage}}
{{/unmappedFields}}
</a>
</div>
`,
searchResultUniqueIdAttribute: "data-searchstax-unique-result-id"
},
noSearchResultTemplate: {
template: `
{{#searchExecuted}}
<div class="searchstax-no-results" aria-live="polite">
Showing <strong>no results</strong> for <strong>"{{ searchTerm }}"</strong>
<br>
{{#spellingSuggestion}}
<span> Did you mean <a href="#" class="searchstax-suggestion-term" onclick="searchCallback('{{ spellingSuggestion }}')" aria-label="suggested term: {{ spellingSuggestion }}">{{ spellingSuggestion }}</a>?</span>
{{/spellingSuggestion}}
</div>
<ul class="searchstax-no-results-list" aria-live="polite">
<li>Try searching for search related terms or topics. We offer a wide variety of content to help you get the information you need.</li>
<li>Lost? Click on the ‘X” in the Search Box to reset your search.</li>
</ul>
{{/searchExecuted}}
`
}
},
hooks: {
afterLinkClick: function (result: ISearchstaxParsedResult) {
// gets result that was clicked, if passed along further functions will execute, if null then event gets canceled
const propsCopy = { ...result };
return propsCopy;
},
},
});
example of result widget initialization with infinite scroll enabled. This will render pagination widget with infiniteScrollTemplate
searchstax.addSearchResultsWidget("searchstax-results-container", {
templates: {
mainTemplate: {
template: `
<div class="searchstax-search-results-container">
<div class="searchstax-search-results" id="searchstax-search-results"></div>
</div>
`,
searchResultsContainerId: "searchstax-search-results",
},
searchResultTemplate: {
template: `
<div class="searchstax-search-result {{#thumbnail}} has-thumbnail {{/thumbnail}}">
<a href="{{url}}" data-searchstax-unique-result-id="{{uniqueId}}" class="searchstax-result-item-link searchstax-result-item-link-wrapping" tabindex="0">
{{#promoted}}
<div class="searchstax-search-result-promoted"></div>
{{/promoted}}
{{#ribbon}}
<div class="searchstax-search-result-ribbon">
{{{ribbon}}}
</div>
{{/ribbon}}
{{#thumbnail}}
<img src="{{thumbnail}}" alt="" class="searchstax-thumbnail">
{{/thumbnail}}
<div class="searchstax-search-result-title-container">
<h5 class="searchstax-search-result-title">{{{title}}}</h5>
</div>
{{#paths}}
<p class="searchstax-search-result-common">
{{paths}}
</p>
{{/paths}}
{{#description}}
<p class="searchstax-search-result-description searchstax-search-result-common">
{{{description}}}
</p>
{{/description}}
{{#unmappedFields}}
{{#isImage}}
<div class="searchstax-search-result-image-container">
<img src="{{value}}" alt="" class="searchstax-result-image">
</div>
{{/isImage}}
{{^isImage}}
<p class="searchstax-search-result-common">
{{{value}}}
</p>
{{/isImage}}
{{/unmappedFields}}
</a>
</div>
`,
searchResultUniqueIdAttribute: "data-searchstax-unique-result-id"
},
noSearchResultTemplate: {
template: `
{{#searchExecuted}}
<div class="searchstax-no-results" aria-live="polite">
Showing <strong>no results</strong> for <strong>"{{ searchTerm }}"</strong>
<br>
{{#spellingSuggestion}}
<span> Did you mean <a href="#" class="searchstax-suggestion-term" onclick="searchCallback('{{ spellingSuggestion }}')" aria-label="suggested term: {{ spellingSuggestion }}">{{ spellingSuggestion }}</a>?</span>
{{/spellingSuggestion}}
</div>
<ul class="searchstax-no-results-list" aria-live="polite">
<li>Try searching for search related terms or topics. We offer a wide variety of content to help you get the information you need.</li>
<li>Lost? Click on the ‘X” in the Search Box to reset your search.</li>
</ul>
{{/searchExecuted}}
`
}
},
renderMethod: 'infiniteScroll',
hooks: {
afterLinkClick: function (result: ISearchstaxParsedResult) {
// gets result that was clicked, if passed along further functions will execute, if null then event gets canceled
const propsCopy = { ...result };
return propsCopy;
},
},
});
Pagination Widget
Initialization properties
a. id of container where widget will be rendered
b. Pagination widget config object of type: ISearchstaxSearchPaginationConfig
example of pagination widget initialization with minimum options
searchstax.addPaginationWidget("searchstax-pagination-container", {});
example of pagination widget initialization with various options
searchstax.addPaginationWidget("searchstax-pagination-container", {
templates: {
mainTemplate: {
template: `
{{#results.length}}
<div class="searchstax-pagination-container">
<div class="searchstax-pagination-content">
<a class="searchstax-pagination-previous {{#isFirstPage}}disabled{{/isFirstPage}}" id="searchstax-pagination-previous" tabindex="0" aria-label="Previous Page">< Previous</a>
<div class="searchstax-pagination-details">
{{startResultIndex}} - {{endResultIndex}} of {{totalResults}}
</div>
<a class="searchstax-pagination-next {{#isLastPage}}disabled{{/isLastPage}}" id="searchstax-pagination-next" tabindex="0" aria-label="Next Page">Next ></a>
</div>
</div>
{{/results.length}}
`,
previousButtonClass: "searchstax-pagination-previous",
nextButtonClass: "searchstax-pagination-next"
},
infiniteScrollTemplate: {
template: `
{{#results.length}}
<div class="searchstax-pagination-container">
<div class="searchstax-pagination-content">
<a class="searchstax-pagination-previous {{#isFirstPage}}disabled{{/isFirstPage}}" id="searchstax-pagination-previous" tabindex="0" aria-label="Previous Page">< Previous</a>
<div class="searchstax-pagination-details">
{{startResultIndex}} - {{endResultIndex}} of {{totalResults}}
</div>
<a class="searchstax-pagination-next {{#isLastPage}}disabled{{/isLastPage}}" id="searchstax-pagination-next" tabindex="0" aria-label="Next Page">Next ></a>
</div>
</div>
{{/results.length}}
`,
loadMoreButtonClass: "searchstax-pagination-load-more"
}
},
});
Facets Widget
Initialization properties
a. id of container where widget will be rendered
b. Facets widget config object of type: ISearchstaxSearchFacetsConfig
example of facets widget initialization with minimum options
searchstax.addFacetsWidget("searchstax-facets-container", {
facetingType: "and",
itemsPerPageDesktop: 3,
itemsPerPageMobile: 99,
});
example of facets widget initialization with various options
searchstax.addFacetsWidget("searchstax-facets-container", {
facetingType: "and",
itemsPerPageDesktop: 3,
itemsPerPageMobile: 99,
templates: {
mainTemplateDesktop: {
template: `
{{#hasResultsOrExternalPromotions}}
<div class="searchstax-facets-container-desktop"></div>
{{/hasResultsOrExternalPromotions}}
`,
facetsContainerId: "",
},
mainTemplateMobile: {
template: `
<div class="searchstax-facets-pills-container">
<div class="searchstax-facets-pills-selected">
</div>
</div>
<div class="searchstax-facets-mobile-overlay {{#overlayOpened}} searchstax-show{{/overlayOpened}}" >
<div class="searchstax-facets-mobile-overlay-header">
<div class="searchstax-facets-mobile-overlay-header-title">Filter By</div>
<div class="searchstax-search-close" tabindex="0" aria-label="close overlay" role="button"></div>
</div>
<div class="searchstax-facets-container-mobile"></div>
<button class="searchstax-facets-mobile-overlay-done">Done</button>
</div>
`,
facetsContainerClass: `searchstax-facets-container-mobile`,
closeOverlayTriggerClasses: ["searchstax-facets-mobile-overlay-done","searchstax-search-close",],
filterByContainerClass: `searchstax-facets-pills-container`,
selectedFacetsContainerClass: `searchstax-facets-pills-selected`,
},
showMoreButtonContainerTemplate: {
template: `
<div class="searchstax-facet-show-more-container">
{{#showingAllFacets}}
<div class="searchstax-facet-show-less-button searchstax-facet-show-button" tabindex="0">less</div>
{{/showingAllFacets}}
{{^showingAllFacets}}
<div class="searchstax-facet-show-more-button searchstax-facet-show-button" tabindex="0">more {{onShowMoreLessClick}}</div>
{{/showingAllFacets}}
</div>
`,
showMoreButtonClass: `searchstax-facet-show-more-container`,
},
facetItemContainerTemplate: {
template: `
<div>
<div class="searchstax-facet-title-container">
<div class="searchstax-facet-title" aria-label="Facet group: {{label}}" tabindex="0">
{{label}}
</div>
<div class="searchstax-facet-title-arrow active"></div>
</div>
<div class="searchstax-facet-values-container"></div>
</div>
`,
facetListTitleContainerClass: `searchstax-facet-title-container`,
facetListContainerClass: `searchstax-facet-values-container`,
},
clearFacetsTemplate: {
template: `
{{#shouldShow}}}
<div class="searchstax-facets-pill searchstax-clear-filters searchstax-facets-pill-clear-all" tabindex="0" role="button">
<div class="searchstax-facets-pill-label">Clear Filters</div>
</div>
{{/shouldShow}}
`,
containerClass: `searchstax-facets-pill-clear-all`,
},
facetItemTemplate: {
template: `
<div class="searchstax-facet-input">
<input type="checkbox" class="searchstax-facet-input-checkbox" {{#disabled}}disabled{{/disabled}} {{#isChecked}}checked{{/isChecked}} aria-label="{{value}} {{count}}" tabindex="0"/>
</div>
<div class="searchstax-facet-value-label">{{value}}</div>
<div class="searchstax-facet-value-count">({{count}})</div>
`,
inputCheckboxClass: `searchstax-facet-input-checkbox`,
checkTriggerClasses: ["searchstax-facet-value-label","searchstax-facet-value-count",],
},
filterByTemplate: {
template: `
<div class="searchstax-facets-pill searchstax-facets-pill-filter-by" tabindex="0" role="button" >
<div class="searchstax-facets-pill-label">Filter By</div>
</div>
`,
containerClass: `searchstax-facets-pill-filter-by`,
},
selectedFacetsTemplate: {
template: `
<div class="searchstax-facets-pill searchstax-facets-pill-facets" tabindex="0" role="button">
<div class="searchstax-facets-pill-label">{{value}} ({{count}})</div>
<div class="searchstax-facets-pill-icon-close"></div>
</div>
`,
containerClass: `searchstax-facets-pill-facets`,
},
},
});
SearchFeedback Widget
Initialization properties
a. id of container where widget will be rendered
b. SearchFeedback widget config object of type: ISearchstaxSearchFeedbackConfig
example of search feedback widget initialization with minimum options
searchstax.addSearchFeedbackWidget("search-feedback-container", {});
example of search feedback widget initialization with various options
searchstax.addSearchFeedbackWidget("search-feedback-container", {
templates: {
main: {
template: `
{{#searchExecuted}}
<a href="#searchstax-search-results" class="searchstax-skip">Skip to results section</a>
<h4 class="searchstax-feedback-container">
{{#hasResults}}
<span> Showing <b>{{startResultIndex}} - {{endResultIndex}}</b> </span> of <b>{{totalResults}}</b> results {{#searchTerm}} for "<b>{{searchTerm}}</b>" {{/searchTerm}}
<div class="searchstax-feedback-container-suggested">
{{#autoCorrectedQuery}}
Search instead for <a href="#" aria-label="Search instead for: {{originalQuery}}" class="searchstax-feedback-original-query">{{originalQuery}}</a>
{{/autoCorrectedQuery}}
</div>
{{/hasResults}}
</h4>
{{/searchExecuted}}
`,
originalQueryClass: `searchstax-feedback-original-query`
}
},
});
RelatedSearches widget
Initialization properties
a. id of container where widget will be rendered
b. RelatedSearches widget config object of type: ISearchstaxRelatedSearchesConfig
example of search feedback widget initialization with minimum options
searchstax.addRelatedSearchesWidget("searchstax-related-searches-container", {
relatedSearchesURL: "URL",
relatedSearchesAPIKey: "KEY"
})
example of search feedback widget initialization with various options
searchstax.addRelatedSearchesWidget("searchstax-related-searches-container", {
relatedSearchesURL: "URL",
relatedSearchesAPIKey: "KEY",
templates: {
main: {
template: `
{{#hasRelatedSearches}}
<div class="searchstax-related-searches-container" id="searchstax-related-searches-container">
Related searches: <span id="searchstax-related-searches"></span>
{{#relatedSearches}}
<span class="searchstax-related-search">
</span>
{{/relatedSearches}}
</div>
{{/hasRelatedSearches}}
`,
relatedSearchesContainerClass: `searchstax-related-search`,
},
relatedSearch: {
template: `
<span class="searchstax-related-search searchstax-related-search-item" aria-label="Related search: {{related_search}}" tabindex="0">
{{ related_search }}{{^last}}<span>,</span>{{/last}}
</span>
`,
relatedSearchContainerClass: `searchstax-related-search-item`,
},
},
});
ExternalPromotions widget
Initialization properties
a. id of container where widget will be rendered
b. ExternalPromotions widget config object of type: ISearchstaxExternalPromotionsConfig
example of search feedback widget initialization with minimum options
searchstax.addExternalPromotionsWidget("searchstax-external-promotions-layout-container", {})
example of search feedback widget initialization with various options
searchstax.addExternalPromotionsWidget("searchstax-external-promotions-layout-container", {
templates: {
mainTemplate: {
template: `
{{#hasExternalPromotions}}
<div class="searchstax-external-promotions-container" id="searchstax-external-promotions-container">
External promotions go here
</div>
{{/hasExternalPromotions}}
`,
externalPromotionsContainerId: `searchstax-external-promotions-container`,
},
externalPromotion: {
template: `
<div class="searchstax-external-promotion searchstax-search-result">
<div class="icon-elevated"></div>
{{#url}}
<a href="{{url}}" data-searchstax-unique-result-id="{{uniqueId}}" class="searchstax-result-item-link"></a>
{{/url}}
<div class="searchstax-search-result-title-container">
<span class="searchstax-search-result-title">{{name}}</span>
</div>
{{#description}}
<p class="searchstax-search-result-description searchstax-search-result-common">
{{description}}
</p>
{{/description}}
{{#url}}
<p class="searchstax-search-result-description searchstax-search-result-common">
{{url}}
</p>
{{/url}}
</div>
`,
},
},
});
Sorting Widget
Initialization properties
a. id of container where widget will be rendered
b. Sorting widget config object of type: ISearchstaxSearchSortingConfig
example of sorting widget initialization with minimum options
searchstax.addSearchSortingWidget("search-sorting-container", {});
example of sorting widget initialization with various options
searchstax.addSearchSortingWidget("search-sorting-container", {
templates: {
main: {
template: `
{{#searchExecuted}}
{{#hasResultsOrExternalPromotions}}
<div class="searchstax-sorting-container">
<label class="searchstax-sorting-label" for="searchstax-search-order-select">Sort By</label>
<select id="searchstax-search-order-select" class="searchstax-search-order-select" >
{{#sortOptions}}
<option value="{{key}}">
{{value}}
</option>
{{/sortOptions}}
</select>
</div>
{{/hasResultsOrExternalPromotions}}
{{/searchExecuted}}
`,
selectId: `searchstax-search-order-select`
}
},
});
Template overrides
Templates use mustache templating. For more info see https://github.com/janl/mustache.js
STYLING
scss styles can be imported from searchstudio-ux-js
@import './../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/scss/mainTheme.scss';
css can be taken from
./../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css
Data Flow Subscriptions
All variables are observables that have these functions:
$observable.subscribe((var) => {})
$observable.setValue(value)
$observable.getValue()
Loading change subscription
subscribing to loading event observable is available at
searchstax.dataLayer.$loadingChange.subscribe((data) => {})
searchstax.dataLayer.$loadingChange.setValue(data)
const data = searchstax.dataLayer.$loadingChange.getValue()
$loadingChange observable is of type: boolean
Search metadata subscription
subscribing to search metadata event observable is available at
searchstax.dataLayer.$searchResultsMetadata.subscribe((data) => {})
searchstax.dataLayer.$searchResultsMetadata.setValue(data)
const data = searchstax.dataLayer.$searchResultsMetadata.getValue()
$searchResultsMetadata observable is of type: ISearchstaxSearchMetadata and can be used for custom pagination implementation along with afterSearch hook
Search pagination data subscription
subscribing to pagination data event observable is available at
searchstax.dataLayer.$paginationData.subscribe((data) => {})
searchstax.dataLayer.$paginationData.setValue(data)
const data = searchstax.dataLayer.$paginationData.getValue()
$paginationData observable is of type: IPaginationData and can be used for custom pagination implementation along with afterSearch hook
Data Calculations
At any moment data calculation object is accessible with various variables aimed to help build templates in a cleaner way.
Data calculations
Data calculations can be accessed through
searchstax.dataLayer.parsedData.data
parsedData.data is of type: ISearchstaxParsedData