This article covers a Bloomreach Experience Manager version 12. There's an updated version available that covers our most recent release.

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 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:
  jcr:primaryType: 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 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:
  jcr:primaryType: 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 delivery tier 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 repository-data/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 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:
  jcr:primaryType: hst:template
  hst:renderpath: webfile:/freemarker/gogreen/productpage-main.ftl

In the 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:
    jcr:primaryType: hst:component
    /left:
      jcr:primaryType: 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  repository-data/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!
  • Make sure to include an edit button in the channel preview using the <@hst.manageContent> tag.

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.manageContent 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.manageContent 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>
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?