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.

Tuesday, June 1, 2010

HibernateDaoSupport with HibernateTemplate - defaultAutoCommit set false...

I am working on a web application which needs hibernate for DB integration. So i decided to use hibernate with spring. As we all know that Spring is very lightweight and supports hibernate in an easy way. Also i am used to spring JdbcDaoSupport for DB integration and StoredProcedure class for SQL Server stored procedures.

While working this time i decided to use HibernateDaoSupport. Your DAO class must extend this class to use HibernateTemplate class.

Steps to make DAO using this,

- Extend HibernateDaoSupport and also an interface if you have.
- Define your methods which provides different Database operations (CREATE, DELETE, FETCH, UPDATE, INSERT etc...)
- Define required beans as below,

 <bean id="dataSource"  
 class="org.apache.commons.dbcp.BasicDataSource">  
  <property name="driverClassName">  
   <value>com.mysql.jdbc.Driver</value>  
  </property>  
  <property name="url">  
   <value>jdbc:mysql://localhost/testdb</value>  
  </property>  
  <property name="username" value="username" />  
  <property name="password" value="passwordforit" />  
  <property name="defaultAutoCommit" value="true" />  
  <property name="defaultTransactionIsolation" value="2" />  
  <property name="maxActive" value="100" />  
  <property name="maxIdle" value="50" />  
  <property name="minIdle" value="50" />  
  <property name="maxWait" value="100" />  
  <property name="initialSize" value="20" />  
 </bean>  
 <!-- Database Property -->  
 <bean id="hibernateProperties"  
 class="org.springframework.beans.factory.config.PropertiesFactoryBean">  
  <property name="properties">  
   <props>  
    <prop key="hibernate.hbm2ddl.auto">update</prop>  
    <prop key="hibernate.dialect">  
    org.hibernate.dialect.MySQLDialect</prop>  
    <prop key="hibernate.show_sql">true</prop>  
    <prop key="hibernate.cache.provider_class">  
    net.sf.ehcache.hibernate.EhCacheProvider</prop>  
   </props>  
  </property>  
 </bean>  
 <!-- Hibernate SessionFactory -->  
 <bean id="sessionFactory"  
 class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
  <property name="dataSource">  
   <ref local="dataSource" />  
  </property>  
  <property name="hibernateProperties">  
   <ref bean="hibernateProperties" />  
  </property>  
  <!-- OR mapping files. -->  
  <property name="mappingResources">  
   <list>  
    <value>myMapping.hbm.xml</value>  
   </list>  
  </property>  
 </bean>  
 <bean id="MyDAOTarget" class="com.dao.MyDAO">  
  <property name="sessionFactory">  
   <ref bean="sessionFactory" />  
  </property>  
 </bean>  

- Define transactionManager for transaction managment as below,

 <bean id="transactionManager"  
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  <property name="dataSource">  
   <ref bean="dataSource" />  
  </property>  
 </bean>  

- Define org.springframework.transaction.interceptor.TransactionProxyFactoryBean as below, which will eventually make proxy bean on the class given in target property.

 <bean id="MyDAO" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">   
   <property name="transactionManager">   
     <ref bean="transactionManager"/>  
   </property>   
   <property name="target">   
     <ref bean="MyDAOTarget"/>  
   </property>   
   <property name="transactionAttributes">  
     <props>   
       <prop key="insert*">PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>  
       <prop key="update*">PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>   
       <prop key="delete*">PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>  
       <prop key="find*">PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED, readOnly</prop>   
     </props>   
   </property>  
 </bean>  


- Use getHibernateTemplate().get(Class entityClass,Serializable id) OR getHibernateTemplate().load(Class entityClass,Serializable id) to load particular enoty from Database.
- Use getHibernateTemplate().find(String queryString,Object value) to find entities using HQL/SQL query.
- Use getHibernateTemplate().delete(), getHibernateTemplate().deleteAll() for removing entities from Database.
- Use getHibernateTemplate().update() OR getHibernateTemplate().saveOrUpdate() for updating/inserting entity in the Database.

It seems so easy right? But no, wait! All these works fine when you have <property name="defaultAutoCommit" value="true" /> . When you change this property to false, all updates to database will not take effect. Because in that case we need to begin and commit transaction form the code.

I have tried using getHibernateTemplate() for update operations but it failed. It doesn't work with transactions. So try as per the following method.

 public void updateMyObj(MyObj myObj) {  
   Session session = getSession(true);// true means allow create new session if required.  
   Transaction trans = session.beginTransaction();  
   session.saveOrUpdate(myObj);  
   trans.commit();  
 }  

Same thing works for any update operation. This is really strange, i tried for one whole day using getHibernateTemplate() but it never worked with <property name="defaultAutoCommit" value="false" />.