7.10.4. Displaying the Invoices
The JSP page contains the layout for displaying the grid with invoice headers and the navigation bar. Invoice items are displayed as a drop-down grid when the header of the selected invoice is clicked.
<%@page contentType="text/html" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><c:set var="cp" value="${pageContext.request.servletContext.contextPath}"scope="request" /><!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>An example of a Spring MVC application using Firebird and jOOQ</title><!-- Scripts and styles --><%@ include file="../jspf/head.jspf" %><script src="${cp}/resources/js/jqGridProduct.js"></script><script src="${cp}/resources/js/jqGridCustomer.js"></script><script src="${cp}/resources/js/jqGridInvoice.js"></script></head><body><!-- Navigation menu --><%@ include file="../jspf/menu.jspf" %><div class="container body-content"><h2>Invoices</h2><table id="jqGridInvoice"></table><div id="jqPagerInvoice"></div><hr /><footer><p>© 2016 - An example of a Spring MVC application usingFirebird and jOOQ</p></footer></div><script type="text/javascript">var invoiceGrid = null;$(document).ready(function () {invoiceGrid = JqGridInvoice({baseAddress: '${cp}'});});</script></body></html>
The basic logic on the client side is concentrated in the /resources/js/jqGridInvoice.js JavaScript module.
var JqGridInvoice = (function ($, jqGridProductFactory, jqGridCustomerFactory) {return function (options) {var jqGridInvoice = {dbGrid: null,detailGrid: null,options: $.extend({baseAddress: null}, options),// return invoice model descriptiongetInvoiceColModel: function () {return [{label: 'Id',name: 'INVOICE_ID', // field namekey: true,hidden: true},{label: 'Customer Id'name: 'CUSTOMER_ID',hidden: true,editrules: {edithidden: true, required: true},editable: true,edittype: 'custom', // custom typeeditoptions: {custom_element: function (value, options) {// add hidden inputreturn $("<input>").attr('type', 'hidden').attr('rowid', options.rowId).addClass("FormElement").addClass("form-control").val(value).get(0);}}},{label: 'Date',name: 'INVOICE_DATE',width: 60,sortable: true,editable: true,search: true,edittype: "text", // input typealign: "right",// format as dateformatter: jqGridInvoice.dateTimeFormatter,sorttype: 'date', // sort as dateformatoptions: {srcformat: 'Y-m-d\TH:i:s', // input formatnewformat: 'Y-m-d H:i:s' // output format},editoptions: {// initializing the form element for editingdataInit: function (element) {// creating datepicker$(element).datepicker({id: 'invoiceDate_datePicker',dateFormat: 'dd.mm.yy',minDate: new Date(2000, 0, 1),maxDate: new Date(2030, 0, 1)});}},searchoptions: {// initializing the form element for searchingdataInit: function (element) {// create datepicker$(element).datepicker({id: 'invoiceDate_datePicker',dateFormat: 'dd.mm.yy',minDate: new Date(2000, 0, 1),maxDate: new Date(2030, 0, 1)});},searchoptions: { // search typessopt: ['eq', 'lt', 'le', 'gt', 'ge']}}},{label: 'Customer',name: 'CUSTOMER_NAME',width: 250,editable: true,edittype: "text",editoptions: {size: 50,maxlength: 60,readonly: true},editrules: {required: true},search: true,searchoptions: {sopt: ['eq', 'bw', 'cn']}},{label: 'Amount',name: 'TOTAL_SALE',width: 60,sortable: false,editable: false,search: false,align: "right",// foramt as currencyformatter: 'currency',sorttype: 'number',searchrules: {"required": true,"number": true,"minValue": 0}},{label: 'Paid',name: 'PAID',width: 30,sortable: false,editable: true,search: true,searchoptions: {sopt: ['eq']},edittype: "checkbox",formatter: "checkbox",stype: "checkbox",align: "center",editoptions: {value: "1",offval: "0"}}];},initGrid: function () {// url to retrieve datavar url = jqGridInvoice.options.baseAddress + '/invoice/getdata';jqGridInvoice.dbGrid = $("#jqGridInvoice").jqGrid({url: url,datatype: "json", // data formatmtype: "GET", // http request type// model descriptioncolModel: jqGridInvoice.getInvoiceColModel(),rowNum: 500, // number of rows displayedloadonce: false, // load only once// default sort by INVOICE_DATE columnsortname: 'INVOICE_DATE',sortorder: "desc", // sorting orderwidth: window.innerWidth - 80,height: 500,viewrecords: true, // display the number of entriesguiStyle: "bootstrap",iconSet: "fontAwesome",caption: "Invoices",// pagination elementpager: '#jqPagerInvoice',subGrid: true, // show subGrid// javascript function to display the child gridsubGridRowExpanded: jqGridInvoice.showChildGrid,subGridOptions: {// load only oncereloadOnExpand: false,// load the subgrid string only when you click on the "+"selectOnExpand: true}});},// date format functiondateTimeFormatter: function(cellvalue, options, rowObject) {var date = new Date(cellvalue);return date.toLocaleString().replace(",", "");},// returns a template for the editing dialoggetTemplate: function () {var template = "<div style='margin-left:15px;' id='dlgEditInvoice'>";template += "<div>{CUSTOMER_ID} </div>";template += "<div> Date: </div><div>{INVOICE_DATE}</div>";// customer input field with a buttontemplate += "<div> Customer <sup>*</sup>:</div>";template += "<div>";template += "<div style='float: left;'>{CUSTOMER_NAME}</div> ";template += "<a style='margin-left: 0.2em;' class='btn' ";template += "onclick='invoiceGrid.showCustomerWindow(); ";template += "return false;'>";template += "<span class='glyphicon glyphicon-folder-open'>";template += "</span>Select</a> ";template += "<div style='clear: both;'></div>";template += "</div>";template += "<div> {PAID} Paid </div>";template += "<hr style='width: 100%;'/>";template += "<div> {sData} {cData} </div>";template += "</div>";return template;},// date conversion in UTCconvertToUTC: function(datetime) {if (datetime) {var dateParts = datetime.split('.');var date = dateParts[2].substring(0, 4) + '-' +dateParts[1] + '-' + dateParts[0];var time = dateParts[2].substring(5);if (!time) {time = '00:00:00';}var dt = Date.parse(date + 'T' + time);var s = dt.getUTCFullYear() + '-' +dt.getUTCMonth() + '-' +dt.getUTCDay() + 'T' +dt.getUTCHour() + ':' +dt.getUTCMinute() + ':' +dt.getUTCSecond() + ' GMT';return s;} elsereturn null;},// returns the options for editing invoicesgetEditInvoiceOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/edit',reloadAfterSubmit: true,closeOnEscape: true,closeAfterEdit: true,drag: true,modal: true,top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,template: jqGridInvoice.getTemplate(),afterSubmit: jqGridInvoice.afterSubmit,editData: {INVOICE_ID: function () {var selectedRow = jqGridInvoice.dbGrid.getGridParam("selrow");var value = jqGridInvoice.dbGrid.getCell(selectedRow, 'INVOICE_ID');return value;},CUSTOMER_ID: function () {return $('#dlgEditInvoice input[name=CUSTOMER_ID]').val();},INVOICE_DATE: function () {var datetime = $('#dlgEditInvoice input[name=INVOICE_DATE]').val();return jqGridInvoice.convertToUTC(datetime);}}};},// returns options for adding invoicesgetAddInvoiceOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/create',reloadAfterSubmit: true,closeOnEscape: true,closeAfterAdd: true,drag: true,modal: true,top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,template: jqGridInvoice.getTemplate(),afterSubmit: jqGridInvoice.afterSubmit,editData: {CUSTOMER_ID: function () {return $('#dlgEditInvoice input[name=CUSTOMER_ID]').val();},INVOICE_DATE: function () {var datetime = $('#dlgEditInvoice input[name=INVOICE_DATE]').val();return jqGridInvoice.convertToUTC(datetime);}}};},// returns the options for deleting invoicesgetDeleteInvoiceOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/delete',reloadAfterSubmit: true,closeOnEscape: true,closeAfterDelete: true,drag: true,msg: "Delete the selected invoice?",afterSubmit: jqGridInvoice.afterSubmit,delData: {INVOICE_ID: function () {var selectedRow = jqGridInvoice.dbGrid.getGridParam("selrow");var value = jqGridInvoice.dbGrid.getCell(selectedRow, 'INVOICE_ID');return value;}}};},initPager: function () {// display the navigation barjqGridInvoice.dbGrid.jqGrid('navGrid', '#jqPagerInvoice',{search: true,add: true,edit: true,del: true,view: false,refresh: true,searchtext: "Search",addtext: "Add",edittext: "Edit",deltext: "Delete",viewtext: "View",viewtitle: "Selected record",refreshtext: "Refresh"},jqGridInvoice.getEditInvoiceOptions(),jqGridInvoice.getAddInvoiceOptions(),jqGridInvoice.getDeleteInvoiceOptions());// Add a button to pay the invoicevar urlPay = jqGridInvoice.options.baseAddress + '/invoice/pay';jqGridInvoice.dbGrid.navButtonAdd('#jqPagerInvoice',{buttonicon: "glyphicon-usd",title: "Pay",caption: "Pay",position: "last",onClickButton: function () {// get the id of the current recordvar id = jqGridInvoice.dbGrid.getGridParam("selrow");if (id) {$.ajax({url: urlPay,type: 'POST',data: {INVOICE_ID: id},success: function (data) {// Check if an error has occurredif (data.hasOwnProperty("error")) {jqGridInvoice.alertDialog('??????',data.error);} else {// refresh grid$("#jqGridInvoice").jqGrid('setGridParam',{datatype: 'json'}).trigger('reloadGrid');}}});}}});},init: function () {jqGridInvoice.initGrid();jqGridInvoice.initPager();},afterSubmit: function (response, postdata) {var responseData = response.responseJSON;// Check if an error has occurredif (responseData.hasOwnProperty("error")) {if (responseData.error.length) {return [false, responseData.error];}} else {// refresh grid$(this).jqGrid('setGridParam',{datatype: 'json'}).trigger('reloadGrid');}return [true, "", 0];},getInvoiceLineColModel: function (parentRowKey) {return [{label: 'Invoice Line ID',name: 'INVOICE_LINE_ID',key: true,hidden: true},{label: 'Invoice ID',name: 'INVOICE_ID',hidden: true,editrules: {edithidden: true, required: true},editable: true,edittype: 'custom',editoptions: {custom_element: function (value, options) {// create hidden inputreturn $("<input>").attr('type', 'hidden').attr('rowid', options.rowId).addClass("FormElement").addClass("form-control").val(parentRowKey).get(0);}}},{label: 'Product ID',name: 'PRODUCT_ID',hidden: true,editrules: {edithidden: true, required: true},editable: true,edittype: 'custom',editoptions: {custom_element: function (value, options) {// create hidden inputreturn $("<input>").attr('type', 'hidden').attr('rowid', options.rowId).addClass("FormElement").addClass("form-control").val(value).get(0);}}},{label: 'Product',name: 'PRODUCT_NAME',width: 300,editable: true,edittype: "text",editoptions: {size: 50,maxlength: 60,readonly: true},editrules: {required: true}},{label: 'Price',name: 'SALE_PRICE',formatter: 'currency',editable: true,editoptions: {readonly: true},align: "right",width: 100},{label: 'Quantity',name: 'QUANTITY',align: "right",width: 100,editable: true,editrules: {required: true, number: true, minValue: 1},editoptions: {dataEvents: [{type: 'change',fn: function (e) {var quantity = $(this).val() - 0;var price =$('#dlgEditInvoiceLine input[name=SALE_PRICE]').val()-0;var total = quantity * price;$('#dlgEditInvoiceLine input[name=TOTAL]').val(total);}}],defaultValue: 1}},{label: 'Total',name: 'TOTAL',formatter: 'currency',align: "right",width: 100,editable: true,editoptions: {readonly: true}}];},// returns the options for editing the invoice itemgetEditInvoiceLineOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/editdetail',reloadAfterSubmit: true,closeOnEscape: true,closeAfterEdit: true,drag: true,modal: true,top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,template: jqGridInvoice.getTemplateDetail(),afterSubmit: jqGridInvoice.afterSubmit,editData: {INVOICE_LINE_ID: function () {var selectedRow = jqGridInvoice.detailGrid.getGridParam("selrow");var value = jqGridInvoice.detailGrid.getCell(selectedRow, 'INVOICE_LINE_ID');return value;},QUANTITY: function () {return $('#dlgEditInvoiceLine input[name=QUANTITY]').val();}}};},// returns options for adding an invoice itemgetAddInvoiceLineOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/createdetail',reloadAfterSubmit: true,closeOnEscape: true,closeAfterAdd: true,drag: true,modal: true,top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,template: jqGridInvoice.getTemplateDetail(),afterSubmit: jqGridInvoice.afterSubmit,editData: {INVOICE_ID: function () {var selectedRow = jqGridInvoice.dbGrid.getGridParam("selrow");var value = jqGridInvoice.dbGrid.getCell(selectedRow, 'INVOICE_ID');return value;},PRODUCT_ID: function () {return $('#dlgEditInvoiceLine input[name=PRODUCT_ID]').val();},QUANTITY: function () {return $('#dlgEditInvoiceLine input[name=QUANTITY]').val();}}};},// returns the option to delete the invoice itemgetDeleteInvoiceLineOptions: function () {return {url: jqGridInvoice.options.baseAddress + '/invoice/deletedetail',reloadAfterSubmit: true,closeOnEscape: true,closeAfterDelete: true,drag: true,msg: "Delete the selected item?",afterSubmit: jqGridInvoice.afterSubmit,delData: {INVOICE_LINE_ID: function () {var selectedRow = jqGridInvoice.detailGrid.getGridParam("selrow");var value = jqGridInvoice.detailGrid.getCell(selectedRow, 'INVOICE_LINE_ID');return value;}}};},// Event handler for the parent grid expansion event// takes two parameters: the parent record identifier// and the primary record keyshowChildGrid: function (parentRowID, parentRowKey) {var childGridID = parentRowID + "_table";var childGridPagerID = parentRowID + "_pager";// send the primary key of the parent record// to filter the entries of the invoice itemsvar childGridURL = jqGridInvoice.options.baseAddress+ '/invoice/getdetaildata';childGridURL = childGridURL + "?INVOICE_ID="+ encodeURIComponent(parentRowKey);// add HTML elements to display the table and page navigation// as children for the selected row in the master grid$('<table>').attr('id', childGridID).appendTo($('#' + parentRowID));$('<div>').attr('id', childGridPagerID).addClass('scroll').appendTo($('#' + parentRowID));// create and initialize the child gridjqGridInvoice.detailGrid = $("#" + childGridID).jqGrid({url: childGridURL,mtype: "GET",datatype: "json",page: 1,colModel: jqGridInvoice.getInvoiceLineColModel(parentRowKey),loadonce: false,width: '100%',height: '100%',guiStyle: "bootstrap",iconSet: "fontAwesome",pager: "#" + childGridPagerID});// displaying the toolbar$("#" + childGridID).jqGrid('navGrid', '#' + childGridPagerID,{search: false,add: true,edit: true,del: true,refresh: true},jqGridInvoice.getEditInvoiceLineOptions(),jqGridInvoice.getAddInvoiceLineOptions(),jqGridInvoice.getDeleteInvoiceLineOptions());},// returns a template for the invoice item editorgetTemplateDetail: function () {var template = "<div style='margin-left:15px;' ";template += "id='dlgEditInvoiceLine'>";template += "<div>{INVOICE_ID} </div>";template += "<div>{PRODUCT_ID} </div>";// input field with a buttontemplate += "<div> Product <sup>*</sup>:</div>";template += "<div>";template += "<div style='float: left;'>{PRODUCT_NAME}</div> ";template += "<a style='margin-left: 0.2em;' class='btn' ";template += "onclick='invoiceGrid.showProductWindow(); ";template += "return false;'>";template += "<span class='glyphicon glyphicon-folder-open'>";template += "</span> Select</a> ";template += "<div style='clear: both;'></div>";template += "</div>";template += "<div> Quantity: </div><div>{QUANTITY} </div>";template += "<div> Price: </div><div>{SALE_PRICE} </div>";template += "<div> Total: </div><div>{TOTAL} </div>";template += "<hr style='width: 100%;'/>";template += "<div> {sData} {cData} </div>";template += "</div>";return template;},// Display selection window from the goods directory.showProductWindow: function () {var dlg = $('<div>').attr('id', 'dlgChooseProduct').attr('aria-hidden', 'true').attr('role', 'dialog').attr('data-backdrop', 'static').css("z-index", '2000').addClass('modal').appendTo($('body'));var dlgContent = $("<div>").addClass("modal-content").css('width', '760px').appendTo($('<div>').addClass('modal-dialog').appendTo(dlg));var dlgHeader = $('<div>').addClass("modal-header").appendTo(dlgContent);$("<button>").addClass("close").attr('type', 'button').attr('aria-hidden', 'true').attr('data-dismiss', 'modal').html("×").appendTo(dlgHeader);$("<h5>").addClass("modal-title").html("Select product").appendTo(dlgHeader);var dlgBody = $('<div>').addClass("modal-body").appendTo(dlgContent);var dlgFooter = $('<div>').addClass("modal-footer").appendTo(dlgContent);$("<button>").attr('type', 'button').addClass('btn').html('OK').on('click', function () {var rowId = $("#jqGridProduct").jqGrid("getGridParam", "selrow");var row = $("#jqGridProduct").jqGrid("getRowData", rowId);$('#dlgEditInvoiceLine input[name=PRODUCT_ID]').val(row["PRODUCT_ID"]);$('#dlgEditInvoiceLine input[name=PRODUCT_NAME]').val(row["NAME"]);$('#dlgEditInvoiceLine input[name=SALE_PRICE]').val(row["PRICE"]);var price = $('#dlgEditInvoiceLine input[name=SALE_PRICE]').val()-0;var quantity = $('#dlgEditInvoiceLine input[name=QUANTITY]').val()-0;var total = Math.round(price * quantity * 100) / 100;$('#dlgEditInvoiceLine input[name=TOTAL]').val(total);dlg.modal('hide');}).appendTo(dlgFooter);$("<button>").attr('type', 'button').addClass('btn').html('Cancel').on('click', function () {dlg.modal('hide');}).appendTo(dlgFooter);$('<table>').attr('id', 'jqGridProduct').appendTo(dlgBody);$('<div>').attr('id', 'jqPagerProduct').appendTo(dlgBody);dlg.on('hidden.bs.modal', function () {dlg.remove();});dlg.modal();jqGridProductFactory({baseAddress: jqGridInvoice.options.baseAddress});},// Display the selection window from the customer's directory.showCustomerWindow: function () {// the main block of the dialogvar dlg = $('<div>').attr('id', 'dlgChooseCustomer').attr('aria-hidden', 'true').attr('role', 'dialog').attr('data-backdrop', 'static').css("z-index", '2000').addClass('modal').appendTo($('body'));// block with the contents of the dialogvar dlgContent = $("<div>").addClass("modal-content").css('width', '730px').appendTo($('<div>').addClass('modal-dialog').appendTo(dlg));// block with dialog headervar dlgHeader = $('<div>').addClass("modal-header").appendTo(dlgContent);// button "X" for closing$("<button>").addClass("close").attr('type', 'button').attr('aria-hidden', 'true').attr('data-dismiss', 'modal').html("×").appendTo(dlgHeader);// title of dialog$("<h5>").addClass("modal-title").html("Select customer").appendTo(dlgHeader);// body of dialogvar dlgBody = $('<div>').addClass("modal-body").appendTo(dlgContent);// footer of dialogvar dlgFooter = $('<div>').addClass("modal-footer").appendTo(dlgContent);// "OK" button$("<button>").attr('type', 'button').addClass('btn').html('OK').on('click', function () {var rowId = $("#jqGridCustomer").jqGrid("getGridParam", "selrow");var row = $("#jqGridCustomer").jqGrid("getRowData", rowId);// Keep the identifier and the name of the customer// in the input elements of the parent form.$('#dlgEditInvoice input[name=CUSTOMER_ID]').val(rowId);$('#dlgEditInvoice input[name=CUSTOMER_NAME]').val(row["NAME"]);dlg.modal('hide');}).appendTo(dlgFooter);// "Cancel" button$("<button>").attr('type', 'button').addClass('btn').html('Cancel').on('click', function () {dlg.modal('hide');}).appendTo(dlgFooter);// add a table to display the customers in the body of the dialog$('<table>').attr('id', 'jqGridCustomer').appendTo(dlgBody);// add the navigation bar$('<div>').attr('id', 'jqPagerCustomer').appendTo(dlgBody);dlg.on('hidden.bs.modal', function () {dlg.remove();});// display dialogdlg.modal();jqGridCustomerFactory({baseAddress: jqGridInvoice.options.baseAddress});},// A window for displaying the error.alertDialog: function (title, error) {var alertDlg = $('<div>').attr('aria-hidden', 'true').attr('role', 'dialog').attr('data-backdrop', 'static').addClass('modal').appendTo($('body'));var dlgContent = $("<div>").addClass("modal-content").appendTo($('<div>').addClass('modal-dialog').appendTo(alertDlg));var dlgHeader = $('<div>').addClass("modal-header").appendTo(dlgContent);$("<button>").addClass("close").attr('type', 'button').attr('aria-hidden', 'true').attr('data-dismiss', 'modal').html("×").appendTo(dlgHeader);$("<h5>").addClass("modal-title").html(title).appendTo(dlgHeader);$('<div>').addClass("modal-body").appendTo(dlgContent).append(error);alertDlg.on('hidden.bs.modal', function () {alertDlg.remove();});alertDlg.modal();}};jqGridInvoice.init();return jqGridInvoice;};})(jQuery, JqGridProduct, JqGridCustomer);
Displaying and Editing Invoice Lines
In the invoice module, the main grid is used to display headers and the detail grid, opened with a click, is used to display invoice items. For the child grid to be displayed, the True value is assigned to the subGrid property. The child grid is displayed using the subGridRowExpanded event connected with the showChildGrid method.
The items are filtered by the primary key of the invoice. Along with the main buttons on the navigation bar, a custom button for paying for the invoice is added to the invoice header using the jqGridInvoice.dbGrid.navButtonAdd function (see the initPager method).
Dialog Boxes
Dialog boxes for editing secondary modules are much more complicated than their primary counterparts. They often use options selected from other modules. For that reason, these edit dialog boxes cannot be built automatically using jqGrid. However, this library has an option to build dialog boxes using templates, which we use.
The dialog box template is returned by the getTemplate function. The invoiceGrid.showCustomerWindow() function opens the customer module for selecting a customer. It uses the functions of the JqGridCustomer module described earlier. After the customer is selected in the modal window, its key is inserted into the CUSTOMER_ID field. Fields that are to be sent to the server using pre-processing or from hidden fields are described in the editData property of the Edit and Add options.
Processing Dates
To get back to processing dates: as we already know, the InvoiceController controller returns the date in UTC. Because we want to display it in the current time zone, we specify the jqGridInvoice.dateTimeFormatter date formatting function via the formatter property of the corresponding INVOICE_DATE field.
When sending data to the server, we need the reverse operation — convert time from the current time zone to UTC. The convertToUTC function is responsible for that.
The custom template returned by the getTemplateDetail function is also used for editing invoice items. The invoiceGrid.showProductWindow() function opens a window for selecting a product from the product list. This function uses the functions of the JqGridProduct module.
The code for the JqGridInvoice module contains detailed comments and more explanation so that you can understand the logic of its workings.
