# Extending the Front End

Modifying the front-end is one of the most common custom code you'll ever do, for any storefront. More often than not it's not just some CSS, but also the HTML structure itself.

Sadly, there isn't much documentation on how to do that.

Until now.

# Intro: The Salesforce way

Configuration over Convention

  • Maximize the usage of configuration settings to hide/show stuff.
  • If possible, try not to modify too many handlebars templates.
  • It's ok to change the stylesheet via the theme file though.

Realistically, the above statements may not be enough for many storefronts UI requirements.

When that happens, this is what we are taught to do (as of 2019):

  1. Open the storefront page you want to modify/update.
  2. Find the uiproperty to change by looking into the source code of the HTML of the page (hoping you will find it).
  3. Create a new Visualforce page and write your custom handlebars js code within the Apex page. IMPORTANT: Don't forget to include those four VF page header declarations! E.g.
    <apex:page docType="html-5.0"
    sidebar="false" showHeader="false" standardStylesheets="false" 
    applyHtmlTag="false"
    id="ckz54_MyCustomVFPageBodyInclude">
    ...
    <!-- CUSTOM CODE GOES HERE -->
    ...
    </apex:page>
    
    1
    2
    3
    4
    5
    6
    7
    8
  4. Do the same if adding Visualforce components.
  5. In the vf page/component, replace the OOTB handlebar template with the one you created in the steps above (the new uiproperty)
  6. In CC Admin, create a Page Include and point it to the visualforce page you created.
  7. Create and activate a new configuration cache.
  8. Rinse and repeat for every page where you want to update the UI.

TL;DR: The standard approach to extend the handlebars templates requires adding page includes. The page includes then are used to refer a custom VF page. In the VF page (or VF components included in that VF page), the handlebars overrides are written, and then a js code is added to replace the default CCRZ.uiproperties to use the new template. This also requires a new configuration cache to be made.

When multiple handlebars are required, this process may be cumbersome and hard to maintain.

# The Missing SF B2B Guide way

Covention over Configuration

  • You shouldn't need a page include unless there is a custom controller in it.
  • All Handlebars overrides are stored in the Theme's static resource.
  • No Visualforce page code needed. It's just handlebars.
  • Other than setting the storefront theme to use, there is no Configuration Cache updates needed.

Know Your Basics

It's important to have a solid foundation on handlebars, backbone and css.

Why this works:

All themes comes with a js/uiproperties.js file, called at the beginning of the page, across the storefront. It's main purpose is to define custom CCRZ.uiproperties, but is a normal javascript file.

PROs CONs
* No need to refresh configuration cache (other than setting the storefront theme) * You have to know Handlebars and Backbone (not difficult, but not as well known as jQuery)
* Easy to port (since all the customizations are in the theme file) * Collaborating on a static resource may be prone to conflicts (since it is a zip file and different people may be working on different parts of it at the same time)

# High level solution

  1. Find the uiproperty to override.
  2. Create the handlebars templates as you would normally do it, if you were a front end developer (aka: "normal" js, aka: not within an apex page) and create a CCRZ.uiproperties entry to use the custom handlebars.

# Step by Step

These instructions assumes you have a custom theme enabled and available in your VS project.

# Step 1. Finding the uiproperty to override

  1. In your SFDX B2B Commerce, open your theme file in your favorite IDE (e.g VS Code)
  2. Under the js folder, edit uiproperties.js. Add the following code:
    jQuery(function($) {
        // override to display handlebars ID if cclogToken is present
        CCRZ.util.template = function(id) {
            var source = $('#' + id).html();
            preSource = "<div class='internalHB'>"+id+" ("+ckz54_findUIProperty(CCRZ.uiProperties,id).join(";")+")</div>";
            source	=((CCRZ.getCookie("apex__cclgtkn")!="")?"<div class='cc_hb'>"+preSource+ source + "</div>":source) ;
            return Handlebars.compile(source);
        }
        // finds what is the CCRZ.uiproperty for the value passed:
        ckz54_findUIProperty = function(uiPropertyArray,strToFind){
            let arrKeys=Object.keys(uiPropertyArray);
            var uiTest = false;
            var uiArray =[];
            var regex;
            for (uiKey of arrKeys){
                uiTest = false;
                replaceThis='(("tmpl":"'+strToFind+'"))';
                regex = new RegExp(replaceThis, 'g');
                uiTest=JSON.stringify(uiPropertyArray[uiKey]).match(regex);
                if (uiTest) {
                    uiArray.push(uiKey)
                }
            }
            return uiArray;
        }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
  3. Under the css3 folder, edit styles.css. Add the following code:
     .cc_hb:hover{
         background-color: #aefaaefb;
         border:dashed 2px #aca9a9;
     }
     .cc_hb:hover > .internalHB, .cc_hb:hover > .externalHB {
         color: blue;
     }
     .internalHB{
         color:crimson;
         font-size:10px;
     }
     .externalHB{
         color:peru;
         font-size:10px;
     }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  4. Save the files and deploy the static resource (if using VS code, right click on the file and select SFDX: Deploy Source to Org)
  5. In a browser, open the storefront, and add the ccLog token at the end of it.
  6. If successful, you would see the name of the current handlebars template and, between parenthesis the name of the CCRZ.uiproperty that you would need to update if you want to override the handlebars. E.g. Finding the UI Property

Explanation:

  • We are overriding the OOTB CCRZ.util.template to wrap the handlebars in additional HTML to display the id and the CCRZ.properties where it is used.
  • Because we only need this for development, we add a check to only show these values when the ccLog token (cookie) is present.

# Step 2. Creating a Handlebars Template

  1. Back in your favorite IDE (e.g VS Code)
  2. Edit uiproperties.js. We'll add support for "external handlebars", so replace the CCRZ.util.template code written in step 1 with the following:
    // replaces CC's OOTB rendering for handlebar templates (to support external ones)
    CCRZ.util.template = function(id) {
    	const uncompiledHB=".hbs";
    	var source = $('#' + id).html();
    	if (_.isUndefined(source)) {
    		// Load external handlebar;
    		source = '';
    		// Get template from the hbs folder
    		if(id.indexOf(uncompiledHB)>=0){
    			$.ajax({
    				url: CCRZ.pagevars.themeBaseURL + "tmpl/hbs/"+id,
    				cache: true,
    				async: false,
    				success: function(data) {
    					preSource 	= "<div class='externalHB'>"+id+" ("+ckz54_findUIProperty(CCRZ.uiProperties,id).join(";")+")</div>";
    					source	= ((CCRZ.getCookie("apex__cclgtkn")!="")?"<div class='cc_hb'>"+preSource+ data + "</div>":data);
    				}
    			});
    		}
    	} else {
    		// Load internal handlebar (aka: OOTB approach)
    		preSource = "<div class='internalHB'>"+id+" ("+ckz54_findUIProperty(CCRZ.uiProperties,id).join(";")+")</div>";
    		source	=((CCRZ.getCookie("apex__cclgtkn")!="")?"<div class='cc_hb'>"+preSource+ source + "</div>":source) ;
    	}
    	return Handlebars.compile(source);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
  3. Before the jQuery section, add these CCRZ.uiproperties override:
    CCRZ.uiProperties = $.extend(true,CCRZ.uiProperties,{
        headerView: {
            desktop: {
                tmpl: 'ckz54_header.hbs'
            }
        }
    });
    
    //JS functions and overrides for ckz54 Theme
    jQuery(function($) {
        // replaces CC's OOTB rendering for handlebar templates (to support external ones)
        CCRZ.util.template = function(id) {
            ...
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  4. Under the js folder, create a tmpl folder. Under the tmpl folder, create a hbs folder. This is where were we'll store the uncompiled external handlebars.
  5. Under js\tmpl\hbs folder, create a file called ckz54_header.hbs. Enter the following code:
    <a href="#" id="logoUrl"><div id="cc_store_logo_img" class="cc_store_logo_img"></div></a>
    <h1>THIS IS A HANDLEBARS TEST</h1>
    <div class="navbar-inverse" id="ckz54Header">
        <div class="container">
            <div class="row">
                <div id="ckz54Locale" class="col-md-4 col-xs-12">
                    <div class="pull-left">
                        <span class="misc-function" />
                        <span class="lssec"></span>
                    </div>
                </div>
                <div id="ckz54Account" class="col-md-5 col-xs-12">
                    <div class="pull-right">
                        <ul class="list-inline navbar-nav">
                            <li>
                                <a href="#" id="goToAccount"><i class="fa fa-user" aria-hidden="true"></i><span>{{pageLabelMap 'Component_SiteHeader_MyAccount'}}</span></a>
                            </li>
                            <li>
                                <a href="#" id="cartHeader" class="chead cartHeaderLink"></a>
                            </li>
                            <li class="loginoutsec"></li>
                        </ul>
                    </div>
                </div>
                <div id="ckz54Search" class="col-md-3 col-xs-12 pull-right">
                    <div class="search_form" />
                </div>
            </div>
        </div>
    </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
  6. Save the files and deploy the static resource (if using VS code, right click on the file and select SFDX: Deploy Source to Org)
  7. Refresh your storefront page.
  8. If successful, you would see the new Handlebars override. Overriding the OOTB handlebars

Explanation:

  • This overrides will check for the "tmpl/hbs/" folder if the handlebars have a ".hbs" suffix in the id value (which we define in CCRZ.uiproperties, in this case for the headerView)
  • Note how much cleaner the handlebars override is in this case (the .hbs file is just handlebars. No Apex or jQuery or anything else)
  • No Configuration Cache refresh required

No more Page Includes for Handlebars overrides

  • You can use this convention to add all the handlebars overrides under the /hbs folder, and update CCRZ.uiProperties js array, but depending on the uiProperty, additional entries (selector attribute, different structure, etc.) may be required. E.g
    ...
    CCRZ.uiProperties = $.extend(true,CCRZ.uiProperties,{
        headerView: {
            desktop: {
                tmpl: 'ckz54_header.hbs'
            }
        },
        SellerLocatorSearchView : {
            desktop : {
                tmpl : 'ckz54_sellerLocatorMap.hbs',
                selector : '.seller-locator-search'
            }
        },
        PaymentsPOView: {
            desktop: {
                tmpl: 'ckz54_payments.hbs',
                selector: '.paymentContainer'
            }
        },
        productDetailView: {
            desktop: {
                tmpl: 'ckz54_ProductDetail.hbs',
                right: {
                    selector: '.right_column .widgetSection'
                },
                main: {
                    selector: '.prodDetailContainer'
                },
            }
        }
    });
    ...
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  • We recommend to have a "common style" theme to use as a base for any of your Storefronts, and build additional themes on top of it
  • You can have custom themes per Account Groups (plus you can further modify this behavior via cc_hk_DynamicTheme extension!). In other words, with this approach that the same storefront could have completely different look and feel depending on who is logged (and not just the stylesheet!!)

Keep an eye on these

  • Because these are pure handlebars, it's recommended to avoid using jQuery or any kind of inline js within the template. Instead, you should stick with handlebars helpers or similar. You can even have helpers to set up the backbone view, or additional click events, etc.
  • Some OOTB handlebars are used in different CCRZ.uiproperties. AND VICEVERSA (depending on some conditions, some handlebars may be chosen instead of others)
  • To keep it simple, we are not covering the use of Handlebars partials. We found that OOTB partials are not really that reused among the handlebars, so more often than not, we replace them with inline partials instead of adding additional js code to support external partial files.

# Extras

# Homework

  • For better performance, it's better to compile the templates before deploying to production.
  • B2B commerce OOTB handlebars templates are over engineered to cover configuration settings and edge cases. Feel free to use lighter and handlebars templates that are closer to your needs, but don't overlook the power of having some configuration settings available (sp. for display or translations purposes)

Extending the HK User Interface

We could put every js we needed under uiproperties.js and that'll be ok for the browser. However, it is not a very good structure for the developers. It will be easier if you split the file in several ones for ease of maintenance. For example, we like to split the custom js files this way:

  • uiproperties.js, ONLY to keep the uiproperties overrides.
  • myPrefix_hbs.js, for handlebars functions, including helpers.
  • myPrefix_app.js, for anything else (e.g. js constants, google analytics code, etc.)

These can be defined via a hk_userinterface extension. In addition, we can use this file to modify the js files that get loaded OOTB, add a favicon, etc.

These are to help keep the code clean. When deploying these to production, a good practice is to always compile the handlebars, and minimize and bundlify the rest of js and css files. And if using a CDN, cache the file(s).

# Compiling Handlebars

In order to compile your templates, you will need handlebars to be installed in your machine. Alternatively, you could use the docker container maintained by "office numero 2" here: https://hub.docker.com/r/numero2/num2_handlebarsgulp/

Once you have handlebars installed, all you need to do is run this command (for each file):

handlebars hbs/<uncompiled_handlebars_file.hbs> -f <compiled_handlebars_file.tpl.js>
1

# CDN tips

You may have to update the paths of the CCRZ.util.template or customize hk_userinterface class to point to the CDN (by default it will look only within your Org's storefront)

Cache the JS and CSS in the browser! (Salesforce has a limited control of what you can cache and for how long if not using a CDN)

  • Do:
    • Use the Cache-Control and ETag headers to control cache behavior for static assets
    • Set long max-age values to reap the benefits of browser cache
    • Use fingerprinting or versioning for cache busting
  • Don't:
    • Use HTML meta tags to specify cache behavior
    • Use query strings for cache busting

Tags: front end, external handlebars, theme, uiproperties

References: