PDA

View Full Version : Grails, jQuery & the jQuery Grid - Part Four



dave
10-04-2010, 11:48 AM
The fourth installment of my Grails & jQuery grid tutorial is to cover adding, editing and deleting data.

I also want to spend a little time tweaking the interface to take advantage of some of the core jQuery functionality to improve the user experience.

1. Adding Data

The jQuery grid already has a built-in system that provides us with a popup form based UI for adding new records. All we need to do is change our grid definition to enable it and let it know we want to provide the user with the ability to add.

To accomplish this we can use the 'navgrid' method which allows us to add buttons to perform adding/editing/deleting, searching and refreshing of the grid.

The navgrid method takes several sets of options for controlling which buttons to show, and individual options for adding/editing and deleting as shown in the code below.

Add the navGrid method to your jqGrid definition so your whole call to jqGrid looks as below.


jQuery("#customer_list").jqGrid({
url:'jq_customer_list',
datatype: "json",
colNames:['First Name','Last Name','Age','Email Address','id'],
colModel:[
{name:'firstName'},
{name:'lastName'},
{name:'age'},
{name:'emailAddress'},
{name:'id',hidden:true}
],
rowNum:2,
rowList:[1,2,3,4],
pager:'#customer_list_pager',
viewrecords: true,
gridview: true
}).navGrid('#customer_list_pager',
{add:true,edit:false,del:false,search:false,refres h:false}, // which buttons to show?
{}, // edit options
{addCaption:'Create New Customer'}, // add options
{} // delete options
);
By default when you use the navGrid, buttons are set to appear by default, so I have indicated all buttons are disabled except for the add button.
I have also indicated that the add dialog has a custom caption of 'Create New Customer'.

If you run the tutorial now, you will see the Add button appear in the pager area marked with a image of a 12. Alternative images can be specified in the options but that is beyond the scope of this tutorial.



By clicking the add button, you will see the dialog appear as below, but there is a obvious issue in that no fields are present!

11

We need to now instruct the grid that fields in our column model should be used when modifying/adding data. This is accomplished by adding editable:true to each set of column options, e.g.


{name:'firstName', editable:true}Mark each field as editable except for the id column and re-run your application to see the new add dialog.

13

That's better but the age column certainly doesn't need to be that wide, thankfully when a column is marked as being editable, we can also supply edit options which will let us change the width.

Change the age colmodel entry to include a size option:


{name:'age',
editable:true,
editoptions:{size:3}
}, I will also increase the size of the email to 30.

Running the application again we will see the age field in the dialog has been resized accordingly.

We can also add a few rules to apply some basic validation before the data is submitted by making use of editrules. In this case I will indicate that all fields are required, that the age field should be an integer and that the email address should be a valid email.

The entire colModel section now should be:


colModel:[
{name:'firstName',
editable:true,
editrules:{required:true}
},
{name:'lastName',
editable:true,
editrules:{required:true}
},
{name:'age',
editable:true,
editoptions:{size:3},
editrules:{required:true,integer:true}
},
{name:'emailAddress',
editable:true,
editoptions:{size:30},
editrules:{required:true,email:true}
},
{name:'id',hidden:true}
],And if you run the application now, you can test out the validation rules, this example show an incorrect value in the age field:

So far, so good, we can validate the data before sending to the server, but we still need to add to our controller a method which will process the data sent and persist it. In our grid definition we define the url to retrieve data as 'jq_customer_list', an appropriate name for the edit/action would be jq_edit_customer so add that as the 'editurl' property underneath the 'url' property in the grid definition so you will now have:


url:'jq_customer_list',
editurl:'jq_edit_customer',and in the controller, create the corresponding new method as below.


def jq_edit_customer = {

}Note we will need to interrogate the request parameters to determine the data AND the operation we want to perform, i.e. Add. To see all the parameters the grid sends, see what is output in the server log when you place a 'println params' statement in our method and try adding a new record. You should see something like


This single method will be used for multiple functions, Adding, Editing and Deleting hence the requirement of the 'oper' parameter.

So a basic method that will fulfill the task of adding data would be:


def jq_edit_customer = {
def customer = null

// determine our action
switch (params.oper) {
case 'add':
// add instruction sent
customer = new Customer(params)
if (customer.hasErrors() || ! customer.save())
customer = null

break;
}

// return our customer object as JSON
render customer as JSON
}This simply firstly checks the operation we want to perform, if it is an ADD operation then create a new customer object based on the parameters sent, persist it and return the object as a JSON representation.

This works fine, but needs some necessary adjustments. If you run the application with just the above changes, You may not see the data being added to the table unless you keep a close eye on the number of records. There is also no status of the operation being passed back to the user so we really need to let the user know of the result of the add operation.

What we can do to gain more control is hook into the 'afterSubmit' event. We can add our own function that fires after the submit, and interprets the server response. As we're taking over the afterSubmit event we can change the controller to return whatever we want as long as we interpret the response in the event.

Firstly though, I want to create a 'DIV' placeholder for us to let the user know any 'success' messages we have. This will go on the main page, any 'errors' I want to be displayed within the add dialog itself.

Add a div placeholder to list.gsp just under the Customer List H1 heading as below;


<div id='message' class="message" style="display:none;"></div>Note: We predefine the style display:none so it will only be shown when we need it to.

Change the add/edit controller method to the following:


def jq_edit_customer = {
def customer = null
def message = ""
def state = "FAIL"
def id

// determine our action
switch (params.oper) {
case 'add':
// add instruction sent
customer = new Customer(params)
if (! customer.hasErrors() && customer.save()) {
message = "Customer ${customer.firstName} ${customer.lastName} Added"
id = customer.id
state = "OK"
} else {
message = "Could Not Save Customer"
}

break;
}

def response = [message:message,state:state,id:id]

render response as JSON
} Now instead of passing back the customer object as JSON, we are sending a message, the state (either 'FAIL' or 'OK') and the id of the customer if it's new.

Change your 'addOptions' definition to include the new afterSubmit event we're about to add:


{addCaption:'Create New Customer',
afterSubmit:afterSubmitEvent}, // add options Now add the afterSubmitEvent code, (add this function just above the ending </script> tag) :


function afterSubmitEvent(response, postdata) {
var success = true;

var json = eval('(' + response.responseText + ')');
var message = json.message;

if(json.state == 'FAIL') {
success = false;
} else {
$('#message').html(message);
$('#message').show().fadeOut(10000);
}

var new_id = json.id
return [success,message,new_id];
}
In this function we now interpret the JSON response. If it is a failure, we set success to false. This will trigger the dialog to display the contents of the message variable in the dialog.

If it's not FAIL, then we use a quick jQuery snippet to place the message text in our 'DIV' placeholder, then set it visible with .show(), and then fade it out after 10 seconds.

16

You will have noticed the dialog stays open after we've added a new record, this behaviour is optional and can be turned off by setting the option 'closeAfterAdd' to true.

By leaving the dialog open we enable to the user to enter data faster into the system than by closing it and having the user click the add button each time. An alternative would be to have an input form constantly available on the main page.

Another basic UI improvment for the user would be to enable the dialog to submit data when the user presses the enter key, this is always a good idea so the user doesn't have to reach for the mouse after each record is entered just to click submit.

This is accomplished by setting the option 'savekey' to [true,13]. The true represents we want to enable the feature and the 13 represents the ASCII key code, i.e. Enter.


2. Editing Data

The method I want to describe for editing data closely follows the steps we have already taken for adding new records via the popup dialog.

An alternative to using the dialog form of data modification is to use inline editing. This means we can edit directly the data on the rows in the grid, or even further just allow cell editing.

I will create a seperate tutorial that should be considered as optional to this series to show how you can use inline editing with grails and the grid.

Firstly enable editing by setting the edit property to true in the navGrid call :


{add:true,edit:true,del:false,search:false,refresh :true},

This will provide you with the edit button on the grid toolbar 14
You will already be able to select a row, and click the edit button and see the popup dialog displayed with the data presented.

I want to also indicate that the dialog should be closed after submitting data, when editing, this we do with the closeAfterEdit parameter.

I also want the edit dialog to use the same callback event after submitting that the add dialog used, so also set the afterSubmit property in the edit options, so the entire edit options set looks like:


{closeAfterEdit:true,
afterSubmit:afterSubmitEvent
},All we need to do to complete the functionality in the dialog is to ensure our controller will respond when we submit the data, so we must add functionality for when the 'oper' param is 'edit'

Add the following case block to the switch statement to respond to an 'edit' request:

We can either setup a case condition specifically for 'edit', or just add code to the 'default' switch condition. I highlight this now, as if you decide to also use inline editing, when the request is made to the grids edit url, inline edits do not also supply a 'oper' parameter, so you would need to use the default switch condition.

So in short, either start the next block with case 'edit': for extra code clarity or case default: if you may also use inline editing.


case 'edit':
// edit action
// first retrieve the customer by its ID
customer = Customer.get(params.id)
if (customer) {
// set the properties according to passed in parameters
customer.properties = params
if (! customer.hasErrors() && customer.save()) {
message = "Customer ${customer.firstName} ${customer.lastName} Updated"
id = customer.id
state = "OK"
} else {
message = "Could Not Update Customer"
}
}
break;That's all we need to do, running now you should be able to edit each record and see a notification claiming the update was successful. Notice how the grid also updates automatically upon submit.

3. Deleting Data

The changes we now have to make to allow the user to delete records is very minimal as we are building on our existing code.

Firstly, we need to enable the Delete button. Change the navgrid options, to enable the 'del' property:


add:true,edit:true,del:true,search:false,refresh:t rue

We also want to let the user know if the deletion was successful so we should again hook into the afterSubmit event by adding afterSubmit:afterSubmitEvent to the delete options.

And finally we just need to add the necessary controller additions:


case 'del':
// check customer exists
customer = Customer.get(params.id)
if (customer) {
// delete customer
customer.delete()
message = "Customer ${customer.firstName} ${customer.lastName} Deleted"
state = "OK"
}
break;When you delete a customer now, you should be seeing the confirmation dialog, and when you confirm the deletion, you should also see the fading message above the grid.

[I]Miscellaneous Tweaks

I personally prefer NOT to use the grid's own toolbar and present the user with full buttons that perform the add,edit and delete functions.

To accomplish this first define your button area, in this case place this div underneath the div tag for the customer_list_pager:


<div style="margin-top:5px">
<input class="ui-corner-all" id="btnAdd" type="button" value="Add Record"/>
<input class="ui-corner-all" id="btnEdit" type="button" value="Edit Selected Record"/>
<input class="ui-corner-all" id="btnDelete" type="button" value="Delete Selected Record"/>
</div>Now just to wire up the buttons to the grid functionality, add the following immediately after your $(document).ready(function () { line


// set on click events for non toolbar buttons
$("#btnAdd").click(function(){
$("#customer_list").jqGrid("editGridRow","new",
{addCaption:'Create New Customer',
afterSubmit:afterSubmitEvent,
savekey:[true,13]});
});

$("#btnEdit").click(function(){
var gr = $("#customer_list").jqGrid('getGridParam','selrow');
if( gr != null )
$("#customer_list").jqGrid('editGridRow',gr,
{closeAfterEdit:true,
afterSubmit:afterSubmitEvent
});
else
alert("Please Select Row");
});

$("#btnDelete").click(function(){
var gr = $("#customer_list").jqGrid('getGridParam','selrow');
if( gr != null )
$("#customer_list").jqGrid('delGridRow',gr,
{afterSubmit:afterSubmitEvent});
else
alert("Please Select Row to delete!");
});
That will now give you 3 new functioning buttons underneath your main table:

15

That concludes the basic form based editing section of this tutorial. As mentioned I will create a separate tutorial for those interested in using inline cell or row editing.



A full set of edit options can be viewed here : http://www.trirand.com/jqgridwiki/doku.php?id=wiki:common_rules#common_editing_prope rties

rvauradkar
29-07-2010, 12:54 AM
Great tutorial!
Looking forward to inline row a and cell editing!

bnawalage
03-08-2010, 12:53 PM
wow. nice tutorial.
in my example email address not saved.
so i changed col name to emailAddress instead use index property.

jnorman67
02-09-2010, 06:29 PM
Very nice. This works as advertised. I'd like to find an equivalent for jsTree. If none exists, perhaps I'll write one. I'm inspired by your work, you see.

dave
03-09-2010, 01:09 PM
Thanks, I do want to expand the set of tutorials, haven't quite decided what next, as doing a fair bit of Android development at the moment, the Grails work is on a lower priority but depending what seems to be in demand I'll try and find time to add some more. I noticed someone was asking how to do Ajax Master/Detail pages on nabble, that would be a very quick addition so that will probably come next.




Very nice. This works as advertised. I'd like to find an equivalent for jsTree. If none exists, perhaps I'll write one. I'm inspired by your work, you see.

sminath3
13-09-2010, 09:59 PM
Did you ever add the separate inline cell or row editing example?

Thanks for the tutorial, it was great!

dave
16-09-2010, 09:16 AM
I can certainly make that the focus of my next one.

Kamaraj
21-10-2010, 05:15 PM
Thanks a lot for the tutorial! It helped a lot! But it appears that when I try to edit a row or add a new row, the popup window does not close after entering the details and clicking on submit. I had to click on "cancel" and then reload the grid either by refreshing the page or by clicking on the reload icon on the bottom of the grid. Is there a way around this? Would appreciate a lot if this can be resolved. Thanks a lot in advance!

dave
20-01-2011, 11:01 AM
Thanks a lot for the tutorial! It helped a lot! But it appears that when I try to edit a row or add a new row, the popup window does not close after entering the details and clicking on submit. I had to click on "cancel" and then reload the grid either by refreshing the page or by clicking on the reload icon on the bottom of the grid. Is there a way around this? Would appreciate a lot if this can be resolved. Thanks a lot in advance!

Check your code/grid definition for the presence of 'closeAfterEdit' , e.g.


$("#btnEdit").click(function(){
var gr = $("#customer_list").jqGrid('getGridParam','selrow');
if( gr != null )
$("#customer_list").jqGrid('editGridRow',gr,
{closeAfterEdit:true,
afterSubmit:afterSubmitEvent
});
else
alert("Please Select Row");
});

if thats present it should automatically close the popup dialog after clicking submit.

Anamika
21-09-2011, 09:47 PM
I am doing inline edit for jqGrid, I am not able to post the data on the server , I can edit the grid but changes dont not reflect in the database also when I refresh the grid all the changes I made goes away, I had been trying to make it work, but no luck, I would appreciate your help

here is the code I am trying to make it work



afterEditCell: function(rowid, cellname, value, irow, icol) {
if (window.pageXOffset) {window.pageXOffset = scrollPosition;};
if (document.body.parentElement) {document.body.parentElement.scrollLeft = scrollPosition;};
if (document.body) {document.body.scrollLeft = scrollPosition
}
jQuery("#grid_list").jqGrid('saveRow',rowid, false)
},

onSelectCell: function(id){
if(id && id!==lastsel){
// jQuery('#grid_list').jqGrid('restoreRow',lastsel);
jQuery('#grid_list').jqGrid('editRow',id, true);
jQuery("#grid_list").jqGrid('saveRow',id, false);
lastsel=id;
}
var cont = $('#grid_list').getCell(id, 'MyCol');
var val = getCellValue(cont);
},

gridComplete: function(){
var ids = jQuery("#grid_list").jqGrid('getDataIDs');
for(var i=0;i < ids.length;i++){
jQuery("#grid_list").jqGrid('saveRow',ids[i], false)
jQuery("#grid_list").jqGrid('setRowData', ids[i], false);
}
},

pager: jQuery("#grid_list_pager"),
viewrecords: true,
gridview: true,
height: 500,
// forceFit: true,
cellEdit:true,
caption: "All_Defects",
cellurl : 'jq_defect_edit',
cellsubmit: 'remote',
multiselect: false,



}).navGrid('#grid_list_pager',
{add:true,edit:true,del:true,search:false,refresh: true}, // which buttons to show?
{closeAfterEdit:true,
afterSubmit:afterSubmitEvent
}, // edit options
{addCaption:'Create New defect',
afterSubmit:afterSubmitEvent,
savekey:[true,13]}, // add options
{afterSubmit:afterSubmitEvent} // delete options
);
});


function afterSubmitEvent(response, postdata) {
var success = true;

var json = eval('(' + response.responseText + ')');
var message = json.message;

if(json.state == 'FAIL') {
success = false;
} else {
$('#message').html(message);
$('#message').show().fadeOut(10000);
}

var new_id = json.id
return [success,message,new_id];
}


</script>
</div>
</body>
</html>

dave
22-09-2011, 12:00 PM
Hi Anamika,

What ajax output are you seeing when the save call is made? Are you certain the server is receiving the right data?

When debugging this sort of task, I tend to add a quick 'println params' as the first line of the controller action I'm testing, and the check the ajax call in the browser. Either by using firebug when I'm using Firefox, or if I'm using Chrome, it has the same functionality built in.

Ghost
08-03-2012, 03:43 PM
I want to change the add function to the default create action from grails, so that the page is called to create a new object with the create page. So if I click the + button i want to redirect to a the default create page from grails.

Can you tell me how I can manage that?

The code i have now is:
// set on click events for non toolbar buttons
$("#btnAdd").click(function(){
$("#hond_list").jqGrid("editGridRow","new",
{addCaption:'Creeer nieuwe hond',
afterSubmit:afterSubmitEvent,
savekey:[true,13]});
});

dave
08-03-2012, 04:38 PM
These days I tend not to use the buttons on the grid navbar itself as I find them too small, so would prefer a big 'Add <name>' button as opposed to the small '+' icon.

However to do what I think you want, I would first disabled the 'add' button from the pager by setting 'add:false', then I would add a custom button to the pager using the navButtonAdd method. Example:


.navButtonAdd('#Pager',{ caption:"Add",
buttonimg:"fullpath/row_add.gif",
onClickButton: function(){ window.location("<g:createLinkTo action:'create'/>") },
position:"last" })

At least something along those lines, note thats totally untested!

Check out http://www.trirand.com/jqgridwiki/doku.php?id=wiki:custom_buttons for more info.



I want to change the add function to the default create action from grails, so that the page is called to create a new object with the create page. So if I click the + button i want to redirect to a the default create page from grails.

Can you tell me how I can manage that?

The code i have now is:
// set on click events for non toolbar buttons
$("#btnAdd").click(function(){
$("#hond_list").jqGrid("editGridRow","new",
{addCaption:'Creeer nieuwe hond',
afterSubmit:afterSubmitEvent,
savekey:[true,13]});
});

Ghost
12-03-2012, 04:14 PM
The link is on the navgrid but it won't work like this:

onClickButton: function(){ window.location("<g:createLinkTo class:'create' action:'create'/>") },
Can you help me futher?

lmayfort
13-08-2012, 11:03 PM
Awesome, this was very helpful. Thanks.

afrieden
16-01-2013, 08:27 PM
Great Tutorial thanks!

nazeer
05-10-2013, 09:44 AM
just use below code
var lastsel2;
$(document).ready(function() {
.................
.................
onSelectRow:function(id)
{
var rowData = jQuery(this).editRow(id,true);
lastsel2=id;


}
......
......
});