Overview 

Providing customers with easy to navigate select option is crucial in driving conversions. In catalogs with varied product assortment it bring a challenge as there could be alternative representations apart from "textual options" that can be more effective. For example consider "colour". It is much easier to depict colour as colour boxes which actually depict that colour rather than having a textual representation. Talking on the subject of colour it is also worth to note that number of options could extend into millions. Usually customers when shop only interested in "main colours" palette and therefore not the challenge is not only depicting colour boxes to ease selection but also no narrow selection options.

The configuration of such complex scenarios is already solved in the platform's PIM, so this article will only focus on configuration of the UI through CMS in order to achieve customisation that is required at the presentation layer.  

This cookbook aims to provide working examples in the following key areas:

 

Examples

 

Filter navigation

 

For configuring a filter navigation template please consult product type and filter navigation documentation.

  1. In the product type attribute configuration type template you want to use for given filter block, for example "color_map".
  2. Create a new content include with URI "SHOP10_variant_ProductsNavBlock_color_map". Note the prefix "SHOP10" is the shop code and suffix "color_map" matches our chosen template name.
  3. Clear shop cache
  4. Paste the snippet provided in block below (Content body for ProductsNavBlock_color_map, use expand button to view full listing) into content body and navigate to a category that uses given product type for attribute navigation

 

<%

// [SHOP_CODE]_variant_ProductsNavBlock_color_map use 'color_map' as template in product type attributes template

def _filteredNavBlock = binding.hasVariable('filteredNavBlock') ? filteredNavBlock :  null;
// int _filteredNavBlockLimit = binding.hasVariable('filteredNavBlockLimit') ? filteredNavBlockLimit : 25;

if (_filteredNavBlock != null) {

// Color mapping from _filteredNavBlockValue.value to RBG
def _colors = [
'black':'#000000',
'night':'#0c090a',
'gunmetal':'#2c3539',
'midnight':'#2b1b17',
'charcoal':'#34282c',
'darkslategrey':'#25383c',
'oil':'#3b3131',
'blackcat':'#413839',
'iridium':'#3d3c3a',
'blackeel':'#463e3f',
'blackcow':'#4c4646',
'graywolf':'#504a4b',
'vampiregray':'#565051',
'graydolphin':'#5c5858',
'carbongray':'#625d5d',
'ashgray':'#666362',
'cloudygray':'#6d6968',
'smokeygray':'#726e6d',
'gray':'#736f6e',
'granite':'#837e7c',
'battleshipgray':'#848482',
'graycloud':'#b6b6b4',
'graygoose':'#d1d0ce',
'platinum':'#e5e4e2',
'metallicsilver':'#bcc6cc',
'blue':'#0000ff',
'bluegray':'#98afc7',
'lightslategray':'#6d7b8d',
'slategray':'#657383',
'jetgray':'#616d7e',
'mistblue':'#646d7e',
'marbleblue':'#566d7e',
'slateblue':'#737ca1',
'steelblue':'#4863a0',
'bluejay':'#2b547e',
'darkslateblue':'#2b3856',
'midnightblue':'#151b54',
'navyblue':'#000080',
'bluewhale':'#342d7e',
'lapisblue':'#15317e',
'denimdarkblue':'#151b8d',
'earthblue':'#0000a0',
'cobaltblue':'#0020c2',
'blueberryblue':'#0041c2',
'sapphireblue':'#2554c7',
'blueeyes':'#1569c7',
'royalblue':'#2b60de',
'blueorchid':'#1f45fc',
'bluelotus':'#6960ec',
'lightslateblue':'#736aff',
'windowsblue':'#357ec7',
'glacialblueice':'#368bc1',
'silkblue':'#488ac7',
'blueivy':'#3090c7',
'bluekoi':'#659ec7',
'columbiablue':'#87afc7',
'babyblue':'#95b9c7',
'lightsteelblue':'#728fce',
'oceanblue':'#2b65ec',
'blueribbon':'#306eff',
'bluedress':'#157dec',
'dodgerblue':'#1589ff',
'cornflowerblue':'#6495ed',
'skyblue':'#6698ff',
'butterflyblue':'#38acec',
'iceberg':'#56a5ec',
'crystalblue':'#5cb3ff',
'deepskyblue':'#3bb9ff',
'denimblue':'#79baec',
'lightskyblue':'#82cafa',
'dayskyblue':'#82caff',
'jeansblue':'#a0cfec',
'blueangel':'#b7ceec',
'pastelblue':'#b4cfec',
'seablue':'#c2dfff',
'powderblue':'#c6deff',
'coralblue':'#afdcec',
'lightblue':'#addfff',
'robineggblue':'#bdedff',
'palebluelily':'#cfecec',
'lightcyan':'#e0ffff',
'water':'#ebf4fa',
'aliceblue':'#f0f8ff',
'azure':'#f0ffff',
'lightslate':'#ccffff',
'lightaquamarine':'#93ffe8',
'electricblue':'#9afeff',
'aquamarine':'#7fffd4',
'cyanoraqua':'#00ffff;',
'tronblue':'#7dfdfe;',
'bluezircon':'#57feff',
'bluelagoon':'#8eebec',
'celeste':'#50ebec',
'bluediamond':'#4ee2ec',
'tiffanyblue':'#81d8d0',
'cyanopaque':'#92c7c7',
'bluehosta':'#77bfc7',
'northernlightsblue':'#78c7c7',
'mediumturquoise':'#48cccd',
'turquoise':'#43c6db',
'jellyfish':'#46c7c7',
'bluegreen':'#7bccb5',
'macawbluegreen':'#43bfc7',
'lightseagreen':'#3ea99f',
'darkturquoise':'#3b9c9c;',
'seaturtlegreen':'#438d80',
'mediumaquamarine':'#348781',
'greenishblue':'#307d7e',
'grayishturquoise':'#5e7d7e',
'beetlegreen':'#4c787e',
'teal':'#008080',
'seagreen':'#4e8975',
'camouflagegreen':'#78866b',
'sagegreen':'#848b79',
'hazelgreen':'#617c58',
'venomgreen':'#728c00',
'ferngreen':'#667c26',
'darkforestgreen':'#254117',
'mediumseagreen':'#306754',
'mediumforestgreen':'#347235',
'seaweedgreen':'#437c17',
'pinegreen':'#387c44',
'junglegreen':'#347c2c',
'shamrockgreen':'#347c17',
'mediumspringgreen':'#348017',
'forestgreen':'#4e9258',
'greenonion':'#6aa121',
'springgreen':'#4aa02c',
'limegreen':'#41a317',
'clovergreen':'#3ea055',
'greensnake':'#6cbb3c',
'aliengreen':'#6cc417',
'greenapple':'#4cc417',
'yellowgreen':'#52d017',
'kellygreen':'#4cc552',
'zombiegreen':'#54c571',
'froggreen':'#99c68e',
'greenpeas':'#89c35c',
'dollarbillgreen':'#85bb65',
'darkseagreen':'#8bb381',
'iguanagreen':'#9cb071',
'avocadogreen':'#b2c248',
'pistachiogreen':'#9dc209',
'saladgreen':'#a1c935',
'hummingbirdgreen':'#7fe817',
'nebulagreen':'#59e817',
'stoplightgogreen':'#57e964',
'algaegreen':'#64e986',
'jadegreen':'#5efb6e',
'green':'#00ff00',
'emeraldgreen':'#5ffb17',
'lawngreen':'#87f717',
'chartreuse':'#8afb17',
'dragongreen':'#6afb92',
'mintgreen':'#98ff98',
'greenthumb':'#b5eaaa',
'lightjade':'#c3fdb8',
'teagreen':'#ccfb5d',
'greenyellow':'#b1fb17',
'slimegreen':'#bce954',
'goldenrod':'#edda74',
'harvestgold':'#ede275',
'sunyellow':'#ffe87c',
'yellow':'#ffff00',
'cornyellow':'#fff380',
'parchment':'#ffffc2',
'cream':'#ffffcc',
'lemonchiffon':'#fff8c6',
'cornsilk':'#fff8dc',
'beige':'#f5f5dc',
'blonde':'#fbf6d9',
'antiquewhite':'#faebd7',
'champagne':'#f7e7ce',
'blanchedalmond':'#ffebcd',
'vanilla':'#f3e5ab',
'tanbrown':'#ece5b6',
'peach':'#ffe5b4',
'mustard':'#ffdb58',
'rubberduckyyellow':'#ffd801',
'brightgold':'#fdd017',
'goldenbrown':'#eac117',
'macaroniandcheese':'#f2bb66',
'saffron':'#fbb917',
'beer':'#fbb117',
'cantaloupe':'#ffa62f',
'beeyellow':'#e9ab17',
'brownsugar':'#e2a76f',
'burlywood':'#deb887',
'deeppeach':'#ffcba4',
'gingerbrown':'#c9be62',
'schoolbusyellow':'#e8a317',
'sandybrown':'#ee9a4d',
'fallleafbrown':'#c8b560',
'orangegold':'#d4a017',
'sand':'#c2b280',
'cookiebrown':'#c7a317',
'caramel':'#c68e17',
'brass':'#b5a642',
'khaki':'#ada96e',
'camelbrown':'#c19a6b',
'bronze':'#cd7f32',
'tigerorange':'#c88141',
'cinnamon':'#c58917',
'bulletshell':'#af9b60',
'darkgoldenrod':'#af7817',
'copper':'#b87333',
'wood':'#966f33',
'oakbrown':'#806517',
'moccasin':'#827839',
'armybrown':'#827b60',
'sandstone':'#786d5f',
'mocha':'#493d26',
'taupe':'#483c32',
'coffee':'#6f4e37',
'brownbear':'#835c3b',
'reddirt':'#7f5217',
'sepia':'#7f462c',
'orangesalmon':'#c47451',
'rust':'#c36241',
'redfox':'#c35817',
'chocolate':'#c85a17',
'sedona':'#cc6600',
'papayaorange':'#e56717',
'halloweenorange':'#e66c2c',
'pumpkinorange':'#f87217',
'constructionconeorange':'#f87431;',
'sunriseorange':'#e67451',
'mangoorange':'#ff8040;',
'darkorange':'#f88017',
'coral':'#ff7f50',
'basketballorange':'#f88158',
'lightsalmon':'#f9966b',
'tangerine':'#e78a61',
'darksalmon':'#e18b6b',
'lightcoral':'#e77471',
'beanred':'#f75d59',
'valentinered':'#e55451',
'shockingorange':'#e55b3c',
'red':'#ff0000',
'scarlet':'#ff2400',
'rubyred':'#f62217',
'ferrarired':'#f70d1a',
'fireenginered':'#f62817',
'lavared':'#e42217',
'lovered':'#e41b17',
'grapefruit':'#dc381f',
'chestnutred':'#c34a2c',
'cherryred':'#c24641',
'mahogany':'#c04000',
'chillipepper':'#c11b17',
'cranberry':'#9f000f',
'redwine':'#990012',
'burgundy':'#8c001a',
'chestnut':'#954535',
'bloodred':'#7e3517',
'sienna':'#8a4117',
'sangria':'#7e3817',
'firebrick':'#800517',
'maroon':'#810541',
'plumpie':'#7d0541',
'velvetmaroon':'#7e354d',
'plumvelvet':'#7d0552',
'rosyfinch':'#7f4e52',
'puce':'#7f5a58',
'dullpurple':'#7f525d',
'rosybrown':'#b38481',
'khakirose':'#c5908e',
'pinkbow':'#c48189',
'lipstickpink':'#c48793',
'rose':'#e8adaa',
'rosegold':'#ecc5c0',
'desertsand':'#edc9af',
'pigpink':'#fdd7e4',
'cottoncandy':'#fcdfff',
'pinkbubblegum':'#ffdfdd',
'mistyrose':'#fbbbb9',
'pink':'#faafbe',
'lightpink':'#faafba',
'flamingopink':'#f9a7b0',
'pinkrose':'#e7a1b0',
'pinkdaisy':'#e799a3',
'cadillacpink':'#e38aae',
'carnationpink':'#f778a1;',
'blushred':'#e56e94',
'hotpink':'#f660ab',
'watermelonpink':'#fc6c85',
'violetred':'#f6358a',
'deeppink':'#f52887',
'pinkcupcake':'#e45e9d',
'pinklemonade':'#e4287c',
'neonpink':'#f535aa',
'magenta':'#ff00ff',
'dimorphothecamagenta':'#e3319d',
'brightneonpink':'#f433ff',
'palevioletred':'#d16587',
'tulippink':'#c25a7c',
'mediumvioletred':'#ca226b',
'roguepink':'#c12869',
'burntpink':'#c12267',
'bashfulpink':'#c25283',
'darkcarnationpink':'#c12283',
'plum':'#b93b8f',
'violapurple':'#7e587e',
'purpleiris':'#571b7e',
'plumpurple':'#583759',
'indigo':'#4b0082',
'purplemonster':'#461b7e',
'purplehaze':'#4e387e',
'eggplant':'#614051',
'grape':'#5e5a80',
'purplejam':'#6a287e',
'darkorchid':'#7d1b7e',
'purpleflower':'#a74ac7',
'mediumorchid':'#b048b5',
'purpleamethyst':'#6c2dc7',
'darkviolet':'#842dce',
'violet':'#8d38c9',
'purplesagebush':'#7a5dc7',
'lovelypurple':'#7f38ec',
'purple':'#8e35ef',
'aztechpurple':'#893bff',
'mediumpurple':'#8467d7',
'jasminepurple':'#a23bec',
'purpledaffodil':'#b041ff',
'tyrianpurple':'#c45aec',
'crocuspurple':'#9172ec',
'purplemimosa':'#9e7bff',
'heliotropepurple':'#d462ff',
'crimson':'#e238ec',
'purpledragon':'#c38ec7',
'lilac':'#c8a2c8',
'blushpink':'#e6a9ec',
'mauve':'#e0b0ff',
'wisteriapurple':'#c6aec7',
'blossompink':'#f9b7ff',
'thistle':'#d2b9d3',
'periwinkle':'#d9cfec',
'lavenderpinocchio':'#ebdde2',
'lavenderblue':'#e3e4fa',
'pearl':'#fdeef4',
'seashell':'#fff5ee',
'milkwhite':'#fefcff',
'white':'#ffffff',
'gold':'#eea236',
'silver': '#bcc6cc',

/* Demo data specific */

'aluminium,gold': '#bcc6cc',
'black,blue': '#265a88',
'black,brown': '#835c3b',
'black,graphite': '#c7a317',
'black,graphite,metallic': '#c7a317',
'black,white': '#bcc6cc',
'black,silver': '#bcc6cc',
'black,grey': '#bcc6cc',
'grey,silver': '#bcc6cc',
];



    %><div class="filter-nav-holder">

        <h2>${(_filteredNavBlock.displayName != null ? _filteredNavBlock.displayName : _filteredNavBlock.name)}</h2>

        <div class="single-nav-block single-nav-block-color"><%

            def _fnValuesToDisplay = _filteredNavBlock.fnValues; // No macroFilterNavUtils.cutTheTail(_filteredNavBlock.fnValues, _filteredNavBlockLimit);
            def _multi = 'M'.equals(_filteredNavBlock.navigationType);
            
            _fnValuesToDisplay.each { _filteredNavBlockValue ->


                def _base = filteredURL('');
                def _path = '/' + _filteredNavBlock.code + '/' + encodeURI(_filteredNavBlockValue.value);
                def _selected = _multi && _base.contains(_path);

                def _href;
                if (_selected) {
                    _href = _base.replace(_path, '').replace('//', '/');
                } else {
                    _href = (_base + _path).replace('//', '/');
                }

                %><span>
                <a rel="search"
                           data-filtercode="${_filteredNavBlock.code}"
                           data-filtervalue="${_filteredNavBlockValue.value}"
                           href="${_href}"
                           title="${(_filteredNavBlockValue.displayValue ? _filteredNavBlockValue.displayValue : _filteredNavBlockValue.value)
                                    + ' (' + _filteredNavBlockValue.count + ')'}">
                    <%

                        def _color = _colors[_filteredNavBlockValue.value.replace(" ", "").toLowerCase()];
                        if (_color) {
                            if ('#ffffff'.equals(_color)) {
                                if (_selected) {
                                    %><i class="fa fa-check-square-o  fa-2x" style="color:#000000" aria-hidden="true"></i><%
                                } else {
                                    %><i class="fa fa-square-o  fa-2x" style="color:#000000" aria-hidden="true"></i><%
                                }
                            } else {
                                if (_selected) {
                                    %><i class="fa fa-check-square  fa-2x" style="color:${_color}" aria-hidden="true"></i><%
                                } else {
                                    %><i class="fa fa-square  fa-2x" style="color:${_color}" aria-hidden="true"></i><%
                                }
                            }
                        } else {
                            %><i class="fa fa-question-circle  fa-2x" aria-hidden="true"></i><%
                        }
                %>
                </a>
            </span>
            <%
            }
        %></div>
    </div><%
} %>

 

Snippet above achieves two goals:

  1. Defines colour map (_colors) and then uses this map to convert product attributes values (_filteredNavBlockValue.value) to create an RGB colour that can be used in style attribute.
  2. Converts textual product attribute values (colour in this case) into a visual form as a colour box

 

 

 

Multi SKU selector

 

For configuring product data please consult PIM documentation.

  1. Ensure that you set the "VARIATIONATTRIBUTES" on you product if you want multi select or if you want to force selection controls to be visible
  2. Create content in CMS with the following URI: "SHOP10_variant_SkuListViewDim_13320". Note the prefix "SHOP10" is the shop code and suffix "13320" that must match the attribute code on which we distinguish SKU variations.
  3. Clear shop cache
  4. Paste the snippet provided in block below (Content body for SkuListViewDim_13320, use expand button to view full listing) into content body and navigate to to the product details page.

 

<%

// [SHOP_CODE]_variant_SkuListViewDim_13320 used for '13320', which is simple value and shows as a text box + value name hint

def _sku = binding.hasVariable('sku') ? sku : null;
def _fc = binding.hasVariable('fc') ? fc : null;
def _variations = binding.hasVariable('variations') ? variations : [];
def _variations_opts = binding.hasVariable('variationsOptions') ? variationsOptions : [:];
def _dim = binding.hasVariable('variationDimension') ? variationDimension : null;
def _selection = binding.hasVariable('variationSelection') ? variationSelection : [:];
def _variation_attrs = binding.hasVariable('variationAttributes') ? variationAttributes : [:];

if (_dim != null) {

    def _lang = sf.locale;
    def _multi_dimension = _variations.size() > 1;

    if (_multi_dimension) {
        def _attr = _variation_attrs[_dim];
        def _attrName = _dim;
        if (_attr != null) {
            _attrName = _attr.getLocalName(_lang);
        }

        %><div class="product-detail-sku-list-dim-name" data-dim="${_dim}">${_attrName}</div><%

    }

    %><div class="product-detail-sku-list-dim" data-dim="${_dim}"><%

        def _dim_opts = _variations_opts[_dim];

        if (_multi_dimension) { // group SKU by attribute value

            _dim_opts.each { _opt, _skuItems ->

                def _skuItem = _skuItems[0];
                def _active = false;
                for (def _skuItemCandidate : _skuItems) {
                    if (_sku.skuId == _skuItemCandidate.skuId) {
                        _active = true;
                        _skuItem = _skuItemCandidate;
                        break;
                    }
                }

                def _code = sf.useManufacturerSku ? _skuItem.manufacturerCode : _skuItem.code;
                def _name = _skuItem.getLocalName(_lang); // use SKU name as dimension name by default
                def _val = _code;

                def _currentVal = _skuItem.getAttribute(_dim);
                if (_currentVal != null) {
                    _val = _currentVal.val;
                    _name = _currentVal.getLocalVal(_lang); // set attribute as dimension name
                }

                %>
                <span class="${(_active ? 'active sku-variant-box' : 'sku-variant-box')}">
                    <a rel="bookmark" href="${skuURL([_fc, _skuItem.uri])}" title="${_name}" data-dim="${_dim}" data-dim-value="${_val}">
                        <span>${_name}</span>
                    </a>
                </span>
<%

            }

        } else { // render all

            _dim_opts.each { _opt, _skuItems ->

                for (def _skuItem : _skuItems) {

                    def _code = sf.useManufacturerSku ? _skuItem.manufacturerCode : _skuItem.code;
                    def _name = _skuItem.getLocalName(_lang); // use SKU name as dimension name by default
                    def _val = _code;
                    def _active = _sku.skuId == _skuItem.skuId;

                    def _currentVal = _skuItem.getAttribute(_dim);
                    if (_currentVal != null) {
                        _val = _currentVal.val;
                        _name = _currentVal.getLocalVal(_lang); // set attribute as dimension name
                    }

                    %>
                    <span class="${(_active ? 'active sku-variant-box' : 'sku-variant-box')}">
                        <a rel="bookmark" href="${skuURL([_fc, _skuItem.uri])}" title="${_name}" data-dim="${_dim}" data-dim-value="${_val}">
                            <span>${_name}</span>
                        </a>
                    </span>
<%

                }
            }

        }
    %></div><%

} %>

 

Snippet above achieves the following:

  1. Determines from the runtime variables whether given product is multi dimensional variant (i.e. multiple attributes define the choice of SKU).
  2. Converts textual product attribute values (number of touch points in this case) into a visual form as a selectable box

Product options selector

 

For configuring product data please consult PIM documentation.

  1. Ensure that you set the components and options on you product with relevant SKU configurations.
  2. Create content in CMS with the following URI: "SHOP10_variant_SkuOptionsDim_OPTMOUSE". Note the prefix "SHOP10" is the shop code and suffix "OPTMOUSE" that must match the attribute code which is used for the option defintion.
  3. Clear shop cache
  4. Paste the snippet provided in block below (Content body for SkuOptionsDim_OPTMOUSE, use expand button to view full listing) into content body and navigate to to the product details page.

 

<%

// [SHOP_CODE]_variant_SkuOptionsDim_OPTMOUSE used for 'OPTMOUSE', which show a SKU thumbnail + value name hint

def _skus = binding.hasVariable('product') ? product.skus : null;
def _sku = binding.hasVariable('sku') ? sku : null;
def _fc = binding.hasVariable('fc') ? fc : null;
def _optionsModel = binding.hasVariable('optionsModel') ? optionsModel : null;
def _optionsModelItem = binding.hasVariable('optionsModelItem') ? optionsModelItem : null;

if (_optionsModelItem != null) {

    def _lang = sf.locale;
    def _optionName = _optionsModelItem.getLocalName(_lang);

    %><div class="product-detail-sku-options-option-name" data-option="${_optionsModelItem.attributeCode}">${_optionName}</div><%

    %><div class="product-detail-sku-options-option" data-option="${_optionsModelItem.attributeCode}"><%

        _optionsModelItem.optionSku.each { _skuItem ->

            def _code = sf.useManufacturerSku ? _skuItem.manufacturerCode : _skuItem.code;
            def _name = _skuItem.getLocalName(_lang);
            def _val = _skuItem.code;

            def _skuImage = _skuItem.images != null && _skuItem.images.size() > 0 ? _skuItem.images[0].thumbnailUrl : null;

            %>
            <span class="sku-option-img">
                <a
                        class="js-buy-option"
                        href="#"
                        title="${_name}"
                        data-product="${_sku?.code}"
                        data-option="${_optionsModelItem.attributeCode}"
                        data-optionqty="${_optionsModelItem.quantity}"
                        data-optionrequired="${_optionsModelItem.mandatory}"
                        data-optionvalue="${_val}">

                    <script type='yc-include'>/internal/skubuypanel?skuId=${_skuItem.skuId}&fc=${_fc}&itemQuantity=${_optionsModelItem.quantity}&view=SkuBuyPanelOption</script>
<%
                    if (_skuImage) {
                        %><img src="${_skuImage.substring(0, _skuImage.indexOf('?')) + '?w=40&h=40'}"/><%
                    } else {
                        %><span>${(_code + ' ' + _name)}</span><%
                    }
%>
                </a>
            </span>
<%
        }

    %></div><%

} %>


Snippet above achieves the following:

  1. Determines from the runtime variables whether given SKU has options for given attribute code.
  2. Renders options as image thumbnails for SKU that can be selected
  3. Uses "yc-include" to pull in a "span" with data attributes for current price of the option (this is used by default JavaScript that updates the enabled/disabled state of the "Add to cart" button and also updates the total price)