Add a Custom Display Option to your Search Results

As we all know, search is an integral part of an ecommerce site. Getting it right is vital. We've already had a few discussions about what you can do to customize your search results pages and there's another one we can look at: the item list.

The item list is what's returned from the search API: it's all the products that meet the user's specified keyword and refinement criteria. By default, we include three different view options:

  1. Grid — where items are displayed as blocks, 4 items (or columns) per row (not available on mobile)
  2. Table — where items are displayed as bigger blocks, 2 items per row
  3. List — where items are displayed full-width, 1 item per row

If you're interested in poking around in the code, you can take a look at FacetsFacets.js, as well as in the backend configuration (Search > Result Display Options). If you take a look at the JavaScript file, you'll see that in the prepareItemDisplayOptions function we include some notes about this functionality.

Anyway, what this function does it tell the application what view options to load because while you can drag and resize the window on a desktop, we actually load different options depending on device and screensize. I bring this up because I wanted to talk about how:

  1. You can add a new item display option
  2. How we might make this appropriate for mobile users

It's relatively easy to add a new option, so let's do that first, and then let's look at some of things we could do afterwards.

Prep the Module

We're adding new code so we need to set up a new module. Pop over to your customizations directory and create CustomFacetViews@1.0.0. In that directory, create directories for Configuration, JavaScript, Templates and Sass. Similarly, create an ns.package.json file and put the following in it:

{
  "gulp": {
   "configuration": [
      "Configuration/*"
    ]
  , "javascript": [
      "JavaScript/*"
    ]
  , "sass": [
      "Sass/*"
    ]
  , "templates": [
      "Templates/*"
    ]
  }
}

Next up, create the entry point file (JavaScript > CustomFacetViews.js) with this code:

define(
  'CustomFacetViews'
, [
  ]
, function (
  )
{
  'use strict';

  return console.log('CustomFacetViews loaded')
});

Finally, update distro.js to include the new module in the dependencies, to the shopping application JavaScript and its Sass. Save and deploy.

The Basics of Adding a New Item Display Option

I recognize that you may not want to add the exact display option that I'm going describe in this post. The good news is that we can easily separate out the two parts so that you can build up the structure and then deviate when you need to. In short, the required steps are:

  1. Create a new item cell template file which is used to render each item returned from the search
  2. Update the entry point file to include the new template as a dependency
  3. Add a configuration file that adds the new template file as an option in the backend configuration
  4. Select the new template as a display option in the configuration

Create a New Template

For the sake of the whole tutorial, I'm going to create a new display option that's pretty much 'fullscreen' — a portrait of each item. Thus, you may want to change the names as we go if this isn't what you want.

In Templates, create facets_item_cell_portrait.tpl. Now, what you can put into an item cell template is determined by what's available from the context object which we can see in two places:

  1. The view: Facets.Itemcell.View.js
  2. At the bottom of any other item cell template, eg: facets_item_cell_grid.tpl

In fact, I think it's a good idea to spend a few minutes familiarizing yourself with the existing templates so you can get a good idea about how to construct them. You'll note that there isn't too much that needs to be added, and that the child views as easy to include/exclude as you wish.

For now, the basics, we can just add in something really simple to our template:

<a href="{{url}}">{{name}}</a>

All we're doing is showing the name and a link to each product. Simple.

Update the Entry Point File

With this there isn't much to say: we need the template to load when the facets do. Technically speaking, we don't need to connect the facets module to the template — we just need to include the template as a dependency somewhere within the application. Add facets_item_cell_portrait.tpl as a dependency and save the file; you don't need to do anything else.

Add a Configuration File

The backend configuration contains options for a site administrator to determine what item display options are available to shoppers on the frontend. They are split into three groups: desktop, phone and tablet; in other words, the site administrator can control whether a display option is available depending on the device being used.

When configuring each option, you set basic things such as its ID and display name, but you must also set the template and what icon to use, whether it is default and how many columns (ie items per row) to use.

This skeleton is determined already, however, by the existing configuration file. What we want to do is make our new option available for selection. To do that we need to add entries for each device (as desired) but first we need to add an option to the templates dropdown for our new template.

You should know that each module's configuration JSON files are concatenated into one file on deploy creating a manifest. You should never modify the manifest, nor should you edit the configuration files directly. Instead, we need to make use of functionality that allows us to send changes to the configuration object before the manifest is created. For this we use a configuration file and something called JSONPath. JSONPath lets us add, replace or remove things to existing structures, which is precisely what we need to do.

In Configuration, create CustomFacetViews.json and put the following in it:

{
  "type": "object"
, "modifications":
  [{
    "target": "$.resource.template.facets-item-cell"
  , "action": "add"
  , "value": "facets_item_cell_portrait.tpl"
  }]
}

For more information on this, see our documentation on the configuration modification schema.

What we're doing here is targeting the precise point in the configuration object and then telling it to add a new value. Why this target? Take a look at the main configuration file for facets: Facets.json. If you look at the values for resource.template.facets-item-cell you'll see the list of the templates we include with the application. Thus, the above JSONPath targets this array and adds a new value: our new template.

Save and deploy. Now we just need to configure it all.

Configure the New Display Option

Once the files have been deployed you can head over to Setup > SuiteCommerce Advanced > Configuration, and then Search > Result Display Options. It is here where you can add new rows to each device table to enable the new display option to be available.

For now, add a new row to the phone table:

  • ID — portrait
  • Name — Portrait
  • Template — facets_item_cell_portrait.tpl
  • Columns — 1
  • Icon — icon-display-grid
  • Default? — No

For the sake of the tutorial, we are re-using the icon — we will change this later. Save the form and then head over to your site. Shrink down the window to the mobile breakpoint and then perform a search. When the page loads you should be able to select the new item option display; alternatively, you can append ?display=portrait to the end of the URL. When the page finishes loading, you should see something like this:

And that's it for the basics: we added in a new display option and from here you can make your own changes. If you keep reading, we're going to develop this idea ourselves and look at how we could implement a 'portrait' mode for search results.

Building it Up: What's Available out of the Box

When creating some new like this, it's worth taking a look at what's already available out of the box. I previously mentioned the context object, so let's re-examine Facets.ItemCell.View.js and facets_item_cell_grid.tpl. In them, we can see the following variables available to us:

  • itemId (Number)
  • name (String)
  • url (String)
  • sku (String)
  • isEnvironmentBrowser (Boolean)
  • thumbnail (Object)
  • thumbnail.url (String)
  • thumbnail.altimagetext (String)
  • itemIsNavigable (Boolean)
  • showRating (Boolean)
  • rating (Number)

We can also see, from looking at the template, that there are a number of child views that are also pulled in:

  • ItemViews.Price
  • GlobalViews.StarRating
  • ItemDetails.Options
  • Cart.QuickAddToCart
  • ItemViews.Stock
  • StockDescription

So these two lists form the building blocks for all templates of this kind. If you want more, you can always log the model to the console or extend the view.

Build up the Template and Sass

The plan is this: I want a display option that shows the product in the fullscreen of a mobile device. The user will see a big image and some essential details, which will link through to the product detail page. They can swipe through the items like a slider.

For now, let's build up the template with the information we need and then look at styling it.

So at the moment, our template is junk: it just shows the product name with a link to the product detail page; it's minimal but a rather bit too minimal. A good place to start is an existing template, such as the one for the grid. In facets_item_cell_grid.tpl we can see the structure and how things translate. When creating the following template, I copied and pasted that template into mine, removed some things, moved things about and changed class names from grid to portrait.

Put this in facets_item_cell_potrait.tpl:

<div class="facets-item-cell-portrait" data-type="item" data-item-id="{{itemId}}" itemprop="itemListElement" itemscope="" itemtype="http://schema.org/Product" data-track-productlist-list="{{track_productlist_list}}" data-track-productlist-category="{{track_productlist_category}}" data-track-productlist-position="{{track_productlist_position}}" data-sku="{{sku}}">
    <a class="facets-item-cell-portrait-link" href="{{url}}">
        <meta itemprop="url" content="{{url}}"/>

        <div class="facets-item-cell-portrait-image-wrapper">
            <img class="facets-item-cell-portrait-image" src="{{resizeImage thumbnail.url 'main'}}" alt="{{thumbnail.altimagetext}}" itemprop="image"/>
        </div>

        <div class="facets-item-cell-portrait-details">
            <div class="facets-item-cell-portrait-name" itemprop="name">{{name}}</div>

            <div class="facets-item-cell-portrait-quickaddtocart" data-view="Cart.QuickAddToCart"></div>
        </div>
    </a>
</div>

Here are some of the key differences between this and other item cell templates:

  • In order to make it look cleaner, I've removed a number of child views (such as rating and stock information) — this is purely aesthetic
  • The entire contents of the container is wrapped in an anchor tag: this template is just for mobile users so I want to make it easy to click through to the PDP
  • We've told the system to serve the main image rather than the thumbnail — we want to show a big image and modern smartphones can have quite high screen resolutions

Next, some styling. In your Sass folder, create _facets-item-cell-portrait.scss and put in the following:

.facets-facet-browse-portrait {
    display: flex;
    overflow: auto;
}

.facets-item-cell-portrait {
    width: 65vw;
}

.facets-item-cell-portrait-name,
.facets-item-cell-portrait-quickaddtocart {
    text-align: center;
    margin: $sc-small-margin 0;
}

That first declaration we'll get to in a minute.

The second one deals with the width of each item cell that's returned. Remember, the plan is show each one as if it's fullscreen — we want each one large so we've set it 65vw. If you remember our discussion about flexboxes then you might remember using vw as a unit: it is a percentage of the viewport's width. So we're not setting it to 100: that's fine because we don't want it to be quite fullscreen — we'll see why soon.

Finally, we set some styling for the name and price information. Both of these are just aesthetic choices: center the text under each image and space it out a little bit.

If you restart your local server and visit a search results page, you'll see a large image with small text underneath in vertically scrolling list. Great! We're nearly there.

Make it Scroll Horizontally

What I want it to do is make it scroll horizontally but a problem is that all of the templates are set up to scroll vertically, which means we need to change things up.

The Sass we added for the .facets-facet-browse-portrait class is great, but we need to apply that class. Unfortunately, the template we have added only covers the items within the collection and not its container — we need to do something to the container to apply this class.

My approach to this problem was to have the class appear in the container template but only when the potrait display option is in use. Thus for that we'd need two things:

  1. A flag in the context object for the display option
  2. A flag check in the template for the template

Now, I'm not a fan overriding templates for new functionality — I'd much rather inject stuff into existing templates as it keeps things contained. So, instead, I propose we make modifications to the entry point file. Replace the contents of the entry point file with:

define(
  'CustomFacetViews'
, [
    'Facets.Browse.View'
  , 'jQuery'
  , 'facets_item_cell_portrait.tpl'
  ]
, function (
    BrowseView
  , jQuery
  )
{
  'use strict';

  _.extend(BrowseView.prototype,
  {
    showContent: _.wrap(BrowseView.prototype.showContent, function(fn)
    {
      fn.apply(this, _.toArray(arguments).slice(1));
      var displayOption = this.translator.options.display;

      if (displayOption === 'portrait')
      {
        self.jQuery('.facets-facet-browse-items').addClass('facets-facet-browse-portrait');
      }
    })
  });
});

Let's breakdown what's happening here.

  • We've added the view for the container and jQuery as dependencies.
  • We're extending the view so that we can add new code to it.
  • Specifically, we're adding new code to showContent after first wrapping the existing code into a new function
  • Luckily for us, the facets translator (a utility file, which I mentioned in the matrix options article) records what display option is currently in use, so we've pulled that out easily.
  • From there we've performed a conditional check to see if it matches the ID of the one we set in the configuration (ie 'potrait').
  • Finally, we run a line of jQuery to add our special class to the container element — note that we use self.jQuery. self is already defined in the original showContent method and ensures we execute the jQuery in the right place; if it wasn't, we would need to add var self = this.

So, in other words, what we're doing is hijacking the code that displays the view so we can add the class we want if the potrait display option has been selected by the user.

If you save and refresh your local server you should see something like this:

And there's our side-scrolling list with big, bold imagery. You'll note the scrollbar appears, which is a little ugly. However, if you visit the site on your phone (after deploying it) you should find that it's hidden and, in fact, everything looks rather sleek.

Looking good! Now all that's left is to sort out that icon.

Add an Icon for the Display Option

There's two parts to adding an icon for use as a display option:

  1. Making the icon available within the application
  2. Setting it in the configuration

Using icons has come up a few times in these articles but the gist is this: we use a custom font library called Font Awesome to display icons. It works by injecting a character into an element which has been associated with a particular reference in that font family. Rather than use characters like a normal font, the characters have been replaced with icons, so it shows them instead. It's flexible enough that, if we want, we can extend it and add new icons but the simplest solution is usually to use an existing one.

If you don't know what icon to use, I suggest taking a look at their cheatsheet but note two things: the version of FA included in SCA (currently 4.2.0) and the version numbers next to some of the icons. In other words, some of the icons listed will not available on your site without adding them manually.

So, I've picked the icon I want to include (fa-picture-o) and now I need to include this in my code. A good place to learn about this quickly is to take a look at how we declare the styles for the other icons in BaseSassStyles > atoms > _icons.scss. Coincidentally, the first three entries after the base styles relate to the icons used for the display options; thus, we can just copy them!

In _facets-item-cell_portrait.scss add:

.icon-display-portrait {
    @extend .fa;
    @extend %fa-picture-o;
}

There is a base class (.fa) that we're extending that provides the basics for every Font Awesome icon (eg the font family). Then we extend the class for the particular icon we want to use (%fa-picture-o). These two declarations are all we need to use an icon — the template used to display it already has the required markup.

Now, Font Awesome and font stuff in SCA can be a bit fiddly so now I'd recommend quiting the local server and then running gulp clean && gulp deploy to purge the distribution folders and then push the changes up.

The final part is adjusting the the configuration. If you remember at the start, we set the icon to the grid one — so just change that to the name of the new class (.icon-display-portrait). That should be it!

If your icon is still not showing:

  • Clear your browser cache. The old font file is probably still saved on your device.
  • Visit the site in a fresh browser or private browsing session.
  • Check for typos. When I was writing this tutorial I typoed twice (!) and kept wondering why it didn't work.

Final Thoughts

Here's what we've used in this tutorial:

  • Modified the configuration using JSONPath to add a new value to a dropdown in the backend configuration. This is much safer and sustainable than directly editing the constituent files or the manifest.
  • Learnt about the item cell templates and what data is available to them.
  • Used some CSS3 to achieve the style we want. As the vast majority of smartphone visitors will be using a modern device, we can use newer coding specifications as we don't have to worry about older browsers.
  • Added some JavaScript to a view's showContent function so that we could modify a template. We made use of the helpful facet translator file, which gave us a flag we could check so that we could run this JS conditionally.

    We picked a new icon from Font Awesome and implemented it in our Sass.

All in all, it was a good look at an interesting aspect of faceted search. Different display options allow you and the shopper to emphasize and look closer at different aspects of a search results. On small-screened devices such as mobiles, we can think about how what might be the best way to do this.

If you like, you download my sourcecode here: CustomFacetViews@1.0.0.zip.