Sunday, March 6, 2011

Struts2, Tiles2, Spring, Hibernate, JQuery plugin - Linked JQGrids

Disclaimer:
All the software applications that appear below represent trademarks and they are the property of their respective owners.
Use and implement my notes in this article at your own risk.

Discussion here:
1. General Considerations;
2. Prerequisites;
3. JQuery JQGrids;
4. Linking JQGrids;
5. KeyValuePairBean;
6. Struts 2 Actions;
7. DAOs;
8. Annotated Hibernate entities;

1. General Considerations:
This sample is purposed to show how to link 3 JQuery JQGrids together; the 3 grids will represent records from the tables "zone", "country", "city" of the "blog" database.
The sample will be presented somehow in a "top-down" fashion, ie. starting from View, then Controller, then Model components of the MVC web app architecture.

NOTE: if you don't know for example what is "blog" database, or other aspects, then you should read the "Struts2, Tiles2, Spring, Hibernate, JQuery plugin - General Considerations", which is used as a basis for this sample.

2. Prerequisites:
Make sure you have read the "Struts2, Tiles2, Spring, Hibernate, JQuery plugin - General Considerations", and installed and configured all that is written there;

3. JQuery JQGrids:
The JQuery JQGrid refers to the "<sjg:grid />" object which is offered by the JQuery JQGrid plugin taglib, namely "struts2-jquery-grid-plugin-2.5.0.jar".
In order to use this plugin, you will have to include the following lines in your JSP file:
<<
<%@ taglib prefix="sj" uri="/struts-jquery-tags"%>
<%@ taglib prefix="sjg" uri="/struts-jquery-grid-tags"%>

>>
Thus, you made the JQuery JQGrid available to be used in your JSP file.

An JQuery JQGrid used for zones ("gridZones") will look like:
<<
        <s:url id="urlSelectZones" action="selectZone" />
        <sjg:grid
            id="gridZones"
              caption="Zones"
              dataType="json"
              href="%{urlSelectZones}"
            width="400"
              altRows="true"
              pager="true"
              navigator="true"    
              navigatorSearch="true"
              navigatorSearchOptions="{sopt:['eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc'], multipleSearch: true}"
              navigatorAdd="false"
              navigatorEdit="false"
              navigatorView="true"
              navigatorDelete="false"
              gridModel="gridModel"
              rowList="5,10,15,20"
              rowNum="5"
              editinline="false"
              onSelectRowTopics="/zoneRowSelect">
                <sjg:gridColumn width="50" name="key" align="center" index="cod" title="Code" editable="false" sortable="true" search="true" searchoptions="{sopt:['eq','bw','ew','cn']}" />
                <sjg:gridColumn width="350" name="value" index="name" title="Name" editable="false" sortable="true" search="true" searchoptions="{sopt:['eq','bw','ew','cn']}" />
        </sjg:grid>

>>
NOTE: <s:url id="urlSelectZones" action="selectZone" /> defines the Struts Action data source for the dialog which will be discussed at "6. Struts 2 Actions".

4. Linking JQGrids
Supposing you have other JQGrids for countries, cities, you may want to obtain which cities are from a country and which countries are from a zone.
The JQuery JQGrid for countries ("gridCountries") looks like:
<<
        <s:url id="urlSelectCountries" action="selectCountry" />
        <sjg:grid
            id="gridCountries"
              caption="Countries"
              dataType="json"
              href="%{urlSelectCountries}"
            width="400"
              altRows="true"
              pager="true"
              navigator="true"    
              navigatorSearch="true"
              navigatorSearchOptions="{sopt:['eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc'], multipleSearch: true}"
              navigatorAdd="false"
              navigatorEdit="false"
              navigatorView="true"
              navigatorDelete="false"
              gridModel="gridModel"
              rowList="5,10,15,20"
              rowNum="5"
              editinline="false"
              onSelectRowTopics="/countryRowSelect">
                <sjg:gridColumn width="50" name="key" align="center" index="cod" title="Code" editable="false" sortable="true" search="true" searchoptions="{sopt:['eq','bw','ew','cn']}" />
                <sjg:gridColumn width="350" name="value" index="name" title="Name" editable="false" sortable="true" search="true" searchoptions="{sopt:['eq','bw','ew','cn']}" />
        </sjg:grid>

>>

If you want to link "gridCountries" to "gridZones", then you will use a "<sj:a />" Struts JSP tag will will behave as a "select" button:
<<
    <sj:a id="btnSelectZone" button="true">select zone &gt;&gt;</sj:a>
>>

Then, when user clicks this button, the following javaScript code executes:
<<
    $("#btnSelectZone").click(function(){
        var gridZonesId = $("#gridZones").jqGrid("getGridParam", "selrow");
        var cod_zone = $("#gridZones").jqGrid('getCell', gridZonesId, 'key');
        var name_zone = $("#gridZones").jqGrid('getCell', gridZonesId, 'value');
        $("#gridCountries").jqGrid('setGridParam', {
            url: "/bloggrid/selectCountry.action?cod_zone=" + cod_zone,
            page: 1
        });
       
        $("#gridCountries").trigger("reloadGrid");
           $("#divGridCountries").css("display", "");
    })

>>

This javaScript code practically gets the selected row in the "gridZones" and passes it as HTTP parameter "cod_zone" to "gridCountries"; when gridCountries is loaded with the content served by "selectCountry.action" it will trigger the "reloadGrid" event.

The same happens also for cities, the difference being the correspondence is now between countries and cities.

5. KeyValuePairBean:
This bean is used as a structure to store key/value pair for JQGrid columns denoted by "<sjg:gridColumn />" Struts JSP tags.

6. Struts 2 Actions:
A JQuery JQGrid is rendered by JSON data source.
So, the Struts action should return a JSON type result.
To be sure of this, you have to include this annotation to your struts action:
<<
@ParentPackage("blog-json")
>>
This will use the Struts "blog-json" package defined in struts.xml, ie. it will extend the "json-default" package of the Struts2; "json-default" uses "struts2-json-plugin-2.2.1.jar" to render a JSON result.

To populate for example the zones, you will use this code:
<<
    @Override
    @Action(value = "/selectZone", results = {
            @Result(name = "success", type = "json"),
            @Result(name = "input", location = "/jsp/empty.jsp") })
    public String execute() {
        try {
            List<KeyValuePairBean> list = new ArrayList<KeyValuePairBean>();
            List<Zone> listZones = (List<Zone>) zoneDAO.findAll();
            for (Zone zone : listZones) {
                list.add(new KeyValuePairBean(zone.getCod(), zone.getName()));
            }

            if (list != null && !list.isEmpty()) {
                if (getSord() != null && getSord().equalsIgnoreCase("asc")) {
                    sortList(list);
                }
                if (getSord() != null && getSord().equalsIgnoreCase("desc")) {
                    sortList(list);
                    Collections.reverse(list);
                }
                setRecords(list.size());

                if (getTotalrows() != null) {
                    setRecords(getTotalrows());
                }

                Integer to = (getRows() * getPage());
                Integer from = to - getRows();

                if (to > getRecords())
                    to = getRecords();

                List<KeyValuePairBean> listClone = cloneList(list);
                if (isLoadonce()) {
                    if (getTotalrows() != null && getTotalrows() > 0) {
                        setGridModel(listClone.subList(0, getTotalrows()));
                    } else {
                        setGridModel(list);
                    }
                } else {
                    if (filters != null && !filters.isEmpty()) {
                        setGridModel(returnGridModel(listClone, from, to));
                    } else {
                        setGridModel(listClone.subList(from, to));
                    }
                }

                setTotal((int) Math.ceil((double) getRecords()
                        / (double) getRows()));
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return ActionSupport.INPUT;
        }
        return ActionSupport.SUCCESS;
    }

>>

Here:
"gridModel" is the Java List which will be rendered as a JSON object;
"zoneDAO" is the DAO (see "6. DAOs") managed by Spring over the Hibernate entities which contains methods as "findByCriteria()" in order to obtain data from database; you will find it in the sample source;
The "execute()" method contains also code for sorting, finding, paging the JQGrid.
The sorting is done by using Java Collections class, while the finding is done by using "returnGridModel()".

Also, you may observe here annotations to define the "success" and "input" results (overcomes) of the action "selectZone".

In the same action you should define getters and setters for "String term" and "List<KeyValuePairBean> zones".

Same you should do for countries and cities.
NOTE: to see how the Struts 2 Actions are accessed, and how their results are rendered, checkout also the "struts.xml" and "tiles.xml" files.

7. DAOs
DAOs, namely Data Access Objects, are used by Spring to obtain data from database via Hibernate.
DAOS are part of the business layer of a database web app.
A DAO class extends in this case the "HibernateDaoSupport" and contains methods to access the database via annotated Hibernate entities.
Any DAO should be described as a bean in the "applicationContext.xml" file in order to be seen by the sessionFactory; for example, for zones:
<<
    <bean id="ZoneDAO" class="blog.hibernate.dao.ZoneDAO">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

>>

From Java point of view, a DAO should use a session manager, in this case, "HibernateDaoSupport" which is a Spring class that manages Hibernate sessions over annotated entities.
NOTE: a method that uses Hibernate session to get data from database uses HQL (Hibernate Query Language); to understand the HQL syntax you should google for HQL documentation.

8. Annotated Hibernate entities
An annotated entity is a POJO class that describes a database table by means of annotations.
It represents the Model from the MVC architecture.
For example, for the database table "zone", it was defined the Zone.java entity class; to refer table "zone" from database "blog", annotations are placed at the top of the class definition:
<<
@Entity
@Table(name = "zone", catalog = "blog")

>>
, while for each of table "zone" fields, there are specified on their value getters: primary key, foreign key, column name, field length, aso, if they exist.
Here are some examples of annotations, taken from Zone.java:
<<
    @Id
    @GeneratedValue
    @Column(name = "cod", unique = true, nullable = false, length = 4)

>>
<<
    @Column(name = "name", nullable = false, length = 45)
>>
<<
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "zone")
>>

NOTE: if MyEclipse is used as IDE, you may generate both DAO and entity for each table by using Hibernate Reverse Engineering perspective.

For any other aspects do not hesitate to contact.

9 comments:

  1. Very useful article. Can you email the source code @ wliu_ca@yahoo.com? Thanks.

    Wei

    ReplyDelete
  2. Very useful article. Can you email the source code @ donvatsyayan@gmail.com? Thanks.

    Parikshit

    ReplyDelete
  3. hi, Very useful article. Can you email the source code bpalaciosa@misena.edu.co Thanks.

    ReplyDelete
  4. Very useful article. Can you email the source code @ m.mharzi@gmail.com? Thanks.

    ReplyDelete
  5. Please send your source code to omanandgiri@gmail.com

    ReplyDelete
  6. Hi can you email me the source code to kondabolu.bindu@gmail.com

    ReplyDelete
  7. Hi all,
    I know there were guys that wanted the code and probably I couldn't send them the code in useful time. First of all I want to apologize to all these guys.
    Second, due to latest activity, I really have no time to maintain this blog, so I want you guys understand I CANNOT SEND THE CODE ANYMORE.
    In this case, you have 2 solutions:
    1) the main aspects for each sample are already posted on respective topics; practically, if you read the "general considerations" and the related topics, you will not need the code; you just need to configure a new app and paste there the code from the topics;
    2) if you really want the code, then ALL THE GUYS THAT LEFT THEIR EMAILS ON THE COMMENTS HAVE RECEIVED THE CODE; so, if I cannot respond, then maybe they can ;) in fact, it is a distributable code, isn't it ? ;)
    I hope you guys understand the above, thanx, vsorinel.

    ReplyDelete
  8. pls send me source code withrequired libraries. on rgcomp555@gmail.com

    ReplyDelete