5.10.3. Dialog Boxes for Invoices
The dialog boxes for editing secondary sets of data are much more complicated than for the primary sets. Since they often use options selected from other modules, it will not be possible to use the standard jqGrid methods to build these edit dialog boxes. However, this library has an option to build dialog boxes using templates, which we will use.
To enable customer selection, we will create a read-only field with a button at its right-hand side for opening the form displaying the customer selection grid.
// returns properties to create edit dialogsfunction update(act) {// editing dialog templatevar 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='showCustomerWindow(); return false;'>";template += "<span class='glyphicon glyphicon-folder-open'></span>";template += " 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 {top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,modal: true,drag: true,closeOnEscape: true,closeAfterAdd: true,closeAfterEdit: true,reloadAfterSubmit: true,template: (act != "del") ? template : null,onclickSubmit: function (params, postdata) {// get row idvar selectedRow = dbGrid.getGridParam("selrow");switch (act) {case "add":params.url = '@Url.Action("Create")';// get customer id for current rowpostdata.CUSTOMER_ID =$('#dlgEditInvoice input[name=CUSTOMER_ID]').val();break;case "edit":params.url = '@Url.Action("Edit")';postdata.INVOICE_ID = selectedRow;// get customer id for current rowpostdata.CUSTOMER_ID =$('#dlgEditInvoice input[name=CUSTOMER_ID]').val();break;case "del":params.url = '@Url.Action("Delete")';postdata.INVOICE_ID = selectedRow;break;}},afterSubmit: function (response, postdata) {var responseData = response.responseJSON;// check the result for error messagesif (responseData.hasOwnProperty("error")) {if (responseData.error.length) {return [false, responseData.error];}}else {// refresh grid$(this).jqGrid('setGridParam',{datatype: 'json'}).trigger('reloadGrid');}return [true, "", 0];}};};}
Now we will write a function for opening the customer module that invokes the Bootstrap library to create a dialog box containing the grid from which a customer can be selected. It is actually the same grid we used earlier but, this time, it is enclosed by a dialog box. A click on the OK button will place the customer identifier and the customer name into the input fields of the parent dialog box for editing invoices.
/*** Display a window for selecting a customer*/function showCustomerWindow() {// 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 dialogue 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("&asmp;times;").appendTo(dlgHeader);// title$("<h5>").addClass("modal-title").html("Select customer").appendTo(dlgHeader);// body of dialoguevar dlgBody = $('<div>').addClass("modal-body").appendTo(dlgContent);// footer of the dialoguevar dlgFooter = $('<div>').addClass("modal-footer").appendTo(dlgContent);// button "OK"$("<button>").attr('type', 'button').addClass('btn').html('OK').on('click', function () {var rowId = $("#jqgCustomer").jqGrid("getGridParam", "selrow");var row = $("#jqgCustomer").jqGrid("getRowData", rowId);// To save the identifier and customer name// to 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);// button "Cancel"$("<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', 'jqgCustomer').appendTo(dlgBody);// add the navigation bar$('<div>').attr('id', 'jqgCustomerPager').appendTo(dlgBody);dlg.on('hidden.bs.modal', function () {dlg.remove();});// show dialogdlg.modal();// create and initialize jqGridvar dbGrid = $("#jqgCustomer").jqGrid({url: '@Url.Action("GetData", "Customer")', // URL to retrieve datamtype: "GET", // http type of requestdatatype: "json", // data formatpage: 1,width: '100%',// view descriptioncolModel: [{label: 'Id',name: 'CUSTOMER_ID',key: true,hidden: true},{label: 'Name',name: 'NAME',width: 250,sortable: true,editable: true,edittype: "text", // input typesearch: true,searchoptions: {sopt: ['eq', 'bw', 'cn'] // allowed search operators},// size and maximum length for the input fieldeditoptions: { size: 30, maxlength: 60 },// required inputeditrules: { required: true }},{label: 'Address',name: 'ADDRESS',width: 300,sortable: false,editable: true,search: false,edittype: "textarea",editoptions: { maxlength: 250, cols: 30, rows: 4 }},{label: 'Zip Code',name: 'ZIPCODE',width: 60,sortable: false,editable: true,search: false,edittype: "text",editoptions: { size: 30, maxlength: 10 },},{label: 'Phone',name: 'PHONE',width: 85,sortable: false,editable: true,search: false,edittype: "text",editoptions: { size: 30, maxlength: 14 },}],loadonce: false,pager: '#jqgCustomerPager',rowNum: 500, // number of rows displayedsortname: 'NAME', // sort by default by NAME columnsortorder: "asc",height: 500});dbGrid.jqGrid('navGrid', '#jqgCustomerPager',{search: true,add: false,edit: false,del: false,view: false,refresh: true,searchtext: "Search",viewtext: "View",viewtitle: "Selected record",refreshtext: "Refresh"});}
All that is left to write for the invoice module is the showChildGrid function that enables the invoice lines to be displayed and edited. Our function will create a grid with invoice lines dynamically after a click on the ‘+’ button to show the details.
Loading data for the lines requires passing the primary key from the selected invoice header.
// handler of the event of opening the parent grid// takes two parameters: the identifier of the parent record// and the value of the primary keyfunction showChildGrid(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 = '@Url.Action("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 gridvar detailGrid = $("#" + childGridID).jqGrid({url: childGridURL,mtype: "GET",datatype: "json",page: 1,colModel: [{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',width: 300,editable: true,edittype: "text",editoptions: {size: 50,maxlength: 60,readonly: true},editrules: { required: true }},{label: 'Price',name: '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=Price]').val() - 0;$('#dlgEditInvoiceLine input[name=Total]').val(quantity * price);}}],defaultValue: 1}},{label: 'Total',name: 'Total',formatter: 'currency',align: "right",width: 100,editable: true,editoptions: {readonly: true}}],loadonce: false,width: '100%',height: '100%',pager: "#" + childGridPagerID});// displaying the toolbar$("#" + childGridID).jqGrid('navGrid', '#' + childGridPagerID,{search: false,add: true,edit: true,del: true,refresh: true},updateDetail("edit"),updateDetail("add"),updateDetail("del"));// function that returns settings for the editing dialogfunction updateDetail(act) {// editing dialog templatevar template = "<div style='margin-left:15px;' id='dlgEditInvoiceLine'>";template += "<div>{INVOICE_ID} </div>";template += "<div>{PRODUCT_ID} </div>";// input field for goods with a buttontemplate += "<div> Product <sup>*</sup>:</div>";template += "<div>";template += "<div style='float: left;'>{Product}</div> ";template += "<a style='margin-left: 0.2em;' class='btn' ";template += "onclick='showProductWindow(); return false;'>";template += "<span class='glyphicon glyphicon-folder-open'></span>";template += " ???????</a> ";template += "<div style='clear: both;'></div>";template += "</div>";template += "<div> Quantity: </div><div>{Quantity} </div>";template += "<div> Price: </div><div>{Price} </div>";template += "<div> Total: </div><div>{Total} </div>";template += "<hr style='width: 100%;'/>";template += "<div> {sData} {cData} </div>";template += "</div>";return {top: $(".container.body-content").position().top + 150,left: $(".container.body-content").position().left + 150,modal: true,drag: true,closeOnEscape: true,closeAfterAdd: true,closeAfterEdit: true,reloadAfterSubmit: true,template: (act != "del") ? template : null,onclickSubmit: function (params, postdata) {var selectedRow = detailGrid.getGridParam("selrow");switch (act) {case "add":params.url = '@Url.Action("CreateDetail")';// get invoice idpostdata.INVOICE_ID =$('#dlgEditInvoiceLine input[name=INVOICE_ID]').val();// get the product ID for the current recordpostdata.PRODUCT_ID =$('#dlgEditInvoiceLine input[name=PRODUCT_ID]').val();break;case "edit":params.url = '@Url.Action("EditDetail")';// get current record idpostdata.INVOICE_LINE_ID = selectedRow;break;case "del":params.url = '@Url.Action("DeleteDetail")';// get current record idpostdata.INVOICE_LINE_ID = selectedRow;break;}},afterSubmit: function (response, postdata) {var responseData = response.responseJSON;// check the result for error messagesif (responseData.hasOwnProperty("error")) {if (responseData.error.length) {return [false, responseData.error];}}else {// refresh grid$(this).jqGrid('setGridParam',{datatype: 'json'}).trigger('reloadGrid');}return [true, "", 0];}};};}
Now we are done with creating the invoice module. Although the showProductWindow function that is used to select a product from the list while filling out invoice lines is not examined here, it is totally similar to the showCustomerWindow function that we examined earlier to implement the selection of customers from the customer module.
An observant reader might have noticed that the functions for displaying the selection from the module and for displaying the module itself were almost identical. Something you could do yourself to improve the code is to move these functions into separate .js script files.
