Skip to content

Recently Viewed Products

Display to customers the last 10 products they viewed in a carousel. The Products are stored in Session Storage.

Installation

  1. Install the optional-extensions/recently-viewed package on your theme:
    • Copy the extension from optional-extensions/recently-viewed to themes/custom-extensions/recently-viewed
  2. Add the extension template code to a new function conditional block in the JAPI page template.

    • Make sure the following two items are assigned:
      • html_profile (to load in our global variables)
      • readytheme (for the product card iterator)
      • customfields (if you need them)
    • Create a new ReadyTheme Content Section with the code load_products_dataand place this template code in it.
<mvt:comment>
|
|   Take in `l.settings:load_products_data`
|       :settings
|           :items          Array of Product Codes, or Product IDs. Please see `code_or_id`.
|           :image_type     Image Type Code. Default: Main
|           :image_height   Image Height. Default: 360
|           :image_width    Image Width. Default: 360
|           :code_or_id     Use Product Code, or ID from the array. Defaults to `ID`.
|           :customfields   Comma seperated list of custom field codes
|
|   Output
|       :products           Array of Products with data
|       :products_count     Count of the :products array
|
</mvt:comment>

<mvt:assign name="l.settings:load_products_data:products"               value="''" />
<mvt:assign name="l.settings:load_products_data:products_count"         value="0" />
<mvt:assign name="l.settings:load_products_data:settings:code_or_id"    value="toupper( l.settings:load_products_data:settings:code_or_id )" />

<mvt:if expr="ISNULL l.settings:load_products_data:settings:code_or_id">
    <mvt:assign name="l.settings:load_products_data:settings:code_or_id" value="'ID'" />
</mvt:if>

<mvt:if expr="ISNULL l.settings:load_products_data:settings:image_type">
    <mvt:assign name="l.settings:load_products_data:settings:image_type" value="'main'" />
</mvt:if>

<mvt:assign name="l.settings:load_products_data:settings:image_height" value="int( l.settings:load_products_data:settings:image_height )" />

<mvt:if expr="l.settings:load_products_data:settings:image_height EQ 0">
    <mvt:assign name="l.settings:load_products_data:settings:image_height" value="360" />
</mvt:if>

<mvt:assign name="l.settings:load_products_data:settings:image_width" value="int( l.settings:load_products_data:settings:image_width )" />

<mvt:if expr="l.settings:load_products_data:settings:image_width EQ 0">
    <mvt:assign name="l.settings:load_products_data:settings:image_width" value="360" />
</mvt:if>

<mvt:comment>
|
|   Create discount state to use for discount pricing
|
</mvt:comment>

<mvt:do file="g.Module_Feature_PGR_UT" name="l.discount_state_success" value="DiscountState_CreateEmpty( l.discount_state )" />

<mvt:if expr="l.discount_state_success">
    <mvt:do file="g.Module_Feature_PGR_UT" name="l.pricegroup_count" value="ResolvedPriceGroupAndModuleList_Load_Customer_Cached( g.Basket:cust_id, l.pricegroups )" />
    <mvt:do file="g.Module_Feature_PGR_UT" name="l.success" value="DiscountState_Set_PriceGroups( l.discount_state, l.pricegroups, l.pricegroup_count )" />
    <mvt:do file="g.Module_Feature_PGR_UT" name="l.void" value="DiscountState_Disable_Unused_Item_PriceGroups( l.discount_state )" />
    <mvt:do file="g.Module_Feature_PGR_UT" name="l.void" value="DiscountState_Predict_Baseline_Discounts( l.discount_state )" />
</mvt:if>

<mvt:do file="g.Module_Library_DB" name="l.ImageType_Load_Code_Success" value="ImageType_Load_Code( l.settings:load_products_data:settings:image_type, l.image_type )" />

<mvt:foreach iterator="code_or_id" array="load_products_data:settings:items">
    <mvt:assign name="l.settings:product_data"  value="''" />
    <mvt:assign name="l.productimage"           value="''" />
    <mvt:assign name="l.temp"                   value="''" />
    <mvt:assign name="l.load_cropped"           value="''" />

    <mvt:comment>
    |
    |   Load product runtime data / skip if product cannot be loaded
    |
    </mvt:comment>

    <mvt:if expr="l.settings:load_products_data:settings:code_or_id EQ 'ID'">
        <mvt:do file="g.Module_Library_DB" name="l.loaded_product" value="Runtime_Product_Load_ID_Cached( l.settings:code_or_id, l.settings:product_data )" />
    <mvt:else>
        <mvt:do file="g.Module_Library_DB" name="l.loaded_product" value="Runtime_Product_Load_Code_Cached( l.settings:code_or_id, l.settings:product_data )" />
    </mvt:if>

    <mvt:if expr="NOT l.loaded_product">
        <mvt:foreachcontinue />
    </mvt:if>

    <mvt:comment>
    |
    |   Set product data members:
    |       :base_price
    |       :formatted_base_price
    |       :formatted_price
    |       :link
    |       :imagetypes:main (or image type code)
    |       :src
    |
    </mvt:comment>

    <mvt:do file="g.Module_Feature_URI_UT" name="l.settings:product_data:link" value="Store_Product_URL( l.settings:product_data, l.flags )" />

    <mvt:do file="g.Module_Library_DB" name="l.ProductImage_Load_Type_Success" value="ProductImage_Load_Type( l.settings:product_data:id, l.image_type:id, l.temp:productimage )" />
    <mvt:do file="g.Module_Library_DB" name="l.load_cropped" value="GeneratedImage_Load_Dimensions( l.temp:productimage:image_id, l.settings:load_products_data:settings:image_width, l.settings:load_products_data:settings:image_height, l.temp:cropped_image )" />

    <mvt:if expr="NOT l.load_cropped">
        <mvt:do file="g.Module_Library_DB" name="l.Image_Load_ID_Success" value="Image_Load_ID( l.temp:productimage:image_id, l.temp:imagedata )" />
        <mvt:do file="g.Module_Library_DB" name="l.Image_Load_File_Success" value="Image_Load_File( l.temp:imagedata:image, l.temp:product_image )" />
        <mvt:do file="g.Module_Library_DB" name="l.GeneratedImage_FindOrInsert_Image_Dimensions_Success" value="GeneratedImage_FindOrInsert_Image_Dimensions( l.temp:product_image, l.settings:load_products_data:settings:image_width, l.settings:load_products_data:settings:image_height, l.temp:cropped_image )" />
    </mvt:if>

    <mvt:assign name="l.settings:product_data:imagetypes:main" value="l.temp:cropped_image:image" />

    <mvt:if expr="NOT ISNULL l.settings:product_data:imagetypes:main">
        <mvt:assign name="l.settings:product_data:src" value="l.settings:product_data:imagetypes:main" />
    </mvt:if>

    <mvt:assign name="l.settings:product_data:base_price" value="l.settings:product_data:price" />

    <mvt:if expr="ISNULL l.settings:product_data:formatted_price">
        <mvt:do file="g.Module_Store_Module_Currency" name="l.settings:product_data:formatted_price" value="CurrencyModule_AddFormatting( g.Store:currncy_mod, l.settings:product_data:price )" />
    </mvt:if>

    <mvt:assign name="l.settings:product_data:formatted_base_price" value="l.settings:product_data:formatted_price" />

    <mvt:if expr="l.discount_state_success">
        <mvt:do file="g.Module_Feature_PGR_UT" name="l.success" value="DiscountState_Predict_Product_Discounts_WithProductSubscriptionTerm( l.discount_state, l.settings:product_data, 0, 0, 1, l.settings:product_data:price, l.settings:product_data:discounts, l.settings:product_data:discount_count )" />
        <mvt:do file="g.Module_Store_Module_Currency" name="l.settings:product_data:formatted_price" value="CurrencyModule_AddFormatting( g.Store:currncy_mod, l.settings:product_data:price )" />
    </mvt:if>

    <mvt:if expr="l.settings:load_products_data:settings:customfields">
        <mvt:if expr="miva_splitstring( l.settings:load_products_data:settings:customfields, ',', l.settings:load_products_data:settings:customfields_codes, 'trim' ) EQ 1">
            <mvt:assign name="l.settings:load_products_data:settings:customfields" value="l.settings:load_products_data:settings:customfields $ ',fake_customfield'" />
        </mvt:if>

        <mvt:item name="customfields" param="Read_Product_ID( l.settings:product_data:id, l.settings:load_products_data:settings:customfields, l.settings:product_data:customfield_values:customfields )" />
    </mvt:if>

    <mvt:assign name="l.settings:load_products_data:products_count" value="miva_array_insert_var( l.settings:load_products_data:products, l.settings:product_data, -1 )" />
</mvt:foreach>

<mvt:assign name="l.settings:load_products_data:settings" value="''" />
  1. On PROD add the following where you want the Recently Viewed products carousel to display:
    <div id="js-recently-viewed" class="lazyload"></div>
    
  2. Install the Storage Factory package.
  3. In PROD.js, add the following code in the onReady method:
    this.recentlyViewed();
    
  4. In PROD.js, add the following functions:
    recentlyViewed () {
        const recentlyViewedElement = document.getElementById('js-recently-viewed');
    
        if (!recentlyViewedElement) {
            return;
        }
    
        if (recentlyViewedElement.classList.contains('lazyloaded')) {
            this.loadRecentlyViewed();
            return;
        }
    
        document.addEventListener('lazybeforeunveil', (e) => {
            if (e.target.id === 'js-recently-viewed') {
                this.loadRecentlyViewed();
            }
        });
    }
    
    async loadRecentlyViewed () {
        const {default: RecentlyViewed} = await import('custom-extensions/recently-viewed');
    
        new RecentlyViewed(this.pageContext.Product_Code, this.pageContext.Japi_Url);
    }
    

Options

The RecentlyViewed object takes in 2 required parameters, and an optional object as a third argument that is utilized for overriding any of the default configuration settings. The following table defines the available options:

Name Type Description Default Value
productCode String The Current Product Code you're viewing. Required.
apiURL String The API url (JAPI). Required.
container Element Overwrite recently-viewed container element. #js-recently-viewed
productsToShow Number Overwrite number of products to show. 10

Template

The default template is the same as the "Related Products" carousel on the Product Page and can be updated on the JAPI page template after adding the extension template code. If you need to load in more data (example: custom fields), you can make your edits there. It will utilize the product_card_iterator template for each product card.