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:
Component | Description | Scripting Usage |
---|---|---|
Pipeline | Models 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. |
Pipelet | Small, 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. |
Template | Templates 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
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.
Feature | Call | Description |
---|---|---|
Import packages | importPackages( dw.util ); | Reuse processes that are outside the pipeline |
Import class | importClass( dw.util.HashMap ); | Reuse classes that are outside the pipeline |
Library mechanism | importScript( "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:
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:
- Assign node From_0
dw.catalog.ProductMgr.queryAllSiteProducts()
To_0products
- Loop node that iterates through products where the element key is
CurrentProduct
and the iterator key isproducts
- GetProduct pipelet node supplies a ProductID of
CurrentProduct.ID
to retrieve aProduct
- Script pipelet node runs ScriptFile UpdateProducts.ds, a custom script file, which opens a transaction, updates the
Product,
and commits the transaction.
- GetProduct pipelet node supplies a ProductID of
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:
- Assign node From_0
dw.catalog.ProductMgr.queryAllSiteProducts()
To_0products
- 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:
- The
SearchSystemObject
pipelet creates a SearchResult iterator. - 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. - 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).
Statement | Description |
---|---|
throw | Use the throw statement to throw an exception. When you throw an exception, you specify an expression containing the value of the exception |
try...catch | The 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 Type | Description |
---|---|
Conversion Error | Represents a conversion error. For example, between string and JavaScript objects. |
Error | Error represents a generic exception. |
EvalError | Represents an evaluation error. The eval() function is used in an incorrect manner. |
Fault | Indicates 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. |
InternalError | Represents an internal error. |
IOError | Indicates 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. |
RangeError | Represents a range error. A numeric variable exceeds its allowed range. |
ReferenceError | Represents a reference error. An invalid reference is used. |
SyntaxError | Represents a syntax error. A syntax error occurs while parsing JavaScript code. |
SystemError | This 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. |
TypeError | The type of a variable isn't as expected. |
URIError | The encodeURI() or decodeURI() functions are used in an incorrect manner. |
XMLStreamError | This 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:
Error | Description |
---|---|
IOError | IOException, SOAPException, and all subclasses |
Fault | SOAP fault |
XMLStreamError | Errors related to an XML stream |
SystemError | All 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:
- 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 logger | It'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.
-
Select Administration > Operations > Custom Log Settings.
-
On the Custom Log Settings page, you can configure custom log setting filters:
-
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. -
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.
-
Click Add.
The log category appears in the list.
-
To enable a specific category, check the Active box.
-
To delete a log category, click the trash icon beside it.
You can't delete the root log category.
-
-
In the Custom Log Targets section:
Only the messages that pass the filters are available for the log targets.
-
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.
-
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.
-
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.
-
View log messages using the Request Log Viewer in the Storefront Toolkit on instances other than Development and Production.
-
-
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:
Configuration | Active | Category | Level | Generated 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.
Method | Description |
---|---|
push( String message ) : void | Pushes new diagnostic context information for the current script execution. |
peek() : String | Looks 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() : String | Call 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() : void | Removes 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:
Operator | Purpose |
---|---|
`empty()` | Returns true if the object is empty. Empty is defined as:
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:
|
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.