Developing Your First Custom SuiteCommerce Advanced Module: Part 2

This tutorial is the second in a four-part tutorial on the basics of developing in SCA. It is most appropriate for Denali and Mont Blanc versions. If you're running Vinson or newer, the section on service files will still work but is a deprecated coding style. You may wish to follow up with a tutorial on service controllers, eg how to build a contact us form.

This article is the second in the series. The first part introduced the SuiteCommerce Advance development tools and file structure; this article builds on the previous one, teaching you about models and collections. By the end of the article our code will be able to create records and use templates to display information on the frontend.

Create a Model and a Collection

Models in Backbone are the heart of a JavaScript application and contain the interactive data and most of the logic that handles that data. However, SCA does it slightly differently: all of the data and logic that would be contained in this file lives in separate, SuiteScript-powered files. So, in the model file, we'll need to direct it to this file.

In the JavaScript folder, create Artist.Model.js and add the following to it:

define('Artist.Model',
  [
  'Backbone',
  'underscore'
  ],
  function (Backbone, _) {
    return Backbone.Model.extend({
      urlRoot: _.getAbsoluteUrl('services/Artist.Service.ss')
    });
  }
);

We'll create the file it points to later. Next we'll create a collection. Collections are simply an ordered set of models and generally handle server requests. Create Artist.Collection.js in your JavaScript folder and add the following:

define('Artist.Collection',
  [
  'Backbone',
  'Artist.Model'
  ],
  function (Backbone, Model) {
    return Backbone.Collection.extend({
      model: Model,
      url: _.getAbsoluteUrl('services/Artist.Service.ss')
    });
  }
);

We then add this to Artist.Router.js:

  • Add the model and collection as dependencies.
  • Add the collection as a property to the artistList function.
  • Add code to fetch data from the server and then show it; we already have view.showContent() – this will need to be moved into this code.

Thus, your code will look like the following:

define('Artist.Router', [
  'Backbone',
  'Artist.List.View',
  'Artist.Model',
  'Artist.Collection'
  ], function (Backbone, ListView, Model, Collection) {

  return Backbone.Router.extend ({

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

    routes: {
      'artists': 'artistList',
      'artists/new': 'newArtist',
      'artists/:id': 'artistDetails'
    },

    artistList: function () {
      var view = new ListView({application: this.application});
      var collection = new Collection();
      collection.fetch().done(function() {
        view.showContent();
      });
    }
  });
});

Run gulp local and check your local site again. When it refreshes, you should see an "internal error" message. If you look in the network tab of your browser's dev tools, you'll also see a 404 error for Artist.Service.ss. This means your code worked but the SuiteScript service wasn't found (which it won't, because we haven't created it yet).

Create a SuiteScript Service

SuiteScript services typically handle the create, read, update and delete (CRUD) operations of a module. A quick way to start is to copy and paste an existing service. Create Artist.Service.ss under the SuiteScript folder and then paste into it a copy of the service used in the Address module. Then perform a search and replace, changing Address to Artist.

If you look in this file you will notice a number of things. Firstly, there is a check to see if the user is logged in (which is required in the My Account section). Secondly, if that is true, it then gets the CRUD operation that user is requesting. Thirdly, there is a switch statement to handle what is returned, which in turn executes the requested operation based on the result. Lastly, there is some code to handle erroneous results. In all, the code pattern is typical across the services in the SCA application – this is because the logic specific to the module is going to be contained in a second file called Artist.Model.js, contained under the SuiteScript folder. While it has the same name as the other model file, it performs different functions: namely it interacts with the NetSuite API and contains SuiteScript, which is not something the Backbone model can do. Thus, it is very much a backend component.

Create Artist.Model.js in your module's SuiteScript folder. Inside of it, put in some skeleton code to handle the CRUD operations and the list operation:

define('Artist.Model',
  [
  'SC.Model'
  ],
  function (SCModel) {
    return SCModel.extend({
      name: 'Artist',
      get: function(id) {

      },
      list: function() {
        return [
          {internalid: 2, name: 'John', genre: 'Rock'},
          {internalid: 3, name: 'Christine', genre: 'Rock'},
          {internalid: 1, name: 'Mick', genre: 'Blues'},
          {internalid: 4, name: 'Lindsey', genre: 'Rock'},
          {internalid: 5, name: 'Stevie', genre: 'Pop'}
        ];
      },
      create: function(data) {

      },
      update: function(id, data) {

      },
      remove: function(id) {

      }
    });
  }
)

For now, we're going to hardcode the list function to return an array of values – we'll change this to an API call later. We need to update our distro.json file: we have told Gulp to include the frontend files, now we must tell it to include the backend files. After the JavaScript and Sass tasks, we must add to the list of dependencies for ssp-libraries. Add Artist.Model to the end of the list. Now, as this is backend code, you will have to deploy this to your server. Run gulp deploy and when that finishes, stop and start gulp local again. Once that's finished, go to your local site and log in. After visiting #artists, go to the network tab in your browser's dev tools console. After selecting Artist.Service.ss, you should see the hardcoded values returned in the preview.

Use the Collection and Template to Show Data

So we know our code works because we're seeing it being pulled into the console. The next steps will be using the collection to show this in the page. The first thing we need to do is import the helper for the collection view. Add Backbone.CollectionView, Backbone.CompositeView and Artist.Details.View as dependencies to Artist.List.View.js, and then add CollectionView, CompositeView and ArtistDetailsView as parameters to the function.

After that, add an initialize function and childViews property to the return statement:

    initialize: function (options) {
      CompositeView.add(this);
    },

    childViews: {
      'Artist.Collection': function() {
        return new CollectionView({
          'childView': ArtistDetailsView,
          'collection': this.collection,
          'viewsPerRow': 1
        });
      }
    },

So we've now introduced two new concepts: collection views and composite views. We use collections when we want to render a repeating list of models (items) that have the same view – we're doing this for the artists and their internal IDs. In other words, we need a view for showing each item and the final product is a collection view. Composites are also used for a repeating list of models but when you want to wrap them in another view for display purposes, which is what we will use to show a list (but you could, for example, use a composite to show a table). We've called the child view ArtistDetailsView, which doesn't exist yet but will contain styling for how we're going to show each item of the collection.

The viewsPerRow property is also added as it defines how many times to render the view per row; e.g., if we were using tables to display the items then setting this to 1 means a single item will be shown per <td>.

When it comes to populating areas in templates with collections of data, you use HTML data attributes on page elements. We've just created the Artist.Collection function, so in artist_list.tpl add the following code:

<h1>Artists</h1>
<table>
    <thead>
      <tr>
          <th>Internal ID</th>
          <th>Artist</th>
          <th>Genre</th>
      </tr>
    </thead>
    <tbody data-view="Artist.Collection"></tbody>
</table>

Note that we've created a template that's going to display a table, with the collection being used to populate the body of the table.

Back in Artist.Router.js swap the order of var view and var collection in the artistList function so that var collection is first. Then pass collection: collection as a value to ListView. Thus, the artistList function has now been passed the collection. Your function will look like this:

    artistList: function () {
      var collection = new Collection();
      var view = new ListView({collection: collection, application: this.application});
      collection.fetch().done(function() {
        view.showContent();
      });
    },

Now we need to pass these to our view. Open Artist.List.View.js and go the initialize function. This function now needs to accept options as parameters, which will expose the collection and application to the list view. Modify the function to the following:

    initialize: function (options) {
      CompositeView.add(this);
      this.application = options.application;
      this.collection = options.collection;
    },

So we've mentioned using a child view, so now we have to define it. Under the JavaScript folder create Artist.Details.View.js. It follows the standard boilerplate that we've been using so far, but also references to a template for the child view (which we haven't created yet) and also a function that returns an object with the data from the model to the view. Put the following in the file:

define('Artist.Details.View',
  [
  'Backbone',
  'artist_details.tpl'
  ],
  function (Backbone, artist_details_template) {
    return Backbone.View.extend({
      getContext: function () {
        return {
          'name': this.model.get('name'),
          'genre': this.model.get('genre'),
          'internalid': this.model.get('internalid')
        }
      },

      template: artist_details_template

    });
  }
)

The code mentions a template, so we have to create it now. Under the Templates directory, create artist_details.tpl. We've already created a collection that has a table in it, so this template will be used to process and build the rows in the table. In the template put the following:

<tr>
    <td>{{internalid}}</td>
    <td>{{name}}</td>
    <td>{{genre}}</td>
</tr>

SCA uses Handlebars for templating. This allows us to use curly braces to substitute in values that we want to appear on the frontend. We now have enough code to test this again, so run gulp local (if it's already running, stop and start it again) and the load the local version of the view in your browser again. Remember: if you have to log in again, make sure you change the URL to go back to the local version afterwards. You should see something like the following:

Summary

In this article we added a model and collection to our module, meaning that we put in the architecture for interacting with the NetSuite API to create and retrieve records. We also worked with templates to create pages on the frontend to display information. In the next article, we will put in a SuiteScript service and backend model to make use of this architecture, such as performing all of the standard CRUD operations.

You can download a copy of the files as they should be here SCA-First-Custom-Module-Part2.zip.

Part three of the series is available here.

Further Reading