Litium Docs
  • Documentation
    • Release notes
    • What's new
    • Upgrading to Litium 7
    • Architecture
    • Data modelling
    • Get started
    • Areas
    • Litium Accelerator
    • Add-ons
    • Previous versions
  • Download
  • Add-ons
  • Support
    • Request license
    • Litium policies
  • More
    • Best practices
    • Training
  • Log in
  • Documentation
  • Release notes
  • What's new
  • Upgrading to Litium 7
  • Architecture
  • Data modelling
  • Get started
  • Areas
  • Litium Accelerator
    • Release notes
    • Install
    • Configure
    • Develop
      • Architecture
      • Front end
      • Content pages
      • Search, filtering and navigation
      • Checkout
      • Shopping cart
      • State transitions
      • Setting up a development environment
      • The Buy button
    • Accelerator version handling
    • Axure wireframes for Litium Accelerator
  • Add-ons
  • Previous versions

Search, filtering and navigation

  • Litium 7
How to configure search in Litium Accelerator, and how filtering and navigation works.

First you need to create a new page from the template Search result. Then select the Cogwheel icon > Websites > Websites  and select your website. To configure, go to the Properties tab, find the Search section and point Search result page to the page you created. 

setting search.PNG

There are two ways to get to the search result page:

  • Directly by URL, for example http://litium.local/search-result?q=hammond. Then "search-result" is the URL of the search result page and "q=hammond" is the query with "hammond" as the keyword
  • Via quick search. Type a keyword and press the enter key or click on the search icon.

    quick search.PNG

 

Structure

The search result page has three sections:

  1. Other result: Includes all pages and categories related to the search keyword. They must be indexed before they can be found and are currently limited to one hundred. 

    other result.png

  2. Facet filter: There are two types of facet filters: category based and filter based. Select the Cogwheel icon > Websites > Websites > select your website) to choos your website. To configure, go to the Properties tab, find the Navigation tab, and change Navigation theme.

Category based:

category base.png

Filter based: 

filter base.png

  1. Product list: Show the products related to a search keyword. The number of displayed products can be set manually. Select the Cogwheel icon > Websites > Websites  and choos your website. To configure, go to the Properties tab, find the tab for Product lists and change the Products per page attribute. Products must be fully configured before they can be found.

product list.png

Search result controllers

The search result page is handled by two controllers:

  • The MVC search controller: receives the URL of the search result page along with a search query, to return the result (product list and other results) for the specific Razor view.
  • The API product filter controller: when a user changes filter or page index, this controller takes the request and returns a new view and a new facet filter, without refreshing the page. The page URL will also be changed to fix the new filter and page index.

The API input is query keyword, filter criteria and page index. The output includes new facet filter data, the navigation theme (category based or filter based), the HTML product view to replace the product list, the sorting criteria and category links (category page only).

These controllers use the search service provided by Litium's Lucene Index.

All logic for the search result page can be found in:

  • Litium.Accelerator.Mvc\Views\Search\Index.cshtml: the view of the search result page. This is the place for style and display of product list, facet filter and other result.
  • Litium.Accelerator.Mvc\Controllers\Search\SearchController.cs – Index action: the MVC search controller, contains the logic to return other result and product list.
  • Litium.Accelerator.Mvc\Controllers\Api\ ProductFilterController.cs – Get action: The API search controller, same as the MVC controller, but handles API requests.

Facet search on client

This is the filter feature that allows users to search and filter the products they are interested in. To implement this feature we have the following files: 

  • Litium.Accelerator.Mvc\Client\Scripts\Actions\FacetedSearch.action.js: Contains actions for facet search, dispatch and API calling.
  • Litium.Accelerator.Mvc\Client\Scripts\Reducers\FacetedSearch.reducer.js: Reducer handle state for facet search
  • Litium.Accelerator.Mvc\Client\Scripts\Containers\FacetedSearch.container.js : Facet search container, connects children component to Redux, contains components: FacetedSearch
  • Litium.Accelerator.Mvc\Client\Scripts\Containers\FacetedSearchCompact.container.js: Facet search compact container, connect child component to Redux, manages it ‘s own state, contains components: FacetedSearchCompact, FilterTags.
  • Litium.Accelerator.Mvc\Client\Scripts\Components\FacetedSearch.js - Facet search component:

    facet search component.png

  • Litium.Accelerator.Mvc\Client\Scripts\Components\FacetedSearchCompact.js - facet search compact component:

    facet.PNG

  • Litium.Accelerator.Mvc\Client\Scripts\Components\FilterTags.js - Filter tag component:

    filter tag component.png

Page responsiveness

  • Faceted search has two layouts for computers: category based and filter based.
  • Faceted search has one layout for mobile units:

    moblie layout.png

 

Rendering 

We use both server rendering to support SEO and client rendering to improve the user experience on the web browser.

Server rendering

  • When a user, or a bot from Google or similar, access the search result page via URL, everything except the parts marked with red in the example below is rendered to support SEO.  (http://litium.local/search-result?q=shirt)

    server render.png

  • Pagination is handled by the server. Pages 2, 3 and onwards each have a link to them, for example http://litium.local/search-result?q=nolita&page=2

Client Rendering

We use React, Redux, SessionStorage, and call-to-services API to implement the client rendering. The search flow works like this:

  • Facet components (FacetedSearch, FilterTags, FacetedSearchCompact): when the search page was loaded completely. FacetSearchContainer, FacetSearchCompactContainer is injected to website in index.js file.
    if (document.getElementById('facetedSearch')) {
        ReactDOM.render(
            <Provider store={store}>
                <FacetedSearchContainer/>
            </Provider>,
            document.getElementById('facetedSearch')
        );
    }
    if (document.getElementById('facetedSearchCompact')) {
        ReactDOM.render(
            <Provider store={store}>
                <FacetedSearchCompactContainer/>
            </Provider>,
            document.getElementById('facetedSearchCompact')
        );
    }
  • FacetedSearchCompact.container will call query in the componentDidMount function, and pass parameters with HtmlResult = false. Since the HTML content is rendered from the server, we only need information of current facet search in this query.

    componentDidMount() {
        this.props.query(window.location.search.substr(1) || '', false);
    }
  • Query is a function from FacetedSearch.action.js . When a user has made a search or selected filters, this function is called to fetch the result from server and populate data to the view that is displayed to the user .

  • withHtmlResult is an input parameter of the Query function, to tell the server when to return the product list view (productView) in HTML or not. If withHtmlResult = false, the result response does not contain productVie.

  • The result response from the server of a query function is a JSON object, it contains thes following fields:

    • productView: the content of the page, after search and filter, in HTML format.

    • sortCriteria: use of sort drop-down menu.

    • subNavigation: some pages have sub navigation

    • navigationTheme: decides if the theme is filter or category based.

    • facetFilters: data of filter groups, filter options and selected filters

  • After FacetedSearch.action.js receives the result response from the server, it dispatches it to FacetedSearch.reducer.js. Here is the code for the query function in FacetedSearch.action.js. You can see this line of code dispatch(receive(result)) which pushes the result to FacetedSearch.reducer.js. 

    export const query = (queryString = '', withHtmlResult = true, productsViewCachedId = (new Date()).getTime()+'') => (dispatch, getState) => {
        dispatch({
            type: FACETED_SEARCH_QUERY,
            payload: {
                query: queryString,
            }
        });
    
        let url = withHtmlResult ? '/api/productFilter/withHtmlResult' : '/api/productFilter'
        if (queryString && queryString.trim() !== '') {
            url += `?${queryString}`;
        }
        return get(url)
            .then(response => response.json())
            .then(result => {
                const {productsView, sortCriteria, subNavigation, ...others} = result;
                withHtmlResult && sessionStorage.setItem(PRODUCT_VIEW_CACHED, JSON.stringify({
                    productsViewCachedId,
                    productsView,
                }));
                result = {
                    ...others,
                    sortCriteria : transformSortCriteria(sortCriteria),
                    subNavigation : transformSubNavigation(subNavigation),
                    productsViewCachedId,
                }
                dispatch(receive(result));
            })
            .catch(ex => dispatch(catchError(ex, error => searchError(error))))
        ;
    }
  • In FacetedSearch.reducer.js: 

    • FACETED_SEARCH_RECEIVE: the result from the API will be pushed into Redux

    • FACETED_SEARCH_TOGGLE_VALUE: when a user check/uncheck a filter, it will update the state.

    • FACETED_SEARCH_TOGGLE_COMPACT: show/hide the compact drop-down.

    • After updating Redux's state, all components will be updated.

  • In Filter based mode, when a user checks a box in the left column, the filter will be triggered immediately. filter base.png
  • In Category based mode: when a user selects a filter, the filter will not be triggered until theapply button is selected.

Capture1.PNG

  • FacetedSearchCompactContainer : to allow this behavior, we use the component state of React. We don't use Redux here.facet search compact.png
  • When a user checks/unchecks a filter, another query is sent from FacetedSearch.action.js.

  • The result is a JSON object and productView is a field in this object. It contains the product list in HTML format.

  • The parameter withHtmlResult tells the server to if the productView should be returned or not.

  • With the parameter withHtmlResult = true by default, productsView will be included within the response that the server returns to the client. productView is a result field that may be large in HTML format, so it shouldn’t be stored in Redux. Instead, it will be stored in sessionStorage. The parameter productsViewCachedId – the random number representing the productView – will be stored in Redux.

  • After Redux has changed, the properties of FacetedSearchCompact.container.js will be updated. It knows that a new result is coming, because productsViewCachedId is changed. So it will get the new result from sessionStorage. In onSearchResultDataChange function, it will move existingFilter into new HTML and apply that to page.

    onSearchResultDataChange(dom) {
        if ( [null, undefined].includes(dom) ) {
            return;
        }
        const container = document.createElement('div');
        container.innerHTML = dom;
        const existingResult = document.querySelector("#search-result");
        const tempResult = container.querySelector("#search-result");
        const existingFilter = existingResult.querySelector('#facetedSearchCompact');
        const tempFilter = tempResult.querySelector('#facetedSearchCompact');
        const replace = (node, newNode) => node.parentNode.replaceChild(newNode, node);
        // move existingFilter from existingResult to tempResult
        replace(tempFilter, existingFilter);
        // replace existingResult with tempResult ( newResult )
        replace(existingResult, tempResult);
    }
    componentDidUpdate() {
        const {productsViewCachedId} = this.props;
        if (productsViewCachedId && this.currentProductsViewCachedId !== productsViewCachedId) {
            this.currentProductsViewCachedId = productsViewCachedId;
            const productViewCached = JSON.parse(sessionStorage.getItem(PRODUCT_VIEW_CACHED)||"{}");
            const dom = productViewCached.productsView;
            dom && this.onSearchResultDataChange(dom);
        }
    }

     

 

 

 

 

 

Litium Docs


About Litium

Join Litium

Blog


Support

System status

Contact us