Explore Page and Component Type Controllers, JSON, and Scripts
Learning Objectives
- List three development elements you can use to create Page Designer page and component types.
- List the JSON file naming convention.
- Explain how controllers are used for Page Designer pages and components.
- Describe the difference between how B2C Commerce uses JSON and JavaScript files for page and component types.
Introduction
Vijay Lahiri, the Cloud Kicks developer, appreciates that Page Designer’s development landscape requires only a few essential elements. Controllers, JSON, and script files are the most important file types beneath the surface. There’s more on the visual elements, such as ISML, HTML, and CSS later.
Controllers run the show, while JSON and script files work the details. Page type JSON files describe the regions of the page where Vijay can place components. Component type JSON files describe the attributes that he defines for using the component type, and possibly the regions within the component type.
Each JSON file has a comparable script file that includes a render function to return the markup for the page, the results of which must be a string. Always give the JSON and script files the same name, except for their extension, and put them in the same folder.
Controllers
Controllers are server-side scripts that handle storefront requests. They manage the flow of control in your application, and create instances of models and views to process each storefront request and generate an appropriate response. For example, clicking a category menu item or entering a search term triggers a controller that renders a page.
A storefront controller calls the dw.experience.PageMgr API to render pages, components and regions. You can use the controller to create a URL to Page Designer pages. For example, Vijay can use the URL to link to a page from a Cloud Kicks marketing email. He can also use the URL to add a link to the page from the global header or footer or main navigation of the storefront.
JSON
Always place JSON files in the following directories of the custom cartridge, or a subdirectory within them.
- Page types: <your_cartridge>/cartridge/experience/pages
- Component types: <your_cartridge>/cartridge/experience/components
The JSON file name can include only alphanumeric or underscore characters. If you put the meta definition file into a subdirectory within the /experience/pages or /experience/components directories, the names of the levels in the subdirectory must also use only alphanumeric or underscore characters.
Page: storePage.json
Here’s what the storePage.json looks like.
{ "name":"Storefront Page", "description":"A Storefront Page Type", "region_definitions":[ { "id":"headerbanner", "name":"Header Banner Region", "max_components": 1, "component_type_exclusions": [ { "type_id": "commerce_assets.categorytile" }, { "type_id": "commerce_assets.category" }, { "type_id": "commerce_assets.editorialRichText" }, { "type_id": "commerce_assets.imageAndText" }, { "type_id": "commerce_assets.mainBanner" }, { "type_id": "commerce_assets.photoTile" }, { "type_id": "commerce_assets.popularCategory" }, { "type_id": "commerce_assets.productTile" }, { "type_id": "commerce_assets.shopTheLook" }, { "type_id": "commerce_layouts.carousel" }, { "type_id": "commerce_layouts.mobileGrid1r1c" }, { "type_id": "commerce_layouts.mobileGrid2r1c" }, { "type_id": "commerce_layouts.mobileGrid2r2c" }, { "type_id": "commerce_layouts.mobileGrid2r3c" }, { "type_id": "commerce_layouts.mobileGrid3r1c" }, { "type_id": "commerce_layouts.mobileGrid3r2c" }, { "type_id": "commerce_layouts.mobileGridLookBook" }, { "type_id": "commerce_layouts.popularCategories" }, { "type_id": "einstein.einsteinCarousel" }, { "type_id": "einstein.einsteinCarouselCategory" }, { "type_id": "einstein.einsteinCarouselProduct" } ] }, { "id":"main", "name":"Main Region", "component_type_exclusions": [ { "type_id": "commerce_assets.campaignBanner" } ] }, { "id":"legalnotice", "name":"Legal Notice Text", "max_components": 1, "component_type_inclusions": [ { "type_id": "commerce_assets.editorialRichText" } ] } ] }
It has three regions: headerbanner, main, and legalnotice.
- The headerbanner and legalnotice regions display only one component at a time, as specified by max_components = 1.
- The headerbanner region uses component_type exclusion to specify that several component types are not allowed in the headerbanner.
- The main region does not include a max_components value, which means that the main region can display multiple components.
- The main region uses component_type_exclusion to specify that components of type commerce_assets.editorialRichTxt are not allowed in the main region.
The max_components value restricts the number of components rendered for a region at a time, but the region can contain multiple components. Vijay can employ different components for different customers on different schedules. For example, a banner region can show a different component in the spring than in the summer.
Component: mainBanner.json
The mainBanner.json file defines three attributes that the merchandiser can set: a banner image file, text overlay, and a shop now link. In Page Designer, the attributes appear in groups. For example, the attributes defined in this file appear in the visual editor in a group called Banner Image Configuration.
{ "name": "Main Banner", "description": "Image, text overlay that links user to a category using the markup editor", "group": "commerce_assets", "attribute_definition_groups": [ { "id": "bannerImage", "name": "Banner Image", "description": "The visual display of the banner image in the background.", "attribute_definitions": [ { "id": "image", "name": "Image", "type": "image", "required": true } ] }, { "id": "textOverlay", "name": "Text Overlay", "description": "The overlay text on top of the banner image.", "attribute_definitions": [ { "id": "heading", "name": "Heading", "type": "markup", "required": true } ] }, { "id": "shopNowLink", "name": "Shop Now Link", "description": "The Shop Now link on the banner image", "attribute_definitions": [ { "id": "categoryLink", "name": "Category Link", "type": "category", "required": true } ] } ], "region_definitions": [] }
Script Files
As Vijay learned earlier, a script file must have the same name as its matching meta definition file, but with a .js extension. The script file name can include only alphanumeric or underscore characters and must be in the same folder as its corresponding meta definition file.
The script file includes a render function that returns the markup for the page. Vijay can assemble the markup using any process he wants, as long as the result is a string. Typically, the render function calls an ISML template to which it passes information about the page or component type and its content. If you use an ISML template, you must use the dw.util.Template API to render the markup from it.
User Decorators
Vijay wants to implement various strategies for using decorators with Page Designer pages. For example, he can write the script file for a page type so that he can pass in a custom decorator as a parameter when the page is rendered, or fall back to a default decorator defined in the script. He can also write a controller that renders the page so that a different decorator is used based on the value of a certain condition.
Page: storePage.js
Vijay takes a look at the storePage.js page. Here’s what it looks like.
'use strict'; /* global response */ var Template = require('dw/util/Template'); var HashMap = require('dw/util/HashMap'); var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper.js'); /** * Render logic for the storepage. * * @param {dw.experience.PageScriptContext} context The page script context object. * @param {dw.util.Map} [modelIn] Additional model values created by another cartridge. This will not be passed in by Commcerce Cloud Plattform. * * @returns {string} The markup to be displayed */ module.exports.render = function (context, modelIn) { var model = modelIn || new HashMap(); var page = context.page; model.page = page; model.content = context.content; // automatically register configured regions model.regions = PageRenderHelper.getRegionModelRegistry(page); if (PageRenderHelper.isInEditMode()) { var HookManager = require('dw/system/HookMgr'); HookManager.callHook('app.experience.editmode', 'editmode'); model.resetEditPDMode = true; } model.CurrentPageMetaData = PageRenderHelper.getPageMetaData(page); // no pagecache setting here, this is dynamically determined by the components used within the page // render the page return new Template('experience/pages/storePage').render(model).text; };
The file renders the storePage. The storePage.js renders the type defined in storePage.json. This is done by calling `new Template(...).render`. The primary part of the page type is in the template `storePage.isml` and in `storePage.css`.
Component: mainBanner.js
Then he looks at mainBanner.js, one of the components he wants to use.
'use strict'; /* global response */ var Template = require('dw/util/Template'); var HashMap = require('dw/util/HashMap'); var URLUtils = require('dw/web/URLUtils'); var ImageTransformation = require('*/cartridge/experience/utilities/ImageTransformation.js'); /** * Render logic for the storefront.MainBanner component * @param {dw.experience.ComponentScriptContext} context The Component script context object. * @param {dw.util.Map} [modelIn] Additional model values created by another cartridge. This will not be passed in by Commerce Cloud Platform. * * @returns {string} The markup to be displayed */ module.exports.render = function (context, modelIn) { var model = modelIn || new HashMap(); var content = context.content; model.heading = content.heading; model.image = ImageTransformation.getScaledImage(content.image); model.categoryLink = URLUtils.url('Search-Show', 'cgid', content.categoryLink.getID()).toString(); // instruct 24 hours relative pagecache var expires = new Date(); expires.setDate(expires.getDate() + 1); // this handles overflow automatically response.setExpires(expires); return new Template('experience/components/commerce_assets/mainBanner').render(model).text; };
The file renders the mainBanner.js Good JavaScript
Vijay uses the npm script to make sure his JavaScript adheres to the repository’s guidelines. He enters these commands before committing his code:
npm install
npm run lint