Skip to main content

Partial form submit with Grails and JQuery

At times you would want process a part of the form and refresh the page with the results obtained from the partial form submission. Given below is a screen shot of the typical use case. It is a form used in a sales application.



Take a look at the section "Particulars" in the screen. The fields - Item, Unit Price, Quantity, Discount%, Tax% are editable and fields - Total Discount, Total Tax and Grand Total are calculated as the result of clicking "Calculate" link in this section.
There is on option to add multiple rows to this table by selecting "Add" in the first cell of this table. The section of the code used for the table "Particulars" is given below.





<g:form name="myform" action="save" method="post" >

...
...

...

<table id="pList" border="1px" style="width:650px;">
<thead>
<tr>
<td><a href="#" id="addR">Add</a></td>
<th>Item</th>
<th>Unit Price</th>
<th>Quantity</th>
<th>Discount%</th>
<th>Tax%</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<g:if test="${salesBillInstance.lineItems?.size()>0}">

<g:each var="l" in="${salesBillInstance.lineItems}">
<tr>
<td><a href="#" class="delete">Del</a></td>
<td><input type="text" class="sp1" name="item" value="${l.itemName}"/></td>
<td><input style="width:80px;" class="sp1" name="unitPrice" value="${l.unitPrice}" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="qty" value="${l.quantity}" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="discount" value="${l.discount}" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="tax" value="${l.tax}" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="amount" readonly="readonly" value="${l.totalPrice}" type="text"/></td>

</tr>
</g:each>

</g:if>
<g:else>

<tr>
<td><a href="#" class="delete">Del</a></td>
<td><input type="text" class="sp1" name="item"/></td>
<td><input style="width:80px;" class="sp1" name="unitPrice" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="qty" value="1" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="discount" value="0" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="tax" value="0" type="text"/></td>
<td><input style="width:80px;" class="sp1" name="amount" readonly="readonly" type="text"/></td>

</tr>
</g:else>
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<th>Special Disc</th>
<th>Total Disc</th>

<th>Total Tax </th>
<th>Grand Total</th>
</tr>
<tr>
<td></td>
<td></td>
<th><a href="#" id="calcP">Caculate</a></th>
<td><input style="width:80px;" id="totalSD" class="sp1" value="${salesBillInstance.specialDiscountValue}" name="specialDiscountValue" type="text"/></td>
<td><input style="width:80px;" id="totalD" value="${salesBillInstance.discount}" readonly="readonly" type="text"/></td>

<td><input style="width:80px;" id="totalT" value="${salesBillInstance.vat}" readonly="readonly" type="text"/></td>
<td><input style="width:80px;" id="totalA" value="${salesBillInstance.billAmount}" readonly="readonly" type="text"/></td>
</tr>

</tfoot>

</table>


...
...

</g:form>






The following Jquery library and the plugin jquery.form is used. Add the scripts to the gsp page. (create.gsp or edit.gsp)






Note the following key links between the html section and the script section

1. The name of the form is "myform" and the declaration of the variable queryString

queryString = $('#myform .sp1').fieldSerialize();

2. Form fields unitPrice, item, qty, discount, tax etc are defined with css class "sp1". eg: <td><input style="width:80px;" class="sp1" name="unitPrice" type="text"/></td>

and the queryString in the script has reference to sp1 as given below.
queryString = $('#myform .sp1').fieldSerialize();

3. The link between the "Calculate" link and the javascript used to invoke the jquery function.

$('#calcP').click( function() { ...

<tr>
<td></td>
<td></td>
<th><a href="#" id="calcP">Caculate</a></th>
...
</tr>



4. Note the link between the gsp page and the Grails controller viz salesBill.

$.post("${createLinkTo(dir:'/salesBill/calculate')}"

A "calculate" closure has to be defined in the controller class the returns output as JSON.


5. "calculate" closure in the controller class.



def calculate ={

println (params)

def errMsg = null

def items = params.item


def quantities = params.qty
def unitPrice = params.unitPrice
def discount = params.discount
def tax = params.tax



def output = new ArrayList()


if(items == null){

errMsg = '{error:"No data. Add Items for calculation"}'
}
else if (items instanceof String)
{
CalcResult calc = new CalcResult()

try{
int quant = Integer.parseInt(quantities)
double up = Double.parseDouble(unitPrice)
println ("Disc = " + discount)
double disc = Double.parseDouble(discount)
println ("Tax = " + tax)
double tx = Double.parseDouble (tax)
double amt = quant * up

double itemDisc = (disc/100) * amt

double itemTax = (amt - itemDisc) * (tx/100)

amt = (amt - itemDisc + itemTax)

calc.amount = amt.round(2)

calc.discount = itemDisc.round(2)

calc.tax = itemTax.round(2)




}
catch(Exception e)
{
e.printStackTrace()
errMsg = '{error:"data error in row ' + 1 + '"}'

}
if (errMsg == null)
{

if (items == "")
errMsg = "No item added to row 1"
else
output.add(calc)
}

}
else{



for(int i=0;i < items.length;i++ )
{
println ("item = " + items[i])
println ("quantities = " + quantities[i])
println ("unitPrice = " + unitPrice[i])

if (items[i].trim() =="" || quantities[i].trim() == "" ||
unitPrice[i].trim() == "")
{
println ("error ...")
errMsg = '{error:"data error in row ' + (i+1) + '"}'
break
}

CalcResult calc = new CalcResult()

try{

int quant = Integer.parseInt(quantities[i])
double up = Double.parseDouble(unitPrice[i])
double disc = Double.parseDouble(discount[i])
double tx = Double.parseDouble (tax[i])

double amt = quant * up

double itemDisc = (disc/100) * amt

double itemTax = (amt - itemDisc) * (tx/100)

amt = (amt - itemDisc + itemTax)

calc.amount = amt.round(2)



calc.discount = itemDisc.round(2)

calc.tax = itemTax.round(2)

}
catch(Exception e)
{
e.printStackTrace()
errMsg = '{error:"data error in row ' + (i+1) + '"}'
break
}


output.add (calc)

}


}



if (errMsg != null)
render errMsg
else{
println ("output size = " + output.size())
render output as JSON
}
}







6. Note the use of groovy util class used as value object that is converted to json object in the grails "calculate" closure.

class CalcResult {

String item

double unitPrice

double quantity

double tax

double discount

double amount


}

7. Finally calc(json) function used in the javascript that is used to update the calculated fields.





function calc(json){
var i =0;
var grandTotal = 0;
var grandDisc = 0;
var grandTax = 0;

$.each (json, function(){
//alert (this.amount);
var amt = this.amount;
var discount = this.discount;
var tax = this.tax;

var k =0;
$("input[name='amount']").each(function()
{

if (k == i){
$(this).val(amt);
grandTotal = grandTotal + amt;
grandDisc = grandDisc + discount;
grandTax = grandTax + tax;
}
k++;
});
i++;
});

grandTotal = grandTotal - ($('#totalSD').val());

$('#totalA').val(Math.round(grandTotal));
$('#totalD').val(Math.round(grandDisc));
$('#totalT').val(Math.round(grandTax));
// alert (json);

}


Comments

  1. hi sir !
    i had a problem with gsp like
    how can i show the different fields for different peoples?

    ReplyDelete
  2. You need to follow these steps.
    1)Integrate security plugins like acegi or shiro.
    2)Create application roles.
    3)Create users for the application and assign them roles.
    4) use the following gsp tags as shown in the example below.
    <g:ifAnyGranted role="ROLE_ADMIN">
    <a href="${createLink(controller: 'secUser', action:'list')}" >User Admin </a> </g:ifAnyGranted>

    Likewise you can create as many roles as you need and give access accordingly. Note that this is advised only if you want to disable access to certain portions of the within a gsp page.

    If you want to completely disable Page or a URL for a user you can create access control rules using the security plugins listed in step 1.

    ReplyDelete
  3. Hi sir! thanks for previous reply.......
    i had an exception in comparison using inList.
    my code is like this.
    def students = Student.list()
    def lnames= students.collect{it.lastName}
    if(Student.lastName(inList:[lnames]))

    & exception like :groovy.lang.MissingMethodException: No signature of method: com.symfore.student.Student.lastName() is applicable for argument types: (java.util.LinkedHashMap) values: [[inList:[[rani]]]] Possible solutions: getLastName(), setLastName(java.lang.String).....

    how can i resolve it sir..

    ReplyDelete

Post a Comment

Popular posts from this blog

Grails - cross-field Date validation

Often we run into domain classes with date fields. If the domain class has two datefields, startDate and endDate, and the rule for a valid combination is "endDate to be greater than startDate", how do we handle? I listed below two of the options, either using domain level constraints or using the domain classes. Option 1: Using domain constraints. Let us take a sample Grails Domain class class Student{ String name String school Date startDate Date endDate } Add the following constraints to enforce validation rules where name, school, startDate cannot be blank and endDate if present should be greater than startDate.

Grails - Querying complex associations

Criteria class allows performing complex searches on grails objects. There are number of shortcut methods for performing queries but these methods have limitations in terms of number of conditions used in "where clauses". Traditional sql "joins" are not possible as shown in some of the Grails "Finder" methods shown below. Sample 1: def list = AccountTransaction.findAllByCompanyCodeAndVoucherDateBetween(branch, fromDate, toDate, params) Sample 2: def list = AccountTransaction.findAllByCompanyCodeAndVoucherDateGreaterThanEquals(branch, fromDate, params) Sample 3: def list = AccountTransaction.findAllByCompanyCodeAndTransGroup(branch, group, params) "params" contains attributes related to sorting, paging etc. It is very easy to use finder methods but when you want to filter objects by more conditions we need to look for alternatives. For understanding the relationships used in this sample, I listed the grails domain classes. class TransactionTyp

Implementing advanced sort in Grails

The "list" pages generated by inbuilt scaffolding/template features of grails have pagination and sorting features. However, if the domain object displayed in the list is a nested object having another domain object as a property, you may notice that sort is not enabled for that field. Boiler plate code for the header of the list is shown below. As you would have noticed few columns have sortable columns automatically generated by Grails command, generate-all or generate-views. The properties 'partyAccount' and 'bankAccount' in this sample are domain classes nested in the domain class 'partyTransaction'. We could convert them to sortable columns by using the tag g:sortableColum