Displaying Product Pricing in a B2B Search Page With the Augmented Result List Component

The AugmentedResultList component extends the ResultList component to let you invoke a function to augment the raw field of result list items before rendering them.

In a Visualforce Page for a Salesforce B2B Commerce Storefront search page, you can use the B2B Commerce for Visualforce (formerly CloudCraze) API to retrieve product information in that function.

A typical scenario where you would do this is in a B2B commerce interface where you want your result list items to display product information that’s not indexed by Coveo, such as pricing information. This article lays out the steps and provides examples to help you do so. While this article focuses on pricing information, you could adapt the same steps for any other product information you would want to retrieve using the B2B Commerce for Visualforce API.

Step 1: Create a Custom Visualforce Controller

Create a custom Visualforce controller which uses the B2B Commerce for Visualforce API to return the target product information.

Your controller takes a list of product ids as an argument and returns a dictionary that can contain the following key-value pairs:

  • "commonData": Map<String,Object> (optional): A dictionary of data common to all products.

  • "resultData": List<Map<String, Object>> (optional): A list of product-specific information.

Example: Retrieving Product Pricing Information

You use the ccrz.ccApiProduct.fetch function to retrieve product prices and the ccrz.cc_CallContext.userCurrency object to retrieve user currency.

// ProductResultListController.apxc
global with sharing class ProductResultListController {
    @RemoteAction
    global static ccrz.cc_RemoteActionResult fetchProducts(final ccrz.cc_RemoteActionContext ctx, List<String> productIds) {
        ccrz.cc_RemoteActionResult res = new ccrz.cc_RemoteActionResult();
        ccrz.cc_CallContext.initRemoteContext(ctx);
        res.success = false;
        // Prepare a product query for the ccrz API
        Map<String, Object> productFetchQuery = new Map<String, Object>{
            ccrz.ccApi.API_VERSION => ccrz.ccApi.CURRENT_VERSION,
            ccrz.ccApiProduct.PRODUCTIDLIST => new Set<String>(productIds),
            ccrz.ccApiProduct.INCLUDE_ATTRIBUTE_PRICING => true,
            ccrz.ccApiProduct.PARSE_ATTRIBUTE_PRICING => true,
            ccrz.ccApiProduct.PARAM_INCLUDE_PRICING => true,
            ccrz.ccApi.SIZING => new Map<String, Object>{
                ccrz.ccApiProduct.ENTITYNAME => new Map<String, Object>{
                    ccrz.ccApi.SZ_DATA => ccrz.ccApi.SZ_XL
                }
            }
        };
        try {
            // Call ccrz API with the above query
            Map<String, Object> productFetchData = ccrz.ccApiProduct.fetch(
                productFetchQuery
            );
            // Initialize return object
            Map<String,Object> productDataResults = new Map<String,Object>();
            // Populate the `resultData` object with product specific data
            productDataResults.put('resultData', (List<Map<String, Object>>) productFetchData.get(
                ccrz.ccApiProduct.PRODUCTLIST
            ));
            // Call ccrz API to fetch data common to all products
            Map<String,Object> contextData = new Map<String,Object>();
            contextData.put('currency', ccrz.cc_CallContext.userCurrency);
            // Populate the `commonData` object with data common to all products
            productDataResults.put('commonData', contextData);
            res.success = true;
            res.data = productDataResults;
        }
        catch (Exception ex) {
            res.success = false;
            res.data = false;
            ccrz.ccLog.log(System.LoggingLevel.ERROR, 'ERR',ex.getMessage());
        }
        return res;
    }
}

Step 2: Leverage Your Controller in Your Visualforce Page

In the target Visualforce page, you need to create a function that leverages your controller and sets that function as a value of the fetchAugmentData option on your AugmentedResultList component, as shown in the following example.

Example: Using Your Visualforce Controller in Your Visualforce Page

<apex:page sidebar="false" showHeader="false" standardStylesheets="false" applyHtmlTag="false" controller="ProductResultListController">
    <script>
        // Remote action to fetch Pricing info for Product list
        function getProducts(objectIdsList) {
            return new Promise((resolve, reject) => {
                if (!objectIdsList && !objectIdsList.isArray()) {
                    reject('objectIdsList is not an array.');
                }
                Visualforce.remoting.Manager.invokeAction(
                    '{!$RemoteAction.ProductResultListController.fetchProducts}',
                    CCRZ.pagevars.remoteContext,
                    objectIdsList,
                    function (result, event) {
                        if (event.status) {
                            resolve(result);
                        } else if (event.type === 'exception') {
                            reject(`${event.message} : ${event.where}`);
                        } else {
                            reject(event.message);
                        }
                    }
                )
            });
        }
        // Set the remote action function as an option on your AugmentedResultList component
        document.addEventListener('DOMContentLoaded', function () {
            var coveoSearchInterfaceRoot = document.getElementById('search');
            Coveo.options(
                coveoSearchInterfaceRoot,
                {
                    AugmentedResultList: {
                        fetchAugmentData: window.getProducts
                    },
                }
            );
        })
    </script>
    <CoveoV2:SearchInterface name="<SEARCH_INTERFACE_NAME>" catalog="<YOU_CATALOG_ID>"></CoveoV2:SearchInterface>
</apex:page>

Step 3: Include the AugmentedResultList Component

Use the AugmentedResultList component in your markup and use the target fields in your result template.

Example: Including the AugmentedResultList Component

In your <SEARCH_INTERFACE_NAME> interface markup, you use an AugmentedResultList component and create a result template where you leverage the raw.price and raw.currency fields retrieved in the previous steps:

...
<div class="CoveoAugmentedResultList" data-layout="list">
    <script id="ProductList" class="result-template" type="text/underscore" data-field-objecttype="Product">
        <div class="coveo-result-frame">
            <div class="coveo-result-cell">
                ...
                <span class="CoveoFieldValue" data-field="@price" data-helper="currency" data-helper-decimals="2" data-helper-symbol="$"></span>
                <span class="CoveoFieldValue" data-field="@currency"></span>
            </div>
        </div>
    </script>
</div>
...