Develop Your First SuiteCommerce Advanced Module (Kilimanjaro) - Part 1: Hello World!

This tutorial is the first in a four-part tutorial on the basics of developing a SuiteCommerce Advanced site. It is intended for the Kilimanjaro version, but can be adapted to work with Vinson and Elbrus. If you're using Denali or Mont Blanc, refer to our older tutorial.

Before we begin, this tutorial assumes that you have a NetSuite account, the SuiteCommerce Advanced bundle installed, a website set up, and your developer tools set up. It also assumes that you have web development experience, and are comfortable coding in JavaScript, HTML, CSS.

This tutorial will teach the very basics of developing a SuiteCommerce Advanced site. To do this, we're going to add new functionality to the account section of your site that allows shoppers to save some preferences. In particular, we're going to look at:

  • The languages and frameworks (such as Backbone) that are central to how the code works
  • The various key concepts in Backbone and SCA's JavaScript, such as entry point files, views, models, collections, routers, models, etc
  • Services, service controllers, and SuiteScript
  • Creating, reading, updating and deleting data
  • Custom records
  • The configuration tool
  • Built-in, re-usable modules, such as modals and confirmation boxes
  • Templating with Handlebars
  • Styling with Sass

The tutorial will be split into sections roughly along the lines of:

  1. Understanding the basics of a module and creating a 'hello world' experience
  2. Upgrading it by creating a service that serves dummy data, learning how Backbone and SCA handle data
  3. Connecting the module to the backend, to perform HTTP operations on actual data in NetSuite
  4. Styling and improving the experience of the module

Note that while we will cover some areas in detail, this tutorial is no substitute for the intensive training we offer. We recommend that new SCA developers complete the developer training.

Prepare the File and Folder Structure

To begin, open up your local version of your SCA code into your file editor. Within the Modules folder, you will see a number of other folders:

  • extensions — where customizations for your site live
  • suitecommerce — where SCA-specific code that NetSuite wrote lives
  • third_parties — where the third-party code we rely on lives

Blocks of functionality, called modules, live within these parent folders. To add new functionality, we need to create a new module.

Within the extensions folder, create folders with the following structure:

  • UserPreferences@1.0.0
    • JavaScript
    • Templates

Module folders take the pattern of <MODULE NAME>@<VERSION NUMBER>. Note that the module name is a namespace, so it must not match the name of any existing module and the version number follows the semantic versioning system.

With the folder structure in place, we need to tell the application where to look for our code within our module. To do this, create a file in UserPreferences called ns.package.json and this in it:

{
  "gulp":
  {
    "javascript": ["JavaScript/*.js"]
  , "templates": ["Templates/*.tpl"]
  }
}

A module's ns.package.json lists all the areas of the module that we want to include when the code compiles. For now we only want to include JavaScript and template files, but later we can add in entries for SuiteScript, Sass and others.

While we've pointed to where the files will live, we next need to point the application to the module itself. To do that, we need to edit the site's distro.json file, which you can find in your root SCA directory.

The distribution (distro) file is a series of JSON objects that list metadata about the site, as well as paths to all the site's modules indicating which ones should be compiled into which application. In particular, look at the modules object near the top: this is where we list all the modules we want to include in our site.

At the top of the modules object, add in the path to our newly created module and the version number, eg:

{
    "name": "SuiteCommerce Advanced Kilimanjaro",
    "version": "2.0",
    "isSCA": true,
    "buildToolsVersion": "1.3.1",
    "folders": {...},
    "modules": {
        "extensions/UserPreferences": "1.0.0",
        "extensions/MyExampleCartExtension1": "1.0.0",
        ...
        "suitecommerce/Account": "2.4.0",
        "suitecommerce/Address": "2.4.3",
        ...
        "third_parties/twitter-typeahead": "0.10.5",
        "third_parties/underscore.js": "1.7.0"
    }
    ...
}

The next part is to specify into which part of the site our new functionality can be used in. An SCA site is divided into three applications: shopping.js (ie browsing and adding to cart), myaccount.js (registering for and accessing personal shopper data), and checkout.js (converting a cart's contents to an order). When you deploy your code, the modules listed as dependencies for each of these applications are compiled into one or more of those JavaScript files.

In this tutorial, our functionality relates to a logged-in shopper who's going to save some preferences against their user record. This, therefore, fits with the notion of accessing personal shopper data, so we need to list our new module as a dependency of myaccount.js.

In distro.json, scroll down through the tasksConfig object until you get to the object that has the key/value pair of "exportFile": "myaccount.js", then add the following to the dependencies object:

"UserPreferences",

After saving, we can move onto writing some code.

Create the Entry Point File

In UserPreferences > JavaScript, create UserPreferences.js. This file is an entry point file: when the module is first loaded, this file is called first.

Its name matches the module folder name, as well as the value we just added to the dependencies object. This is necessary.

Within the file put:

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

  return {
    mountToApp: function ()
    {
      console.log('Hello World! UserPreferences calling!');
    }
  }
});

If you're unfamiliar with the define statement, you should know that this is asynchronous module definition (AMD) functionality, provided to us by a library called requireJS. Using AMD means that our code:

  • Performs faster, as we only load modules that we need (and they are loaded separately)
  • Is less likely to error, as we can specify which dependencies must be in place before other code can run
  • Is neatly segmented into modules, so that it is more logically organized and easier to work with

So to 'define' the module, we use the along with a name, followed by an array of dependencies (none, currently, in our case), and then the code we want the entry point to run. In this file, we're keeping it extremely simple by merely logging a simple statement to the developer console when the module is mounted to the application.

What this one file means is that when the account application is loaded, this module will be too. We can test this by deploying our code up to our site and then visiting the account section of the site.

In your command line, change to the working directory of your SCA site and then run:

gulp deploy --no-backup

The --no-backup flag is a time-saving measure when you're rapidly developing and deploying code: normally, after uploading your code, a copy of the original code is made and saved so that you can restore in case things go wrong. I'd certainly recommend making a backup when you deploying a big change to your live site, but for now we needn't bother.

NOTE that if you're running a sandbox account, then you'll need to run:

gulp deploy --no-backup --m sandbox

Refer to our documentation on Gulp commands for more information.

If this is your first deploy, then you'll need to complete some prompts to provide the tools with some information about your account. Make sure you push it up to the right account and that you upload the code to the Development (not the Source) directory. After completing them, the values you enter will be saved to your computer — if you need to change them then attach the --to flag to end of the command, which will cause the tools to run through the prompts again.

Once it finishes deploying, go to your development site and log into a shopper account. Then, open the developer console and you should see our happy little message!

From here, we can add so more functionality by using the Backbone framework.

Add a Router

A router is a part of the module that deals with URL fragments after the main domain. You create pairings that say "when this fragment is provided, run this function". For example, we're going to allow people to visit /preferences when they're in the account area, so the application needs to know what to do when this happens.

Our module needs routes that allow shoppers to add, view and edit user preference data.

In your JavaScript folder, create UserPreferences.Router.js, and in it put:

define('UserPreferences.Router'
, [
    'Backbone'
  ]
, function
  (
    Backbone
  )
{
  'use strict';

  return Backbone.Router.extend({
    routes:
    {
      'preferences': 'preferencesList'
    , 'preferences/add': 'preferencesAdd'
    , 'preferences/:id': 'preferencesEdit'
    }
  })
});

Just like the entry point file, we start with a define statement. This time, however, we've included a dependency. We list dependencies in an array after we name the current file, using the names that they've been defined with. Then, we pass them, in order, to the function as arguments.

We need Backbone because that is the name of the framework we use to run our applications. Broadly speaking, Backbone lets us split the data and presentation parts of our site up, giving us structure so that we don't get in a tangle around sending data back and forth, connecting parts of the site together, keeping things in sync, etc.

There's a lot more to read up on this, if you're interested, but for now, in practice, it means that we have a lot of ready-made moving parts that we can use throughout our applications to build the site the way we want, in relative ease and structure.

To make use of these parts, after adding them as a dependency, we extend the base classes and add in the bits that are special to our instance. In our example, we take the base router object and then add in the bits that's specific to how our module will function: ie, the routes. The rest of the router functionality is provided by the base version of it, so we don't need to worry about writing it ourselves. Handy!

Finally, the routes object: this where we specify the URL fragments for our module. When a shopper visits <BASE URL>/preferences, the application will call the preferencesList function and run its code. It's important to note that both the route name and callback name are case sensitive; if you camelcase either of them, for example, then you're required to use that style in the rest of your code and the site.

Add a View

However, this is incomplete. When those routes are triggered, the functions they reference can't be called because they don't exist yet. We'll create them in a moment, but first we need to create a view.

Views are the presentation JavaScript of an application. They get data, do something with it, and then render a template. In an application as complex as ours, there isn't a 1:1 connection between a page and views. When a shopper visits a page, it will almost certainly be made up numerous, if not dozens of views. Even within a particular module, you can have one parent view that has numerous child views. In our module, we're going to have a list view that renders a child view for every preference record.

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

define('UserPreferences.List.View'
, [
    'Backbone'
  , 'user_preferences_list.tpl'
  ]
, function
  (
    Backbone
  , user_preferences_list_tpl
  )
{
  'use strict';

  return Backbone.View.extend({
    template: user_preferences_list_tpl

  , getContext: function ()
    {
      return {
        message: 'Hello world! 🌍👋'
      }
    }
  })
});

As well as including Backbone as a dependency, we've now added in a template. This is because views are responsible for the presentation of your site's pages, and will render the template you specify. You'll see that we pass this template the base view class that we're extending.

Along with that, we also have a method called getContext. A view/template's context is an object that contains all of the values that we want to pass from a view to a template. They can be the results of complex functions, data values, or (in our case) simple text messages.

Update the Router

So, we now have something that will render when called. However, we've left our router in complete: we added the routes but not the code to run when the routes are called.

Go back to UserPreferences.Router.js and add in 'UserPreferences.List.View' as a dependency with the name UserPreferencesListView, and then add in the follow code to the return statement:

, initialize: function (application)
  {
    this.application = application
  }

, preferencesList: function ()
  {
    var view = new UserPreferencesListView
    ({
      application: this.application
    })
    view.showContent();
  }
})

The initialize function runs when the file is loaded. We need it because we need to pass through an object called application. This contains a lot of useful values about the state of the application.

The second new function is what runs when the user hits the preferences route. We added our list view as a depedency, and now we're using it in a constructor. We then run the showContent() method, which effectively instructs it to render.

Update the Entry Point File

Our basic router is ready to go (for one route at least), we just need to change the entry point file so that it's called when the module is mounted to the application.

Replace the contents of UserPreferences.js with the following:

define('UserPreferences'
, [
    'UserPreferences.Router'
  ]
, function
  (
    UserPreferencesRouter
  )
{
  'use strict';

  return {
    mountToApp: function (application)
    {
      return new UserPreferencesRouter(application);
    }
  }
});

You'll note that we added our router as a dependency and that we're returning it when the module loads, with the application passed to it.

Add a Template

The final part of this is the template. At the moment, we've got a process going like this:

  1. Entry point file loads when the account application loads
  2. Router loads when the entry point file loads
  3. View loads when the router loads
  4. Template will load when the view loads

In the Templates folder, create user_preferences_list.tpl with the following:

<h1>{{message}}</h1>

The templating system is provided by the Handlebars framework. It accepts normal HTML but also allows for values to to be passed to it when rendered, as well as perform some basic functions like conditional statements and blocks. We'll look at more complex uses of Handlebars later.

In our case, we're passing a context object to the template and in it is our message. It can be referenced by using {{message}} in our template.

Test (Locally)

The barebones of the module is now complete and will work if we deploy it. However, you've probably noticed that uploading takes a couple of minutes, even without the backup running.

As part of the developer tools, we offer a local development environment where changes to your site's JavaScript, HTML and CSS can be updated in near-realtime. It effectively runs a server from your computer, with these resources served locally rather than from NetSuite (note, however, that calls for data, such as those through SuiteScript, are still served from NetSuite servers).

To start the local server, run the following command in your CLI:

gulp local

Once the task has finished running, Gulp will watch your files for any changes. If it notices any, it will re-run any compilation job to produce the new compiled files; just note that some jobs take longer to run and your changes won't take effect until they've finished.

To access the local version of your site, you'll need to use a 'local version' of your site's URL, like this:

// Pattern
https://<DOMAIN>/c.<COMPANY>/<SSP>/<APPLICATION>-local.ssp?n=<SITE>
// Example
https://checkout.netsuite.com/c.TSTDRV12345678/sca-dev-kilimanjaro/my_account-local.ssp?n=3

If you don't have an active session to a logged-in part of your site then you'll be asked to log in again. However, note two things:

  1. You will need to edit the URL again to change my_account.ssp to my_account-local.ssp
  2. You may get entirely white screen, like nothing has loaded

With the second one, you will need to give your browser permission to run unsafe scripts. The scripts we're running aren't actually unsafe, rather they're being run locally which the browser isn't expecting and is unsure whether they're safe.

In Google Chrome, this'll be represented by a shield at the end of the address bar that you can click and OK it:

When the page reloads, your shopper's account homepage will be shown. In the address bar, add #preferences to the end of the URL — this will trigger the route we set up. The page should navigate away and you should see something like this:

Final Thoughts

And that's kinda it for the first part of this tutorial. We've covered the very basics of creating a new module and adding it to the site.

We've looked at some of the most important parts of Backbone and AMD, such as entry point files, views, and routers. However, there's still plenty of concepts left to explore, including adding actual functionality to this module.

In the second part, we'll look at handling some data: using models and collections, and requesting dummy data from NetSuite using a service.

If you're having difficulty with the module, or want to compare code, take a look at UserPreferences@1.0.0-part1.zip, which is my code — note that this does not include the distro file.