Sie sind auf Seite 1von 59

Documentation Draft SinnerSchrader Gucci Group projects 2011 Felix Abraham et.al.

Index
1.1 Hello World - How do I create a new page?..............................................................................5 Motivation / context / problem scenario.....................................................................................5 Defining the URLs / routes.........................................................................................................5 Routes / routing......................................................................................................................6 Backend controller......................................................................................................................8 Templating.................................................................................................................................10 Helper classes............................................................................................................................12 i18n / internationalization.........................................................................................................12 Folder structure.........................................................................................................................13 1.2 Use case "Product detail page"................................................................................................13 Motivation / context / problem scenario...................................................................................13 Preconditions / prior knowledge...............................................................................................13 Demandware products..........................................................................................................14 Import...................................................................................................................................15 GlobalData............................................................................................................................15 Implementation.........................................................................................................................16 Backend................................................................................................................................16 Frontend................................................................................................................................17 1.3 Use case "Category page"........................................................................................................17 Motivation / context / problem scenario...................................................................................18 Preconditions / prior knowledge...............................................................................................18 Implementation.........................................................................................................................18 Server-side implementation..................................................................................................18 Client-side implementation..................................................................................................20 1.4 Use case "Checkout"................................................................................................................20 Motivation / context / problem scenario...................................................................................20 Preconditions / prior knowledge...............................................................................................20 Functional requirements.......................................................................................................20 Demandware settings............................................................................................................20 Implementation.........................................................................................................................21 Gift option............................................................................................................................22 Coupon codes.......................................................................................................................22 Personal customer data (may need registration / login).......................................................22 Address details (shipment / billing address).........................................................................23 Shipping types......................................................................................................................23 Payment types.......................................................................................................................24 Order completion..................................................................................................................24 2.1 Reference for "CurrentMasterProduct and CurrentVariantProduct"........................................24 Structure and properties............................................................................................................25 Creating objects for products....................................................................................................26 Example 1: Creating a product with a master ID.................................................................26 Example 2: Creating a product with a variant ID:................................................................27 2

2.2 Recipe "Content asset with jsonContent"................................................................................27 Motivation / context / problem scenario...................................................................................27 Solution model..........................................................................................................................28 Implementation.........................................................................................................................28 Advantages................................................................................................................................28 Disadvantages...........................................................................................................................29 Outlook......................................................................................................................................29 2.3 Recipe "Product detail page with globalData".........................................................................29 Motivation / context / problem scenario...................................................................................29 Solution model..........................................................................................................................30 Implementation.........................................................................................................................30 2.4 Use case "Send email".............................................................................................................30 Motivation / context / problem scenario...................................................................................31 Preconditions / prior knowledge...............................................................................................31 Overview of steps to be completed...........................................................................................31 a) Configuration of MVCPipes............................................................................................31 b) Creation of a sendMailTo() helper method......................................................................32 c) Implementation of the SendEmail helper in the controller of the relevant page..............32 d) Creation of an asset in Demandware Business Manager.................................................34 email_tell_a_friend asset.................................................................................................34 Integration of asset within template.................................................................................35 e) Creation of a business object, in order to be able to validate and subsequently access data from the form................................................................................................................35 3.1 Use case "Frontend controller"................................................................................................36 Motivation / context / problem scenario...................................................................................36 Preconditions / prior knowledge...............................................................................................37 Implementation.........................................................................................................................37 Creating the basic framework...............................................................................................37 The rootNode property.........................................................................................................38 ready() and nodeReady()......................................................................................................38 Accessing return values from the backend controller..........................................................38 Sub-methods.........................................................................................................................38 has properties........................................................................................................................39 Access to Weet URLs...........................................................................................................39 Access to widgets and inits...................................................................................................39 3.2 Use case "Sharable widget".....................................................................................................40 Motivation / context / problem scenario...................................................................................40 Preconditions / prior knowledge...............................................................................................40 Embedding the Sharable widget................................................................................................40 Creating the basic framework...............................................................................................40 3.3 Recipe "Sharing a product page".............................................................................................41 Motivation / context / problem scenario...................................................................................41 Various solutions.......................................................................................................................41 a) Facebook "Like" button....................................................................................................41 3

b) Facebook/Twitter share link.............................................................................................42 c) Others...............................................................................................................................42 Outlook......................................................................................................................................43 3.4 Use Case "Frontend JavaScript inits"......................................................................................43 Motivation / context / problem scenario...................................................................................43 Conventions...............................................................................................................................43 Solution model 1: Simple init (without Joose class).................................................................43 Solution model 2: Complex init (with Joose class)...................................................................43 Implementation.........................................................................................................................44 Advantages................................................................................................................................44 Disadvantages...........................................................................................................................44 Outlook......................................................................................................................................44 3.5 Use Case "Frontend JavaScript widgets".................................................................................45 Motivation / context / problem scenario...................................................................................45 Conventions...............................................................................................................................45 Solution model 1: Simple widgets (without Joose class)..........................................................45 Solution model 2: Complex widgets (with Joose class)............................................................45 Implementation.........................................................................................................................46 Advantages................................................................................................................................46 Disadvantages...........................................................................................................................47 Outlook......................................................................................................................................47 3.6 Use case "Video player init"....................................................................................................47 Motivation / context / problem scenario...................................................................................47 Preconditions / prior knowledge...............................................................................................47 Embedding the video player init...............................................................................................47 Creating the basic framework...............................................................................................48 4 Recipe "CSS"..............................................................................................................................48 Motivation / context / problem scenario...................................................................................49 The use of LESS-CSS...............................................................................................................49 Client-side usage..................................................................................................................49 Splitting of CSS files.................................................................................................................49 CSS structure.............................................................................................................................50 Conventions..........................................................................................................................50 Conversion for use in a live context..........................................................................................51 5. Use case "Make AMS content maintainable"............................................................................51 Motivation / context / problem scenario...................................................................................51 Creating a page in AMS............................................................................................................51 Qooxdoo window......................................................................................................................52 Template...............................................................................................................................52 Qooxdoo model.........................................................................................................................53 Document.............................................................................................................................53 Node Exporter...........................................................................................................................54 Export...................................................................................................................................54

1.1 Hello World - How do I create a new page?


This documentation is intended to show you how to create a page, as well as the steps that are necessary to ensure that the desired result is provided at your chosen URL. By applying this information, the reader should be capable of defining a URL, implementing the logic at the appropriate place and producing a template for presenting the page.

Motivation / context / problem scenario


Starting from scratch can be quite difficult. This chapter helps with the first steps that are necessary to create a website following the same principles that apply to all Gucci shops created by SinnerSchrader. The main intention here is not so much showing the various aspects and concepts of Demandware but rather to present those that have been used and are important in shops created by SinnerSchrader.

Defining the URLs / routes


Unlike standard Demandware implementations, we will not be using the graphical pipelines. Instead, we have constructed an MVC framework that provides the range of options with which you will be familiar as a web developer. The graphical pipelines have a number of disadvantages. First, they are highly static and difficult to maintain. Above all, however, it is also theoretically impossible to work on a pipeline with multiple developers. The reason for this is that even the repositioning of a single pipelet leads to changes at various places in the XML file a file in which the pipeline is ultimately saved. This leads to a situation where conflicts that occur when merging before checking-in to a shared repository are very difficult or impossible to resolve. In addition, the URLs that result from the pipeline model are strictly predefined (...demandware.../PIPELINENAMEACTION). The MVC framework that has been implemented here permits the user-friendly definition of any number of URLs in the form of regular expressions. Routing functionality is also provided that permits the centralized definition of URL patterns for particular functions. The end result is a powerful tool for defining any number of URL patterns in a simple way. In addition, one also has the option of specifying that particular patterns will only be matched against URLs if the situation involves either a POST or a GET request. With this pattern familiar from the Rails environment one can define two different routes for identical URLs. Accordingly, accessing a URL with a GET request can first result in the presentation of a form; however, if the same URL is then accessed from a POST request, this leads to the processing of the form data from the submitted form. In addition, one has the option of defining whether requests that match this URL pattern are to be treated as transactional requests in the Demandware sense (i.e. they may change or create Demandware business objects). One may also define whether a request may only be made via HTTP or only via 5

HTTPS/SSL. An automatic redirect to the corresponding protocol is supplied as standard and can if desired be utilized. The definition of a page within the MVC framework consists of the following steps:

Defining the route in route.ds Writing a method in the backend controller Writing the ISML template

Routes / routing
As already briefly explained, routes use a queried URL to describe a particular kind of behavior and call a "handler" in this case, a class and a method of the class. Only two pieces of information are mandatory for defining a route: a regular expression, which describes its pattern with a queried URL, plus the specification of a backend controller and corresponding controller action that is to be called. Further details must only be given if additional, special restrictions are to be defined (e.g. access is possible only via HTTPS or only POST requests are allowed). The definition of the backend controller and the action is made using a simple string, whereby controller and action are separated by a hyphen (e.g. Home-view). Here, it is important that the name of the action is written entirely in lowercase. The system then searches for a controller with the name "HomeController", in which the method view will be accessed. If no template is specified, then the name of the template is derived automatically from the definition of the backend controller and the action. In this case, the system assumes that the template has the same name as the action (e.g. view.isml) and is located in a folder with the same name as the controller (e.g. /templates/default/home/view.isml). The possible attributes of a route are defined in the header of the file route.ds:

handler: e.g. Account-login, combination of backend controller and method/action to be executed, separated by a dash (mandatory) method: POST or GET if set, then the route definition is applied only for POST or GET requests (optional) secure: if true, only HTTPS/SSL requests permitted (optional); depending on implementation, automatic redirect to the HTTPS version of the page insecure: if true, only HTTP/non-SSL requests permitted (optional); depending on implementation, automatic redirect to the HTTP version of the page transactional: if true, (optional) permits the alteration of Demandware business objects preRunPipelets: e.g. ["LoginCustomer"], (optional), list of pipelets that will be executed before the controller method is called template: e.g. account/forgotpassword.isml, (optional), here, variant templates can be specified. Normally, the name is derived from the name of the action. json: if true, (optional) then a special rendering template for JSON is used

Here is some sample code for a simple route definition file (route.ds): 6

/** * Route the app * This file is the entry point for all requests * Pathes are matched against the defined regexes * * @input path : String URL path * @input CurrentHttpParameterMap : Object * * @output controllerActionName : String A Continuation Object * @output continuation : Object A Continuation Object * @output pathParas : Array the paras in the URL * @output route : Object * @output country : Object * @output fixedParameters : Object * */ importScript("global/require.ds"); var router = require("pipeline/router.ds"); requireJoose(); require("gc/srv/bo/country/Country.ds"); require("gc/srv/util/URL.ds"); /** * Sample Route "^/login/(\w+)$", { // match the path /login - The regex captures will be passed as position parameters to the controller method // Use the Account controller (gc.srv.controller.Account) with the method login // Method names a lowercased before calling them handler: "Account-login", method: "POST", // only accept post request. Other possible value GET. (optional) secure: true, // only accept secure requests (optional) transactional: true, // do transactions in requests preRunPipelets: ["LoginCustomer"] // run the LoginCustomer pipelet before the request template: "account/forgotpassword.isml", // use this template (optional, default to lowercase controller name/lowercase method name) json: true // set to true if route always returns JSON } */ importScript('global/pipeline/route.ds'); function execute( pdict ) { return router.route(Routes, pdict) } //var shopPrefix = gc.srv.util.URL.shopPrefix; var shopPrefix = "shop-[^/]+"; var storiesPrefix = gc.srv.util.URL.storiesPrefix; var pid = "[\\w-]+"; var pids = "[\\w-,]+";

var var var var

qty = "\\d+"; orderNo = "\\d+"; zipCode = "\\w+"; defaultLocale = dw.system.Site.getCurrent().getDefaultLocale();

// first matching route wins // add more routes freely //var prefix = "^/([a-z]{2,5})/([a-z-]+)" var prefix = "^/([a-z-_A-Z]+)"; var Routes = [ prefix+"/grant/me/access/to/smc$", { handler: "Access-grant", everyone: true }, // collections - looks prefix+"/"+shopPrefix+"/collections/looks/(.*)/*$", { insecure: true, handler: "Collections-looks" }, // collections - looks - short url prefix+"/c/(.*)/(" + pid + ")/*$", { insecure: true, handler: "Collections-looks" } ];

Backend controller
In accordance with the MVC approach, the business logic i.e. the Model is encapsulated in the business objects, while the Controller assumes control and prepares the business objects necessary for the View of the respective request. The template itself should therefore contain only the code that is required to display the content (MVC pattern). Every controller is derived from the PageController class. This base class contains a number of helper functions used e.g. to provide a standardized way of describing and providing caching, tracking or other items. In addition, this base class also implements key MVC framework functions, as well as server-side redirects. Generally, one needs to know nothing more than the fact that this base class represents all backend controllers. In an actual controller class, one may now override the default implementations of the PageController. This is useful to change the caching behavior, for example. In PageController, caching is deactivated. Here, the method cacheEnabled always returns false. If you now wish to change the caching behavior for the pages that are rendered via a particular controller, then you can override the functions cacheEnabled, cacheMinutes, cacheHours and cacheVaryByPricePromotion accordingly. The important fact here is that the controller 8

determines the particular caching behavior of the pages generated in each case. The consequence of this is that, depending on the situation, you may need to make use of a different controller to achieve different caching behavior. Above all, however, here is the place you define the business logic necessary to render a page. In the process, the MVC framework automatically calls the method that has been defined in the route. The result returned by each of these methods is a JavaScript object that will later be available in the template as a property of the variable p. This is similar to the pdict entity familiar from Demandware. Here is some sample code for a simple controller:
importPackage( dw.system ); importPackage( dw.util ); importScript("global/require.ds"); requireJoose(); require_(); require( "gc/srv/controller/PageController.ds" ); require( "gc/srv/bo/Category.ds" ); require( "gc/srv/bo/content/Folder.ds" ); require( "gc/srv/bo/content/Asset.ds" ); /* * Controller for stories */ Module("gc.srv.controller", function (m) { Class("Content", { isa: gc.srv.controller.PageController, methods: { stories: function (storyCnt) { var folder = gc.srv.bo.content.FolderBase.get("stories"); if(!folder) { return this.notFound(); }; var subNavBaseFolder = gc.srv.bo.content.FolderBase.getSubnavRoot(); return { folder: folder, rootFolder: gc.srv.bo.content.FolderBase.getStoriesRoot(), activeIndex: storyCnt, subnavFolder: subNavBaseFolder, pageTitle: folder.getPageTitle(), pageMetaDescription: folder.getPageDescription(), pageMetaKeywords: folder.getPageKeywords(), canonicalUrl: this.getHelper().to(folder), activeMainNav: "stellasworld"

},

json: function (id) { var content = gc.srv.bo.content.AssetBase.get(id); if(!content){ throw "Not Found: "+id; } return content; }, cacheEnabled: function () { return true; }, cacheMinutes: function () { return 60*24; }, xitiSubSite: function () { return "2"; }

} }) });

Here, you can also see three functions that have a special significance. In the methods cacheEnabled, cacheMinutes and cacheHours, the PageController base class defines caching as deactivated and how long a page should otherwise be cached. In order for caching to be activated, the special controller implementation must override these values. In addition, you can also see the xitiSubSite function here: this is used for Xiti tracking and specifies the subsite ID used to call tracking for pages that are created by this controller.

Templating
The templates are standard ISML templates, as familiar to any Demandware developer. As already described, the pdict entity from Demandware is supplemented with the variable p, with which one can access the result from the controller method. Sample code:
<isinclude template="util/page_script.isml"> <isinclude template="util/modules" > <isdecorate template="pagetypes/pt_standard"> <isscript> var bodyclass = 'stories'; var richItem = p.content; var linkSubpages = true; </isscript> <isinclude template="content/partials/sidebar" />

10

<div id="main"> <h3 class="h2">${ p.folder.getDisplayName() }</h3> <ul class="catnav"> <li><a href="${ str(to(p.folder)) }" class="back">$ {i18n('view.all')} ${ p.folder.getDisplayName() }</a></li> <li><issharelink sharingclass="rt stella_overview" sharingtext="$ { richItem.getSharingText().text }" sharingwhat="$ { richItem.getSharingText().what }" linktext="Share Page" /></li> </ul> <isinclude template="content/partials/storyhead" /> <isif condition="${richItem.hasExtraImages()}"> <div class="imagescol2"> <isloop items="${ richItem.getExtraImages() }" var="image"> <isif condition="${image.pid && image.pid.length > 0 || image.zoom}"> <div class="facon"> </isif> <img src="${ html(image.url) }" alt="" /> <isif condition="${ image.pid && image.pid.length > 0 || image.zoom}"> <ul class="floatactions js_actions"> <isif condition="${ image.pid && image.pid.length > 0}"> <li data-productid="${ image.pid.join(',') }"><a href="${ 'javascript://' }" class="info js_viewInfo">View Info</a></li> </isif> <isif condition="${image.zoom}"> <li data-zoomurl="${ image.zoom }"><a href="$ { 'javascript://' }" class="zoom js_zoom">${i18n('zoom')}</a></li> </isif> </ul> </isif> <isif condition="${image.pid && image.pid.length > 0 || image.zoom}"> </div> </isif> </isloop> </div> </isif> <div class="related"> <isif condition="${ p.relatedContent && p.relatedContent.length > 0 }"> <div class="archive"> <h3 class="h5">${i18n('see.more')}:</h3> <ul> <isloop items="${ p.relatedContent }" var="col" status="status"> <isloop items="${ col }" var="c"> <isinclude template="content/partials/box_archive.isml" /> </isloop>

11

</isloop> </ul> </div> </isif> </div> </div> <isinclude url="${ URLUtils.url('MVC-Dispatch', 'path', str(to('sharing/sharing'))) }"> </isdecorate>

Within the templates, the Demandware "decorator" approach is used. This means that a set of "page type" templates (e.g. pt_standard) are available. These contain the basic outer structure for a page (basic HTML structure with HEAD and BODY area plus navigational elements). This provides a convenient way of encapsulating and reusing repetitive HTML code. The inner template, which utilizes the page type template, must also include the decorator tag (e.g. <isdecorate template="pagetypes/pt_standard">). The decorator template itself contains the <isreplace/> tag at the position at which the template code is then included (see Demandware documentation).

Helper classes
The framework provides a great many helper methods, which can as just one example be used for correctly outputting prices, etc. To ensure that the backend controller is executed, the variable p is filled and the remaining helper classes are available, the very first action in a template must be to include the page_scripts template (<isinclude template="util/page_script.isml" />). After this, the developer then has access to all of the utility functions.

i18n / internationalization
Although internationalization (or i18n for short) covers a number of different aspects, its primary role is to provide pages in multiple languages. To ensure this can be achieved, one should never include language-specific text in a template or controller directly. Instead of this, one should use the Demandware property mechanism. As familiar from languages such as Java, this permits the definition of entities known as "property" files, which assign the actual language-specific texts to unique keys. Since Demandware loads all property files automatically, this means you always have access to the keys of all property files. Instead of using the Resource class from Demandware, you should utilize the i18n method provided by the framework. This offers a few extensions that, among other things, permit the utilization of property file content in frontend JavaScript functions. Sample property file ("account.properties"):
account.profile.complete.head=Complete Your Profile account.profile.complete.promo=What is this? account.profile.complete.buttons.next=Next

12

account.profile.complete.buttons.or=or account.profile.complete.buttons.skip=Skip this step account.profile.welcome=Welcome on {0} dear {1} ...

Within a template or controller, you can then apply your chosen text via the i18n method. In a template, for example, this may look as follows:
${i18n('account.profile.complete.promo')}

As familiar from Java, the use of placeholders within a resource definition is also possible:
${i18n('account.profile.welcome', "Stella McCartney", "Tim")}

The placeholders are numbered sequentially starting with 0 and passed to the method as additional parameters.

Folder structure
Here, we give a brief explanation of where these various files are to be found within a project:

1.2 Use case "Product detail page"


This use case provides knowledge of:

Which data is required in order to display a product? How is this data brought into Demandware? How is this data extracted back out of Demandware? How is this data displayed?

This use case covers the following items from the list of topics to be documented:

Motivation / context / problem scenario


Products are to be displayed on a page. The page is to display items of product information such as name, description, images, price, availability, etc. Gucci projects implemented to date have used a standardized procedure for the technical realization of such requirements. The solution presented here should not only follow this standardized procedure but also convey it to the reader in a way that makes it possible to implement further projects or changes to existing projects in accordance with this procedure.

Preconditions / prior knowledge


In order to be displayed, products must first exist within the system. The following two sections show how products are modeled within Demandware and which aspects of this model we utilize and how the data that describes a product is brought into the Demandware system. We assume prior knowledge of any details that are part of the Demandware documentation and which are not given special treatment in this document.

13

A third section presents a short overview of the GlobalData mechanism, which is used to transport data from the backend to the frontend. GlobalData is presented in more detail in a dedicated chapter (see 3.2).

Demandware products
There are a number of types of products. We use "master" products and the variations assigned to master products. (Rarely, also "product sets", but that topic exceeds the scope of this use case.) Products are assigned to categories. Categories are used to create the menu system on the shop pages. Products and categories are part of the Catalog. (For products and categories, see also the Demandware documentation.) There are a few conventions that should be observed in connection with Catalog data. The observation of these conventions can be assumed for a number of components that we have developed. These conventions affect the notation of IDs, the colors of products and the assignment of variations to products.

IDs: The ID of a master product is a combination of style and material. Often, a combined ID is output, consisting of style, material and color. The IDs for variants do not correspond to this schema. Colors: The color of a product is saved within the product at multiple locations. The Colour Code attribute stores the color code of the product. This is the exact same code that can also be output as a component of the ID (see above). One example would be the color code "4100" ("Notte"). The Variant RGB Colour attribute stores a color value that corresponds to the color of the variation or the product. Products with a number of color codes should also have different values for the Variant RGB Colour attribute. An example of a value for this attribute would be "#7D242A (Carmine)". The RGB Colour Code attribute stores a color value for the product, taken from a less differentiated scale. This value is used to organize products in terms of color pickers. Products with different (similar) values for the Variant RGB Colour attribute may have the same value here. The total number of possible values for this attribute should not exceed the number that can be shown simultaneously in the color pickers. An example of a value for this attribute would be "#662D36 (Red)". Variant assignment: Variants are assigned to the Master products. This takes place via one or both of the attributes Size and Colour. The values used for Colour correspond to the values in the attribute Colour Code. When assigning variants to a master product, it is important that no variant is assigned via an attribute with the value "-NONE-". Master products with variants function properly only if all variants have been assigned to the master product via specific values in the attributes.

In addition to data from the product catalog, there are other types of data that also displayed on product pages. Examples of these types of data include prices, availability or images.

The prices for the products are stored in "pricebooks". This is a standard Demandware feature. 14

The availability of products is mapped to custom objects. Demandware's "inventory" feature was not suited for implementing the requirements. These custom objects are stored using a combined ID made up of the product ID and the warehouse ID. The warehouse ID is specified for each website individually in the settings (under Global Preferences / Custom Preferences / Gucci, using the storeCode key). Access to availability is provided via the API for the product classes (see below). Images for the products are stored within a CDN, such as Limelight. The images attribute for the product is used to define which images are available at which resolutions. An example of a valid value for this attribute would be {"1200x768": ["A","B"],"90x90":["A","B"],"52x52":["A"]}. This entry is created by a script that searches across the CDN for all product images (see below). In the product classes, a path is implemented at which the images on the CDN can be located. This path is used both to generate paths to specific images and, in addition, to determine the images that are actually available on the CDN. Access to image paths is provided via the API for the product classes (see below).

Import
The data items that make up a product are imported separately.

Catalog: The Catalog is imported manually, using Demandware's import function. Prices: A Scheduled Job (ImportPricebooks) determines whether new pricebook files are present in the import folder and, if so, imports the new pricebook using Demandware's import pipeline. Stocks: A Scheduled Job (ImportStocks) determines whether new stock files are present in the import folder and, if so, imports the new stock data using a custom import script developed in-house. This script creates the custom objects mentioned above for product availability. Images: Images are initially copied to the CDN, using the naming convention that is also implemented in the CDN path in the product classes. A script is then executed to check the images (ImportImages): this searches across the CDN for all product images and creates the correct entries in the images attribute (see above).

GlobalData
Developers have a number of server-side methods available for storing arbitrary kinds of data in a JavaScript hash: these data items are later made available in the client as JavaScript objects. The ProductController adds all of the products discovered during the processing of the current request to the GlobalData hash, using the method add2GlobalData() that is provided by BaseHelper. Finally, this hash is then serialized into the HTML document using the method printGlobalData(); the method printData(...) does not collect data first, but outputs data directly.

15

In the frontend, the previously serialized data is re-read, compiled into JavaScript data items and then provided to the client by means of a GlobalData structure. The methods gc.globalData() and gc.globalData$() are used here. A detailed description of the possibilities provided by GlobalData is given in a dedicated chapter (see 2.3).

Implementation
Once the preconditions have been satisfied in particular, that products have been created in the system then a page for displaying the products can be implemented. The following sections list the steps necessary to do so.

Backend
The construction of a page with product details essentially involves the same steps as constructing any other kind of page. The construction of a simple page is described in the "Hello World" use case (see 1.1). In addition to this, the following aspects specific to product detail pages must be considered. The route must be created in a way that ensures the product name or the ID can be extracted from the URL called. The following example extracts the ID from the URL and passes this to the "Product" controller and the "view" action.
prefix+"/shop-[^/]+/.*/*[^_/]*_([\\w-]+)\\.html$", { handler: "Product-view" }

The controller must have an action view in the example shown above that accepts a unique ID from a product as a parameter. The action creates an instance of the requested product, reads data items, modifies these as required, and then ultimately passes the product or the product data to the template. The sample code below shows this kind of minimal action.
Module("gc.srv.controller", function (m) { Class("Product", { isa: gc.srv.controller.PageController, methods: { view: function ( pid ) { var product = gc.srv.bo.product.getProduct(pid); var h1 = product.getCurrent().getName(); return { product: product, h1: h1 } } } } }

The products can be created using the gc.srv.bo.product.getProduct(pid) function. For the various kinds of products (simple product, product set, master product with variants, 16

etc.), an instance of the corresponding classes is thus created in each case. Figure 1 shows the class diagram for the most important classes involved in products.

Figure 1: Class diagram The product classes encapsulate product properties and also provide a wide variety of helpful functions, such as, for example: getStockInfo(), for reading availabilities; getDimensions(), for reading and formatting the product size; or getShortURL(), for generating a short URL for links to the product. In the template, the data passed from the action can then be output. The following piece of code uses a few sample lines from a template for displaying product details to show how the objects are accessed from the action, how functions are called on these objects and how the various kinds of data can then be output.
<isscript> var product = p.product; var data = p.data; var current = product.getCurrent(); </isscript> <h2 class="h5">${ current.getShortDescription() }</h2> ${ productImageTag(current, '470x550', 'standard', null, 'pdi', true) }

17

Here, the product image is output using the helper function productImageTag(...) from the product helper. The product controller inherits from PageController. PageController ensures that key pieces of information are passed from the backend to the frontend. Pieces of product information are written to HTML using the keyword "Styles" via the GlobalData mechanism (described briefly above), thus ensuring they can be extracted from here by the frontend

Frontend
In the frontend, the data written via GlobalData is extracted again and then transformed into JavaScript objects. For the product page, this means that an object of a JavaScript product class also exists in the frontend. This object can then be used in the client-side JavaScript. Some examples of possible usage scenarios include the Selector, with which product variants can be selected, or the dynamic replacement of images. Separate documentation provides more detailed information about the Selector and JavaScript for handling product images (see 3.4 and 3.5).

1.3 Use case "Category page"


This use case provides knowledge of:

Which data is required in order to display a category with products? How is this data brought into Demandware? How is this data extracted back out of Demandware? How is this data displayed?

Motivation / context / problem scenario


A category is to be displayed on a page. The page is to display a list with products. The individual products should be displayed with items of product information such as name, description, images, price, availability, etc. Gucci projects implemented to date have used a standardized procedure for the technical realization of such requirements. The solution presented here should not only follow this standardized procedure but also convey it to the reader in a way that makes it possible to implement further projects or changes to existing projects in accordance with this procedure.

Preconditions / prior knowledge


The preconditions and prior knowledge required for this use case are exactly the same as those for the product detail page. For details, please consult the documentation on the product detail page (see 1.2).

18

Implementation
Once the preconditions have been satisfied in particular, that products have been created in the system (see 1.2) then a page for displaying the category can be implemented: The following sections list the steps necessary to do so.

Server-side implementation
The construction of a page with a product category and/or a list of products essentially involves the same steps as constructing any other kind of page. The construction of a simple page is described in the "Hello World" use case. In addition to this, the following aspects specific to category pages must be considered. The route must be created in a way that ensures the category name or the ID can be extracted from the URL called. The following example extracts the ID from the URL and passes this to the "Category" controller and the "view" action.
prefix+"/shop-[^/]+/(.*)/*$", { handler: "Category-view" }

The controller must have an action "view" in the example shown above that accepts a unique ID from a category as a parameter. The action creates an object of the requested category, reads the data items, modifies any of these as required, and then ultimately passes the category or the category data to the template. The sample code below shows this kind of minimal action.
Module("gc.srv.controller", function (m) { Class("Category", { isa: gc.srv.controller.PageController, methods: { view: function ( categoryPath ) { var category = gc.srv.bo.Category.fromCategoryPath(categoryPath); return { product: product } } } } }

The categories can be created using the gc.srv.bo.Category.fromCategoryPath(categoryPath). For categories, then, an object of the Category class is created, which provides a number of useful functions that can be utilized in the template or the controller, for example getOnlineSubCategories() or getOnlineProducts(). Figure 2 shows the class diagram for the most important classes involved in categories.

19

Figure 2: Class diagram In the template, the data passed from the action can be output. The following piece of code uses a few sample lines from a template for displaying categories to show how the objects are accessed from the action, how functions are called on these objects and how the various kinds of data can then be output.
<isscript> var category = p.category; var mainCategory = p.category.getMainCategory(); var subCategories = isSubBottomCategoryi ? category.getParent() : category; var directProducts = category.getOnlineProducts(); </isscript> <h2 class="h1">${ isSubBottomCategory ? category.getParent().getHeadline() : category.getHeadline() }</h2> <isloop items="${ subCategories.getOnlineSubCategories() }" var="cat" status="status"> ... </isloop> <isloop items="${ directProducts }" var="product" status="status">

20

... </isloop>

Appropriate functions include in particular the functions for reading the subcategories (getOnlineSubCategories) and the functions for reading the category's products (getOnlineProducts). The data for the individual products is then treated in the exact same way as for a product detail page. The use case for creating a product detail page (see 1.2) describes this in more detail. The product controller inherits from PageController. PageController ensures that key pieces of information are passed from the backend to the frontend. Pieces of product information are written to HTML using the keyword "Styles" via the GlobalData mechanism, thus ensuring they can be extracted again from here by the frontend. GlobalData is described in more detail in its own chapter (see 3.2).

Client-side implementation
In the frontend, the data written via GlobalData is extracted again and then transformed into JavaScript objects. For the category page, this means that an instance of a JavaScript category class also exists in the frontend. This object can then be used in the client-side JavaScript.

1.4 Use case "Checkout"


This use case provides knowledge of:

Sample solutions for typical modifications to the basket Transforming the basket into an order

Motivation / context / problem scenario


The checkout process is one of the core elements in the shop area. It covers the final adjustments made to elements in the basket, plus the entry of customer, shipping and payment details.

Preconditions / prior knowledge Functional requirements


For the checkout process to be executed appropriately, it is necessary for one or more products to be located in the basket.

Demandware settings
The following steps assume several Demandware settings have been made beforehand. These include:

Configuration of taxes 21

Creation of shipping types Payment types Coupon codes (optional)

Implementation
The checkout is based on an instance of the "Basket" business object. This is created immediately the customer places the first product in the shopping basket and persists until the entire checkout process has been completed. The "Basket" class provides a number of methods for accessing the included Demandware object. Depending on the use case, different strategies will be needed to enrich the basket with information. In some cases, it is enough to set the attributes by making simple calls to the respective functions of the DW object; in other cases, pipelines may in fact need to be used in order to extract or write the attributes. As the first step in developing the checkout process, we require a corresponding server-side controller. This should be stored within the Cartridge in scripts/gc/srv/controller. The exact allocation of tasks for the required actions depends on the functional setup of the checkout in question. As a rule, dedicated routes are created for the individual steps within which the basket is modified. On the one hand, a route with a GET route for displaying the form that is to submit the data; on the other, a POST route for processing the data after it has been submitted. Since sensitive data is submitted during the checkout process, the secure attribute should be set in all routes in order to force an encrypted connection. Since the basket can only be requested with the help of a pipeline, this pipeline must be called before each controller action. As examples of the two related routes for adding a shipping address, consider the following (extract from the route.ds file):
// Addressformular anzeigen prefix+"/checkout/addressform$", { method: "GET", secure: true, handler: "Checkout-show_addressform", template: "checkout/addressform.isml", preRunPipelets: ["GetBasket"] } // Addressformular verarbeiten prefix+"/checkout/addressform$", { method: "POST", secure: true, handler: "Checkout-handle_addressform", preRunPipelets: ["GetBasket"] }

In order to ensure the basket is then available in each controller action, this item should be set in the runBefore function that is executed before each action:
// Initialisierung der basket-Property override: {

22

initialize: function() { this.runBefore(function () { ... this.basket = gc.srv.bo.order.Basket.get(); ... }); this.SUPER(); } }

Gift option
In Demandware, information about the gift option is maintained in the "Shipment" object. This, in turn, is linked directly to the basket. Demandware supports multiple shipment addresses for one order. However, since this feature has not been used to date, one may, as a rule, access the default shipment. By using the Shipment object, one can then set or extract the values for the gift option (Boolean) and the gift message (String), as appropriate.

Coupon codes
To apply a coupon code to the shopping basket, the AddCouponToBasket2 pipeline is called before executing the controller action. Following this call, the values returned by the pipeline must be analyzed within the controller action. First, you will need to check whether a CouponLineItem could be found using the key; second, you must check whether it was possible to apply the coupon to the basket. If no matching coupon was found, then you have the option of using the CouponStatus in order to determine the reason.

Personal customer data (may need registration / login)


As a rule, it should be possible to assign an order to a customer. Accordingly, you will also need to enter customer-specific details such as first/last name, for example, and an email address. If you utilize Demandware user management for this purpose, then the customer details unlike address and payment details will not initially be stored in the basket object. In this case, it would also make sense to use the "runBefore" function to load the Customer in all controller actions.
// Initialisierung der customer-Property override: { initialize: function() { this.runBefore(function () { ... this.customer = gc.srv.bo.customer.Customer.get(); ... }); this.SUPER(); } }

23

As an alternative, you have the option of using custom attributes to store individual customer details directly to the basket.

Address details (shipment / billing address)


The shipment and billing address are mandatory items for the checkout. The shipment address is stored to a Shipment object. Demandware supports the option of adding multiple shipments to the basket. However, no use has been made of this functionality to date. Accordingly, you may simply utilize the defaultShipment. The Basket offers the methods getShippingAddress and getBillingAddress, each of which returns an object of an Address class. The actual class of the address object returned can depend on the region that is currently selected. As soon as the corresponding addresses are requested, these are also created at the Demandware basket, as required. To be able to display an address form, the controller action must supply an address object that can accept the values of the individual form fields. For processing following the submission of the form, the helper is used to fill the address object with the form data. Following this, the address should also then be validated using the method defined in its class (e.g. via Cybersource). If the data submitted is accepted, then the next step can be initiated. However, if an error occurs, the address form must be displayed again with the corresponding error messages. In simplified form, the two controller actions for displaying and processing an address form could be written as follows:
// Action fr das Anzeigen eines Adressformulars show_addressform: function() { return { shippingAddress: this.basket.getShippingAddress(), shippingAddressErrors: gc.srv.flash("shippingAddressErrors") }; } // Action fr die Verarbeitung eines Adressformulars handle_addressform: function() { var shippingAddress = this.basket.getShippingAddress(); this.getHelper().formUpdateObject(shippingAddress, "shippingAddress"); if (shippingaddress.isValid() && shippingaddress.isCybersourceValid()) { this.redirectTo("checkout/paymentform"); } else { gc.srv.flash("shippingAddressErrors", shippingAddress.getErrors()); this.redirectTo("checkout/addressform"); } }

24

Shipping types
In Demandware, a shipping type is defined per "Shipment". Since only a single "Shipment" has previously been considered per order, the function currently available from the Basket is changeDefaultShippingMethod. This will pass a ShippingMethod that can be requested via the function getShippingMethod. Demandware provides the ShippingMgr for the identification of all shipping types that have been entered within the Business Manager.

Payment types
For processing the payment type for the checkout, the "Basket" offers functions that are similar to those used for address processing. Accordingly, the function getPayment can be used to request a Payment object, which is created beforehand as required. This Payment object can also be filled by the helper and is then itself responsible for the validation.

Order completion
Once all pieces of information have been stored to the Basket object, the CreateOrder pipeline can then be called. This creates an Order object from the basket. Once the pipeline has completed, you can then access the order to perform further manipulations on it. For example, you can then add any additional customer details. In addition, it is important that you update the stock details for the products contained. The following code snippet displays possible actions following the creation of the order:
// Aufruf der Pipeline CreateOrder this.addContinuation(["CreateOrder"], function() { var order = gc.srv.bo.order.Order.get(); this.redirectTo("checkout/confirmation/" + order.getOrderNo()); order.initializeFromBasket(this.basket); order.applyStockAvailabilities(); order.setCustomerOrderReference(this.customer.getID()); order.prepareForExport(); ...

In this context, the export status of the order is also important. Within the code, this should be set to READY_TO_EXPORT as soon as the order is ready for exporting. Only then will the order be available for exporting as XML by the recurring Export job.

2.1 Reference for "CurrentMasterProduct and CurrentVariantProduct"


The two classes CurrentMasterProduct and CurrentVariantProduct extend the product model familiar from entities such as the Product, ProductBase, 25

MasterProduct und VariantProduct classes. Unlike elsewhere in the product model, this extension does not occur by inheritance, but in accordance with the Decorator design pattern. This mechanism is comparatively complex and is therefore presented in a dedicated chapter. If we consider the product model, then we may, as a rule, consider both CurrentMasterProduct and CurrentVariantProduct as abstractions, since these classes support the same interface as the classes thereby decorated. For most work involving the product model, it is not necessary for us to consider CurrentMasterProduct and CurrentVariantProduct directly. Here, we describe an idealized state. The actual implementation may show minor deviations from this state.

Structure and properties


Figure 3 shows the components involved, plus the structure of their mutual interrelationships.

Figure 3: Class diagram Relevant properties and relationships for the classes and objects involved are as follows: 26

The factory(...) method in ProductManager returns a CurrentMasterProduct. In most cases, factory(...) is called by the $(...) method from require.ds, with which Demandware objects are transformed into objects of classes from the product model. For each master product, only a single object of the MasterProduct class is created. If there are multiple different variants of the same master on a page, only exactly one object of the MasterProduct class is created for these. Although the master product knows about all the variants, it does not know which variant is currently selected. In CurrentMasterProduct, the MasterProduct class is extended with the getCurrent() method. The getCurrent method returns the currently-selected variant as an object of the CurrentVariantProduct class. The getVariants() method returns all variants as objects of the CurrentVariantProduct class. Although a VariantProduct knows about the master, it does not know which variant is currently selected. With the getMaster() method, a VariantProduct can return only a MasterProduct not a CurrentMasterProduct. CurrentVariantProduct extends VariantProduct and overrides getMaster() so as to return a CurrentMasterProduct; in this way, the currently-selected variant can thus be retained. In addition, CurrentVariantProduct also provides the method getCurrent(), which is an abbreviated form of getMaster().getCurrent(). CurrentMasterProduct delegates all calls to unknown methods to the internal object of the MasterProduct class, passing __noSuchMethod__. Methods whose existence is checked ("duck typing", e.g. toJSON()) must be implemented explicitly and handle delegation to the internal object themselves. CurrentVariantProduct delegates all calls to unknown methods to the internal object of the VariantProduct class, passing __noSuchMethod__. Methods whose existence is checked ("duck typing", e.g. toJSON()) must be implemented explicitly and handle delegation to the internal object themselves. The objects with which we work are therefore always objects of the CurrentVariantProduct or CurrentMasterProduct class.

Creating objects for products


Figure 4 shows a sequence diagram for the creation of a product when taking the classes CurrentVariantProduct and CurrentMasterProduct into account.

27

Figure 4: Sequence diagram The following examples clarify the interplay between the participating classes and objects.

Example 1: Creating a product with a master ID.


The following example shows the key steps taken when creating a product from the starting point of a master product ID. As a rule, products are created from the starting point of a variant ID. This will be shown in the next example. The approach taken using a master ID is slightly simpler and is therefore shown first. 1. The factory(masterId) method in ProductManager creates an object of the CurrentMasterProduct class. Referenced in here is an object of the MasterProduct class, with the properties of the selected product. 2. The variants of the master product are initially each created as objects of the VariantProduct class. Following this, they are each encapsulated in an object of the CurrentVariantProduct class. 3. The references to the variants (CurrentVariantProduct) are stored in the object of the CurrentMasterProduct class. The getVariants() method in the object of the CurrentMasterProduct class returns precisely these variants. 4. Since no variant is specified, the default variant is created and set and stored in the object of the CurrentMasterProduct class. The getCurrent() method in the object of the CurrentMasterProduct class then returns precisely this variant. 28

Example 2: Creating a product with a variant ID:


The following example shows the key steps taken when creating a product from the starting point of a product variant ID. As a rule, products are created in this exact way. Essentially, the creation of the product from the starting point of a variant ID is the same as the product creation in the preceding example. The following differences apply. 1. The factory(masterId) method in ProductManager creates an object of the CurrentMasterProduct class. Referenced in here is an object of theMasterProduct` class, with the properties of the master product that belongs to the selected variant. 2. Since a variant is specified, the object of the CurrentVariantProduct class for precisely this variant is stored as the selected variant in the object of the CurrentMasterProduct class.

2.2 Recipe "Content asset with jsonContent"


This recipe provides knowledge of:

Optimizing the structural clarity of objects (re custom attributes) (e.g. products, categories, etc.) Improving the flexibility of custom attributes for objects

Motivation / context / problem scenario


Demandware objects such as categories, products, preferences and also custom objects, must be extended with custom attributes in accordance with the requirements for the system. If these requirements change or are implemented step-by-step during the development process, the custom attributes for the objects will change. In the objects, attributes start to "pile up": over time, the objects become increasingly cluttered due to the number of attributes. Of the attributes available for objects, none are used all of the time. Reasons for unused attributes may be: * With multiple sites, attributes are used only site-specifically, but were not created in a site-specific way. * Attributes will be replaced by others, but the old ones cannot be removed immediately and are then never removed. * Attributes may exist such that all may be used only potentially and, as a rule, only a few are actually used (e.g. "Image-1", "Image-2", ..., "Image-n"). * Objects can be deployed in a variety of different contexts, which each require their own set of attributes. Only the common members of these attribute sets can always be used. Attributes required for each of the other contexts remain unused. A simple approach to ensuring objects are less cluttered is to store attributes externally in implicitly associated objects i.e. generally in content assets. As an example, we have a product for which all images can be displayed that are present in content assets, which are present in a library folder, which has the same ID as the product. This approach generates a set of new problems. 29

The solutions are not robust and are strongly dependent on observing the particular convention selected. Depending on the use case, this may involve different sets of conventions (for product images, teasers on category pages). An arbitrary number of different, competing conventions may exist. The lack of clarity in the object is not resolved but only "outsourced".

Solution model
We select the following model for resolving the problem described above: Instead of saving data items belonging to an object in a set of dedicated custom attributes, they are stored in a single attribute within a JSON structure.

Implementation
In the simplest case, the data items are stored in a JSON structure directly on the object itself, in a single attribute ("jsonContent"). For products and categories, it may be necessary to continue to store data in associated content assets. Content assets are site-specific, whereas this need not be true of products and categories. In order to store site-specific content on site-independent objects, this content is stored in content assets that are implicitly associated with the object by convention. Due to the 1-to-1 relationship between object and content asset, this at least enables us to reduce the problems with associated content mentioned above. Encapsulation, provision of an interface to product, category and content asset: The current implementation provides the functions getJsonContent() and getJsonData(). These functions return the content in their JSON structure. The content is loaded either from the object itself or from associated content assets, as required. Two other functions createJsonData() and augmentJsonData() are also called from the functions mentioned above, as required. The createJsonData() function creates an empty structure. This function can be replaced or overridden in order to create more complex structures. The default is an empty object {}. The function augmentJsonData() is used to extend the content in the JSON structure before it is returned. This function can be replaced or overridden: with references, one use would be to replace IDs in JSON with objects. The default is to return the object unchanged.

Advantages
The solution model described above has the following advantages:

Product attributes remain uncluttered. Flexibility is ensured by the ability to change or extend the JSON structure while keeping product attributes unchanged. 30

Cross-site, uniform attributes may exist in the objects while, simultaneously, a variety of site-specific content is present in the JSON structures. Simplification of data import from external sources (see e.g. AMS). Import always occurs into the exact same selection of attributes and the jsonContent attribute in particular. JSON is simpler and less cluttered than e.g. XML, etc.; JSON can be used directly within JavaScript.

Disadvantages
The above solution model has the following disadvantages/does not resolve the following problems:

In the Business Manager, it is almost impossible to change objects with content held in jsonContent (at least the content in jsonContent). When editing this content, no selection dialogs are available for images or similar aids that otherwise facilitate the editing of attributes. The problem with implicitly-associated content remains essentially unresolved. There are limitations to content search functionality. The search does not distinguish between the content itself and the keys of the JSON structure.

Outlook
Together with its implementation, the solution model presented here can achieve significant improvements to the structural clarity of custom attributes in objects. Only this approach makes the deployment of external tools for editing content (AMS) into a relatively simple process.

2.3 Recipe "Product detail page with globalData"


This recipe provides knowledge of:

Transferring JavaScript objects and data from the backend to the frontend, for deployment on the client side A mechanism to transport objects without explicitly transforming them into character strings and without a separate AJAX call Use of the mechanism on a Demandware page Use of the mechanism in the client (browser)

Motivation / context / problem scenario


As a rule, any web page consists of server-side logic and client-side logic. While the latter must be supplied with data, it does not have a direct link to the Demandware data source. For this purpose, the required data can be requested from the server via an additional AJAX call or embedded in the HTML webpage during the initial loading process. The latter is especially

31

useful for data created as a result of complex calculations. A subsequent AJAX call would repeat the execution of these calculations.

Solution model
The globalData methods offer a simple solution without explicit embedding via the data attribute for DOM elements, for example and without a repeat explicit extraction from the HTML.

Implementation
To achieve this, techniques are available in the active controller for writing to a JavaScript object. In the templates, this object is then serialized to an HTML comment and, following the delivery of the HTML document, transformed back into a globalData object within the client. In the backend, the following properties apply:

Each controller inherits the globalData object from PageController, with the properties data and events. data is itself a hash and its key/value pairs will be serialized later to JSON. events can be enriched with functions that are executed at the point in time at which serialization takes place. Since events is itself a hash, keys that are already present may be overwritten. In the template, the printGlobalData() helper method is called: this takes the values stored in globalData, serializes them and writes them to the HTML output stream to be generated. These values then later appear in an HTML comment in the HTML document delivered to the client. The base helper supplies the addGlobalData(key, value) method for writing to data. The base helper supplies the add2GlobalData( function() {...} ) method for writing to events.

In the frontend, gc is available and already contains all values in JavaScript format . Accordingly, no explicit de-serialisation must be carried out. If multiple globalData comments occur in the HTML document or if additional HTML documents are loaded subsequently via AJAX, then the data items they contain are also imported to gc, and any data pairs already present may be overwritten. Access to the values in gc is achieved by using the gc.globalData( key ) and gc.globalData$( key ) functions. The first function throws an exception if no key entry is present; the second function returns null if a key entry is missing.

2.4 Use case "Send email"


This use case provides knowledge of: 32

How the logic is implemented for sending an email How an email is sent with data from a form How a template for the email can be utilized The required format for the template to use in an HTML email Sending an HTML email with data from a form

This use case covers the following items from the list of topics to be documented:

Motivation / context / problem scenario


This use case shows how an email can be sent with data from a form. A detailed description shows where the implementation needs to be carried out at for relevant points in the source code.

Preconditions / prior knowledge


The reader should be familiar with the underlying structure of the project. Topics such as business objects, controllers, ISML templates, assets in Demandware Business Manager and pipelines should already be understood.

Overview of steps to be completed


Here, we provide a short overview of the steps that are required in order to complete sending an email: a) b) c) d) e) Configuration of MVCPipes Creation of a sendMailTo() helper method Implementation of the SendEmail helper in the controller of the relevant page Creation of an asset in Demandware Business Manager Creation of a business object, in order to be able to validate and subsequently access data from the form

The following sections discuss the individual steps in more detail.

a) Configuration of MVCPipes
Demandware permits access to the internal business objects via pipelines and pipelets. In Demandware UX Studio, the Pipeline Editor is used to create individual pipelines and pipelets. These items of information are stored in XML files. A "SendMail" pipelet is provided out of the box. This pipelet is created in the Editor in the familiar way. The following properties and the corresponding values used in the example are envisaged for email dispatch:

LocaleID - null MailBCC - continuation.data.email_bcc MailCC - continuation.data.email_cc MailFrom - continuation.data.email_from MailSubject - continuation.data.email_subject MailTemplate - continuation.data.email_template 33

MailTo - continuation.data.email_to

Accordingly, the values must be assigned as shown above.

b) Creation of a sendMailTo() helper method


See helloWorld for details of the directory structure of the project. In the directory
cartridge/scripts/global/gc/srv/helper

you will find the file "Base.ds". A sendMailTo() method is created in this class. The contents may look as follows:
sendMailTo: function(from, to, asset, bo, anothermail, bcc, fn) { this.controller.addContinuation(["SendEmail"], function () { if (fn) { return fn(); } }); if(!asset) { throw "no email-asset found to send a mail" } return { email_to : to, email_from: from, email_subject : this.renderAsset(asset.getHeadline(), bo), email_template : "mail/smtpmail.isml", email_bcc : bcc, email_body: this.renderAsset(asset.getBodyMarkup().getMarkup(), bo), anothermail : anothermail } },

When this method is called, a SendEmail continuation is initially placed onto a stack to be processed, but not yet executed. The signature of the sendMailTo() method permits a function as its last parameter. This is executed once the continuation here SendEmail has been executed. First, a check is now made to see whether an asset exists. If not, an exception is thrown. The function returns an object containing all of the data items relevant to the pipelet. This object is made available within the pipeline under "continuation.data". This is the exact point in time at which the continuation or pipeline that was placed on stack is executed, and the email is sent.

34

c) Implementation of the SendEmail helper in the controller of the relevant page


For our example, we take a form which is labeled as "Tell a Friend" on the website. As the name suggests, the idea is for a visitor to use the form to tell a friend about the web page. The first step is to create a controller. This can be found at
cartridge/scripts/gc/srv/controller/Sharing.ds

and its contents are as follows:


importPackage( dw.system ); importPackage( dw.util ); importScript("global/require.ds"); requireJoose(); require( "gc/srv/controller/PageController.ds" ); require( "gc/srv/bo/content/Asset.ds" ); require( "gc/srv/bo/sharing/TellAFriend.ds" ); Module("gc.srv.controller", function (m) { Class("Sharing", { isa: gc.srv.controller.PageController, methods: { tell_a_friend: function () { var obj = new gc.srv.bo.sharing.TellAFriend(); var helper = this.getHelper(); var asset = gc.srv.bo.content.Asset.getActiveAssetByType("email.friend"); var from = obj.senderEmail, to = obj.recipientEmail, data = { senderName: obj.senderName, message: obj.message, sharedLink: obj.url, siteUrl: helper.str(helper.to('').absolute()), customerCare: 'mailto:'+helper.i18n('global.contactcustomerCareEmail') }, bcc = (obj.sendEmailCopy === 'true') ? obj.senderEmail : undefined; return helper.sendMailTo(from, to, asset, data, undefined, bcc); }, tell_a_friend_form: function() { return {} }

} }) })

Within the routing in 35

cartridge/scripts/piplines/route.ds

there is a POST and a GET route:


prefix+"/sharing/tell_a_friend$", { method: "POST", handler: "Sharing-tell_a_friend", template: "partials/lightbox/tellafriend_success.isml" }, prefix+"/sharing/tell_a_friend$", { method: "GET", handler: "Sharing-tell_a_friend_form", template: "partials/lightbox/tellafriend.isml" }

Here, we can see that the tell_a_friend() method is called for a POST request and the tell_a_friend_form() method is called for a GET request. In the tell_a_friend method (POST), the first step taken is to create an object of the TellAFriend business object class (see 2.4.e). This object contains the form data. Furthermore, the HTML markup from the Demandware Business Manager is assigned to the asset variable (see 2.4.d). Finally, all of the relevant data is passed to the sendMailTo() method and the email is sent assuming that no errors have occurred.

d) Creation of an asset in Demandware Business Manager


email_tell_a_friend asset

In the Demandware Business Manager, a configuration asset is created from the AMS in the Site > Content area (see AMS).

36

Within this asset and under "Rich Content (JSON):", you will find the JSON object with all of the relevant entries and also the entry
"email.friend":"email_tell_a_friend"

The email.friend entry refers to another asset named email_tell_a_friend. In this asset, you will find the HTML markup that is then included in the template smtpmail.isml, as described below.

37

<p>Hello from our website.</p> <p><%= bo.senderName %> would like to share this information with you from <a href="<%= bo.siteUrl %>">site.com</a>:</p>

38

<p><%= bo.message %></p> <p><%= bo.sharedLink %></p>

This data was passed to the sendMailTo() method by the data object (see 3.). Within the asset, access is now possible via the bo.variable.
Integration of asset within template

In the SendMailTo() helper method, a template and the content from the asset is specified in the object to be returned:
email_template: "mail/smtpmail.isml" email_body: this.renderAsset(asset.getBodyMarkup().getMarkup(), bo)

The email_body is then assigned the markup that was extracted using the asset object previously specified from the Business Manager. email_template is the framework template that is being used here. Fully-specified HTML markup is integrated within this template. Naturally, inline JavaScript can also be used here in the standard way. The content of the asset must be included within the template. Here, it is important that the HTML markup is kept unchanged and is not encoded. Accordingly, the <isprint> tag is used and the encoding="off" attribute is set:
<isprint value="${ p.email_body }" encoding="off" />

In this way, the asset from the Business Manager is integrated within the template. The email is complete.

e) Creation of a business object, in order to be able to validate and subsequently access data from the form
Here, it is appropriate to create a business object that encapsulates the validation and the access to the form data. In the present example, an object of this kind with the name "TellAFriend" has been created. This can be found at:
cartridge/scripts/gc/bo/sharing/

Which can also be derived by calling


var obj = new gc.srv.bo.sharing.TellAFriend();

within the "Sharing.ds" controller. The content of the class is as follows:


importPackage( dw.customer ); importScript("global/require.ds"); requireJoose(); require("gc/srv/bo/BusinessObject.ds"); require_(); Module("gc.srv.bo.sharing", function (m) { Class("TellAFriend", { isa: gc.srv.bo.BusinessObject, has: {

39

},

senderName: { is: "rw" }, senderEmail: { is: "rw" }, recipientName: { is: "rw" }, recipientEmail: { is: "rw" }, message: { is: "rw" }, sendEmailCopy: { is: 'rw' }, url: { is: "rw" }

validations: { senderName: { simple: ["required"] }, senderEmail: { simple: ["required", "email"] }, recipientName: { simple: ["required"] }, recipientEmail: { simple: ["required", "email"] } } }); });

The class has a relatively simple structure. By using Joose and initially setting individual variables such as senderName, these variables can be accessed very simply in the controller by the use of getters and setters (is: "rw").

3.1 Use case "Frontend controller"


This use case provides knowledge of: * Which data is required in order to display a category with products? * How is this data brought into Demandware? * How is this data extracted back out of Demandware? * How is this data displayed?

40

Motivation / context / problem scenario


A specific type of frontend functionality is to be made available for all pages of a specific controller. Here, access to widgets and inits is particularly important, as is the global gc object and splitting into sub-methods.

Preconditions / prior knowledge


Prior knowledge of Joose is necessary. Furthermore, this topic also goes hand-in-hand with frontend_widgets and frontend_inits, since access to these widgets is possible from frontend controllers. When you wish to execute JavaScript functionality on HTML elements, these elements receive a js_* class (e.g. class="js_controller" or class="js_zoom"). JavaScript functionality is never executed on classes that are reserved only for CSS (e.g. class="zoom"). This standard must be observed. The following prerequisites must also be acknowledged: 1. In /cartridge/static/default/extensions/js/ysl/bo/controller, a file must be created that matches the name of the backend controller, e.g. ProductController.js. 2. Loading of the file takes place in /static/default/extensions/js/basic.js via an entry with extensions/js/ysl/cl/controller/ProductController.js. The file is included with every request, but is applied only in cases where we have ProductController active in the backend (see /controller/Product.ds).

Implementation
Once the preconditions have been satisfied, then a frontend controller for inserting frontend functionality can be implemented for a complete backend controller. The following sections list the steps necessary to do so.

Creating the basic framework


gc.namespace("ysl.cl.controller", function() { Class("ysl.cl.controller.ProductController", { isa: ysl.cl.controller.ApplicationController, methods: { ready: function () { }, nodeReady: function (node) { },

41

} }); });

The controller must inherit from ApplicationController or from another controller that inherits from ApplicationController.

The rootNode property


The rootNode property is the DOM element with the .js_controller class. It is the root element for all DOM elements for this controller and should also be utilized as such. Example:
gc.namespace("ysl.cl.controller", function() { Class("ysl.cl.controller.ProductController", { isa: ysl.cl.controller.ApplicationController, methods: { ready: function () { var self = this; var $looks = this.rootNode.find("#pddetails, .looks li"); $looks.each(function() { self.initColorAndSizeSelector($(this)); }); }, } }); });

ready() and nodeReady()


The ready function is executed once all DOM elements have been loaded and can thus be used for the initialization of the controller so as to access sub-methods, for example, and thus bind functionality to DOM elements. It is executed exactly once. A nodeReady method also exists, which can be created in the controller. This is executed both on loading the controller and also after each AJAX request. It contains a parameter that corresponds to the this.rootNode element on the initial loading of the controller and which corresponds to the AJAX response after being called by an AJAX request (e.g. to the elements loaded by AJAX).

Accessing return values from the backend controller Sub-methods


It is generally a very good idea to split up controller functionality into a set of sub-methods instead of writing all of the functionality into the ready() method. Example: 42

gc.namespace("ysl.cl.controller", function() { Class("ysl.cl.controller.ProductController", { isa: ysl.cl.controller.ApplicationController, has: { products: { is: "rw", init: function() { return {}; } } }, methods: { ready: function () { var self = this; this.rootNode.find("#pddetails, .looks li").each(function() { self.initColorAndSizeSelector($(this)); }); self.initChartSwitching(); self.initCartAddCountryWarning();

},

initColorAndSizeSelector: function($node) { // ... }, initChartSwitching: function() { // ... }, initCartAddCountryWarning: function() { // ... }

has properties Access to Weet URLs


The Weet object is global and therefore also available in controllers:
initColorAndSizeSelector: function($node) { // ... variant = self.products[id] = selector.start(Weet.get('products.' + id)); }

Access to widgets and inits


The main difference between widgets and inits is that widgets are initialized automatically when the page is accessed. They function a little like controllers, except that they are initialized for each request and not just for pages that match the corresponding backend controller.

43

Inits, on the other hand, must be instantiated directly from the controller. However, this functions as usual with the "new" parameter and the specification of the Joose namespace:
images = new ysl.cl.init.productImages.ProductImages(parameter1, parameter2, ..);

3.2 Use case "Sharable widget"


This use case provides knowledge of:

Embedding Twitter, Facebook share links Widget construction using the Sharable widget (YSL project) as an example

Motivation / context / problem scenario Preconditions / prior knowledge


Basic knowledge of Joose Basic knowledge of the "widget" concept (see recipe)

Embedding the Sharable widget


The Sharable widget is embedded by adding the "js_sharelink" class for any link as desired, e.g.:
<a ${ doNothingHref() } class="js_sharelink" data-sharingurl="${ shareUrl }" data-sharetext="${ shareText }" data-sharewhere="facebook"

> Share on Faceook! </a>

Here, the parameters are passed via "data-" attributes; these function as follows: data-sharingurl : The url to share. If no url is provided, location.href is used. data-sharingtext : The text to share. Can contain a placeholder {1} for the url. data-sharingwhere : The plattform to share this link on. Supported are "twitter" and "facebook".

Creating the basic framework


The initialization of the widget takes place using the nodeReady method:
var namespace = 'ysl.cl.widgets.sharable'; gc.widget(namespace, {}, function() { var widget = this; widget.nodeReady = function($node) { $node.delegate('.js_sharelink', 'click', function(e) {

44

widget.Sharable.shareLinkClick($(this)); }); }; });

This delegates all clicks to the instances of .js_sharelink found in the $node to a Joose class named Sharable. This then contains the actual implementation of the desired functionality.

3.3 Recipe "Sharing a product page"


This recipe provides knowledge of:

Motivation / context / problem scenario Various solutions


Contents: a) Facebook "Like" button b) Facebook/Twitter share link c) Others

a) Facebook "Like" button


To integrate the Facebook "Like" button, the plug-in from Facebook is utilized. A detailed set of plug-in documentation is available at developers.facebook.com/docs/reference/plugins/like. The iFrame version of the plug-in is deployed. The following steps must be carried out:

Generate a script tag on the above-mentioned Facebook page Embed the generated script tag in the ISML template Embed og tags in the page header

Additional embedding of scripts is not required. Note: If a user clicks the Facebook "Like" button, it is always the URL set in the og tag in the page header that is sent to Facebook not the URL entered on the button itself. If the URL or Weet hash is changed by JavaScript following the loading of the page, this does not change the URL passed to Facebook. A practical example of this is the product page. A single product page is used to present a number of product variants. For the different product variants, only the Weet hash is changed by JavaScript. Accordingly, this enables a page to be accessed directly. However, the Facebook "Like" button always sends the default URL of the product. 45

List of og tags used:


<meta property="og:title" content="value for page title" /> <meta property="og:type" content="product" /> <meta property="og:image" content="value for image url" /> <meta property="og:url" content="value for canonical, permanent URL of the page" /> <meta property="og:description" content="value for description of page content" /> <meta property="og:site_name" content="value for site name, e.g. YSL" />

b) Facebook/Twitter share link


To embed a Facebook/Twitter share link, the corresponding link must contain the following attributes or CSS classes: * CSS class: js\_sharelink * data-sharingurl: MY\_PAGE_URL (URL of the page) * data-sharetext: MY\_SHARETEXT (text to display on Facebook/Twitter) * data-sharewhere: MY\_SHARETARGET (target, Facebook/Twitter) Example:
<a href="javascript://" class="js_sharelink facebook" datasharingurl="${ shareUrl }" data-sharetext="${ shareText }" datasharewhere="facebook">Share on Facebook</a> <a href="javascript://" class="js_sharelink twitter" data-sharingurl="$ { shareUrl }" data-sharetext="${ shareText }" datasharewhere="twitter">Share on Twitter</a>

To generate the URL accessed for Facebook/Twitter, the Sharable widget is utilized (widgets/sharable.js). For nodeReady, this widget binds a click handler to all links with the js_sharelink CSS class. When the link is clicked, the shareLinkClick method of the Sharable class is called. The values of the data- attributes named above are extracted and a URL is generated. The URL is generated according to the following pattern: * http://www.facebook.com/sharer.php? u=MY\_PAGE\_URL&t=MY\_PAGE\_TITLE * http://twitter.com/home? status=SHARETEXT Note: The maximum length of a Twitter post (tweet) is 140 characters. Here, passing a Weed hash and thus the direct linking to a product variant as with the Facebook "Like" button is not possible. The Facebook share link, on the other hand, supports URLs inclusive of the Weed hash.

c) Others
In addition to the above-mentioned sharing systems for Facebook and Twitter, the user is offered the following options for sharing pages within the Gucci projects:

Share Blogparts: Code block with linked image, for embedding on other pages

46

Email to Friend: Used to send an email with a personal message and URL of the page to be shared Click to Copy: Input field with the URL of the page to be shared, for simple copying

Outlook
With all three variants, it is possible to pass the complete URL, including the Weed hash.

3.4 Use Case "Frontend JavaScript inits"


Inits serve to permit the re-use of JavaScript that, as a rule, is activated only if explicitly initialized by a controller. Unlike widgets, inits do not register a "nodeReady" function.

Motivation / context / problem scenario Conventions


Inits that should be available in all pages are stored in the global project, in the cartridge/static/default/extensions/js/gc/cl/init folder, e.g.:
cartridge/static/default/extensions/js/gc/cl/init/lightbox_manager.js

Project-specific inits, on the other hand, are stored directly in the project repository: cartridge/static/default/extensions/js/smc/cl/init, e.g.:
cartridge/static/default/extensions/js/smc/cl/init/zoomable.js

Solution model 1: Simple init (without Joose class)


In the simplest case, an init can simply make one or more methods available within its namespace, e.g.:
gc.init("smc.cl.init.log", function() { this.debug = function(str) { console.log(str); }; });

This function can now be used anywhere:


smc.cl.init.log.debug('Hello World');

Solution model 2: Complex init (with Joose class)


For more complex inits, we recommend the provisioning of one or more Joose classes, which are then initialized in one of the controllers, e.g.:
gc.init("smc.cl.init.videoPlayer", function() { Class('smc.cl.widgets.videoPlayer.Player', {

47

has: { options: null, player: null }, methods: { initialize: function(options) { this.options = options; this.player = jwplayer(options.id); if (options.autoPlay) { this.play(); } }, play: function() { this.player.play(); } } }); });

This init class can now be used anywhere:


var player = new smc.cl.init.videoPlayer.Payer({ id: 'my-element', autoPlay: true });

Implementation
All inits are defined using the gc.init(namespace, fn) method. In the process, the fn closure passed is called once and grants the init its definition in an isolated namespace.

Advantages

Generally, inits and widgets have similar advantages Unlike widgets, initialization is on a demand-only basis this may offer better performance. During initialization, it is easy to pass additional parameters, which may also be entirely independent from the underlying DOM structure.

Disadvantages

Since inits are initialized explicitly, this logic must be re-initialized in every controller that utilizes the init.

Outlook
The systematic implementation of the inits described above is handled with varying degrees of success in individual cases. In the future, more attention should be paid to the systematic utilization of the techniques described.

48

3.5 Use Case "Frontend JavaScript widgets"


Widgets serve to organize the kinds of JavaScript functionality that are activated automatically as soon as a suitable DOM structure is available. This is achieved by the widget defining a "nodeReady" function and inspecting the element thereby passed for suitable js_* classes. Unlike inits, widgets are, as a rule, activated automatically and are not regulated by a controller.

Motivation / context / problem scenario Conventions


Widgets that should be available in all pages are stored in the global project, in the cartridge/static/default/extensions/js/gc/cl/widgets folder, e.g.:
cartridge/static/default/extensions/js/gc/cl/widgets/lightbox_manager.js

Project-specific widgets, on the other hand, are stored directly in the project repository: cartridge/static/default/extensions/js/smc/cl/widgets, e.g.:
cartridge/static/default/extensions/js/smc/cl/widgets/zoomable.js

Solution model 1: Simple widgets (without Joose class)


For simple widgets, that do not contain any complex workflows or states, we recommend the exclusive use of jQuery, without Joose classes, e.g.:
gc.widget("smc.cl.widgets.zoomable", {}, function () { this.nodeReady = function($node) { $node .find('js_zoomable') .click(function() { // Dieser code wird fr jeden click auf ein js_zoomable event // abgefangen, }); }; });

Here, the nodeReady method is always called when new elements are loaded into the document, and also on the initial page load. Here, the $node parameter always references the jQuery parent element subsequently loaded.

Solution model 2: Complex widgets (with Joose class)


For more complex widgets, however, we recommend working with Joose classes. This may then look as follows:
gc.widget("smc.cl.widgets.zoomable", {}, function () { var widget = this; this.nodeReady = function($node) {

49

}; });

new widget.Zoomable($node);

Class('smc.cl.widgets.zoomable.Zoomable', { has: { $node: null }, methods: { initialize: function($node) { this.$node = $node; var self = this; this.$node.click(function() { self.zoom(); }); }, zoom: function() { this .$node .find('js_zoomimage') .show(); }

} });

The advantage here consists of the less cluttered structuring of individual behavioral elements. In addition, Joose classes also enable the user-friendly administration of additional variables and states that are not represented in the DOM.

Implementation
All widgets are defined using the gc.widget(namespace, fn) method. In the process, the fn closure passed is called once and grants the widget its definition in a namespace isolated from other widgets.

Advantages

Widgets permit the re-use of interactive JavaScript Joose classes enable clean structuring for the individual parts of the code The "nodeReady" method means widgets also function in elements subsequently loaded by AJAX The use of js_*classes as selectors ensures a distinction is made between CSS classes used for style purposes and classes that influence page behavior. Via the targeted selecting of elements in the $node element, it is not necessary to search through the entire DOM each time.

50

Disadvantages

Although the use of js_* classes ensures a certain decoupling of behavior from style, reuse can nonetheless lead to problems that are specific to DOM or the layout. On the initial page load, a large number of selectors can be involved, which may well impact the performance of the loading process.

Outlook
The systematic implementation of the widgets described above is handled with varying degrees of success in individual cases. In the future, more attention should be paid to the systematic utilization of the techniques described.

3.6 Use case "Video player init"


This use case provides knowledge of:

Embedding the video player init Init construction using the Video Player init (YSL project) as an example

Motivation / context / problem scenario Preconditions / prior knowledge


Basic knowledge of Joose Basic knowledge of the "init" concept (see recipe)

Embedding the video player init


The embedding of the Video Player init is achieved by explicit initialization in one of the controllers, e.g.:
gc.namespace("ysl.cl.controller", function() { Class("ysl.cl.controller.VideoFragmentController", { isa: ysl.cl.controller.FragmentController, methods: { ready: function () { var node = this.rootNode; new ysl.cl.init.videoPlayer.VideoPlayer(node.find('#video')); } } }) })

In the process, the constructor of the VideoPlayer class expects a jQuery object that contains the desired video element. Here, the corresponding markup must be similar or identical to the following template: 51

cartridge/templates/default/grid/fragment_video.isml

In particular, the jQuery element is expected to have a "data-params" attribute containing the following JSON:
{ "staticBaseUrl": "${staticlink("")}", "h264Src": "${content.video}", "webmSrc":"", "oggSrc":"" }

Furthermore, the init also expects the jQuery node passed to contain a video element, e.g.:
<video id="jwplayer" width="972" height="469" controls> <source src="${content.video}" type="video/mp4"/> </video>

The id="jwplayer" that can be seen here is also mandatory.

Creating the basic framework


Since the video player entity is an init, the only task to complete now is to define a Joose class in its own namespace:
gc.init("ysl.cl.init.videoPlayer", function(m) { Class("ysl.cl.init.videoPlayer.VideoPlayer", { has: { node: {is: "rw"}, }, methods: { initialize: function(node) { this.node = node; } } }); }); // Code um den player zu initialiseren.

Unlike widgets, no automated initialization occurs as a result of the nodeReady event.

4 Recipe "CSS"
This section provides knowledge of: - The use of LESS-CSS - Splitting of CSS files - CSS structure - Conversion for use in a live context

52

Motivation / context / problem scenario The use of LESS-CSS


To create the stylesheet, we use the library LESS-CSS. One reason why we use LESS-CSS in the first place is the option it offers for splitting the content of the stylesheet into individual files, which can then be combined back into a single(!) document later on. This reduces the likelihood of conflicts when working in a team. In addition, one can also author CSS more quickly, since LESS-CSS will write out in full selectors specified with indented CSS formatting. This does create the "danger" that one unintentionally creates excessively long selectors. Authors need to be aware of this. Further, we also use the following LESS-CSS options:

Namespaces and mixins (mostly for CSS3 rules) @var (for an overview of colors used, among other things) Operations (e.g. to calculate box sizes) Color functions (to create uniform highlight colors) One-line comments

Client-side usage
In the HTML page, the LESS-CSS JavaScript and the global LESS file is included in the HEAD area:
<link rel="stylesheet/less" type="text/css" href="PROJEKTNAME.less"> <script src="less.js" type="text/javascript"></script>

LESS-CSS caches the generated styles in the browser. To ensure proper emptying of the cache during development, a tried-and-tested approach is to call a refresh before the closing "body" tag:
<script type="text/javascript"> less.refresh(true); </script>

A blank page may be shown in IE7 in certain cases. If this happens, comment out the refresh instruction.

Splitting of CSS files


As already mentioned, the main reason for splitting up the CSS files is to avoid conflicts arising from simultaneous editing by multiple developers. However, it does not make sense to split the file into too many parts. This ends up being more confusing, less helpful and also involves more time when loading on development machines. If conflicts frequently occur for one particular file, then this file can still be partitioned later on. We recommend the following split strategy:

basic (Styles that are utilized on all pages: global layout, header, sidebar, footer, ...) 53

home (Styles for the home page) forms (Styles for the forms) account (Styles for the account area) checkout (Styles for the checkout process) catalog (Styles for the shop area) lightbox (Styles for the lightboxes) 1024 (Styles for the iPad/1024x layout) print (Specialized adjustments to the style for print preview, based on the screen design) fonts (Custom font definitions) vars (LESS-CSS variables and mixin definition for CSS3 rules)

In addition, there is a LESS file with the same name as the project: this loads all other LESS files using an @import instruction. Depending on the project, there may be more or fewer LESS files whatever makes sense to facilitate day-to-day work.

CSS structure
The CSS structure is oriented on the HTML structure. This is based on the SMaX grid:

Accordingly, the modules are grouped into the areas #header, #sidebar, #main, #extra and #footer.

54

Conventions
Generally, there is no "right" or "wrong" when it comes to the syntax of CSS rules. We have developed the following conventions from best practice:

Stylesheet valid to W3C CSS3 spec Full utilization of the entire CSS2.1 standard (if no support for IE6) Use of CSS3 for optimized presentation on new browsers Short selectors, but long enough so as to enable the use of generic classes (next, prev, left, button) Class names contain no(!) visual characteristics (.blueBorderTop) No utilization of the ClearFix class Properties in the order of their influence on website appearance (e.g. "float" up front, "color" at the back) Definition of the main grid in pixels, further subdivisions in percent Package decor images into sprites Always present fixes for IE7 and IE8 separately, so that later one can find and remove them easily when support for the corresponding browser is withdrawn.

Conversion for use in a live context


The conversion of LESS files into CSS via less.js is slightly slower than pure CSS but does not present problems in development mode. However, this would be too slow on the live server. Accordingly, the post-process script contains a step that generates the CSS files: 1. Convert the LESS files with "less" and "node" into a stylesheet file 2. Embed all images into the CSS using Base64/MHTML 3. CSS minification using YUI Compressor As a result, the following files are generated:

PROJECTNAME-min.css (minified CSS file for IE7 over HTTPS) PROJECTNAME-min-mhtml.css (minified CSS file with embedded images for IE8 (HTTPS/HTTP) and IE7 (HTTP)) PROJECTNAME-min-bas64.css (minified CSS file with embedded images for all other browsers over HTTP and HTTPS)
<!--[if lt IE 8]> <link rel="stylesheet" type="text/css" href="PROJEKTNAME-min.css" media="screen,projection,print" /> <![endif]--> <!--[if gte IE 8]> <link rel="stylesheet" type="text/css" href="PROJEKTNAME-minbase64.css" media="screen,projection,print" /> <![endif]--> <!--[if !IE]>--> <link rel="stylesheet" type="text/css" href="PROJEKTNAME-minbase64.css" media="screen,projection,print" /> <!--<![endif]-->

Embedding in HTML with the help of conditional comments

55

5. Use case "Make AMS content maintainable"


This use case provides knowledge of:

Creation of a template in AMS Creation of a model for storing the data Exporting the asset/model to Demandware

Motivation / context / problem scenario Creating a page in AMS


The entire AMS is written in JavaScript and uses the Qooxdoo Framework for presentation. An introduction to the framework can be found at getting started, and an overview of the available elements is accessible from Widgets. Each Demandware page type in AMS consists of three parts:

Qooxdoo window Qooxdoo model Node Exporter to Demandware

Qooxdoo window
The widget describes the user interface for editing a Demandware page type. Each window must inherit from the qx.ui.window.Window class and contain the model property. As an example, a simple window is generated with simple text entry and localized text entry. The two input boxes are labeled with the labels ( labelText, labelLocalesText ) and linked to the properties of the model ( docField, docLocaleField ).

Template
admin/source/class/ams/ba/widgets/TestPage.js: qx.Class.define('ams.ba.widgets.TestPage', { extend: qx.ui.window.Window, construct: function(brand) { this.base(arguments, 'title for Window'); this.__createLayout(); this.__setupBinding(); }, properties: { model: { init: null, event: 'changeModel', apply: '__applyModel'

56

},

members: { __form: null, __formCtrl: null, __createLayout: function() { // layoutmanager for the page var layout = new qx.ui.layout.HBox(5); this.setLayout(layout); this.__form = new qx.ui.form.Form(); form.addGroupHeader('just a headline', { row: 0, column: 0 }); form.add(new qx.ui.form.TextField(), 'labelText', null, 'docField', null); form.add(new ams.widgets.LocalesText('localestext'), 'labelLocalesText', null, 'docLocaleField', null); var saveBtn = new qx.ui.form.Button('text for save button'); saveBtn.addListener('execute', this.__saveBtnHandler, this); var closeBtn = new qx.ui.form.Button('text for close button'); closeBtn.addListener('execute', this.__closeBtnHandler, this); // add buttons to form form.addButton(saveBtn); form.addButton(closeBtn); this.__form = form; var renderer = new ams.widgets.BlockRenderer(form); this.add(renderer, { flex: 1 }); }, __setupBinding: function() { this.__formCtrl = new qx.data.controller.Form(null, this.__form, true); this.bind('model', this.__formCtrl, 'model'); }, /* EVENT HANDLER */ __saveBtnHandler: function() { qx.core.Init.getApplication().block(); this.__formctrl.updateModel(); this.getModel().save( qx.lang.Function.bind(function(data) { this.close(); qx.core.Init.getApplication().unblock(); }, this), []); }, __closeBtnHandler: function() { // close window this.close();

57

}, __applyModel: function(model) { } }); }

Qooxdoo model
The model describes the storage of data in the database and links the data to the elements on the user interface. Accordingly, the values are transferred from the database to the elements and, on saving the window, the data items where changed are updated from the elements to the model. The fields _id, _rev, _attachments and couchrestType are internal database fields and must be available on each model. All other required fields must be defined in the model and can then be linked in the window with the user interface element.

Document
admin/source/class/ams/ba/models/TestDocument.js: qx.Class.define('ams.ba.models.TestDocument', { extend: ams.models.AMSDocument, properties: { _id: { init: null }, _rev: { init: null }, _attachments: { init: null }, couchrestType: { init: null }, docField: { init: null, event: 'changeDocField' }, docLocaleField: { init: null, event: 'changeDocLocaleField', check: 'ams.models.LocaleText' } } });

Node Exporter
The Node Exporter has the task of transforming the database model into Demandware-compliant XML. An XML schema is not known and the structure of the XML is based on Demandware exports.

Export
node/views/ba/ams.ba.models.TestDocument.js: exports.merge = function(doc, config, request, cb) { var helper = require('../../helper').init(request); helper.initLanguages(config);

58

var res = []; var options = { attr: 'jsonContent', type: 'test', fields: ['docLocaleField'], subtype: '', subFields: []}; res.push('<?xml version="1.0" encoding="UTF-8"?>'); res.push(helper.contentTag('library', {'xmlns' : 'http://www.demandware.com/xml/impex/library/2006-10-31'}, helper.contentTag('content', { 'content-id': doc.contentid }, helper.contentTag('online-flag', {}, 'true') + '\n' + helper.contentTag('searchable-flag', {}, 'false') + '\n' + helper.contentTag('page-attributes', {}, '') + '\n' + helper.contentTag('custom-attributes', {}, '\n' + helper.localizeBody('custom-attribute', doc, options) ) + '\n' ) + '\n' )); cb(null, { images: helper.getImages(), data: res.join('\n') + '\n' }); } exports.delete = function(doc, config, request, cb) { this.merge(doc, config, request, cb); }

59

Das könnte Ihnen auch gefallen