Developing Templates in OpenCMS 7
From ThemesWiki
| Official Page |
| Project Documentation |
| Download |
|
Contents
|
[edit] Review of the Page Layout
We'll start off by looking at the site requirements. From the design we see that we need a homepage template, a blog template, and a search template. We also see that we need to support the direct editing feature, which allows previewed content in Offline mode to be edited directly. On each mockup, we can see where the data fields from the BlogEntry content type will need to go. To make the site easier to update, we will also make these common page areas editable:
- The page header
- The page footer
- The text advertisements on the right panel
Although these content items are part of each page, they are not blog entries; so we will need to use some other content type to store them in. These items also appear on every page of the site, so they need to be shared somewhere. A good place to put the content is the index.html file of the site. This file will represent the homepage and can be easily found by other pages needing to reuse its content. But we still need a content type to store the pieces into.
Rather than designing a new content type just for a single instance of content, we can use the xmlpage content type. The xmlpage content type is designed to be used with the TemplateOne sample code that comes with OpenCms, but is also suitable for other uses. It appears as the Page with free text choice while creating a new piece of content from the Workplace Explorer. This content type is a good choice, because its design accommodates an arbitrary number of ad hoc content fields which are of CmsXmlHtmlValue type. This works out perfectly for our site as we need to have three user editable HTML fields: Header, Footer, and Ads.
Let's go ahead and create the content. Select Page with free text from the Workplace Explorer to create a new xmlpage resource:
As we intend to use this content for the homepage, it will be named index.html. The BlogHomepage.jsp template has not yet been created. So, for now, we can choose the default template OpenCms Template one. Note that the Copy body from drop down presents a list of options that can be used to create the content. Each item in the list comes from an xmlpage instance that is used as a model. As there is no model for the homepage content let's choose the 1 column, 1 row model, and then click Continue.
Since we are not using the xmlpage content type as it was designed for TemplateOne use, we can ignore most of the property fields. However, we do want to make use of the Title, which we will use as the page title. Make sure the Title field contains Deep Thoughts, and click Finish.
Now, we need to manually edit the XML content, to put in the fields we want, rather than the ones that came from the model we have selected. Remember that the xmlpage content type allows for ad hoc fields. So, we are able to add our own field names. This is unlike most other content types, which have strict requirements for field names according to their schema design. To edit the XML, click on the resource in the Workplace Explorer, and select the Edit controlcode option from the Advanced menu:
This will bring us to the text editor, where the raw XML content of the file is displayed.
<?xml version="1.0" encoding="UTF-8"?> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.opencms.org/dtd/6.0/xmlpage.xsd"> <page language="de"> <element name="text1"> <links/> <content/> </element> </page> <page language="en"> <element name="text1"> <links/> <content/> </element> </page> </pages>
The model we selected for the content has a single data field named text1, and defines it for two locales. For our use, we will replace this with our own definitions. This is easily done by replacing the text1 field with our field name. We then add the additional fields that are needed, ending up with this:
<?xml version="1.0" encoding="UTF-8"?> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.opencms.org/dtd/6.0/xmlpage.xsd"> <page language="de"> <element name="Header"> <links/> <content/> </element> <element name="Footer"> <links/> <content/> </element> <element name="Ads"> <links/> <content/> </element> </page> <page language="en"> <element name="Header"> <links/> <content/> </element> <element name="Footer"> <links/> <content/> </element> <element name="Ads"> <links/> <content/> </element> </page> </pages>
Notice the three elements that have been added: Header, Footer, and Ads. These elements have been added for the German (de) and English (en) locales after saving the file, we can use the edit page command to bring up the HTML WYSIWYG editor. Go ahead and do this, and add some content for each of the three page areas: Header, Ads, and Footer.
Now we should have an index.html page for the homepage and some blog entries for the site. This should be enough content for us to create some templates, so let's move on to that step.
[edit] Templates in OpenCms
We've gone over how to create instances of structured content in OpenCms and are now ready to start creating templates. In general, a template is written in JSP, and contains code to take a structured content type and display its data onto a page. When the JSP file runs, it needs to access the content item being viewed and may also need to access other content in the VFS. Several JSP tag libraries are available for doing this. The use of the tag libraries is straightforward and is well documented in the OpenCms documentation set. So, we will not cover that topic in this tutorial. Instead, we will take a step further by looking at how to access the template API from JSP code and custom Java beans.
[edit] Creating the Templates
We probably want to ensure that content editors are not able to access the template code, and also that the templates are easily installed on other machines. This is easily accomplished by placing the template code into our com.deepthoughts.templates module. In fact, it is necessary for templates to reside inside a module, as the Workplace Explorer dialog box used to create new content populates the template field by scanning the templates folder of all modules.
We start out with two templates, one for the homepage and the other for individual blog entries. To do this, first navigate to the module's templates folder in the Workplace Explorer and then create the new JSP resource there. The value of the title field is how the template will appear in the drop-down list. So let's use the following names:
After adding these entries, the two templates will appear in the templates drop-down whenever a new xml resource is created. Next, we have to add the template code.
[edit] The Homepage Template
We'll start by adding the code for the homepage template, which is in the BlogHomepage.jsp file. At the top of the JSP file are some declarations and import classes that are needed:
<%@page session="true" import="com.deepthoughts.templates.*"%> <%@taglib prefix="cms" uri="http://www.opencms.org/taglib/cms"%>
Notice that the com.deepthoughts.templates package is imported. Later, we'll create a Java package that contains custom code to support the template. By putting support code into Java classes, we reduce the amount of scriptlet code in the JSP file and isolate functionality to the Java code. The OpenCms tag library is also included, as we want to take advantage of its features. The OpenCms documentation should be consulted to get a more detailed understanding of the tag library. Next, we declare and instantiate a Java bean in the template:
<% // create the bean BlogJspTemplate blog=new BlogJspTemplate (pageContext, request, response); // get all the blogs in the specified folder blog.getBlogsInFolder(); // allow edit mode for offline previewing blog.editable(); %>
The BlogJspTemplate class is a custom Java bean, which we will cover in more detail, a little later on. For now, we should note that after being instantiated, a call is made to the getBlogsInFolder method. This method exists in a custom Java class and is responsible for gathering a list of blogs in the current folder. In a little while, we will go over the Java class in detail.
Another thing to note here is the call to the editable method, which provides support for the direct edit feature. Normally, we would just use the <cms:editable/> tag for this. However, the tag library generates HTML code for the direct edit buttons, which disturbs the layout of the page. Fortunately, OpenCms once again provides an easy way to change this behavior by allowing us to specify our own HTML for the direct edit buttons. This is done in the custom Java code and is also covered later on.
After the initial code comes the HTML for the head content:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1- transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><cms:property name="Title" /></title> <link rel="stylesheet" href="<cms:link>/styles/global.css</cms:link>" type="text/css" /> <script language="JavaScript" src="<cms:link>/script/today.js</cms:link>"> </script> </head>
Here the <cms:property> tag from the tag library is used to retrieve the title property value from the content.
The <cms:link> tag is used around all linked resources. This ensures that the application context and site context are accounted for correctly. It also ensures that the links get resolved correctly, if later the page is statically exported.
Following the head section is the HTML body:
<body bgcolor="#C0DFFD"> <%-- Include the user editable header content --%> <cms:include file="/index.html" element="Header" editable="true" /> <%-- include the standard header code --%> <cms:include file="../elements/common.jsp" element="header" /> <%-- Table, 7 cells across --%> <table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
It begins by using the <cms:include> tag to include the Header data field from the index.html content file we have created earlier. This tag is very flexible and is used to dynamically include other files from the VFS. The OpenCms tag library documentation provides full details. One nice aspect of this tag is that it provides for inclusion of only a section (or element) of a file rather than the entire file. This can be used for structured XML content files, and even for JSP template files. In the next line of code, we take advantage of this to include the header element of the common.jsp file. The common.jsp file is shared between templates and more will be covered on this later on.
[edit] The Blog Content Loop
After this, comes the section used to display the blog content. It uses a loop to iterate through all the blogs it finds in the current folder:
<!-- Content Row -->
<tr align="left" valign="top">
<%-- Left Rail --%>
<td class="leftRail" align="left"></td>
<%-- Para spacer left side --%>
<td class="spacer" align="left"></td>
<%-- Content --%>
<td colspan="2" valign="top" align="left"><img src="<cms:link>/images/spacer.gif</cms:link>" alt="" eight="1"
border="0" /> <br />
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<%-- Load all blogs in the most recent folder and display them --%>
<% while (blog.hasMoreBlogs()) { %>
<tr>
<td width="100%" class="blogHeadline"><%=blog.getField("Title")%> <cms:editable mode="manual" />
</td>
</tr>
<tr>
<td class="blogTimestamp"><%=BlogJspTemplate.formatDate (blog.getField("Date"), "dd.MM.yyyy")%>
</td>
</tr>
<tr>
<td class="bodyText" valign="top">
<div class="photo">
<%=blog.getBlogImage(120)%>
<%=blog.trim(blog.getField("BlogText"), 700)%>
<%=blog.linkToCurrentItem("more...")%>
</div>
</td>
</tr>
<tr>
<td class="blogCategories"><br />
<span class="label">TOPICS:
</span>
<%=blog.showTopics()%>
</td>
</tr>
<tr>
<td class="postDivider">
</td>
</tr>
<% } %>
</table>
<br />
</td>
<td width="10">
<img src="<cms:link>/images/spacer.gif</cms:link>" alt="" width="10" height="1" border="0" /></td>
Recall that at the beginning of the template, the getBlogsInFolder method was used to get a list of blog entries. This list is now iterated over to display each item. Within the loop, the getField method is used for displaying each blog title. This method improves upon the <cms:element> tag by returning a null string if the field has no value entered. This is also supported in the tag library by using the <cms:contentcheck> tag.
Next to the code displaying the title, there is a call to the <cms:editable> tag. This tag generates HTML code for a direct edit button to appear during Offline previewing. Recall that we want to override the default behavior of this tag. To do this, we just need to ensure that our override takes place using the mode="manual" attribute on the tag.
Next, we get the date field from the content and format it using the formateDate bean method. This method takes the date field, which is stored as a long integer, and uses a format string to return a human readable date.
Recall from the data design that each blog had an optional image associated with it. To make it easier to display the image, we've added a getBlogImage method to our bean. The method determines if an image exists, and also takes care of sizing it properly. Following the image is the text of the blog:
<%=blog.trim(blog.getField("BlogText"), 700)%>
<%=blog.linkToCurrentItem("more...")%>
The trim method trims text to an arbitrary size on the nearest word boundary. We use it to provide a teaser type display of the blog entries. A link to the full blog entry is generated with the linkToCurrentItem method.
The content display loop ends with a call to the showTopics method. This method returns a list of topics that have been assigned to each blog entry. The next section of the template deals with the displays on the righthand side and bottom of the page.
[edit] The Sidebar and Footer
The code sidebar and footer page areas are common to the homepage and the blog pages. Code for these common areas has been placed into a single JSP file named common.jsp. This JSP file is placed into the elements folder of the module, and included in the template where needed. The sidebar section of the template does this:
<%-- Sidebar --%> <td valign="top"> <div id="sidebar"> <ul> <%-- Search Form --%> <cms:include file="../elements/common.jsp" element="search_form" /> <%-- Ads --%> <cms:include file="../elements/common.jsp" element="ads" /> <%-- Archives --%> <cms:include file="../elements/common.jsp" element="archives" /> <%-- RSS Client --%> <cms:include file="../elements/common.jsp" element="rss_client" /> <%-- RSS Feed --%> <cms:include file="../elements/common.jsp" element="rss_feed" /> </ul> </div> </td> <td> </td> </tr> <%-- Footer --%> <tr bgcolor="#CCFF99"> <td> </td> <td colspan="5" class="pageFooter"> <cms:include file="../elements/common.jsp" element="footer" /> </td> <td> </td> </tr> </table> </body> </html>
We finish up the template by including another common code section for the footer area. This completes the code for the homepage template. Next, we'll take a look at the common code elements.
[edit] Common Code Elements
Anytime HTML or JSP code logic is repeated, it is a good idea to factor it into common code fragments which can be reused. This makes it easy to manage, and allows changes to be made in one place. We've identified common code in the homepage and blog pages, and placed it into a common code element in our module.
The common JSP file we've created for the blog site contains these template sections:
-
Header: Contains the header HTML. -
SearchForm: Contains the search form. -
Advertisements: Contains the advertisement inclusions. -
Archives: Displays the blog archives. -
RSSClient: Contains logic for the RSS client feeds. -
RSSFeed: Contains links to the RSS feeds. -
Footer: Contains the footer HTML.
Each includable JSP section is demarcated using the <cms:template> tag. The template code starts out with some declarations:
<%-- This template contains common JSP code. The element is divided into sections that can individually be included into a parent JSP via the OpenCms include' API. --%> <%@ page pageEncoding="UTF-8" %> <%-- include necessary imports --%> <%@ page import="java.util.*, org.opencms.file.*, com.deepthoughts.templates.*" %> <%-- include necessary taglibs --%> <%@ taglib prefix="cms" uri="http://www.opencms.org/taglib/cms" %> <% // create the bean BlogJspTemplate blog = new BlogJspTemplate(pageContext, request, response); %>
As in the homepage template, some java imports and the tag library are included. After the declaration, an instance of the Java bean is created, as it will be needed later, in the code sections. The code sections appear next.
[edit] Header Code
The first section of the common template declares an element named header, which is declared using the <cms:template> tag:
<%-- Header --%> <cms:template element="header"> <%-- Header Divider --%> <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0"> <tbody> <%-- Date Bar --%> <tr bgcolor="#ccff99"> <td id="dateformat" colspan="7" height="25"> <script language="JavaScript" type="text/javascript">document.write(TODAY); </script> <%= blog.getCurrentFolderName() %> </td> </tr> <%-- Thin spacer --%> <tr> <td bgcolor="#003366" colspan="7"><img height="1" alt="spacer.gif" width="1" border="0" src="<cms:link>/images /spacer.gif</cms:link>" /> </td> </tr> </tbody> </table> </cms:template>
This section is used by all page templates, and contains common HTML in the header. First, there is some JavaScript code used to write the current date. The JavaScript is assumed to be included in the calling template.
After the JavaScript code, there is a call to the getCurrentFolderName method of the Java bean. This method returns nothing if the current folder is the most recent blog folder. Otherwise, it returns the folder name. It is used to indicate that an archive folder is being browsed against a current list of blogs. When a current blog list is being browsed, the header area appears like this:
But when an archive has been selected for browsing, the header area is modified to contain the archive name to provide additional context feedback:
As we want this feature on all the pages, it is encapsulated into the header logic.
[edit] Search Form
Next in the template, is a section for the search form code:
<%-- search form --%> <cms:template element="search_form"> <li class="widget widget_search" id="search"> <form action="<cms:link>/system/modules/ com.deepthoughts.templates/elements/Search.jsp </cms:link>" method="post" id="searchform"> <div> <input type="text" size="15" id="s" name="s" /> <br /> <input type="submit" value="Search" /> </div> </form> </li> </cms:template>
The action of the form is targeted towards the Search.jsp file which is located in the elements folder of the module. We will cover this in a later tutorial. So, searching will not work yet, but the required framework will be in place.
[edit] Advertisements
Recall that we created an element in the index.html named Ads. This element is used to contain user-editable HTML for advertisements that can be put onto the page. The ads section contains the code responsible for inserting the element onto the page:
<%-- Advertisements --%> <cms:template element="ads"> <li class="widget widget_text" id="text-4"> <h2 class="widgettitle"></h2> <div class="textwidget"> <cms:include file="/index.html" element="Ads" editable="true" /> </div> </li> </cms:template>
The feature is easily supported by using the <cms:include> tag and referencing the element name Ads. Content editors can then edit the content and the template takes care of inserting it onto the page.
[edit] Blog Archives
Archives of past blogs are accessed from the homepage and the blog pages on the righthand panel. The panel contains a list sorted in descending order with a count of entries for each month:
The archives code section takes care of generating the HTML for this:
<%-- Blog Archives --%>
<cms:template element="archives">
<li class="widget widget_archives" id="archives">
<h2 class="widgettitle">Archives</h2>
<ul>
<%
// get the list of blog folders, in descending date order
List lstBlogs = blog.getBlogFolders();
Iterator i = lstBlogs.iterator();
// for each folder...
while (i.hasNext()) {
CmsResource res = (CmsResource) i.next();
// count blogs in it
String strCount = Integer.toString(blog.getBlogCount(res));
// get the title
String strTitle = blog.getCmsObject().readPropertyObject(res, CmsPropertyDefinition.PROPERTY_TITLE,
false).getValue();
// build a uri parameter to it
String strLink = blog.getCmsObject().getSitePath(res);
// link to the home page with a folder switch
String strI = blog.link("/index.html").concat("?uri=").concat(strLink);
%>
<li><a title="<%=strTitle%>" href="<%=strI%>"><%=strTitle%></a> (<%=strCount%>)
</li>
<% } %>
</ul>
</li>
</cms:template>
The code starts by retrieving a list of blog folders by calling the getBlogFolders method on the Java bean. This returns blog folders in the descending order of date. The code then iterates over the list, using the getBlogCount method to obtain the number of blog entries in each folder. The Title property on each folder is used for the displayed name.
Next, a link is constructed for each archive folder. Archives are displayed using the same template as the homepage. To display the blogs in an archive folder, the code just needs to know to retrieve blogs from a past archive folder rather than the one with the most recent date. This is accomplished by using a parameter containing the archive folder to be viewed. Whenever the homepage template code retrieves a list of blogs, it looks for this parameter. Recall that the Java bean on the homepage template makes a call to the getBlogsInFolder method. This is the method that takes the parameter into account. So in this code, we need to ensure that the parameter gets created.
The link for the homepage is /index.html. We just have to add the additional parameter to this URL. The resulting link will look like this:
opencms/opencms/index.html?uri=/Blogs/2007-06/
When generating this URL, the link method is used with the path to the index file. This ensures that the web application and servlet context are accounted for in the URL.
[edit] RSS Client and RSS Feeds
These sections will be developed in the later tutorials. But for now, stub code is used in their place. In the RSS client section, a dummy link placeholder is used:
<%-- RSS Client --%> <cms:template element="rss_client"> <li class="widget widget_archives" id="archives"> <h2 class="widgettitle">Feeds</h2> <ul> <li><a title="" href="http://www.somelink.com">Feed goes here</a> (16:50)</li> </ul> </li> </cms:template> <%-- RSS Feed --%> <cms:template element="rss_feed"> <li class="widget widget_text" id="text-6"> <h2 class="widgettitle">Subscribe</h2> <div class="textwidget"> <br/> <ul> <a type="application/rss+xml" rel="alternate" title="Subscribe to my feed" href="/RSS?type=RSS0.91"> <img style="border: 0pt none ;" alt="RSS" src="<cms:link>/images/xml.gif </cms:link>"> RSS 0.91 Feed </a> </ul> <ul> <a type="application/rss+xml" rel="alternate" title="Subscribe to my feed" href="/RSS?type=RSS1.0"> <img style="border: 0pt none ;" alt="RSS" src="<cms:link>/images/xml.gif </cms:link>"> RSS 1.0 Feed </a> </ul> <ul> <a type="application/rss+xml" rel="alternate" title="Subscribe to my feed" href="/RSS?type=RSS2.0"> <img style="border: 0pt none ;" alt="RSS" src="<cms:link>/images/xml.gif</cms:link>"> RSS 2.0 Feed </a> </ul> <ul> <a type="application/rss+xml" rel="alternate" title="Subscribe to my feed" href="/RSS?type=atom"> <img style="border: 0pt none ;" alt="RSS" src="<cms:link>/images/xml.gif</cms:link>"> ATOM 0.3 Feed </a> </ul> </div> </li> </cms:template>
Likewise, for the RSS feed area, we use stub links. Anticipating that the RSS feeds will support multiple formats, URL parameters have been used to specify a feed type. Later, we will discuss developing RSS feeds for the site content.
[edit] Footer Section
To complete the common template, there is an element used to include the footer element from the index.html content:
<%-- Footer --%> <cms:template element="footer"> <%-- Include the footer content --%> <cms:include file="/index.html" element="Footer" editable="true" /> </cms:template>
That completes the common element. We can see how placing the elements into the common.jsp file makes it easier to make central changes that affect all the pages of the site.
[edit] The Supporting Java Bean Class
The bulk of the work done in the template is accomplished by a Java bean. The BlogJspTemplate Java bean subclasses the OpenCms provided CmsJspXmlContentBean class. The OpenCms class provides a Java analogue to the features provided by the XML content tag library. This bean has been subclassed in order to provide methods specific to our template, and also to override some of the base bean behavior. The usage pattern for the bean is similar to that of the tag library and its base class:
- Load content from some VFS location into a collection.
- Iterate the content items in the collection.
- Display each content item with the ability to edit it using direct edit.
The beginning of the custom class file looks like this:
public class BlogJspTemplate extends CmsJspXmlContentBean {
// useful constant
protected final static String NULLSTR = "";
// path to our blog respository, blogs are arranged by month and // year
protected final static String CONTENT_PATH = "/Blogs/";
// fieldnames in our blogentry content type
protected final static String FIELD_IMAGE = "Image";
protected final static String FIELD_ALIGNMENT = "Alignment";
protected final static String FIELD_TOPIC = "Category";
// the resource type id for our BlogEntry resource
protected final static int BLOG_RESOURCE_TYPEID = 3000;
// container for the currently loaded list of blogs
I_CmsXmlContentContainer m_iContent;
/**
* Empty constructor, required for every JavaBean.
*/
public BlogJspTemplate()
{
super();
}
/**
* Constructor, with parameters.
*
* @param context the JSP page context object
* @param req the JSP request
* @param res the JSP response
*/
public BlogJspTemplate(PageContext context, HttpServletRequest req, HttpServletResponse res) {
super(context, req, res);
}
The file starts out declaring some useful constants that will be used later on, including the location of the blog repository and the resource type id for the BlogEntry data type. Constructors are provided to use the class either as a bean or with parameters. The next method overrides the direct editing feature.
As mentioned earlier, OpenCms provides direct editing support for in-site content editing. The <cms:editable> tag is used for this and utilizes what is called a direct edit provider. This is a Java class responsible for generating the HTML and JavaScript code required for the edit button to appear in the Offline preview mode. For our page templates however, the default direct edit provider causes alignment problems. OpenCms allows for custom direct edit providers to be registered and created, and also provides alternate direct edit providers that may be used. Fortunately, one of the available alternatives does not cause the problem. The alternate class is specified in our override of the editable method:
public void editable() throws JspException
{
CmsJspTagEditable.editableTagAction(getJspContext(),
"org.opencms.workplace.editors.directedit.CmsDirectEditTextButtonProvider", CmsDirectEditMode.AUTO, null);
}
Here, the method simply uses the tag library implementation, passing the name of the alternate direct edit provider class. The alternate provider generates HTML that works better in our template code.
Next in the bean, there is a method used to gather all the blogs together in a folder:
public I_CmsXmlContentContainer getBlogsInFolder() {
try {
String strPath = null;
String URI = getRequest().getParameter("uri");
if (null == URI) {
strPath = getMostRecentBlogFolder();
}
else {
strPath = URI;
}
// we retrieve everything in the folder with
// the resource id of the BlogEntry type
m_iContent = contentloadManualEdit(
"allInFolderDateReleasedDesc",
strPath.concat("|").
concat(Integer.toString(BLOG_RESOURCE_TYPEID)));
} catch (JspException e) {
e.printStackTrace();
}
return m_iContent;
}
Aside from returning the list of current blogs, the method also supports the requirement for browsing past blog archives. As discussed in the last tutorial, to make this easier to implement, the content has been arranged date wise into folders. This is leveraged by the method. When the getBlogsInFolder method is called, it looks for a parameter named uri. If the parameter is present, then a list of blogs from the folder that uri refers to, is used. Otherwise, the list is obtained from the current blog folder, which is the folder having the most recent date. Recall that in the common template code, the uri parameter is generated in the archives section.
The method makes use of two utility methods. The first one determines the most current blog folder:
public String getMostRecentBlogFolder() {
String strLatestPath = CONTENT_PATH;
// get the list of blog folders
List collectorResult = getBlogFolders();
if (null != collectorResult &&
collectorResult.iterator().hasNext()) {
// the first one in the list is the most recent
CmsResource res = (CmsResource)
collectorResult.iterator().next();
strLatestPath = CONTENT_PATH + res.getName() + "/";
}
return strLatestPath;
}
The utility method in turn relies on another method to get a list of all the blog folders in the descending date order. It takes advantage of the list order to return only the first item, which is always the most recent and hence, the current folder.
The second utility method is a JSP analogue to the <cms:contentload> tag:
protected I_CmsXmlContentContainer contentloadManualEdit(
String collectorName,
String collectorParam)
throws JspException {
return new CmsJspTagContentLoad(null,
getJspContext(), collectorName, collectorParam,
null, null, getRequestContext().getLocale(),
CmsDirectEditMode.MANUAL);
}
We override this method in order to set the editing mode to MANUAL. This is necessary to ensure that the default direct edit buttons are overridden with our own. The method utilizes the tag constructor directly. This utility method is also used by the getBlog method:
public boolean getBlog(String URI){
try {
m_iContent = contentloadManualEdit("singleFile", URI);
return true;
} catch (JspException e) {
e.printStackTrace();
}
return false;
}
This provides support for the BlogEntry.jsp template to retrieve the blog that was clicked on from the home page.
Moving on, the getBlogFolders method provides a list of blog archive folders:
public List getBlogFolders() {
// Use the collector to obtain a list of resources
String collectorName = "allInFolderDateReleasedDesc";
try {
// now collect the resources
I_CmsResourceCollector collector =
OpenCms.getResourceManager().
getContentCollector(collectorName);
if (null == collector) {
throw new CmsException(Messages.get().container(
Messages.ERR_COLLECTOR_NOT_FOUND_1, collectorName));
}
// get list of folders underneath the /Blogs' folder
List collectorResult = collector.getResults(
getCmsObject(), collectorName,
CONTENT_PATH + "|" + Integer.toString(
CmsResourceTypeFolder.RESOURCE_TYPE_ID));
return collectorResult;
} catch (CmsException e) {
e.printStackTrace();
}
return null;
}
To build the folder list, a resource collector is used. This is an OpenCms class that returns a list of resources, given a filter pattern. Use of the class is well documented, and more details can be found in the tag library online documentation. To get the list of blog folders, the parameters specify that only folder resources located in the /Blogs path should be returned. The parameters also specify that the returned list is to be ordered by date.
On both the homepage and the blog pages, optional support is available for displaying an image. To simplify template coding , the Java class provides the getBlogImage method for retrieving the image:
public String getBlogImage(int Height) {
if (fieldExists(FIELD_IMAGE)) {
CmsImageScaler scaler = new CmsImageScaler();
scaler.setHeight(Height);
HashMap<String, String> attributes = new HashMap<String, String>();
String strAlign = getField(FIELD_ALIGNMENT);
attributes.put("align", strAlign);
attributes.put("height", Integer.toString(Height));
return img(getField(FIELD_IMAGE), scaler, attributes);
}
Return NULLSTR;
}
As blog images are optional, it first checks to see if the image exists in the data field. If an image is available, the CmsImageScaler class is utilized to size it properly. Only the height parameter is used, as the scaler will automatically compute the width and maintains the aspect ratio.
Finally in the Java class, there is a method that returns the list of topics assigned to each blog:
public String showTopics() {
// StringBuffer to hold the response
StringBuffer sbCategories = new StringBuffer(NULLSTR);
// get list of categories
I_CmsXmlContentContainer iCategories = contentloop(m_iContent, FIELD_TOPIC);
boolean bFirst = true;
try {
while (iCategories.hasMoreContent()) {
if (false == bFirst) {
// after first on we can append a comma
sbCategories.append(", ");
} else {
// no longer the first one
bFirst = false;
}
// add the category
sbCategories.append(this.contentshow(iCategories));
}
} catch (JspException e) {
e.printStackTrace();
}
return sbCategories.toString();
}
The method calls contentloop to get a list of field values from the content item. The contentloop method is implemented in the base class and is documented in the tag library. The method loops over the content values and returns a String for display.
[edit] The Blog Template
The second code template we need to write is for the BlogEntry.jsp file. This template supports the display of individual blog entries. The template starts out with declarations and imports, and looks a lot like the home page template:
<%@page session="true" import="com.deepthoughts.templates.*"%> <%@taglib prefix="cms" uri="http://www.opencms.org/taglib/cms"%> <% // create the bean BlogJspTemplate blog=new BlogJspTemplate( pageContext, request, response); // get the blog entry that was clicked on blog.getBlog(blog.getCmsObject(). getRequestContext().getUri()); // allow edit mode for offline previewing blog.editable(); t%>
The difference is that instead of getting a list of blogs, it gets just one blog by calling getBlog. As we have already seen in the Java code, the getBlog method uses a collector to locate a single file with a given URI. The remainder of the template code is similar to the homepage template, including the calls to the common code elements.
That completes the code for the two content templates. We can now use them to view the blog content. To do this, we will need to first understand how templates are applied and loaded for content items. The next section discusses how this is done.
[edit] The Content and Template Loading Process
OpenCms uses Java classes called resource loaders for handling the delivery of resources from the VFS. When a request is made for a resource, the OpenCms servlet first locates it in the VFS. After ensuring that the user has access permissions, the servlet determines the type of the requested resource. It then locates a suitable resource loader for that type and passes control to the loader. The resource loader is responsible for handling the details of how a resource is rendered and returned. The following diagram illustrates this process:
Resource loaders are associated with resource types through unique resource ids, and are registered with OpenCms in the opencms-vfs.xml configuration file. Inside the configuration file, we can find the following resource loaders:
-
CmsDumpLoader: Loads plain text files. -
CmsImageLoader: Loads image resources. -
CmsPointerLoader: Loads pointers to other content or links. -
CmsJspLoader: Loads JSP files. -
CmsXmlPageLoader: Loads the xmlpage resource type. -
CmsXmlContentLoader: Loads XML structured resource types.
The last two loaders are responsible for loading XML content types. When they are called, rather than rendering the requested content themselves, they utilize a template to do this. A property value on the original resource identifies the location of the template. When control is passed to the template it uses the original URI to load and render the originating resource, as illustrated here:
Although both XML loaders use templates to display their content, each loader uses a different property value. The CmsXmlPageLoader uses the template property, while the CmsXmlContentLoader uses the template-elements property. As for all properties, the value may be set on a parent folder instead of each resource. This allows a default template property to be set for a group of content, and to be individually overridden, if so desired.
The only thing left to do is to set the appropriate property so the loader will invoke our template. For the index.html file, we need to set the template value to point to the full path of the template. As the template is in the module, the value should be set to:
/system/modules/com.deepthoughts.templates/templates/BlogHomepage.jsp
For the blogs we need to set the template-elements property instead of the template property. As just mentioned, instead of setting this property on each individual blog entry we can just set it on the Blogs parent folder. Any time later a special blog template can be set to an individual blog by setting the value for that blog entry. On the Blogs parent folder, the property should be set to:
/system/modules/com.deepthoughts.templates/templates/BlogEntry.jsp
After setting the property values, we can view the content through the templates by clicking on the index.html file in the Explorer View. Remember to switch back to the default site in the Workplace Explorer before doing this.
[edit] Expressions in JSP Templates
The code we've covered shows how to use custom beans in templates. For many sites however, this is not necessary, as the base tag library can be combined with expressions to achieve many things that don't require custom beans. Support for the use of expressions has been present in OpenCms for a while. However, since the release of 7.0.2, it has become possible to combine these with the JSP tag library. This is a powerful feature that provides access to the XML tag libraries and is an alternative to sub classing.
[edit] Using the Tag Library from JSP
Support for using expressions to access the tag library has been added via the org.opencms.jsp.CmsJspVfsAccessBean class. This class provides expression access to the tag library from JSP code. To use it, the JSP may just access the methods using expression syntax:
<%@page session="false" import="org.opencms.util.*" %>
<%@taglib prefix="cms"
uri="http://www.opencms.org/taglib/cms" %>
Context Path: ${pageContext.request.contextPath}<br/>
URI: ${cms:vfs(pageContext).requestContext.uri}<br/>
<h1>
${cms:vfs(pageContext).property[cms:vfs(pageContext). requestContext.uri]["Title"]}
</h1><br/>
<cms:editable/>
<cms:contentload collector="allInFolderDateReleasedDesc"
param="/Blogs/2007-07/*.html|blogentry|3" editable="true">
<cms:contentaccess var="blog" />
<p>
<a href="${cms:vfs(pageContext).link[blog.filename]}">
${cms:vfs(pageContext).property[blog.filename]["Title"]}
</a>
</p>
<p>
${cms:vfs(pageContext).readXml[blog.filename].
value[ BlogText']}<br/>
</p>
</cms:contentload>
Note the inclusion of the tag library using the cms prefix. The same namespace prefix is used in the expressions to access the API. Here are some examples that we see in the previous code:
-
${cms:vfs(pagecontext).requestContext.uri}: Returns the URI of the item that was clicked on, causing control to pass to the template. -
${cms:vfs(pageContext).property[filename][propertyname]: Returns the given property on the given filename. In the previous code, it is combined with the requested URI to obtain theTitleproperty. -
${cms:vfs(pageContext).readXml[filename].value[element]: Returns the value of the element within the XML content of the specified filename. In the example, it is used to obtain the BlogText field of the currently iterated blog.
Documentation for all the methods that are available can be found by looking at the org.opencms.jsp.CmsJspVfsAccessBean class.
[edit] Combining Expressions with JSTL
It is easy to add JSTL support to JSP by including the necessary tag libraries. Of course, the relevant .jar files will need to be added to the web project as well. It then becomes easy to add the tag library code to do things like date formatting:
<%@page session="false" import="org.opencms.util.*" %>
<%@taglib prefix="cms"
uri="http://www.opencms.org/taglib/cms" %>
<%@ taglib prefix="fmt"
uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core" %>
Context Path: <c:out value="${pageContext.request.contextPath}" /><br/>
URI: ${cms:vfs(pageContext).requestContext.uri}<br/>
<h1>
${cms:vfs(pageContext).property[cms:vfs(pageContext).
requestContext.uri]["Title"]}
</h1><br/>
<cms:editable/>
<cms:contentload collector="allInFolderDateReleasedDesc"
param="/Blogs/2007-07/*.html|blogentry|3" editable="true">
<cms:contentaccess var="blog" />
<p>
<a href="${cms:vfs(pageContext).link[blog.filename]}">
${cms:vfs(pageContext).property[blog.filename]["Title"]}
</a>
</p>
<p>
<fmt:formatDate value="${cms:convertDate(blog.file.dateCreated)}" type="date" dateStyle="LONG"/>
</p>
<p>
${cms:vfs(pageContext).readXml[blog.filename]. value[ BlogText']}<br/>
</p>
</cms:contentload>
In the previous example the <fmt:formatData> tag is used to convert the blog created date to a human readable format. The Date object required by the tag is obtained using the blog.file.dateCreated expression.
The template example uses the 1.2 version of the JSTL tag library, rather than the 1.1 version. This is because the 1.2 version accepts the use of expressions while version 1.1 does not.
[edit] Summary
In this tutorial, we started with a review of the template code for the Deep Thoughts site. We then showed how to create templates that utilize a custom Java class, which subclasses a base OpenCms class. The custom class we created shows how to change some of the default template behavior. It also shows how to provide added features or logic, which is specific to the template requirements. We also discussed resource loaders and the template loading mechanism. The tutorial also included a section on using expressions in JSP and how they can be utilized in many cases to avoid custom bean development.
[edit] Additional References
- For instructions in Building OpenCMS 7, click here.
- For instructions on Installing OpenCMS, click here
[edit] Source
The source of this content is Chapter 4: Developing Templates of OpenCms 7 Development by Dan Liliedahl Packt Publishing, 2008).

