@searchstax-inc/searchstudio-ux-vue
v1.0.2
Published
Library to build Site Search page
Downloads
52
Readme
sitesearch-ux-vue
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-vue
Add following code to <head>
<script type="text/javascript">
var _msq = _msq || []; //declare object
var analyticsBaseUrl = 'https://analytics-us-east.searchstax.co';
(function () {
var ms = document.createElement('script');
ms.type = 'text/javascript';
ms.src = 'https://static.searchstax.co/studio-js/v3/js/studio-analytics.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ms, s);
})();
</script>
Usage
After importing SearchstaxWrapper component needs to wrap all other components:
<SearchstaxWrapper
:language="sampleConfig.language"
:searchURL="sampleConfig.searchURL"
:suggesterURL="sampleConfig.suggesterURL"
:trackApiKey="sampleConfig.trackApiKey"
:searchAuth="sampleConfig.searchAuth"
:authType="sampleConfig.authType"
:router="sampleConfig.router"
:beforeSearch="sampleConfig.hooks.beforeSearch" // callback function to intercept search object before search is fired
:afterSearch="sampleConfig.hooks.afterSearch" // callback function to handle results after search is fired
>
// other components will go there
</SearchstaxWrapper>
Initialization
Initialization object needs to be of type: ISearchstaxConfig
Initialization example
sampleConfig = {
language: "en",
searchURL: "",
suggesterURL: "",
trackApiKey: "",
searchAuth: "",
authType: "basic",
router: {
enabled: true,
routeName: "searchstax",
title: (result: ISearchObject) => {
return "Search results for: " + result.query;
},
ignoredKeys: [],
},
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.
<SearchstaxWrapper
:language="sampleConfig.language"
:searchURL="sampleConfig.searchURL"
:suggesterURL="sampleConfig.suggesterURL"
:trackApiKey="sampleConfig.trackApiKey"
:searchAuth="sampleConfig.searchAuth"
:authType="sampleConfig.authType"
:router="sampleConfig.router"
:beforeSearch="sampleConfig.hooks.beforeSearch" // callback function to intercept search object before search is fired
:afterSearch="sampleConfig.hooks.afterSearch" // callback function to handle results after search is fired
>
<template #default>
<div class="searchstax-page-layout-container">
<SearchstaxInputWidget
:afterAutosuggest="afterAutosuggest"
:beforeAutosuggest="beforeAutosuggest"
:suggestAfterMinChars="3"
>
</SearchstaxInputWidget>
<div class="search-details-container">
<SearchstaxSearchFeedbackWidget></SearchstaxSearchFeedbackWidget>
<SearchstaxSortingWidget></SearchstaxSortingWidget>
</div>
<div class="searchstax-page-layout-facet-result-container">
<div class="searchstax-page-layout-facet-container">
<SearchstaxFacetsWidget
:facetingType="'or'"
:itemsPerPageDesktop="3"
:itemsPerPageMobile="99"
>
</SearchstaxFacetsWidget>
</div>
<div class="searchstax-page-layout-result-container">
<div id="searchstax-external-promotions-layout-container"></div>
<SearchstaxResultWidget :afterLinkClick="afterLinkClick">
</SearchstaxResultWidget>
<div id="searchstax-related-searches-container"></div>
<SearchstaxPaginationWidget>
</SearchstaxPaginationWidget>
</div>
</div>
</div>
</template>
</SearchstaxWrapper>
widgets
Following widgets are available:
Input Widget
example of input widget initialization with minimum options
<SearchstaxInputWidget
:afterAutosuggest="afterAutosuggest"
:beforeAutosuggest="beforeAutosuggest"
:suggestAfterMinChars="3"
></SearchstaxInputWidget>
example of input widget initialization with template override
<SearchstaxInputWidget
:afterAutosuggest="afterAutosuggest"
:beforeAutosuggest="beforeAutosuggest"
:suggestAfterMinChars="3"
>
<template #input="{ suggestions, onMouseLeave, onMouseOver, onMouseClick }">
<div class="searchstax-search-input-wrapper">
<input
type="text"
id="searchstax-search-input"
class="searchstax-search-input"
placeholder="SEARCH FOR..."
/>
<div
class="searchstax-autosuggest-container"
:class="{ 'hidden': suggestions.length === 0 }"
@mouseleave="onMouseLeave"
>
<div
class="searchstax-autosuggest-item"
v-for="suggestion in suggestions"
:key="suggestion.term"
>
<div
class="searchstax-autosuggest-item-term-container"
v-html="suggestion.term"
@mouseover="onMouseOver(suggestion)"
@click.stop="onMouseClick()"
></div>
</div>
</div>
<button
class="searchstax-spinner-icon"
id="searchstax-search-input-action-button"
>
</button>
</div>
</template>
</SearchstaxInputWidget>
Result Widget
example of results widget initialization with minimum options
<SearchstaxResultWidget :afterLinkClick="afterLinkClick"></SearchstaxResultWidget>
example of results widget initialization with infinite scroll behavior
<SearchstaxResultWidget :afterLinkClick="afterLinkClick" :renderMethod="'infiniteScroll'"></SearchstaxResultWidget>
example of result widget initialization with template override
<SearchstaxResultWidget :afterLinkClick="afterLinkClick">
<template #results="{ searchResults, resultClicked }">
<div
class="searchstax-search-results"
v-if="searchResults && searchResults.length"
>
<div
class="searchstax-search-result"
:class="{ 'has-thumbnail': searchResult.thumbnail }"
:key="searchResult.uniqueId"
v-for="searchResult in searchResults"
>
<div
v-if="searchResult.promoted"
class="searchstax-search-result-promoted"
></div>
<a
v-if="searchResult.url"
:href="searchResult.url"
:data-searchstax-unique-result-id="searchResult.uniqueId"
@click="resultClicked(searchResult, $event)"
class="searchstax-result-item-link"
></a>
<div
v-if="searchResult.ribbon"
class="searchstax-search-result-ribbon"
> {{ searchResult.ribbon }} </div>
<img
v-if="searchResult.thumbnail"
:src="searchResult.thumbnail"
class="searchstax-thumbnail"
/>
<div class="searchstax-search-result-title-container">
<span class="searchstax-search-result-title">{{ searchResult.title }}</span>
</div>
<p
v-if="searchResult.paths"
class="searchstax-search-result-common"
> {{ searchResult.paths }} </p>
<p
v-if="searchResult.description"
class="searchstax-search-result-description searchstax-search-result-common"
> {{ searchResult.description }} </p>
<div
:key="unmappedField.key"
v-for="unmappedField in searchResult.unmappedFields"
>
<div
v-if="unmappedField.isImage && typeof unmappedField.value === 'string'"
class="searchstax-search-result-image-container"
>
<img
:src="unmappedField.value"
class="searchstax-result-image"
/>
</div>
<div v-else>
<p class="searchstax-search-result-common"> {{ unmappedField.value }} </p>
</div>
</div>
</div>
</div>
</template>
</SearchstaxResultWidget>
Pagination Widget
example of pagination widget initialization with minimum options
<SearchstaxPaginationWidget></SearchstaxPaginationWidget>
example of pagination widget initialization with various options
<template #pagination="{ paginationData, previousPage, nextPage }">
<div
class="searchstax-pagination-container"
v-if="paginationData"
>
<div class="searchstax-pagination-content">
<a
class="searchstax-pagination-previous"
:disabled="paginationData?.isFirstPage"
@click="previousPage"
id="searchstax-pagination-previous"
> < Previous </a>
<div class="searchstax-pagination-details"> {{ paginationData?.startResultIndex }} - {{
paginationData?.endResultIndex }} of {{ paginationData?.totalResults }} </div>
<a
class="searchstax-pagination-next"
:disabled="paginationData?.isLastPage"
@click="nextPage"
id="searchstax-pagination-next"
>Next ></a>
</div>
</div>
</template>
example of pagination widget initialization with infinite scroll template override
<template #infiniteScroll="{ nextPage }">
<div className="searchstax-pagination-container"
<a
class="searchstax-pagination-load-more"
@click="nextPage"
>Load More override</a>
</div>
</template>
Facets Widget
example of facets widget initialization with minimum options
<SearchstaxFacetsWidget
:facetingType="'or'"
:itemsPerPageDesktop="3"
:itemsPerPageMobile="99"
></SearchstaxFacetsWidget>
example of facets widget initialization with template overrides
<SearchstaxFacetsWidget
:facetingType="'or'"
:itemsPerPageDesktop="3"
:itemsPerPageMobile="99"
>
<template
#desktopFacets="{ facetsTemplateDataDesktop, isNotDeactivated, toggleFacetGroup, isChecked, selectFacet, showMoreLessDesktop, facetContainers }"
>
<div
v-if="facetsTemplateDataDesktop?.hasResultsOrExternalPromotions"
class="searchstax-facets-container-desktop"
>
<div
v-for="facet in facetsTemplateDataDesktop.facets"
:key="facet"
class="searchstax-facet-container"
:class="{ 'active': isNotDeactivated(facet.name) }"
>
<div>
<div
class="searchstax-facet-title-container"
@click="toggleFacetGroup(facet.name)"
>
<div class="searchstax-facet-title"> {{ facet.label }} </div>
<div class="searchstax-facet-title-arrow active"></div>
</div>
<div class="searchstax-facet-values-container">
<div
v-for="(facetValue, key) in facet.values"
:key="facetValue.value + facetValue.parentName"
class="searchstax-facet-value-container"
:class="{ 'searchstax-facet-value-disabled': facetValue.disabled }"
:ref="el => { facetContainers[key + facet.name] = el }"
>
<div class="searchstax-facet-input">
<input
type="checkbox"
class="searchstax-facet-input-checkbox"
:checked="isChecked(facetValue)"
:disabled="facetValue.disabled"
@click="selectFacet(key + facet.name, $event, facetValue, true)"
/>
</div>
<div
class="searchstax-facet-value-label"
@click="selectFacet(key + facet.name, $event, facetValue, false)"
>{{ facetValue.value }}</div>
<div
class="searchstax-facet-value-count"
@click="selectFacet(key + facet.name, $event, facetValue, false)"
>({{ facetValue.count }})</div>
</div>
<div
class="searchstax-facet-show-more-container"
v-if="facet.hasMoreFacets"
>
<div
class="searchstax-facet-show-more-container"
@click="showMoreLessDesktop($event, facet)"
>
<div
v-if="facet.showingAllFacets"
class="searchstax-facet-show-less-button searchstax-facet-show-button"
>less</div>
<div
v-else
class="searchstax-facet-show-more-button searchstax-facet-show-button"
>more</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<template
#mobileFacets="{ facetsTemplateDataMobile, selectedFacetsCheckboxes, isNotDeactivated, toggleFacetGroup, isChecked, selectFacet, showMoreLessDesktop, facetContainers, openOverlay, unselectFacet, unselectAll, closeOverlay }"
>
<div
class="searchstax-facets-container-mobile"
v-if="facetsTemplateDataMobile?.hasResultsOrExternalPromotions"
>
<div class="searchstax-facets-pills-container">
<div
class="searchstax-facets-pill searchstax-facets-pill-filter-by"
@click="openOverlay"
>
<div class="searchstax-facets-pill-label">Filter By</div>
</div>
<div class="searchstax-facets-pills-selected">
<div
class="searchstax-facets-pill searchstax-facets-pill-facets"
v-for="facet in selectedFacetsCheckboxes"
:key="facet.value"
@click="unselectFacet(facet)"
>
<div class="searchstax-facets-pill-label">{{ facet.value }} ({{ facet.count }})</div>
<div class="searchstax-facets-pill-icon-close"></div>
</div>
</div>
<div
class="searchstax-facets-pill searchstax-clear-filters searchstax-facets-pill-clear-all"
v-if="selectedFacetsCheckboxes.length"
@click="unselectAll"
>
<div class="searchstax-facets-pill-label">Clear Filters</div>
</div>
</div>
<div
class="searchstax-facets-mobile-overlay"
:class="{ 'searchstax-show': facetsTemplateDataMobile?.overlayOpened }"
>
<div class="searchstax-facets-mobile-overlay-header">
<div class="searchstax-facets-mobile-overlay-header-title">Filter By</div>
<div
class="searchstax-search-close"
@click="closeOverlay"
></div>
</div>
<div class="searchstax-facets-container-mobile">
<div
v-for="facet in facetsTemplateDataMobile?.facets"
:key="facet"
class="searchstax-facet-container"
:class="{ 'active': isNotDeactivated(facet.name) }"
>
<div>
<div
class="searchstax-facet-title-container"
@click="toggleFacetGroup(facet.name)"
>
<div class="searchstax-facet-title"> {{ facet.label }} </div>
<div class="searchstax-facet-title-arrow active"></div>
</div>
<div class="searchstax-facet-values-container">
<div
v-for="(facetValue, key) in facet.values"
:key="facetValue.value + facetValue.parentName"
class="searchstax-facet-value-container"
:class="{ 'searchstax-facet-value-disabled': facetValue.disabled }"
:ref="el => { facetContainers[key + facet.name] = el }"
>
<div class="searchstax-facet-input">
<input
type="checkbox"
class="searchstax-facet-input-checkbox"
:checked="isChecked(facetValue)"
:disabled="facetValue.disabled"
@click="selectFacet(key + facet.name, $event, facetValue, true)"
/>
</div>
<div
class="searchstax-facet-value-label"
@click="selectFacet(key + facet.name, $event, facetValue, false)"
>{{ facetValue.value }}</div>
<div
class="searchstax-facet-value-count"
@click="selectFacet(key + facet.name, $event, facetValue, false)"
>({{ facetValue.count }})</div>
</div>
<div
class="searchstax-facet-show-more-container"
v-if="facet.hasMoreFacets"
>
<div
class="searchstax-facet-show-more-container"
@click="showMoreLessDesktop($event, facet)"
>
<div
v-if="facet.showingAllFacets"
class="searchstax-facet-show-less-button searchstax-facet-show-button"
>less</div>
<div
v-else
class="searchstax-facet-show-more-button searchstax-facet-show-button"
>more</div>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="searchstax-facets-mobile-overlay-done"
@click="closeOverlay"
>Done</button>
</div>
</div>
</template>
</SearchstaxFacetsWidget>
SearchFeedback Widget
example of search feedback widget initialization with minimum options
<SearchstaxSearchFeedbackWidget></SearchstaxSearchFeedbackWidget>
example of search feedback widget initialization with template overrides
<SearchstaxSearchFeedbackWidget>
<template #searchFeedback="{ searchFeedbackData, onOriginalQueryClick }">
<div
class="searchstax-feedback-container"
v-if="searchFeedbackData && searchFeedbackData?.searchExecuted && searchFeedbackData?.totalResults"
> Showing <b>{{ searchFeedbackData.startResultIndex }} - {{ searchFeedbackData.endResultIndex }}</b> of
<b>{{ searchFeedbackData.totalResults }}</b> results <span v-if="searchFeedbackData.searchTerm">for "<b>{{
searchFeedbackData.searchTerm }}</b>" </span>
<div class="searchstax-feedback-container-suggested">
<div v-if="searchFeedbackData.autoCorrectedQuery"> Search instead for <a
href="#"
@click.prevent="onOriginalQueryClick($event)"
class="searchstax-feedback-original-query"
>{{ searchFeedbackData.originalQuery }}</a>
</div>
</div>
</div>
</template>
</SearchstaxSearchFeedbackWidget>
RelatedSearches widget
example of search feedback widget initialization with minimum options
<SearchstaxRelatedSearchesWidget
:relatedSearchesURL="config.relatedSearchesURL"
:relatedSearchesAPIKey="config.relatedSearchesAPIKey"
></SearchstaxRelatedSearchesWidget>
example of search feedback widget initialization with template overrides
<SearchstaxRelatedSearchesWidget
:relatedSearchesURL="config.relatedSearchesURL"
:relatedSearchesAPIKey="config.relatedSearchesAPIKey"
>
<template #related="{ relatedData, executeSearch }">
<div
class="searchstax-related-searches-container"
id="searchstax-related-searches-container"
v-if="relatedData && relatedData?.searchExecuted && relatedData?.hasRelatedSearches"
> Related searches: <span id="searchstax-related-searches"></span>
<span
class="searchstax-related-search"
v-if="relatedData.relatedSearches"
>
<span
v-for="related in relatedData.relatedSearches"
:key="related.related_search"
@click="executeSearch(related)"
class="searchstax-related-search searchstax-related-search-item"
> {{ related.related_search }}<span v-if="!related.last">,</span>
</span>
</span>
</div>
</template>
</SearchstaxRelatedSearchesWidget>
ExternalPromotions widget
example of search feedback widget initialization with minimum options
<SearchstaxExternalPromotionsWidget></SearchstaxExternalPromotionsWidget>
example of search feedback widget initialization with template overrides
<SearchstaxExternalPromotionsWidget>
<template #externalPromotions="{ externalPromotionsData, trackClick }">
<div
class="searchstax-external-promotions-container"
id="searchstax-external-promotions-container"
v-if="externalPromotionsData && externalPromotionsData?.searchExecuted && externalPromotionsData?.hasExternalPromotions"
>
<div
class="searchstax-external-promotion searchstax-search-result"
v-for="externalPromotion in externalPromotionsData.externalPromotions"
:key="externalPromotion.id"
>
<div class="icon-elevated"></div>
<a
v-if="externalPromotion.url"
href="{{externalPromotion.url}}"
@click="trackClick(externalPromotion, $event)"
class="searchstax-result-item-link"
></a>
<div class="searchstax-search-result-title-container">
<span class="searchstax-search-result-title">{{ externalPromotion.name }}</span>
</div>
<p
v-if="externalPromotion.description"
class="searchstax-search-result-description searchstax-search-result-common"
> {{ externalPromotion.description }} </p>
<p
v-if="externalPromotion.url"
class="searchstax-search-result-description searchstax-search-result-common"
> {{ externalPromotion.url }} </p>
</div>
</div>
</template>
</SearchstaxExternalPromotionsWidget>
Sorting Widget
example of sorting widget initialization with minimum options
<SearchstaxSortingWidget></SearchstaxSortingWidget>
example of sorting widget initialization with template override
<SearchstaxSortingWidget>
<template #sorting="{ sortingData, orderChange, selectedSorting }">
<div
class="searchstax-sorting-container"
v-if="sortingData && sortingData?.searchExecuted && sortingData?.hasResultsOrExternalPromotions"
>
<label
class="searchstax-sorting-label"
for="sort-by"
>Sort By</label>
<select
id="searchstax-search-order-select"
class="searchstax-search-order-select"
:value="selectedSorting"
@change="orderChange($event)"
>
<option value=""> Relevance </option>
<option value="date desc"> Newest Content </option>
<option value="date asc"> Oldest Content </option>
</select>
</div>
</template>
</SearchstaxSortingWidget>
Template overrides
Templates use vue slots.
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