Building a Fully Functional Blog with eXo Platform – Part 2

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:

<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:

<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 New Content action, Blog Post is now available:
Select Template

We can tell the Fast Content Creator to use this type to create blog posts:

<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:

fcc form

Add this piece to postform.gtmpl.

// 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 /l11n/cleanName. The returned string is also used as the permalink to the blog post article.

<%
  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:

Blog Detail

The /article page uses the blog template we declared as viewpost.gtmpl.

<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"/>&nbsp;$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 javax.jcr.Node via def node = uicomponent.node. Then most of the work is trivial display and formatting logic.

Let’s see how to render the body of the post:

/* 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 getSummary() 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 default.html/jcr:content/jcr:data property.

Lastly, the uicomponent.getInlineEditingField() provides support for inline editing of the post’s html content.

Blog Main Page

The main blog page (/blog) must show the list of recent blog posts. In this example, we chose to display only the excerpts of recent posts on this page.

Content List

The first thing to do is to declare a CLV template in wcm-templates.gtmpl:

<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 formViewTemplatePathportlet preference in pages.xml:

<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 List<javax.jcr.Node> via uicomponent.getCurrentPageData().

Code:
def entries = uicomponent.getCurrentPageData()
// loop on entries
for (entry in entries) { … }

We won’t render the whole post here, but rather provide a Read more link to the /blog/article detail page. You can retrieve that link with uicomponent.getURL(entry).

Displaying an article is pretty much the same action as we used in viewpost.gtmpl:

$excerpt&nbsp;<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!

Related Posts

Patrice Lamarque

I am the product officer at eXo. I oversee product management and product marketing. My teams design, create and promote the features of and improvements to eXo Platform. As a former enterprise software developer turned product manager, I have a passion for how IT can improve people’s lives. In this blog, I write about some of my personal interests, such as productivity, alternative forms of management and corporate organisations, collaboration, open-source and emerging technologies..

Comments

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>