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 dialogs
function update(act) {
// editing dialog template
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 button
template += "<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 id
var selectedRow = dbGrid.getGridParam("selrow");
switch (act) {
case "add":
params.url = '@Url.Action("Create")';
// get customer id for current row
postdata.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 row
postdata.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 messages
if (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 dialog
var 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 dialog
var dlgContent = $("<div>")
.addClass("modal-content")
.css('width', '730px')
.appendTo($('<div>')
.addClass('modal-dialog')
.appendTo(dlg));
// block with dialogue header
var 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 dialogue
var dlgBody = $('<div>')
.addClass("modal-body")
.appendTo(dlgContent);
// footer of the dialogue
var 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 dialog
dlg.modal();
// create and initialize jqGrid
var dbGrid = $("#jqgCustomer").jqGrid({
url: '@Url.Action("GetData", "Customer")', // URL to retrieve data
mtype: "GET", // http type of request
datatype: "json", // data format
page: 1,
width: '100%',
// view description
colModel: [
{
label: 'Id',
name: 'CUSTOMER_ID',
key: true,
hidden: true
},
{
label: 'Name',
name: 'NAME',
width: 250,
sortable: true,
editable: true,
edittype: "text", // input type
search: true,
searchoptions: {
sopt: ['eq', 'bw', 'cn'] // allowed search operators
},
// size and maximum length for the input field
editoptions: { size: 30, maxlength: 60 },
// required input
editrules: { 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 displayed
sortname: 'NAME', // sort by default by NAME column
sortorder: "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 key
function 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 items
var 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 grid
var 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 input
return $("<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 input
return $("<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 dialog
function updateDetail(act) {
// editing dialog template
var template = "<div style='margin-left:15px;' id='dlgEditInvoiceLine'>";
template += "<div>{INVOICE_ID} </div>";
template += "<div>{PRODUCT_ID} </div>";
// input field for goods with a button
template += "<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 id
postdata.INVOICE_ID =
$('#dlgEditInvoiceLine input[name=INVOICE_ID]').val();
// get the product ID for the current record
postdata.PRODUCT_ID =
$('#dlgEditInvoiceLine input[name=PRODUCT_ID]').val();
break;
case "edit":
params.url = '@Url.Action("EditDetail")';
// get current record id
postdata.INVOICE_LINE_ID = selectedRow;
break;
case "del":
params.url = '@Url.Action("DeleteDetail")';
// get current record id
postdata.INVOICE_LINE_ID = selectedRow;
break;
}
},
afterSubmit: function (response, postdata) {
var responseData = response.responseJSON;
// check the result for error messages
if (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.