Monday, April 4, 2011

JQuery-Plugin, JQGrid + FileRepository(Spring MVC Sample Joining JQuery)

Hey...
Hope you've had nice holiday.

Now we are familiar with Spring-MVC and JQuery enough. I think it's the time to mix our knowledge of High-Tech technologies. Here I'm going to continue the JQuery course in JQuery-plugin and also take a sample joining Spring MVC and JQuery-Plugin(JQGrid) using JSon.

Ok let's start...

First JQuery-Plugin
7. JQuery-Plugin

7.1 jqGrid
jqGrid is an Ajax-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web. Since the grid is a client-side solution loading data dynamically through Ajax callbacks, it can be integrated with any server-side technology, including PHP, ASP, Java Servlets, JSP, ColdFusion, and Perl.
jqGrid uses a jQuery Java Script Library and is written as plugin for that package. jqGrid's Home page can be found here.
Here you can take look of simple view of jqGrid
Simple jqGrid
7.1.1 Basic Grids
An instance of jqGrid is a javascript object, with properties, events and methods. Properties may be strings, numbers, arrays, boolean values or even other objects.
Calling Convention:
jQuery("#grid_id").jqGrid(properties );
Where: 
  • grid_id is the id of the <table> element defined separately in your html and used as the name of your grid. 
  • properties is an array of settings in name: value pairs format. Some of these settings are values, some are functions to be performed on an event. Some of these settings are optional while others must be present for jqGrid to work.
An example
jQuery("#list").jqGrid({
    url:'example.html',
    datatype: 'xml',
    mtype: 'GET',
    colNames:['Inv No','Date', 'Amount','Tax','Total','Notes'],
    colModel :[
      {name:'invid', index:'invid', width:55},
      {name:'invdate', index:'invdate', width:90},
      {name:'amount', index:'amount', width:80, align:'right'},
      {name:'tax', index:'tax', width:80, align:'right'},
      {name:'total', index:'total', width:80, align:'right'},
      {name:'note', index:'note', width:150, sortable:false} ],
    pager: jQuery('#pager'),
    rowNum:10,
    rowList:[10,20,30],
    sortname: 'id',
    sortorder: "desc",
    viewrecords: true,
    imgpath: 'themes/basic/images',
    caption: 'My first grid'
  });
When the grid is created, normally several properties are set in the same statement (although these  properties can be individually overridden later): see Properties
Events raised by the grid, which offer opportunities to perform additional actions, are described in Events in future post.
The grid also offers several methods for getting or setting properties or data: Methods will be described in future post.
A key property of the grid is the column model (colModel) that defines the contents of the grid: colModel Properties
Additional properties, events and methods of the basic grid, not described in this section, are used to create and manage the three special types of grids: multiselect grids, subGrids and treeGrids. I will handle them in future posts.

7.1.2 Properites

The available properties are listed here, in alphabetic order. Some have more details described.

PropertyTypeDescriptionDefault
altRowsbooleanSet a zebra-striped gridtrue
captionstringDefines the Caption layer for the grid. This caption appear above the Header layer. If the string is empty the caption does not appear. empty string
cellEditbooleanEnables (disables) cell editing.true
cellsubmitstringDetermines where the contents of the cell are saved: 'remote' or 'clientArray'.'remote'
cellurlstringthe url where the cell is to be saved.null
colModelarrayArray which describes the parameters of the columns. For a full description of all valid values see colModel API. empty array
colNames arrayArray which describes the column labels in the gridempty array
datastrstringThe string of data when datatype parameter is set to xmlstring or jsonstringnull
datatypestringDefines what type of information to expect to represent data in the grid. Valid options are xml - we expect xml  data; xmlstring - we expect xml data as string; json - we  expect JSON data; jsonstring - we expect JSON data as string; clientSide - we expect data defined at client side (array data)xml
editurlstringDefines the url for inline and form editing.null
ExpandColumnstringindicates which column (name from colModel) should be used to expand the tree grid. If not set the first one is used. Valid only when treeGrid option is set to true.null
forceFitbooleanIf set to true, and resizing the width of a column, the adjacent column (to the right) will resize so that the overall grid width is maintained (e.g., reducing the width of column 2 by 30px will increase the size of column 3 by 30px). In this case there is no horizontal scrolbar. Note: this option is not compatible with shrinkToFit option - i.e if shrinkToFit is set to false, forceFit is ignored.false
gridstatestringDetermines the current state of the grid (i.e. when used
with hiddengrid, hidegrid and caption options). Can have
either of two states: 'visible' or 'hidden'
visible
hiddengridbooleanIf set to true the grid initially is hidden. The data is not loaded (no request is sent) and only the caption layer is shown. When the show/hide button is clicked the first time to show grid, the request is sent to the server, the
data is loaded, and grid is shown. From this point we have a regular grid. This option has effect only if the caption property is not empty and the hidegrid property (see below) is set to true.
false
hidegridbooleanEnables or disables the show/hide grid button, which appears on the right side of the Caption layer. Takes effect only if the caption property is not an empty string.true
heightstringThe height of the grid. Can be set as percentage or any
valid measured value
150px
imgpathstringDefines the path to the images that are used in the grid.
Set this option without / at end
empty string
jsonReaderarrayArray which describes the structure of the expected json
data. For a full description and default setting
loadoncebooleanIf this flag is set to true, the grid loads the data from the server only once (using the appropriate datatype). After the first request the datatype parameter is automatically changed to clientSide and all further manipulations are done on the client side. The functions of the pager (if present) are disabled.false
loadtextstringThe text which appear when requesting and sorting dataLoading...
loaduistringThis option controls what to do when an ajax operation is in progress.
  • disable - disables the jqGrid progress indicator. This way you can use your own indicator.  
  • enable (default) - enables the red "Loading" message in the upper left of the grid.  
  • block - enables the progress indicator using the characteristics you have specified in the css for div.loadingui and blocks all actions in the grid until the ajax request is finished. Note that this disables paging, sorting and all actions on toolbar, if any.
mtypestringDefines the type of request to make ("POST" or "GET")GET
multikeystringThis parameter have sense only multiselect option is set to true. Defines the key which will be pressed when we make multiselection. The possible values are: shiftKey - the user should press Shift Key altKey - the user should press Alt Key ctrlKey - the user should press Ctrl Keyempty string
multiboxonlybooleanThis option works only when multiselect = true. When multiselect is set to true, clicking anywhere on a row selects that row; when multiboxonly is also set to true, the row is selected only when the checkbox is clicked (Yahoo style).false
multiselectbooleanIf this flag is set to true a multi selection of rows is enabled. A new column at left side is added. Can be used with any datatype option.false
prmnamesarrayCustomizes names of the fields sent to the server on a Post: default values for these fields are "page", "rows", "sidx", and "sord". For example, with this setting, you can change the sort order element from "sidx" to "mysort":  prmNames: {sort: "mysort"}
The string that will be posted to the server will then be  myurl.html?page=1&rows=10&mysort=myindex&sord=asc
rather than
myurl.php?page=1&rows=10&sidx=myindex&sord=asc
none
postDataarrayThis array is passed directly to the url. This is associative array and can be used this way: {name1:value1...}.empty array
resizeclassstringAssigns a class to columns that are resizable so that we can show a resize handle only for ones that are resizablegrid_resize
scrollbooleanCreates dynamic scrolling grids. When enabled, the pager elements are disabled and we can use the vertical scrollbar to load data. This option currently should be used carefully on big data sets, since I have not developed an intelligent swapper, which means that all the data is loaded and a lot of memory will be used if the dataset is large. You must be sure to have a initial vertical scroll in grid, i.e. the height should not be set to auto.false
scrollrowsbooleanWhen enabled, selecting a row with setSelection scrolls the grid so that the selected row is visible. This is especially useful when we have a verticall scrolling grid and we use form editing with navigation buttons (next or previous row). On navigating to a hidden row, the grid scrolls so the selected row becomes visible.false
sortclassstringthe class to be applied to the currently sorted column, i.e. applied to the th elementgrid_sort
shrinkToFitbooleanThis option describes the type of calculation of the initial width of each column against with the width of the grid. If the value is true and the value in width option is set then: Every column width is scaled according to the defined option width. Example: if we define two columns with a width of 80 and 120 pixels, but want the grid to have a 300 pixels - then the columns are recalculated as follow: 1- column = 300(new width)/200(sum of all width)*80(column width) = 120 and 2 column = 300/200*120 = 180. The grid width is 300px. If the value is false and the value in width option is set then: The width of the grid is the width set in option. The column width are not recalculated and have the values defined in colModel. true
sortascimg,
sortdescimg
stringLinks to image url which are used when the user sort a columnsort_asc.gif, sort_desc.gif
sortnamestringThe initial sorting name when we use datatypes xml or json (data returned from server) none (empty string)
sortorderstring The initial sorting order when we use datatypes xml or json (data returned from server)asc
toolbararrayThis option defines the toolbar of the grid. This is array with two values in which the first value enables the toolbar and the second defines the position relative to body Layer. Possible values "top" or "bottom"[false,"top"]
treeGridbooleanEnables (disables) the tree grid format. false
tree_root_level numericDetermines the level where the root element begins when treeGrid is enabled 0
urlstringThe url of the file that holds the requestnull
userDataarrayThis array contain custom information from the request. Can be used at any time.empty array
widthnumberIf this option is not set, the width of the grid is a sum of the widths of the columns defined in the colModel (in pixels). If this option is set, the initial width of each column is set according to the value of shrinkToFit option.none
xmlReaderarrayArray which describes the structure of the expected xml
data.

$.jgrid.defaultarray This array is used to define a grid option common to all jqGrids in the application. Typically, this is called once to set the default for one or more grid parameters -- any parameter can be changed. Typical implementation;
$.extend($.jgrid.defaults,{rowNum:10})
empty array

7.1.3 colModel
The colModel property defines the individual grid columns as an array of properties.
Syntax:
colModel: [
    {name:'name1', index:'index1'...},
    {...},
     ...
  ]
The available colModel properties are listed here, in alphabetic order. All of these properties are values, there are no events or methods associated with the colModel. The only required property is name.

PropertyTypeDescriptionDefault
align string Defines the alignment of the cell in the Body layer, not in header cell. Possible values: left, center, right.left
datefmtstringGoverns format of sorttype:date and editrules {date:true} fields. Determines the expected date format for that column. Uses a PHP-like date formatting. Currently "/", "-", and "." are supported as date separators. Valid formats are:
  • y,Y,yyyy for four digits year
  • YY, yy for two digits year
  • m,mm for months
  • d,dd for days 
ISO Date (Y-m-d)
editable boolean Defines if the field is editable. This option is used in inline and form modules.false
editoptionsarrayArray of allowed options (attributes) for edittype optionempty array
editrulesarrayeditrules: {edithidden:true(false), required:true(false), number:true(false), minValue:val, maxValue:val, email:true(false), date:true(false)} 
  • edithidden: if true, fields hidden in the grid are included as editable in form editing when add or edit methods are called. 
  • searchhidden: if true, fields hidden in the grid are included in the search form. 
  • required: if true the value will be checked and, if it is empty, an error message will be displayed. 
  • number: if true the value will be checked to be sure it is a number and, if it is not, an error message will be displayed. 
  • i nteger: if true the value will be checked to be sure it is an integer and, if it is not, an error message will be displayed. 
  • minValue: if set to a valid number, the value will be checked and if it is less than the minValue, an error message will be displayed. 
  • maxValue: the same as minValue but the value will be checked to be sure it is not greater than the maxValue. 
  • email: if true, the value will be checked to ensure it conforms to a valid e-mail format (not that the email address exists) and, if it does not, an error message will be displayed. 
  • date: if set to true, the format of the field is governed by the setting of the datefmt parameter
When a field is not required, the validation rules do not fire and so do not raise an alert for missing data.
For example,
colModel: [
...
{...editrules:{required:false, number:true..}...}
...
]
In this case, if no data is provided by the user (it is left blank) the alert message does not appear - i.e. this is considered to be valid input. 
empty array
edittypestringDefines the edit type for inline and form editing Possible values: text, textarea, select, checkbox, passwordtext
formatoptionsarrayFormat options can be defined for particular columns,overwriting the defaults from the language file. See formatter for more details. none
formatterstringThe predefined types or custom function name that controls the format of this field. See formatter for more details.none
hidedlgbooleanIf set to true this column will not appear in the modal dialog where users can choose which columns to show or hide.false
hiddenbooleanDefines if this column is hidden at initialization.false
indexstringSet the index name when sorting. Passed as sidx parameter.the order of cell
jsonmapstringDefines the json mapping for the column in the incoming json string. none
keybooleanIn case if there is no id from server, this can be set as as id for the unique row id. Only one column can have this property. If there are more than one key the grid finds the first one and the second is ignored.false
labelstringWhen colNames array is empty, defines the heading for this column. If both the colNames array and this setting are empty, the heading for this column comes from the name property.none
namestringSet the unique name in the grid for the column. This
property is required. As well as other words used as
property/event names, the reserved words (which
cannot be used for names) include subgrid and cb.
 
resizablebooleanDefines if the column can be resizedtrue
searchbooleanWhen used in formedit, disables or enables searching on that columntrue
sortablebooleanDefines is this can be sorted.true
sorttypestringUsed when datatype is clientSide. Defines the type of the column for appropriate sorting. Possible values:
  • int - for sorting integer  
  • float - for sorting decimal numbers  
  • date - for sorting date  
  • text - for text sorting
text
widthnumberSet the initial width of the column, in pixels150
xmlmapstringDefines the xml mapping for the column in the incomming xml file. Use a CCS specification for thisnone


7.1.4 jsonReader
JSON data is handled in a fashion very similar to that of xml data. What is important is that the definition of the jsonReader matches the data being received
datatype: json, (or jsonstring)
The default definition of the jsonreader is as follows:
jsonReader : {
  root: "rows",
  page: "page",
  total: "total",
  records: "records",
  repeatitems: true,
  cell: "cell",
  id: "id",
  userdata: "userdata",
  subgrid: {root:"rows",
    repeatitems: true,
    cell:"cell"
  }
}
datastr:
If the parameter datatype is 'json', jqGrid expects the following default format for json data.
{
  total: "xxx",
  page: "yyy",
  records: "zzz",
  rows : [
    {id:"1", cell:["cell11", "cell12", "cell13"]},
    {id:"2", cell:["cell21", "cell22", "cell23"]}, 

      ...
  ]
The tags used in this example are described in the following table:
PropertyDescription
total total pages for the query
page current page of the query
records total number of records for the query
rows  an array that contains the actual data
id the unique id of the row
cellan array that contains the data for a row

In this case, the number of the elements in the cell array should equal the number of elements in
colModel.


7.1.5 formatter
Formatter can be used in either of two ways: Predefined and Custom.
7.1.5.1 Predefined formats
Default formatting functions are defined in the language files e.g., grid.locale-xx (where xx is your language). To modify these, open the language file and search for "$.jgrid.formatter". Here you will find all the settings that you may want to review or change before using the predifined formats. These settings can also be overridden for specific columns using the FormatOptions parameter, described below.
The second step is to set the desired formatting in colModel. This is done using the option formatter. For example
colModel :[
        ...
    {name:'myname', ... formatter:'number', ...},
        ...
]
will format the contents of the 'myname' column according to the rules set for 'number' in the actve language file.
The predefined types are
  • integer
  • number
  • currency
  • date (uses formats compatable with php function date.)
  • checkbox
  • mail
  • link
  • showlink
  • select (this is not a real select but a special case for editing modules. See note below)

The types are treated as normal strings and must be enclosed in single or double quotes. All predefined types are compatible with the editing modules. This means that the numbers, links, e-mails, etc., are converted so that they can be edited correctly.
Note: Type = 'Select' 
The select type is not real select. This is used when we use some editing module and have edittype:'select'. Before this release we pass the value of the select in grid and not the key. For example:
colModel : [
    {name:'myname', edittype:'select', editoptions:{value:"1:One;2:Two"}}
...
]
In this case the data for the grid should contain "One" or "Two" to be set in the column myname.
Now, with this setting
colModel : [
    {name:'myname', edittype:'select', formatter:'select', editoptions:{value:"1:One;2:Two"}}
...
]
the data should contain the keys "1" or "2", but the value will be displayed in the grid.
7.1.5.2 Custom formats
You can define your own formatter for a particular row. Usually this is a function. When set in the formatter option this should not be enclosed in quotes and not entered with () - show just the name of
the function. For example,
colModel:[
...
   {name:'price', index:'price', width:60, align:"center", editable: true, formatter:currencyFmatter},
...
]
jqGrid passes 3 parameters to this function:
  • cellValue: the cell value
  • opts:  a set of options containing
    • rowId - the id of the row
    • colModel - the colModel for this column
    • rowData - the data for this row
  • rowObject: the object of this row containing the its fields as object field.
The array looks something like this: {rowId: row.id, colModel:cm, rowData:row}
For example:
function folder(cellValue, opts, rowObject) {
        if (!rowObject.file) {
            return "<div class='ui-state-default'><span class='ui-icon ui-icon-folder-collapsed'></span></div>";
        }
        return "<div></div>";
    }
7.1.6 Formatter Options
Formatter options can be defined for particular columns, overwriting the defaults from the language file. This is accomplished by using the formatoptions array in colModel. For example:
colModel : [
...
{name:"myname"... formatter:'currency', formatoptions:{decimalSeparator:",", thousandsSeparator: " ",
decimalPlaces: 2, prefix: "$ "}},
...
This definition will overrite the default one from the language file. In formatoptions should be placed values appropriate for the particular format type
TypeOptions
integer{thousandsSeparator: " ", defaulValue: 0}
number{decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, defaulValue: 0}
currency{decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "",
suffix:"", defaulValue: 0}
date{srcformat: 'Y-m-d',newformat: 'd/m/Y'}
showlink{baseLinkUrl: '',showAction: 'show'}

7.1.7 Navigating
If your grids are all so small that they can display all records at the same time, then you don't need to worry about navigation. But more likely, you will want to display the available records a few at a a time. And for that, you will need the Navigation Bar.
To use this feature we need to enable form editing.
7.1.7.1 HTML
The Navigation Bar, also known as the pager, is defined first in html -- normally, but not necessarily,
placed so it appears at the bottom of the grid. Note that it is a <div>, not a <table>.
<body>
<table id="list" class="scroll"></table>
<div id="pager" class="scroll" style="text-align:center;"></div>
</body> 
7.1.7.2 Grid Definition
The pager is then defined in the grid by a grid property:
pager: '#pager_id'
7.1.7.3 Syntax
Calling Convention:
$("#grid_id").navGrid("#pager",{parameters}) 
Where:
  • grid_id - the id of the already constructed jqGrid.
  • pager - the id of the navigation bar
  • parameters - an array of settings, defined below 
7.1.7.4 Properties
Several properties of the grid govern the function and appearance of the Navigation bar:
PropertyTypeDescriptionDefault
firstimgstringLink to image url for the first buttonfirst.gif
lastimgstringLink to image url for the last buttonlast.gif
nextimgstringLink to image url for the next button next.gif
page integer The requested initial page number when we use datatypes xml or json (data returned from server) 1
pagerDOM
element or
string
Sets the pager bar for the grid. Must be a valid html element. If the element has class “scroll”, then the width is equal to the grid. Usage:  If parameter is a DOM element, jQuery("#mypager");  if using a string, "mypager", where mypager is the id of the pager. Note the missing "#"true
pgbuttonsbooleanDisables or enables pager buttons, if pager is presenttrue
pginputbooleanDisables or enables the input box for current page, if pager is presenttrue
pgtextstringText that appear before the number of total pages"/"
previmgstringLink to image url for the previous button prev.gif
recordtextstringDisplays the text associated with the display of total records; specified value must be in quotes."Rows"
rowListarrayThis parameter constructs a select box element in the pager in which the user can change the number of the visible rows.empty
array
rowNumintegerThe initial number of rows that are be returned from the server20
viewrecordsbooleanDisplay the total records from the query in the pager bar false

7.1.7.5 Methods
The only methods we need are to invoke the pager itself and to add custom buttons, if necessary
MethodParametersReturnsDescription
navGridpager_id,
parameters
jQuery
object
accepts the following settings to govern which buttons appear on the Navigation bar; any of them may be set to true or false. The default for all is true, but may be changed by, for example
{refresh: true, edit: true, add: true, del: false, search: true}
or
{del: false}
The position of these buttons is controlled by a position setting (the default is left):
{add:false,del:false,edit:false,position:"right"}
Parameters for these buttons can be sent by adding them after the main array:
...{add:false,edit:false,del:false},
  {}, // edit parameters
  {}, // add parameters
  {reloadAfterSubmit:false} //delete parameters 
navButtonAdd
jQuery
object
supports adding custom buttons. This method must be chained with the setting of the Standard Buttons. See details and examples in Custom Buttons

7.1.7.6 Custom Buttons
Calling Convention:
jQuery("#grid_id").navGrid("#pager",{standard parameters}).navButtonAdd("#pager",{custom parameters}); 
The Custom parameters are
{ caption:'NewButton',
  buttonicon: "ui-icon-plus",
  onClickButton:function(){
    alert('custom button');
  },
  position "last",
  title:'ToolTip'}
where
  • caption: (string) the caption of the button, can be a empty string.
  • buttonicon: (string) css class name of icon.
  • onClickButton: (function) action to be performed when a button is clicked. Default null.
  • position: ("first" or "last") the position where the button will be added (i.e., before or after the standard buttons).
  • title: (string) a tooltip for the button.
Example

jqGrid provide us more features and facilities but I think that's enough for today. Now Its is the time to take a sample using jqGrid and Spring MVC. As you remember I have post called Spring MVC - MultipartFile. I'm going to extend it. The file list grid was so simple
classic grid
I'm going to change it to this:

file list with jqGrid
This sample is an extension of FileRepository project. So only these classes are added or changed:

1. Pagination
public class Pagination {
    public static final String ASC = "asc";
    public static final String DESC = "desc";
    int currentPage;
    int rowSizePerPage;
    String sortIndex;
    String sortOrder;

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public int getRowSizePerPage() {
        return rowSizePerPage;
    }

    public void setRowSizePerPage(int rowSizePerPage) {
        this.rowSizePerPage = rowSizePerPage;
    }

    public String getSortIndex() {
        return sortIndex;
    }

    public void setSortIndex(String sortIndex) {
        this.sortIndex = sortIndex;
    }

    public String getSortOrder() {
        return sortOrder;
    }

    public void setSortOrder(String sortOrder) {
        this.sortOrder = sortOrder;
    }

    public Boolean isAscending() {
        return sortOrder.equals(ASC);
    }

    public int getFirstResult() {
        return (currentPage - 1) * rowSizePerPage;
    }
}
This is a java bean class containing needed jqGrid paging parameter. I use in BaseController as a field. Whenever jqGrid requests via ajax, it also sends these parameters so these parameters will be bound into pagination field of BaseController class. Then we can use paging(For example current page) information to fetch the needed records. Here is BaseController class:
public abstract class BaseController {
    Pagination pagination;
    Model model;
    HttpServletRequest request;
    HttpServletResponse response;

    public Pagination getPagination() {
        return pagination;
    }

    public void setPagination(Pagination pagination) {
        this.pagination = pagination;
    }

    public Model getModel() {
        return model;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    public HttpServletResponse getResponse() {
        return response;
    }

    public abstract void init();

    @ModelAttribute("command")
    public BaseController init(HttpServletRequest request, HttpServletResponse response, Model model) {
        this.model = model;
        this.request = request;
        this.response = response;
        ServletRequestDataBinder binder = new ServletRequestDataBinder(this, "command");
        binder.bind(request);
        init();
        return this;
    }
}
2. FileController class
This class has some changes in these methods:
2.1 list
@RequestMapping("/list")
    public String list() {
        return "file.list";
    }
List method gets simpler. That's because of jqGrid architecture. The page will be rendered first then jqGrid request the for a list of jsonArray object via ajax. That's why that we are not appointed to set the list of files into the request attribute in List method.
2.2 fileList
@RequestMapping("/fileList")
    public void fileList() throws IOException {
        List<File> fileList = fileService.findAllFiles(getCurrentUser(), parentFolder, getPagination());
        Integer fileListSize = fileService.findAllFilesSize(getCurrentUser(), parentFolder);
        JsonConfig jc = new JsonConfig();
        jc.setExcludes(new String[]{"fileData", "parentFolder", "owner"});
        JSONArray ja = JSONArray.fromObject(fileList, jc);
        getResponse().setCharacterEncoding("UTF8");
        getResponse().getWriter().write(paginationHelper(getPagination(), fileListSize, ja));
    }
This method is triggered via ajax by jqGrid. In this method we should prepare a JSon object containing JSon array of file list(The lines in red. I don't need fileData, parentFolder and owner in client. So I Exclude them to prevent rendering in JSonArray class). First we should find a file list. I do that by fileService and fileDao. Grid needs paging information and list of record. One of paging information is records. That's why that I use findAllFilesSize method. I use paginationHelper method to prepare other grid paging maparameters:
public String paginationHelper(Pagination pagination, int totalRowSize, JSONArray jsonArray) {
        int pageCount = 1;
        if (pagination.getRowSizePerPage() > 0) {
            pageCount = totalRowSize / pagination.getRowSizePerPage();
            if (totalRowSize % pagination.getRowSizePerPage() != 0) {
                pageCount++;
            }
        }
        return "{\"page\":\"" + pagination.getCurrentPage() + "\", \"total\":\"" + pageCount + "\", \"records\":\"" + totalRowSize + "\", \"rows\":" + jsonArray.toString() + "}";
    }
The parameters in red are the needed JSon fields in client congaing paging information(page, total, records) and records list(rows).
2.3 delete
@RequestMapping("/delete")
    public void delete(@RequestParam(value = "id", required = true) String sIds) {
        List<Long> ids = new ArrayList();
        String[] sIdParts = sIds.split(",");
        for (String sId : sIdParts) {
            ids.add(Long.parseLong(sId));
        }
        fileService.deleteAll(ids);
    }
When client user ticks the boxes and press delete key and ajax request will be sent thenthis method will be triggered. The records Ids will be sent in request parameters named 'id'. But the Ids are int CSV format. Here first we should prepare a list of Long from CSV.
2.4 getParentFolders
    public String getParentFolders() {
        String parentFolders = "";
        File folder = parentFolder;
        while (folder != null && folder.getName() != null) {
            parentFolders = "/" + folder.getName() + parentFolders;
            folder = folder.getParentFolder();
        }
        if(parentFolders.equals("")){
            parentFolders = "/";
        }
        return parentFolders;
    }
Here I am going to prepare current folder full path.

3. FileService Class
@Service
public class FileServiceImpl implements FileService {
    @Autowired
    FileDao fileDao;

    public List<File> findAllFiles(User user, File parentFolder, Pagination pagination) {
        return fileDao.findAllFiles(user, parentFolder, pagination);
    }

    public Integer findAllFilesSize(User user, File parentFolder) {
        return fileDao.findAllFilesSize(user, parentFolder);
    }

    ...
} 
I pass Pagination object to findAllFiles method of FileDao class.
4. FileDao Class
@Repository
public class FileDaoImpl extends BaseDao implements FileDao {
    public List<File> findAllFiles(final User user, final File parentFolder, final Pagination pagination) {
        return getHibernateTemplate().execute(new HibernateCallback<List<File>>() {
            public List<File> doInHibernate(Session session) throws HibernateException, SQLException {
                return session.createCriteria(File.class).add(Restrictions.eq("owner", user)).
                        add((parentFolder != null && parentFolder.getId() != null) ? Restrictions.eq("parentFolder", parentFolder) : Restrictions.isNull("parentFolder")).
                        addOrder(Order.asc("file")).addOrder(pagination.isAscending() ? Order.asc(pagination.getSortIndex()) : Order.desc(pagination.getSortIndex())). 

setFirstResult(pagination.getFirstResult()).setFetchSize(pagination.getRowSizePerPage()).list();
            }
        });
    }

    public Integer findAllFilesSize(final User user, final File parentFolder) {
        return getHibernateTemplate().execute(new HibernateCallback<Integer>() {
            public Integer doInHibernate(Session session) throws HibernateException, SQLException {
                return (Integer) session.createCriteria(File.class).setProjection(Projections.count("id")).add(Restrictions.eq("owner", user)).add((parentFolder != null && parentFolder.getId() != null) ? Restrictions.eq("parentFolder", parentFolder) : Restrictions.isNull("parentFolder")).uniqueResult();
            }
        });
    }

    ...
I set pagination data into Hibernate Criteria to fetch only the needed records.

JSPs
These JSP files are changed:
1. /WEB-INF/layout/view.jsp
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<html>
<head>
    <script type="text/javascript" src="<spring:url value="/resources/js/jquery-1.4.4.min.js"/>"></script>
    <script type="text/javascript" src="<spring:url value="/resources/js/grid.locale-en.js"/>"></script>
    <script type="text/javascript" src="<spring:url value="/resources/js/jquery.jqGrid.min.js"/>"></script>

    <link rel="stylesheet" type="text/css" href="<spring:url value="/resources/css/jquery-ui-1.8.6.custom.css"/>"/>
    <link rel="stylesheet" type="text/css" href="<spring:url value="/resources/css/ui.jqgrid.css"/>"/>
</head>
<body>
<div>
    <security:authorize access="isAuthenticated()">
        <a href="/j_spring_security_logout">logout</a> welcome <security:authentication property="name"/>&nbsp;<br/>
        main menu:
        <a href="<spring:url value="/user/list.html"/>">user list</a>&nbsp;
        <a href="<spring:url value="/file/list.html"/>">file repository</a>&nbsp;
        <br/>
        <br/>
    </security:authorize>
    <tiles:insertAttribute name="body"/>
</div>
</body>
</html>
The red lines are needed javascript and css resources for jqGrid which should be imported in every pages.
2. /WEB-INF/pages/file/file-list.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<spring:url value="/file/fileList.html" var="listUrl">
    <c:if test="${not empty command.parentFolder.id}">
        <spring:param name="parentFolder.id" value="${command.parentFolder.id}"/>
    </c:if>
</spring:url>
<script type="text/javascript">
    function folder(cellValue, opts, rowObject) {
        if (!rowObject.file) {
            return "<div class='ui-state-default'><span class='ui-icon ui-icon-folder-collapsed'></span></div>";
        }
        return "<div></div>";
    }
    function edit(cellValue, opts, rowObject) {
        return "<input type='submit' value='edit' onclick='setFileId(" + rowObject.id + "); setFormAction(\"<spring:url value="/file/edit.html"/>\");'/>";
    }

    function view(cellValue, opts, rowObject) {
        if (rowObject.file) {
            return "<a href='<spring:url value="/file/download.html"/>?file.id=" + rowObject.id + "'>download</a>";
        } else {
            return "<a href='<spring:url value="/file/list.html"/>?parentFolder.id=" + rowObject.id + "'>view</a>";
        }
    }

    function setFormAction(action) {
        document.getElementById("fileForm").action = action;
    }

    function setFileId(id) {
        document.getElementById("fileId").value = id;
    }

    $(function() {
        $("#fileGrid").jqGrid({
            url:'${listUrl}',
            datatype:'json',
            contentType:'contentType:"application/json; charset=utf-8"',
            jsonReader:{repeatitems: false,id:"id"},
            direction:'ltr',
            width:'500',
            colNames:['','name','view','edit'],
            colModel:[{sortable:false, width:'20', formatter:folder},{name:'name',sortable:true},{align:'center', sortable:false,formatter:view},{align:'center', sortable:false,formatter:edit}],
            rowNum:10,
            rowList:[10,20,30],
            pager:'#filePager',
            sortname:'id',
            viewrecords:true,
            multiselect:true,
            multiboxonly:true,
            sortorder:'desc',
            prmNames:{rows:'pagination.rowSizePerPage', page:'pagination.currentPage', sort:'pagination.sortIndex', order:'pagination.sortOrder'},
            editurl:'<spring:url value="/file/delete.html"/>',
            caption:'File List',
            recordtext:'View {0} - {1} of {2}',
            emptyrecords:'No Records',
            loadtext:'Loading ...',
            pgtext:'Page {0} Of {1}'
        });
        $("#fileGrid").jqGrid('navGrid', '#filePager', {search:false, edit:false, add:false, del:true}).navButtonAdd("#filePager", {
            caption:'',
            buttonicon: "ui-icon-plus",
            onClickButton:function() {
                setFormAction('<spring:url value="/file/add.html"/>');
                $("#fileForm").submit();
            },
            position:"first", title:'add'});
    });
</script>
<form:form id="fileForm">
    <form:hidden path="file.id" id="fileId"/>
    <form:hidden path="parentFolder.id" id="fileId"/>
    <div style="color:red;">${deleteError}</div>
    <br/>
    <table class='ui-state-default'>
        <tr>
            <td>
                <span class='ui-icon ui-icon-folder-open'></span>
            </td>
            <td>${command.parentFolders}</td>
        </tr>
    </table>
    </table>
    <c:if test="${not empty command.parentFolder.name}">
        <spring:url value="/file/list.html" var="parentFolderUrl">
            <c:if test="${not empty command.parentFolder.parentFolder}">
                <spring:param name="parentFolder.id" value="${command.parentFolder.parentFolder.id}"/>
            </c:if>
        </spring:url>
        <a style="font-weight:bold; font-size:20px"
           href="${parentFolderUrl}">..</a>
    </c:if>
    <br/>
    <br/>
    <table id="fileGrid"></table>
    <div id="filePager"></div>
</form:form>
The red lines are jqGrid setting up in this page. I have 3 formatted columns(1rst, 3rd, 4th ones) which handel them in folder, view and edit javascript methods.

Source Code
It's the time to download the source code and try it yourselves. The application database script is available in [app-root]/db/filerepository.sql. you can restore it in your mysql server. the connection datasource properties is in [app-root]/src/database.properites. and after you deploy the project in you application server (like tomcat) the home page will be: http://localhost:8080/home/view.html also file list page is: http://localhost:8080/file/list.html
the admin user specification is:
username: administrator
password: 123456
you can use it to log in for the first time.


all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

No comments: