Developing Your First Custom SuiteCommerce Advanced Module: Part 4

This tutorial is the fourth 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.

In the preceding articles we built our module to the point where it is a fully functional form and list where can perform all of the basic API operations. In this article we're going to look at how we can make improvements to the user experience in the form of changes to site design and styling.

Conditional Changes in a Template

One thing we can change is put in some conditional changes into our template. We can use the isNew method we set in our getContext function to show different text depending on whether the user is creating a new artist record or updating an existing one. In artist_edit.tpl change the h1 tag to the following:

<h1>{{#if isNew}}New{{else}}Edit{{/if}} Artist Details</h1>

Go to your local site and try adding and updating records, you should see different heading text depending on the context of the operation. You could now, for example, put in similar logic for the submit button text.

Add a Menu Item

So we have the ability to create, read, and delete the records, but we don't have an intuitive way for users to access these records on the frontend other than by going directly to the URL. Let's add a way to navigate to these pages in the menu. The menu is dynamic, so we don't need to write the HTML ourselves – we just need to write some JavaScript.

Open Artist.js and add a new parameter to the top of the main return statement as follows:

      MenuItems: {
        parent: 'settings',
        id: 'artistslist',
        name: 'Artists List',
        url: 'artists',
        index: 0
      },

Our code added the following properties:

  • parent – the menu item the link will live under.
  • id – the HTML data-id parameter added to the link.
  • name – the label (text) shown on the link.
  • url – the URL to which the user will be sent to.
  • index – the position in the list it will appear.

After gulp local has compiled your code, visit your local My Account page again. You'll see, under the Settings parent menu item, a link to our Artists List page.

Add Breadcrumbs

If you click around the My Account section on your site you'll see that there are breadcrumbs above the top heading of every page. These easily added and are controlled by the view for that page. So, in Artist.List.View.js add in the following to the main return statement:

    getBreadcrumbPages: function() {
      return [{text: 'Artists', href: '/artists'}]
    },

Once gulp local updates, go to the list of artists on your local site and you'll see the breadcrumbs appear. We can repeat this process for Artist.Edit.View.js. Note, however, that we need different text depending on the context, so we can add in a conditional into our JavaScript as follows:

      getBreadcrumbPages: function() {
        if (this.model.isNew()) {
          return [
            {text: 'Artists', href: '/artists'},
            {text: 'New', href: '/artists/new'}
          ]
        } else {
          return [
            {text: 'Artists', href: '/artists'},
            {text: 'Edit', href: '/artists/' + this.model.get('internalid')}
          ]
        }
      }

If you notice, we've added in two entries for the return statement: this simply adds in an extra entry for the breadcrumbs. Now if you go to your local site and go to the form, you'll see the changes you've made.

Auto-Expanding Navigation

One small user experience improvement we can make is to the navigation: when a user visits a page related to the artist functionality, we can auto-expand the menu to show where the user is in context to the entire My Account section. To do this, add the following code to the return statements in Artist.List.View.js and Artist.Edit.View.js:

    getSelectedMenu: function() {
      return 'artistslist'
    }

getSelectedMenu and getBreadcrumbPages are examples of a number of helper functions that are built into the SCA code to make this sort of thing easy.

Prepare for Sass

At the moment our form looks pretty poor, so we should add some styling to it. SCA uses Sass for styling, which is a scripting language for CSS and allows us to extend it with new syntax, such as variables, mixins and nesting. The Sass is compiled by Gulp and outputted as CSS, which is then used in the browser.

Like JavaScript and templates, each module has its own Sass directory. To start you'll need to do the following:

  • Create a directory called Sass at the same level as the module's JavaScript and Templates directory.
  • Add an entry to the module's ns.package.json file as follows:
  •         "sass": [
                "Sass/*.scss"
            ]
  • In distro.json add "Artist" to the list of modules that have Sass to be included in myaccount.css (search the file for this). In other words, this means adding it as a dependency.

There are a number of SCA-specific conventions to do with Sass that we need to remember:

  1. When creating Sass files, the Sass files are modularized: one Sass file per template file. Furthermore, the names of the Sass files match the template they're styling, prefixed by an underscore.
  2. Within the template files, all classes are prefixed with the template name but using hyphens instead of underscores. So, for example, we're going to add some styling to artist_list.tpl so we'll be adding classes starting with artist-list-.
  3. There exists a large number of helper classes built in to SCA which you can use by using the @extend keyword in your code blocks. These live in the Sass files in the Modules > BaseSassStyles > Sass directory.

Style the List Template

At the moment, our artist list looks pretty bad – we should make it look better. In the Sass directory, create _artist-list.scss and put in it the following:

.artist-list-header {
    @extend .list-header;
    position: relative;
    margin-bottom: $sc-base-margin * 3;
    display: inline-block;
    width: 100%;
}
.artist-list-title {
    @extend .list-header-title;
    float:none;
    @media (min-width: $screen-sm-min) {
        float: left;
    }
}
.artist-list-header-button-new {
    @extend .list-header-button;
    margin-top: $sc-base-padding * 4;
    position: absolute;
    top:25;
    z-index:1;
    right: 0;

    @media (min-width: $screen-sm-min) {
        margin-top: 0;
        z-index:0;
        top: 0;
        margin-bottom: $sc-base-margin * 3;
    }
}
.artist-list-results-container
{
    @media (min-width: $screen-md-min) {
        .artist-list-results-table-header-row-id {
            width: 20%;
        }
        .artist-list-results-table-header-row-artist {
            width: 30%;
        }
        .artist-list-results-table-header-row-genre {
            width: 30%;
        }
        .artist-list-results-table-header-row-actions {
            text-align: center;
        }
    }
}
.artist-list-results-table {
    @extend .recordviews-table;
}
.artist-list-results-table-header {
    @extend .recordviews-row-header;
    border-top: 1px solid $sc-color-theme-light;
}

This is a lot of CSS, we'll go through it in a minute. However, you'll notice that we've put in a lot of classes; this means that we have to update our list template so that they are marked up with these classes. In artist_list.tpl, change the HTML to the following:

<section class="artist-list">
    <header class="artist-list-header">
        <h1 class="artist-list-title">Artists</h1>
        <a href="artists/new" class="artist-list-header-button-new">Create New Artist</a>
    </header>

    <div class="artist-list-results-container">
        <table class="artist-list-results-table">
            <thead class="artist-list-results-table-header">
                <tr class="artist-list-results-table-header-row">
                    <th class="artist-list-results-table-header-row-id">Internal ID</th>
                    <th class="artist-list-results-table-header-row-artist">Artist</th>
                    <th class="artist-list-results-table-header-row-genre">Genre</th>
                    <th class="artist-list-results-table-header-row-actions" colspan="2">Actions</th>
                </tr>
            </thead>
            <tbody data-view="Artist.Collection"></tbody>
        </table>
    </div>
</section>

Restart gulp local and go to your local site. When you visit the list page, it should look slightly better – so what happened? If you look at the Sass file again, you'll see that for a lot of our classes simply specify @extend .[class name]. The @extend directive is used in Sass where one class should all the styles of another class, as well as its own styles. In regular CSS, this means that you would attach multiple classes to the element your styling and build up the styles in the HTML. The downside to this is that you always have to remember to chain classes together when templating, meaning a maintenance headache and non-semantic style concerns. @extend avoids these problems by telling Sass that one selector should inherit the styles of another selector.

@extend works by inserting the extending selector wherever the extended selector is. For example, we have an extending class called .artist-list-results-table that is extending .recordviews-table; this means that wherever the extended class is used, the extending class is inserted. If there are other styles that we have declared for the extending class, they are outputted separately.

Style the Details Template

The list and table itself are now styled, but the individual rows and cells of the table are not – this is because the details template is separate (and the Sass for it is separate too). Replace the code in artist_details.tpl with the following:

<tr class="artist-details-results-table-row">
    <td class="artist-details-results-table-row-id">
        <span class="artist-details-results-table-row-id-label">Internal ID: </span>
        <span class="artist-details-results-table-row-id-value">{{internalid}}</span>
    </td>
    <td class="artist-details-results-table-row-name">
        <span class="artist-details-results-table-row-name-label">Name: </span>
        <span class="artist-details-results-table-row-name-value">{{name}}</span>
    </td>
    <td class="artist-details-results-table-row-genre">
        <span class="artist-details-results-table-row-genre-label">Genre: </span>
        <span class="artist-details-results-table-row-genre-value">{{genre}}</span>
    </td>
    <td class="artist-details-results-table-row-action-edit">
        <a class="artist-details-results-table-row-action-edit-button" href="/artists/{{internalid}}">Edit</a>
    </td>
    <td class="artist-details-results-table-row-action-delete">
        <button class="artist-details-results-table-row-action-delete-button" data-action="remove" data-id="{{internalid}}">Delete</button>
    </td>
</tr>

You'll notice that this is quite a significant change to the template:

  • The class names mirror the same pattern as the list template.
  • You'll also see that the class names build up gradually as you go deeper in nested elements; this is one of the benefits of Sass – every class is meaningful.
  • We've added in labels to each of the cells. We're going to hide these for most responsive views but we're going to add in some styling later that collapses the tables on narrower views and we'll need these labels for the values.

So now we need to add the styling for these rows. Create _artist-details.scss and put the following in it:

.artist-details-hide-label {
    @media (min-width: $screen-md-min) {
        display: none;
    }
}
.artist-details-results-table-row {
    @extend .recordviews-row;
}
.artist-details-results-table-row:hover {
    @extend .recordviews-row:hover;
}
.artist-details-results-table-row-id-label {
    @extend .artist-details-hide-label;
}
.artist-details-results-table-row-name-label {
    @extend .artist-details-hide-label;
}
.artist-details-results-table-row-genre-label {
    @extend .artist-details-hide-label;
}
.artist-details-results-table-row-action-edit-button {
    @extend .button-small;
    @extend .button-tertiary;
    @media (max-width: $screen-md-min) {
        margin-bottom: $sc-small-margin;
    }
}
.artist-details-results-table-row-action-delete-button {
    @extend .button-small;
    @extend .button-primary;
    background-color: $sc-color-error;
    border-color: darken($sc-color-error,20);
    @media (max-width: $screen-md-min) {
        margin-bottom: $sc-small-margin;
    }
}
.artist-details-results-table-row-action-delete-button:hover {
    background-color: saturate(lighten($sc-color-error, 4), 4);
}

If you stop and start gulp local again, and go to your local site, you will see the changes. Also, if you resize the width of the window, you'll see that the styling changes as you make it larger and smaller. You'll notice in the above code that we added some styling that only applies when the window's width is less/more than a 'breakpoint'. Breakpoints (such as $screen-md-min) are defined by Bootstrap, a responsive frontend framework. Bootstrap provides a lot of ready-to-use styling and helpers to aid us in styling pages quickly and consistently.

Another thing you'll notice is that, for example, we've used the saturate and lighten functions; these, and other useful functions, are available via Sass and enable us to make ad-hoc changes to colors – rather than hardcoding values throughout our CSS, we just have one file (SuiteCommerce > BaseSassStyles > Sass > variables > _colors.scss).

Style the Edit Template

Now that we've styled two templates already, there is just one left and that is the form where users can add or edit artist details. Edit artist_edit.tpl and replace it with the following:

<section class="artist-edit">
    <header class="artist-edit-header">
        <h1 class="artist-edit-header-title">{{#if isNew}}New{{else}}Edit{{/if}} Artist Details</h1>
    </header>
    <form class="artist-edit-form">
        <fieldset>
            <small>Required <span class="artist-edit-form-label-required">*</span></small>
            <div class="artist-edit-form-name" data-input="name" data-validation="control-group">
                <label class="artist-edit-form-label" for="name">Name <span class="artist-edit-form-label-required">*</span></label>
                <span data-validation="control"><input class="artist-edit-form-input" type="text" name="name" id="name" value="{{name}}"></span>
            </div>
            <div class="artist-edit-form-genre" data-input="genre" data-validation="control-group">
                <label class="artist-edit-form-label" for="genre">Genre <span class="artist-edit-form-label-required">*</span></label>
                <span data-validation="control"><input class="artist-edit-form-input" type="text" name="genre" id="genre" value="{{genre}}"></span>
            </div>
        </fieldset>
        <div class="artist-edit-form-submit">
            <button class="artist-edit-form-submit-button" type="submit">{{#if isNew}}Add{{else}}Update{{/if}}</button>
        </div>

        <div data-type="alert-placeholder"></div>
    </form>
</section>

As with the templates before, we've added more semantic elements to it and marked it up with classes that follow a clear pattern: while you're not required to follow this guideline, do what's best for your site.

Next you need to create Sass > _artist-edit.scss and put the following in it:

.artist-edit {
    @extend .box-column-bordered;
}
.artist-edit-header {
    margin-bottom: $sc-medium-margin;
}
.artist-edit-form-name, .artist-edit-form-genre, .artist-edit-form-submit {
    @extend .control-group;
}
.artist-edit-form-label {
    @extend .input-label;
}
.artist-edit-form-input {
    @extend .input-large;
}
.artist-edit-form-label-required {
    @extend .input-required;
}
.artist-edit-form-submit-button {
    @extend .button-primary;
    @extend .button-medium;
}

This styling is pretty basic but does the job. As we've added a new file, we'll need to restart gulp local again, and when you refresh your local site you should see the form styled simply, but nicely. When creating things like lists and forms, it is worth investigating the structure of similar modules (i.e., their templates and Sass).

Summary

This particular article covered styling templates using Sass. We also added UI components such as breadcrumbs and an auto-expanding navigation. Throughout the examples we used a number of helper functions and existing classes to style the interface with minimal effort using, for example, the @extend keyword in Sass.

In conclusion, this series of articles has taken you through the steps to building a new part of an SCA site. It has covered not only the particulars of the NetSuite application and API, but also the frameworks that we use on the frontend of the site. The process moved from coding the backbone for our module to styling it; returning hardcoded dummy data to doing CRUD operations. Now, after all of this, you should be comfortable in creating your own module.

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

Further Reading