This blog is the second part of the series “Building a Blog with eXo”. If you missed it, check out Part 1, where we demonstrated how to build the core structure, navigation and pages, and quickly configured the apps. In this Part 2, we will show how to improve the look-and-feel by adding custom templates.
The blog application in eXo Platform 3.5 uses the built-in web content document type to store posts. While a web content structure has everything needed for storing and displaying blog posts, let’s create a dedicated document type so we can easily differentiate between blog posts and other types of content.
Blog Document Type
It’s typically a good idea to establish your own data structure, as this provides a lot of customization capabilities for each specific document type. The data structure is defined by JCR node types. Check out blog-nodetype.xml:
|
1 2 3 4 5 6 7 8 9 10 |
<nodeType name="exo:blog" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
<supertypes>
<supertype>exo:webContent</supertype>
</supertypes>
<propertyDefinitions>
<propertyDefinition name="exo:title" requiredType="String" autoCreated="false" mandatory="false" onParentVersion="COPY" protected="false" multiple="false">
<valueConstraints />
</propertyDefinition>
</propertyDefinitions>
</nodeType> |
To have this data structure type recognized, we need to establish it as a document type, give it a form for input as well as a template for viewing. Here’s what you need to place in wcm-templates.xml:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$NodeType">
<field name="nodetypeName">
<string>exo:blog</string>
</field>
<field name="documentTemplate">
<boolean>true</boolean>
</field>
<field name="label">
<string>Blog Post</string>
</field>
<field name="referencedView">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$Template">
<field name="templateFile">
<string>/blog/viewpost.gtmpl</string>
</field>
<field name="roles">
<string>*</string>
</field>
</object>
</value>
</collection>
</field>
<field name="referencedDialog">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$Template">
<field name="templateFile">
<string>/blog/postform.gtmpl</string>
</field>
<field name="roles">
<string>*</string>
</field>
</object>
</value>
</collection>
</field>
</object> |
Now, when you go to the backoffice and click on action, is now available:

We can tell the Fast Content Creator to use this type to create blog posts:
|
1 2 3 4 5 |
<preference>
<name>type</name>
<value>exo:blog</value>
<read-only>false</read-only>
</preference> |
Blog Post Form
We want to make it as easy as possible for blog authors to create and post, so they can focus on the content instead of tedious administrative steps. By creating a form, authors simply input a few fields and their blog post is ready for review or publication. This form will have a title, a section for content input, and an optional excerpt. To build this form, simply complete the following:
Add this piece to postform.gtmpl.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// the title field
String[] webContentFieldTitle = ["jcrPath=/node/exo:title", "options=noSanitization", "validate=empty", "editable=if-null", "options=width:'950px'"];
uicomponent.addTextField("title", webContentFieldTitle) ;
// the main content
if(webContentNode != null && webContentNode.hasNode("default.html")) {
Node htmlNode = webContentNode.getNode("default.html");
htmlContent = htmlNode.getNode("jcr:content").getProperty("jcr:data").getValue().getString();
}
String[] htmlArguments = ["jcrPath=/node/default.html/jcr:content/jcr:data", "options=toolbar:CompleteWCM,height:'410px',noSanitization", htmlContent];
uicomponent.addRichtextField(htmlWYSIWYGFormId, htmlArguments) ;
// the excerpt field
String[] fieldSummary = ["jcrPath=/node/exo:summary", "options=Basic", ""] ;
uicomponent.addRichtextField("summary", fieldSummary) ; |
Another interesting point to note is how the node name is defined. It’s automatically extrapolated from the title using a REST service called . The returned string is also used as the permalink to the blog post article.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<%
String[] webContentFieldName = ["jcrPath=/node", "nodetype=exo:blog", "options=noSanitization", "mixintype=mix:votable,mix:commentable,mix:i18n", "editable=if-null","validate=name,empty"] ;
uicomponent.addTextField("name", webContentFieldName) ;
%>
<p class="help-block muted">http://int.exoplatform.org/portal/content/blog/article/<span id="uri"></span></p>
<script type="text/javascript">
//<![CDATA[
var jq = jQuery.noConflict();
jq(function() {
var name = jq("#name");
var syncuri = function() {
var data = name.val()
jq('#uri').replaceWith('<span id="uri">'+data+'</span>');
}
syncuri();
// when user types in title, we set the name and the uri
jq("#title").change(function() {
if (!name.readOnly) {
var title = this.value;
var portalContext = eXo.env.portal.context;
var portalRest = eXo.env.portal.rest;
var url = portalContext+"/"+portalRest+"/l11n/cleanName";
jq.ajax({
type: "GET",
url: url,
data: { name: title},
success: function(data) {
jq('#name').val(data).trigger('change');
}
}); // end ajax
} // end if not readonly
}); // end change title
jq("#name").change(syncuri); // end change name
}); // end jq
//]]>
</script> |
Blog Post Detail
The article detail page is optimized for reading, by providing a full-width view of the article, excerpts and post content. The name and avatar of the author and the publication date are also displayed. Here is what the body of an article looks like:
The page uses the blog template we declared as viewpost.gtmpl.
|
1 2 3 4 5 6 7 8 9 |
<article id="post-$uuid" class="post type-post status-publish format-standard hentry clearfix" role="article">
<header>
<div class="page-header"><h1 class="single-title" itemprop="headline">$title</h1></div>
<p class="meta"><a href="/portal/intranet/profile/$poster" title="View author profile" rel="author"><img src="$avatar" title="Avatar of $author" class="avatar"/> $author</a></span> on <time datetime="$date" pubdate>$date</time></p>
</header>
<section class="post_content clearfix">
<% renderPost(node)%>
</section>
</article> |
You can retrieve the blog post as a via . Then most of the work is trivial display and formatting logic.
Let’s see how to render the body of the post:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* render a blog entry */
void renderPost(def entry) {
...
def excerpt = getSummary(entry);
// start with the excerpt
if (excerpt) {
%><em>$excerpt</em><%
}
// print the main content
def html = entry.getNode("default.html/jcr:content").getProperty("jcr:data").string
print uicomponent.getInlineEditingField(entry, "default.html/jcr:content/jcr:data", html, "WYSIWYG", "Text", "Content", true, cssOption, "toolbar=CompleteWCM");
... |
Using the allows you to take the summary and print the excerpt (if one is provided for a particular post). The actual html content is retrieved from the property.
Lastly, the provides support for inline editing of the post’s html content.
Blog Main Page
The main blog page () must show the list of recent blog posts. In this example, we chose to display only the excerpts of recent posts on this page.
The first thing to do is to declare a CLV template in wcm-templates.gtmpl:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<external-component-plugins>
<target-component>org.exoplatform.services.cms.views.ApplicationTemplateManagerService</target-component>
<component-plugin>
<name>acme.clv.templates.plugin</name>
<set-method>addPlugin</set-method>
<type>org.exoplatform.services.cms.views.PortletTemplatePlugin</type>
<description>This plugin is used to import views templates for Content List Viewer</description>
<init-params>
<value-param>
<name>portletName</name>
<value>content-list-viewer</value>
</value-param>
<value-param>
<name>portlet.template.path</name>
<value>war:/templates</value>
</value-param>
<object-param>
<name>Blog</name>
<description>Main Blog Template</description>
<object type="org.exoplatform.services.cms.views.PortletTemplatePlugin$PortletTemplateConfig">
<field name="templateName">
<string>Blog.gtmpl</string>
</field>
<field name="category">
<string>list</string>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins> |
There we declared our template as Blog.gtmpl.
Next we need to tell the CLV to use it. This is done via the portlet preference in pages.xml:
|
1 2 3 4 5 |
<preference>
<name>formViewTemplatePath</name>
<value>/exo:ecm/views/templates/content-list-viewer/list/Blog.gtmpl</value>
<read-only>false</read-only>
</preference> |
The blog main page is not too different since we are going to loop on entries provided by the CLV and display titles and excerpts. Let’s dig into Blog.gtmpl. To loop on post entries, simply get them as a via .
Code:
We won’t render the whole post here, but rather provide a Read more link to the detail page. You can retrieve that link with .
Displaying an article is pretty much the same action as we used in viewpost.gtmpl:
|
1 |
$excerpt <h4><a href="${uicomponent.getURL(entry)}" class="more-link">Read more »</a></h4> |
Wrapping Up
Making things user-friendly (and aesthetically pleasing) is easy. A little config and very minimal coding skills are needed. Here’s the recipe for making any content gorgeous. Start by creating your own document type. Give it a styled form and a shiny detail template. Then finish with the Content List template, where you’ll be able to reuse a lot of previous work. And there’s more — we’re only scratching the surface. In Part 3 of this series, we will continue our feature tour and show you how to add categories and tags to the blog. Stay tuned!


