Get Started with Custom Content Types

This article applies to Kilimanjaro and newer releases.

The site management tools (SMTs) provide independence to site administrators who normally would rely on developers to make changes to the site. They empower administrators to make small changes themselves, which have, historically, been in the form of text, images, merchandising zones and snippets of HTML. They save time for developers and administrators, freeing each to get on with what they would rather be doing.

So, good news if you've been enjoying them so far: in the latest release of SuiteCommerce Advanced, Kilimanjaro, we have introduced an update to enhance this functionality further. It's called custom content types.

As developers, CCTs lets you take the content concept further and extend it like you would normal SCA modules. What this means is that if you find yourself doing repetitive development work which could be automated with SMTs, then this is the functionality for you.

Before we get into examples of CCTs, I want to look at a couple of things that make this functionality possible.

Firstly, you should be aware that we introduced SuiteCommerce Standard recently. The benefit of SCS over SCA is that it's a lot easier to get a site up and running, and you get updates deployed as part of a managed bundle, when they are available. What makes this possible are changes we've been making to the base SuiteCommerce product (that SCA and SCS are both built on), following the introduction of the extensibility layer. This functionality added the concept of components, which you can use to modify the functionality of specific areas of your site, such as your product detail page. In Kilimanjaro, a new component has been added for SMTs. In other words, this is effectively an API for the SMTs.

Secondly, we have bumped the version number of the SMTs from v2 to v3. If you've upgraded your bundles for Kilimanjaro, then there are still some settings you'll need to tinker with.

In your configuration record, you'll need to check to see if it's set your adapter version to 3:

The next thing you need to do is install a bundle called SMT Core Content Types. In previous versions of the SMTs, the core content types were included but as part of the move to implement CCTs, we've effectively created the standard ones the same way you would make a custom one.

The final thing you need to do only applies if you have existing content that want to continue to use: migrate content. In most cases, migrating content to version 3 can be done with a click of the button by going to Lists > Web Site > CMS Contents.

For more information on migrating, see Upgrade from Version 2 to Version 3 of SMT.

Basic Example: Instagram Post Embedder

There are a lot of things that the CCTs can do, but I want to start with the basic structure and something simple first.

If you're an Instagram user then you may know that you can easily generate HTML code so that you can embed posts into web pages. This is also, conceivably, something that someone running an ecommerce site might want to do too. For example, if you run a fashion retail site, then people might take pictures of themselves in your clothing and you want to share them.

The process for adding a custom content type can be split into three general steps:

  1. Adding a custom record type
  2. Adding a CMS content type
  3. Creating and deploying the CCT's code

Our documentation suggests creating the code first and then the records but I want to look at the records first to get them out of the way.

Create the Custom Record Type

When we look at the embed code generated by Instagram we can see that there are two big parts: some HTML and a call to load a script from Instagram. The HTML acts as a container for where the image is going to be, as well as providing some fallback content in case the image or script fails to load. The script, which is called asynchronously, plucks the image URL from the generated HTML and uses it as a way of identifying what image to load. The image is returned by the script, along with some additional details, such as the caption and date, which then replaces any of the existing content with an iframe and its own code.

Thus, when building this functionality we know that there is only one mandatory field that we need, which is the URL of the image we want to embed. We can include additional fields if we like, such as the post's author (although this will get overwritten if the image loads as expected).

So, the first part is to create a custom record type. Each time you create a CCT, you'll need to create a new record type for it.

Go to Customization > Lists, Records & Fields > Record Types > New. Set it up as follows:

  • Name: CCT Instagram Embed
  • ID: _cct_instagram_embed
  • Access Type: No Permission Required

All other fields can be left as default. Save the record type and then go back into it and add new fields:

  • Label: CCT Instagram Embed URL
  • ID: _cct_instagram_url
  • Type: Free-Form Text
  • Mandatory: (checked)

And:

  • Label: CCT Instagram Author
  • ID: _cct_instagram_url
  • Type: Free-Form Text

Now just a quick note: there is currently a limitation in the CMS tool that means that you must not create record fields with IDs longer than 32 characters, including the customrecord string that is prepended to it. I had a chat with one of our developers about this and he said that the team is aware and are looking into ways of increasing the limit, but at the time of writing it is 32 characters — so don't go over it!

Create the CMS Content Type

The content type record is what links the module code, the custom record we just created, and the interface together. The crucial thing here is that the name we set for it is going to act like an ID and so it's vital we set something that is entirely lowercase and contains standard alphanumeric characters with no spaces.

When dragging and dropping new content using the SMTs, you'll note that there are icons associated with each content type. For the one we're creating, the story is no different and we'll need one for our custom type. As this example is for Instagram, I'm going to recommend using an SVG version of the Instagram logo — you can download a copy here. If you don't set a logo then a generic looking one will be displayed in the UI, so it's fine if you don't find one.

You can include and deploy the SVG with the module code — and I'll show you how to do that later — but as we're doing the UI setup stuff now, we need to upload it manually.

Go to Documents > Files > File Cabinet. Click through to where your SSP stores its images, by default it should be Web Site Hosting Files > Live Hosting Files > SSP Applications > NetSuite Inc. - SCA Kilimanjaro > Development > img. Then, click Add File and select the copy of the SVG. When it's uploaded, go to the image's record and copy its fully-qualified URL. It's usually the one starting http://system.na1.netsuite.com/ followed by your account ID and the SSP name. We're going to need it in a moment.

After doing that, it's time to set up the custom content type. Go to Lists > Web Site > CMS Content Types. If you've installed the aforementioned core content types bundle, you should see a list of all standard content types. Click New and create a new type as follows:

  • Name: sc_cct_instagram_embed
  • Description: Embed an Instagram Post
  • Custom Record: CCT Instagram Embed
  • Label: Instagram Post
  • Icon Image Path: (paste the copied SVG URL here)

After saving the record, you've completed all the required backend setup, so we can now move onto the code.

Basic Module Setup

As mentioned above, one of the great benefits of CCTs is that you build them up like you would any other SCA module. This is because of when the content is told to render, it is the CMS that passes it the data and it is the Backbone-powered SCA application that renders the content.

To start, create a basic module structure in your customizations directory:

InstagramEmbed@1.0.0

  • Images
  • JavaScript
  • Sass
  • Templates
  • ns.package.json

Update ns.package.json so that it has the following in it:

{
  "gulp":
  {
    "images": ["Images/*"]
  , "javascript": ["JavaScript/*"]
  , "sass": ["Sass/**/*.scss"]
  , "templates": ["Templates/*"]
  }
}

You can put a copy of Instagram_logo_2016.svg in the Images folder; if we hadn't already uploaded it, then it would be when we deploy our code.

Next, update distro.json:

  • Add the module to the modules array
  • Add the module to the dependencies array for shopping.js
  • Add the module to the dependencies array for shopping.css

Save the files and we can move on to the JavaScript.

The Entrypoint and View Files

In JavaScript, create InstagramEmbed.js and put in the following:

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

  return {
    mountToApp: function (application)
    {
      application.getComponent('CMS').registerCustomContentType({
        id: 'sc_cct_instagram_embed'
      , view: View
      });
    }
  }
});

You'll see that it's very similar to other entry point files you've worked with before, in the sense that it has a mountToApp function. However, you'll note it's different in that we're call upon the extensibility API and its newest component: the CMS.

If you want to take a look at how this component is created, you can take a look at CMSadapter > JavaScript > CMSadapter.Component.js. However, in the meantime looking at the above code you will see two important features:

  1. The id value, which is used to identify the CCT, is exactly the same as the value we set in the name field when we created the custom CMS content type — this is crucial for linking the frontend code to the backend content type
  2. The view is a yet uncreated view, specified much like we would when instantiating a model in any other module

But that's it for the entrypoint file, and now we can look at the view.

Create InstagramEmbed.View.js and put in the following:

define('InstagramEmbed.View'
, [
    'CustomContentType.Base.View'

  , 'instagram_embed.tpl'
  ]
, function
  (
    BaseView

  , Template
  )
{
  'use strict';

  return BaseView.extend({
    template: Template

  , getContext: function ()
    {
      return {
        url: this.settings.custrecord_cct_instagram_url
      , author: this.settings.custrecord_cct_instagram_author
      }
    }
  });
});

This is quite possibly the most basic view that you'll ever see in a module, but it works.

You'll note that we have a base view for the custom content types, which we extend each we make a new one. You can check it out now, if you want, but we'll take a look at some of the important parts of it later.

I suppose the only key thing to point out here is that if you want to get data from the inputs the user defines when they drag the content block into the page, you need to use this.settings rather than call the get() method of the model. Here you can see that we're calling the URL and author of the Instagram post we want to embed, when the user adds the Instagram block to the page, they'll have to specify that.

There are more complicated and special things we can add to a CCT view but, for the time-being, let's keep things simple.

Template and Sass

For the template, we need to put these values into it along with the embed code that Instagram provides. I've already grabbed the Instagram code so now we need to create the template file.

In Templates, create instagram_embed.tpl and in put:

<blockquote class="instagram-media" data-instgrm-captioned data-instgrm-version="7" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);"><div style="padding:8px;"> <div style=" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;"> <div style=" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div> <p style=" margin:8px 0 0 0; padding:0 4px;"> <a href="{{url}}" style=" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;" target="_blank">By {{author}}</a></p></div></blockquote>
<script async defer src="//platform.instagram.com/en_US/embeds.js"></script>

The template loads some placeholder stuff, and then calls Instagram's JavaScript file. If it loads successfully, it'll scrape the {{url}} value and then load image and its data, and then replace the placeholder stuff with what it wants.

If you compare this embed code to the stock embed code, it's different, but unremarkably so.

In Sass, create _instagram_embed.scss and put in:

.cms-content-sc_cct_instagram_embed {
    width: 400px;
    float: left;
    margin: 0 $sc-margin-lv4 $sc-margin-lv4 0;
}

By default, the Instagram embedded images are huge and this just makes things easier for us to deal with in our example. Note, however, that we haven't specified the above class name in our template: this is because it is the class that is automatically put on the container element when the view renders.

Deploy and Test

That's the setup and code done, now we can deploy and test. Do gulp deploy.

When it's deployed, log in to the SMTs. For testing purposes, you can either add an element to an existing page or create a landing page for testing purposes (I'm doing the latter, creating /instagram).

When you go to edit mode, and then click the button to add a new item, you should be able to drag a new Instagram post in the page.

After dragging it into the page, fill out the form that appears in the sidebar. You can repeat this if you like. When you've done that, publish it. You should see something like this:

And that's it! These are the basics of adding a new custom content type.

Working with Context Data

At the moment, the module is very much self-contained. However, we have anticipated scenarios where it could be useful to access data about the page the user is adding the content to.

Via the contextDataRequest method, you can specify one of four objects to get, which directly correlate to the relevant views of three important areas of a site: a category page, a product details page and a search results page. You can read about this in the documentation but I'll summarize it here:

  • categoryFacets.Browse.View.js
  • item and productProductDetails.Base.View.js
  • itemlistFacets.Browse.View.js

There is a subtle but important difference between the item and product objects. The data returned is defined by the contextData in ProductDetails.Base.View.js:

contextData: {
  'product': function ()
  {
    return this.model;
  }
, 'item': function ()
  {
    return this.model.get('item');
  }
}

Thus, you specify item when you want the currently selected (ie a child item) item and product when you want the item in general (ie a parent item).

Let's take a look at adding this data to a CCT.

Conditional Banner on the PDP

OK, let's complicate things a little bit. Let's imagine that you now have a site administrator who wants to show a banner on every product detail page. Except, not every PDP, just ones where the products have been marked as discounted and are part of a sale. We could do this ourselves, and simply build this into the views and templates of the PDP, but what we want more is for the site admin to have autonomy over this so they can do it themselves.

One of the cool things about SMTs is that when you're adding new content to a site, you can specify to add content to "all pages of this type", which means PDPs (if we wish). However, the limitations of the current system mean that we can only add an image to all PDPs, and not the ones for items on sale. This is where custom content types can help us.

The plan is this:

  1. The items on sale will have a custom field attached to them, which is set to true (I'm reusing one I created before), and the admin provides the name of it when they add the content to the page
  2. They will also have to specify the URL of the banner they want to use (note that the image field type is not supported, so they will have to enter the URL in a text field)
  3. The view takes the information from the admin's settings and then checks the item data to see if there's a match
  4. The template will then check if this value is set to true, if it is then it'll render an image using the banner URL, which also links to search results for other items with this field set to true

To speed this up, I'm going to provide a zip file with the module in it: CCTOnSaleBanner@1.0.0.zip.

If you want to implement this functionality yourself, then you'll need to repeat the earlier steps to add a custom record type with two text fields (_cct_onsale_field and _cct_onsale_img) and attach it to a new custom content type named sc_cct_onsale_banner.

Take a look at CCTOnSaleBanner.View.js and see how it differs from our previous view. I think two things that really stand out are two new methods.

Using contextDataRequest: ['item'] allows us to set the context of the current operation to items. If you remember from before, this means that we're able to access the same data that ProductDetails.Base.View.js has, which has access to item's model.

By making this call, data is added to this.contextData, which we can then use in our logic. You can see how we're using this data in the getContext function: we're pulling the user-specified field from this.settings and then checking the item data for the value of this field. This forms the basis of our conditional.

The other values we're returning form the design element: we're pulling the banner location from the settings and then constructing a search URL. As you can tell from the search URL, we're going to link the banner to a search that returns all items, filtered by the field, which acts like a facet.

This is reflected in our template, cct_onsale_banner.tpl. As you can see, we first check if onsale is true and, if it is, we render a banner which links to the search. Simple.

Finally, I've included a small Sass file in the module. All it does it is hide a divider that we include in our base Sass. I personally don't like it and it renders even when our banner doesn't, which looks weird.

Anyway, the final result is this:

Now we shouldn't have any trouble selling those orange things!

Advanced Features

If you've taken a look at the base view or the documentation then you'll notice a couple of things that we haven't included in these two examples. Let me spend some time talking about them, in case they're appropriate to your code.

install

This appears in the sample module we provide in the documentation. Why might you need it? When you're using the SMTs, this method is called the first time a CCT is dragged from the admin panel to the website window. You pass it the CCT settings and any contexts it needs. Note that the base view has two methods appropriate for this function: install and _install. The latter is what does the work, but as we anticipate that you may need to overwrite it with your own code, you can call _install, which is the function that actually does the work. Remember: if you don't overwrite these functions when you extend the base view for your module, then the system will use the base methods.

You overwrite install if you need to make a call to a service or a third-party AJAX service. But you have to make sure you include this._install(settings, context_data); or else you won't be able to access the settings or context.

In our sample module, we simulate a service call by putting in a setTimeout and then returning a deferred object.

validateContextDataRequest

When you've made a request for context data, the SMTs validate it to see if it has all of the data it requires to work. If you've requested context data that is non-essential then you can overwrite this function in your custom view to simply return true; this prevents the validation from occuring and let's the SMTs proceed.

Alternatively, you can overwrite it to include any additional validation that your context requires.

Requesting More than One Context

As a note, if your module requires it then you can specify additional contexts to include. contextDataRequest is an array and you can, thus, specify whatever ones you want. Note, of course, though that you'll never (or, rather, shouldn't) have more than one context data object. But you might specify additional requests in case your module works in different situations and can pull the data it needs from different views.

Troubleshooting

As you'll be aware, this functionality works mostly like any other SCA module, so you can diagnose and fix problems like you would a normal SCA module. However, I recognize that there are some unique issues that can arise.

CCT is Greyed Out in the SMTs

The biggest one is likely to be that you go to add the CCT to a page and the tile is greyed out and unusable.

The underlying issue that causes this is that the CCT module isn't 'ready'. Here are some things to check for:

  • Typos and spelling mistakes — when I first started learning this stuff, I misspelt the name of my module in my entrypoint file
  • The context data isn't ready, so you either need to wait longer, find out what's blocking it (or if it's failing, fix it) or override the validation check

No Core Content Types

As mentioned above, moving to the new version of the SMTs has meant that we've refactored the core content types so that they're technically 'custom'. Thus, if you don't see tiles for text, image, HTML and merchandising zones then you will need to install the SMT Core Content Types bundle.

Server Error When Saving Settings Values

When you go to add a new content item to a page, you may see a server error. If you take a look at the Network tab, you may also see that a POST failed too.

The error returned by the failed POST will usually elucidate the problem and the most common cause returns something like this:

{"error":"An unexpected error occurred: java.lang.RuntimeException: java.sql.SQLException: ORA-12899: value too large for column \"NLCOMPANY\".\"ALLCMSDRAFTDETAILS\".\"SFIELDNAME\" (actual: 34, maximum: 32)\n","errors":{"error":"an unexpected error occurred"}}

This occurs when the field names that you created for your custom record are too long. For example, custrecord_cct_onsale_field is fine but custrecord_cct_onsale_banner_field is not. The maximum field length is 32 and the latter is 34, which is why I got the error when I tried to use that field name.

Remember: when setting field names, the final count will include the prefix custrecord_, which by itself is 11 characters, so you'll need to be brief.

Final Thoughts

Custom content types are about taking the work out of cookie-cutter work that developers typically face. The core content types made progress in this direction, by giving power and autonomy to site administrators, but CCTs are an important development. As developers, you can now work with your colleagues to find the solutions to their problems and to find ways to solve them themselves.

The two examples I offered here should give you some ideas about the sorts of things you can use these for. For example, the Instagram idea allowed the admin to define an Instagram post that they wanted to embed in a page. The user specified the URL, the module fetches it and the associated JS and renders it in page. Now the developer doesn't have to get involved.

The second example showed how it's possible to interface with item data and then perform actions based on the value of a custom field. In our example, we used to it to choose specific times to show a banner on a PDP (in our case, it was when it was on sale).

I think that this tool will provide a lot of power, especially in cases where technical knowledge of SCA is concentrated in an organization: CCTs can free up those people, so they can focus on more architectural things, rather than day-to-day issues.

If you operate as an agency, then the modularization of this functionality means that it will be easy for you to create, manage and share this functionality across all of the sites you build and maintain.

Code Snippets

More Information