Implementing an eCommerce Search Results Page (SERP)
Fast Simon JavaScript SDK allows developers to create a modern ecommerce search results page that includes Fast Simon advanced BigData AI, Merchandising, Personalization and more.
Using the Fast Simon JavaScript SDK, you can easily implement the following Search Results Page capabilities:
Displaying search results: Retrieves search results rendered in a product grid.
Filtering search results: Allows applying filters to refine the search results.
Navigating through search results: Support search result pages either in an infinite single scroll down page manner or as separate pages.
Reporting Shopper Activity: Records activities to enable Fast Simon analytics and improve search results relevancy.
Getting Search Results
The fullTextSearch method allows you to get the search results for a given shopper query.
The search method utilizes the Fast Simon search functionality to execute the search operation based on the shopper's input. It communicates with the Fast Simon SDK to retrieve the corresponding search results, filter data, and pagination details.
In the search query the shopper defines what they are looking for, which is implemented using the term parameter. The callback function contains the relevant products and filters matching the shopper term.
window.FastSimonSDK.fullTextSearch({
term: term,
page: this.page,
productsPerPage: 48,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
onFacetsLoaded: (response) => {
this.renderFacets(response.payload.facets);
},
onProductsLoaded: (response) => {
this.renderProducts(response.payload.products);
windows.FastSimonSDK.event({
eventName: windows.FastSimonEventName.SmartCollectionPreformed,
data: {
categoryID: category_id,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
products: response.payload.products.map(({id}) => id).toString(),
rescount: response.payload.totalResults,
pagenum: Math.ceil(response.payload.totalResults / 48)
}
});
}
});
The fullTextSearch method options:
Option | Type | Description |
---|---|---|
term | string (required) | Search query. |
onProductsLoaded | response void (required) | Callback to handler the collection products results. |
onFacetsLoaded | response void (required) | Callback to handler the collection facets results. |
page | number Default: 1 | Page number to retrieve. |
productsPerPage | Number Default: 40 | Determines how many products are displayed on each page. Note: Make sure that the productsPerPage is the same on all pages. |
sortBy | sortBy Default: relevency | Determines the sort order by which the shopper sorts the list of products. (add link to the relevant place in the docs, Extra) |
narrowBy | narrowBy Default: [] | Determines what filters are used to narrow the results. (add link to the relevant place in the docs, Extra) |
facets | boolean Default: true | Determines whether to fetch filters (to allow shoppers to narrowBy parameter). |
withAttributes | boolean Default: false | Determines whether to include attributes in the product response. Note: might increase latency, so use only if you need product tags or attributes. |
withProductTypes | boolean Default: false | Determines whether to include Product Types in the response. |
searchWithinSearch | string (required) | Search bar within the filters menu to refine results. |
Getting Search Results and Reporting Shopper Activity
Having a fast rendered search results is key to optimizing user experience and conversion optimization. Below is a short code snippet illustrating getting search results, rendering products and filters and reporting on shopper activity.
window.FastSimonSDK.fullTextSearch({
term: term,
page: this.page,
productsPerPage: 48,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
onFacetsLoaded: (response) => {
this.renderFacets(response.payload.facets);
},
onProductsLoaded: (response) => {
this.renderProducts(response.payload.products);
windows.FastSimonSDK.event({
eventName: windows.FastSimonEventName.SmartCollectionPreformed,
data: {
categoryID: category_id,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
products: response.payload.products.map(({id}) => id).toString(),
rescount: response.payload.totalResults,
pagenum: Math.ceil(response.payload.totalResults / 48)
}
});
}
});
Notice that the callback function can be fired once or twice depending whether the server completed the facets results. Here are the possible situations for you to prepare to;
response.action Value | Description |
---|---|
“products” | Render search results products in a grid. |
“facet and products” | Rending pages facets (filters). |
Adding searchWithinSearch
to the Search Page
Enhance your search page with the searchWithinSearch
feature, allowing users to refine results directly on the search results page.
Expected UI Flow
- Initial Search: Users perform a full-text search, and results are displayed with relevant facets and products.
- Search Bar Within Results: A search bar is added to the page for refining results using the
searchWithinSearch
parameter.
Code Example
// Function to handle search requests and render results
function loadSearchResults({ searchWithinSearch = "" } = {}) {
window.FastSimonSDK.fullTextSearch({
term: term,
page: this.page,
productsPerPage: 48,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
searchWithinSearch, // Pass the search query if available
onFacetsLoaded: (response) => {
this.renderFacets(response.payload.facets);
},
onProductsLoaded: (response) => {
this.renderProducts(response.payload.products);
window.FastSimonSDK.event({
eventName: searchWithinSearch
? window.FastSimonEventName.SearchWithinPerformed
: window.FastSimonEventName.FullTextSearchPerformed,
data: {
term: term,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
searchWithinSearch,
products: response.payload.products.map(({ id }) => id).toString(),
rescount: response.payload.totalResults,
pagenum: Math.ceil(response.payload.totalResults / 48),
},
});
},
});
}
// Initial search load
loadSearchResults();
// Search Within Results
const searchBar = document.getElementById("search-within-results-bar");
searchBar.addEventListener("input", (event) => {
const searchQuery = event.target.value;
loadSearchResults({ searchWithinSearch: searchQuery });
});
Rendering Search Results in a Product Grid
A product grid is a layout or display format used to present products. It is a structured arrangement of product thumbnails or tiles, organized in a grid-like pattern.
The product grid is designed to provide users with an overview of available products and data allowing them to browse and compare items quickly.
What data to include in the product grid
With Fast Simon SDK you can create a product grid that displays the following product properties:
interface Product {
c: string // currency
d: string // description
f: number // product merch stuff
id: string // product id
iso: boolean // is sold out
l: string // product title
p: string // price
p_c: string // comapre price
p_max: string
p_max_c: string
p_min: string
p_min_c: string
p_spl: number
review: number // review score
reviews_count: number // review count
s: string // sku (shopify foramt)
sku: string // sku
skus: string[] // skus
t: string // main image
t2: string // fallback image
u: string // url
v_c: number // varaint count
vra: Variant[] //product variants
vrc: object
att?: Attribute[] // product attribiutes
alt?: AlternativeProduct[] // alternative (sibiling) products
}
type Variant = [string, VariantData[]]
type VariantData = [string, string[]]
type Attribute = [string, AttributeData[]]
type AttributeData = [string, string[]]
export type AlternativeProduct = [AlternativeProductName, AlternativeProductURL, productID?]
type AlternativeProductName = string
type AlternativeProductURL = string
type productID = number
The product grid options:
Option | Type | Description |
---|---|---|
c | string | Product currency |
d | string | Product description |
f | number | Product merch stuff |
id | string | Product ID |
iso | boolean | Product is sold out or not |
l | string | Product main title |
p | string | Product price |
p_c | string | Product compare price in case of sale |
p_max | string | Variant max price range |
p_max_c | string | Compare variant max price range |
p_min | string | Variant min price range |
p_min_c | string | Compare variant min price range |
p_spl | number | · 1 if the product has variants · 0 in case of no variants |
p_spl_id | string | The ID of the product with variants |
review | number | Review number out of score rate between 0 – 100 |
review_count | number | The number of people reviewed the product |
s | string | SKU Shopify format codes that you can use internally to track your inventory and report on your sales. |
sku | string | SKU regular format |
skus | string | List of variant skus related to product. |
t | string | URL product main image |
t2 | string | URL fall back generic image presented only when the main URL is not accessible. |
u | string | Product location URL |
v_c | number | Variants count number |
vra | Variant[] | Product variants list |
vrc | object | Defined attributes for variant list. |
att? | Attribute[] | Product attributes such as tags custom defined per customer. |
alt? | AlternativeProduct[] | Alternative (sibling) products linked to the main product as viewed alternative selections. |
In addition, you can incorporate various features and interactions within the product grid, such as sorting options, filtering capabilities, and pagination, to enhance the browsing experience and help users find desired products efficiently. To implement product grid characteristics Use the data in response.payload.products.product.vra The 'vra' property comprises a list of product variants, with each variant containing information about its specific options (e.g., Size and Color).
For example:
{
"vra": [
[39746014183484, [["Barcode", ["WFTOP391-1-XS"]], ["Price", ["USD:44.99"]], ["Product-sku", ["WFTOP391-1-XS"]], ["Sellable", [true]], ["Size", ["XS"]], ["Weight", ["130"]]]],
[39746014216252, [["Barcode", ["WFTOP391-1-S"]], ["Price", ["USD:44.99"]], ["Product-sku", ["WFTOP391-1-S"]], ["Sellable", [true]], ["Size", ["S"]], ["Weight", ["130"]]]],
[39746014249020, [["Barcode", ["WFTOP391-1-M"]], ["Price", ["USD:44.99"]], ["Product-sku", ["WFTOP391-1-M"]], ["Sellable", [true]], ["Size", ["M"]], ["Weight", ["130"]]]],
[39746014281788, [["Barcode", ["WFTOP391-1-L"]], ["Price", ["USD:44.99"]], ["Product-sku", ["WFTOP391-1-L"]], ["Sellable", [true]], ["Size", ["L"]], ["Weight", ["130"]]]],
[39746014314556, [["Barcode", ["WFTOP391-1-XL"]], ["Price", ["USD:44.99"]], ["Product-sku", ["WFTOP391-1-XL"]], ["Sellable", [false]], ["Size", ["XL"]], ["Weight", ["130"]]]]]
}
Implementation Examples
The full example of implementation you can find in the Fast Simon GitHub: Fast-Simon-Sample-JavaScript-SDK
HTML:
<div id="productGrid"></div>
JavaScript:
// Render product grid
renderProductGrid(response.payload.products);
function renderProductGrid(products) {
productGrid.innerHTML = ''; //remove previous results
products.forEach((product) => {
// Render product card
const productCard= document.createElement('div');
productCard.setAttribute('id', product.id);*// product id*
const title = document.createElement('div');
title.innerText = product.l; *// product title*
const image= document.createElement('img');
image.src = product.l; *// main image*
const price = document.createElement('span');
title.innerText = product.p; *// price*
productCard.appendChild(image);
productCard.appendChild(title);
productCard.appendChild(price);
productGrid.appendChild(productCard);
});
}
Implementing a Routing Manager
A routing manager handles navigation and routing within an ecommerce website. It plays an important role in processing incoming requests and user interactions, ensuring they are directed to the appropriate URL destinations.
A routing manager is used to manage the navigation between different pages or views. It updates the URLs when filters or pagination options are selected. This results in URLs that contain the necessary data to easily select the desired page and apply specific filters whenever needed.
By implementing routing manager, the website's URLs become consistent for given queries and filters. This allows users to conveniently revisit previous search results or specific filtered views by simply accessing the saved URL (or shared by shoppers for other people).
Suggested structure:
- Listener to the URL changes.
- URL Parser that can get the URL and build the query, filters, pagination, sortBy values.
- URL Generator which should build the URL every time a user uses a query, filters, pagination, or sortBy.
Rendering Search Results Filters
Fast Simon SDK provides the ability to present filters for shoppers per a given query. Facets are versatile filtering options that allow them to refine their search results based on specific attributes of the search results.
Facets are typically displayed as a set of options that shoppers can interact with to narrow down their search results and find the most relevant information or products.
With Fast Simon SDK you can retrieve the data to implement the following facet types:
Facet Type | Illusration |
---|---|
Checkbox | |
Price slider | |
Color swatches | |
Customers Rating Note: The Product Reviews filter is unique. Like on any major ecommerce site, it includes results with the selected review score and higher. For example, selecting products with a 4-star review will also display products with a 5-star review score. |
Working with Facets
Render filters: You can render the search term facets found in the response.payload.facets.
Narrow results: Perform a search after the shopper selects a facet option and performs a Full Text Search method with the narrowBy: parameter.
Implementation Examples
The full example of implementation you can find in the Fast Simon GitHub: Fast-Simon-Sample-JavaScript-SDK
HTML:
<div id="filters"></div>
JavaScript:
renderFilters(response.payload.facets);
function renderFilters(facets) {
const filtersContainer = document.getElementById("filters");
filtersContainer.innerHTML = ''; // Clear previous filters
const filterContainer = document.createElement("div"); // Create a container div for the filter
filterContainer.classList.add('fs_filter_container');
const labelElement = document.createElement("label"); // Create a label element for the filter
labelElement.classList.add('fs_label_element');
labelElement.setAttribute("name", `${facet[0]}`);
labelElement.textContent = facet[2]; //you can change this name in the dashboard
for (let j = 0; j < facet[1].length; j++) { // Create checkboxes for each filter option
const option = facet[1][j][0]; // value of the checkbox
const count = facet[1][j][1]; //number of products matching this filter
const checkboxWrap = document.createElement("div");
const checkboxCount = document.createElement("span");
checkboxCount.innerText = `(${count})`;
const checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.value = option;
checkboxElement.setAttribute("key", `${facet[0]}`);
checkboxElement.setAttribute("name", `${facet[2]}`);
// Add event listener to handle checkbox selection
checkboxElement.addEventListener("change", () => handleCheckboxSelection(checkboxElement));
const optionLabel = document.createElement("label");
optionLabel.textContent = option;
optionLabel.textContent = option; //for categories use:facet[1][j] [2];
checkboxWrap.appendChild(checkboxElement);
checkboxWrap.appendChild(optionLabel);
checkboxWrap.appendChild(checkboxCount);
filterContainer.appendChild(checkboxWrap);
}
}
Sorting Results
A sortBy function allows to organize the products on a page according to different parameters, which enables finding what the shoppers are looking for faster and easier.
By providing a simple and easy-to-use sorting capability, businesses can enhance user experience and potentially increase sales.
Here are the possible sorting options (notice they first need to be turned on in the Fast Simon Dashboard in the Search Results Configuration)
const FastSimonSortBy = {
PriceHighToLow: "price_max_to_min",
PriceLowToHigh: "price_min_to_max",
NewFirst: "creation_date",
OldFirst: "creation_date_oldest",
Popularity: "popularity",
Reviews: "reviews",
AtoZ: "a_to_z",
ZtoA: "z_to_a",
relevancy: "relevency"
};
Fast Simon provides the following sorting options:
Option | Value | Description |
---|---|---|
PriceHighToLow | "price_max_to_min" | Sorts results by product price from high to low. |
PriceLowToHigh | "price_min_to_max" | Sorts results by product price from low to high. |
NewFirst | "creation_date" | Sorts results by latest product insertion by date. |
OldFirst | "creation_date_oldest" | Sorts results by oldest product insertion by date. |
Popularity | "popularity" | Sorts results by product popularity calculated by number of clicks, visits, and sells. |
Reviews | "reviews" | Reviews number out of score rate between 0 – 100. |
AtoZ | "a_to_z" | Sorts results by product name in an ascending order. |
ZtoA | "z_to_a" | Sorts results by product name in a descending order. |
relevancy | "relevency" | When the URL returns without the sortBy value, the default result page is displayed. Note: The word relevency in the code appears with a typo. |
Related Categories
An array of related categories that can be used to show a top wheel of related collections.
Implementation examples
HTML:
<div class="related-categories">
<div class="related-categories-container" id="relatedCategoriesContainer"></div>
</div>
JavaScript:
window.FastSimonSDK.fullTextSearch({
term: term,
page: this.page,
productsPerPage: 48,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
onFacetsLoaded: (response) => {
this.renderFacets(response.payload.facets);
},
onProductsLoaded: (response) => {
this.renderProducts(response.payload.products);
if(response.payload.relatedCategories?.length) {
// Render product grid
const container = document.getElementById("relatedCategoriesContainer");
relatedCategories.forEach(category => {
const categoryItem = document.createElement("div");
categoryItem.className = "category-item";
categoryItem.innerHTML = `
<a href="${category.url}">
<img src="${category.image}" alt="${category.title}">
<div class="category-item-title">${category.title}</div>
</a>
`;
container.appendChild(categoryItem);
});
}
windows.FastSimonSDK.event({
eventName: windows.FastSimonEventName.SmartCollectionPreformed,
data: {
categoryID: category_id,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
products: response.payload.products.map(({id}) => id).toString(),
rescount: response.payload.totalResults,
pagenum: Math.ceil(response.payload.totalResults / 48)
}
});
}
});
export interface RelatedCategory {
id: string
title: string
image: string
url: string
}
const relatedCategories: RelatedCategory[] = [
{
id: "1",
title: "Dresses",
image: "https://example.com/image1.jpg",
url: "https://example.com/category1"
},
{
id: "2",
title: "Top Shirt",
image: "https://example.com/image2.jpg",
url: "https://example.com/category2"
}
];
Pagination
Pagination helps improve user experience by making large amounts of data more manageable and navigable. The following pagination options are available:
Dividing search results into pages, which can then be navigated using controls usually found at the bottom of the page.
Using an infinite scroll, where search results are automatically loaded and added to the bottom of the page when the user reaches the end.
Fast Simon SDK automatically identifies the browser behavior for displaying search products either through separate pages with their related URLs or through an infinite scroll on a single page. In the case of infinite scroll, the URL is updated every time a page of products is loaded.
Reporting Shopper Activity
Every time performed a search query, and the fullTextSearch is called rendering and products an event type searchPerformed is sent to keep Fast Simon dashboard statistics and relevancy up to date.
window.FastSimonSDK.event({
eventName: window.FastSimonEventName.SearchPerformed,
data: {
kl_id: this.klaviyoID,
query: term,
narrowBy: this.narrowBy,
sortBy: this.sortBy,
products: response.payload.products.map(({id}) => id).toString(),
rescount: response.payload.totalResults,
pagenum: Math.ceil(response.payload.totalResults / 48)
}
)};
Event type searchPerformed mandatory options:
Option | Value | Description |
---|---|---|
kl_id | string | (Optional) application output ID number i.e.: klaviyoID |
query | term | Determines the desired search results |
narrowBy | narrowBy | Set of filters sent |
sortBy | sortBy | Sort method selected |
products | string | List of product IDs |
rescount | string | Number of total results matches in the current event search |
pagenum | number Default: 1 | The current page number |
Product Click Event
Every click on a product in the search results page, an event type SerpProductClicked created send update statistics to the admin dashboard.
window.FastSimonSDK.event({
eventName: window.FastSimonEventName.SerpProductClicked,
data: {
query: "dog", // (Required)
productID: "11223344", // (Required)
position: 5, // counting starting at 1
imageID: "asdfsdfsf" // If Image Optimization is used
}
)};
SerpProductClicked options:
Option | Value | Description |
---|---|---|
query | string | Query string used |
productID | number | Product identification ID |
position | number | Position of the product from the beginning of the presented products from the search result |
imageID | number | Optimizations usage send ID in the response back |
Quick View Event
Shopper clicked on product in quick view link and redirected to product page, this creates event type QuickViewProductClicked.
window.FastSimonSDK.event({
eventName: window.FastSimonEventName.QuickViewProductClicked,
data: {
productID: "11223344", // (Required)
originalProduct: "2342432" , // Required)
position: 7 // counting starting at 1
}
)};
QuickViewProductClicked options:
Option | Value | Description |
---|---|---|
productID | number | Product identification ID |
originalProduct | number | Internal ID |
position | number | Position of the product from the beginning of the presented products from the search result |