Previous|Next

I apologize for the delay in getting this out, but I ended up breaking the ability to post in XBlog. I've actually had this post written for a week or so, but just couldn't get around to fixing my blog. But in the last installment we left off with a configured development environment and a basic definition for our custom component. In this installment we'll build our custom renderer. The theory around all the different parts of a component is that the component class (what we built in the last installment) is an object that Java can work with. It defines all of the properties and methods of our component and if necessary any children this component may have. The renderer defines what the component will look like in the browser.  Any markup you want sent to the browser is defined in the renderer. So, let's start putting together our renderer class.

We now need to create a new class in our com.keithstric.renderkit package. We'll name it CommonContactInfoRenderer and in the Super Class field add javax.faces.render.Renderer. The class name isn't really important, but just for simplicity and maintainablity sake I always name my renderer like ComponentClassNameRenderer. This makes it easy to know which renderers go with which components. Also, our renderer doesn't need a constructor, all we need is encodeBegin, encodeChildren and encodeEnd methods and then any helper methods we may need. So, let's go ahead and define our encodeBegin method.

	@Override
	public void encodeBegin(FacesContext context, UIComponent component) {
		try {
			super.encodeBegin(context, component);
			ResponseWriter writer = context.getResponseWriter();
			writer.startElement("fieldset", component);
			writer.writeAttribute("class", "ContactFieldset", null);
			writer.startElement("legend", component);
			writer.writeAttribute("class", "ContactLegend", null);
			writer.append("Contact Information");
			writer.endElement("legend");		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

As you can see in this method we're just basically building HTML for a field set. We're using the ResponseWriter to do this. Basically the ResponseWriter is just a fancy String builder but with enough intellect to know what HTML tags to output based on what you tell it you want. I'm not absolutely sure, but I believe if you miss closing something, the response writer will even close it for you, but don't quote me on that. But we start our field set with ResponseWriter.startElement, this will create a <fieldset> tag for us. The writeAttribute method will add html attributes to the previously defined element (i.e. the field set). We then add a legend to the field set and that's it for encode begin.

The next method we need to be concerned with is encodeChildren. This method will encode and render any child components that may be present with our main component. For the most part, you can always leave this as "super.encodeChildren(context,component)" but if you need to touch each child of a component to add a property or perform other logic, this is where you would take over rendering of the children to do that. If you are so inclined, take a look at the FacesUtil class in the extension library and specifically the renderChildren and renderComponent methods. Also of note with encodeChildren, all of the fields associated with this component are children of this component. This is where the table, fields and labels will be rendered. Since they know how to render themselves, we don't have to do anything. Also, the addition of the child components required a change to the original component which we'll get to in a moment.

Lastly is the encodeEnd method. This is where we'll close any tags that we opened in encodeBegin or even encodeChildren. However, defining HTML in encodeChildren is generally bad practice as each child should know how to encode itself. So our encodeEnd will be:

	@Override
	public void encodeEnd(FacesContext context, UIComponent component) {
		try {
			super.encodeEnd(context, component);
			ResponseWriter writer = context.getResponseWriter();
			writer.endElement("fieldset");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

And that's it for our renderer. We should end up with:

package com.keithstric.renderkit;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

public class CommonContactInfoRenderer extends Renderer {

	/**
	 * Here is the common encodeBegin method. This method is present in almost all renderers
	 * This is where we'll start opening tags and do anything that needs to be done before
	 * we start rendering children of the component.
	 */
	@Override
	public void encodeBegin(FacesContext context, UIComponent component) {
		try {
			super.encodeBegin(context, component);
			ResponseWriter writer = context.getResponseWriter();
			writer.startElement("fieldset", component);
			writer.writeAttribute("class", "ContactFieldset", null);
			writer.startElement("legend", component);
			writer.writeAttribute("class", "ContactLegend", null);
			writer.append("Contact Information");
			writer.endElement("legend");		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Here is the encodeChildren method. This method is present in almost all renderers.
	 * This is where you can wrap logic around the rendering of children or completely take
	 * over the rendering of children in order to add additional properties or whatever.
	 */
	@Override
	public void encodeChildren(FacesContext context, UIComponent component) {
		try {
			super.encodeChildren(context, component);		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Here is the encodeEnd method. This method is present in almost all renderers.
	 * This is where you close any tags you opened in encodeBegin and perform any
	 * logic that may be necessary after the children are encoded
	 */
	@Override
	public void encodeEnd(FacesContext context, UIComponent component) {
		try {
			super.encodeEnd(context, component);
			ResponseWriter writer = context.getResponseWriter();
			writer.endElement("fieldset");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

During the course of writing this blog post, I had to make massive changes to the original component. My initial idea was to have this component store the values for the different fields that will be present within the component. However in practice, this is not practical and just produces more work than necessary. Plus, we've got a data source for that.  So click the "read more" link below to see the changes I made to the component.

Read More...

Today we're looking at custom component creation within an NSF. We're not talking about custom controls, though creating a custom control before creating a component is definately a good way to prototype a custom component. Plus the DDE Builder does kind-of turn our custom controls into components, so they are kind-of custom components, but not really. What we're really talking about is a component that shows up in the DDE Component Palette and has it's own properties, methods and visual representation within designer and on an XPage and is serializable. This component can be used in multiple applications without any modification and may require a complex type in order to function (if you're familiar with the application layout in the extension library, all of the different nodes you can add to the various properties are complex types).

So you're asking, but don't I have to include these in a plugin that lives on the server? Well, you can certainly do that and by including your component in a library that resides on the server you won't have to move your custom component from application to application, it would just be available. But that is a whole different topic altogether, we're wanting to create our component in an NSF so we don't have to bug an administrator to put files on the server for us.

Since this will be a rather lengthy article, we will cover this topic in multiple posts. In part 1 we'll setup our DDE environment to allow us to create java class files and come up with what the properties of the component will be. And if it's not too long of a post, going ahead and creating our component class file.

To start with we'll need to setup our NSF so that we have a place to put our class files. So, go ahead and create a new application and in DDE add the package explorer view to your perspective. You do this by clicking "Window\Show Eclipse Views\Other" and in the resulting dialog navigate to "Java\Package Explorer" and click OK. You should now have a new tab next to the Application Explorer called Package Explorer. Go ahead and open that up if you haven't already.

Navigate to your new application and open up the WebContent folder, right click the WEB-INF folder and select "New\Folder" (if you don't have this option click other and select folder from the dialog). Name your folder "src". Now right click the "src" folder and select "Build Path\Use as Source Folder". This will cause the folder to disappear from WebContent\WEB-INF\src and be moved towards the top of the folder list into "WebContent/WEB-INF/src". This is where we'll be placing all of our java files for this series. As a side note, if you're using 8.5.3 you really don't have to do this. You can use the Java section underneath the code category. But I don't like how it organizes the class files as it doesn't "categorize" the class files underneath their respective packages. But that's my personal preference.

Right click the "WebContent/WEB-INF/src" folder and select "New\Package" (if you don't have this option click other and select package from the dialog). This will be where our component classes are stored. I'm naming mine "com.keithstric.components". Follow the same process and add another package called "com.keithstric.renderkit" and this will be where our renderers are stored.

This component will be for a contact type application with some basic information. So for reusability and simplicity sake we'll create a component with a few fields within a fieldset. This should allow us to demonstrate a couple of different methods and show that a component doesn't have to be just a single html element but can be something more complex also.

Now that we've got our basic structure setup we can start writing some code. We'll start off with the component class. This class should have a few different properties and all of these properties will have getters and setters. The properties will be:

  • Boolean showFirstName
  • Boolean showLastName
  • Boolean showEmailAddress
  • Boolean showPhoneNumber
  • String firstName
  • String lastName
  • String emailAddress
  • String phoneNumber

We'll be defining these properties and then let eclipse create getters and setters for us. So, let's start our class by right clicking the com.keithstric.components package we created earlier and pick "Class" from the drop down menu (If you don't have this option click other and select class from the dialog). Give it a name of CommonContactInfo and set the "Super class" to javax.faces.component.UIComponent and click the "Finish" button. This should create a new .java file in our com.keithstric.components package and our class will have a name of com.keithstric.components.CommonContactInfo.

Once that is completed we can now define our properties like below:

private static final String RENDERER_TYPE = "com.keithstric.CommonContactInfo";
private static final String COMPONENT_FAMILY = "com.keithstric";

private Boolean showFirstName;
private Boolean showLastName;
private Boolean showEmailAddress;
private Boolean showPhoneNumber;
private String firstName;
private String lastName;
private String emailAddress;
private String phoneNumber;

Once that is done, right click one of the properties and select "Source\Generate Getters and Setters" from the drop down menu. This will generate all of our getters and setters for us. You can also define a code template to help us stick to any company standards that may be present. I generally try to stick with the IBM standard and format my getters like:

public String getFirstName() {
	if (this.firstName != null) {
		return this.firstName;
	}
	ValueBinding _vb = getValueBinding("firstName");
	if (_vb != null) {
		return (String) _vb.getValue(getFacesContext());
	} else {
		//Or return a default value of some sort, can be hard coded or
		// determined programmatically
		return null;
	}
}

public void setFirstName(String firstName) {
	this.firstName = firstName;
}

Next up for the component class is to define the constructor and override the "getFamily()" method like below. Take note of the "RENDERER_TYPE" and "COMPONENT_FAMILY" constants used in the constructor where we set the renderer type and then in the getFamily() method. This is VERY IMPORTANT as it tells the server via the faces-config and xsp-config which renderer class to use to render this component. If you forget this, your component will not render, or at least won't use your renderer class:

public CommonContactInfo(){
	setRendererType(RENDERER_TYPE);
}

@Override
public getFamily() {
	return COMPONENT_FAMILY;
}

So, we should end up with this:

package com.keithstric.components;

import javax.faces.component.UIComponent;
import javax.faces.el.ValueBinding;

public class CommonContactInfo extends UIComponent {

	private static final String RENDERER_TYPE = "com.keithstric.CommonContactInfo";
	private static final String COMPONENT_FAMILY = "com.keithstric";

	private Boolean showFirstName;
	private Boolean showLastName;
	private Boolean showEmailAddress;
	private Boolean showPhoneNumber;
	private String firstName;
	private String lastName;
	private String emailAddress;
	private String phoneNumber;
	
	public CommonContactInfo(){
		setRendererType(RENDERER_TYPE);
	}

	@Override
	public getFamily() {
		return COMPONENT_FAMILY;
	}

	public Boolean isShowFirstName() {
		if (showFirstName != null) {
			return showFirstName;
		}
		ValueBinding _vb = getValueBinding("showFirstName");
		if (_vb != null) {
			return (Boolean) _vb.getValue("showFirstName");
		} else {
			return null;
		}
	}

	public void setShowFirstName(Boolean showFirstName) {
		this.showFirstName = showFirstName;
	}

	public Boolean isShowLastName() {
		if (showLastName != null) {
			return showLastName;
		}
		ValueBinding _vb = getValueBinding("showLastName");
		if (_vb != null) {
			return (Boolean) _vb.getValue("showLastName");
		} else {
			return null;
		}
	}

	public void setShowLastName(Boolean showLastName) {
		this.showLastName = showLastName;
	}

	public Boolean isShowEmailAddress() {
		if (showEmailAddress != null) {
			return showEmailAddress;
		}
		ValueBinding _vb = getValueBinding("showEmailAddress");
		if (_vb != null) {
			return (Boolean) _vb.getValue("showEmailAddress");
		} else {
			return null;
		}
	}

	public void setShowEmailAddress(Boolean showEmailAddress) {
		this.showEmailAddress = showEmailAddress;
	}

	public Boolean isShowPhoneNumber() {
		if (showPhoneNumber != null) {
			return showPhoneNumber;
		}
		ValueBinding _vb = getValueBinding("showPhoneNumber");
		if (_vb != null) {
			return (Boolean) _vb.getValue("showPhoneNumber");
		} else {
			return null;
		}
	}

	public void setShowPhoneNumber(Boolean showPhoneNumber) {
		this.showPhoneNumber = showPhoneNumber;
	}

	public String getFirstName() {
		if (firstName != null) {
			return firstName;
		}
		ValueBinding _vb = getValueBinding("firstName");
		if (_vb != null) {
			return (String) _vb.getValue("firstName");
		} else {
			return null;
		}
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		if (lastName != null) {
			return lastName;
		}
		ValueBinding _vb = getValueBinding("lastName");
		if (_vb != null) {
			return (String) _vb.getValue("lastName");
		} else {
			return null;
		}
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmailAddress() {
		if (emailAddress != null) {
			return emailAddress;
		}
		ValueBinding _vb = getValueBinding("emailAddress");
		if (_vb != null) {
			return (String) _vb.getValue("emailAddress");
		} else {
			return null;
		}
	}

	public void setEmailAddress(String emailAddress) {
		this.emailAddress = emailAddress;
	}

	public String getPhoneNumber() {
		if (phoneNumber != null) {
			return phoneNumber;
		}
		ValueBinding _vb = getValueBinding("phoneNumber");
		if (_vb != null) {
			return (String) _vb.getValue("phoneNumber");
		} else {
			return null;
		}
	}

	public void setPhoneNumber(String phoneNumber) {
		this.phoneNumber = phoneNumber;
	}	
	
}

You now have a component definition class with the preliminary structure defined and something to work with. In the next installment we'll create a renderer that will define how the component is represented in the browser. We'll also be coming back to this class to add the Save & Restore State methods with a short discussion around those methods which make the serialization of the component possible.

While just cruising around the web wasting time I stumbled across a grid component on sitepen's site for an alternative to the dojo grid. To me it looks very similar to a jquery grid. But I thought this was very interesting and may play with it at some point in time.

Today while working on a solution for this post on the XPages Forum I couldn't find any API documentation on a DynamicViewColumn which is a child of the Dynamic View Panel included in the extension library. This prompted me to come up with this crude custom control that will give you a drop down box of the component tree's components and then when you make a selection it'll spit out the java class name and all of it's methods with each method's argument types. This should prove to be a very handy custom control when you can't find a property/method of a component. I'm sure I'll think of more to add to this component, but in the mean time, here it is for your enjoyment.


	
		
			
				
			
			
			
		
		
			
				
			
		
		
		
			
				
					
					
				
					
					
			
			
				
					
					
					
					
				
					
					" + method.getName() + "("
			var params = [];
			params = method.getParameterTypes();			
			for (var j = 0;j < params.length;j++) {
				delim = "";
				if (j > 0 && j != (params.length -1)) {
					delim = ", ";
				}
				var methodParam:java.lang.Class = params[j];
				html = html + methodParam.getSimpleName() + delim;
			}
			html = html + ")
";
		}
	}
}
return html;}]]>
						
		
	


Read More...

We've added 2 new family members to the Strickland household (they don't have names yet). They should make fine additions to the family. We used to have a 6 1/2 foot iguana named Puff that pretty much had run of the house. He was certainly cool. But we resurrected my 75 gallon tank to the new bearded dragons. Since they're babies hopefully we can keep the tank hot enough, but we will one way or the other. But here they are.

Orange Bearded Dragon

Green Bearded Dragon

Today I released version 0.7.2a of XBlog. This version includes updates to the Sencha Touch site and the license for using Sencha Touch. New features included are:

  • Bottom Toolbar
    • Home Link
    • Full Archive Link
    • Downloads Link
    • Top 10 Link
  • Sencha Back Button improvements
  • Scrolling issue in Sencha Touch site fixed
  • License Update using the Sencha Touch Exception for Applications

As an early christmas present my wife got me a new Samsung Galaxy S II from T-Mobile. This is by far the best gadget I've ever owned. My initial impression is WOW! It's very fast, much faster than my old myTouch or the LG that replaced the myTouch when it started over-heating.

The T-Mobile version of the Galaxy S II is a little different than the stock Galaxy S II. It sports a 1.5 GHZ Dual Core Processor (as opposed to a 1.2 GHZ Dual Core), a huge 4.5" Super AMOLED screen (as opposed to a 4.3" screen) with Gorilla Glass and a very slim, light case (it's lighter than my LG which is smaller) and a 1850mah battery (as opposed to 1650mah). The battery life so far is ok, not great it was useful for about 8 hours, which I guess is about the norm for android phones, at least the ones I've owned. It also sports an 8 MP camera on the back with an led flash and a 2 MP front facing camera.

The UI is great, it's using the Samsung TouchWiz 4.0 launcher. It's a very smooth UI and totally customizable. It comes with the Swype keyboard and a samsung keyboard which is very close to the Android Ice Cream Sandwich keyboard. It's running Android 2.3.5 but will hopefully be updated to Android 4.0 (Ice Cream Sandwich). 

So hopefully I'll get a lot of use out of this phone and it'll last the whole two years that we re-signed the contract for. We've been very happy with T-Mobile and have no plans of switching carriers as you can't find better customer service from a cell phone company. But if you're in the market for a new phone, take a look at the Samsung Galaxy S2, you won't be disappointed.

I've recently incorporated a VERY simple Sencha Touch application into XBlog, if you visit this blog with a mobile device you will see it in action. The reason for this was because when attempting to read one of my favorite blogs on my Android phone I got really frustrated that it was so small and having to scroll up, down, left and right just to read a short post. This prompted me to look at my blog on my Android and it was even worse! So, we now need a mobile front end for XBlog.

Now for a normal Sencha Touch application seems everyone places their javascript files in a directory called app and within that app.js. The Sencha touch framework is placed in a lib directory and then an index.html file. This actually poses a couple of problems for an XPage application. First, how do you get a visitor to the index.html file when they enter the application with a mobile device? Sure, we could redirect them, but that's not cool and some browsers complain about being redirected. Next issue is if we run this from a javascript file that is not running within the context of an XPages application, how do we get data from our application?

To address both of these issues, there is a component that is not included in the component palette (it may be there it's just I don't know the name of it) which is an <xp:scriptBlock> component. This component allows you to place a client side javascript script on your xpage. This actually fits the bill for the issue of our client side javascript running outside the context of an XPage application by placing our application within the context of an XPage application. And since it's on an XPage, no need to redirect to index.html. All thats left to getting everything working is to include all the other needed javascript and css files as resources to the page. As a bonus we can set the "loaded" property accordingly to only be loaded when someone visits the site with a mobile device for our script block and the included resource definitions.

So let's take a look at the setup here. I've added the sencha framework in WebContent\sencha\lib\ and the only javascript file that I placed in this directory structure is within WebContent\sencha\app\models\ you can also find WebContent\sencha\app\app.js and WebContent\sencha\index.html however these files are basically a formality to traditional sencha touch apps. I've also added a script blog component to a custom control which contains our sencha touch application. I want to veer off course for a second at this point and comment about Sencha Touch in general. As an application framework, the use of Ext makes it very straight forward to program with. Everything is an object and using basic javascript skills it's easy to implement a sencha touch application. The Mobile UI for XBlog only took a few hours to implement. It was getting data and learning how models, stores and proxies work in relationship to sencha touch that took the most time. Once I learned the basic premise of the data store model it was just a matter of creating my own REST service and pointing my data store to that REST service.

Let's take a look at the javascript which is pretty much the entire UI of the XBlog Mobile site:

Ext.regApplication({
	name: "XBlogMobile",
	defaultTarget: "viewport",
	models: ['BlogListModel'],
	stores: ['BlogListStore'],
	launch: function() {
		//This is the data store with proxy to the REST service
		Ext.StoreMgr.register(new Ext.data.Store({
			storeId: 'BlogListStore',
			model: 'BlogListModel',
			autoload: true,
			proxy: {
				type: 'rest',
				url: Ext.getHead().dom.baseURI + "/default.xsp?" + xblogMobileRest.extraArgs + "&type=BLOG",
				format: 'json'
			}
		}));
	
		//This is the title bar at the top of the viewport
		this.listTitleBar = new Ext.Toolbar({
			title: "#{javascript:XBlogUtils.getConfigValue('BlogName',0).toString();}",
			items: [{
				id: "SenchaBackButton",
				text: 'back',
				ui: 'back',
				handler: function() {			
					XBlogMobile.viewPort.setActiveItem('blogList', {type: 'slide', direction: 'right'});
					var backButton = Ext.getCmp("SenchaBackButton");
					if (backButton) {
						backButton.hide();
					}
				}				
			}]
		});
		var backButton = Ext.getCmp("SenchaBackButton");
		if (backButton) {
			backButton.hide();
		}
		
		//This is the panel that we navigate to when someone clicks an entry in the blog list
		this.postPanel = new Ext.Panel({
			id: "postPanel",
			scroll: 'vertical',
			tpl: "<div class='MobileBlogTitle'>{title}</div><div class='MobileBlogDate'>{createdDate}</div><div class='MobileBlogEntry'>{content}</div>"
		});
		
		//This is the list of blog posts
		this.listPanel = new Ext.List({
			id: 'blogList',
			store: 'BlogListStore',
			scroll: 'vertical',
			itemTpl: "<div class='MobileBlogListItem'><span class='MobileBlogListTitle'>{title}</span><br><span class='MobileBlogListDate'>{createdDate}</span></div>",
			onItemDisclosure: function(record, btn, index) {
				var backButton = Ext.getCmp("SenchaBackButton");
				if (backButton) {
					backButton.show();
				}
				XBlogMobile.postPanel.update(record.data);
				XBlogMobile.viewPort.setActiveItem("postPanel");
			}
		});
		
		//This is our actual viewport panel that holds everything
		this.viewPort = new Ext.Panel({
			fullscreen: true,
			scroll: false,
			layout: 'card',
			cardSwitchAnimation: 'slide',
			dockedItems: [XBlogMobile.listTitleBar],
			items: [XBlogMobile.listPanel, XBlogMobile.postPanel]
		});
			
		Ext.getStore("BlogListStore").load();
	}
});

For the data store I included it here so that I could use EL to make SSJS calls to get some of the more pertinent information easily. Normally the data store would be it's own file. So, this covers almost everything except the data model and REST service. The data model just defines the structure of our data and is a very simple file:

 

Ext.regModel('BlogListModel', {
	fields: [
	         {name: 'title', type:'string'},
	         {name: 'createdDate', type: 'string'},
	         {name: 'content', type: 'string'},
	         {name: 'unid', type: 'string'}
	]
});

I know that all this isn't that complex but I think that's the beauty of Sencha Touch, it makes for very clean, simple code. Plus it's a fun framework to mess with. Now, if you'll notice in the top most code snippet there is a data store which includes a proxy item. The proxy is what tells the store the url to call to get it's data. In this case that url is the url or our REST service along with what we're looking for (i.e. BLOG, FULLARCHIVE or ARCHIVE note only BLOG is currently implemented). I'm sure this will all change in the future to support more data being sent to the browser, but for now simple is good. I would like to make the way data is retrieved to be more lazy and provide some paging in order to navigate all of the blog posts instead of the top 10.

So hopefully this will prompt you to take a look at Sencha Touch for your applications. To me it's a very elegant framework that isn't too difficult to use. The biggest hurdle is learning it, but once you take the plunge it's not very difficult, the hardest part is getting data. As for issues I encountered.... well the only real issue was scrolling (which sadly wasn't fixed in the 0.7.1a release) but the fix was just adding a couple of missing properties to the appropriate sencha components.

While developing XBlog one of the requirements was that the layout of posts, comments, permalink pages, pages, archives and downloads needed to be customizable by the blog owner. This would allow the blog owner to make XBlog look exactly as (s)he imagined. The problem is, how do you pull this off and yet still have the application perform as one would expect without a massive amount of requests going back and forth between the browser and server? While this task seems a little daunting it is doable and performs pretty well. Also, I need to credit Declan Lynch and BlogSphere with this idea, so it's not something new.

To start with, you need to come up with a way to plug in the proper values (i.e. title, date, author, etc.) in the location that the blog owner desires. The way I accomplished this was to define some "Hot Tags". These tags look something like "<$Title$>". This hot tag is stored in a document that says the <$Title$> tag should be replaced with the value of the title field from the post form. You can also say this is custom HTML and replace the <$Title$> tag with the custom HTML. We'll come back to the hot tags in a moment.

Now that we've got a way to place actual values into our HTML we need to come up with an HTML "template" to use for each type of component and be able to determine what the state of the page is that's being viewed so we know which template to use. Let's look at a blog entry template that is used on this site for each blog entry in the list of blog entries:

<div class="entry">
	<div class="header">
		<h2 class="entryHeader">
			<a href="<$Permalink$>"><$Title$></a>
		</h2>
	</div>
	<div class="authorHeader">
		<span class="entryAuthor authorHeaderItem"><$Author$></span>
		<span class="dateContainer authorHeaderItem"><$CreatedDate$></span>
		<span class="tagsContainer authorHeaderItem">
			<$TransCategories$><$Tags$>
		</span>	
		<span class="viewedContainer authorHeaderItem">
			<$ViewNumber$> Views	
		</span>
		<span class="numCommentsContainer authorHeaderItem">
			<a href="<$FullPermalink$>#comments" title="<$Title$>"><$NumberComments$> <$TransComments$></a>
		</span>
	</div>	
	<div class="entryBody"><$Content$>	</div>
	<div class="entryFooter">		
		<div class="addthis_toolbox addthis_default_style" addthis:url="<$FullPermalink$>">
			<$SocialToolbar$>
		</div>
		<div class="footerItem attachmentItem"><$Attachments$></div>
		<div class="footerItem readMoreText">
			<a href="<$Permalink$>"><$TransReadMore$></a>
		</div>
	</div>
</div>

Using a template like this allows you to say that every post in a blog entry list will have this structure. This structure is stored in the applicationScope so we don't have to keep making a request to the document to determine what the default html for a blog entry will be. Since it's only stored once, you also don't have to worry about scalability because this is being stored for every post for every user (guess how I know this is an issue). It's stored once and used by everything and everyone. We're able to do this because once you determine this basic template it's probably not going to change very much and it's not specific to the current user, but to all users.

We've now got our default html and a way to determine what values should be placed where. We now need to setup our values in order to replace the pertinent tags with the proper value. We do this by putting a repeat control on the page that is bound to the view which contains our blog entries. This repeat only has a "Post" component within it that get it's property values from the fields on the document. So it's just a matter of getting the values from the document being processed by the repeat.

Since we've got our default html, a way to determine what value should be placed where and we've got all of our data we can now start replacing the hot tags with the value from the "Post" component. To accomplish this we do a lookup to our hot tags view and get all the hot tags for a "Post". We then follow the steps below:

  • We create a hash map (a hash map is just a container of key/value pairs) that contains the tag as the key and the value is the value from the Post component
  • We loop through the hash map and just do a replace of the hash map key (tag) with the value for that key within the default html
  • We then clean up any left over tags that may be present
  • This html is what is sent to the browser
Keep in mind that this all happens in the rendering portion of the component as that takes away having to keep track of the document we're working with. Also handling this during the rendering of the component makes it easy to keep up with what you're working with and reduces the if/if else/else statements if you try to handle every type of content within the same method or class.

Here's the short snippet of code that does our replacement:

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
	super.encodeBegin(context, component);
	Post post = (Post) component;
	postHtml = post.getDefaultHtml();
	hotTags = getHotTagMap(post);
	for (String replaceTag : hotTags.keySet()) {
		if (hotTags.get(replaceTag) != null) {
			postHtml = postHtml.replace(replaceTag, hotTags.get(replaceTag).toString());
		}
	}
}

And that's how you setup an html template that is totally customizable, scalable, easily modified and maintainable. Of course there are some special considerations to take into account like for attachments there may be many that we need to account for, etc but the concept is still the same. Since you're already reading this you've seen a demo of it in action as this is how all the content in XBlog is determined. So hopefully you can get some mileage out of this concept in your own application.

XBlog 0.7a released to OpenNTF. This version includes several new features:

  • Posts, Comments, Pages and Archive items all componentized which resulted in a slight performance gain
  • Search incorporated in both the front-end and back-end websites
  • Full Archive now available
  • Small Client Side API implemented
  • All Downloads section added

Items fixed in this release:

  • Sorting arrows for Links
  • Hot Text replacement only occurs in content of a post, not all html, fixes breakage of html due to text replacement
  • Emoticon replacement only occurs in content of a post and comment, not all html, fixes breakage of html due to text replacement
  • Reset of scoped variables now happens during save operation for pertinent pages
  • Java Security issue corrected (403 error)
  • Number of RSS Items to show fixed
  • Changed RSS from Atom to RSS 2.0 for inclusion in PlanetLotus aggregator
  • Fixed many RSS issues

This release is what the first release should've been. It should be a very stable release. All of these new features can be seen right here on keithstric.com

Page12345678...

The opinions and ideas posted on keithstric.com are not necessarily the opinions and ideas of my employer. The solutions, techniques and code provided here are not guaranteed or warranted in any way and are free for you to use at your own risk.


Creative Commons License
keithstric.com is licensed under a Creative Commons Attribution 3.0 Unported License.
Based on a work at www.keithstric.com.


All code samples and downloads are copyright Keith Strickland and licensed under Apache License 2.0

Copyright 2002-2011 Keith Strickland
Powered by XBlog