Pages

Showing posts with label struts 2. Show all posts
Showing posts with label struts 2. Show all posts

Thursday, April 26, 2012

How to use jQuery grid with struts 2 without plugin ?

When using jQuery with struts 2, the developers are persuaded to use struts2-jQuery plug-in. Because most of the forums and other Internet resources support struts2 jQuery plug in. I have this experience. I wanted to use jQuery grid plug-in with struts 2,but without using struts2 jQuery plug-in. It was very hard for me to find a tutorial or any good resource to implement struts 2 action class to create the jQuery grid without using struts2 jQuery plug-in. Finally, I got through this by myself and intended to post for your convenience.
This tutorial explains, How to create jQuery grid with struts2 without using the plug-in. I filtered this code out from my existing project. The architecture of the project is based on strts2, spring and hibernate integrated environment. I am sure, You can customise these code so that it suits to your environment.

Step 01: Creating entity class for 'Province' master screen. 

I use JPA as a persistence technology and hibernate data access support given by spring (HibernateDaoSupport). I am not going to explain these stuff in detail. My major concern is, How to create the struts 2 action class that supports jQuery grid. Here is my entity class.

Province.java 

/**
 * 
 */
package com.shims.model.maintenance;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

import com.shims.model.Audited;

/**
 * @author Semika Siriwardana
 *
 */
 @Entity
 @Table(name="PROVINCE") 
 public class Province extends Audited implements Serializable {

 private static final long serialVersionUID = -6842726343310595087L;
 
 @Id
 @SequenceGenerator(name="province_seq", sequenceName="province_seq")
 @GeneratedValue(strategy = GenerationType.AUTO, generator = "province_seq")
 private Long id;
 
 @Column(name="description", nullable = false)
 private String name;
 
 @Column(name="status", nullable = false)
 private char status;
 
 /**
  * 
  */
 public Province() {
      super();
 }

 /**
  * @param id
  */
 public Province(Long id) {
      super();
      this.id = id;
 }

 /**
  * @return the id
  */
 public Long getId() {
      return id;
 }

 /**
  * @param id the id to set
  */
 public void setId(Long id) {
      this.id = id;
 }

 /**
  * @return the name
  */
 public String getName() {
      return name;
 }

 /**
  * @param name the name to set
  */
 public void setName(String name) {
      this.name = name;
 }

 /**
  * @return the status
  */
 public char getStatus() {
      return status;
 }

 /**
  * @param status the status to set
  */
 public void setStatus(char status) {
      this.status = status;
 }
}

Step 02: Creating JSP file for 'Province' master screen grid.

Keep in mind that jQuery grid is a plug-in for jQuery. So You need to download the relevant CSS file and JS file for the jQuery grid plug-in. You may need to include following resources in the head part of the JSP file.

<link type="text/css" rel="stylesheet" media="screen" href="<%=request.getContextPath()%>/css/jquery/themes/redmond/jquery-ui-1.8.16.custom.css">
<link type="text/css" rel="stylesheet" media="screen" href="<%=request.getContextPath()%>/css/ui.jqgrid.css">
<script src="<%=request.getContextPath()%>/js/jquery-1.6.2.min.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/grid.locale-en.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/jquery.jqGrid.src.js" type="text/javascript"></script>
<script src="<%=request.getContextPath()%>/js/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>

Then, We will create the required DOM contents in JSP file to render the grid. For this, You need only a simple TABLE and DIV elements placed with in the JSP file with the given ID's as follows.

<table id="list"></table> 
<div id="pager"></div>

The 'pager' DIV tag is needed to render the pagination bar of the jQuery grid.

Step 03: Creating JS file for 'Province' master screen grid. 

jQuery grid is needed to be initiated with javascript. I am going to initiate the grid with page on load. There are so many functionalities like adding new records, updating records, deleting records, searching supported by jQuery grid. I guess, You can familiar with those stuff, If You can create the initial grid. This javascript contains only the code that initiating the grid.

var SHIMS = {}
var SHIMS.Province = {
 
 onRowSelect: function(id) {
      //Handle event
 },
 
 onLoadComplete: function() {
      //Handle grid load complete event.
 },

 onLoadError: function() {
      //Handle when data loading into grid failed. 
 },

 /**
  * Initialize grid
  */
 initGrid: function(){
  
  jQuery("#list").jqGrid({ 
         url:CONTEXT_ROOT + '/secure/maintenance/province!search.action', 
         id:"gridtable", 
         caption:"SHIMS:Province Maintenance",
         datatype: "json", 
         pager: '#pager', 
         colNames:['Id','Name','Status'], 
         pagerButtons:true,
         navigator:true,
         jsonReader : {
                      root: "gridModel", 
                      page: "page",
                      total: "total",
                      records: "records",
                      repeatitems: false,      
                      id: "0"      
         }, 
         colModel:[ {
                     name:'id',
                     index:'id', 
                     width:200, 
                     sortable:true, 
                     editable:false, 
                     search:true
                    },{
                     name:'name',
                     index:'name', 
                     width:280, 
                     sortable:true, 
                     editable:true, 
                     search:true, 
                     formoptions:{elmprefix:'(*)'},
                     editrules :{required:true}
                    },{
                     name:'provinceStatus',
                     index:'provinceStatus', 
                     width:200, 
                     sortable:false, 
                     editable:true, 
                     search:false, 
                     editrules:{required:true}, 
                     edittype:'select', 
                     editoptions:{value:'A:A;D:D'}
                 }], 
         rowNum:30, 
         rowList:[10,20,30], 
         width:680,
         rownumbers:true,
         viewrecords:true, 
         sortname: 'id', 
         viewrecords: true, 
         sortorder: "desc", 
         onSelectRow:SHIMS.Province.onRowSelect, 
         loadComplete:SHIMS.Province.onLoadComplete,
         loadError:SHIMS.Province.onLoadError,
         editurl:CONTEXT_ROOT + "/secure/maintenance/province!edit.action" 
         });
 },
 
 /**
  * Invoke this method with page on load.
  */
        
 onLoad: function() {
     this.initGrid();
 }
};

I wish to highlight some code snips from the above code. The 'jsonReader' attribute of the grid initialisation object was the key point where it was difficult to find and spent plenty of times to make the grid work.

root - This should be list of objects.
page - Current page number.
total - This is the total number of pages. For example, if You have 1000 records and the page size is 10, the 'total' value will be 100.

records - The total number of records or count of records.

If You are creating grid with JSON data, the response of the specified 'url' should be JSON response in this format. Sample JSON response is shown bellow.

{"gridModel":[
          {"id":15001,"name":"Western","provinceStatus":"A"},
          {"id":14001,"name":"North","provinceStatus":"A"},
          {"id":13001,"name":"North Central","provinceStatus":"A"},
          {"id":12002,"name":"East","provinceStatus":"A"},
          {"id":12001,"name":"Southern","provinceStatus":"A"}
             ],
"page":1,
"records":11,
"rows":30,
"sidx":"id",
"sord":"desc",
"total":2}

Above fields should be declared in the action class and updated appropriately. I will come to that later. If You intend to use operations like add record, delete record, update record, search etc, You must specify 'editurl'.

With in the JSP file, just above the close body tag, I have placed the following script to call the grid initialisation code.

 var CONTEXT_ROOT = "<%=request.getContextPath()%>";
 jQuery(document).ready(function(){
      SHIMS.Province.onLoad();
 }); 
Step 04: Creating Struts2 action class for 'Province' master screen grid. 

This is the most important part of this tutorial.

ProvinceAction.java 

/**
 * 
 */
package com.shims.web.actions.maintenance.province;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.opensymphony.xwork2.ModelDriven;
import com.shims.dto.Page;
import com.shims.dto.ProvinceDto;
import com.shims.model.maintenance.Province;
import com.shims.service.maintenance.api.ProvinceService;
import com.shims.support.SHIMSSoringSupport;
import com.shims.web.actions.common.BaseAction;
import com.shims.web.common.WebConstants;

/**
 * @author Semika Siriwardana
 *
 */
 @Controller
 @Namespace(WebConstants.MAINTENANCE_NAMESPACE) 
 @ParentPackage(WebConstants.MAINTENANCE_PACKAGE)
 @Results({@Result(name=ProvinceAction.SUCCESS, location="/jsp/maintenance/province.jsp"), 
       @Result(name = "json", type = "json")}) 
 public class ProvinceAction extends BaseAction<Province> implements ModelDriven<Province> {

 private static final long serialVersionUID = -3007855590220260696L;

 private static Logger logger = Logger.getLogger(ProvinceAction.class);
 
 @Autowired
 private ProvinceService provinceService;
 
 private List<Province> gridModel = null;
 
 private Province model = new Province();
 
 private Integer rows = 0;
 private Integer page = 0;
 private String sord;
 private String sidx;
 private Integer total = 0;
 private Integer records = 0;
 
 @Override
 public String execute() {
      return SUCCESS;
 }
 
 /**
  * Search provinces
  * @return
  */
 public String search() throws Exception {
  
      Page<Province> resultPage = provinceService.findByCriteria(model, getRequestedPage(page));
      List<Province> provinceList = resultPage.getResultList(); 
      setGridModel(provinceList); 
      setRecords(resultPage.getRecords());
      setTotal(resultPage.getTotals());
   
      return JSON;
 }

 /**
  * @return the gridModel
  */
 public List<Province> getGridModel() {
      return gridModel;
 }

 /**
  * @param gridModel the gridModel to set
  */
 public void setGridModel(List<Province> gridModel) {
      this.gridModel = gridModel;
 }

 /**
  * @return the page
  */
 public Integer getPage() {
      return page;
 }

 /**
  * @param page the page to set
  */
 public void setPage(Integer page) {
      this.page = page;
 }

 /**
  * @return the rows
  */
 public Integer getRows() {
      return rows;
 }

 /**
  * @param rows the rows to set
  */
 public void setRows(Integer rows) {
      this.rows = rows;
 }

 /**
  * @return the sidx
  */
 public String getSidx() {
      return sidx;
 }

 /**
  * @param sidx the sidx to set
  */
 public void setSidx(String sidx) {
      this.sidx = sidx;
 }

 /**
  * @return the sord
  */
 public String getSord() {
      return sord;
 }

 /**
  * @param sord the sord to set
  */
 public void setSord(String sord) {
      this.sord = sord;
 }

 /**
  * @return the total
  */
 public Integer getTotal() {
      return total;
 }

 /**
  * @param total the total to set
  */
 public void setTotal(Integer total) {
      this.total = total;
 }

 /**
  * @return the records
  */
 public Integer getRecords() {
      return records;
 }

 /**
  * @param records the records to set
  */
 public void setRecords(Integer records) {
      this.records = records;
 }

 @Override
 public Province getModel() {
      return model;
 }
 
}

The getRequestedPage() method is a generic method implemented with in the 'BaseAction' class which returns a 'Page' instance for a given type. 

BaseAction.java

/**
 * 
 */
package com.shims.web.actions.common;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.SessionAware;

import com.opensymphony.xwork2.ActionSupport;
import com.shims.dto.Page;
import com.shims.dto.security.UserDto;
import com.shims.web.common.WebConstants;

import flexjson.JSONSerializer;

/**
 * @author Semika Siriwardana
 *
 */
 public abstract class BaseAction<T> extends ActionSupport implements ServletRequestAware, SessionAware {

 private static final long serialVersionUID = -8209196735097293008L;
 
 protected static final Integer PAGE_SIZE = 10;

 protected HttpServletRequest request;
 
 protected Map<String, Object> session; 
 
 protected String JSON = "json";
 
 public abstract String execute(); 
 
 public HttpServletRequest getRequest() {
      return request;
 }

 @Override
 public void setServletRequest(HttpServletRequest request) {
      this.request = request;
 }

 protected void setRequestAttribute(String key, Object obj) {
      request.setAttribute(key, obj);
 }
 
 /**
  * Returns generic Page instance.
  * @param domain
  * @param employeeDto
  * @return
  */
 protected Page<T> getRequestedPage(Integer page){
      Page<T> requestedPage = new Page<T>(); 
      requestedPage.setPage(page);
      requestedPage.setRows(PAGE_SIZE);
      return requestedPage;
 }
 
 /**
  * @return the session
  */
 public Map<String, Object> getSession() { 
      return session;
 }

 /**
  * @param session the session to set
  */
 public void setSession(Map<String, Object> session) { 
      this.session = session;
 }
 
}

I already explained 'gridModel', 'page', 'total' and 'records'. 'sord' and 'sidx' which are referenced as 'sorting order' and 'sorting index', are passed by the jQuery grid when We sort the data in the grid with some column. To fetch those tow fields, We should declare it with in the action class and provide setter and getter methods. Later, We can sort our data list based on those tow parameters.

Step 05: Implementing service methods. 

From here onwards, most of the techniques are specific to my current project framework. I will explain those so that You can understand, How I developed the related service and DAO methods.
Since, jQuery grid supports for paging, It was need to have a proper way of exchanging grid information from front-end to back-end and then back-end to front-end. I implemented generic 'Page' class for this purpose.

/**
 * 
 */
package com.shims.dto;

import java.util.ArrayList;
import java.util.List;

/**
 * @author semikas
 *
 */
 public class Page<T> {

 /**
  * Query result list.
  */
 private List<T> resultList = new ArrayList<T>(); 
 
 /**
  * Requested page number.
  */
 private Integer page = 1;
 
 /**
  * Number of rows displayed in a single page.
  */
 private Integer rows = 10;
 
 /**
  * Total number of records return from the query.
  */
 private Integer records;
 
 /**
  * Total number of pages.
  */
 private Integer totals;

 /**
  * @return the resultDtoList
  */
 public List<T> getResultList() {
      return resultList;
 }

 /**
  * @param resultDtoList the resultDtoList to set
  */
 public void setResultList(List<T> resultList) {
      this.resultList = resultList;
 }

 /**
  * @return the page
  */
 public Integer getPage() {
      return page;
 }

 /**
  * @param page the page to set
  */
 public void setPage(Integer page) {
      this.page = page;
 }

 /**
  * @return the rows
  */
 public Integer getRows() {
      return rows;
 }

 /**
  * @param rows the rows to set
  */
 public void setRows(Integer rows) {
      this.rows = rows;
 }

 /**
  * @return the records
  */
 public Integer getRecords() {
      return records;
 }

 /**
  * @param records the records to set
  */
 public void setRecords(Integer records) {
      this.records = records;
 }

 /**
  * @return the totals
  */
 public Integer getTotals() {
      return totals;
 }

 /**
  * @param totals the totals to set
  */
 public void setTotals(Integer totals) {
      this.totals = totals;
 }
 
}

Also, with some search criteria, We can fetch the data and update the grid accordingly.

My service interface and implementation classes are as follows.

ProvinceService.java 

/**
 * 
 */
package com.shims.service.maintenance.api;

import java.util.List;

import com.shims.dto.Page;
import com.shims.exceptions.ServiceException;
import com.shims.model.maintenance.Province;

/**
 * @author Semika Siriwardana
 *
 */
public interface ProvinceService {

 /**
  * Returns list of provinces for a given search criteria.
  * @return
  * @throws ServiceException
  */
  public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws ServiceException;
 
}

ProvinceServiceImpl.java 

/**
 * 
 */
package com.shims.service.maintenance.impl;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shims.dto.Page;
import com.shims.exceptions.ServiceException;
import com.shims.model.maintenance.Province;
import com.shims.persist.maintenance.api.ProvinceDao;
import com.shims.service.maintenance.api.ProvinceService;


/**
 * @author Semika Siriwardana
 *
 */
 @Service
 public class ProvinceServiceImpl implements ProvinceService {

 @Autowired
 private ProvinceDao provinceDao; 
 
 /**
  * {@inheritDoc} 
  */
 @Override
 public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws ServiceException {
  
      Page<Province> resultPage = provinceDao.findByCriteria(searchCriteria, page); 
  
      return resultPage;
 }

}

I am using 'page' instance to exchange the grid information in between front end and back end.

Next, I will explain the other important part of this tutorial.

Step 06: Implementing data access method. 

In the DAO method, We should filtered only the records of the requested page by the user and also should be updated the 'page' instance attributes so that those should be reflected to the grid. Since, I am using hibernated, I used 'Criteria' to retrieve the required data from the database. You implement this in your way, But it should update the grid information properly.

ProvinceDao.java 
 
/**
 * 
 */
package com.shims.persist.maintenance.api;

import com.shims.dto.Page;
import com.shims.exceptions.DataAccessException;
import com.shims.model.maintenance.Province;
import com.shims.persist.common.GenericDAO;

/**
 * @author Semika Siriwardana
 *
 */
public interface ProvinceDao extends GenericDAO<Province, Long> { 

 /**
  * Returns search results for a given search criteria.
  * @param searchCriteria
  * @param page
  * @return
  * @throws DataAccessException
  */
  public Page<Province> findByCriteria(Province searchCriteria, Page<Province> page) throws DataAccessException;
 
}

ProvinceDaoImpl.java

/**
 * 
 */
package com.shims.persist.maintenance.impl;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.shims.dto.Page;
import com.shims.exceptions.DataAccessException;
import com.shims.model.maintenance.Province;
import com.shims.persist.maintenance.api.ProvinceDao;

/**
 * @author Semika Siriwardana
 *
 */
 @Repository
 public class ProvinceDaoImpl extends AbstractMaintenanceDaoSupport<Province, Long> implements ProvinceDao {

 @Autowired
 public ProvinceDaoImpl(SessionFactory sessionFactory) {
      setSessionFactory(sessionFactory);
 }

 /**
  * {@inheritDoc} 
  */
 @SuppressWarnings("unchecked")
 @Override
 public Page<Province> findByCriteria(ProvinceDto searchCriteria, Page<Province> page) throws SHIMSDataAccessException {
  
      Criteria criteria = getSession().createCriteria(Province.class);
  
      if (searchCriteria.getName() != null && searchCriteria.getName().trim().length() != 0) {
          criteria.add(Restrictions.ilike("name", searchCriteria.getName(), MatchMode.ANYWHERE)); 
   
      }
  
      //get total number of records first
      criteria.setProjection(Projections.rowCount());
      Integer rowCount = ((Integer)criteria.list().get(0)).intValue();
  
      //reset projection to null
      criteria.setProjection(null);
  
      Integer to = page.getPage() * page.getRows();
      Integer from = to - page.getRows();
  
      criteria.setFirstResult(from);
      criteria.setMaxResults(to); 
  
      //calculate the total pages for the query
      Integer totNumOfPages =(int) Math.ceil((double)rowCount / (double)page.getRows());
  
      List<Province> privinces = (List<Province>)criteria.list(); 
  
      //Update 'page' instance.
      page.setRecords(rowCount); //Total number of records
      page.setTotals(totNumOfPages); //Total number of pages
      page.setResultList(privinces);
  
      return page; 
 }
}

I think, This will be a good help for the developers who wish to use pure jQuery with struts2. If You find more related to this topic, please post bellow.

Sunday, December 25, 2011

Struts2 json plugin - Action class public method names with "get" prefix.

I recently figured out this, what I am going to explain you, and it can make a huge impact for an application. I am using struts 2 JSON plugin for an AJAX invocations to action class methods and to send a JSON response. Struts 2 JSON plugin serializes all the bean properties when sending a JSON response to the browser. Sometime, you may not know that, While serialization process, it invokes all the public method which are having “get” prefix in their method names. If we really don’t know about this, it can make a significant impact to our application’s performance and generates exceptions which are hard to figure out. I will explain how this can impact for an application. Look into the following two methods which are defined in ‘DepartmentAction’ class. Those tow method returns JSON response to the browser.

public String getAllEmployees() throws Exception {
    List<Employee> employees = departmentService.getDepartmentEmployees();
    setEmployees(employees);
    return JSON;
}
public String getActiveDepartmentsByLocationId() throws Exception { 
    List<Department> departmetns = departmentService.getActiveDepartmentsByLocationId(locationId);
    setDepartments(departmetns); 
    return JSON;
} 
Normally, When you want to get the list of departments for a given location id, you will invoke the method ‘getActiveDepartmentsByLocationId’ method with an AJAX request. When the function returns the JSON response, it serializes action class bean properties which causes to invoke "getAllEmployees" method also. That means, it invoke the relevant service method from “getAllEmployees” method also. But you only need to get the list of departments for a given location id. Can you guess the impact for the application?

If you put a break point in “getAllEmployees” method while invoking “getActiveDepartmentsByLocationId” method, You will understand this behavior. Normally, developers are used to give “get” prefix for action class methods. If you use struts2 JSON plugin, make sure not to use “get” prefix for public method names which are resulting JSON response. You can use “find” prefix instead.

Friday, October 22, 2010

Struts2 exception flow interceptor

The purpose of this post is to explain, How we can use struts 2 interceptor to bring exception messages into the view screen of your web application and be aware the user about those in meaning full way. There may be several ways of implementing this kind of functionality. My major purpose of this technique is to keep developer significantly free for taking exception messages to the view screen, and let the interceptor to do this instead, once We have added this feature into our system.

This is very straight forward method. Each struts 2 action is invoked from a interceptor's intercept method and catch the exception with in intercept method and send the message to the view. I am going to explain this technique for struts 2 action invocation via AJAX request. I named the interceptor as ExceptionFlowInterceptor. Let's see, How configure the interceptor in
struts.xml file.

<package name="autoac" extends="struts-default json-default" namespace="/secure">
<interceptors>
     <interceptor name="exceptionFlowInterceptor" class="com.semika.autoac.web.interceptors.ExceptionFlowInterceptor"></interceptor>
     <interceptor-stack name="autoac-stack">
           <interceptor-ref name="exceptionFlowInterceptor"></interceptor-ref
           <interceptor-ref name="defaultStack"></interceptor-ref>
     </interceptor-stack>
</interceptors>

<default-interceptor-ref name="autoac-stack"></default-interceptor-ref>

</package>

Better to give little explanation about the above configuration. I have declared a package called "autoac" which extends struts-default and json-default packages. Since I want to invoke actions in autoac package with ajax request, I have extended autoac package from json-default package. To know further about that, please read about struts 2 json plugin.

I have configured the interceptor so that each action in /secure name space is undergone interceptor's pre-processing and post processing.

Next, We will see the code for our interceptor. I am not going to put the whole source here. I just put, How intercept method behaves.

@Override
public String intercept(ActionInvocation invocation) throws Exception {
   String result = null;
   try{
       result = invocation.invoke();
   } catch(Exception e) {
       ValueStack vs = invocation.getStack();
       if (e.getMessage() == null) {
          vs.setValue("errorMessage", "Internal system faliure.Please contact system administrator.");
       } else {
          vs.setValue("errorMessage", e.getMessage());
       }
       vs.setValue("status", "fail");
       result = "json";
 }

 return result;
}


I guess, You have already got the point. I have invoked the action from intercept method and within in try catch block. Any of the exceptions raised after the action invocation, will be caught here. In case of an exception, You can catch the exception and send it into the view in nice way. In my case, I am handling status field at action's model and update that field accordingly. That is up to you to handle this point.

I have obtained the value stack and update the status field as an error. Struts 2 json plugin serializes the whole action and resulting json object which can be accessed with Javascript.

Monday, December 22, 2008

Creating custom result type - struts 2

Creating a custom result type comes to the field when your are trying
to develop a Ajax base development with struts 2. By default, struts 2 uses RequestDispatcher originated from servlets API, as the default result type. That is, after returning the control string from your action, this default result type will show the way for final page rendering.

Suppose you want to have some data response to the client, in stead of despatching to a JSP page after returning from your action class. Ajax request to the server often needs this kind of data.
Here, I am going to explain you, how to make a Ajax request to a struts 2 action class and how to use a custom result type to send some data back to the client, instead of rendering a another JSP page as usual. I am not going to give you a detailed description on Ajax related things. My major concern is how to create a custom result type and use it with Ajax development.

I have a list of User's names in a struts select box component and onchange event will make a request to a action class to get full details about the user to the client and update some segment of current rendered page of the browser as usual Ajax application does. I am passing userId as query string parameter to the action class.
Here is the javascript code the responsible to make the request to the server. I have encpsulated all related functions needed to make a request to the server, into one javascript object.

var PDA = {};
PDA.common = {};
PDA.common.ContentLoader = function(httpMethod,url,params,callBack){
     this.init(httpMethod,url,params,callBack);
}
PDA.common.ContentLoader.prototype = {
READY_STATE_UNINITIALIZED :0,
READY_STATE_LOADING :1,
READY_STATE_LOADED :2,
READY_STATE_INTERACTIVE :3,
READY_STATE_COMPLETE :4,
httpMethod : null,
url : null,
params : null,
callBack : null,
error : null,
request : null,

defaultError:function(){
      alert("error fetching data!"
      +"\n\nreadyState:"+this.request.readyState
      +"\nstatus: "+this.request.status
      +"\nheaders: "+this.request.getAllResponseHeaders());
},

getXMLHttpRequest: function(){
   if(window.XMLHttpRequest){
     this.request = new XMLHttpRequest();
   }else if(typeof ActiveXObject != "undefined"){
       try {
           this.request = new ActiveXObject("Msxml2.XMLHTTP");
       }catch(e) {
           try{
                  this.request = new ActiveXObject("Microsoft.XMLHTTP");
              } catch (e){
                    this.request = null;
              }
        }
   } 
},

onReadyState: function(){
 var req = this.request;
 var ready = req.readyState;
 if (ready == this.READY_STATE_COMPLETE){
     var httpStatus = req.status;
    if (httpStatus == 200 || httpStatus == 0){
         this.callBack.call(this);
     }else{
         this.error.call(this);
     }
  }
},

sendRequest: function(){
   this.getXMLHttpRequest();
      if(this.request){
         try{
             var loader = this;
             this.request.onreadystatechange = function(){
                  loader.onReadyState.call(loader);
             }
             this.request.open(this.httpMethod,this.url,true);
             this.request.send(this.params);
          }catch(e){
                this.error.call(this);
          }
    }else{
         this.error.call(this);
    }
},

init: function(httpMethod,url,params,callBack){
      this.httpMethod = httpMethod;
      this.url = url;
      this.params = params;
      this.callBack = callBack;
      this.error = this.defaultError;
      this.sendRequest();
   }
};
The above object almost contains all the function to make a Ajax request to the server. I will simply explain the constructor parameters need to initiate object from this class.
  • httpMethod : eidther "POST" or "GET"
  • url : request url (in my case, it is action name)
  • params : url parameter needed to pass to the server
  • callBack : javascrip function name which handles the response data and update the client browser content
If you want to make a request to the server, you can simply initiate an object from this class. Easy isn't it? Now the time to call the action.
My user list select box' onchange event will fire the fallowing javascrip function which get the user id form the select box and make a request to the "ViewUsers" class.
function fetchUser(){
    var selectBox = document.getElementById('userId');
    var selectedIndex = selectBox.selectedIndex;
    var selectedValue = selectBox.options[selectedIndex].value;
    new PDA.common.ContentLoader("POST",
                                 "ViewUsers.action?userId=" + selectedValue,
                                  null,
                                  showUsers);
}
"showUsers" function is the callback function in my case which is responsible for client side updates. Let's see the source code for ViewUsers action class next.
package com.axiohelix.pda.actions;
import org.hibernate.Transaction;
import com.opensymphony.xwork2.ActionSupport;

public class ViewUsers extends ActionSupport{
 private static final long serialVersionUID = 1L;
 private int userId;
 private User user;
 private Object jsonModel;
 
 public String execute(){
  String result = null;
  try{
   //do some thing to get the user object from userId
   setUserType(user);
   setJsonModel(user);
   result = SUCCESS;
  }catch(Exception e){
   e.printStackTrace();
   result = ERROR;
  }finally{
  }
  return result;
 }
 
 public int getUserId() {
  return userId;
 }

 public void setUserId(int userId) {
  this.userId = userId;
 }

 public User getUser() {
  return user;
 }

 public void setUser(User user) {
  this.user = user;
 }

 public Object getJsonModel() {
  return jsonModel;
 }

 public void setJsonModel(Object jsonModel) {
  this.jsonModel = jsonModel;
 }
}
As you can see, I have created User object from the userId and exposed it into the ValueStack under the property of "jsonModel". After returning from this action class, it should hit our custom result type and should send the user informations back to the client. Here, I am going to use JSON which provides a succinct text-based serialization of data objects. JSON can be a powerfully succinct and lightweight means of communication between a web application server and an Ajax client.
Next, we will see the core of the post.We are going to create the custom result which sends JSON data string back to the client. The only requirement needs to create a struts2 custom result is, implementing Result interface of struts 2. Here is the source code to create a custom result type and serialize the object in to JSON.

package com.axiohelix.pda.jsonresult;

import java.io.PrintWriter;

import org.apache.struts2.ServletActionContext;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.util.ValueStack;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class JSONResult implements Result{
 private static final long serialVersionUID = 1L;
 public static final String DEFAULT_PARAM = "classAlias";
 String classAlias;

 public String getClassAlias() {
  return classAlias;
 }
 public void setClassAlias(String classAlias) {
  this.classAlias = classAlias;
 }
 @Override
 public void execute(ActionInvocation invocation) throws Exception {
  ServletActionContext.getResponse().setContentType("text/plain"); 
  ServletActionContext.getResponse().addHeader("Cache-Control", "no-cache");
  PrintWriter responseStream = ServletActionContext.getResponse().getWriter();
  ValueStack valueStack = invocation.getStack();
  Object jsonModel = valueStack.findValue("jsonModel");
  XStream xstream = new XStream(new JettisonMappedXmlDriver());
  if ( classAlias == null ){
   classAlias = "object";
  }
  xstream.alias(classAlias, jsonModel.getClass() ); 
  responseStream.println(xstream.toXML( jsonModel ));
 }
}

Here, I have used two open source libraries, XStream and Jettison. XStream helps to serialize java objects into XML format while Jettison helps to create JSON from the XML.As you can see,I am getting "jsonModel" property exposed into the ValueStack from our ViewUsers action calss,progrmmetically and serialize it into JSON.
Now the time to tell ViewUsers action class about our custom result type. We have to add fallowing entries into our declarative architecture, ie struts.xml file. To declare our custom result type, you have to add fallowing into struts.xml file.
<result-types>
<result-type name="customJSON" class="com.axiohelix.pda.jsonresult.JSONResult">
</result-type>
</result-types>
To use our custom result type, we need to add the fallowing to struts.xml file.
<action name="ViewUsers" class="com.axiohelix.pda.actions.ViewUsers">
<result type="customJSON">user</result>
</action&>
Here, you can see, I am passing in a parameter to the custom result type. It will be set to the "classAlias" field and finally, it will be the name of the root JSON object return to the client. Fallowing is the response JSON string representation of JSON

{"user":{"username":"Mary",
"password":"max",
"age":"28",
"firstName":"Mary",
"lastName":"Greene",
"receiveJunkMail":"false"}}

Saturday, December 6, 2008

Handling custom error pages - struts 2

When you work with struts 2, you may need to handle exception and
forward it to a custom error pages as you wish. The exception interceptor of struts 2 will catch exception and map them to user-defined error pages by type. For example, suppose you want to map all java.lang.Exception to a custom error page defined by yourself, you have to involve the exception interceptor.
You can add the fallowing entry to your struts.xml file just after the package element.


<global-results>
<result name="error">/WEB-INF/jsp/errorPage.jsp
</result>
</global-results>
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error" />
</global-exception-mappings>

With this declaration, each java.lang.Exception exception will end up with your error page. Also you can display more details on error in your custom page.Before yielding control to the result, the exception interceptor will create an ExceptionHolder object and place it on top of the ValueStack. The Exception-Holder is a wrapper around an exception that exposes the stack trace and the exception as JavaBeans properties that you can access from a tag in your error page. The following snippet shows our error JSP page:

Exception Name: <s:property value="exception" />

What you did wrong:<s:property value="exceptionStack" />

We can use the
exception and exceptionStack property which are already exposed to ValueStack by ExceptionHolder object to get the details about the exception into the error page.

Tuesday, December 2, 2008

Object backed-components – Struts 2

The automatic data transfer of Struts 2 needs only to provide
JavaBeans properties on actions, using the same names as the form
fields being submitted. Object backed – components of Struts 2 allows us to transfer data as objects. Suppose you want
to create a new user.
We have User class as fallows,
public class User{
private String name;
private String email;
// getter and setter methods
}
If you use Struts 2 default data transferring process, you must give the same field names as input JSP’s field’s name, must write JavaBeans properties with the same name in you action class and you have to create User object by yourself to create a new user. If you use, object – backed components of Struts 2 for data transferring, We don’t even have to create the User object that backs the property because the framework’s data transfer will handle this for us when it starts trying to move the data over. You can have more cleaner action class as fallows.
public String execute(){
createUser(user);
return SUCCESS;
}
private User user;
public void setUser(User user){
this.user = user;
}
public User getUser(){
return this.user;
}
You also need to do slight changes in your input JSP’s and the resulting JSP’s when addressing appropriate fields. For example, in the input JSP for creating a new user,
Similarly, when rendering the result,
Welcome <s:property value="user.name" />
The minor consequences are that we have to go a dot deeper when we reference our data from the JSP pages.



Saturday, November 22, 2008

Vertically formatted CHECKBOXLIST - struts 2

This is one tricky point encountered by myself when creating
checkboxlist with struts 2. The default 'xhtml' them will always rendered
horizontally formatted list into the page. To have vertically formatted checkboxlist, we have to overwrite the default FreeMarker template for checkboxlist component.
If you check carefully, you can find the set of templates files
written in FreeMarker for each theme of struts 2 from struts-core.jar file inside the template folder. Here, I am going to overwrite the template file for checkboxlist component to have vertically formated checkboxlist.
Consider fallowing HTML mark up rendered into the view page from
checkboxlist component. It tries to list the users of the system preceding with a checkbox. User id has been set as the value of each checkbox.


<input type="checkbox" id="users-1" value="1000" name="users"/>
<label class="checkboxLabel" for="users-1">administrator</label>
<input type="checkbox" id="users-2" value="1001" name="users"/>
<label class="checkboxLabel" for="users-2">siriwardana</label>

You will get the fallowing out put.

This is a mark up of horizontally formated checkboxlist. If we can add additional '<br>' tag after each of this <label> tag, we are done !. We can have a vertically formatted checkboxlist. We have to overwrite checkboxlist.ftl file located at template/xhtml in struts-core.jar file. I do not suggest you to change this file. Instead, we will create a new theme. To create a new our own theme, create a folder called 'template' under /WEB-INF/classes. Then create a folder with new theme name. In my case, I created a folder called 'pda' and copied all the template files from default xhtml folder to pda folder. Now, I am going to change the checkboxlist.ftl file located at pda folder.

We will look at the contents of this file for a moment.

<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<#include "/${parameters.templateDir}/simple/checkboxlist.ftl" />
<#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /><#nt/>

This is written in FreeMarker. I guess, now you know what we have to do is. You can see, this has included checkboxlist.ftl file from default simple theme. We have to edit that file. Again, I am going to overwrite the default simple theme comes with struts 2 for my new theme. I am creating a folder called "pda_simple" under the /WEB-INF/classes/template and copy the checkboxlist.ftl file from default simple theme folder to pda_simple folder. Before edit this checkboxlist.ftl file, we have to change the file path of checkboxlist.ftl file located at pda folder as fallows.



<#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
<#include "/${parameters.templateDir}/pda_simple/checkboxlist.ftl" />
<#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /><#nt/>

Now this refers checkboxlist.ftl file from our new simple theme which is pda_simple. To control the alignment of our checkboxlist component, I am going to use the cssStyle attribute of the checkboxlist component. Here is the my view code.


<s:form action="deleteUser" method="POST" theme="pda">
<s:checkboxlist list="users" name="users" listKey="userId"
listValue="userName" cssStyle="vertical">
</s:checkboxlist>
<s:submit value="update"></s:submit>
</s:form>




From above code, I want your attention on theme attribute of form component and the cssStyle attribute of checkboxlist component. I have specified our new theme for current form and cssStyle is set to vertical to have a vertically formatted checkboxlist.
Now, we have to edit checkboxlist.ftl file located at /WEB-INF/classes/template/pda_simple as fallows.


<#assign itemCount = 0/>
<#if parameters.list?exists>
<@s.iterator value="parameters.list">
<#assign itemCount = itemCount + 1/>
<#if parameters.listKey?exists>
<#assign itemKey = stack.findValue(parameters.listKey)/>
<#else>
<#assign itemKey = stack.findValue('top')/>
</#if>
<#if parameters.listValue?exists>
<#assign itemValue = stack.findString(parameters.listValue)/>
<#else>
<#assign itemValue = stack.findString('top')/>
</#if>
<#assign itemKeyStr=itemKey.toString() />
<input type="checkbox" name="${parameters.name?html}" value="${itemKeyStr?html}" id="${parameters.name?html}-${itemCount}"<#rt/>
<#if tag.contains(parameters.nameValue, itemKey)>
checked="checked"<#rt/>
</#if>
<#if parameters.disabled?default(false)>
disabled="disabled"<#rt/>
</#if>
<#if parameters.title?exists>
title="${parameters.title?html}"<#rt/>
</#if>
<#include "/${parameters.templateDir}/simple/scripting-events.ftl" />
<#include "/${parameters.templateDir}/simple/common-attributes.ftl" />
/>
<label for="${parameters.name?html}-${itemCount}" class="checkboxLabel">${itemValue?html}</label>
<#if parameters.cssStyle?exists>
<#if "${parameters.cssStyle?html}" == "vertical">
<br><#rt/>
</#if>
</#if>
</@s.iterator>
<#else>
&nbsp;
</#if>



Actually you don't need to worry about the whole code above, but what I have highlighted. At the very beginning, I mentioned my target is adding additional <br> tag at the end of each <label> tag. I have done. There may be better ways of achieving this. This is how, I got through this. Finally, I got the expected output as fallows.


The CHECKBOXLIST component - struts 2

This a vary usefull UI component comes with struts 2 which allows user
for multiple selection. If we use normal HTML checkbox component, we have to write a custom function to get the selected values from a list of check boxes. Struts 2 provides a convenience way of fetching selected from a list of check boxes. Struts 2 ValueStack will resolve an array of string for the selected check boxes.


I will further demonstrate this with a simple example. Suppose I want to create a check box list which allows user to select several users from the list. This is my User class.

public class User{
private int userId,
privte Stirng userName;

// -------
}

Then I should create the HTML markup for the component.

<s:form action="deleteUser" method="POST" >
<s:checkboxlist list="users" name="users" listKey="userId"
listValue="userName">
</s:checkboxlist>
<s:submit value="update"></s:submit>
</s:form>

Here, the value of list attribute "users" should be prepopulated with an action class, and then that action class should result into this page. Don't be confuse, I have use same for the value of the name attribute. You can use any meaningful one for this. Then you have to write the action class to fetch the checked list as fallows.

package com.axiohelix.pda.actions;

import com.opensymphony.xwork2.ActionSupport;

public class DeleteUser extends ActionSupport{
private String[] users;

public String[] getUsers() {
return users;
}
public void setUsers(String[] users) {
this.users = users;
}
public String execute(){
String[] userId = this.getUsers();
// --------------------------
return SUCCESS;
}
}

Sunday, November 9, 2008

The SELECT component - struts 2

The select component is perhaps the most common collection-based
UI component comes with struts 2. In a java web application, it's common to build these list of options from a Collection, Map or Arrays of data. The fallowing snippet shows the simplest use case of the select UI component.


<s:select name="userType" list="{'Administrator','Power User','Guest'}" />

Here, we have supplied an OGNL list literal as the list value of the select box. This render the fallowing HTML markup into the page.

<select name="userType" id="registerUserForm_userType">
<option value="Administrator">Administrator</option>
<option value="Power User">Power User</option>
<option value="Guest">Guest</option>
</select>

Here, "registerUserForm" is the my action name submitted with the form. But, most of the time, we may need to create select UI component with more advance Collection data. In this case, it may be list of UserType objects. Suppose, we have a class as fallows.

public class UserType{
private int userTypeId,
private String userTypeName,
// .....
}

Now, we want to create the select UI component from a List of UserType objects. In this case, we need to have userTypeId as the value and userTypeName as option contents (what user can see from select box). Here, the userTypeId should be a key attribute in the matching database table.

<s:select list="userType" key="userType" listKey="userTypeId"
listValue="userTypeName">

</s:select>
userType is the list of UserType objects which is already exposed into the ValueStack through an action class. listKey and listValue are important tow attributes, when it comes to this kind advance data types.

  • listKey - The property of the List’s elements to be used for the value submitted when those elements are complex types; key by default.
  • listValue - The property of the List’s elements to be used for the content of the option when those elements are complex types in other words, the string seen by the user; value by default.

Here is the rendered HTML from the above UI components.

<select id="registerUserForm_userType" name="userType">
<option value="1">administrator</option>
<option value="2">power</option>
<option value="3">guest</option>
</select>

Saturday, November 8, 2008

Using global messages - struts 2

Putting text resources in properties files is a common web
application development practice enhancing proper management of texts,easy modifications and for internationalization. When it comes to struts 2, it further helps to keep our code clean and reduce the development time. To use global messages with struts 2, we need to create "global-messages.properties" and place it on the class path at WEB-INF/classes. All the messages can be put in this file as key- value pairs.


//.....
userName=User Name
password=Password
//.....
In order to use this file, we have to tell struts 2 where to find this file. As usual , we need to add one entry into the "struts.properties" file residing in the class path.

//.....
struts.custom.i18n.resources=global-messages
//.........

Here, how we use these messages in our view pages. You have to use the "key" attribute of UI components to specify the key from the global-messages.

<s:textfield key="userName"/>
<s:password key="password"/>

Key attribute does infer the name and value for the text filed with message key. Here, proper naming convention is important to have this kind of service from the struts 2 framework.

Sunday, November 2, 2008

Changing the "theme" of UI components - struts 2

The struts 2 UI components come with four themes for rendering HTML markup into your view page.

  • simple
  • xhtml
  • css_xhtml
  • ajax
The default theme coming with struts 2 is xhtml which is involving html table layout to render the form component in the view page. For example consider the fallowing UI components.

<s:form action="doLogin.action" method="POST">
<s:textfield name="userName" label="User name" />
</s:form>

This UI components will render the fallowing HTML markup into your view page base on the default theme of the struts 2. You may confuse, if you haven't heard about struts 2 themes.

<form method="post" action="doLogin.action" onsubmit="return true;" id="doLogin">
<table class="wwFormTable">
<tbody>
<tr>
<td class="tdLabel">
<label class="label" for="doLogin_userName">User name:</label>
</td>
<td>
<input type="text" id="doLogin_userName" value="" name="userName"/>
</td>
</tr>
</tbody></table>
</form>
This HTML markup was rendered into view page on xhtml theme of struts 2. You may probably want to change the default theme. You can change this by overriding the default property which is defined in default.properties, found in struts 2 core jar file at org.apache.struts2.

To override any of the framework’s default properties, you only need to create your own properties file, named struts.properties, and place it in the classpath. To change the default theme, just create a struts.properties file with the following property definition:
struts.ui.theme=css_xhtml

In addition to these four themes that comes with struts 2, you can create your own them too.

Saturday, November 1, 2008

Filtering and Projecting with OGNL - struts 2

This is important OGNL feature when we are working with collections
to generate the UI components with Struts 2 tag libraries. To explain this, I will consider a class called "
Employee". Suppose we have a list of employee objects from the "ValueStack".

public class Employee{

private int age;

private String firstName;

private String lastName;

//......

}

Now, you want to filter out the employees where the age is grater than 30. The OGNL filtering will resolve the problem. You can get this as fallows .

<s:set name="empList" value="employeeList.{?#this.age > 30}"></s:set>
<s:iterator value="#empList">
<s:property value="firstName"/>
</s:iterator>

Here, #this refers to the "employeeList" which is resolved in "ValueStack". That is it, about filtering. Now I will explain, how we use the projection. Think, you want to have a separate list with employee's full name. The OGNL projection feature will lead us to achieve it .

<s:set name="empNameList"
value="employeeList.{firstName + ' ' + lastName}"></s:set>
<s:iterator value="#empNameList" status="stat">
<s:property value="#empNameList[#stat.getIndex()]"/>
</s:iterator>

Tuesday, October 28, 2008

OGNL - Calling Static Methods and Fields - struts 2

Most of the time, we have used to declare system constants as static fields
of a class, and some time static methods are being involved. If we want to access this kind of field from a JSP page with struts 2 taglibs, we have tow methods. I have tow classes 'SystemConstants' and 'Utils' in my own application.

public class
SystemConstants{
// ............
public static final
SESSION_DATA = "sessionData";
// .........
}

Also I have a separate class called "SessionData" to keep the data needed for the entire session. Useful information needed to keep through out the session are stored in the "
SessionData".

public class
SessionData{

private User user ;

public setUser(User user){
this.user = user;
}

public User getUser(){
return this.user;
}

}
I have stored the SessionData object in the session object from an "Action" class of struts 2.
SessionData sd = new SessionData();
getServletRequest().getSession()
.setAttribute(SystemConstants.
SESSION_DATA, sd);

Now, I want to access the session object and the "
SESSION_DATA" static field from the jsp page. This is where the point comes. For this, we have tow methods.


Method one:
To access the static field ,
<s:set name="sdKey"
value="@com.axiohelix.pda.utils.SystemConstants@
SESSION_DATA"></s:set>

Then to access the session object
from the "ActionContext",
<s:set name="objSd" value="#session[#sdKey]"/>

Then we can access the fields and methods declared in the SessionData object.
<s:property value="#objSd.user.userName"/>

This is how, we access static methods
@
com.axiohelix.utils.Utils@getSessionId()

If our field or method will resole in the 'ValueStack' in advance, we can use the fallowing syntax to access relevants
Method tow:
@vs@
SESSION_DATA
@vs@getSessionId()

The SESSION_DATA static field is declared in the class 'SystemConstants' packaged at
'com.axiohelix.utils' and getSessionId() function is implemented within the Utils class packaged at 'com.axiohelix.utils'


Share

Widgets