Understanding Server-Side JavaScript

The B2C Commerce development components give you a great deal of flexibility in how you customize your ecommerce application. You develop your application locally, but run it on the server. A JavaScript interpreter runs on the application server to process each JavaScript class or method.

Use the B2C Commerce APIs (a combination of Javascripts and pipelets) to make scripting calls. Standard pipelets uses the APIs internally. To the JavaScript interpreter, the source of the script call is irrelevant.

B2C Commerce development components that can make JavaScript calls include the following:

ComponentDescriptionScripting Usage
PipelineModels the business process of the storefront application, including the actual business process, page display, and data input and validation. For example, the stores pipeline locates the nearest stores and lists them on the browser.Use an EVAL pipelet to evaluate an expression.
PipeletSmall, reusable units or building blocks that define B2C Commerce business logic. For example, the addProductToBasket pipelet within the cart pipeline creates and returns a ProductLineItem by assigning the specified Product and Quantity to the specified Basket.Custom: Create a pipelet within a pipeline that calls ascript.ds file to invoke a new business process. Standard: Access JavaScript for commonly used processes.
TemplateTemplates define how data and page information is transformed into the HTML that is rendered on a browser. For example, the cart template defines how the cart page appears, which includes basket contents, shipping types, product variations, availability, promotions, and shipping and tax calculations. This template uses <isscript> to run through odd and even row colors.Access a business process via an <isscript> tag, or wherever B2C Commerce JavaScript APIs are used.

How you use each of these elements depends on your application requirements.

For more information on the APIs, refer to the B2C Commerce Script API documentation.

B2C Commerce JavaScript must be stored in the /cartridge/scripts folder of a cartridge, with the .ds extension. However, it's possible to call a script from a different cartridge using the following syntax:

When you specify a script in this way, it doesn't search the cartridge path the way it would for a template, pipeline, js, or css file.

Use examples to understand how to incorporate script into your storefront.

Consider the following examples, taken from the SiteGenesis application:

The following is an example of using the EVAL pipelet in a pipeline to evaluate an expression, which in this case is confirms the order status.

Pipeline: COPlaceOrder-PlaceOrder

Pipelet: EVAL: Confirm order

Expression: Order.setConfirmationStatus(Order.CONFIRMATION_STATUS_CONFIRMED)

The continueshopping.ds script file is called by a Script pipelet in the Cart-ContinueShipping pipeline to find the last catalog click and return it as the target for the continue shopping button redirect.

In the example, the script imports and uses API calls from dw.system, dw.web, and dw.util. The JavaScript class URLUtils.url is located in the dw.util package.

The JavaScript used in a standard pipelet isn't public. For example, the GetProduct pipelet returns the product that is identified by a supplied product ID. The actual internal JavaScript code is unavailable to the developer.

The following portion of the ordertotals.isml template uses <isscript> to calculate order-level discounts as part of the order total.

The example uses the getAdjustedMerchandizeTotalPrice method.

Using standard JavaScript comment techniques, you can declare input and output variable to be used within a script.

You pass data into Salesforce B2C Commerce scripts using the @input annotation. You pass data out of a B2C Commerce script using the @output annotation.

In your script file, such as GetDefaultVariant.ds, you define the input variables and save the script file.

The example defines two input variables, Product and CurrentVariationModel, which contain string values. The last part of the variable definition is a description of the variable.

In your script file, such as GetDefaultVariant.ds, you define the output variables and save the script file.

The example defines an output variable, newProduct, which contains a product object.

When you’ve saved the script files with the input and output variables defined, script node pipelet using the script adds a Dictionary Input section to the pipelet properties with the name of the input variables and a Dictionary Output section to the pipelet properties with the names of the output variables.

You bind values to the parameters by entering a string, a variable name, or an expression.

In the example above, the input variable Prod defined in your script file is bound to the Product variable in the Pipeline Dictionary. The input variable CurrentVariationModel defined in your script file is bound to the Product variable in the Pipeline Dictionary.

When you bind a value to a parameter, the binding is stored in the pipeline file. If you remove script parameters, the pipeline file sometimes retains bindings for these removed parameters. To correct this, UX Studio checks pipelines and identifies orphaned bindings. The orphaned bindings are reported as Warnings in the Problems and Markers views. You can correct the problem by right-clicking the Warning and selecting the Quick Fix menu option.

Library and package import features can speed development.

JavaScript libraries can be shared across cartridges using the importScript("cartridgename/scriptname.ds") syntax (the cartridge name is optional if the script is located in the current cartridge). You can also share JavaScript pipelets, which can be stored in a dedicated cartridge so they can be used from other cartridges.

To specify the script, use the file name of the actual script (name.ds), relative to the scripts folder containing the pipeline or the scripts folder of another cartridge. A simple path refers to the pipeline cartridge and a prefix such as 'cartridgename:' refers to a specific cartridge.

FeatureCallDescription
Import packagesimportPackages( dw.util );Reuse processes that are outside the pipeline
Import classimportClass( dw.util.HashMap );Reuse classes that are outside the pipeline
Library mechanismimportScript( "mylib:export/orderhandling.ds" );Reuse scripts that are outside the pipeline

Use the B2C Commerce API Import Package as you would use a Java Import within a B2C Commerce JavaScript.

For example, consider the following statement:

Script Package Support

JavaScript 1.7 supports an Iterator class. This class is used in B2C Commerce for all Collection classes, for all Map classes, and for the Iterator (dw.util.Iterator) class.

When the dw.util.Iterator class isn't imported, "Iterator" refers to the native Iterator class. If dw.util.Iterator is imported, for example, with importPackage( dw.util ), then "Iterator" refers to the dw.util.Iterator.

These examples demonstrate iterating over objects.

When iterating over objects, make sure that you retrieve the object as part of the same transaction that acts on the object. Otherwise, B2C Commerce returns an error to prevent you from accidentally overwriting the object with stale data.

If a node has the Transactional property set to True, it executes as a separate transaction. Script nodes, by default, have the Transactional property set to true.

To successfully update objects, you can use one of the following methods:

Identify all objects to update, retrieve each object one at a time, and then update each object in a separate transaction. We recommended this method for updating objects.

An example is a pipeline that does the following:

  1. Assign node From_0 dw.catalog.ProductMgr.queryAllSiteProducts() To_0 products
  2. Loop node that iterates through products where the element key is CurrentProduct and the iterator key is products
    1. GetProduct pipelet node supplies a ProductID of CurrentProduct.ID to retrieve a Product
    2. Script pipelet node runs ScriptFile UpdateProducts.ds, a custom script file, which opens a transaction, updates the Product, and commits the transaction.

If you use this method, it's important to be aware if a transaction removes or modifies an object. If the object is referenced in later transactions, it might not exist.

Update all objects in a single transaction. Use this method if you have only a few objects to update and you want changes to be made to all or none of the objects.

An example is a pipeline that does the following:

  1. Assign node From_0 dw.catalog.ProductMgr.queryAllSiteProducts() To_0 products
  2. Script pipelet node runs ScriptFile UpdateProducts.ds, a custom script file, which opens a transaction, iterates through each product, updates it, and commits the transaction.

If you use this method, it's important to be aware that:

  • the transaction might become large, with a risk of exceeding the allowed transaction size and using a large amount of system resources.
  • the transaction commit might fail due to other concurrent threads. If the commit fails, none of the objects are updated.

As a best practice, don’t reuse the same iterator in multiple pipelets in the same pipeline. Doing so can cause an IllegalStateException: Iterator is invalid error.

Iterators in this state don't return accurate results. Any attempt to use them results in a WARN log message that says Iterator is invalid. Scripts and pipelines that generate this error should be rewritten to ensure that they generate correct and complete results.

For example:

  1. The SearchSystemObject pipelet creates a SearchResult iterator.
  2. The ExportCustomers pipelet uses that iterator to export the results to a file. As a side effect, the iterator moves to the end of the collection.
  3. A second pipelet attempts to use the same iterator. At this point, the iterator has been consumed so a warning is logged, and the results are inconsistent.

The correct way to reuse iterators is to repeat the SearchSystemObject step so that the second pipelet gets a fresh iterator.

JavaScript comes with built-in exception handling capabilities. These exception handlers make it possible to catch errors and resolve them internally, which insulates the user from complex decisions and cryptic technical information.

You can throw exceptions using the throw statement and handle them using the try...catch statements (JavaScript 1.5 or higher).

StatementDescription
throwUse the throw statement to throw an exception. When you throw an exception, you specify an expression containing the value of the exception expression
try...catchThe try...catch statement marks a block of statements to try, and specifies one or more responses should an exception be thrown. If an exception is thrown, the try...catch statement catches it.

The B2C Commerce JavaScript Engine supports Mozilla's proprietary extension of conditional catch statements. Conditional catch statements can be compared with typed catch statements in Java.

In this example, the exception is only caught if the thrown object is an IOError. All other exception objects that don't meet the condition bubble up to other exception handlers. The condition can be any boolean expression, even an expression as in the following code example:

In this example, B2C Commerce's extension to the error classes, which adds information about the underlying Java exception, is used to further constrain the catch statement.

Specific classes in the top-level API script package handle server-side JavaScript errors, as follows:

Exception TypeDescription
Conversion ErrorRepresents a conversion error. For example, between string and JavaScript objects.
ErrorError represents a generic exception.
EvalErrorRepresents an evaluation error. The eval() function is used in an incorrect manner.
FaultIndicates an RPC-related error in the system. It's always related to a systems internal Java exception. In particular, it provides details about the error sent from the remote system.
InternalErrorRepresents an internal error.
IOErrorIndicates an I/O related error in the system. It's always related to a systems internal Java exception. The class provides access to more details about this internal Java exception.
RangeErrorRepresents a range error. A numeric variable exceeds its allowed range.
ReferenceErrorRepresents a reference error. An invalid reference is used.
SyntaxErrorRepresents a syntax error. A syntax error occurs while parsing JavaScript code.
SystemErrorThis error is always related to a systems internal Java exception. This error indicates an error in the system, which doesn't fall into any of the other categories, such as, IOError. The class provides access to more details about this internal Java exception.
TypeErrorThe type of a variable isn't as expected.
URIErrorThe encodeURI() or decodeURI() functions are used in an incorrect manner.
XMLStreamErrorThis error is always related to a systems internal Java exception. This error indicates an XML streaming-related error in the system. The class provides access to more details about this internal Java exception. In particular, the class describes the location of the error.

The italicized errors are the six primary ECMAScript exception types defined by the JavaScript 1.5 specification.

Beyond the standard JavaScript error classes, B2C Commerce maps internal JavaExceptions to the following errors in JavaScript:

ErrorDescription
IOErrorIOException, SOAPException, and all subclasses
FaultSOAP fault
XMLStreamErrorErrors related to an XML stream
SystemErrorAll other Java Exceptions

In general, Java errors such as an OutOfMemoryException, can't be caught in scripting code. See Java concerning exception versus error.

The timeout values for SFTPClient, HTTPClient, and webreference objects are based on the timeout value of the calling script. The script timeout value is used as an upper bound for the timeout that is configured for the SFTPClient and HTTPClient.

It’s recommended to use the service framework, which has multiple monitoring and management features for web services. Nevertheless, for HTTPClient or SFTPClient, the timeouts can also be set as part of the function call.

To view the default global timeout:

  1. Select Administration > Global Preferences > Global Timeouts.

If you’re having difficulty with scripts timing out while calling a third-party system or service, you can contact Customer Support to change the timeouts temporarily.

B2C Commerce logging supports log categories in a way that is similar to Log4J2 (a Java-based logging utility supported by Apache Software Foundation). A log category is basically a name, where a dot in the name is interpreted as a hierarchical structure. For example, the log category "product.import" would be a subcategory of "product". All categories are subcategories of a "root" category.

B2C Commerce's category rules are:

  • If logging is enabled for a category, it’s also enabled for all of its subcategories
  • If logging is enabled for a specific severity level, then it's also enabled for all the higher severity levels

Examples:

If...Then...
Logging is enabled for "product"It's also enabled for "product.import"
logging is enabled for the root loggerIt's enabled for all custom log categories
WARN logging is enabled for "product"WARN and ERROR are logged for "product" and all its subcategories
WARN logging is enabled for "product" and DEBUG for "product.import"WARN and ERROR are logged for "product" and all its subcategories, for the subcategory "product.import" also DEBUG and INFO are logged.

Example: :

With Nested Diagnostic Context (NDC), the application can provide more context information, which is associated with the current running thread. A simple string can be provided as NDC, which is organized like a stack. Both the NDC and log category are reported as a standard element of the actual log message. The following example shows how to use this feature.

Message formatting supports a simple '{}' for inserting an argument. The method is tolerant if more argument placeholders are provided then arguments exist. In that case, the placeholder isn't replaced but remains a '{}'.

For backwards compatibility, B2C Commerce also supports Java message formatting.

Using log category-specific filters, you can decide which messages are made available to log targets. The filter rules enable you to define which log messages are generated. The changes you make become active after you click Save.

The hierarchy of logging levels in B2C Commerce is Fatal, Error, Warn, Info, and Debug. You can create your own categories of messages that are specific to the scripts you’re developing.

System log messages aren’t configurable.

We recommend that you plan a simple hierarchy of logging messages before creating logging categories. For example, you might want to plan category names that include the script name, object, and method as a standard. Having a standard set of logging names can make code maintenance easier.

B2C Commerce's custom logging is modeled after log4j logging. Logging uses hierarchical category names, granular logging, and similar methods for creating log statements in your code. For example, if you create three logging categories: product, product.import, and product.feed, product is considered the ancestor category and, if enabled, includes all messages logged to product.import and product.feed. The two descendant categories, if enabled, log only those messages assigned to them. You can control the granularity of logging by enabling or disabling categories.

The root filter rule is always present. It defines the base level for all custom logs (corresponds to 'ALL' in the old configuration). You can disable custom logging by setting the root level to OFF. If the filters contain just the root rule, all the custom logs are filtered by this rule. The defined level is the minimum level. If Info is set, then all the levels above Info are also generated (Warn, Error, Fatal).

To prevent excessive log size, maximum size of SOAP-related log messages in the custom debug log is 20,000 characters.

  1. Select Administration > Operations > Custom Log Settings.

  2. On the Custom Log Settings page, you can configure custom log setting filters:

    1. Enter a log category name.

      Custom log category names can't include 'custom' as a prefix.

      For each category, you can define a special logging rule. The level that you set on this rule applies to the defined category and to the subcategories of that category. The category rule is considered first, and then the root rule. Only one rule can be defined per category. You can disable a rule by unchecking the active checkbox of that rule. All is a reserved category name and can't be used in category rules.

    2. Select the Log Level.

      If you set the log to OFF, no log messages for that category are logged, not even Fatal.

      You can't configure the Debug level on a Production instance.

    3. Click Add.

      The log category appears in the list.

    4. To enable a specific category, check the Active box.

    5. To delete a log category, click the trash icon beside it.

      You can't delete the root log category.

  3. In the Custom Log Targets section:

    Only the messages that pass the filters are available for the log targets.

    1. Enter an email address for Fatal messages.

      One email is sent per minute. Messages with a Fatal log level can be sent to email recipients. You can enter a maximum of 1000 characters for all email addresses.

    2. Select the log levels that are written to the files:

      Different log files are created for each log level. The exception is that when log messages are written using a developer-provided file prefix, all messages for that log category are written to the same file, regardless of the log level.

      Custom log files are constrained to 10 MB per day.

      The Debug log level can't be enabled on a Production instance.

    3. View log messages in a log search web application, such as Log Center.

      There is a limit to number of log messages per realm per day that can be viewed in Log Center. A message appears when the limit is exceeded.

    4. View log messages using the Request Log Viewer in the Storefront Toolkit on instances other than Development and Production.

  4. Click Save to save your changes.

The following examples show how log messaging is enabled and disabled using settings that take advantage of the log hierarchy:

ConfigurationActiveCategoryLevelGenerated logs
1

-

yes

root

category1

WARN

INFO

category1: INFO, WARN, ERROR, FATAL

category1.subcategory: INFO, WARN, ERROR, FATAL

other custom categories: WARN, ERROR, FATAL

2

-

no

root

category1

WARN

INFO

category1: WARN, ERROR, FATAL

category1.subcategory: WARN, ERROR, FATAL

other custom categories: WARN, ERROR, FATAL

3

-

yes

root

category1

OFF

INFO

category1: INFO, WARN, ERROR, FATAL

category1.subcategory: INFO, WARN, ERROR, FATAL

other custom categories: no logs

4

-

yes

root

category1

WARN

ERROR

category1: ERROR, FATAL

category1.subcategory: ERROR, FATAL

other custom categories: WARN, ERROR, FATAL

5

-

yes

root

category1

WARN

OFF

category1: no logs

category1.subcategory: no logs

other custom categories: WARN, ERROR, FATAL

6

-

yes

yes

root

category1

category1.subcategory

WARN

INFO

DEBUG

category1: INFO, WARN, ERROR, FATAL

category1.subcategory: DEBUG, INFO, WARN, ERROR, FATAL

other custom categories: WARN, ERROR, FATAL

You can add logging to scripts.

The dw.system.Logger class includes the following methods:

  • getRootLogger() : Log - A static method for retrieving a category logger.
  • getLogger( String category ) : Log - A method for logging to this category logger. A category name must only consist of alpha numeric characters and a ".".

The dw.system.Log class lets you write to a specific log level and category. This class has the following methods:

  • public void debug( String msg, Object... args );
  • public boolean isDebugEnabled();
  • ... similar methods for info, warn, and error
  • public void fatal( ... );

For NDC support, the static method LogNDC getNDC() provides access to the NDC. The NDC property supports access to the nested diagnostic context.

The following methods are instance methods and not class methods:

  • debug( String msg, Object... args ) : void
  • isDebugEnabled() : boolean
  • info( String msg, Object... args ) : void
  • isInfoEnabled() : boolean
  • warn( String msg, Object... args ) : void
  • isWarnEnabled() : boolean
  • error( String msg, Object... args ) : void
  • isErrorEnabled() : boolean
  • fatal( ... );

Fatal is always enabled.

The API class dw.system.LogNDC has the following methods.

MethodDescription
push( String message ) : voidPushes new diagnostic context information for the current script execution.
peek() : StringLooks at the last diagnostic context at the top of this NDC without�removing it. The returned value is the value that was pushed last. If no context is available, then the empty string "" is returned.
pop() : StringCall this method before leaving a diagnostic context. The returned value is the value that was pushed last. If no context is available, then the empty string "" is returned.
remove() : voidRemoves the diagnostic context for this script call.

A script can't maintain state from one execution to the next. After the execution of a script, all variables are erased from the system.

To maintain state across calls, use other mechanisms such as:

  • The Session
  • Persistent Business Objects
  • Client-side cookies

Use the Session class to access details about browser actions, such as:

  • The current click stream if it's an HTTP or HTTPS session
  • The session's custom attributes (stored for the lifetime of the session and aren’t cleared when the customer logs out)
  • The current customer associated with the storefront session
  • If the customer associated with the session is authenticated
  • The session's custom privacy attributes (stored for the lifetime of the session and cleared when the customer logs out)
  • The unique session identifier

The SiteGenesis application handles most general ecommerce processing situations. However, as you develop your storefront, you might identify a particular processing need that isn't implemented. In that case, you might want to create a custom business object to handle the transaction. For example, you might want to offer customers the ability to add a monogram or personalized name to certain products such as tote bags or camera cases.

Using the Salesforce B2C Commerce APIs, not only can you collect information about the cookies that are bound to a session, you can also track all associated cookies via an array.

If one script sets the prototype of an object, it provides shared access for all instances of the object. Each request gets it own copy of the object, so that changes to Object X don't influence Object Y. If user or request A changes the prototype of Object X, it doesn't influence Object Y of user or request B, even if they are of the same type.

In JavaScript, every object includes an internal reference to another object. This object is known as a prototype object. This system allows inheritance in JavaScript.

Consider the following function:

You can now create a prototype object to which all instances of name have access. You can create the prototype object anywhere in the code at any time; and all instances of the object inherit.

So the following is true.

So if you create 10 objects of type Name, only one object of completename is created, because it's shared (inherited) for all instances of Name.

Where it gets confusing is when you create a property that you can write to, with shared across all me instances. If you create a prototype property of middlename, for Name: Name.prototype.middlename="middlename", then do the following, because properties are read/write.

This example returns the string middlename for both instances, because they inherit that property and the value. While there are two instances of the Name object, me and mywife,, there’s only one instance of middlename, and it's set to "middlename". However, you can then set middlename for me as follows:

Now there are two instances of Name, me and mywife, and two instances of middlename, one set to "Joseph" and one set to "Epps".

The prototype object is a way for instances of an object to inherit "shared" objects. However, when the object that is being inherited is a writeable property and the property is written to, updated or changed, the object gets its own copy of that object, so as not to interfere with each object's version of the property.

To simplify coding, we recommend that you use B2C Commerce JavaScript expressions within ISML templates.

This example prints the product display name from the Pipeline Dictionary:

The product SKU is:

This example checks if there are any items or gift certificates in the cart (as entered by the customer):

Empty has a particular significance when accessing an object, as follows:

OperatorPurpose

`empty()`

Returns true if the object is empty. Empty is defined as:

  • null

  • undefined

  • a string with zero length

  • an array with no elements

  • a collection with no elements

If the object is never declared, the JavaScript throws a `ReferenceError` so `Empty(object)` isn't `true`.

`isEmpty()`

Returns true if the collection is empty.

`isNotEmpty()`

If the object is empty, propagates an assertion using the specified message.

Parameters:

  • arg - the object to check.

  • msg - the assertion message.

Your company has multiple brands and each brand has its own host name, but they’re all part of the same site. You can let customers share baskets and other session information when they switch between brands via a redirect link.

This approach has several advantages, including:

  • customers can shop several brands and only need one checkout
  • registered customers don't have to log in again when switching brands
  • you can create cross-brand campaigns and promotions related to qualifying products in the basket

To retain session information, the customer clicks a redirect link in the storefront to switch from brand A (host name) to brand B. When clicking a redirect link, the customer stays in the same Salesforce B2C Commerce site and the same session, and the basket is preserved.

This feature only works within one B2C Commerce site: you can't share sessions and baskets across B2C Commerce sites.

Use this API method to provide links between brands on storefront pages:

This method generates redirect URLs to the target host names, and preserves the current storefront session. It doesn't work with direct storefront links, only via redirects. If the customer changes the URL, rather than clicking the redirect link, the session isn't preserved.

For example, if a customer goes from brand A to brand B through a direct link (or by typing the URL manually), B2C Commerce creates a session. Going back to brand A brings the customer to the old session. The cart isn't discarded. However, if the customer goes back to brand A through a redirect link, the customer session from brand B is used and the cart in brand A is discarded.

This example redirects to storefront homepage for hostname www.a-brand.com. You can substitute the second parameter with any object of type URL, such as a direct link to a product within another brand.