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.

  1. // returns properties to create edit dialogs
  2. function update(act) {
  3. // editing dialog template
  4. var template = "<div style='margin-left:15px;' id='dlgEditInvoice'>";
  5. template += "<div>{CUSTOMER_ID} </div>";
  6. template += "<div> Date: </div><div>{INVOICE_DATE} </div>";
  7. // customer input field with a button
  8. template += "<div> Customer <sup>*</sup>:</div>";
  9. template += "<div>";
  10. template += "<div style='float: left;'>{CUSTOMER_NAME}</div> ";
  11. template += "<a style='margin-left: 0.2em;' class='btn'";
  12. template += " onclick='showCustomerWindow(); return false;'>";
  13. template += "<span class='glyphicon glyphicon-folder-open'></span>";
  14. template += " Select</a> ";
  15. template += "<div style='clear: both;'></div>";
  16. template += "</div>";
  17. template += "<div> {PAID} Paid </div>";
  18. template += "<hr style='width: 100%;'/>";
  19. template += "<div> {sData} {cData} </div>";
  20. template += "</div>";
  21. return {
  22. top: $(".container.body-content").position().top + 150,
  23. left: $(".container.body-content").position().left + 150,
  24. modal: true,
  25. drag: true,
  26. closeOnEscape: true,
  27. closeAfterAdd: true,
  28. closeAfterEdit: true,
  29. reloadAfterSubmit: true,
  30. template: (act != "del") ? template : null,
  31. onclickSubmit: function (params, postdata) {
  32. // get row id
  33. var selectedRow = dbGrid.getGridParam("selrow");
  34. switch (act) {
  35. case "add":
  36. params.url = '@Url.Action("Create")';
  37. // get customer id for current row
  38. postdata.CUSTOMER_ID =
  39. $('#dlgEditInvoice input[name=CUSTOMER_ID]').val();
  40. break;
  41. case "edit":
  42. params.url = '@Url.Action("Edit")';
  43. postdata.INVOICE_ID = selectedRow;
  44. // get customer id for current row
  45. postdata.CUSTOMER_ID =
  46. $('#dlgEditInvoice input[name=CUSTOMER_ID]').val();
  47. break;
  48. case "del":
  49. params.url = '@Url.Action("Delete")';
  50. postdata.INVOICE_ID = selectedRow;
  51. break;
  52. }
  53. },
  54. afterSubmit: function (response, postdata) {
  55. var responseData = response.responseJSON;
  56. // check the result for error messages
  57. if (responseData.hasOwnProperty("error")) {
  58. if (responseData.error.length) {
  59. return [false, responseData.error];
  60. }
  61. }
  62. else {
  63. // refresh grid
  64. $(this).jqGrid(
  65. 'setGridParam',
  66. {
  67. datatype: 'json'
  68. }
  69. ).trigger('reloadGrid');
  70. }
  71. return [true, "", 0];
  72. }
  73. };
  74. };
  75. }

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.

  1. /**
  2. * Display a window for selecting a customer
  3. */
  4. function showCustomerWindow() {
  5. // the main block of the dialog
  6. var dlg = $('<div>')
  7. .attr('id', 'dlgChooseCustomer')
  8. .attr('aria-hidden', 'true')
  9. .attr('role', 'dialog')
  10. .attr('data-backdrop', 'static')
  11. .css("z-index", '2000')
  12. .addClass('modal')
  13. .appendTo($('body'));
  14. // block with the contents of the dialog
  15. var dlgContent = $("<div>")
  16. .addClass("modal-content")
  17. .css('width', '730px')
  18. .appendTo($('<div>')
  19. .addClass('modal-dialog')
  20. .appendTo(dlg));
  21. // block with dialogue header
  22. var dlgHeader = $('<div>').addClass("modal-header").appendTo(dlgContent);
  23. // button "X" for closing
  24. $("<button>")
  25. .addClass("close")
  26. .attr('type', 'button')
  27. .attr('aria-hidden', 'true')
  28. .attr('data-dismiss', 'modal')
  29. .html("&asmp;times;")
  30. .appendTo(dlgHeader);
  31. // title
  32. $("<h5>").addClass("modal-title")
  33. .html("Select customer")
  34. .appendTo(dlgHeader);
  35. // body of dialogue
  36. var dlgBody = $('<div>')
  37. .addClass("modal-body")
  38. .appendTo(dlgContent);
  39. // footer of the dialogue
  40. var dlgFooter = $('<div>').addClass("modal-footer").appendTo(dlgContent);
  41. // button "OK"
  42. $("<button>")
  43. .attr('type', 'button')
  44. .addClass('btn')
  45. .html('OK')
  46. .on('click', function () {
  47. var rowId = $("#jqgCustomer").jqGrid("getGridParam", "selrow");
  48. var row = $("#jqgCustomer").jqGrid("getRowData", rowId);
  49. // To save the identifier and customer name
  50. // to the input elements of the parent form
  51. $('#dlgEditInvoice input[name=CUSTOMER_ID]').val(rowId);
  52. $('#dlgEditInvoice input[name=CUSTOMER_NAME]').val(row["NAME"]);
  53. dlg.modal('hide');
  54. })
  55. .appendTo(dlgFooter);
  56. // button "Cancel"
  57. $("<button>")
  58. .attr('type', 'button')
  59. .addClass('btn')
  60. .html('Cancel')
  61. .on('click', function () { dlg.modal('hide'); })
  62. .appendTo(dlgFooter);
  63. // add a table to display the customers in the body of the dialog
  64. $('<table>')
  65. .attr('id', 'jqgCustomer')
  66. .appendTo(dlgBody);
  67. // add the navigation bar
  68. $('<div>')
  69. .attr('id', 'jqgCustomerPager')
  70. .appendTo(dlgBody);
  71. dlg.on('hidden.bs.modal', function () {
  72. dlg.remove();
  73. });
  74. // show dialog
  75. dlg.modal();
  76. // create and initialize jqGrid
  77. var dbGrid = $("#jqgCustomer").jqGrid({
  78. url: '@Url.Action("GetData", "Customer")', // URL to retrieve data
  79. mtype: "GET", // http type of request
  80. datatype: "json", // data format
  81. page: 1,
  82. width: '100%',
  83. // view description
  84. colModel: [
  85. {
  86. label: 'Id',
  87. name: 'CUSTOMER_ID',
  88. key: true,
  89. hidden: true
  90. },
  91. {
  92. label: 'Name',
  93. name: 'NAME',
  94. width: 250,
  95. sortable: true,
  96. editable: true,
  97. edittype: "text", // input type
  98. search: true,
  99. searchoptions: {
  100. sopt: ['eq', 'bw', 'cn'] // allowed search operators
  101. },
  102. // size and maximum length for the input field
  103. editoptions: { size: 30, maxlength: 60 },
  104. // required input
  105. editrules: { required: true }
  106. },
  107. {
  108. label: 'Address',
  109. name: 'ADDRESS',
  110. width: 300,
  111. sortable: false,
  112. editable: true,
  113. search: false,
  114. edittype: "textarea",
  115. editoptions: { maxlength: 250, cols: 30, rows: 4 }
  116. },
  117. {
  118. label: 'Zip Code',
  119. name: 'ZIPCODE',
  120. width: 60,
  121. sortable: false,
  122. editable: true,
  123. search: false,
  124. edittype: "text",
  125. editoptions: { size: 30, maxlength: 10 },
  126. },
  127. {
  128. label: 'Phone',
  129. name: 'PHONE',
  130. width: 85,
  131. sortable: false,
  132. editable: true,
  133. search: false,
  134. edittype: "text",
  135. editoptions: { size: 30, maxlength: 14 },
  136. }
  137. ],
  138. loadonce: false,
  139. pager: '#jqgCustomerPager',
  140. rowNum: 500, // number of rows displayed
  141. sortname: 'NAME', // sort by default by NAME column
  142. sortorder: "asc",
  143. height: 500
  144. });
  145. dbGrid.jqGrid('navGrid', '#jqgCustomerPager',
  146. {
  147. search: true,
  148. add: false,
  149. edit: false,
  150. del: false,
  151. view: false,
  152. refresh: true,
  153. searchtext: "Search",
  154. viewtext: "View",
  155. viewtitle: "Selected record",
  156. refreshtext: "Refresh"
  157. }
  158. );
  159. }

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.

  1. // handler of the event of opening the parent grid
  2. // takes two parameters: the identifier of the parent record
  3. // and the value of the primary key
  4. function showChildGrid(parentRowID, parentRowKey) {
  5. var childGridID = parentRowID + "_table";
  6. var childGridPagerID = parentRowID + "_pager";
  7. // send the primary key of the parent record
  8. // to filter the entries of the invoice items
  9. var childGridURL = '@Url.Action("GetDetailData")';
  10. childGridURL = childGridURL + "?invoice_id="
  11. + encodeURIComponent(parentRowKey)
  12. // add HTML elements to display the table and page navigation
  13. // as children for the selected row in the master grid
  14. $('<table>')
  15. .attr('id', childGridID)
  16. .appendTo($('#' + parentRowID));
  17. $('<div>')
  18. .attr('id', childGridPagerID)
  19. .addClass('scroll')
  20. .appendTo($('#' + parentRowID));
  21. // create and initialize the child grid
  22. var detailGrid = $("#" + childGridID).jqGrid({
  23. url: childGridURL,
  24. mtype: "GET",
  25. datatype: "json",
  26. page: 1,
  27. colModel: [
  28. {
  29. label: 'Invoice Line ID',
  30. name: 'INVOICE_LINE_ID',
  31. key: true,
  32. hidden: true
  33. },
  34. {
  35. label: 'Invoice ID',
  36. name: 'INVOICE_ID',
  37. hidden: true,
  38. editrules: { edithidden: true, required: true },
  39. editable: true,
  40. edittype: 'custom',
  41. editoptions: {
  42. custom_element: function (value, options) {
  43. // create hidden input
  44. return $("<input>")
  45. .attr('type', 'hidden')
  46. .attr('rowid', options.rowId)
  47. .addClass("FormElement")
  48. .addClass("form-control")
  49. .val(parentRowKey)
  50. .get(0);
  51. }
  52. }
  53. },
  54. {
  55. label: 'Product ID',
  56. name: 'PRODUCT_ID',
  57. hidden: true,
  58. editrules: { edithidden: true, required: true },
  59. editable: true,
  60. edittype: 'custom',
  61. editoptions: {
  62. custom_element: function (value, options) {
  63. // create hidden input
  64. return $("<input>")
  65. .attr('type', 'hidden')
  66. .attr('rowid', options.rowId)
  67. .addClass("FormElement")
  68. .addClass("form-control")
  69. .val(value)
  70. .get(0);
  71. }
  72. }
  73. },
  74. {
  75. label: 'Product',
  76. name: 'Product',
  77. width: 300,
  78. editable: true,
  79. edittype: "text",
  80. editoptions: {
  81. size: 50,
  82. maxlength: 60,
  83. readonly: true
  84. },
  85. editrules: { required: true }
  86. },
  87. {
  88. label: 'Price',
  89. name: 'Price',
  90. formatter: 'currency',
  91. editable: true,
  92. editoptions: {
  93. readonly: true
  94. },
  95. align: "right",
  96. width: 100
  97. },
  98. {
  99. label: 'Quantity',
  100. name: 'Quantity',
  101. align: "right",
  102. width: 100,
  103. editable: true,
  104. editrules: { required: true, number: true, minValue: 1 },
  105. editoptions: {
  106. dataEvents: [
  107. {
  108. type: 'change',
  109. fn: function (e) {
  110. var quantity = $(this).val() - 0;
  111. var price =
  112. $('#dlgEditInvoiceLine input[name=Price]').val() - 0;
  113. $('#dlgEditInvoiceLine input[name=Total]').val(quantity * price);
  114. }
  115. }
  116. ],
  117. defaultValue: 1
  118. }
  119. },
  120. {
  121. label: 'Total',
  122. name: 'Total',
  123. formatter: 'currency',
  124. align: "right",
  125. width: 100,
  126. editable: true,
  127. editoptions: {
  128. readonly: true
  129. }
  130. }
  131. ],
  132. loadonce: false,
  133. width: '100%',
  134. height: '100%',
  135. pager: "#" + childGridPagerID
  136. });
  137. // displaying the toolbar
  138. $("#" + childGridID).jqGrid('navGrid', '#' + childGridPagerID,
  139. {
  140. search: false,
  141. add: true,
  142. edit: true,
  143. del: true,
  144. refresh: true
  145. },
  146. updateDetail("edit"),
  147. updateDetail("add"),
  148. updateDetail("del")
  149. );
  150. // function that returns settings for the editing dialog
  151. function updateDetail(act) {
  152. // editing dialog template
  153. var template = "<div style='margin-left:15px;' id='dlgEditInvoiceLine'>";
  154. template += "<div>{INVOICE_ID} </div>";
  155. template += "<div>{PRODUCT_ID} </div>";
  156. // input field for goods with a button
  157. template += "<div> Product <sup>*</sup>:</div>";
  158. template += "<div>";
  159. template += "<div style='float: left;'>{Product}</div> ";
  160. template += "<a style='margin-left: 0.2em;' class='btn' ";
  161. template += "onclick='showProductWindow(); return false;'>";
  162. template += "<span class='glyphicon glyphicon-folder-open'></span>";
  163. template += " ???????</a> ";
  164. template += "<div style='clear: both;'></div>";
  165. template += "</div>";
  166. template += "<div> Quantity: </div><div>{Quantity} </div>";
  167. template += "<div> Price: </div><div>{Price} </div>";
  168. template += "<div> Total: </div><div>{Total} </div>";
  169. template += "<hr style='width: 100%;'/>";
  170. template += "<div> {sData} {cData} </div>";
  171. template += "</div>";
  172. return {
  173. top: $(".container.body-content").position().top + 150,
  174. left: $(".container.body-content").position().left + 150,
  175. modal: true,
  176. drag: true,
  177. closeOnEscape: true,
  178. closeAfterAdd: true,
  179. closeAfterEdit: true,
  180. reloadAfterSubmit: true,
  181. template: (act != "del") ? template : null,
  182. onclickSubmit: function (params, postdata) {
  183. var selectedRow = detailGrid.getGridParam("selrow");
  184. switch (act) {
  185. case "add":
  186. params.url = '@Url.Action("CreateDetail")';
  187. // get invoice id
  188. postdata.INVOICE_ID =
  189. $('#dlgEditInvoiceLine input[name=INVOICE_ID]').val();
  190. // get the product ID for the current record
  191. postdata.PRODUCT_ID =
  192. $('#dlgEditInvoiceLine input[name=PRODUCT_ID]').val();
  193. break;
  194. case "edit":
  195. params.url = '@Url.Action("EditDetail")';
  196. // get current record id
  197. postdata.INVOICE_LINE_ID = selectedRow;
  198. break;
  199. case "del":
  200. params.url = '@Url.Action("DeleteDetail")';
  201. // get current record id
  202. postdata.INVOICE_LINE_ID = selectedRow;
  203. break;
  204. }
  205. },
  206. afterSubmit: function (response, postdata) {
  207. var responseData = response.responseJSON;
  208. // check the result for error messages
  209. if (responseData.hasOwnProperty("error")) {
  210. if (responseData.error.length) {
  211. return [false, responseData.error];
  212. }
  213. }
  214. else {
  215. // refresh grid
  216. $(this).jqGrid(
  217. 'setGridParam',
  218. {
  219. datatype: 'json'
  220. }
  221. ).trigger('reloadGrid');
  222. }
  223. return [true, "", 0];
  224. }
  225. };
  226. };
  227. }

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.