From 8d6a4415e5e19b3c79edce94bcf8e37456a5f736 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 May 2019 22:33:55 +1000 Subject: [PATCH 1/5] Better documentation for the modal form jQuery code --- InvenTree/static/script/inventree/modals.js | 85 +++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/InvenTree/static/script/inventree/modals.js b/InvenTree/static/script/inventree/modals.js index f729282b52..8d0f37a8f1 100644 --- a/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/static/script/inventree/modals.js @@ -1,20 +1,35 @@ function makeOption(id, title) { + /* Format an option for a select element + */ return ""; } -function attachSelect(modal) { - // Attach to any 'select' inputs on the modal - // Provide search filtering of dropdown items - $(modal + ' .select').select2({ +function attachSelect(modal) { + /* Attach 'select2' functionality to any drop-down list in the modal. + * Provides search filtering for dropdown items + */ + + $(modal + ' .select').select2({ dropdownParent: $(modal), + // dropdownAutoWidth parameter is required to work properly with modal forms dropdownAutoWidth: true, }); } -function afterForm(response, options) { - // Should we show alerts immediately or cache them? +function afterForm(response, options) { + /* afterForm is called after a form is successfully submitted, + * and the form is dismissed. + * Used for general purpose functionality after form submission: + * + * - Display a bootstrap alert (success / info / warning / danger) + * - Run a supplied success callback function + * - Redirect the browser to a different URL + * - Reload the page + */ + + // Should we show alerts immediately or cache them? var cache = (options.follow && response.url) || options.redirect || options.reload; @@ -49,24 +64,42 @@ function afterForm(response, options) { } + function modalSetTitle(modal, title='') { + /* Update the title of a modal form + */ $(modal + ' #modal-title').html(title); } + function modalSetContent(modal, content='') { + /* Update the content panel of a modal form + */ $(modal).find('.modal-form-content').html(content); } + function modalSetButtonText(modal, submit_text, close_text) { + /* Set the button text for a modal form + * + * submit_text - text for the form submit button + * close_text - text for the form dismiss button + */ $(modal).find("#modal-form-submit").html(submit_text); $(modal).find("#modal-form-close").html(close_text); } + function closeModal(modal='#modal-form') { + /* Dismiss (hide) a modal form + */ $(modal).modal('hide'); } + function modalSubmit(modal, callback) { + /* Perform the submission action for the modal form + */ $(modal).off('click', '#modal-form-submit'); $(modal).on('click', '#modal-form-submit', function() { @@ -76,6 +109,16 @@ function modalSubmit(modal, callback) { function openModal(options) { + /* Open a modal form, and perform some action based on the provided options object: + * + * options can contain: + * + * modal - ID of the modal form element (default = '#modal-form') + * title - Custom title for the form + * content - Default content for the form panel + * submit_text - Label for the submit button (default = 'Submit') + * close_text - Label for the close button (default = 'Close') + */ var modal = options.modal || '#modal-form'; @@ -118,6 +161,8 @@ function openModal(options) { function launchDeleteForm(url, options = {}) { + /* Launch a modal form to delete an object + */ var modal = options.modal || '#modal-delete'; @@ -175,13 +220,23 @@ function launchDeleteForm(url, options = {}) { }); } + function injectModalForm(modal, form_html) { - // Inject the form data into the modal window + /* Inject form content into the modal. + * Updates the HTML of the form content, and then applies some other updates + */ $(modal).find('.modal-form-content').html(form_html); attachSelect(modal); } + function handleModalForm(url, options) { + /* Update a modal form after data are received from the server. + * Manages POST requests until the form is successfully submitted. + * + * The server should respond with a JSON object containing a boolean value 'form_valid' + * Form submission repeats (after user interaction) until 'form_valid' = true + */ var modal = options.modal || '#modal-form'; @@ -237,12 +292,16 @@ function handleModalForm(url, options) { }); } -/* - * launchModalForm - * Opens a model window and fills it with a requested form - * If the form is loaded successfully, calls handleModalForm - */ + function launchModalForm(url, options = {}) { + /* Launch a modal form, and request data from the server to fill the form + * If the form data is returned from the server, calls handleModalForm() + * + * A successful request will return a JSON object with, at minimum, + * an object called 'html_form' + * + * If the request is NOT successful, displays an appropriate error message. + */ var modal = options.modal || '#modal-form'; @@ -289,4 +348,4 @@ function launchModalForm(url, options = {}) { // Send the AJAX request $.ajax(ajax_data); -} \ No newline at end of file +} From 11af4e568324c03004942e77614aceb39cc59300 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 May 2019 23:00:05 +1000 Subject: [PATCH 2/5] Disable form input unless the form is valid - Disable on first open until server responds - Disable after form submission until next server response --- InvenTree/static/script/inventree/modals.js | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/InvenTree/static/script/inventree/modals.js b/InvenTree/static/script/inventree/modals.js index 8d0f37a8f1..dd730b0c92 100644 --- a/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/static/script/inventree/modals.js @@ -18,6 +18,16 @@ function attachSelect(modal) { } +function loadingMessageContent() { + /* Render a 'loading' message to display in a form + * when waiting for a response from the server + */ + + // TODO - This can be made a lot better + return 'Loading...'; +} + + function afterForm(response, options) { /* afterForm is called after a form is successfully submitted, * and the form is dismissed. @@ -61,7 +71,15 @@ function afterForm(response, options) { else if (options.reload) { location.reload(); } +} + +function modalEnable(modal, enable=true) { + /* Enable (or disable) modal form elements to prevent user input + */ + + // Enable or disable the submit button + $(modal).find('#modal-form-submit').prop('disabled', !enable); } @@ -138,12 +156,18 @@ function openModal(options) { } }); + // Unless the title is explicitly set, display loading message if (options.title) { modalSetTitle(modal, options.title); + } else { + modalSetTitle(modal, 'Loading Form Data...'); } + // Unless the content is explicitly set, display loading message if (options.content) { modalSetContent(modal, options.content); + } else { + modalSetContent(modal, loadingMessageContent()); } // Default labels for 'Submit' and 'Close' buttons in the form @@ -156,6 +180,11 @@ function openModal(options) { backdrop: 'static', keyboard: false, }); + + // Disable the form + modalEnable(modal, false); + + // Finally, display the modal window $(modal).modal('show'); } @@ -258,8 +287,14 @@ function handleModalForm(url, options) { modalSubmit(modal, function() { $(modal).find('.js-modal-form').ajaxSubmit({ url: url, + beforeSend: function() { + // Disable modal until the server returns a response + modalEnable(modal, false); + }, // POST was successful success: function(response, status, xhr, f) { + // Re-enable the modal + modalEnable(modal, true); if ('form_valid' in response) { // Form data was validated correctly if (response.form_valid) { @@ -322,6 +357,10 @@ function launchModalForm(url, options = {}) { }); }, success: function(response) { + + // Enable the form + modalEnable(modal, true); + if (response.title) { modalSetTitle(modal, response.title); } From bade13f3b86f0c14d79f960ddd62aa90f5b755d6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 May 2019 23:08:31 +1000 Subject: [PATCH 3/5] Fixed stocktake form to match new scheme --- InvenTree/static/script/inventree/stock.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/static/script/inventree/stock.js b/InvenTree/static/script/inventree/stock.js index ac7d151413..73199466f6 100644 --- a/InvenTree/static/script/inventree/stock.js +++ b/InvenTree/static/script/inventree/stock.js @@ -110,6 +110,8 @@ function updateStock(items, options={}) { $(modal).find('#note-warning').hide(); + modalEnable(modal, true); + modalSubmit(modal, function() { var stocktake = []; From 2b3094eac4fa9ee90650c4315785f805d5a6f64d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 May 2019 23:41:31 +1000 Subject: [PATCH 4/5] Add error dialog box for forms - Display status message - Display status code - Display extended error message in collapsible section --- InvenTree/static/script/inventree/modals.js | 61 ++++++++++++++++++++- InvenTree/templates/modals.html | 18 ++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/InvenTree/static/script/inventree/modals.js b/InvenTree/static/script/inventree/modals.js index dd730b0c92..4f8ec88b6b 100644 --- a/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/static/script/inventree/modals.js @@ -126,6 +126,61 @@ function modalSubmit(modal, callback) { } +function renderErrorMessage(xhr) { + + var html = '' + xhr.statusText + '
'; + + html += 'Status Code - ' + xhr.status + '

'; + + html += ` +
+
+ +
+
`; + + html += xhr.responseText; + + html += ` +
+
+
+
`; + + return html; +} + + +function showDialog(title, content, options={}) { + /* Display a modal dialog message box. + * + * title - Title text + * content - HTML content of the dialog window + * options: + * modal - modal form to use (default = '#modal-dialog') + */ + + var modal = options.modal || '#modal-dialog'; + + $(modal).on('shown.bs.modal', function() { + $(modal + ' .modal-form-content').scrollTop(0); + }); + + modalSetTitle(modal, title); + modalSetContent(modal, content); + + $(modal).modal({ + backdrop: 'static', + keyboard: false, + }); + + $(modal).modal('show'); +} + function openModal(options) { /* Open a modal form, and perform some action based on the provided options object: * @@ -317,8 +372,10 @@ function handleModalForm(url, options) { } }, error: function(xhr, ajaxOptions, thrownError) { - alert('Error posting form data:\n' + thrownError); - $(modal).modal('hide'); + // There was an error submitting form data via POST + + $(modal).modal('hide'); + showDialog('Error posting form data', renderErrorMessage(xhr)); }, complete: function(xhr) { //TODO diff --git a/InvenTree/templates/modals.html b/InvenTree/templates/modals.html index 9ba0335572..7238c5cf2a 100644 --- a/InvenTree/templates/modals.html +++ b/InvenTree/templates/modals.html @@ -37,4 +37,22 @@ + + + \ No newline at end of file From 109b6b868cc845760e8b0a8b4fadb7af685ae08b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 3 May 2019 23:46:00 +1000 Subject: [PATCH 5/5] Cature all form AJAX errors - remove all references to alert() - Display error modal instead --- InvenTree/static/script/inventree/modals.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/InvenTree/static/script/inventree/modals.js b/InvenTree/static/script/inventree/modals.js index 4f8ec88b6b..c16772704a 100644 --- a/InvenTree/static/script/inventree/modals.js +++ b/InvenTree/static/script/inventree/modals.js @@ -273,13 +273,14 @@ function launchDeleteForm(url, options = {}) { modalSetContent(modal, response.html_data); } else { - alert('JSON response missing HTML data'); + $(modal).modal('hide'); + showDialog('Invalid form response', 'JSON response missing HTML data'); } }, error: function (xhr, ajaxOptions, thrownError) { - alert('Error requesting JSON data:\n' + thrownError); $(modal).modal('hide'); + showDialog('Error requesting form data', renderErrorMessage(xhr)); } }); @@ -297,8 +298,8 @@ function launchDeleteForm(url, options = {}) { afterForm(response, options); }, error: function (xhr, ajaxOptions, thrownError) { - alert('Error deleting item:\n' + thrownError); $(modal).modal('hide'); + showDialog('Error deleting item', renderErrorMessage(xhr)); } }); }); @@ -335,6 +336,7 @@ function handleModalForm(url, options) { }); form.submit(function() { + // We should never get here (form submission is overridden) alert('form submit'); return false; }); @@ -362,7 +364,8 @@ function handleModalForm(url, options) { injectModalForm(modal, response.html_form); } else { - alert('HTML form data missing from server response'); + $(modal).modal('hide'); + showDialog('Invalid response from server', 'Form data missing from server response'); } } } @@ -427,13 +430,13 @@ function launchModalForm(url, options = {}) { handleModalForm(url, options); } else { - alert('JSON response missing form data'); $(modal).modal('hide'); + showDialog('Invalid server response', 'JSON response missing form data'); } }, error: function (xhr, ajaxOptions, thrownError) { - alert('Error requesting form data:\n' + thrownError); $(modal).modal('hide'); + showDialog('Error requesting form data', renderErrorMessage(xhr)); } };