Develop the Products Feature Part 3: Product Detail Page
Previous Step
Develop the Products Feature Part 2: Products Overview Page
After implementing the Products Overview page and its business logic and template, you will do the same for the Product Detail page.
Page Configuration
Open the Hippo Console in your browser: http://localhost:8080/cms/console/. Log in as user 'admin' with password 'admin'.
Browse to the node /hst:hst/hst:configurations/gogreen/hst:pages. This is where the page configurations are stored.
Add a new node called productpage of type hst:component.
Add a property hst:referencecomponent to the productpage node and enter the value hst:abstractpages/twocolumns.
/hst:hst/hst:configurations/gogreen/hst:pages + productpage [hst:component] - hst:referencecomponent = hst:abstractpages/twocolumns
You have now defined a new page configuration productpage that extends the abstract twocolumns page configuration you defined earlier. You will define what goes in the content area later.
URL Configuration
In the Hippo Console browser to the node /hst:hst/hst:configurations/gogreen/hst:sitemap/products. This is the URL you configured for the Products Overview page in the previous step.
Add a new child node called _any_.html of type hst:sitemapitem to the products node. The string " _any_" represents a wildcard comparable to ** in e.g. Unix. It allows you to define a URL pattern so you don't have to define a separate URL for each product.
Add a property hst:componentconfigurationid to the _any_.html node and enter the value hst:pages/productpage.
Add a property hst:relativecontentpath to the _any_.html node and enter the value ${parent}/${1}. This combines the relative content path mapped to the parent URL ( products) with the value matched by the wildcard ( _any_).
/hst:hst/hst:configurations/gogreen/hst:sitemap/products + _any_.html [hst:sitemapitem] - hst:componentconfigurationid = hst:pages/productpage - hst:relativecontentpath = ${parent}/${1}
You have now effectively created URLs for all product documents in the products folder, even those that don't exist yet. They are also mapped to the productpage page configuration.
At this point, if you point your browser at the Products Overview page and click on any of the products listed you will see they link to a specific URL and the page on that URL shows the common page elements. The main content area is still empty, you will get to that in a minute.
Product Detail Business Logic
Once again you can rely on one of the provided Hippo Java components to implement the business logic for the Product Detail component. The Content Component retrieves a single document from the content repository and makes it available as a rendering attribute. It also implements exception handling in case the document doesn't exist.
All you need to know is
- The Java class name is org.onehippo.cms7.essentials.components.EssentialsContentComponent.
- The document is made available through the rendering attribute document.
Product Detail Template
Open the file products-detail.html found in the web design. Locate the element <div class="col-md-9 col-sm-9">. This contains the HTML markup for the Product Detail page.
Also locate the CSS and Javascript references that are specific to this page. They are marked with the following comments:
<!-- start page specific css --> <!-- end page specific css -->
and:
<!-- start page specific js --> <!-- end page specific js -->
Create the file bootstrap-webfiles/src/main/resources/site/freemarker/gogreen/productpage-main.ftl in your project. This will be the Freemarker template that renders the contents of the product document.
Add the following line to productpage-main.ftl:
<#include "../include/imports.ftl">
You will implement the actual template in a minute.
Open the Hippo Console in your browser and browse to the node /hst:hst/hst:configurations/gogreen/hst:templates. This is where the templates are configured.
Add a new node called productpage-main of type hst:template.
Add a property hst:renderpath to the productpage-main node and enter the value webfile:/freemarker/gogreen/productpage-main.ftl.
/hst:hst/hst:configurations/gogreen/hst:templates + productpage-main [hst:template] - hst:renderpath = webfile:/freemarker/gogreen/productpage-main.ftl
In the Hippo Console browse to the node /hst:hst/hst:configurations/gogreen/hst:pages/productpage.
Add a new node called main of type hst:component.
Add a new child node called left of type hst:component to the main node.
Add a property hst:componentclassname to the left node and enter the fully qualified Java class name of the Content Component: org.onehippo.cms7.essentials.components.EssentialsContentComponent.
Add a property hst:template to the left node and enter the value productpage-main.
/hst:hst/hst:configurations/gogreen/hst:pages/productpage + main [hst:component] + left [hst:component] - hst:componentclassname = org.onehippo.cms7.essentials.components.EssentialsContentComponent - hst:template = productpage-main
Go back to the template productpage-main.ftl.
Use Freemarker syntax to make it dynamically render the contents of the product document conforming to the web design.
Some hints:
- You can look at bootstrap-webfiles/src/main/resources/site/freemarker/hstdefault/newspage-main.ftl for inspiration.
- The product document is available through the variable document.
- Product ratings are out of scope in this sprint so you can comment out the related markup ( <#-- -->).
- Remember that the product document type allows multiple images to be selected!
- Don't forget to add head contributions for the CSS and Javascript files that are loaded specifically for this page!
You will end up with something like this:
<#include "../include/imports.ftl"> <#if document??> <@hst.link var="link" hippobean=document/> <div class="blog-post"> <div class="blog-post-type"> <i class="icon-shop"> </i> </div> <div class="blog-span"> <h2>${document.title?html}</h2> <@hst.cmseditlink hippobean=document/> <#if document.images??> <div class="blog-post-featured-img img-overlay"> <div class="cycle-slideshow frame1" data-cycle-slides="> .slider-img" data-cycle-swipe="true" data-cycle-prev=".cycle-prev" data-cycle-next=".cycle-next" data-cycle-overlay-fx-out="slideUp" data-cycle-overlay-fx-in="slideDown" data-cycle-timeout="0"> <div class="fa fa-chevron-right cycle-next"></div> <div class="fa fa-chevron-left cycle-prev"></div> <div class="cycle-pager"></div> <#list document.images as item> <@hst.link var="img" hippobean=item/> <div class="slider-img"> <img src="${img}" alt="${document.title?html}"> <div class="item-img-overlay"> <a class="portfolio-zoom icon-zoom" href="${img}" data-rel="prettyPhoto[portfolio]" title="${document.title?html}"></a> </div> </div> </#list> </div> </div> </#if> <div class="blog-post-body"> <p>${document.introduction?html}</p> <@hst.html hippohtml=document.description/> </div> <div class="blog-post-details"> <div class="blog-post-details-item blog-post-details-item-left"> <img src="<@hst.webfile path="/images/icon-banknote.png"/>" class="icon"> <span class="price"><@fmt.formatNumber value="${document.price}" type="currency" /></span> </div> <#-- <div class="blog-post-details-item blog-post-details-item-left rating-info"> <span id="document-rating" data-score="3.5"></span> </div> --> </div> </div> </div> <@hst.headContribution category="htmlHead"> <link rel="stylesheet" href="<@hst.webfile path="/css/prettyPhoto.css"/>" /> </@hst.headContribution> <@hst.headContribution category="htmlHead"> <link rel="stylesheet" href="<@hst.webfile path="/css/jquery.raty.css"/>" /> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.raty.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.cycle.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.prettyPhoto.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript"> jQuery(document).ready(function($) { /* Portfolio PrettyPhoto */ $("a[data-rel^='prettyPhoto']").prettyPhoto({ animation_speed: 'fast', /* fast/slow/normal */ slideshow: 5000, /* false OR interval time in ms */ autoplay_slideshow: false, /* true/false */ opacity: 0.80 /* Value between 0 and 1 */ }); }); $( '#comments .comment-rating > div, #document-rating') .raty( { score : function() { return $(this).attr('data-score'); }, readOnly : true, half : true, starType : 'i' }); $('#rating-field') .raty( { targetText : 0, target : '#rating', targetType : 'score', targetKeep : true, starType : 'i' }); </script> </@hst.headContribution> </#if>
Open the site in your browser and browse to a product. The Product Detail page will now render all the contents of the product document conform to the web design.
Next Step
Add Featured Products to the Home Page
Full Source Code
productpage-main.ftl
<#include "../include/imports.ftl"> <#if document??> <@hst.link var="link" hippobean=document/> <div class="blog-post"> <div class="blog-post-type"> <i class="icon-shop"> </i> </div> <div class="blog-span"> <h2>${document.title?html}</h2> <@hst.cmseditlink hippobean=document/> <#if document.images??> <div class="blog-post-featured-img img-overlay"> <div class="cycle-slideshow frame1" data-cycle-slides="> .slider-img" data-cycle-swipe="true" data-cycle-prev=".cycle-prev" data-cycle-next=".cycle-next" data-cycle-overlay-fx-out="slideUp" data-cycle-overlay-fx-in="slideDown" data-cycle-timeout="0"> <div class="fa fa-chevron-right cycle-next"></div> <div class="fa fa-chevron-left cycle-prev"></div> <div class="cycle-pager"></div> <#list document.images as item> <@hst.link var="img" hippobean=item/> <div class="slider-img"> <img src="${img}" alt="${document.title?html}"> <div class="item-img-overlay"> <a class="portfolio-zoom icon-zoom" href="${img}" data-rel="prettyPhoto[portfolio]" title="${document.title?html}"></a> </div> </div> </#list> </div> </div> </#if> <div class="blog-post-body"> <p>${document.introduction?html}</p> <@hst.html hippohtml=document.description/> </div> <div class="blog-post-details"> <div class="blog-post-details-item blog-post-details-item-left"> <img src="<@hst.webfile path="/images/icon-banknote.png"/>" class="icon"> <span class="price"><@fmt.formatNumber value="${document.price}" type="currency" /></span> </div> <#-- <div class="blog-post-details-item blog-post-details-item-left rating-info"> <span id="document-rating" data-score="3.5"></span> </div> --> </div> </div> </div> <@hst.headContribution category="htmlHead"> <link rel="stylesheet" href="<@hst.webfile path="/css/prettyPhoto.css"/>" /> </@hst.headContribution> <@hst.headContribution category="htmlHead"> <link rel="stylesheet" href="<@hst.webfile path="/css/jquery.raty.css"/>" /> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.raty.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.cycle.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript" src="<@hst.webfile path="/js/jquery.prettyPhoto.js"/>"></script> </@hst.headContribution> <@hst.headContribution category="htmlBodyEnd"> <script type="text/javascript"> jQuery(document).ready(function($) { /* Portfolio PrettyPhoto */ $("a[data-rel^='prettyPhoto']").prettyPhoto({ animation_speed: 'fast', /* fast/slow/normal */ slideshow: 5000, /* false OR interval time in ms */ autoplay_slideshow: false, /* true/false */ opacity: 0.80 /* Value between 0 and 1 */ }); }); $( '#comments .comment-rating > div, #document-rating') .raty( { score : function() { return $(this).attr('data-score'); }, readOnly : true, half : true, starType : 'i' }); $('#rating-field') .raty( { targetText : 0, target : '#rating', targetType : 'score', targetKeep : true, starType : 'i' }); </script> </@hst.headContribution> </#if>