MFRA

Download as pdf or txt
Download as pdf or txt
You are on page 1of 54

MFRA

MFRA

Table of Contents
1. Getting Started with Mobile First Reference Architecture (MFRA) ................................................................ 1
1.1. MFRA overview .................................................................................................................................... 1
1.1.1. MFRA hooks ............................................................................................................................... 9
1.1.2. Module lookup .......................................................................................................................... 11
1.1.3. MFRA modules ......................................................................................................................... 15
1.1.4. MFRA controllers ..................................................................................................................... 20
1.1.5. MFRA standards compliance .................................................................................................... 22
1.2. MFRA Setup ........................................................................................................................................ 23
1.2.1. Installing and configuring MFRA tools .................................................................................... 25
1.3. Building MFRA ................................................................................................................................... 26
1.4. Uploading code for MFRA .................................................................................................................. 27
1.5. Configuring MFRA .............................................................................................................................. 29
1.5.1. Configuring storefront preferences for MFRA ......................................................................... 30
1.5.2. Importing MFRA data into an instance .................................................................................... 31
1.5.3. Configuring the Mobile First Reference Architecture (MFRA) cartridge path ......................... 31
1.6. Troubleshooting MFRA ....................................................................................................................... 32
1.7. Testing Mobile First Reference Architecture (MFRA) ....................................................................... 32
1.8. Customizing Mobile First Reference Architecture (MFRA) ............................................................... 33
1.8.1. Adding custom cartridges ......................................................................................................... 34
1.8.2. Building your cartridge stack ................................................................................................... 37
1.8.3. Customizing templates .............................................................................................................. 37
1.8.4. Customizing models .................................................................................................................. 39
1.8.5. Customizing controllers and routes .......................................................................................... 39
1.8.6. MFRA forms ............................................................................................................................. 42
1.9. Contributing to Mobile First Reference Architecture .......................................................................... 51

i
MFRA

1. Getting Started with Mobile First Reference Architecture


(MFRA)
Commerce Cloud provides twp sample sites, called MobileFirst and MobileFirstGlobal, which you can
customize. For developers, it provides sample code that you can inspect to see current best practices. For
merchants, it provides sample data for catalogs, categories, products, and other merchandising configurations.
If you want to compare these to the SiteGenesis and SiteGenesis global sites, you can install them on the same
Sandbox.

Register with Salesforce Commerce Cloud XChange to access Commerce Cloud Support and developer forums.

Open a ticket to get a Sandbox from Commerce Cloud Support if you are a customer or partner developer. If
you are a link partner developer, get a Sandbox from your link partner program manager.

Note
The setup instructions assume you are familiar with and have access to git. It also assumes you have a
BitBucket account. If you do not, follow the instructions available here.

For an introduction to MFRA architecture and important concepts for implementation, see MFRA Overview.

Getting started with MFRA has four basic steps:

• Setup-set up your local machine with the MFRA source code and your development and deployment tools.

• Build-build MFRA and push the code up to the server.

• Configure-add MFRA data to your instance so that you can see products and catalogs on the site.

• Customize-after you've seen the basic MFRA application, explore customizing it for your organization,
brand, and products.

Next Step: MFRA Overview

1.1. MFRA overview


Mobile First Reference Architecture (MFRA) represents a new approach to building and customizing your
storefront site. Salesforce provides an app_storefront_base cartridge and a server module.

The base cartridge contains only the functionality common to most sites. Additional functionality can be layered
over the base cartridge with plugin cartridges, LINK cartridges, and custom code cartridges. Commerce Cloud
provides plugin cartridges that provide additional features, such as wish lists, gift registries, apple pay, or
product comparison. LINK partners, such as PayPal and BizarreVoice, provide LINK cartridges that make it
easy to do third-party integrations. You can create one or more custom cartridges to override portions of the
base cartridge and customize the functionality and branding of your storefront site.

The server module registers routes that map a URL to code that is executed when Digital detects the URL.The
server module uses a modern JavaScript approach that is conceptually similar to NodeJS's Express.

The app_storefront_base cartridge includes a set of models that use Digital script to retrieve data from
the platform for a functional area of the application, such as orders, and provide a JSON object that can be
used to render a template. The server module provides a set of objects that contain data from HTTP requests,
responses, and session objects.

The app_storefront_base cartridge models and server module objects are guaranteed to be backward
compatible between major point releases. This allows you to more easily adopt new features and maintain
a clear distinction between base and custom code, which can be very helpful in troubleshooting issues and
adopting bug fixes. It also paves the way for more modular functionality to be developed in the future.

1
MFRA

Important
Editing the app_storefront_base cartridge or server module voids the guarantee of backward
compatibility and hinders feature and fix adoption. Instead, use Commerce Cloud methods to extend
functionality in the base cartridge and add additional middleware . The JSON objects created by the
server module and app_storefront_base models retain their structure and never remove properties
between point releases. However, Salesforce might change the way that these objects are created.

MFRA architecture
A storefront site using Mobile First Reference Architecture has a cartridge stack. The order of the cartridge
stack is set by the cartridge path in Business Manager. The stack is determined by the cartridges on the cartridge
path.
A typical cartridge stack includes several layers:
• custom layer: add specific customizations for your brand and organization.
• LINK layer: adds third-party functionality to your site.
• plugin layer: enhances your ecommerce capabilities provided by Commerce Cloud or anyone else in the
Salesforce community.
• base layer: contains the core ecommerce functionality common to all sites. It is provided by Commerce
Cloud.

The cartridge path is always searched left to right, and the first controller or pipeline found with a particular
name is used. That means that cartridges earlier on the path override the functionality of those later on the path.
The cartridge path for this stack is:

app_custom_mybrand:app_custom_mysite:LINK_bazaarvoice:LINK_wordpress:plugin_applepay:plugin_compa

Custom layer
Branding and custom functionality specific to the customer. Perform all customizations of the base, LINK, and
product cartridges in custom cartridges to allow for easy adoption of future features.
Commerce Cloud recommends naming all custom cartridges with app_custom_* to make it easy to distinguish
them.

2
MFRA

LINK layer
Integrates features from LINK partners, such as payment providers, tax services, and other services.
You might need to import LINK partner data, such as tax tables or inventory feeds. See the documentation for
the specific LINK cartridge for more information.
Plugin layer
Cartridges to integrate additional products and features. Plugins are provided by Commerce cloud for optional
site features, such as product comparison, wish list, and gift registry.
Plugins can create custom objects or data specific to a product or feature.
Base layer
Provides essential ecommerce functionality common to all sites. This functionality is only changed by the
Commerce Cloud team or through contributions to the project repositories on BitBucket. The base cartridge
includes best practice code for features used by the majority of customers.
Some of the features in the base cartridge are configured in Business Manager, such as pick-up in store.
Commerce Cloud provides demo data that allows you to view and explore the base cartridge as a working site.

Note
Data for any cartridge is imported into the platform and used by the code in each cartridge, but is not
stored in the cartridge itself. See MFRA setup for the location of demo data provided by Commerce
Cloud.

Base cartridge architecture


We recommend that you never modify anything in app_storefront_base. Instead, create your own cartridge
and add it as an overlay cartridge in the Business Manager cartridge path. Using an overlay cartridge enables
you to upgrade to a newer version of MFRA without having to manually cherry-pick changes and perform
manual merges. You might still need to adjust your custom code, but the upgrade and feature adoption process
is quicker and less painful.

sitegenesis-mobile-first
dw.json //used to upload code
package.json
cartridges
### app_storefront_base
### client //client-side javascript and CSS
# ### js
# ### scss
### controllers // business logic for the application
### forms
### models //gets data from server and provides it as JSON objects
### scripts //reusable functionality
# ### cart
# ### factories
# ### helpers
# ### payment
# ### search
### static //static resources unlikely to change, such as branding images
### templates //ISML templates
### default
### resources

3
MFRA

...

### modules
### server
...

The modules folder and the server module


In the global modules folder, MFRA has a server module that provides the routing functionality for
controllers. Every MFRA controller requires this module. Any JavaScript module included in the modules
folder is globally available and can be required without a path in any controller or script.
The following example requires the server module in the modules folder.

var server = require('server');

The modules folder is a separate cartridge, so you can easily upload it to the platform, but you don't have to
include it on the cartridge path. For information on using the server module, see extending the base cartridge
architecture.
Do not directly extend or customize the server module. If you customize the server module directly,
Salesforce Commerce Cloud cannot be held responsible for changes that are not backward compatible in future
versions of MFRA.
However, if you want to extend or customize server module functionality, you can create your own module
and place it in the modules folder. You can require server module functions and extend them in your own
module as you would any JavaScript function.

ViewModules and controllers


MFRA uses a variant of Model-View-Controller architecture. Controllers handle information from the user,
create ViewModules, and render pages. The ViewModules request data from the platform, convert Digital script
API objects into pure JSON objects, and apply business logic.

A controller requests data from the platform and passes the returned objects to a ViewModel to be converted
into a serializable JavaScript Object.
A ViewModel differs from a standard Digital script objects in the following ways.
• A Digital Script object sometimes behaves like a Java object, not a JavaScript object, whereas a model is a
serializable JavaScript object.
• A ViewModels provides the information needed to render pages in the application and often combines data
from multiple Digital script objects.

4
MFRA

Note
You might also see ViewModels referred to simply as models, since that is the name of the folder where
they are located in the application.

Defining endpoints
MFRA defines Commerce Cloud endpoints (URIs) with a Controller-RouteName syntax. Defining an
endpoint depends on the filename of your controller and the routes defined in the controller.
The syntax of your endpoint depends on the SEO options you choose for your site. While developing, it is
easiest to use the Commerce Cloud standard URL syntax without additional SEO options, since it makes it
simple to test your controllers.
Example: Defining an endpoint for your home page
Assume that you want to create an endpoint in Commerce Cloud standard URL syntax that looks like the
following:

http://www.mystore.com/on/demandware.store/Sites-YourShopHere-Site/EN_US/Home-Show

You create Home.js module in the controller folder with the following code:

'use strict';

var server = require('server'); //the server module is u


var cache = require('*/cartridge/scripts/middleware/cache');

server.get('Show', cache.applyDefaultCache, function (req, res, next) { //registers the Show rou
res.render('/home/homepage'); //renders the hompage te
next(); //notifies middleware chain that it can move to the next step or terminate
});

module.exports = server.exports();

The route of Home-Show is determined by the module name (Home.js) and the first parameter in the
server.get function (Show).

Defining routes
The server module use function takes the RouteName of the function to execute as the first argument. MFRA
provides two utility methods to call the use function (server.get and server.post) that make sure that the
URI is either a GET or POST request.
This functionality is different from many other frameworks, which define routes by overriding anchor
functionality in the URI to define actions to execute.

How controllers are executed


When a request is made via a URI in the web browser, the last part of the endpoint specifies the Controller-
RouteName to execute. In this example, the filename is Page.js and the route name is Show. The Page.js
file must be stored in the controllers folder for the platform to recognize it as a controller.

http://www.mystore.com/on/demandware.store/Sites-YourShopHere-Site/EN_US/Page-Show

The Commerce Cloud Digital server executes the first controller in the cartridge path with the correct name.
Digital recognizes both SGJC and MFRA controllers as equal and does not prioritize one over the other. If no
controller is found in the cartridge path, the server executes the first pipeline file found in the cartridge path.
Because MFRA does not use pipelines, this pipeline is the first one found in a custom cartridge.

5
MFRA

When the controller is located, all require statements at the beginning of the controller are executed. These
statements must include a require for the server module. The server module is located in the modules
folder, which is a peer of the app_storefront_base cartridge. Requiring the server module returns an
empty server object.

The next line to be executed is:

module.exports = server.exports();

This line is present in all controllers. Calling server.exports() causes the server to register all functions in
the controller that use the server.get, server.post, or server.use functions as routes. The server then
executes the function whose first parameter matches the route name in the URI.

For example, if you assume the URI in the previous example that ends in Page-Show, Digital registers all
functions in Page.js and then executes the Show function.

server.get('Show', locale, function (req, res, next) {


res.render('/home/homepage');
next();
});

Rendering a JSON string


Assume this code is saved to a file named Page.js.

var server = require('server');


server.get('Show', function(req, res, next) {
res.json({ value: 'Hello World'});
next();
});
module.exports = server.exports();

The Page.js file creates a new route for a URL.

http://sandbox-host-name/on/demandware.store/site-name/en_US/Page-Show

Whenever that URL is called, the provided function is executed and renders a page with:

Content-Type: application/json

and body

{ value: 'Hello World '}

Protecting route access


You can enhance this code by adding the server.middleware.https parameter after Show, to limit this
route to only allow HTTPS requests. This example restricts the Account-Show route to HTTPS. This is one of
the middleware functions provided by Commerce Cloud.

server.get('Show', server.middleware.https, function (req, res, next) {


var accountModel = getModel(req);
if (accountModel) {
res.render('account/accountdashboard', {
account: accountModel,
accountlanding: true

6
MFRA

});
} else {
res.redirect(URLUtils.url('Login-Show'));
}
next();
});

Note
Information about middleware filtering classes is available in the MFRA JSDoc in the server-side global
documentation for the server module.

Using server.use, server.get, or server.post


For a server.get or server.post function, the first parameter is always the name of the route (the URL
endpoint). The last parameter is always the main function for the endpoint. Usually, the main function in the
controller renders a page for the storefront or redirects to another controller.

You can add as many parameters in between the first and last parameter as you need. Each parameter specifies a
function to be executed in order and can either allow the next step to be executed (by calling next() or reject a
request by calling next(new Error()).

Example 1: conditionally executing a middleware step

This example shows a main function that conditionally executes next()or next(new Error()) depending on
whether an applepay order is being placed.

server.post('Submit', function (req, res, next) {


var order = OrderMgr.getOrder(req.querystring.order_id);

if (!order && req.querystring.order_token !== order.getOrderToken()) {


return next(new Error('Order token does not match'));
}

var orderPlacementStatus = orderHelpers.placeOrder(order);

if (orderPlacementStatus.error) {
return next(new Error('Could not place order'));
}

var orderModel = orderHelpers.buildOrderModel(order);


res.render('checkout/confirmation/confirmation', { order: orderModel });
return next();
});

The code executed between the first and last parameter is referred to as middleware and the whole process is
called chaining. You can create your own middleware functions to limit route access, add information to the
data object passed to the template for rendering, or for any other purpose. One limitation to this approach is that
you must call the next function at the end of every step in the chain; otherwise, the next function in the chain is
not executed.

Middleware
Each step of a middleware chain is a function that takes three arguments: req, res, and next, in that order.

req

7
MFRA

req stands for request and contains information about the server request that initiated execution. The req
object contains user input information, such as the content-type that the user accepts, the user's login and locale
information, or session information. The req argument parses query string parameters and assigns them to the
req.querystring object.

res

res stands for response and contains functionality for outputting data back to the client. For example,

• res.cacheExpiration(24): sets cache expiration to 24 hours from now.


res.render(templateName, data) outputs an ISML template back to the client and assigns data to
pdict.

• res.json(data) prints out a JSON object back to the screen. It is helpful in creating AJAX service
endpoints that you want to execute from the client-side scripts.

• res.setViewData(data) does not render anything, but sets the output object. This can be helpful if you
want to add multiple objects to the pdict of the template, which contains the information for rendering
that is passed to the template. setViewData merges all the data that you passed into a single object, so
you can call it at every step of the middleware chain. For example, you might want to have a separate
middleware function that retrieves information about a user's locale to render a language switch on the
page. The output object of the ISML template or JSON is set after every step of the middleware chain is
complete.

You can also use the ViewData object to extend the data created in a controller that you are extending. You
don't have to duplicate the logic used in the original controller to get the data. You only have to add any
additional data to the ViewData object and render it.

next()

Executing the next function notifies the server that you are done with a middleware step so that it can execute
the next step in the chain.

The power of this approach is that by chaining multiple middleware functions, you can compartmentalize your
code better and extend or modify routes without having to rewrite them.

Event emitters
The server module emits events at every step of execution and you can subscribe and unsubscribe to events
from a given route. Use an event emitter to override the middleware chain, by removing the event listener and
creating a new one. However, Salesforce recommends replacing a route if you need to change individual steps
in a middleware chain. While MFRA does supply removeListener and removeAllListener functions, they
do not recognize named event emitters. For this reason, it is not possible to use Step event emitters to override
a specific step in the middleware chain.

The following is a list of currently supported events:

• route:BeforeComplete is emitted before the route:Complete event but after all middleware
functions. Used to store user submitted data to the database; most commonly in forms.

• route:Complete is emitted after all steps in the chain finish execution. Subscribed to by the server to
render ISML or JSON back to the client.

• route:Redirect is emitted before res.redirect execution.

• route:Start is emitted as before middleware chain execution.

• route:Step is emitted before execution of each step in the middleware chain.

All events provide both req and res as parameters to all handlers.

8
MFRA

Subscribing or unsubscribing to an event allows you to do complex and interesting things. For example,
the server subscribes to the route:Complete event to render ISML back to the client. If you want to
use something other than ISML to render the content of your template, you can unsubscribe from the
route:Complete event and subscribe to it again with a function that uses your own rendering engine instead
of ISML, without modifying any of the existing controllers.

OnRequest and OnSession event handlers


The OnRequest and OnSession event handlers that were implemented as pipelines in SGPP and as controllers
in SGJC are not used in MFRA. You still have access to request and session data using the middleware req
(request) and res (response) objects, but MFRA avoids using OnRequest and OnSession anywhere in our code
outside of the server module.

If you want to implement OnRequest and OnSession, you must use hooks, because Digital looks for OnSession
as the controller name and the new architecture does not do that. The only difference between a hook and
controller is that the hook does not have access to req and res objects.

Extending base cartridge architecture


Salesforce Commerce Cloud owns and maintains the app_storefront_base cartridge. The base cartridge is
hosted on BitBucket and anyone can contribute to it through a pull request. The Commerce Cloud MFRA team
must test and approve all pull requests before they are accepted.

The app_storefront_base cartridge contains controllers for business logic, models with JSON objects
populated from the Commerce Cloud Digital Script API, and ISML templates. It also contains the modules
directory with a server module for MFRA.

Note
Anything in the modules folder or TopLevel package is globally available and can be required without
a path. You can add your own custom modules to the modules folder.

Extension example plugin_applepay

Commerce Cloud provides a sample plugin cartridge as part of the MFRA project that demonstrates how to
selectively add custom functionality to the base cartridge. Add this cartridge to the left of the base cartridge on
the cartridge path to observe the functionality.

Commerce Cloud has published an NPM node named sgmf-scripts that has tools to compile the CSS and scripts
for your storefront site. Use the tools to create a CSS and client-side JavaScript that includes functionality from
other cartridges.

Best practices for Mobile First Reference Architecture maintenance


Make sure to regularly update your app_cartridge_base and server module to have access to the most up
to date security and bug fixes. Regularly updating your base cartri

See also MFRA hooks and Getting started with Mobile First Reference Architecture (MFRA)

1.1.1. MFRA hooks


Use hooks to configure a piece of functionality to be called at a specific point in your application flow or at a
specific event.

You can use these hooks with a Commerce Cloud storefront application:

• OCAPI hooks—Digital provides extension points to call scripts before or after specific OCAPI calls.
These hooks are defined by Commerce Cloud.

9
MFRA

• Custom hooks—you can define custom extension points and call them in your storefront code using the
Digital script System package HookMgr class methods. You can then access the hook in either OCAPI or
your storefront code. This makes them useful for functionality in a multichannel set of applications based
on the same site.

Hook Definition
The package.json file points to the hook file for a cartridge, using the hooks keyword.

{
"hooks": "./cartridge/scripts/hooks.json"
}

The hook file defines a uniquely named extension point and a script to run. Implement hook scripts as A
CommonJS module, so that the script identifier is a module identifier and can be a relative path or any other
valid module identifier.

OCAPI Hook Example

hook.json file defines a dw.ocapi.shop.basket.calculate hook that calls the calculate.js script.

{
"hooks": [
{
"name": "dw.ocapi.shop.basket.calculate",
"script": "./hooks/cart/calculate.js"
},
{
"name": "app.payment.processor.default",
"script": "./hooks/payment/processor/default"
},
{
"name": "app.payment.processor.basic_credit",
"script": "./hooks/payment/processor/basic_credit"
},
{
"name": "app.validate.basket",
"script": "./hooks/validateBasket"
}
]
}

This example shows an OCAPI hook and several custom hooks. The OCAPI hook runs a script to calculate
the cart in the scripts/hooks/cart directory. The The custom hooks can be called if you use the System
package HookMgr class callHook method. These hooks are all in subdirectories of the scripts/hooks
directory.

Custom Hook Example

This example calls the hook from calculate.js.

return dw.system.HookMgr.callHook('app.payment.processor.default', 'Handle', {


Basket : cart
});

10
MFRA

Running multiple hooks for an extension point


You can register multiple modules to call for an extension point in a single hooks.json file. However, you
cannot control the order in which the modules are called. When you call multiple modules, only the last hook
called returns a value. All modules are called, regardless of whether any of them return a value.
At runtime, Digital runs all hooks registered for an extension point in all cartridges in your cartridge path in the
order of the cartridges on the path. So if each cartridge registers a module for the same hook, the modules are
called in cartridge path order of the cartridges in which they are registered.
Note: Because hooks are called in the order of the cartridges on the path, when you change the order of the
cartridges, you also change the order in which hooks are called.

Error logging
Controller and script logging is available.
• Custom error log – Contains the hierarchy of controller and script functions and the line numbers related
to exceptions thrown. Intended for developers to debug code.
• System error log – Primarily used for Commerce Cloud Support.
Example: Custom error log

Error while executing script 'test_cartridge_treatascustom/cartridge/controllers/TestController.j


at test_cartridge_treatascustom/cartridge/controllers/TestController.js:21 (isml)
at test_cartridge_treatascustom/cartridge/controllers/TestController.js:52 (anonymous)

See also: MFRA modules

1.1.2. Module lookup


Digital supports the Modules 1.1.1 CommonJS specification so that you can access JavaScript or Digital script
modules as storefront script code. Unlike traditional Digital script files, these modules do not have to be located
in cartridges. For more information see Path lookup behavior of the require method.

Note
This topic assumes that you are familiar with the Modules 1.1.1 CommonJS specification.

What is a Digital script module?


A CommonJS-compliant Digital script module is either a script file (with a .js, .json, or .ds extension) or a
directory containing script files. A module can hide private data while exposing public objects and methods
via a free variable named exports. A free variable is accessible within a function, but it's not a local variable
defined within the function nor a parameter of the function. A free variable might be (but does not have to be) a
global variable.

Note
MFRA uses module.exports, instead of simply exports, because it accepts objects.

Before version 13.6, you had to use the importScript and importPackage methods to access other scripts.
These methods automatically export everything into the global context. Now you can access CommonJS-
compliant modules using the require method and these modules do not automatically export everything into
the global context.
Within a script file, you load a CommonJS-compliant Digital script module using the
TopLevel.global.require(path:String) method. After the module is loaded, you can use any of its
exported variables or methods.

11
MFRA

Example 1: creating the mod1.js module

In this example, the mod1 module exports the doit() method and the aaa variable, but hides the
localMethod().

exports.aaa = "bbb";
exports.doit = function() {
return "done.";
}
var localVariable = 1;

/**
* @param {String} Error message.
* @returns {Error} New object.
*/
function localMethod(message, localVariable) {
return new Error({message: localVariable});
}

Example 2: loading the mod1 module

In this example, the script loads the mod1 module, retrieves the aaa variable from the module, and runs the
doit() method.

/*
* @output ModuleId : String
* @output Mod1_1 : String
* @output Mod1_2 : String
*/
var m1 = require( './mod1.js' );

function execute( pdict )


{
pdict.ModuleId = module.id;
pdict.Mod1_1 = m1.aaa;
pdict.Mod1_2 = m1.doit();

return PIPELET_NEXT;
}

Path lookup behavior of the require method


Digital's TopLevel.global.require(path:String) method has different lookup behavior than the
require() function as specified in the Modules 1.1.1 CommonJS specification.

Path lookup relative to the current module

If the path argument starts with "./" or "../" then it loads relative to the current module. The module can be a
file or a directory. A file extension is acknowledged, but not required. If it is a directory, a package.json or a
main file is expected in the directory.

Note
A main file is a file named main, with either a .js, .ds, or .json extension.

12
MFRA

If the package.json file does not contain a main property, then the script defaults to the main file in the
directory, if one exists. Access to parent files cannot go beyond the top-level version directory. Access to other
cartridges is allowed.
Path lookup relative to the current cartridge
If the path argument starts with "~/" it is a path relative to the current cartridge. This is relative to the top level
of the current cartridge.
If you want to reference a script in the current cartridge. In MFRA, this is only used for scripts that you do not
expect to be extended or overridden by other cartridges in the stack.

var myscript = require('~/cartridge/scripts/myscript');

Path lookup relative to the start of the cartridge path


If the path argument starts with "*/" it is a path relative to the start of the cartridge path. This is used in all
require statements in the app_storefront_base cartridge, so that if you require a controller or model from the
base cartridge into your own custom cartridge, with the intention of overriding or extending it, the functionality
that the base cartridge requires is the latest on the cartridge path.
For example:
Assume the following cartridge path:

app_storefront_custom:plugin_stores:app_storefront_base

Assume that you want to extend the Stores.js controller in the base cartridge, which requires the storeHelpers.js
script

var StoreHelpers = require('*/cartridge/scripts/helpers/storeHelpers');


var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');

Assume also that the plugin_stores cartridge extends the storeHelpers.js script.
If you create your own custom Stores.js controller, and require or extend the Stores.js controller, the asterisks in
the path for the storeHelpers.js script in the original base controller mean that the storeHelpers.js script from the
plugin cartridge is used, rather than that of the base cartridge.
Path lookup of a Digital script package
A path argument that prepends "dw", or that starts with "dw/", references Digital built-in functions and classes,
for example:

var u = require( 'dw/util' );

loads the classes in the util package, which can be then used like this:

var h = new u.HashMap();

Path lookup of a top level module


A path argument that does not start with "./" or "../" is resolved as top level module.
• The path argument is used to find a folder in the top-level version directory, typically a cartridge itself, but
it can also be a simple folder.
• If nothing is found, the path argument is used to look into a special folder (named modules) in the top
version directory. You can use this folder to manage different modules. For example, you can drop a
module like express.js into that folder.

13
MFRA

If the TopLevel.global.require(path:String) method is used to reference a file, an optional file


extension is used to determine the content of the file. Currently, Digital supports the following extensions, listed
in priority order:
• .js - JavaScript file
• .ds - Digital script file
• .json - JSON file

Note
If you use JSON to define a module, the entire JSON object is implicitly exported via the exports
variable.

Note
You can still use the importScript and importPackage methods. However, it is recommended that
you replace them with the TopLevel.global.require(path:String) method.

Example: path lookup for global mymodule.js


Assume you have a script (used in MFRA) with the following call to the require() method:

var u = require( 'mymodule');

Since there is no path syntax, this is requiring a global module. In this case it is requiring the mymodule
module.

Assume the following project structure on your developer machine:

sitegenesis-mobile-first
### bin
### cartridges
### app_storefront_base
# ### cartridge
# ### client
# ### controllers
# ### mymodule.js
# ### forms
# ### models
# ### mymodule.js
# ### scripts
# ### static
# ### templates
### modules
### server
### mymodule
### myScript.ds
### package.json
### main.js
### mymodule.js

After upload, these files have the following structure on the server:

SITE TOP-LEVEL VERSION DIRECTORY


###mymodule.js (2)
#

14
MFRA

####modules // Global modules directory


# ### mymodule.js (3)
#
####mymodule // Module Library directory
# ### myScript.ds (1b)
# ### package.json (1a) // References myScript.ds
# ### main.js
#
####app_storefront-base // Cartridge directory
### controllers
# ### mymodule.js
### models
### mymodule.js

Note
This structure includes directories and files that are outside of cartridges. These files and directories do
not need to be included on the cartridge path to be accessible.

Regardless of where the script that requires a module is located, Digital finds the module as follows:

1. First, Digital searches the mymodule directory for a package.json file.

a. In this example, the package.json is present, so Digital checks the value of the main property in the
file.

b. This property references the myScript.ds file, so Digital loads myScript.ds as a module.

c. If there is no package.json or if the main property is missing, Digital searches the directory for a
main file to load.

d. If Digital finds a main file, the file is loaded; otherwise, Digital continues to search.

2. Next, Digital finds the mymodule.js file because it is in the top level version directory. If there is no file
at this level, Digital continues to search.

3. Last, Digital searches the modules directory in the top level for a mymodule script file.

Note
The mymodule.js files inside the cartridge are not searched at all, because they are not global modules.

Important
While Salesforce provides a sophisticated fallback mechanism for scripts in the globally available
modules folder, we generally discourage polluting the global namespace with utility scripts. Instead, we
recommend adding scripts to specific project cartridges.

See also Modules.

1.1.3. MFRA modules


You can access Javascript/Digital script modules that conform to the the Modules 1.1.1 CommonJS
specification within your storefront script code

• CommonJS modules and require

• ImportPackage vs. Require

15
MFRA

CommonJS modules and require


CommonJS modules allow you to create scripts with functionality that can be reused by multiple controllers. A
module is a .ds or .js file and is usually stored in a cartridge in the script folder or in a modules folder at the
same level as a cartridge. You can access modules in your cartridge, other cartridges, and the modules folder.

The modules folder is a peer of cartridge folders. Salesforce provides it for globally shared modules, such as
third-party modules.

+-- modules
+-- mymodule1.js
+-- My_Cartridge_1
+-- cartridge
+-- scripts
+-- mymodule2.js
+-- My_Cartridge_2
+-- cartridge
+-- scripts
+-- mymodule3.js

MFRA provides a server module in the modules folder. This name is reserved for this module and editing the
server module voids any promise of backward compatibility or support from Commerce Cloud.

See also Using Commerce Cloud Digital script modules

Accessing modules
Commerce Cloud Digital supports CommonJS require syntax to access modules and relative paths.

Use the following syntax:

Syntax Use Example

~ Specifies the current cartridge require ('~/cartridge/


name. scripts/cart')

. Specifies the same folder (as with require('./shipping');


CommonJS).

.. Specifies the parent folder (as with require('../../util')


CommonJS).

*/ Searches for the module in all var m = require('*/


cartridges that are assigned to the cartridge/scripts/
current site from the beginning of MyModule');
the cartridge path. The search is
done in the order of the cartridges
in the cartridge list. This notation
makes it easier to load modules
for a logical name if you do not
know what cartridge is the first
in the cartridge path for a site to
contain the module. For example,
you might have multiple plugin
cartridges that each have a copy
of the module. This allows you to
add new plugins and always use the

16
MFRA

Syntax Use Example


module that is first in the cartridge
path.

ImportPackage vs. Require


You can use ImportPackage statement to import Digital packages into scripts used by pipeline script nodes.

However, Salesforce recommends using require to import Digital script packages instead of ImportPackage.
For example: require(‘dw/system/Transaction’)

Important
Using the require() function to load a module has an impact on performance. To handle a request, a
controller is loaded and then executed on a "global level", just as exported methods are executed. Once
the controller module is initialized, the actual controller function is invoked. If the dependencies to
other modules are all initialized on the global level (like the require() calls are at the beginning of the
controller file) and not on a function level (as local variables within a function body) there might be
more modules loaded than are actually needed by the used function. For example, if there are several
cases in the business logic of the controller and only one case is followed for a specific request, it does
not make sense to load the modules for the other cases, as they are never used.

If all modules execute their requires globally, this ends up in practically all modules being loaded on
every request. This might significantly degrade performance, depending on the number of modules. It is
therefore suggested to move the require() calls for non-API modules into the function bodies so that they
are resolved lazily and only when they are really needed. A require() is not an import, it is a real function
call,. An import in Java, in contrast, is only a compiler directive and does not have any effect at runtime.

Understanding cartridge path inheritance for controllers and pipelines


When a request arrives for a specific URL:

1. Digital searches the cartridge path for a matching controller. If the controller is found, it is used to handle
the request.

2. If no controller is found, the cartridge path is searched again for a matching pipeline. If the pipeline is
found, it is used to handle the request.

Note
If a cartridge contains a controller and a pipeline with a matching name, the controller takes precedence.

When searching the cartridge path, Digital does not verify whether the controller contains the called function in
the requesting URL. Calling a controller function that does not exist causes an error.

Since the platform simply searches the controllers folder, it does not matter whether the controllers are from
a from SiteGenesis JavaScript Controllers (SGJC) cartridge or a Mobile First Reference Architecture (MFRA)
cartridge.

Understanding cartridge path inheritance for modules


The path lookup function is generally described in Module lookup.

17
MFRA

Inheriting and overriding modules in your cartridge stack using


module.superModule
In many cases, you might have several layers of cartridges that overlay one another. Each cartridge can import
from the previous cartridge and overlay it. To make this easy, Commerce Cloud provides a chaining mechanism
to allow you to access modules that you intend to override.

The module.superModule global property provides access to the most recent module on the cartridge path
module with the same path and name as the current module.

The platform searches cartridges after the current cartridge in the site cartridge path for a module with the same
name and in the same location in the cartridge as the current module.The platform skips cartridges before the
current cartridge in the path and the current cartridge and only searches those after the current cartridge. It
continues searching through all the remaining cartridges in the cartridge path until a matching module is found.
If no matching module can be found, module.superModule returns null.

Note
If you need to create a new module that inherits from an existing module, create the existing module in
your cartridge and require it in your new module. For example, if you want to create a home page for a
specific customer group, named BigSpenderHome.js, first create Home.js in your cartridge and use it to
include the functionality of Home.js from your cartridge stack.

Example: overriding and extending an existing controller:

This example inherits the Page.js module from the last cartridge in the stack and overrides the Show function.
Specifically, it changes the data model used to render the page to display "Hello Commerce Cloud."

//Page.js
var page = module.superModule; // require functionality from last controller in the chain with th
var server = require('server');

server.extend(page);

18
MFRA

server.append('Show', function(req, res, next) {


res.setViewData({ value: 'Hello Commerce Cloud' });
next();
});

module.exports = server.exports();

superModule scenarios
For example, assume you are in Page.js and it defines a page variable using the module.superModule
property:

//Page.js
var page = module.superModule;

Page.js is located in mysite/cartridges/cartridge_B/cartridge/controllers.

Assume that you have the following cartridge path:

cartridge_A: cartridge_B: cartridge_C: cartridge_D: app_storefront_base

The platform searches the path starting with cartridge_C.

If: The platform returns:

All cartridges contain a Page.js file in the same cartridge_C module Page.js module- since it is
location as cartridge_B. the next cartridge after cartridge_B in the path

cartridge_A and cartridge_D contain a Page.js file in cartridge_D module Page.js module - since it is
the same location as cartridge_B. after cartridge_B in the path

Only cartridge_A contains a Page.js file in the same null - since cartridge_A is not after cartridge_B in
location as cartridge_B. the path

All cartridges contain a Page.js file in a different null - since none of the files match the location of
location than cartridge_B. the file in the current cartridge.

cartridge_C contains a Page.js file in a different app_storefront_base Page.js module - since the
location and app_storefront_base contains a Page.js files must match name and location.
file in the same location as cartridge_B.

The power of the module.superModule mechanism is that it allows you to chain overlay cartridges on top of one
another. For example, if cartridge_C and cartridge_D both use the module.superModule function and both
add additional functionality, all of that functionality is available to cartridge_B.

Important
Since chaining depends upon an expected order of cartridges in the site cartridge path, changing the
order of cartridges in the cartridge path might impact your code. For this reason it is important to think
about the order of your cartridge layers in advance, to minimize changes to code.

Global modules
If you decide you want a script to be globally available by adding it to the modules folder, Commerce Cloud
recommends adding it to the sitegenesis-mobile-first repository modules folder for upload. A single
modules directory in one repository is recommended per site because the uploadCartridge tool deletes the

19
MFRA

existing modules folder before upload and the upload tool does not identify or prevent naming collisions in
files to upload.

Next Step: MFRA controllers

1.1.4. MFRA controllers


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

What are controllers?


Controllers are server-side scripts that handle storefront requests. Controllers manage the flow of data in your
application, and create ViewModels to process each storefront request as a route 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 Commerce Cloud Digital script. They must conform to the CommonJS
module standard. For information on modules in Digital, see MFRA Modules and MFRA Hooks

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: MFRA base cartridge controller Hello.js

'use strict';

var server = require('server');


var cache = require('*/cartridge/scripts/middleware/cache');

server.get('World', cache.applyDefaultCache, function (req, res, next) {


res.render('helloworld', {
Message: 'Hello World! Again.'
});;
next();
});

module.exports = server.exports();

Controllers can:

• use require to import script modules: Any Digital script can be made into a CommonJS module and
required by a controller.

• use require to import Digital 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 Digital class. For example:

var rootFolder = require('dw/content/ContentMgr').getSiteLibrary().root;

While it is not best practice, controllers can also:

• call other controllers. It is not 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.

20
MFRA

• call pipelets: calling pipelets from within a controller is strongly discouraged. It is allowed while there are
still pipelets that do not have equivalent Digital script methods, but will not be supported in future.
• import packages. This is discouraged as it does not use standard JavaScript patterns, but is Rhino-specific.
• call pipelines that do not end in interaction continue nodes. This is only intended for use with existing link
cartridges and is highly discouraged for general development.

Note
It is not currently possible to call pipelines that end in interaction continue nodes.

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-MobileFirst-Site/default/Hello-World

See also: Digital URL syntax without SEO.

Note
When beginning development

Reserved names for controllers


Digital has several system names that are reserved and cannot be used for custom controllers and their functions.
For a full list, see System pipelines and controllers.

Global variables, request parameters, and page meta data


MFRA uses the server.req and server.res objects for request and response data. While global variables
are still available, Commerce Cloud does not recommend using them.

Debugging
Almost any JavaScript-related error gets logged in the customerror_* log files, including errors from Digital
scripts and controllers. The error_* log files contain Java-related errors on Digital, and are probably less
useful to non-Digital employees
The Error.js controller services uncaught errors. By default, Digital returns a 410 error, which indicates
that the resource is no longer available. For those who prefer 404, it is fine to use as long as the related ISML
template does not 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 require modules when you need them, not at the top of a module.

Using 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
It is not possible to create jobs with controllers. Jobs can only be created using pipelines.

21
MFRA

1.1.5. MFRA standards compliance


This topic covers the following standards and supported browser versions:

• Bootstrap

• Browsers

• Cookies notification/opt-in for European cookie law

• MFRA and Web Content Accessibility Guidelines (WCAG)

Bootstrap
MFRA uses Bootstrap 4. Bootstrap was selected because of its wide adoption in the technical community.

For browser support: the latest version of Firefox, Chrome, and Safari. Internet Explorer version 10 and later.

For mobile devices : Android, iphone and ipad.

Digital
MFRA requires Digital 17.5 or later, due to the new script APIs used in the application.

Desktop browsers
MFRA officially supports the two latest versions of the following browsers:

• Microsoft Internet Explorer

• Safari (MacOS)

• Firefox

• Chrome

Mobile and table browsers


MFRA officially supports the following devices:

• Android phone-only Chrome is supported on Android devices.

• iphone-only Safari is supported

• ipad-only Safari is supported

• Edge

Cookies notification/opt-in for European cookie law


European Cookie Law requires websites to notify customers that cookies are being used and how. The MFRA
application uses an optional content asset, called cookie_hint, to contain this notice.

If this asset is ... Then ...

Missing or offline No notice will be given. The cookies will be set as


they always have been. This is used in the USA, for
example.

Present and online* The cookie_hint content will display. Clicking I


ACCEPT sets the cookies and causes the popup to
not display again.

22
MFRA

Note
*Customers can also add a Close button if they want a more relaxed interpretation of the Cookie Law.
We exclude the Privacy page so that it can be read without seeing the notification.

MFRA and Web Content Accessibility Guidelines (WCAG)


The Web Content Accessibility Guidelines (WCAG) provide a single shared standard for web content
accessibility that meets the needs of individuals, organizations, and governments internationally. WCAG
documents explain how to make web content more accessible to people with disabilities. See http://
www.w3.org/WAI/intro/wcag.

Important
While Commerce Cloud attempts to conform to Level AA test standards, compliance with WCAG is the
sole responsibility of the merchant.

The WCAG guidelines followed were:

• Level A: 2.4.4 Link Purpose (In Context)

• Level AAA: 2.4.9 Link Purpose (Link Only)

The above title-related corrections to MFRA use technique H33 (http://www.w3.org/TR/2014/NOTE-


WCAG20-TECHS-20140916/H33), where the link text is supplemented with the title attribute to add more
context, making it easier for people with disabilities to determine the purpose of the link.

1.2. MFRA Setup

About this task


Previous Step: MFRA Overview

Important
Salesforce recommends that you sign up for a free account with BitBucket, if you do not have one. With
an account you can view and contribute to the Salesforce Commerce Cloud community repositories.

Procedure
1. Navigate to the Mobile First Reference Architecture project on BitBucket.

2. You see a list of MFRA repositories:

• plugin-applepay - this contains an optional plugin cartridge that extends the base cartridge. This adds
apple pay to the site.

• MobileFirstData - this contains the data for MFRA.

• Mobile First Reference Architecture - this contains the app_storefront_base cartridge, which is
the foundation of MFRA. Do not edit this cartridge directly. This also contains a modules cartridge
with a server module that must not be customized directly.

• mfra-jsdoc - Optional. This builds the JSDoc for the project and can be extended to include your
custom cartridge JSDoc.

Note
You only need to download the sitegenesis-jsdoc repository if you want to build out the
JSDoc.

23
MFRA

• sgmf-scripts - Optional. Commerce Cloud publishes the command-line tools in this repository as a
node that can be installed via NPM. Only download the sgmf-scripts repository if you want to
contribute to the project.

3. For each repository you want to download, click the link in the previous step to navigate to the repository
on BitBucket.com and then clone or download the repository in one of the following ways:

• If you have git:

a. Find the plus icon in the the lefthand blue menubar in BitBucket.

b. Click + and select Clone this repository.

• If you do not have git, click Downloads and select Download Repository from the next screen.

Note
If you want to install from the terminal and are using git, you can simply replace
[email protected] and enter the following commands to install the basic functionality for
Mobile First Reference Architecture:

git clone https://[email protected]/demandware/sitegenesis-mobile-first.git


git clone https://[email protected]/demandware/sitegenesisdata.git
git clone https://[email protected]/demandware/plugin-applepay.git

4. If you cloned the file, skip this step. If you downloaded the file, navigate to the downloaded zip file, which
is named XChange_Download.zip and expand it.

Note
If you are working on a Mac, Commerce Cloud recommends that you use the command line unzip
utility to expand the file. For example, unzip XChange_Download.zip.

5. If you cloned the file, skip this step. Use the same procedure as the previous step to expand other MFRA
repositories.

What to do next
We recommend downloading or cloning any repositories you want to use in your project next to each other as
siblings.

your project

### sitegenesis-mobile-first
### cartridges
# ### app_storefront_base
# ### modules
### sgmf-scripts
### sitegenesis-jsdoc
### sitegenesisdata
### plugin-applepay
### app_custom_domain_mysite1
### app_custom_domain_mysite2

Note
If you install these repositories inside the sitegenesis-mobile-first cartridges folder, you cannot use the
build tools provided by Commerce Cloud.

24
MFRA

Next Step: Installing and configuring MFRA build tools

1.2.1. Installing and configuring MFRA tools


Previous Step: MFRA Setup.
This topic is intended to help you configure the app_storefront_base project and any plugins you include for
your site. For information about configuring tools for a custom cartridge, see Adding Custom Cartridges.

Downloading and installing development, debugging, and deployment tools


Salesforce provides UX Studio, an Eclipse plugin, that provides code development, debugging, and code upload
capabilities. It also provides development tools for pipelines, which are still used to create jobs. For this reason,
UX Studio is used in all getting started and training examples. See also installing and configuring Studio.
You do not have to use UX Studio to edit your code. You can use any JavaScript IDE to edit Commerce Cloud
modules, including controllers. However, if you want debugging capabilities, you can use one of the community
projects or third-party debugger plugins available for your editor of choice.
You might also want to contribute to the dwdebugger repository, which uses the Script Debugger API to provide
debugging capabilities for VSC and node-inspector.

Important
For Mobile First Reference Architecture, the standard build tools replace the unsupported Community
Suite for integration and deployment used with SiteGenesis. The Community Suite does not work with
Mobile First Reference Architecture.

If you want to upload your code automatically, use the upload tool included with the sgmf-scripts command line
tools. See also Building MFRA

Getting Started with Eclipse and UX Studio


1. Download and install UX Studio.
2. Create a workspace.
After installing your development tool, you must also install the command-line build tools for MFRA.

Installing MFRA dependencies


MFRA depends on multiple modules that are managed by NPM. These instructions assume you are using the
Mac OS/X operating system and a bash shell. If you downloaded the repository as a zip file, these instructions
assume you have expanded it.

Important
Use NPM 3.x for installation. NPM 5.x is not supported.

Note
These instructions are based on Mac OS. If you are working on a windows machine, adjust them using
the node.js and NPM documentation.

1. Open a command-line prompt and navigate to the sitegenesis-mobile-first directory.


2. Download and install node.js version 4.6 or later. Earlier versions of node do not work with MFRA
functional tests. You can test whether node is installed by entering:

node -v

25
MFRA

If node is already installed, this command returns a version number. If it is installed, skip to the next step.

Note
Commerce Cloud only uses node.js for NPM dependency management.

3. Use NPM to install the modules included with MFRA. This is only possible if you have node.js installed
and are in the directory.

npm install

Important
Run this command any time you download a new version of MFRA, as the dependencies included
with the application might have changed.

Installing MFRA command line tools


While packages are easy to install globally on most operating systems, it can be quite complicated to globally
install packages on the Mac OS/X. If you are working on a *nix or Window system, Salesforce recommends
that you install the command-line build tools for MFRA globally. This makes it simple to create new custom
cartridges with the correct structure anywhere on your development machine.

npm install -g sgmf-scripts

After installation, you can use the sgmf-scripts command line tools in any directory on your machine with a
syntax similar to:

sgmf-scripts --help

If you are installing on a Mac, and get an EACCES error, you might need to fix your permissions to successfully
install the node globally. You might also need to adjust your node installation. This process can be quite time-
consuming and disruptive. However, global install is not necessary, it simply eliminates the installation step for
any new cartridge.
If you want to install sgmf-scripts locally, enter:

npm install sgmf-scripts

After installation, you can use sgmf-scripts in the current folder with a syntax similar to:

node node_modules/sgmf-scripts --help

Next Step: Building MFRA

1.3. Building MFRA


Previous Step: Installing and Configuring MFRA build tools
This topic describes how to build MFRA and upload code to your Sandbox instance. If a cartridge has
stylesheets or JavaScript it needs to be compiled before being uploaded, otherwise it can just be uploaded.

Building a cartridge
To compile the stylesheets and JavaScript for a cartridge:
1. Open a command line terminal.
2. Navigate to the top level folder of the repository containing the cartridges directory, such as
sitegenesis-mobile-first.

26
MFRA

3. To compile both CSS and JavaScript, enter:

npm run compile:js && npm run compile:scss && npm run compile:fonts

To compile only CSS:

npm run compile:scss

To compile only JavaScript:

npm run compile:js

Note
Usually, you only have to compile fonts once.

See also Customizing Mobile First Reference Architecture (MFRA)

Build Commands
The build commands are defined in the scripts section of the package.json for your top level folder.
You can change the commands and add additional commands for other command-line tools in the the
package.json file.

If you have installed the sgmf-scripts node globally, use the following command to see all current build
commands:

sgmf-scripts --help

If you have installed the sgmf-scripts node locally, use the following command to see all current build
commands:

node node_modules/sgmf-scripts --help

You can customize the build commands or add new ones in your package.json. See NPM documentation for
more information about adding scripts for NPM.

Building the JSDoc


To build an HTML version of the JSDoc:

1. Clone or download the sitegenesis-jsdoc repository from Bitbucket.

2. Read the ReadMe for the most up-to-date instructions on how to configure and run the repository.

Next Step: Uploading code for MFRA

1.4. Uploading code for MFRA


Previous Step: Building MFRA

You can use the Mobile First Reference Architecture (MFRA) command line upload tool or Eclipse to upload
your code. The command line watch and upload commands are wrappers for the dwupload project. Download
the project from Bitbucket if you want to contribute.

Using Eclipse to upload code


This topic assumes you have already installed Eclipse and UX Studio. It also assumes you have created a
workspace.

27
MFRA

1. Create a new storefront project (TBD - Studio features not yet defined. Use the command line build
option).

2. Connect to your Sandbox server.

3. Add the sitegenesis-mobile-first repository app_storefront_base cartridge to your workspace


using the procedure to add an existing cartridge to your storefront.

4. Upload your cartridges.

Note
The topics linked to by this section are used by multiple getting started workflows, so use your browser
to navigate back to this topic when you are finished with each step.

Using the MFRA upload tool


You must configure a dw.json file and run the upload tool from the top directory of each repository.

1. In the cartridge you want to upload, locate or create a dw.json file. This file is automatically created if
the sgmf-scripts createCartridge build tool command is used to create the cartridge.

However, for some cartridges, such as the app_storefront_base and modules cartridges provided by
Commerce Cloud, you must create the dw.json file and add it to the cartridge.

2. Create a dw.json file in the top level folder of the repository, as a sibling of the cartridges directory.
The dw.json file contains the information required to establish a server connection and the code version to
upload your cartridges to.

{
"hostname": "myhostname.com",
"username": "myusername",
"password": "mypassword",
"version": "version1"
}

Note
If you have set up UX Studio before, this is the same information you enter for a server connection.

CAUTION
This file is normally unencrypted. However, since it contains instance and password data necessary
to upload code, you might want to use additional measures to secure it.

3. • Use the uploadCartridge command to upload all the cartridges for the repository. The specific
commands to use are in the scripts section of the package.json file that is located in your top
level folder.

npm run uploadCartridge

• Use the uploadCartridge command to upload a specific cartridge.

npm run uploadCartridge mycartridge

• Use the upload command to upload a single file.

If you use the same commands as the app_storefront_base cartridge, you run:

npm run upload path/to/my/filename.ext

28
MFRA

• Use the watch command to detect changes to your cartridge and automatically upload them.

If you use the same commands as the app_storefront_base cartridge, you run:

npm run watch

This command makes it simple to have up-to-date code on your Sandbox instance all time.

4. Check that your code is actually on the server:

a. In Business Manager: Administration > Site Development > Code Deployment.

b. Click the Code Version link for the code version you specified in the dw.json file.

If your cartridge was uploaded successfully, it appears in the list of cartridges on the Version
Summary page.

Next Step: Configuring MFRA

1.5. Configuring MFRA


Previous Step: Building MFRA

Even after you have built and pushed your code up to your instance, you still need to configure your application
in Business Manager and in your browser.

Register your MFRA cartridges


See Configuring the Mobile First Reference Architecture (MFRA) cartridge path.

Add Data
Salesforce provides data and images for a basic store catalog if you want to demonstrate the basic functionality
of the application. If you do not import this data, the site does not contain any products. Without data you
cannot use site functionality.

See Importing MFRA data into an instance.

Generate search indexes


After you import the MFRA data, you need to generate search indexes, in order for the site to display correctly.

See Generate search indexes.

Page caching
You might want to turn off caching for the site you are working on during development. If you are working with
MFRA, to disable caching, see See See Disabling page caching for development.

Site URLs
Salesforce recommends using standard Digital URL syntax in the early phases of development.

1. In Business Manager: Merchant Tools > Site Preferences > Storefront URL.

2. On the Storefront URL Preferences page, make sure the Enable Storefront URLs option is not selected.

This allows you to enter a URL such as https://localhost/on/demandware.store/Sites-


MobileFirst-Site/default/Hello-World directly in your browser to test new controllers or pipelines.

29
MFRA

Add an extra code version


One of the easiest troubleshooting steps, if you aren't seeing your changes reflected on the server, is to activate
a different code version and then reactivate the current code version. If you immediately add an empty code
version, you're ready to troubleshoot from the start.
1. In Business Manager: Administration > Code Deployment.
2. Click Add and enter a name for your new code version.

MFRA features
To configure MFRA in Business Manager, see Configuring storefront preferences for MFRA.

Note
Many of the feature options available for SGJC and SGPP are not used in MFRA, largely because they
are generally not recommended based on our analysis of mobile user interaction and design. In other
cases, optional features are now implemented as plugin cartridges.

MFRA for your browser


To use MFRA, you must have cookies enabled in your browser.

Note
We do not have a TrendingNow content slot in MFRA, so error messages that were visible in SGJC and
indicated that cookies were disabled are no longer included in the log.

Next Step: Troubleshooting MFRA

1.5.1. Configuring storefront preferences for MFRA

Procedure
1. In Business Manager: site > Merchant Tools > Site Preferences > Custom Preferences > Storefront
Configs.
2. On the Custom Site Preferences page, select the instance type (if you are changing the type).
The instance type is applied immediately. This affects other Business Manager modules with settings that
are specific to a selected instance type.
• Sandbox / Development
• Staging
• Production
3. Enter the tag that will be used by the storefront application as a Google verification tag.
This is the Google tag used to verify the site.
4. Enter the email addresses to be sent the build notifications, separated by a semicolon.
This enables you to send email notification to one or more email addresses when a new build is available.
5. Enter the default list price book ID to be used by the storefront.
6. Click Apply to save your changes.

What to do next
Next Step: Configuring MFRA.

30
MFRA

1.5.2. Importing MFRA data into an instance


In order to view products in the MFRA storefront sites, you must import the standard MFRA data. The standard
data includes product information and images.
See MFRA Setup for instructions on downloading MFRA data from BitBucket.
Uploading and importing MFRA data
It is safe to import MFRA into an empty Sandbox. However, if you also want to import custom sites, make
sure you import MFRA first before importing the custom sites. By importing MFRA first and then importing
your custom site, you ensure that custom attributes for your custom sites are retained if there are conflicts, as
your custom attributes will overwrite the imported MFRA custom attributes. If the MFRA custom attributes are
overwritten, the MFRA site might not function properly, but your custom attribute data is kept intact.

DANGER
Never import MFRA data into an instance in your Primary Instance Group (PIG).

CAUTION
If you import MFRA into an instance that contains other customized sites, you might overwrite existing
existing attributes and lose data.

1. Zip up the repository data.

Note
If you are doing this on a Mac, do not use the Mac OS archive utility. Instead, zip up the repository
from the command line. Files compressed using the MacOS archive utility cause errors on import.

2. In Business Manager, navigate to Administration > Site Development > Site Import & Export.
3. In the Import section, select Local to upload the site zip file from your local machine.
4. Click Browse, select the zip file you want to upload, and click Open.
The name of the zip file appears in the grid below when it is successfully uploaded.
5. In the Select column, select the zip file and click Import.
6. Digital asks you if you want to import the selected archive. Click OK.
Next Step: Configuring MFRA.

1.5.3. Configuring the Mobile First Reference Architecture (MFRA) cartridge


path

About this task


Previous Step: Creating a new storefront project
Before you can use the contents of a cartridge, you must upload the cartridge to the server and register the
cartridge with the server.
In Business Manager, you register cartridges to a site. By registering cartridges, you make the cartridges
available on your system. Cartridges can be shared among sites.

Procedure
1. In Business Manager: Administration > Sites > Manage Sites > site.
2. Click the Settings tab.

31
MFRA

3. In the Cartridges field, enter a colon-separated list of your cartridge names. Cartridges at the beginning of
the list, (at the left), take precedence over cartridges at the end of the path (at the right).

• MFRA storefront - your path should contain:

plugin_applepay: app_storefront_base

• custom storefront - your path should contain:

app_custom_mybrand: plugin_applepay: app_storefront_base

4. Click Apply.

What to do next
Next Step: Configuring MFRA.

1.6. Troubleshooting MFRA


Previous Step: Configuring MFRA.

Community and cloud resources


Visit the Xchange developer forum to search for past answers and add new questions.

Search the Knowledge Base for the latest Customer Support articles.

Debugging
UX Studio includes debugging capabilities. For more information, see Debugging scripts and the Script
Debugging with UX Studio Tutorial on Xchange.

If you want Digital Script debugging capabilities in an IDE other than , you must use one of the IDEs supported
by the dwdebugger repository.

After updating the base cartridge


In BitBucket, you can see the differences between two versions of MFRA. This can allow you to quickly
identify code that has changed.

Create MFRA as a separate site and import the data for the site to see if the behavior remains the same.

After adding MFRA to a new sandbox


If you have added MFRA to a new sandbox and it does not appear to work, check the following:

• Are you on the correct code compatibility version?

• Are you sure that your cartridge path is the same as the cartridges in your workspace?

• If you are using Eclipse, did you check your project references?

Next Step: Testing MFRA

1.7. Testing Mobile First Reference Architecture (MFRA)


Previous Step: Importing MFRA data into an instance

The testing technologies are automatically installed by node when you set up your MFRA project.

Commerce Cloud includes the following kinds of tests:

32
MFRA

• Controllers are tested with integration tests. Commerce Cloud uses mocha, chai, and request-promise for
integration testing.

• Models are tested with unit tests. Commerce Cloud uses mocha, chai, sinon, and proxyquire for unit tests.

• Views are tested with functional tests. Commerce Cloud uses mocha, chai, and webdriver.io for functional
tests.

Testing Commands
Command Description

npm run cover Runs unit tests with a coverage report.

npm run test * Runs all MFRA unit tests.

npm run test:appium * Runs functional tests for mobile devices.

npm run test:integration * Runs the integration tests.

npm run test:functional Runs the functional tests in the browser.

npm run test:functional:docker Runs the functional tests from inside docker.

npm run test:functional:sauce Runs the remote functional tests

Note
* These commands require a dw.json file or additional command-line parameters.

Next Step: Customizing Mobile First Reference Architecture (MFRA)

1.8. Customizing Mobile First Reference Architecture (MFRA)


Unlike any version of SiteGenesis, Mobile First Reference Architecture (MFRA) is not intended to
be customized directly. Do not edit the app_storefront_base cartridge and other plugins, such as
plugin_applepay. This allows you to more easily take security updates, bug fixes, and adopt new features. Since
MFRA is backward compatible between point releases, except when necessary for urgent security fixes, you can
simply download the newer version of MFRA and run your site automated tests, rather than having to identify
areas of changed code in the original application and port them to your customized code.

Note
Do not rename the app_storefront_base cartridge and other provided plugins. Salesforce also
recommends keeping your version of the app_storefront_base cartridge and other plugins up to
date, so that you have access to the most recent bug fixes and performance improvements.

To build a custom site, you must:

• Add a custom cartridge

• Build your cartridge stack

You might want to customize:

• hooks

• modules

• templates

• models

33
MFRA

• controllers and routes

• forms

Adding plugin and LINK cartridges


All available link and plugin cartridges are available on Bitbucket. Plugin cartridges are available in the MFRA
project in Bitbucket. You can Include link and plugin cartridges on your cartridge path and base path as you
would any other cartridge in your stack.

See also Configuring the Mobile First Reference Architecture (MFRA) cartridge path

Next Step: Add a custom cartridge

1.8.1. Adding custom cartridges


Implementing a site requires at least one custom cartridge. However, if you intend to create multiple sites, you
might want to create multiple custom cartridges that separate functionality specific to a brand or locale, so that
you can reuse most of your cartridge stack for a new site or microsite.

Adding custom cartridges when sgmf-scripts is globally installed


If you have sgmf-scripts installed globally through NPM, to add a custom cartridge:

1. Create a new folder to contain your project. For example: mysite.

mkdir mysite

2. Navigate into the folder.

cd mysite

3. Install the sgmf-scripts node.

npm install sgmf-scripts

4. Use the sgmf-scripts --createCartridge command.

sgmf-scripts --createCartridges app_custom_storefront

The following directories and files are created in your folder:

### cartridges
### app_custom_mybrand
### cartridge
...
### dw.json
### node_modules
### package.json

5. Install the dependencies required by the new cartridge:

npm install

Note
To see all the available MFRA-commands, enter sgmf-scripts --help.

34
MFRA

Adding custom cartridges when sgmf-scripts is not globally installed


This is only recommended if you are using NPM with a Mac OS and want to avoid globally installing the sgmf-
scripts node.

1. Create a new folder to contain your project. For example: mysite.

mkdir mysite

2. Navigate into the folder.

cd mysite

3. Install the sgmf-scripts node via NPM.

npm install sgmf-scripts

4. Use node.js to call the sgmf-scripts createCartridges command.

node node_modules/sgmf-scripts --createCartridge app_custom_mybrand

The following directories and files are created in your folder:

### cartridges
### app_custom_mybrand
### cartridge
...
### dw.json
### node_modules
### package.json

5. Install the dependencies required by the new cartridge:

npm install

Note
To see all the available MFRA-commands, enter sgmf-scripts --help.

Updating your package.json


Since you are not modifying the app_storefront_base cartridge directly, Commerce Cloud provides a
mechanism to selectively override CSS styles and client-side JavaScript. In the package.json every cartridge
with CSS and client-side JavaScript functionality customized in your site is listed under the paths property.
When building your cartridge stack, the paths property allows you to import functionality from CSS and
JavaScript files in other cartridges and selectively override it. When you use the command-line compile tool it
compiles the JavaScript and CSS across cartridges.

1. Navigate to the top level of your custom cartridge. For example, mysite.

2. Open the package.json file and modify the paths property. The paths.base property points to the
local directory containing the app_storefront_base. Add additional properties for all the cartridges that
you want to import functionality from.

Example: adding plugin_ratings, plugin_reviews, and app_storefront_base to a custom


cartridge:

"paths": {
"base": "../sitegenesis-mobile-first/cartridges/app_storefront_base/",

35
MFRA

"ratings": "../plugin_ratings/cartridges/plugin_ratings/",
"reviews": "../plugin_reviews/cartridges/plugin_reviews/",
"applepay": "../plugin-applepay/cartridges/plugin_applepay/"
}
}

3. Import all of the CSS files you want to include and override into my_repository/cartridges/my_cartridge/
cartridge/client/default/myfile.scss file. The following is an example of a simple global .scss file that
inherits a majority of its styles from the base cartridge. The location of the base cartridge is defined in the
package.json. This example uses the base property defined in the package.json to import a variety of
stylesheets.

@charset "UTF-8";

@import "base/variables";
@import "bootstrap/scss/bootstrap";
@import "base/bootstrap_overrides";
@import "base/utilities/responsiveUtils";
@import "font-awesome/scss/font-awesome";
@import "flag-icon";
@import "components/menu";
@import "base/components/common";
@import "base/components/footer";
@import "components/footer";
@import "base/components/hero";
@import "base/components/notification";
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro&subset=latin-ext);

body {
font-family: 'Source Sans Pro', sans-serif;
}

.modal-background {
display: none !important;
}

4. Use the path object to include JavaScript modules from other cartridges when you want to inherit and
override client-side JavaScript functionality. The following example uses the path described by the base
property to require in a client-side script in the base cartridge.

'use strict';
var base = require('../base/product/base');

After creating your custom cartridge


After creating your custom cartridge, you need to do the following:

• Upload your code to the platform. This might mean updating your dw.json file, if you are using the
command-line to watch and upload code.

• Add the cartridge to the cartridge path

• Disable page caching.

• Generate search indexes.

36
MFRA

• View the storefront.

Best practices when creating custom cartridges


• create unit tests for models
• make sure to be aware of possible naming collisions between cartridges
Anti-patterns:
• do not override pdict variables
• do not delete properties off the pdict
• replacing something in pdict with something else with a different signature
Next Step: Building your cartridge stack

1.8.2. Building your cartridge stack


Each cartridge is built independently, uploaded, and then added to the cartridge stack. This means you can
change and upload cartridges independently, as long as you are developing against same code version. The
command line tools are designed to work in the top-level folder of each plugin or cartridge repository. For
example, watch only works for the directory in which you start it.
Cartridges provided by Salesforce only need to be built once for each version and uploaded. You might want
to create a separate build script for routine maintenance of cartridges you update and upload. In contrast, you
might want to create a lightweight script that allows you to dynamically build and upload just those cartridges
you are currently working on.
Next Step: Customizing controllers and routes

1.8.3. Customizing templates


Digital searches the cartridge path for the first template in the path that matches the specified name and location.
Overriding a template only requires that you include the template you want to use in a cartridge that is earlier on
the cartridge path than the cartridge containing the template you want to override.
Decorator templates in MFRA
There are only two decorator templates:
• page.isml - contains navigation information
• checkout.isml - does not contain navigation information. Removal of navigation information has been
shown to improve the percentage of cart abandonment.
These templates are located in app_storefront_base/cartridge/templates/default/common/
layout/.

Note
All of the pt_..._VARS and pt_..._UI templates that existed in previous versions of the SiteGenesis
reference application were removed.

The navigation for a page is set through the new decorator templates. Client-side scripts and CSS files are set
for each template using the assets.addJs and assets.addCss functions for each template. The top of every
page template contains code similar to the following:

<isdecorate template="common/layout/page">
<isscript>
var assets = require('~/cartridge/scripts/assets.js');

37
MFRA

assets.addCss('/css/cart.css');
</isscript>

If you put *.css and *.js with the same name and in the same location as indicated in the template, then they
override the *.css and *.js in any cartridge to the right of the current cartridge on the cartridge path.

Overriding CSS and client-side JavaScript for a page


The simplest way to override CSS and client-side JavaScript for a template is to simply add the new version of
the styles or script in a file with the same name and location as the one you want to override. For example, if the
template declares the following assets:

<isdecorate template="common/layout/page">

<isscript>
var assets = require('*/cartridge/scripts/assets');
assets.addJs('/js/productDetail.js');
assets.addCss('/css/product/detail.css');
</isscript>

You can create a productDetail.js and detail.css and place them in the same location as indicated in the template.
The platform uses the first template with the correct name and location that it finds on the cartridge path,
searching from the first cartridge to the last.

When creating the CSS file you want to customize, it is easiest to simply create a file with the same name and in
the same location as the file you are customizing and import it original file.

Example: customizing detail.scss.

@import "base/product/detail";

.description-and-detail {
margin-top: 2em;

.details {

@include media-breakpoint-up(sm) {
margin-bottom: 2em;
}
}
}

Next Step: Customizing models

Best practices with ISML


As a general rule, business logic is not included in app_storefront_base templates. Any logic in the templates is
specific to rendering. The app_storefront_base cartridge demonstrates these best practices for ISML:

• iscache - set by default to 24 hours. Salesforce recommends changing this in the controller.

• isset - only used to set a variable used by isinclude. Do not use complex expressions in isset.

• isscript - only used to add client-side JavaScript/CSS to the page using the asset module.

• ismodule - only used for content assets.

• iscontent - change this in the controller.

38
MFRA

• iscookie - change this in the controller.

• isredirect - change this in the controller.

• isstatus - change this in the controller.

• isobject - used to wrap products for analytics and the Storefront Toolkit.

Replacing ISML with another templating language


While it is not recommended for performance reasons, it is possible to replace ISML rendering in your
storefront with rendering that uses entirely different rendering languages. To change the rendering of templates,
remove the listener for handling the middleware event so that it is skipped and add a new event.

1.8.4. Customizing models


Models in Mobile First Reference Architecture (MFRA) provide a JSON object layer for the application. The
models convert objects returned by the Digital Script APIs into pure JSON objects designed for the storefront.
The models also apply business logic for the storefront. A model passed in for rendering a template is assigned
to the viewData variable. You can add properties to the viewData variable beyond those provided by the
app_storefront_base cartridge.

Controllers create and update models. To customize a model, you create the model and then add data to it that
can be used for rendering.

Anything in the modules folder or the toplevel folder is globally available and extendable.

You can customize models to:

• extend the model to include additional data to be used when rendering the template

• add additional models with objects used to render templates

• change models to be used to render templates in a different framework

1.8.5. Customizing controllers and routes


When replacing a base controller with your own code, you can:

• inherit functionality from another controller and extend it

• replace or add a route

• overriding instead of replacing a step in the middleware chain

• change access to a route

• customize a controller with a web service or a call to a third-party

Inheriting functionality from another controller and extending it


It's important to understand when to extend a controller and when to override it, since this can significantly
impact functionality and performance.

When do I want to override?

It's best to override if you want to avoid executing the middleware of the controller or script you are modifying.

When extending a controller, you first execute the original middleware, and then execute the additional steps
included in your extension. This means that if the original middleware steps include interaction with a third
party system, that interaction is still executed. If your extension also includes the interaction, then the interaction
is executed twice. Similarly, if the original middleware includes one or more steps that are expensive, because

39
MFRA

they include multiple API calls or other complex interactions, you might want to avoid executing the original
middleware.
When do I want to extend?
If the middleware in the controller or script that you are overriding is simply looking up a string or another
inexpensive operation, then it is best to extend the controller or module.
How do I extend or override?
Use the module.superModule mechanism to import the functionality from a controller and then override or add
to it.
Example: adding product reviews to Product.js
This example customizes the product detail page to include product review information. The code for this
example is available in the plugin_reviews demo cartridge.
In this example, the Product.js controller uses the following APIs for customization:
• module.superModule - imports functionality from the first controller with the same name and location
found to the right of the current cartridge on the cartridge path.
• server.extend - inherits the existing server object and extends it with a list of new routes from the super
module. In this case, it adds the routes from the module.superModule Project.js file.
• server.append - modifies the Show route by appending middleware that adds additional properties to the
viewData object for rendering. Using server.append causes a route to execute both the original middleware
chain and any additional steps, so it is not recommended if you are interacting with a web service or third-
party system.
• res.getViewData - gets the current viewData object from the response object.
• res.setViewData - updates the viewData object used for rendering the template. In order for the
template to use this information, you must create a customized template in the same location and with the
same name as the template rendered by the superModule controller. For example, if this controller has
inherited the functionality from app_storefront_base, the template rendered by that controller depends on
the product type being rendered, and can be product/productDetails, product/bundleDetails, or
product/setDetails.

// Product.js

'use strict';

var server = require('server');


var page = module.superModule; //inherits functionality from next Product.js found to the
server.extend(page); //extends existing server object with a list of new routes

server.append('Show', function (req, res, next) { //adds additional middleware


var viewData = res.getViewData();
viewData.product.reviews = [{
text: 'Lorem ipsum dolor sit amet, cibo utroque ne vis, has no sumo graece.' +
' Dicta persius his id. Ea maluisset scripserit contentiones quo, est ne movet dicam.'
' Equidem scriptorem vis no. Civibus tacimates interpretaris has et,' +
' ei offendit ocurreret vis, eos purto pertinax eleifend ea.',
rating: 3.5
}, {
text: 'Very short review',
rating: 5

40
MFRA

}, {
text: 'Lorem ipsum dolor sit amet, cibo utroque ne vis, has no sumo graece.',
rating: 1.5
}];

res.setViewData(viewData);
next();
});

module.exports = server.exports();

Replacing or adding a route


If you want to completely replace a route, rather than append it, use module.superModule to inherit the
functionality of the controller and route you want to replace. Then register the functions you want the route to
use.
Example: replacing the Product-Varation route
In your custom cartridge, create a Product.js file in the same location as the Product.js file in the base cartridge.
Use the following code to import the functionality of Product.js and redefine it.

var page = require('app_storefront_base/cartridge/controller/Product');


var server = require('server);

server.extend(page);

server.replace('Show', server.middleware.get, function(req, res, next){


res.render('myNewTemplate');
next();
});

Overriding instead of replacing a step in the middleware chain


Salesforce recommends replacing a route in order to change individual steps in a middleware chain.
You cannot change a step in the middleware chain. However, you can either replace the whole route or append a
new step after the chain completes, but before the template renders. Usually, if you are appending a step, it is to
override or add data to the ViewData object.

Important
Be careful when appending existing routes. If you append a new step, the existing middleware chain
executes no matter what appended step does, so it is possible to execute the route twice. If the route calls
a web service or updates a third-party system, such as an inventory management system, it might take
the same action twice.

Note
information about middleware filtering classes is available in the MFRA JSDoc in the server-side
documentation for Global.

Changing the access on an existing route


You can use the middleware functions provided by Commerce Cloud or create your own. We recommend
replacing a route when changing access.
Middleware filtering functions provided by Commerce Cloud:

41
MFRA

• get - filter for get requests


• http - filter for http requests
• https - filter for https requests
• include - filter for remote includes
• post - filter for post requests
If the request does not match the filtering condition, the function returns an Error with the text Params do not
match route.

Customizing a controller with a web service or a call to a third-party


In general, customizing a controller with a web service or third-party call uses the same mechanisms as other
types of customizations. However, it is important to make sure that you do not execute the controller twice by
using server.append to customize the ViewData object passed to the template for rendering. Instead, simply
replace the route entirely.
Next Step: Customizing templates

1.8.6. MFRA forms


You can create HTML forms in Digital using our templates and controllers. Using form definitions, you can
also persist form data during a session and store it in system objects or custom objects.
When creating forms, consider the following:
• Creating a simple form
• Localizing forms
• Hiding form fields
• Validating form data
• Saving form data
• Clearing or refreshing forms
• Prepopulating form data
• Sharing data between forms
• Securing forms
• Dynamic, Multi-part, embedded, or nested forms

Creating a form
If you are creating a simple form that does not store data, is easily localized, and only requires client-side
validation, you can create a standard HTML form that uses AJAX for validation and error rendering.
However, you might need to create a complex form that stores data, requires server-side validation, and has
sophisticated localization requirements. Sophisticated localization can include needing to add, remove, or
rearrange fields in the form or changing the data object you need to store with form data.
If you are creating a complex form, you need to use a Commerce Cloud form definition. A form definitions is
used by the platform to create an in-memory object that persists during the session and that you can use with
various platform features for localization, server-side validation, and storage of data.
This is an example of a form that uses a form definition. The form has a text field to input a nickname, a submit
button, and a cancel button. After the form is submitted, another page is rendered that shows the nickname
entered in the previous form.

42
MFRA

Form Definition

The first thing you create for a form is the form definition. The form definition describes the data you need from
the form, the data validation, and the system objects you want to store the data in. This example only has one
input field and two buttons. This form does not validate or store data permanently.

MFRAFormDef.xml

<?xml version="1.0"?>
<form xmlns="http://www.demandware.com/xml/form/2008-04-19">
<field formid="nickname" label="Nickname:" type="string" mandatory="true" max-length="50" />
<action formid="submit" valid-form="true"/>
<action formid="cancel" valid-form="false"/>
</form>

In-memory form object

The form definition determines the structure of the in-memory form object. The in-memory form object persists
data during the session, unless you explicitly clear the data.

In MFRA, the first step in creating a form is to create a JSON object to contain the form data. The
server.getForm function uses the form definition to create this object.

Data from the form is accessible in templates using the pdict variable, but only if the server.getForm object
is passed to the template by the controller when it is rendered.

See also Form definition elements and What is a form definition.

Controller to render the form

The controller in this example exposes a Start function that renders an empty form.

The Start sets the actionURL that is used to handle the submit action for the form and creates a JSON object
based on the form definition.

MFRAForm.js

/**
* A simple form controller.
*
*/

'use strict';
var server = require('server');
var URLUtils = require('dw/web/URLUtils');

server.get(
'Start', server.middleware.http, function (req, res, next) {
var actionUrl = URLUtils.url('MFRAFormResult-Show'); //sets the route to call for the form su
var MFRAhelloform = server.forms.getForm('MFRAFormDef'); //creates empty JSON object using the f
MFRAhelloform.clear();

res.render('MFRAFormTemplate', {
actionUrl: actionUrl,
MFRAhelloform: MFRAhelloform
});
next();
});

43
MFRA

module.exports = server.exports();

See also Using API form classes.


Form Template
In this example, MFRAFormTemplate.isml is the empty form rendered for the user and the
MFRAResultTemplate.isml displays data entered into the form.

MFRAFormTemplate.isml
The client-side JavaScript and css files are included using the assets.js module.
The form action uses the actionUrl property passed to it by the controller.

<!--- TEMPLATENAME: helloform.isml --->


<!--- <isscript>
var assets = require('*/cartridge/scripts/assets.js');
assets.addCss('/css/helloform.css');
assets.addJs('/js/helloform.js'); </isscript>--->

<div class="hero slant-down login-banner">


<h1>MFRA Hello World Form</h1>
</div>

<!--- --->
<div class="card">
<form action="${pdict.actionUrl}" class="login" method="POST"
name="MFRAHelloForm">

<div class="form-group required">


<label> Nickname: </label> <input type="input" id="nickname"
class="form-control" name="nickname">
</div>

<button type="submit" class="btn btn-block btn-primary">Submit</button>


<button type="submit" class="btn btn-block btn-primary">Cancel</button>
</form>
</div>

Controller to render form results


MFRAFormResult.js
After a form is submitted, data from the form is available as part of the req.form property. In the example
below, the nickname entered in the original form is passed to to a new template for rendering.

/**
* Handles the simple form rendered by the MFRAForm.js controller.
*
*/

'use strict';
var server = require('server');
var URLUtils = require('dw/web/URLUtils');

44
MFRA

server.post('Show', server.middleware.http,
function(req, res, next) {

var nickname = req.form.nickname;

res.render('MFRAResultTemplate', {
nickname : nickname});
next();
});

module.exports = server.exports();

Form result template


MFRAResultTemplate.isml
This template prints out the form field label and data stored from the form.

<<!--- TEMPLATENAME: MFRAResultTemplate.isml --->


<iscontent type="text/html" charset="UTF-8" compact="true" />
<!doctype html>
<head></head>
<body>
<h1>Hello World Form Result</h1>
<p>Nice to meet you, ${pdict.nickname}.</p>
</body>
</html>

Back to top.

Localizing forms
Changing form structure and fields for different locales
You can change the structure of a form depending on the locale. For example, you might want to include
different address fields, such as state or province, depending on the country. To localize the form structure, you
can create different form definitions for each locale. These form definitions have the same name, but a different
structure and/or different fields for different locales.
To do this, in your cartridge, create a forms/default folder for the standard locale and then separate folders
that are named for each locale of the form. Store a different form definition in each locale folder. If a locale does
not have a separate folder, the default form definition is used.

forms
default
billingaddress.xml
it_IT
billingaddress.xml
ja_JP
billingaddress.xml

Localizing strings within simple forms


You can use resource strings directly from a form. The following example is of the loginform.isml that
logs customers into the site. In this case, the form uses the label.input.login.email resource string
identifier.

45
MFRA

<form action="${pdict.actionUrl}" class="login" method="POST" name="login-form">


<div class="form-group required">
<label class="form-control-label" for="login-form-email">
${Resource.msg('label.input.login.email', 'login', null)}
</label>
<input type="email" id="login-form-email" class="form-control" name="loginEmail" value="$
<div class="form-control-feedback"></div>
</div>

Depending on the locale, this resource identifier resolves to different values

In the English app_storefront_base/cartridge/templates/resources/login.properties file:

label.input.login.email=Email

In the French app_storefront_base/cartridge/templates/resources/login_fr_FR.properties


file:

label.input.login.email=E-mail

Note
Remember to add the country to select to your country selector and to configure the locale for the site in
Business Manager.

Localizing strings within complex forms

All form strings can be replaced with resource strings. Resource strings for forms are located by default in the
forms.properties file for your cartridge and referenced from the form definition file. Add files with the
name forms_locale_.properties to add localized strings. For example, add a forms_it_IT.properties
file for an Italian version of the same properties. If you have different fields for the form, depending on the
locale, make sure the strings for those fields are included in the localized version of the properties files.

Example: localizing labels and error messages

The following form definition file defines a form to enter contact information. This example does not show the
entire form definition, just some of the fields that use localized strings for labels and error messages. You can
find this file as the contactus.xml form in the SiteGenesis app_storefront_core cartridge.

<?xml version="1.0"?>
<form xmlns="http://www.demandware.com/xml/form/2008-04-19">

<field formid="firstname" label="contactus.firstname.label" type="string" mandatory="true" bindi


<field formid="lastname" label="contactus.lastname.label" type="string" mandatory="true" binding
<field formid="email" label="contactus.email.label" type="string" mandatory="true" parse-error=

The label and error strings in bold above reference the properties set in the forms.properties file, which
contains entries like the following for the default site locale:

##############################################
# Template name: forms/contactus
##############################################
contactus.firstname.label=First Name
contactus.lastname.label=Last Name
contactus.email.label=E-Mail
contactus.email.parse-error=The email address is invalid.

46
MFRA

The form is localized in the forms_it_IT.properties file, (along with the other locale-specific
forms_locale_.properties files), with entries like the following:

##############################################
# Template name: forms/contactus
##############################################
contactus.firstname.label=Nome
contactus.lastname.label=Cognome
contactus.email.label=E-mail
contactus.email.parse-error=L'indirizzo e-mail non è valido.

Back to top.

Hiding form fields


Most MFRA forms are standard HTML forms, so you can use input type="hidden" to hide form fields in
templates.

Validating form data


Server-side validation on form data is configured in the form definition. MFRA uses jquery AJAX methods to
render a page after server-side validation.

Validation by attribute

The attributes set on the form field are used for validation. In the example below, the mandatory attribute
requires a value for the field, the regexp attribute determines the content of the field, and the max-length
attribute sets the maximum length of the data for the field.

Note
The max-length attribute is only used for validation of strings. For other field types it is only used to
format the field length and not to validate data.

<field formid="email" label="contactus.email.label" type="string" mandatory="true" regexp="^[\w.%

Errors displayed for attribute validation:

• default error for form invalidation: value-error attribute message is displayed.

• mandatory flag invalid: missing-error attribute message is displayed.

• entered value invalid: parse-error attribute message is displayed.

Validation by function

You can also use the validation attribute to specify a function to run to validate form data. You can run these
validations on container elements, such as form or group, or on individual fields.

<field formid="password"
label="label.password"
type="string"
range-error="resource.customerpassword"
validation="${require('~/cartridge/scripts/forms/my_custom_script.ds').my_custom_validatio

You can also selectively invalidate form elements using the InvalidateFormElement pipelet in pipelines or
the invalidateFormElement function in the FormModel or any model that requires it. If any element in a
form is invalid, the whole form is invalid. However, in your form definition you can create error messages that

47
MFRA

are specific to a field. See the example of range-error above, which points to a resource string with a message
for the customer on why the field is invalid.
Client-side validation scripts
Simple forms are standard HTML forms, so you can use any client-side validation method you choose. The
Commerce Cloud team uses default HTML5 validation for client-side validation. You can find the client-side
JavaScript for a page, by identifying the the script added by the assets.AddJs function.
Commerce Cloud provides two utility scripts that can be used for validating form data:
• form-validation - this is used to validate a specific field in the form. It uses the validation criteria set in
the form definition and included in the attributes for the form JSON object. This script is required by
the client-side JavaScript doing the validation for a specific form and is loaded at document.ready. This
file is located in app_storefront_base/cartridge/client/js/default/components/form-
validation.js.

• client-side-validation - this is used to validate the entire form and clear a form for validation. This file
is required by main.js and is located in app_storefront_base/cartridge/client/js/default/
components/client-side-validation.js

See also Form validation


Back to top.

Saving form data


The route:BeforeComplete event is used to store form data. Different APIs are used to save data, depending
on the type of form.
Example: saving password data
This example constructs an object that contains the relevant information from the form and saves it to the
ViewData object, so it can be passed. This example can be seen in the Account.js SavePassword function.

var profileForm = server.forms.getForm('profile'); //gets the profile form object


var newPasswords = profileForm.login.newpasswords;
...
var result = { //constructs an object containing the fo
currentPassword: profileForm.login.currentpassword.value,
newPassword: newPasswords.newpassword.value,
newPasswordConfirm: newPasswords.newpasswordconfirm.value,
profileForm: profileForm
};

if (profileForm.valid) {
res.setViewData(result); // adds form result to the ViewData object
this.on('route:BeforeComplete', function () { // creates the function to run before middlewar
var formInfo = res.getViewData(); // creates object with data to save
var customer = CustomerMgr.getCustomerByCustomerNumber( //gets current customer
req.currentCustomer.profile.customerNo
);
var status;
Transaction.wrap(function () { //saves the new customer password and retur
status = customer.profile.credentials.setPassword(
formInfo.newPassword,
formInfo.currentPassword,
true

48
MFRA

);
});

Back to top.

Clearing or refreshing forms


In MFRA, you use the server.getForms function to get the form data structure from the relevant form
definition and convert that into a JSON object. That object is then added to the data passed to the template, so
that it is available to the template via the pdict variable. The getForm function automatically clears the form
and also provides a clear method.
Example: clearing the form using the server module forms.js functions
This example gets the profile form and clears it.

function (req, res, next) {


var accountModel = getModel(req);
var profileForm = server.forms.getForm('profile'); //gets the profile form object
profileForm.clear(); //clears the form using a function from the server module f

Prepopulating form data


You can prepopulate forms with information from system objects, custom objects, and form data.
To get data from system objects
You can use the server module form.js copyObjectToForm method to get data from an existing form object.
You can also use the metadata attributes for a system or custom object to prefill form data.
See also Extracting form field parameters from metadata.
To get data from other forms
You can use the FormModel.js copyFrom function to get data from an existing form object. In most cases, if
you have used app.getForm to get a copy of a form model, it makes more sense to use the function. You can
also transfer form data from one form to another directly. In the example below, if a customer has decided to use
the shipping address for billing, then the values from one form are copied to the other.

server.get(
'EditProfile',
server.middleware.https,
csrfProtection.generateToken,
userLoggedIn.validateLoggedIn,
function (req, res, next) {
var accountModel = getModel(req);
var profileForm = server.forms.getForm('profile'); //gets the profile.xml form definition
profileForm.clear(); //clears the JSON object
profileForm.customer.firstname.value = accountModel.profile.firstName; //copies data f
profileForm.customer.lastname.value = accountModel.profile.lastName;
profileForm.customer.phone.value = accountModel.profile.phone;
profileForm.customer.email.value = accountModel.profile.email;
res.render('account/profile', {
profileForm: profileForm, //adds the JSON o
breadcrumbs: [
{
htmlValue: Resource.msg('global.home', 'common', null),
url: URLUtils.home().toString()

49
MFRA

},
{
htmlValue: Resource.msg('page.title.myaccount', 'account', null),
url: URLUtils.url('Account-Show').toString()
}
]
});
next();
}
);

Converting form data to JSON objects


You can prepopulate forms with information from system objects, custom objects, and in-memory form data.
This data is available directly from the model you are working with or from the ViewData object used for
rendering the template.
The server module in the modules folder includes a forms.js module that converts form data into JSON
objects. For more information, see the following functions in the server-side JSDoc.
• parseForm(Form),
• copyObjectToForm(object, CurrentForm)
• findValue(formGroup, name),
• clearOptions(obj)

Securing forms
CSRF (Cross-Site Request Forgery) protection framework
Use the new CSRF framework to add fields that are protected from request forgery.
CSRF in MFRA is provided as middleware by Commerce Cloud. CSRF checks are performed as the
middleware step csrfProtection.validateAjaxRequest.
Example: CSRF check is made for login information. This example is available in the Account.js controller.

server.post(
'Login',
server.middleware.https,
csrfProtection.validateAjaxRequest,
function (req, res, next) {
var data = res.getViewData();
if (data && data.csrfError) {
res.json();
return next();
}

See Cross site request forgery protection.


For more information see validateRequest and validateAjaxRequest in the JSDoc.
Back to top.

Understanding the forms module


MFRA provides a forms module that abstracts the form definition into a JSON representation. If you want to
work with JSON objects, use the modules/forms methods to get and store data

50
MFRA

Sharing data between forms


Reusing form definitions
The form you create in your template can contain fields from multiple form definitions. The same fields can be
reused in other forms as many times as needed. This can be useful for prepopulating form data that the customer
has already entered. For example, address or payment preference data.
Using form metadata
You can use the metadata entered for a custom or system object in Business Manager to determine form
definition information. This allows you to manage data attributes in one place without having to change code.
For example, if you wanted to allow merchants to change the labels on form fields, you could include label as a
metadata attribute and reference it.
See Extracting form field parameters from metadata
Back to top.

Dynamic, Multi-part, embedded, or nested forms


MFRA does not include dynamic forms.
However, if you wish to create them, the isdynamicform tag can be used to generate dynamic forms. The code
generated by the tag is controlled by the dynamicform.isml template and the dynamicForm.js script.
MFRA does not include multi-part. embedded, or nested forms. Salesforce does not recommend them as a best
practice.
Back to top.

1.9. Contributing to Mobile First Reference Architecture

Reporting a bug or requesting a change


If you find a bug, please let us know by entering the bug for the Mobile First Reference Architecture repository.
If the bug or change is significant, you might also want to mention it on the mention it on the sitegenesis
channel on Slack.

Fixing bugs and contributing to the cartridge


Have some extra time and want to contribute? This section talks about the process of working on issues or
adding functionality.
Downloading and developing
1. Fork the SiteGenesis repository
2. Clone your fork to your local machine
3. Set your git upstream to the SiteGenesis repository
4. Create a branch and add your code
Testing locally
1. Make sure to run eslint to lint your code before committing. If you want to contribute regularly, consider
adding a git hook to automatically lint your code before you commit.
2. Run the automation tests locally
Submitting a pull request
1. Push your changes

51
MFRA

2. Submit the pull request to the master branch


3. Give read access permission for your fork to the Jenkins account and the testing team. See section below.
4. Check whether the pull request has passed the automated testing.
Giving read access permission for your fork
1. In Bitbucket, go to the screen for your fork
2. In the lefthand sidebar, click Settings.
3. Click User and group access.
4. Grant read access to the following users: sg-jenkins, zsardone, anndiep, and tanveer236.
What happens after you submit a pull request?
The MFRA team reviews your pull request to determine if it is something we can pull into the code. If the
change is not accepted, the team will decline the pull request and leave a comment. If the change is accepted and
passes all tests, the change is merged into the MFRA master branch. Note: If you need to make changes for the
pull request to be accepted, the team will leave a comment on the pull request.
Contacting the team about a pull request
If you have a question about the status of a pull request, the best method of contacting the team is to leave a
comment on the pull request.
Making a change to a pull request
If your pull request has failed tests, you might need to troubleshoot it and make changes to the pull request.
You can see that there were failures for the test, but to be able to debug, you must request that the team send
you the log from the test failures. Once you have committed your changes, rebase and remove the extra commit
message.

git commit -a -m "Update1"


git rebase -i HEAD~2 git push origin branchname --force

52

You might also like