Following are the main points that we should consider before developing a component:
1) What is the main function of a component?
2) Is the component going to be only UI component or is there some business logic associated with it?
3) If a component has some business logic associated with it then is it going to be the same for all websites?
4) Is this component is abstract i.e. component itself does not provide any functionality but other components will be inheriting from it and will be adding their own logic (UI or business)?
Once we have decided on the purpose of the component we need to jump on developing it (by writing JSP, CSS, JS and Java code). So, while developing components we should try to follow some best practices and conventions that help our peers (other developers and teams) to understand the code and it also makes the maintenance task easy:
1) We should avoid writing business logic at the UI layer (i.e JSP). It’s always a good practice to extract the business logic into a separate class so that it can be used in other parts of the application, this is very important because our business logic should NOT be scattered around multiple areas. Also, when we have our business logic at one place it makes maintenance tasks easy, we just need to change it in one place and it’ll be reflected for all. For those cases where we have a requirement of different business logic for different components, in that case, we can extend an existing class(es) and add our own functionality.
2) User JSP beans (jsp:usebean) for accessing and class/logic.
3) Application-level initialization should be done in one single place. Commonly used variables like user account information, session, etc. should not be initialized by each component/page, we should have a commonplace that is hooked up at entry-level of application (when a request comes in for a page) and is initialized only once per request.
4) Use the JSP Expression Language (EL) as much as possible that keeps out code clean/readable.
5) Avoid defining CSS styling in the components, this keeps our component loosely coupled from a styling perspective and we can modify it as and when required. We can have some kind of convention for naming the HTML elements so that it can be modified via an external CSS file.
6) It’s fair enough to keep JavaScript code in JSP file but, if there is some code is common for all components then it should be moved to a common JS file.
Ok, let’s try to build a component. I am going to build a component that will read the RSS feeds and will display it on a web page, we can place this component on any page.
Purpose “RSS feed” component:
1) It should read the RSS feeds supplied by various web sites and should display it on the web page.
2) A user can choose to display RSS feeds from a specific site so the component should adapt itself to display the RSS feeds from URL (configured in the user’s preference) when he logs in.
3) Users should be able to place the component on as many pages as pages they want.
Development consideration:
1) The URL from where RSS feeds can be pulled should be configurable (we’ll CQ’s dialog feature here).
2) I should have control over the number of feeds that are going to be displayed at one time (again we’ll user CQ’s dialog feature).
3) The logic which parses the RSS feeds might change in the future and it should not be coded inside the component, my component's main responsibility is just displaying the RSS content.
4) Styling of a component should be configurable (i.e. it should come from CSS).
Component Setup:
I’ll create a CQ component that consists
a) JSP file: that contains the rendering logic.
b) JS (JQuery) code (embedded in JSP itself): that handles the click events.
c) A generic helper class (RequestAwareComponent) that initializes the properties (jcr:content properties configured via dialogs) of a component and current request object (thread-local request).
i. RequestAwareComponent: there are few things that are common for all components, all components always need to access certain properties (jcr:content nodes authored by site authors) and data from the request so I have abstracted them this piece in RequestAwareComponent which performs property initialization and data extraction tasks from request.
d) Supporting classes: component backing bean (RSSFeedComponent) that contains the logic of reading/parsing the RSS feed component, an entity object (RSSFeed) that represents an RSS feed entry
i. RSSFeedComponent: This class contains RSS feed parsing logic and constant for each author able property. We should define (and use) the constants for component properties because if property name(s) changes in the future then the changes that we need to do will be easier (just changing the constant value). Also, since we have the parsing logic in a class this same class can be used by other components (or some other piece of application).
ii. RSSFeed: this class represents an RSS feed entry with getters and setters for accessing feed data (title, link, publish date, author and description). We should always (wherever possible) represents our domain data in the form of objects so that it can be transferred to the other piece of application and that piece can access it without knowing how (and who) has created it.
UML representation of classes that I have used for creating RSS feed component
This how the final component looks like, I have mapped the RSSFeed Object to the actual RSS feed entry that is displayed in the below diagram:
Final RSS Feed component: Mapping between RSSFeed class and Actual RSS feed entry
As you can see that we have mapped the RSSFeed class with actual component and it makes it easy to use the same RSSFeed class (object) as data for other components. Now let’s see the actual code, first I’ll try to explain the classes and then I’ll move on to the components and its styling.
The RequestAwareComponent class: This class is a base class that holds the Current Request object (HttpServletRequest), component properties (valueMap) and a flag (validValueMap) that indicates whether the value map is valid or not? Each component that has some business logic should extend this class and implement the intializeComponentProperties() method to initialize component-specific properties (I’ll show an example of this shortly).
import javax.servlet.http.HttpServletRequest;
import org.apache.sling.api.resource.ValueMap;
public abstract class RequestAwareComponent {
public static final String COMPONENT_PROPERTIES = "properties";
private HttpServletRequest request;
private ValueMap valueMap;
private boolean validValueMap = false;
public RequestAwareComponent() {
intialize();
}
protected void intialize() {
request = ComponentUtil.getRequest();
if(request != null) {
valueMap = (ValueMap)request.getAttribute(
RequestAwareComponent.COMPONENT_PROPERTIES);
if(valueMap != null) {
intializeComponentProperties();
validValueMap = true;
}
}
}
/**
* This class must be implemented by extended component
* classes to do component specific initializations
*/
public abstract void intializeComponentProperties();
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public ValueMap getValueMap() {
return valueMap;
}
public void setValueMap(ValueMap valueMap) {
this.valueMap = valueMap;
}
public boolean isValidValueMap() {
return validValueMap;
}
public void setValidValueMap(boolean validValueMap) {
this.validValueMap = validValueMap;
}
}
The RSSFeedComponent class: This class is a component backing class and it extends from RequestAwareComponent.This class will implement the intializeComponentProperties() method to initialize the component-specific properties (jcr:content) and will have another component-specific method getRssFeeds() that will return the feed entries (List) parsed from provided RSS feed URL.
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.sling.api.resource.ValueMap;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class RSSFeedComponent extends RequestAwareComponent {
public static final String COMPONENT_PROPERTIES = "rssFeedComponentProperties";
//Component Constants
public static final String RSS_ITEM_ATTRIBUE = "item";
public static final String RSS_TITLE_ATTRIBUE = "title";
public static final String RSS_LINK_ATTRIBUE = "link";
public static final String RSS_PUBDATE_ATTRIBUE = "pubDate";
public static final String RSS_AUTHOR_ATTRIBUE = "author";
public static final String RSS_DESCRIPTION_ATTRIBUE = "description";
//Component Value/Property constants
public static final String RSS_FEED_ULR = "rssFeedUrl";
public static final String RSS_MAX_FEED_COUNT = "maxFeedCount";
//Components (Configurable) Attributes
private String rssFeedUrl;
private String maxFeedCount;
public RSSFeedComponent() {
}
public void intializeComponentProperties() {
if(getValueMap() != null && getRequest() != null) {
setValueMap((ValueMap)getRequest().getAttribute(
RSSFeedComponent.COMPONENT_PROPERTIES));
}
this.rssFeedUrl = getValueMap().get(RSS_FEED_ULR,
"http://suryakand-shinde.blogspot.com/feeds/posts/default?alt=rss");
this.maxFeedCount = getValueMap().get(RSS_MAX_FEED_COUNT, "2");
}
public List getRssFeeds() {
List feeds = null;
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
URL u = new URL(this.rssFeedUrl);
Document doc = builder.parse(u.openStream());
NodeList nodes = doc.getElementsByTagName(RSS_ITEM_ATTRIBUE);
if(nodes != null) {
feeds = new ArrayList();
for(int feedIndex = 0; feedIndex
if(feedIndex < Integer.parseInt(this.maxFeedCount)) {
Element element = (Element)nodes.item(feedIndex);
feeds.add(new RSSFeed(getElementValue(element, RSS_TITLE_ATTRIBUE),
getElementValue(element, RSS_LINK_ATTRIBUE),
getElementValue(element, RSS_PUBDATE_ATTRIBUE),
getElementValue(element, RSS_AUTHOR_ATTRIBUE),
getElementValue(element, RSS_DESCRIPTION_ATTRIBUE)));
} else {
break;
}
}
}
} catch(Exception ex) {
ex.printStackTrace();
}
return feeds;
}
public String getElementValue(Element parent,String label) {
return getCharacterDataFromElement(
(Element)parent.getElementsByTagName(label).item(0));
}
public String getCharacterDataFromElement(Element e) {
if(e != null) {
try {
Node child = e.getFirstChild();
if(child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
return " ";
}
public String getRssFeedUrl() {
return rssFeedUrl;
}
public void setRssFeedUrl(String rssFeedUrl) {
this.rssFeedUrl = rssFeedUrl;
}
public String getMaxFeedCount() {
return maxFeedCount;
}
public void setMaxFeedCount(String maxFeedCount) {
this.maxFeedCount = maxFeedCount;
}
}
The RSSFeed class: This class represents the RSS Feeds as a data structure. The component backing bean class parses the RSS feeds XML and constructs a list (List) of RSSFeed objects.
public class RSSFeed {
private String title;
private String link;
private String publishDate;
private String author;
private String description;
public RSSFeed (String title, String link, String publishDate, String author, String description) {
this.title = title;
this.link = link;
this.publishDate = publishDate;
this.author = author;
this.description = description;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getPublishDate() {
return publishDate;
}
public void setPublishDate(String publishDate) {
this.publishDate = publishDate;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
The RSS Feed component JSP: The JSP code is very simple, it will simple use the RSSFeedComponent class a jsp bean (rssFeeder) and will pass the component properties to it. The “rssFeeder” bean will return List that is iterated on JSP to render the feed data/entries. Please not that we are not writing the parsing logic and java code in JSP, rather its part of a reusable class (RSSFeedComponent), this keeps our rendering logic loosely couple with data and its parsing logic. Here is the code of JSP:
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@include file="/libs/foundation/global.jsp" %>
<%@page import="com.newcorp.ccp.web.components.rssfeed.RSSFeedComponent"%>
<%--
Development Notes
Each component has its own properties and in order to pass those properties
to component backing bean (in this case RSSFeedComponent class) we need to
set this as a request (thread-local) attribute.
NOTE: while setting the attribute please use the component-specific constant
(COMPONENT_PROPERTIES) so that the attributes key should not clash with each other.
--%>
<% request.setAttribute(RSSFeedComponent.COMPONENT_PROPERTIES, properties); %>
<jsp:useBean id="rssFeeder" class="com.newcorp.ccp.web.components.rssfeed.RSSFeedComponent" scope="page" />
<script>
$(function() {
$("#rssFeedBox" ).accordion({
fillSpace: true,
collapsible: true
});
});
script>
<c:choose>
<c:when test="${not empty rssFeeder.rssFeeds}">
<div id="rssFeedBox">
<c:forEach var="feedItem" items="${rssFeeder.rssFeeds}" varStatus="feedCounter" >
<h3><a href="#">Title: ${feedItem.title}a>h3>
<div>
<span id="rssAuthor">Author: ${feedItem.author}<br>span>
<span id="rssLink">Link: <a href="${feedItem.link}">Visit Bloga><br>span>
<span id="rssPubDate">Description: ${feedItem.description}<br>span>
<span id="rssPubDate">Date: ${feedItem.publishDate}<br>span>
div>
c:forEach>
div>
c:when>
<c:otherwise>
<span id="rssPubDate">RSS Feeds (from: <c:out value="${rssFeeder.rssFeedUrl}" />) is unavailable at this time!<br>span>
c:otherwise>
c:choose>
There are many other ways to lay down a foundation/convention for components but, it all depends on what kind of application we are building and what level of extensibility we want to achieve. Please feel free to comment and provide your suggestions.
Development Notes
Each component has its own properties and in order to pass those properties
to component backing bean (in this case RSSFeedComponent class) we need to
set this as a request (thread-local) attribute.
NOTE: while setting the attribute please use the component-specific constant
(COMPONENT_PROPERTIES) so that the attributes key should not clash with each other.
--%>
<% request.setAttribute(RSSFeedComponent.COMPONENT_PROPERTIES, properties); %>
<jsp:useBean id="rssFeeder" class="com.newcorp.ccp.web.components.rssfeed.RSSFeedComponent" scope="page" />
<script>
$(function() {
$("#rssFeedBox" ).accordion({
fillSpace: true,
collapsible: true
});
});
script>
<c:choose>
<c:when test="${not empty rssFeeder.rssFeeds}">
<div id="rssFeedBox">
<c:forEach var="feedItem" items="${rssFeeder.rssFeeds}" varStatus="feedCounter" >
<h3><a href="#">Title: ${feedItem.title}a>h3>
<div>
<span id="rssAuthor">Author: ${feedItem.author}<br>span>
<span id="rssLink">Link: <a href="${feedItem.link}">Visit Bloga><br>span>
<span id="rssPubDate">Description: ${feedItem.description}<br>span>
<span id="rssPubDate">Date: ${feedItem.publishDate}<br>span>
div>
c:forEach>
div>
c:when>
<c:otherwise>
<span id="rssPubDate">RSS Feeds (from: <c:out value="${rssFeeder.rssFeedUrl}" />) is unavailable at this time!<br>span>
c:otherwise>
c:choose>
There are many other ways to lay down a foundation/convention for components but, it all depends on what kind of application we are building and what level of extensibility we want to achieve. Please feel free to comment and provide your suggestions.
No comments:
Post a Comment
If you have any doubts or questions, please let us know.