SiteGenesis JavaScript Controllers (SGJC) Standards Compliance

This topic covers the following standards and supported browser versions.

Cookies Notification/Opt-in for European Cookie Law

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

If this asset is... Then...
Missing or offline No notice is given. The cookies are set as they always have been. This approach is used in the USA, for example.
Present and online* The cookie_hint content appears. Clicking I ACCEPT sets the cookies and causes the popup to not appear again.
Note: * If customers want a more relaxed interpretation of the Cookie Law, the can add a Close button. We exclude the Privacy page so that it can be read without seeing the notification.

Browsers

SGJC officially supports the following browsers, and one version earlier.

Desktop

  • Chrome 53+
  • Firefox 49+ (including the latest Extended Support Release (ESR) that is, 45)
  • Microsoft Edge 38+
  • Microsoft Internet Explorer 11
  • Safari 10+

Mobile

  • iOS 10+: Chrome (most recent)
  • Safari (most recent)

See Business Manager User Interface.

Supported Browsers Desktop (supporting the latest browser version):

Commerce Cloud Store supports the latest version of the browsers listed above, as well as one version earlier. Refer to documentation for more details. The plus(+) sign signifies support of both major and minor browser releases following the listed browser version. End of Support for Opera As of January 1, 2017, Commerce Cloud will no longer support Opera due to infrequent client use. We continue to evaluate browser usage throughout 2018, and if we see changes with browser adoption, we consider supporting it. Implications of These Changes To ensure optimal performance, we encourage all clients to upgrade to a supported browser prior to January 1, 2017. Commerce Cloud continues to test and fix bugs in all supported browsers. Customers are welcome to use any browser they want, but there can be noticeable performance issues with unsupported browsers. If you encounter any issues with a supported browser, open a ticket with Commerce Cloud Support.

CSS Input Field Types

The SiteGenesis application supports HTML5 input field types:
  • color
  • date
  • datetime
  • datetime-local
  • email
  • month
  • number
  • range
  • search
  • tel
  • time
  • url
  • week
Note: Not all of these types are relevant for the SiteGenesis application.

For more information, see http://www.w3schools.com/html/html5_form_input_types.asp.

SiteGenesis and Web Content Accessibility Guidelines (WCAG)

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

Note: Salesforce B2C Commerce does not guarantee or certify compliance of SiteGenesis with any WCAG level.

The SGJC application was changed to better conform to the WCAG guidelines. The list of changes shown here is intended to provide examples of how you can make your storefront application more accessible:

  • Added context in the titles of category refinements, folder refinements, and price refinements.
  • Added prefixes in the set/bundle products titles for added context.
  • Mini cart button has the title Go to Cart and not the same text as the button.
  • The compare checkboxes on the category landing page product tiles now have unique label text. The unique text appends the product name to the text and visually hides it. Then, a span was added with just the text "Compare".
  • Removed the title from the image and added it to the link. The image only requires alt, not a title.
  • Made the title different and more contextual from the text.
  • Removed invalid hypertext reference and added visually hidden text.
  • Added visually hidden label to email form element in demo data.
  • Removed unnecessary titles from images, added alt where missing, and ensured that previous changes were not affected.
  • The user-links in headercustomerinfo.isml now include a title that adds more context to the links.
  • Added visually hidden text to buttons.
  • Changed titles of color refinement swatches to add more context.
  • The password reset link now has a title that adds context.
  • Changed demo data in library.xml for the footer content assets and added (or modified) footer titles.
  • Store locator and user icons now have titles that add more context.
  • Product item names in the cart have more contextual titles.
  • Added visually hidden text to the link of category landing page's slot banner.
  • Changed the size chart link title to add more context.
  • Made product action titles are different from text and add context.
  • Changed the title of color and size to add more context.
  • Added prefixes to titles.
  • Added visually hidden text to the social sharing links.
  • Added address and add credit card pages titles have more context.
  • Added titles to paging.
  • Product tile titles now contain prefixes for more context.
  • Removed the invalid hypertext reference error by removing the href attribute.
  • Breadcrumb titles have a prefix for added context.

The WCAG guidelines followed were:

  • Level A: 2.4.4 Link Purpose (In Context)
  • Level AAA: 2.4.9 Link Purpose (Link Only)

The title-related corrections to SiteGenesis use technique H33 (http://www.w3.org/TR/2014/NOTE-WCAG20-TECHS-20140916/H33). The link text is supplemented with the title attribute to add more context, making it easier for people with disabilities to determine the purpose of the link.

Consent Tracking in SGJC

Commerce Cloud enables merchants to track personal information about their shoppers, and use this information to improve their shoppers’ overall shopping experience. Some merchants can decide to provide their shoppers a way to deny or grant their consent to such tracking.

This topic describes a sample implementation for consent tracking in SGJC. The purpose of this sample implementation is to suggest how you can implement this capability on a storefront adapted from SGJC. This sample implementation is meant to be informative, but not prescriptive.

This sample implementation uses:

  • A content asset for displaying a consent request message to the shopper
  • A site-specific preference, Tracking, to disable tracking by default
  • A session-specific flag to allow tracking if the shopper grants consent

More details about the sample implementation are provided later in this document.

Content Asset for the Consent Request Message

To display a consent message to the shopper, the sample implementation uses a content asset whose internal ID is consent_tracking_hint. This content asset contains meaningless text, which you can replace with your own message.

Site-Specific Preference (Tracking)

The Tracking site preference determines the default tracking behavior for a site. If set to Opt-in, personal information is not tracked by default for all shoppers visiting the site; otherwise, personal information is tracked.

To set this preference, select Merchant Tools > site > Site Preferences > Privacy.

The sample implementation assumes that the Tracking preference is set to Opt-In.

Session Tracking Flag

The sample implementation presents a consent request message to the shopper, who can choose to allow tracking. If the shopper allows tracking, the sample implementation enables tracking during the shopper’s session.

You can enable tracking on a session by calling the following method:
  • dw.system.Session.setTrackingAllowed(boolean) -- If you call this method with a value of true, the method enables tracking for the current session; false, disables tracking.

You can determine the current value of the tracking flag by calling the following method:

  • dw.system.Session.isTrackingAllowed() -- This method returns the current state of the session’s tracking flag (true indicates that tracking is enabled; false, disabled).

Extra Implementation Details

The sample implementation uses a server-side endpoint to set the session tracking flag based on the value of a URL parameter. The shopper’s choice to grant or deny consent determines the value of this parameter.

The server-side endpoint is named Account-consentTracking:

function consentTracking() {
   var consent = request.httpParameterMap.consentTracking.value == 'true';
   session.custom.consentTracking = consent;
   session.setTrackingAllowed(consent);
}

The result of the shopper’s decision is stored in the server-side variable session.custom.consentTracking. It is important to communicate the state of this variable with the client-side code running on your storefront, so the sample implementation places this information in a global location that is accessible to all SGJC-based storefront pages (footer_UI.isml).

The client-side implementation can check the state of this variable and store it in a client-side variable (consent). The client-side implementation can then use the value of the variable to determine whether to display the consent request message to the shopper.

Several other changes to the client side complete the sample implementation.

The following properties are added to the β€˜resources’ object in the Resource.ds file:

TRACKING_CONSENT: Resource.msg('global.tracking_consent', 'locale', null),
TRACKING_NO_CONSENT: Resource.msg('global.tracking_no_consent', 'locale', null),

The following properties are added to the β€˜urls’ object in the Resource.ds file:

consentTracking: URLUtils.url('Page-Show', 'cid', 'consent_tracking_hint').toString(),
consentTrackingSetSession: URLUtils.url('Account-ConsentTracking').toString(),

The client-side variable (consent) is set before app.js is included in the footer_UI.isml template:

<script>var consent = ${session.custom.consentUser};</script>
<script src="${URLUtils.staticURL('/js/app.js')}"></script>

A consentTracking module is required in app.js:

consentTracking = require('./consentTracking);
...
consentTracking.init();
...
$('.consent-tracking-policy').on('click', function (e) {
        e.preventDefault();
        consentTracking.show();
    });

Lastly, the consentTracking module is implemented:

function getConsent() {
    dialog.open({
        url: Urls.consentTracking,
        options: {
            closeOnEscape: false,
            dialogClass: 'no-close',
            buttons: [{
                text: Resources.TRACKING_CONSENT,
                click: function () {
                    $(this).dialog('close');
                    $.ajax({
                        type: 'GET',
                        url: util.appendParamToURL(Urls.consentTrackingSetSession, 'consentTracking', true),
                        success: function () {
                            showPrivacyDialog();
                        },
                        error: function () {
                            showPrivacyDialog();
                        }
                    })
                }
            }, {
                text: Resources.TRACKING_NO_CONSENT,
                click: function () {
                    $(this).dialog('close');
                    $.ajax({
                        type: 'GET',
                        url: util.appendParamToURL(Urls.consentTrackingSetSession, 'consentTracking', false),
                        success: function () {
                            showPrivacyDialog();
                        },
                        error: function () {
                            showPrivacyDialog();
                        }
                    })
                }
            }]
        }
    });
}

function enablePrivacyCookies() {
    if (document.cookie.indexOf('dw=1') < 0) {
        document.cookie = 'dw=1; path=/';
    }
    if (document.cookie.indexOf('dw_cookies_accepted') < 0) {
        document.cookie = 'dw_cookies_accepted=1; path=/';
    }
}
function showPrivacyDialog(){

    if (SitePreferences.COOKIE_HINT === true && document.cookie.indexOf('dw_cookies_accepted') < 0) {
        // check for privacy policy page
        if ($('.privacy-policy').length === 0) {
            dialog.open({
                url: Urls.cookieHint,
                options: {
                    closeOnEscape: false,
                    dialogClass: 'no-close',
                    buttons: [{
                        text: Resources.I_AGREE,
                        click: function () {
                            $(this).dialog('close');
                            enablePrivacyCookies();
                        }
                    }]
                }
            });
        }
    } else {
        // Otherwise, we don't need to show the asset, just enable the cookies
        enablePrivacyCookies();
    }
}
var consentTracking = {
    init: function () {
        if (consent == null && SitePreferences.CONSENT_TRACKING_HINT) { // eslint-disable-line no-undef
            getConsent();
        }
        
        if (consent != null && SitePreferences.CONSENT_TRACKING_HINT){ // eslint-disable-line no-undef
            showPrivacyDialog();
        }
        
    },
    show: function () {
        getConsent();
    }
};
module.exports = consentTracking;

Downloading a Shopper's Information in SGJC

When you implement your SGJC-based storefront, consider providing your shoppers with a mechanism to download their data.

Registered users see a Download my data button on the My Account page. When a shopper clicks this button, SGJC downloads a JSON file to the shopper's browser with the following information:

  • Profile
  • Address
  • Payment instruments
  • Orders
  • Wish lists
  • Gift registries
  • Shopping lists

The JSON file illustrates the type of information to provide to your shoppers. Your business can provide different data in a different format.

Sample JSON file

This sample file shows the type of information you can provide to the shopper.

{
  "profile": {
    "birthday": "1988-10-21T00:00:00.000Z",
    "companyName": "",
    "customerNo": "D00000001",
    "email": "[email protected]",
    "fax": "",
    "firstName": "Test1",
    "gender": "Female",
    "jobTitle": "",
    "lastLoginTime": "2018-02-14T20:07:31.074Z",
    "lastName": "Doe",
    "lastVisitTime": "2018-02-14T20:07:31.074Z",
    "phoneBusiness": "",
    "phoneHome": "",
    "phoneMobile": "",
    "preferredLocale": "",
    "previousLoginTime": "2015-05-18T20:43:17.000Z",
    "previousVisitTime": "2015-05-18T20:43:17.000Z",
    "salutation": "",
    "secondName": "",
    "suffix": "",
    "taxID": null,
    "taxIDMasked": null,
    "taxIDType": null,
    "title": "",
    "male": false,
    "female": true,
    "nextBirthday": "2018-10-21T00:00:00.000Z"
  },
  "addressbook": [
    {
      "address1": "104 Presidential Way",
      "address2": null,
      "city": "Woburn",
      "companyName": null,
      "countryCode": "us",
      "firstName": "Test1",
      "fullName": "Test1 User1",
      "id": "Home",
      "jobTitle": null,
      "lastName": "User1",
      "phone": "781-555-1212",
      "postalCode": "01801",
      "postBox": null,
      "salutation": null,
      "secondName": null,
      "stateCode": "MA",
      "suffix": null,
      "suite": null,
      "title": null
    },
    {
      "address1": "91 Middlesex Tpke",
      "address2": null,
      "city": "Burlington",
      "companyName": null,
      "countryCode": "us",
      "firstName": "Jane",
      "fullName": "Jane Doe",
      "id": "Work",
      "jobTitle": null,
      "lastName": "Doe",
      "phone": "781-555-1212",
      "postalCode": "01803",
      "postBox": null,
      "salutation": null,
      "secondName": null,
      "stateCode": "MA",
      "suffix": null,
      "suite": null,
      "title": null
    }
  ],
  "wallet": [],
  "orders": [],
  "productList": {
    "whishlists": [],
    "giftregistries": [],
    "shoppinglists": []
  },
  "thirdpartydata": {}
}

Implementation Details

This example conditionally provides a Download my data button in the accountoverview.isml template.

<isif condition="${pdict.downloadAvailable}">
   <a class="profile-data-download button" 
      href="${URLUtils.url('Account-DataDownload')}">  ${Resource.msg('account.landing.databutton','account',null)}</a>
</isif>

When the user clicks the button, the Account-DataDownload controller is called. The Account.js controller file applies the following guard to the datadownload() function, exporting it as DataDownload.

/** returns customer data in json format.
 * @see {@link module:controllers/Account~datadownload} */
exports.DataDownload = guard.ensure(['get', 'https', 'loggedIn'], datadownload);

The body of the datadownload() function is implemented in Account.js as follows.

/**
 * Allows a logged in user to download the data from their profile in a json file.
 */
function datadownload() {
    var profile = customer.profile;
    var profileDataHelper = require('~/cartridge/scripts/profileDataHelper');
    let response = require('~/cartridge/scripts/util/Response');
    var site = require('dw/system/Site');
    var fileName = site.current.name + '_' + profile.firstName + '_' + profile.lastName + '.json';
    response.renderData(profileDataHelper.getProfileData(profile), fileName);
    return;
}

This function relies on the helper script profileDataHelper.js, which provides several helper functions, such as getWallet(profile), getWishlists(ProductListMgr), and so on. The helper script exports getProfileData , which calls the helper functions, constructs the JSON string, and returns the result to the shopper's browser.


exports.getProfileData = function (profile) {
    var ProductListMgr = require('dw/customer/ProductListMgr');
    var downloadJSONObj = {};

    downloadJSONObj.profile = getProfile(profile);
    downloadJSONObj.addressbook = getAddressBook(profile);
    downloadJSONObj.wallet = getWallet(profile);
    downloadJSONObj.orders = getOrders(profile);
    downloadJSONObj.productList = {
        whishlists: getWishLists(ProductListMgr),
        giftregistries: getGiftregistries(ProductListMgr),
        shoppinglists: getShoppingLists(ProductListMgr)
    };

    downloadJSONObj.thirdpartydata = {};
    return JSON.stringify(downloadJSONObj, null, 2);
};