diff --git a/InvenTree/templates/js/api.js b/InvenTree/templates/js/api.js
index b43bcc8419..aa446028a5 100644
--- a/InvenTree/templates/js/api.js
+++ b/InvenTree/templates/js/api.js
@@ -103,10 +103,11 @@ function inventreePut(url, data={}, options={}) {
}
},
error: function(xhr, ajaxOptions, thrownError) {
- console.error('Error on UPDATE to ' + url);
- console.error(thrownError);
if (options.error) {
options.error(xhr, ajaxOptions, thrownError);
+ } else {
+ console.error(`Error on ${method} to '${url}' - STATUS ${xhr.status}`);
+ console.error(thrownError);
}
},
complete: function(xhr, status) {
diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/forms.js
index 93ab9e696e..8a302bc2b1 100644
--- a/InvenTree/templates/js/forms.js
+++ b/InvenTree/templates/js/forms.js
@@ -79,7 +79,7 @@ function canDelete(OPTIONS) {
* Get the API endpoint options at the provided URL,
* using a HTTP options request.
*/
-function getApiEndpointOptions(url, callback, options={}) {
+function getApiEndpointOptions(url, callback, options) {
// Return the ajax request object
$.ajax({
@@ -108,10 +108,10 @@ function getApiEndpointOptions(url, callback, options={}) {
* options:
* -
*/
-function constructCreateForm(url, fields, options={}) {
+function constructCreateForm(fields, options) {
// We should have enough information to create the form!
- constructFormBody(url, fields, options);
+ constructFormBody(fields, options);
}
@@ -124,11 +124,11 @@ function constructCreateForm(url, fields, options={}) {
* options:
* -
*/
-function constructChangeForm(url, fields, options={}) {
+function constructChangeForm(fields, options) {
// Request existing data from the API endpoint
$.ajax({
- url: url,
+ url: options.url,
type: 'GET',
contentType: 'application/json',
dataType: 'json',
@@ -145,7 +145,7 @@ function constructChangeForm(url, fields, options={}) {
}
}
- constructFormBody(url, fields, options);
+ constructFormBody(fields, options);
},
error: function(request, status, error) {
// TODO: Handle error here
@@ -160,16 +160,16 @@ function constructChangeForm(url, fields, options={}) {
* Request API OPTIONS data from the server,
* and construct a modal form based on the response.
*
- * arguments:
- * - url: API URL
- *
* options:
* - method: The HTTP method e.g. 'PUT', 'POST', 'DELETE',
* - title: The form title
* - fields: list of fields to display
* - exclude: List of fields to exclude
*/
-function constructForm(url, options={}) {
+function constructForm(url, options) {
+
+ // Save the URL
+ options.url = url;
// Default HTTP method
options.method = options.method || 'PATCH';
@@ -187,7 +187,7 @@ function constructForm(url, options={}) {
switch (options.method) {
case 'POST':
if (canCreate(OPTIONS)) {
- constructCreateForm(url, OPTIONS.actions.POST, options);
+ constructCreateForm(OPTIONS.actions.POST, options);
} else {
// User does not have permission to POST to the endpoint
console.log('cannot POST');
@@ -197,7 +197,7 @@ function constructForm(url, options={}) {
case 'PUT':
case 'PATCH':
if (canChange(OPTIONS)) {
- constructChangeForm(url, OPTIONS.actions.PUT, options);
+ constructChangeForm(OPTIONS.actions.PUT, options);
} else {
// User does not have permission to PUT/PATCH to the endpoint
// TODO
@@ -231,7 +231,7 @@ function constructForm(url, options={}) {
-function constructFormBody(url, fields, options={}) {
+function constructFormBody(fields, options) {
var html = '';
@@ -298,7 +298,10 @@ function constructFormBody(url, fields, options={}) {
// TODO: Dynamically create the modals,
// so that we can have an infinite number of stacks!
- var modal = '#modal-form';
+
+ options.modal = options.modal || '#modal-form';
+
+ var modal = options.modal;
modalEnable(modal, true);
@@ -313,12 +316,9 @@ function constructFormBody(url, fields, options={}) {
$(modal).modal('show');
// Setup related fields
- initializeRelatedFields(modal, url, fields, options)
+ initializeRelatedFields(fields, options)
attachToggle(modal);
- // attachSelect(modal);
-
- //$(modal + ' .select').select2();
$(modal + ' .select2-container').addClass('select-full-width');
$(modal + ' .select2-container').css('width', '100%');
@@ -328,29 +328,119 @@ function constructFormBody(url, fields, options={}) {
$(modal).off('click', '#modal-form-submit');
$(modal).on('click', '#modal-form-submit', function() {
- var patch_data = {};
-
- // Construct submit data
- field_names.forEach(function(name) {
- var field = fields[name] || null;
-
- if (field) {
- var field_value = getFieldValue(name);
-
- patch_data[name] = field_value;
- } else {
- console.log(`Could not find field matching '${name}'`);
- }
- })
-
-
- console.log(patch_data);
-
+ submitFormData(fields, options);
});
}
-function initializeRelatedFields(modal, url, fields, options) {
+/*
+ * Submit form data to the server.
+ *
+ */
+function submitFormData(fields, options) {
+
+ // Data to be sent to the server
+ var data = {};
+
+ // Extract values for each field
+ options.field_names.forEach(function(name) {
+
+ var field = fields[name] || null;
+
+ if (field) {
+ var value = getFieldValue(name);
+
+ // TODO - Custom parsing depending on type?
+
+ data[name] = value;
+ } else {
+ console.log(`WARNING: Could not find field matching '${name}'`);
+ }
+ });
+
+ // Submit data
+ inventreePut(
+ options.url,
+ data,
+ {
+ method: options.method,
+ success: function(response, status) {
+ console.log('success', '->', status);
+ },
+ error: function(xhr, status, thrownError) {
+
+ switch (xhr.status) {
+ case 400: // Bad request
+ handleFormErrors(xhr.responseJSON, fields, options);
+ break;
+ default:
+ console.log(`WARNING: Unhandled response code - ${xhr.status}`);
+ break;
+ }
+ }
+ }
+ );
+}
+
+
+/*
+ * Remove all error text items from the form
+ */
+function clearFormErrors(options) {
+
+ // Remove the individual error messages
+ $(options.modal).find('.form-error-message').remove();
+
+ // Remove the "has error" class
+ $(options.modal).find('.has-error').removeClass('has-error');
+}
+
+
+/*
+ * Display form error messages as returned from the server.
+ *
+ * arguments:
+ * - errors: The JSON error response from the server
+ * - fields: The form data object
+ * - options: Form options provided by the client
+ */
+function handleFormErrors(errors, fields, options) {
+
+ // Remove any existing error messages from the form
+ clearFormErrors(options);
+
+ for (field_name in errors) {
+ if (field_name in fields) {
+
+ // Add the 'has-error' class
+ $(options.modal).find(`#div_id_${field_name}`).addClass('has-error');
+
+ var field_dom = $(options.modal).find(`#id_${field_name}`);
+
+ var field_errors = errors[field_name];
+
+ // Add an entry for each returned error message
+ for (var idx = field_errors.length-1; idx >= 0; idx--) {
+
+ var error_text = field_errors[idx];
+
+ var html = `
+
+ ${error_text}
+ `;
+
+ $(html).insertAfter(field_dom);
+ }
+
+ } else {
+ console.log(`WARNING: handleFormErrors found no match for field '${field_name}'`);
+ }
+ }
+
+}
+
+
+function initializeRelatedFields(fields, options) {
var field_names = options.field_names;
@@ -368,7 +458,7 @@ function initializeRelatedFields(modal, url, fields, options) {
continue;
}
- initializeRelatedField(modal, name, field, options);
+ initializeRelatedField(name, field, options);
}
}
@@ -385,7 +475,7 @@ function initializeRelatedFields(modal, url, fields, options) {
function initializeRelatedField(modal, name, field, options) {
// Find the select element and attach a select2 to it
- var select = $(modal).find(`#id_${name}`);
+ var select = $(options.modal).find(`#id_${name}`);
// TODO: Add 'placeholder' support for entry select2 fields
@@ -397,7 +487,7 @@ function initializeRelatedField(modal, name, field, options) {
url: field.api_url,
dataType: 'json',
allowClear: !field.required, // Allow non required fields to be cleared
- dropdownParent: $(modal),
+ dropdownParent: $(options.modal),
dropdownAutoWidth: false,
delay: 250,
cache: true,
@@ -555,7 +645,7 @@ function renderModelData(name, model, data, parameters, options) {
* - Field description (help text)
* - Field errors
*/
-function constructField(name, parameters, options={}) {
+function constructField(name, parameters, options) {
var field_name = `id_${name}`;
@@ -631,7 +721,7 @@ function constructLabel(name, parameters) {
* - parameters: Field parameters returned by the OPTIONS method
*
*/
-function constructInput(name, parameters, options={}) {
+function constructInput(name, parameters, options) {
var html = '';
@@ -741,7 +831,7 @@ function constructInputOptions(name, classes, type, parameters) {
// Construct a "checkbox" input
-function constructCheckboxInput(name, parameters, options={}) {
+function constructCheckboxInput(name, parameters, options) {
return constructInputOptions(
name,
@@ -754,7 +844,7 @@ function constructCheckboxInput(name, parameters, options={}) {
// Construct a "text" input
-function constructTextInput(name, parameters, options={}) {
+function constructTextInput(name, parameters, options) {
var classes = '';
var type = '';
@@ -784,7 +874,7 @@ function constructTextInput(name, parameters, options={}) {
// Construct a "number" field
-function constructNumberInput(name, parameters, options={}) {
+function constructNumberInput(name, parameters, options) {
return constructInputOptions(
name,
@@ -796,7 +886,7 @@ function constructNumberInput(name, parameters, options={}) {
// Construct a "choice" input
-function constructChoiceInput(name, parameters, options={}) {
+function constructChoiceInput(name, parameters, options) {
var html = `