Working with SGJC Controllers

If you are starting a new storefront implementation, Salesforce recommends using controllers instead of pipelines for your application code. For information on the differences between pipelines and controllers, see Comparing Pipelines and SGJC Controllers.

What are controllers?

Controllers are server-side scripts that handle storefront requests. Controllers manage the flow of control in your application, and create instances of models and views to process each storefront request and generate an appropriate response. For example, clicking a category menu item or entering a search term triggers a controller that renders a page.

Controllers are written in JavaScript and Salesforce B2C Commerce script. They must conform to the CommonJS module standard. For information on modules in B2C Commerce, see SiteGenesis Modules and Hooks and Using B2C Commerce Script Modules

The file extension of a controller can be either .ds or .js. Controllers must be located in the controllers folder at the top level of the cartridge. Exported methods for controllers must be explicitly made public to be available to handle storefront requests.

Example: SGJC Controller Hello.Js

/**
 * A hello world controller.
 *
 * @module controllers/Hello
 */
exports.World = function(){
    response.getWriter().println('Hello World!');
};
exports.World.public = true; 
Controllers can:
  • use require to import script modules: Any B2C Commerce script can be made into a CommonJS module and required by a controller.
  • use require to import B2C Commerce packages, instead of the importPackages method. This is the best practice way of referencing additional functionality from a controller. You can also use the require method to import a single B2C Commerce class. For example:
    var rootFolder = require('dw/content/ContentMgr').getSiteLibrary().root; 

While it isn't best practice, controllers can also:

  • call other controllers. It isn't recommended that controllers call each other, because controller functionality should be self-contained to avoid circular dependencies. In some cases, however, such as calling non-public controllers during the checkout process, it is unavoidable.
  • call pipelets: calling pipelets from within a controller is strongly discouraged. It's allowed while there are still pipelets that don't have equivalent B2C Commerce script methods, but will not be supported in future.
  • import packages. This is discouraged as it doesn't use standard JavaScript patterns, but is Rhino-specific.
  • call pipelines that don't end in interaction continue nodes. This is only intended for use with existing link cartridges and is highly discouraged for general development.
    Note: It isn't currently possible to call pipelines that end in interaction continue nodes.

Tools

You can use any IDE with a JavaScript editor to develop controllers. However, to upload code, you must either use UX Studio or an upload utility. Upload utilities are available from the Salesforce Commerce Cloud community repositories in GitHUb.

You can also use any standard JavaScript tools, including linters, static code analysis tools. For information on build tools and linters that are available with SiteGenesis, see SiteGenesis Setup.

Request URLs

A request URL for a controller has the format controller-function. For example, the request URL for the Hello.js controller World function looks like:
https://localhost/on/demandware.store/Sites-SiteGenesis-Site/default/Hello-World

See also: B2C Commerce URL Syntax Without SEO.

Reserved Names for Controllers

B2C Commerce has several system names that are reserved and can't be used for custom controllers and their functions. For a full list, see System Pipelines and Controllers.

Secure Requests

You might need to secure access to the exported functions of the controller. A controller function is only called if it has a public property that is set to true. All other functions that do not have this property are ignored by B2C Commerce and lead to a security exception if an attempt is made to call them using HTTP or any other external protocol.

Example: accessible controller

exports.World = function() {};
exports.World.public = true;

Guards

For controllers, SiteGenesis uses the concept of guards to wrap controller functions when they are exported. The functions in the guard module act as a request filter and let you specify multiple levels of access to controller functionality, such as:

A guard is a wrapper function that encapsulates a delegate function and only invokes it if its guard condition is satisfied. The guard conditions represent single preconditions that can also be combined with each other. The conditions use the B2C Commerce API to determine the properties of the current request and logic to determine whether to continue request processing or throw an error.

Example: require GET

exports.StartCheckout = guard.ensure(['get'], startCheckout);

Example: require HTTPS and that the customer is logged in.

exports.EditProfile = guard.ensure(['get', 'https', 'loggedIn'], editProfile);
Note: You can also create custom guards functions, such as "stagingOnly" or "loggedInAsEmployee".

Global Variables

Controllers do not have access to the Pipeline Dictionary, but they do have access to global variables, such as session, via the TopLevel package Global class.

Variable

Type

Description and example

customer

dw.customer.Customer

The current customer, either an anonymous customer or an authenticated customer with an account.

request

dw.system.Request

The current HTTP request

var httpUserAgent = request.httpUserAgent;

response

dw.system.Response

The current HTTP response

response.redirect(dw.web.URLUtils.https('Account-Show'));

session

dw.system.Session

The current session.

exports.onSession = function() {
    session.custom.device = getDeviceType();
    return new Status(Status.OK);
};

Request Parameters and Page Meta Data

Expression

Type

Description and example

request.httpParameterMap

dw.web.HttpParameterMap

The replacement for the CurrentHttpParameterMap variable in the Pipeline Dictionary.

request.pageMetaData

dw.web.PageMetaData

The replacement for the CurrentPageMetaData variable in the Pipeline Dictionary

Scripts can access these variables. For example, the render method of the View.js script, which renders a template, accesses most global variables and request parameters and passes them to the template, which accesses them via the pdict variable.
 render: function (templateName) {
...
        this.CurrentForms = session.forms;
        this.CurrentHttpParameterMap = request.httpParameterMap;
        this.CurrentCustomer = customer;
        this.CurrentSession = session;
        this.CurrentPageMetaData = request.pageMetaData;
        this.CurrentRequest = request;
        try {
            ISML.renderTemplate(templateName, this);
        } catch (e) {
            dw.system.Logger.error('Error while rendering template ' + templateName);
            throw e;
        }
        return this;

}});

Back to top.

Debugging

Almost any JavaScript-related error gets logged in the customerror_* log files, including errors from B2C Commerce scripts and controllers. The error_* log files contain Java-related errors on B2C Commerce, which are probably only useful for B2C Commerce users.

There's a special controller, conveniently called Error.js, that services uncaught errors. By default, B2C Commerce returns a 410 error, which indicates that the resource is no longer available. For those who prefer 404, it's fine to use as long as the related ISML template doesn't include <isslot>, <iscomponent>, or <isinclude> tags. For genuine server errors, it is more truthful to return a 500 error, but most merchants prefer not to send back this level of detail to their customers. When you want to handle these errors explicitly, you can use try/catch statements to do so.

Best Practices

Performance

Only import modules when you need them, not at the top of a module.

Using Controllers with OCAPI

If you intend to build a mobile or native app in addition to your storefront, you can use hooks to create modules that can be used by both your desktop storefront and mobile app.

Jobs

You cannot create jobs with controllers. Jobs can only be created using pipelines.