Friday, July 30, 2010

Spring MVC portlet with annotations

Java portlet framework is awesome for portal developers. Different portal frameworks are there which supports portlet development in their environment. They also support spring portlet. Spring's portlet support is exposed through Spring MVC interface. Spring provides MVC based portlet development which is so easy and neatly architectured.

Spring provides class org.springframework.web.portlet.DispatcherPortlet which is to be difined in portlet.xml.
  <portlet>  
     <description xml:lang="en">Portlet Description</description>  
     <portlet-name>portlet-name</portlet-name>  
     <display-name xml:lang="en">Portlet Display Name</display-name>  
     <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>  
     <init-param>  
       <name>contextConfigLocation</name>  
       <value>/WEB-INF/appContext.xml</value>  
     </init-param>  
     <expiration-cache>0</expiration-cache>  
     <supports>  
       <mime-type>text/html</mime-type>  
       <portlet-mode>VIEW</portlet-mode>  
     </supports>  
     <portlet-info>  
       <title>Portlet Name</title>  
       <short-title>Portlet Name</short-title>  
       <keywords>Spring Portlet MVC</keywords>  
     </portlet-info>  
   </portlet>   

The init param contextConfigLocation defines the application context XML file to be loaded for this portlet. This file has the spring bean definition for classes implementing Controller interface OR annotated classes with @Controller.

Annotated Controller class is as follows,
 @Controller  
 @RequestMapping  
 public class MyController {  
   
     private static final Logger _LOG = Logger.getLogger(MyController.class);  
       
     //Default render method will call this method...  
     @SuppressWarnings("unchecked")  
     @RequestMapping({"VIEW","/demoportlet/jsp2.do"})  
     public Object defaultRender(Model model, PortletRequest request,RenderResponse response) {  
         response.setContentType("text/html; charset=UTF-8");  
         String action = request.getParameter("action");  
         if("action1".equals(action)) {  
              ...  
             return "demoportlet/jsp1";  
         } else {              
              ...  
             return "demoportlet/jsp2";  
         }  
     }  
       
     // Direct request mapping based on action parameter value = 'someAction'  
     @RequestMapping(params = "action=someAction")   
     public Object actionOne(RenderRequest actionRequest, RenderResponse actionResponse) throws Exception {  
         actionResponse.setContentType("text/html; charset=UTF-8");  
          ...          
         return "demoportlet/jsp2";  
     }  
 }  
   

In web.xml we need to define a servlet org.springframework.web.servlet.DispatcherServlet and org.springframework.web.servlet.ViewRendererServlet for JSP resolving as follows,

<servlet>  
     <servlet-name>view-servlet</servlet-name>  
     <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>  
     <load-on-startup>1</load-on-startup>  
  </servlet>  
    
  <servlet>  
  <servlet-name>normal</servlet-name>  
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  <load-on-startup>1</load-on-startup>  
  </servlet>   
   
  <servlet-mapping>  
  <servlet-name>normal</servlet-name>  
  <url-pattern>*.do</url-pattern>  
  </servlet-mapping>   
    
  <servlet-mapping>  
     <servlet-name>view-servlet</servlet-name>  
      <url-pattern>/WEB-INF/servlet/view</url-pattern>  
  </servlet-mapping>  

In order for DispatcherPortlet rendering to work, you must declare an instance of the ViewRendererServlet .

In the Portlet MVC framework, each DispatcherPortlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext. These inherited beans can be overridden in the portlet-specific scope, and new scope-specific beans can be defined local to a given portlet instance.

The DispatcherServlet will look for normal-servlet.xml as we declared its name as 'normal'. This file will contain the definitions for beans which are needed to handle the portlet scanning, annotation handling etc... So first of all, the request will come to DispatcherServlet as its Spring MVC, and then it will scan for portlet controllers using spring's <context:component-scan> tag.

We also need to define the context param contextConfigLocation in web.xml which is loaded by DispatcherServlet on server startup. This will have all the beans required by portlets.

 <context-param>  
  <description>Spring Context XML location</description>  
  <param-name>contextConfigLocation</param-name>  
  <param-value>classpath:conf/spring/global.xml</param-value>  
  </context-param>  

The normal-servlet.xml will look like follows, where we need to define beans for annotation handling.
 <beans xmlns="http://www.springframework.org/schema/beans"  
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
     xmlns:context="http://www.springframework.org/schema/context"  
     xmlns:util="http://www.springframework.org/schema/util"  
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">  
           
     <context:annotation-config />          
   
     <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">  
       <property name="cookieName" value="lang"/>  
       <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->  
       <property name="cookieMaxAge" value="100000"/>  
     </bean>  
   
     <bean id="localeChangeInterceptor"  
    class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">  
       <property name="paramName" value="locale"/>  
     </bean>  
       
     <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">  
         <property name="interceptors">  
             <list>  
                 <ref bean="localeChangeInterceptor"/>  
             </list>  
         </property>  
         <property name="order" value="10" />  
     </bean>    
     
   <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>  
       
     <context:component-scan base-package="com.spring.controller" />      
 </beans>  
   

The <context:component-scan> will finds the controller classes from given package which are annotated with @Controller.

Other than is, we need to enter portlet information in portal framework's configuration files in which we want to deploy and use this portlet. When we add portlet from the portal, the controller methods will get call.

Wednesday, July 28, 2010

Display tag - Opensource JSP paging library...


Paging is important part when we have much data to show at once. We need to do all hectic coding for paging, like keep the total count, then count total pages to be shown, keep total records per page etc... This is nothing new but repeating and it takes bit time. So why not use some lib which gives you all of this with basic tags?

Note from the homepage : "The display tag library is an open source suite of custom tags that provide high-level web presentation patterns which will work in an MVC model. The library provides a significant amount of functionality while still being easy to use."

Basic steps to use are:

1. Get the jar file from http://displaytag.sourceforge.net and put it in classpath.

2. In your JSP page write following import for taglib.

 <%@ taglib uri="http://displaytag.sf.net" prefix="display" %>  

3. Check the following code for displaying the list of Objects as table with paging. You can configure different propeties of table as below, also can define CSS classes for diff. tags.


 <display:table name="lstObject" defaultsort="1" pagesize="20" sort="external" partialList="true"   
     size="totalCount" uid="lstObject" id="myObject"  
             export="false" requestURI="<%=someUrl%>" class="data">   
               
             <!-- For detailed understanding for each property, check the link http://displaytag.sourceforge.net/1.2/configuration.html -->  
             <display:setProperty name="paging.banner.placement" value="both" />  
             <display:setProperty name="paging.banner.group_size" value="10" />      
             <display:setProperty name="paging.banner.no_items_found">  
                 <span class="lfloat">No {0} found.</span>  
             </display:setProperty>  
             <display:setProperty name="paging.banner.one_item_found">  
                 <span class="lfloat">One {0} found.</span>  
             </display:setProperty>              
             <display:setProperty name="paging.banner.all_items_found">  
                 <span class="lfloat">{0} {1} found, displaying all {2}.</span>  
             </display:setProperty>  
             <display:setProperty name="paging.banner.some_items_found">  
                 <span class="lfloat">{0} {1} found, displaying {2} to {3}.</span>  
             </display:setProperty>              
             <display:setProperty name="paging.banner.full">  
                 <div class="paging_footer">  
                     <div class="rfloat">  
                         <div class="lfloat" style="padding-top:6px;">[<a href="{1}">First</a> | <a href="{2}">Prev</a>]</div>  
                         <div class="paging_wrap lfloat">                          
                             {0}                          
                         </div>  
                         <div class="rfloat" style="padding-top:6px;">[<a href="{3}">Next</a> | <a href="{4}">Last</a>]</div>  
                     </div>  
                 </div><br>  
             </display:setProperty>  
             <display:setProperty name="paging.banner.first">  
                 <div class="paging_footer">  
                     <div class="rfloat">  
                         <div class="lfloat" style="padding-top:6px;">[First | Prev]</div>  
                         <div class="paging_wrap lfloat">  
                              {0}  
                         </div>  
                         <div class="rfloat" style="padding-top:6px;">[<a href="{3}">Next</a> | <a href="{4}">Last</a>]</div>  
                     </div>  
                 </div><br>  
             </display:setProperty>          
             <display:setProperty name="paging.banner.last">                  
                 <div class="paging_footer">  
                     <div class="rfloat">  
                         <div class="lfloat" style="padding-top:6px;">[<a href="{1}">First</a> | <a href="{2}">Prev</a>]</div>  
                         <div class="paging_wrap lfloat">                          
                             {0}  
                         </div>  
                         <div class="rfloat" style="padding-top:6px;">[Next | Last]</div>  
                     </div>  
                 </div><br>  
             </display:setProperty>  
             <display:setProperty name="paging.banner.onepage" value="" /> <!-- Because dont want to show any value if only one page...-->  
               
             <display:setProperty name="paging.banner.page.selected">  
                 <div class="lfloat">  
                     <a onclick="return false;" class="active" href="javascript:void(0);">{0}</a>  
                 </div>  
             </display:setProperty>                      
             <display:setProperty name="paging.banner.page.link">  
                 <div class="lfloat">  
                     <a href="{1}" title="Goto page {0}">{0}</a>  
                 </div>  
             </display:setProperty>  
               
             <display:setProperty name="paging.banner.page.separator" value=" " />    <!-- Seperator between page numbers ,, i.e this will show "[First | Prev] 1 2 3 [Last | Next]"-->          
             <% MyObject myObject = (MyObject)pageContext.getAttribute("myObject"); %> <!-- Get the object from pageContext, which is writter in 'id' attribute -->  
              <display:column title="My title" class="first">  
              <!-- An y HTMl or jsp scriptlet can be used here... -->  
              </display:column>  
              <display:column property="name" title="2nd Column"/>  
              <display:column title="3rd Column"><%= myObject.getSomeValue() %></display:column>  
              ...               
         </display:table>  

4. The displaytag table can be used as follows. Where,

- pagesize is total records per page

- sort="external" means list given is sorted

- size defined the total records for which paging will be done

- name defines the list object for which the table will be generated

- id means internal object for each row which can be used in JSP scriptlets to do some data manipulations.

 <display:table name="lstObject" defaultsort="1" pagesize="20" sort="external" partialList="true"   
     size="totalCount" uid="lstObject" id="myObject"  
             export="false" requestURI="<%=someUrl%>" class="data">  
             ...  
             </display:table>  

5. You can set any property for display tag as below. You can also write HTML inside the property tag. For detailed understanding for each property this library supports, check the link - http://displaytag.sourceforge.net/1.2/configuration.html

 <display:setProperty name="paging.banner.page.separator" value=" " />

Wednesday, June 9, 2010

Liefray: Using JSONObject for Ajax in Plugin Portlet...

Many times we face situations when we need to use Ajax call from JSP pages. Using jQuery, its very easy to call any JSP through AJAX. You can just call the JSP and show its response from callback into some DOM element.

 $.ajax({  
      type: 'POST',  
      url: getUrl(),  
      data: {},                                   
      success: function(returnData) {  
          $('#someElementId').html(returnData);  
      }  
     });  

But this is useful when you want some big data OR some complex UI layout which is hard to generate using callback javascript function. Simple we call the JSP in which we have already designed the layout properly so we don't need to bother about manipulating the returnData in javascript.

Now think if you only need some small amount of data from server side, how will go? Making a new JSP for such a small task is not worhty i think. One technology is there for our help,JSON. JSON is a simple string notation of any object, which represents key-value pair like hashmap but uses its own structure to define objects.

Liferay provides com.liferay.portal.struts.JSONAction class which is a struts Action class. But to use this one in portlet we need to configure the portlet as a com.liferay.portlet. But there is one more way to use JSON with plugin portlet, using overriding serveResource method from GenericPortlet class into your portlet class.

 protected void getSomeDataAsJson(ResourceRequest request, ResourceResponse response)  
 throws PortletException, IOException {  
     String myParam = ParamUtil.getString(request, "myParam","");  
     // Some service call using params from request...  
     Obj obj = getDataFromSomeService(myParam);  
     JSONObject jsonObj = JSONFactoryUtil.createJSONObject();  
     // obj can be anything... Integer,Boolean,String or some custom POJO...  
     jsonObj.put("testData", obj);  
     OutputStream os = response.getPortletOutputStream();  
     try {  
         os.write(jsonObj.toString().getBytes());  
     }  
     finally {  
         os.close();  
     }  
 }  
 @Override  
 public void serveResource(ResourceRequest request, ResourceResponse response)  
         throws PortletException, IOException {  
     String cmd = ParamUtil.getString(request,Constants.CMD);  
     if ("getSomeDataAsJson".equals(cmd)) {  
         getSomeDataAsJson(request, response);  
     }  
 }  

The above method will provide the JSON object as a result in javascript callback function. Check the Ajax call,

 $("input:button[@name='<portlet:namespace />SomeThing']").click (function() {  
     $('#<portlet:namespace />myDiv').text("");  
     //URL which goes to your portlet...  
     var url = "<portlet:resourceURL><portlet:param name='<%=Constants.CMD %>' value='getSomeDataAsJson' /></portlet:resourceURL>";      
     //Append your hidden input fields in the resourceURL...   
     url +="&"+$('input:hidden').serialize();   
     $.ajax({  
             url: url,  
             data: {},  
             dataType: 'json',  
             success: function(message) {  
                     $('#<portlet:namespace />myDiv').text(message.testData);  
                     }  
             }  
         );      
     });  

This way we don't need to create JSPs for small data fetching. Show other ways also using simple AJAX calling (Like DWR supports, direct calling JAVA class method from JS and returns data in JS callback).

Friday, June 4, 2010

Javascript-ajax RSS feed reader - using jQuery with jGFeed ...

Check the new version of Ajax feed reader, which is using jQuery with jGFeed.

 <html><head>  
 <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>  
 <script type= "text/javascript" language='javascript'>  
 //<![CDATA[  
 (function($) {  
   $.extend( {  
 jGFeed : function(url, fnk, num, key) {  
       // Make sure url to get is defined  
       if (url == null) return false;  
       // Build Google Feed API URL  
       var gurl = "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=?&q="+url;  
       if (num != null) gurl += "&num="+num;  
       if (key != null) gurl += "&key="+key;  
       // AJAX request the API  
       $.getJSON(gurl, function(data) {  
         if (typeof fnk == 'function')  
           fnk.call(this, data.responseData.feed);  
         else  
           return false;  
       });  
     }  
   });  
 })(jQuery);  
 var strArr = ['http://www.thinkdigit.com/forum/external.php?type=RSS','http://www.snapfiles.com/feeds/sf20fw.xml',  
        'http://www.download.com/3410-2001-0-10.xml','http://www.download.com/3412-2001-0-10.xml',  
        'http://www.download.com/3412-2003-0-25.xml','http://www.videohelp.com/rss/tools',  
        'http://www.videohelp.com/rss/forum','http://www.pcstats.com/rss/rss.xml','http://feeds.feedburner.com/Techdudes',  
        'http://feeds.feedburner.com/jQueryHowto'];  
 var isXml = true;  
 function loadXml() {  
   var index = Math.floor(Math.random()*strArr.length);  
   getJsonData(strArr[index],'targetDiv');  
 }  
 //loadXml();  
 function getJsonData(dataSource, divId) {  
   $.jGFeed(dataSource,  
   function(feeds) {  
     if (!feeds) {  
       alert('there was an error');  
     }  
     var html = "";  
     for (var i=0;i<feeds.entries.length;i++) {  
       var entry = feeds.entries[i];  
       var title = entry.title;  
       var link = entry.link;  
       var description = entry.contentSnippet;  
       var pubDate = entry.publishedDate;  
       html += "<div class='entry'><h4 class='postTitle'><a class='fancy block' href='" + link + "' target='_blank'>" + title + "</a></h4>";  
       // html += "<em class='date'>" + pubDate + "</em>";  
       var categories = " Lables : ";  
       if (entry.categories && entry.categories.length>0) {  
         categories += entry.categories[0];  
         for (var j=1;j<entry.categories.length;j++) {  
           categories +=", " +entry.categories[j];  
         }  
         html += "<p><b>"+categories+"</b></p>";  
       }  
       html += "<p class='description'>" + description + "</p></div>";  
     }  
     var btn = "<input type='button' value='Reload' onclick='getJsonData(\""+dataSource +"\",\""+divId+"\");' /></br>";  
     $('#'+divId).html(btn + html);  
   },  
   50);  
 }  
 function cleartDiv(divId) {  
   var obj = document.getElementById(divId);  
   if (obj) {  
     //obj.style.display = 'none';  
     obj.innerHTML="";  
   }  
 }  
 function getNewData(url,divId) {  
   if (url!=0) {  
     if (!url) {  
       var index = Math.floor(Math.random()*strArr.length);  
       getJsonData(strArr[index],divId);  
     } else {  
       getJsonData(url,divId);  
     }  
   }  
 }  
 //window.setInterval("getNewData()", 7000); // update the data every 20 mins  
 //]]>  
 </script>  
 </head>  
 <body onload="getNewData('http://www.videohelp.com/rss/tools','targetDiv');">  
     <select onchange="javascript:getNewData(this.options[this.selectedIndex].value,'targetDiv');" size="9" style="width:25%;">  
         <option value='0' selected="selected">Please Select</option>  
         <option value='http://www.thinkdigit.com/forum/external.php?type=RSS'>ThinkDigit.com</option>  
         <option value='http://www.snapfiles.com/feeds/sf20fw.xml'>SnapFiles.com</option>  
         <option value='http://www.articlecity.com/xml/main.xml'>ArticleCity.com</option>  
         <option value='http://www.download.com/3410-2001-0-10.xml'>Download.com populars</option>  
         <option value='http://www.download.com/3412-2001-0-10.xml'>Download.com New</option>  
         <option value='http://www.download.com/3412-2003-0-25.xml'>Download.com 25 New Titles</option>  
         <option value='http://feeds.feedburner.com/Techdudes'>TechDudes</option>  
     </select>  
     <div id="linkDiv" style="width:98%;height:25px;overflow:auto;"/>  
     <div id="targetDiv" style="border:dashed 1px;background:#fafafa;width:70%;height:450px;overflow:auto;font-family:verdana;font-size:10px;"/>  
 </body>  
 </html> 
 
jGFeed: http://plugins.jquery.com/project/jgfeed 

Wednesday, June 2, 2010

jQuery - usage of $.bind method...

jQuery is widely used javascript library which provides easy ways to do complex things. By using simple Javscript, it takes too much time to code it, but jQuery simple it down to few lines.

To check multiple Checkboxes, i have written the following code.

 <script type="text/javascript" src="html/js/jquery.js"></script>  
 <script type="text/javascript">  
 //on load, register events for checkboxes...  
 $(document).ready(  
      function() {       
           $('#chk_all').change(checkAll);            
           $('form[@name="fm"] input:checkbox[@name!="chk_all"]').each (function(index) {  
                $(this).change(selectForDelete);  
           });  
      }  
 );  
 function checkAll(){  
      var isCheck = this.checked;  
      $('form[@name="fm"] input:checkbox[@name!="chk_all"]').each(function(){  
           this.checked = isCheck;  
           $(this).trigger("change");  
      });  
 }  
 function selectForDelete(){  
      var alertIdsObj = $('form[@name="fm"] input[@name="deleteIds"]');  
      if (this.checked) {  
           if (alertIdsObj.val().indexOf(this.value)==-1) {  
                if(alertIdsObj.val()=='') {  
                     alertIdsObj.val(''+this.value);  
                } else {  
                     alertIdsObj.val(alertIdsObj.val()+","+this.value);  
                }  
           }  
      } else {  
           if (alertIdsObj.val().indexOf(this.value)>=0) {  
                alertIdsObj.val(alertIdsObj.val().replace(this.value+',',''));  
                alertIdsObj.val(alertIdsObj.val().replace(','+this.value,''));  
                alertIdsObj.val(alertIdsObj.val().replace(this.value,''));  
           }  
      }  
 }  
 //Function called for item details...  
 function viewAlertDetails(alertId) {  
           $.ajax({  
            type: 'POST',  
            url: getUrl()+"&alertId="+alertId+"&cmd=detailed-market-alerts",  
            data: {},                                           
            success: function(returnData) {  
                 $('#-mainDiv').hide();  
                 $('#-detailsDiv').html(returnData);  
                 $('#-detailsDiv').show();       
            }  
           });       
 }  
 function getUrl() {  
      var url = new String(document.location);  
      var index = url.indexOf("?");  
      if (index!=-1) {  
           url = url.substring(0,index);  
      }  
      return url;  
 }  
 //function called from second page to go back to first page with latest data...  
 //Repalces the old content with new  
 function viewAlerts() {  
      $.ajax({  
            type: 'POST',  
            url: getUrl(),  
            data: {},                                           
            success: function(returnData) {       
                var myData = $(returnData).find('#-mainDiv').html();  
                $('#-mainDiv').html(myData);  
                $('#chk_all').bind('change', checkAll);  
                $('form[@name="fm"] input:checkbox[@name!="chk_all"]').each (function(index) {            
                     $(this).bind('change',selectForDelete);            
                });  
                $('#-detailsDiv').html("");  
                $('#-detailsDiv').hide();  
                $('#-mainDiv').show();  
                return true;  
            }  
           });       
      return false;  
 }  
 </script>  

The flow is as below,
- First page shows list of data.
- By clicking particular item from the list, i hides the mainDiv and shows details of the item in detailsDiv using $.ajax.
- By clicking "Back" button from detailsDiv, i again loads the main page using $.ajax.

The problem is, when i go to second page and comes on the first page using $.ajax,i change the HTML content of mainDiv with the latest data in viewAlerts() javascript function as $('#-mainDiv').html(myData);.

If you check the script, you will notice that i have registered the event handling for checkboxes in the document,One for main checkbox and one for all other checkboxes of the list, using $(document).ready() jQuery function. Event handling works fine at first page. But when we change the html content of the div afterwards, event handler automatically removed.

So to re-enable the events on checkboxes,we need to use following code in viewAlerts() javascript function.

 $('#chk_all').bind('change', checkAll);  
 $('form[@name="fm"] input:checkbox[@name!="chk_all"]').each (function(index) {  
      $(this).bind('change',selectForDelete);            
 });  

$(jQueryDomObj).bind(eventName,function) is very useful if you want to manipulate the element contents using javascript.