(Developer)
Define the storefront assets for Commerce Composer layout templates
We must define the storefront assets for a layout template before our custom template can be used on the storefront. The storefront components for the template define the container for the widget and the configurable slots within the container. The storefront components consist of the definition of the template container and placement of the template slots, the environment setup file.For more information about the overall creation of a layout template, see Creating Commerce Composer layout templates.
Procedure
Create the storefront assets for our custom layout template.
- In the Enterprise Explorer view within WebSphere Commerce Developer, create a directory to contain the code assets for our custom layout templates.
- Go to the Stores\WebContent\Aurora directory.
- If the directory does not exist within the Aurora directory, create the Container-vendor directory. Where vendor is the name of your company. For example, the name for our directory can be Container-MyCompany
- Create the container JSP file for our custom layout template. The container JSP file defines the template, which is an empty widget that is divided into configurable slots. The container definition is similar to the definition of a widget, but does not have any functionality or content that is defined for use on the storefront. The container identifies the configurable slots where a widget can be added and marks these slots with the wcpgl:widgetImport tag. Each slot is defined by an internal slot ID, which must be unique within the container. The container JSP file also identifies the environment setup file.
- Go to the Stores\WebContent\Aurora\Container directory.
- Copy the StaticContentPageDisplayContainer.jsp file. This file is the container JSP file for the Any page, single slot layout template that is provided by default with WebSphere Commerce.
- Go to your Stores\WebContent\Aurora\Container-vendor directory and add the copied JSP file into this directory.
- Rename the JSP file and open the file for editing. For example, we can rename the file MyCustomLayoutTemplate.jsp.By default, the contents of the container JSP file resembles the following code.
1 <%@include file="../Common/EnvironmentSetup.jspf" %> <%@taglib uri="http://commerce.ibm.com/pagelayout" prefix="wcpgl"%> 2 <div class="rowContainer" id="container_${pageDesign.layoutID}"> 3 <div class="row"> 4 <div class="col12" data-slot-id="1"><wcpgl:widgetImport slotId="1"/></div> </div> </div>Where
- 1 The environment setup file. This configuration file is used at run time. This file retrieves and prepares the JSP file path and resource bundle for the container. The configuration file is used to identifies the values of the environment configurations for the Commerce Composer framework. The file identifies the values for any prefix variables, common context file paths, page encoding setting, and the page to display as an error page. This file is called by many JSP files in a store and is shared by multiple containers. By default, the Stores/WebContent/Aurora/Common/EnvironmentSetup.jspf file is specified.
- 2 Container division element. This element is the beginning of the code that defines the overall layout template container. Include the definitions for all rows and slots within this rowContainer element. For the id attribute, the value for all layout templates is "container_${pageDesign.layoutID}". The value for the ID is generated when the layout template is used to create a layout. The value of the new layout ID is retrieved as the ${pageDesign.layoutID} portion of the value for this id attribute. For the class attribute, include "rowContainer" as part of the attribute value to help you identify the division within the corresponding CSS file. For example, we can rename the class value to be "MyCustomTemplate_tab_content rowContainer". The value for this attribute must map to an entry within a corresponding CSS file to define the rendering of the template on the storefront.
- 3 The row division element. Include more <div class="row"> elements to design your layout template to include multiple rows of slots. The value for the class attribute must match an entry within a corresponding CSS file.
- 4 The column division element. This element is used to define a slot within a row. By default the Any page, single slot layout template includes a single slot within one row. To define the slot, define the class and data-slot-id attributes.
The class attribute identifies the CSS entry that defines display information for the template slot. The values for the class attribute in the following steps specify CSS entries that are defined by default for template columns. The CSS file that includes the CSS definitions for layout templates is defined in the Stores\WebContent\Aurora\css\base.css file. By default, the CSS for a layout template uses a 12-column fluid grid system that divides a page into rows and columns. The class value "col12" entry, defines a slot to have a width that equals 100% of the page width, or as wide as all 12 columns. The data-slot-id attribute defines the slotId for the slot within your layout template. This ID displays in Management Center for the template slot wireframe to help users identify and select the slots within a template to include widgets within a layout. The value for the data-slot-id must use the following format
data-slot-id="1"><wcpgl:widgetImport slotId="1"/>Where the specified numerical value is the ID of the slot that the column division element is defining. For each additional slot that you include within a template, IBM recommends that you increment the slot ID value in sequential order. For example, the following code snippet defines the first three slots within a template. All of the slots are included within a single row.
<div class="row"> <div class="col4 acol12" data-slot-id="1"><wcpgl:widgetImport slotId="1"/></div> <div class="col4 acol12" data-slot-id="2"><wcpgl:widgetImport slotId="2"/></div> <div class="col4 acol12" data-slot-id="3"><wcpgl:widgetImport slotId="3"/></div> </div>The class value "col4 acol12" entry, maps to the col4 and acol12 entries in the base.css file. The col4 entry defines a slot width to be equal to a third of the total page width when the page is viewed in a browser with a page range greater than 600 pixels. The acol12 entry defines the slot width to be equal to 100% of the page width when the page is viewed at a page range fewer than 600 pixels. When this fewer 600 pixel breakpoint is encountered, the slots within the layout template rearrange on the storefront to stack vertically so that the slots can show at the defined width. This behavior ensures that the layout template is responsive. For more information about the CSS framework for the fluid grid system that makes up a layout template, see Responsive Web Design (RWD) framework.
- Update the container JSP file for our layout to divide you layout into more rows and columns. To divide your template into more rows, include more row division elements. The following code snippet divides the MyCustomLayoutTemplate.jsp container JSP into three rows. The first row is divided into two columns and, the second row into four columns. The third row includes a single column.
<%@include file="../Common/EnvironmentSetup.jspf" %> <%@taglib uri="http://commerce.ibm.com/pagelayout" prefix="wcpgl"%> <div class="rowContainer" id="container_${pageDesign.layoutID}"> <div class="row"> <div class="col6 acol12" data-slot-id="1"><wcpgl:widgetImport slotId="1"/></div> <div class="col6 acol12" data-slot-id="2"><wcpgl:widgetImport slotId="2"/></div> </div> <div class="row"> <div class="col3 acol12" data-slot-id="3"><wcpgl:widgetImport slotId="3"/></div> <div class="col3 acol12" data-slot-id="4"><wcpgl:widgetImport slotId="4"/></div> <div class="col3 acol12" data-slot-id="5"><wcpgl:widgetImport slotId="5"/></div> <div class="col3 acol12" data-slot-id="6"><wcpgl:widgetImport slotId="6"/></div> </div> <div class="row"> <div class="col12" data-slot-id="7"><wcpgl:widgetImport slotId="7"/></div> </div> </div>
- Update the container JSP to include any tabbed slots that we want within your layout template.
- To include tabbed slots within your template, we must include the ProductTab.js JavaScript file within the container JSP file. This file defines the interactions that a shopper can perform to switch between tabs and defines how the tabbed slots respond. To include this file, add the following code after the include statements for the environment setup file and the tag library
<script type="text/javascript" src="${jsAssetsDir}javascript/Widgets/ProductTab/ProductTab.js"></script>
- Update the definition for the row to include tabbed slots to include the logic to identify the number of tabbed slots and to retrieve the slot ID values for the slots. Within the row definition, replace the division elements for any columns to be tabbed slots with the following code. The following code defines the number of tabbed slots and the logic used to retrieve the ID values for the tabbed slots.
<wcf:useBean var="tabSlotIds" classname="java.util.ArrayList"/> <%-- Double loop to get the slots into the array list in proper order. The service does not return the child widgets in any predictable order. --%> <c:set var="tabSlotCount" value="0"/> 1 <c:forEach var="slotNumber" begin="3" end="6"> <c:set var="foundCurrentSlot" value="false"/> <c:forEach var="childWidget" items="${pageDesign.widget.childWidget}"> <c:if test="${childWidget.slot.internalSlotId == slotNumber && !foundCurrentSlot}"> <wcf:set target="${tabSlotIds}" value="${slotNumber}" /> <c:set var="foundCurrentSlot" value="true"/> <c:set var="tabSlotCount" value="${tabSlotCount+1}"/> </c:if> </c:forEach> </c:forEach>Where
- 1 Defines the beginning and end of the tabbed slots to set the number of tabbed slots. The preceding code snippet defines the row of tabbed slots to include the slots with the slot ID values 3, 4, 5, and 6.
Add the code to define the titles for the tabbed slots. Append the following code after the code for defining the number of tabbed slots.
<div class="tabButtonContainer" role="tablist"> <div class="tab_header tab_header_double"> <c:forEach var="tabSlotId" items="${tabSlotIds}" varStatus="status"> 1 <c:set var="tabSlotName" value="Title${tabSlotId}"/> 2 <c:forEach var="childWidget" items="${pageDesign.widget.childWidget}"> <c:if test="${childWidget.slot.internalSlotId == tabSlotName}"> <c:set var="tabWidgetDefIdentifier" value="${childWidget.widgetDefinitionIdentifier.uniqueID}"/> <c:set var="tabWidgetIdentifier" value="${childWidget.widgetIdentifier.uniqueID}"/> </c:if> </c:forEach> 3 <c:choose> <c:when test="${status.first}"> <c:set var="tabClass" value="tab_container active_tab" /> <c:set var="tabIndex" value="0" /> </c:when> <c:otherwise> <c:set var="tabClass" value="tab_container inactive_tab" /> <c:set var="tabIndex" value="-1" /> </c:otherwise> </c:choose> <c:set var="tabNumber" value="${status.index+1}" scope="request"/> 4 <div id="tab${status.count}" tabindex="${tabIndex}" class="tab_container ${tabClass}" aria-labelledby="contentRecommendationWidget_${tabSlotName}_${tabWidgetDefIdentifier}_${tabWidgetIdentifier}" aria-controls="tab${status.count}Widget" onfocus="ProductTabJS.focusTab('tab${status.count}');" onblur="ProductTabJS.blurTab('tab${status.count}');" role="tab" aria-setsize="${tabSlotCount}" aria-posinset="${status.count}" aria-selected="${status.first == true ? 'true' : 'false'}" onclick="ProductTabJS.selectTab('tab${status.count}');" onkeydown="ProductTabJS.selectTabWithKeyboard('${status.count}','${tabSlotCount}', event);"> 5 <wcpgl:widgetImport slotId="${tabSlotName}"/> </div> <c:remove var="tabNumber"/> </c:forEach> </div> </div>Where
- 1 Sets the title for the tab slot as the tabSlotName
- 2 The code also defines the logic for returning the widgets within a tabbed slot in the correct order.
- 3 Sets which tab to display when a shopper initially views a page that uses this layout template
- 4 Calls the JavaScript functions defined within the ProductTab.js file. These functions define how the page layout changes between tabs.
- 5 The code to import the Text Editor widget to use as the value for the tabSlotName that displays in the storefront as the title for the tabbed slot.
Add the code to define the retrieval of content for display in the tabbed slots. Append the following code after the code to define the tabbed slot titles.
<c:forEach var="tabSlotId" items="${tabSlotIds}" varStatus="status"> <c:set var="tabStyle" value=""/> <c:if test="${!status.first}"> <c:set var="tabStyle" value="style='display:none'" /> </c:if> <div role="tabpanel" class="tab left" data-slot-id="${tabSlotId}" id="tab${status.count}Widget" aria-labelledby="tab${status.count}" ${tabStyle}> <div class="content"> <wcpgl:widgetImport slotId="${tabSlotId}"/> </div> </div> <c:remove var="tabStyle"/> </c:forEach>
- Save the container JSP file. Your updated JSP file can resemble the following code, which defines the Any page, seven slots, tabs layout template.
<%@include file="../Common/EnvironmentSetup.jspf" %> <%@taglib uri="http://commerce.ibm.com/pagelayout" prefix="wcpgl"%> <script type="text/javascript" src="${jsAssetsDir}javascript/Widgets/ProductTab/ProductTab.js"></script> <div class="rowContainer" id="container_${pageDesign.layoutID}"> <div class="row"> <div class="col6 acol12" data-slot-id="1"><wcpgl:widgetImport slotId="1"/></div> <div class="col6 acol12" data-slot-id="2"><wcpgl:widgetImport slotId="2"/></div> </div> <div class="row margin-true"> <div class="col12 acol12 ccol12 right"> <wcf:useBean var="tabSlotIds" classname="java.util.ArrayList"/> <%-- Double loop to get the slots into the array list in proper order. The service does not return the child widgets in any predictable order. --%> <c:set var="tabSlotCount" value="0"/> <c:forEach var="slotNumber" begin="3" end="6"> <c:set var="foundCurrentSlot" value="false"/> <c:forEach var="childWidget" items="${pageDesign.widget.childWidget}"> <c:if test="${childWidget.slot.internalSlotId == slotNumber && !foundCurrentSlot}"> <wcf:set target="${tabSlotIds}" value="${slotNumber}" /> <c:set var="foundCurrentSlot" value="true"/> <c:set var="tabSlotCount" value="${tabSlotCount+1}"/> </c:if> </c:forEach> </c:forEach> <div class="tabButtonContainer" role="tablist"> <div class="tab_header tab_header_double"> <c:forEach var="tabSlotId" items="${tabSlotIds}" varStatus="status"> <c:set var="tabSlotName" value="Title${tabSlotId}"/> <c:forEach var="childWidget" items="${pageDesign.widget.childWidget}"> <c:if test="${childWidget.slot.internalSlotId == tabSlotName}"> <c:set var="tabWidgetDefIdentifier" value="${childWidget.widgetDefinitionIdentifier.uniqueID}"/> <c:set var="tabWidgetIdentifier" value="${childWidget.widgetIdentifier.uniqueID}"/> </c:if> </c:forEach> <c:choose> <c:when test="${status.first}"> <c:set var="tabClass" value="tab_container active_tab" /> <c:set var="tabIndex" value="0" /> </c:when> <c:otherwise> <c:set var="tabClass" value="tab_container inactive_tab" /> <c:set var="tabIndex" value="-1" /> </c:otherwise> </c:choose> <c:set var="tabNumber" value="${status.index+1}" scope="request"/> <div id="tab${status.count}" tabindex="${tabIndex}" class="tab_container ${tabClass}" aria-labelledby="contentRecommendationWidget_${tabSlotName}_${tabWidgetDefIdentifier}_${tabWidgetIdentifier}" aria-controls="tab${status.count}Widget" onfocus="ProductTabJS.focusTab('tab${status.count}');" onblur="ProductTabJS.blurTab('tab${status.count}');" role="tab" aria-setsize="${tabSlotCount}" aria-posinset="${status.count}" aria-selected="${status.first == true ? 'true' : 'false'}" onclick="ProductTabJS.selectTab('tab${status.count}');" onkeydown="ProductTabJS.selectTabWithKeyboard('${status.count}','${tabSlotCount}', event);"> <wcpgl:widgetImport slotId="${tabSlotName}"/> </div> <c:remove var="tabNumber"/> </c:forEach> </div> </div> <c:forEach var="tabSlotId" items="${tabSlotIds}" varStatus="status"> <c:set var="tabStyle" value=""/> <c:if test="${!status.first}"> <c:set var="tabStyle" value="style='display:none'" /> </c:if> <div role="tabpanel" class="tab left" data-slot-id="${tabSlotId}" id="tab${status.count}Widget" aria-labelledby="tab${status.count}" ${tabStyle}> <div class="content"> <wcpgl:widgetImport slotId="${tabSlotId}"/> </div> </div> <c:remove var="tabStyle"/> </c:forEach> </div> </div> <div class="row"> <div class="col12" data-slot-id="7"><wcpgl:widgetImport slotId="7"/></div> </div> </div>
- Optional: Define the CSS to render our custom layout template in the storefront.
- Go to the Stores\WebContent\Aurora\css directory.
- Open the base.css file for review. This file defines the CSS for all layout templates provided by WebSphere Commerce.
- Search for the comment /*** Grid system ***/ within the file. The CSS in the section defines the display for the layout template fluid grid system. By default, this CSS file defines various display options. If the base.css does not include the definition that you require, create a base-vendor.css file to include custom CSS definition. Create this file within the existing css directory. Ensure that the values for the attributes areclass unique and map to your new CSS file.
For more information about defining the CSS for a responsive layout template, see Creating responsive layout templates.
What to do next
With the definition of the storefront assets for our custom layout template complete, we must register your layout template using the Data Load utility.
Next topic: Registering a Commerce Composer layout template
Related concepts
Layout assignments for category pages
Layout assignments for catalog entry pages
Layout assignments for search terms
Pages and page creation
Commerce Composer layout architecture
Layouts, layout templates, and default layouts
Commerce Composer layout template architecture
Related tasks
Registering a Commerce Composer layout template