MFRA
MFRA
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
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.
• Setup-set up your local machine with the MFRA source code and your development and deployment tools.
• 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.
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.
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 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.
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';
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.
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.
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.
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
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.
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()).
This example shows a main function that conditionally executes next()or next(new Error()) depending on
whether an applepay order is being placed.
if (orderPlacementStatus.error) {
return next(new Error('Could not place order'));
}
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.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.
• 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.
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.
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.
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.
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.
See also MFRA hooks and Getting started with Mobile First Reference Architecture (MFRA)
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.
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.
10
MFRA
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
Note
This topic assumes that you are familiar with the Modules 1.1.1 CommonJS specification.
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
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});
}
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' );
return PIPELET_NEXT;
}
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.
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
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:
loads the classes in the util package, which can be then used like this:
13
MFRA
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.
Since there is no path syntax, this is requiring a global module. In this case it is requiring the mymodule
module.
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:
14
MFRA
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:
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.
15
MFRA
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.
Accessing modules
Commerce Cloud Digital supports CommonJS require syntax to access modules and relative paths.
16
MFRA
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.
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.
17
MFRA
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.
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
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;
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.
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.
'use strict';
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:
• 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
Note
When beginning development
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.
Jobs
It is not possible to create jobs with controllers. Jobs can only be created using pipelines.
21
MFRA
• Bootstrap
• Browsers
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.
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:
• Safari (MacOS)
• Firefox
• Chrome
• Edge
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.
Important
While Commerce Cloud attempts to conform to Level AA test standards, compliance with WCAG is the
sole responsibility of the merchant.
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.
• plugin-applepay - this contains an optional plugin cartridge that extends the base cartridge. This adds
apple pay to the site.
• 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:
a. Find the plus icon in the the lefthand blue menubar in BitBucket.
• 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:
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
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
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.
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.
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:
After installation, you can use sgmf-scripts in the current folder with a syntax similar to:
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
npm run compile:js && npm run compile:scss && npm run compile:fonts
Note
Usually, you only have to compile fonts once.
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:
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.
2. Read the ReadMe for the most up-to-date instructions on how to configure and run the repository.
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.
27
MFRA
1. Create a new storefront project (TBD - Studio features not yet defined. Use the command line build
option).
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.
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.
If you use the same commands as the app_storefront_base cartridge, you run:
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:
This command makes it simple to have up-to-date code on your Sandbox instance all time.
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.
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.
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.
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.
29
MFRA
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.
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.
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
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.
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.
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).
plugin_applepay: app_storefront_base
4. Click Apply.
What to do next
Next Step: Configuring MFRA.
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.
Create MFRA as a separate site and import the data for the site to see if the behavior remains the same.
• 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?
The testing technologies are automatically installed by node when you set up your MFRA project.
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 test:functional:docker Runs the functional tests from inside docker.
Note
* These commands require a dw.json file or additional command-line parameters.
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.
• hooks
• modules
• templates
• models
33
MFRA
• forms
See also Configuring the Mobile First Reference Architecture (MFRA) cartridge path
mkdir mysite
cd mysite
### cartridges
### app_custom_mybrand
### cartridge
...
### dw.json
### node_modules
### package.json
npm install
Note
To see all the available MFRA-commands, enter sgmf-scripts --help.
34
MFRA
mkdir mysite
cd mysite
### cartridges
### app_custom_mybrand
### cartridge
...
### dw.json
### node_modules
### package.json
npm install
Note
To see all the available MFRA-commands, enter sgmf-scripts --help.
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.
"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');
• 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.
36
MFRA
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.
<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.
@import "base/product/detail";
.description-and-detail {
margin-top: 2em;
.details {
@include media-breakpoint-up(sm) {
margin-bottom: 2em;
}
}
}
• 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.
38
MFRA
• isobject - used to wrap products for analytics and the Storefront Toolkit.
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.
• extend the model to include additional data to be used when rendering the template
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';
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();
server.extend(page);
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.
41
MFRA
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>
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.
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();
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.
<!--- --->
<div class="card">
<form action="${pdict.actionUrl}" class="login" method="POST"
name="MFRAHelloForm">
/**
* 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) {
res.render('MFRAResultTemplate', {
nickname : nickname});
next();
});
module.exports = server.exports();
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
45
MFRA
label.input.login.email=Email
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.
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.
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">
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.
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.
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
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.
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();
}
);
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();
}
50
MFRA
51
MFRA
52