Part of this tutorial uses extensibility API classes available only to SuiteCommerce, and SuiteCommerce Advanced 2020.2 and newer. However, the central mechanisms are available in versions as far back as 2018.1 (Aconcagua).

A contextDataRequest is a call you can include in a child view so that it will gain access to the model or collection data of a parent view. This is useful in scenarios where you are writing extensions that will add a new view to product list or product detail page, and you need access to an item’s data.

Before we go into detail, let me outline what we’re going to look at in this article:

  1. A very short summary of the correct approach, if you’re confident you can do it by yourself
  2. Commonly suggested approaches, which are either obsolete or don’t follow best practices
  3. A detailed look at the recommended approach

Solution Summary

  1. Create a new extension
  2. In your entry point file, use addChildView() to add a new view to the view that contains where you want your data to be shown
  3. Create a new view class using SCView
  4. Add this.contextDataRequest = ['item'] to your view’s constructor function
  5. Pass the desired value to the template context by referencing properties on the this.contextData object in getContext
  6. Create a template which prints that field’s data

It is important to note that this is only available when adding a child to specific parent views.

Additionally, you may also need to add the new field to the appropriate field set if it is not there already. You may also include a Sass file to add styling.

contextData and contextDataRequest

The key feature we are using in this solution is contextData and its sibling contextDataRequest:

  • contextData is a property of every view but it is only populated with data in some of those views.
  • contextDataRequest is a property available to views added as children to views with contextData. You call it by passing in an array of types of data you want back.

There are four types of objects a view may return, and each view may support multiple types — which types are returned is specific to each view class. Most views do not support these requests.

When requests are supported, and a data object is returned, its values will be specific to the view instance. In other words, if you are working on a PDP then contextData will support requests either or both the product and/or item model. The values in that object will be specific to the particular product page the user is looking at.

The following table list the parent view classes which have contextData properties, and which types of data they can provide child views.

contextData ObjectClass AvailabilityInformation Returned
categoryFacets.Browse.ViewThe category model
itemItemRelations.RelatedItem.ViewThe item model
Facets.ItemCell.View
ProductDetails.Base.View
itemlistFacets.Browse.ViewAn object that contains the item collection
productProductDetails.Base.ViewThe parent model for a product, which also contains the item model

A key thing to keep in mind is that this availability cascades — this means that you don’t have to add your child view directly into the classes listed. As long as your child view has a parent, grandparent, etc, that is one of these views, contextDataRequest will be available.

Remember: this functionality is only available in the view classes mentioned above and their children.

Example Code

The tutorial further down will go into more detail using contextDataRequest but if you just want to see the mechanisms in action, here are some code samples.

The exact syntax will depend on what type of view you are using (eg, this could be because of your web store’s code version).

SCView and Other Extensibility API Views

// ...
function MyCustomViewConstructor (options) {
    SCView.call(this, options); 
    // ...
    this.contextDataRequest = ['item'];
}

// ...
MyCustomViewConstructor.prototype.getContext = function () {
    return {
        detailedDescription: this.contextData.item().storedetaileddescription
    }
}

Backbone.View and PageType.Base.View

// ...
return PageTypeBaseView.extend({
    // ...
    contextDataRequest: ['item'],
    // ...

    getContext: function getContext () {
        return {
            detailedDescription: this.contextData.item().storedetaileddescription
        }
    }
})

Obsolete or Bad Practice Approaches

I’m not going to dwell on these but are included in case you identify these approaches with how you would normally approach this issue (or you may using an old version of SuiteCommerce Advanced).

Don’t Add a Child View by Extending the childViews Property

In older versions of SCA, it was common to extend the childViews property of a view and then add in your view as an additional property. It is tempting to do this because it makes it easy to pass in the model as an argument to the view’s constructor.

While this is fine for versions without access to the extensibility API, it is not advisable in modern versions as it is incompatible with our coding standards.

This approach also requires adding the view as a dependency to your file, which is not permitted for core modules.

Avoid Customizing the Template in the Theme

Generally speaking, customizations to a theme’s template files should not be made simply to implement new pieces of functionality. Furthermore, for many customers, this is not possible as they will be using a managed theme that they cannot modify these files at all.

As we are only seeking to add new information into our pages (while leaving exist information intact) there is no reason to modify any existing code to facilitate that — and that includes templates. Enclosing everything within an extension is the best way to ensure that it is pluggable.

Example Tutorial Walkthrough

The Business Case and Approach

In this tutorial, we’re assuming the role of a developer who has been asked to add the detailed description of each item in a search results page. The detailed description of an item contains HTML that the merchandiser has provided, and is typically only displayed on a product detail page (PDP). The merchandiser would like us to also display it on the product list page (PLP). Specifically, they only want the detailed description to show when the shopper is looking at items in the ‘list’ display style.

As this is a SuiteCommerce web store with a managed theme, we can’t make modifications to the core code or to the theme so can only use extensions.

To do this, we are going to:

  1. Expose the field to the relevant field set
  2. Create the extension skeleton
  3. Use the PLP component’s addChildViews() method in our entry point file to target an existing view into which we will inject our customized view
  4. Create a custom view using SCView that:
    1. Requests item context data from the parent view
    2. Accesses the contextData object with contextDataRequest, passing the required field through to the template context
    3. Conditionally renders the child view if (and only if) the shopper is viewing the page using the List display type
  5. Create a template that renders the detailed description, which can include HTML

Add the Field to Relevant Field Set (If Required)

Before we look at the code, we will need to have the field we want included in the items API response. For the purposes of this tutorial, I am going to assume that we’re dealing with search results on a product list page (PLP), and that it uses the (default) search field set.

I am going to add a standard field called Detailed Description (aka storedetaileddescription), simply because it is one that I have arbitrarily chosen for the purpose of this tutorial — you may choose another standard or custom field as you wish, but just make sure you adjust the code as necessary.

After saving the field set and record, make sure you rebuild the search index.

For information on how to modify field sets, see Define Field Sets.

Create the Extension Skeleton

Begin by creating an extension — you can either do that gulp extension:create using your extension developer tools or by creating one manually. I’m going to do things manually.

Set up your Workspace directory so that it looks like this:

Workspace
└── CDRExample
    └── Modules
        └── CDRExample
            ├── JavaScript
            └── Templates

Then, in CDRExample/Modules, create manifest.json and in it put:

{
    "name": "CDRExample",
    "fantasyName": "Context Data Request Example",
    "vendor": "ExampleVendor",
    "version": "1.0.0",
    "type": "extension",
    "target": "SCA,SCS",
    "target_version": {
        "SCA": ">=20.2.0",
        "SCS": ">=20.2.0"
    },
    "description": "An example extension to demonstrate contextDataRequest",
    "skins": [],
    "templates": {
        "application": {
            "shopping": {
                "files": [
                    "Modules/CDRExample/Templates/cdr_example.tpl"
                ]
            }
        }
    },
    "javascript": {
        "entry_points": {
            "shopping": "Modules/CDRExample/JavaScript/CDRExample.js"
        },
        "application": {
            "shopping": {
                "files": [
                    "Modules/CDRExample/JavaScript/CDRExample.js",
                    "Modules/CDRExample/JavaScript/CDRExample.View.js"
                ]
            }
        }
    },
    "local_folder": "Workspace\\CDRExample"
}

Create the Entry Point File

To show our new field, we’re going to render a view and template that has access to that information. In order for that view to be created, we need to add in some code into our entry point file.

In JavaScript, create CDRExample.js and in it put:

define('CDRExample', [
    'CDRExample.View'
], function (
    CDRExampleView
) {
    'use strict';

    return {
        mountToApp: function mountToApp (container) {
            var PLP = container.getComponent('PLP');

            if (PLP) {
                PLP.addChildViews(PLP.PLP_VIEW, {
                    'ItemViews.Price': {
                        'CDRExample.View': {
                            childViewIndex: 0,
                            childViewConstructor: function () {
                                return new CDRExampleView({PLP: PLP})
                            }
                        }
                    } 
                })
            }
        }
    }
})

In the code sample above, you can see that we are accessing the PLP component. We’re doing this for two reasons:

  1. We want to use its addChildViews() method
  2. We want to access information it has on the current display type

You will note that when we add the child view, we are using the ‘verbose’ syntax which accepts two view injection targets instead of the usual one. The reason for this is because we want to be as specific as possible. There could, for example, be other instances of ItemsView.Price throughout our web store and we don’t necessarily want to inject our new child view into those. Therefore, we use the verbose method attached to a specific visual component (the PLP component) and pass in a reference of the ‘main’ PLP view, which keeps things in scope.

Within that is standard view constructor code, and note we’re passing in the PLP component.

Create the View File

The view is where most of the work is done.

For the view, we’re going to use the modern SCView module, but the general principles are available in other views from SCA 2018.2 onwards.

In JavaScript, create CDRExample.View.js and in it put:

define('CDRExample.View', [
    'SCView',
    'cdr_example.tpl'
], function (
    SCViewModule,
    cdr_example_tpl
) {
    'use strict';

    var SCView = SCViewModule.SCView;

    function CDRExampleView (options) {
        SCView.call(this, options);

        this.template = cdr_example_tpl;
        this.contextDataRequest = ['item'];
        this.displayType = options.PLP.getDisplay().id;
    }

    CDRExampleView.prototype = Object.create(SCView.prototype);
    CDRExampleView.prototype.constructor = CDRExampleView;

    CDRExampleView.prototype.render = function () {
        if (this.displayType == 'list') {
            SCView.prototype.render.call(this);
        }
    }

    CDRExampleView.prototype.getContext = function () {
        return {
            storedetaileddescription: this.contextData.item().storedetaileddescription
        }
    }

    return CDRExampleView
})

You’ll see two interesting things in our CDRExampleView constructor function:

  1. We are making a contextDataRequest for item data
  2. We are creating a custom displayType property that uses the PLP component’s getDisplay() method, which returns a data object about the user’s current display type

After setting the view’s prototype and setting the constructor function, we override the view’s render property with a custom function.

The render property on SCView is ‘protected’, which means that we are allowed to override it if we want. Before overriding a property you should always check the API documentation to see if it is permitted. Private properties should never be overridden in customizations.

The override we are putting in place for render will call the parent class’s render if and only if the display type ID that we passed in is list. This mechanism will prevent it from showing when other display types, such as grid or table, are active.

Finally, in the getContext property, we create a function that defines what context object is sent to the template. You can see that after doing contextDataRequest in the constructor, it is here that we are finally accessing the contextData.

The item() object is essentially a copy of the item model, so we can just pluck from it the property that we want — which is what we are doing.

Create the Template

This is going to be the easiest template you’ll ever write!

In Templates, create cdr_example.tpl and in it put:

{{{storedetaileddescription}}}

Just a quick note here: you’ll see that we’re wrapping the value in triple curly braces — this is a feature of Handlebars that prevents HTML escaping.

In cases where you are certain that the HTML you are passing into the template is ‘safe’ (ie valid HTML from a trusted, secure source) then you can tell Handlebars to interpret the stringified value as raw HTML. This has the effect of injecting the HTML directly into the template’s source.

As we always expect this field to be HTML served straight from the item record stored in our NetSuite account, we are using the triple curly braces!

Test the Extension

Now all that’s left to do is test the extension. Start your local server and then navigate to the local version of your web store. Visit a product list page and begin to change the display type. As you look at the grid and table types, you should see nothing, but you should see the detailed description appear when you view the list type!

"An animated WebP image showing the detailed description for each item in a product list"

Troubleshooting

My contextData is undefined

Are you sure you’ve added it to one of the views in the table above (or one of its children)? contextData is only available in specific scenarios and not every view will expose data this way.

My Field is undefined

Is the field in the API response? Check the data that is coming back from the items API and make sure that your field is there. If it isn’t:

  • Check that you have added the field to the field set
  • Check that you have saved both the field set row and form, as well as the website setup record
  • Check that the data exists in the item record
  • Check that you have run a search re-index
  • Perform a cache invalidation request for the relevant URL path (if necessary, you may need to invalidate the entire domain’s cache)

If it is, log the object to the console and check for typos in your field name!