diff --git a/InvenTree/part/templates/part/index.html b/InvenTree/part/templates/part/index.html
index df51ce51f2..a6f5ca4e29 100644
--- a/InvenTree/part/templates/part/index.html
+++ b/InvenTree/part/templates/part/index.html
@@ -32,56 +32,12 @@
{% block javascript %}
+
+
diff --git a/InvenTree/static/script/modal_form.js b/InvenTree/static/script/modal_form.js
new file mode 100644
index 0000000000..cc1f609b3a
--- /dev/null
+++ b/InvenTree/static/script/modal_form.js
@@ -0,0 +1,59 @@
+/* Bind a button to launch a modal form and handle AJAX requests */
+function bindModalForm(modal, button, url, data) {
+ $(button).click(function () {
+ $.ajax({
+ url: url, // Where to request the data from
+ type: 'get', // GET request
+ data: data, // Any additional context data (e.g. initial values)
+ dataType: 'json',
+ beforeSend: function() {
+ $(modal).modal('show');
+ },
+ success: function(response) {
+ if (response.html_form) {
+ var target = modal + ' .modal-content';
+ $(target).html(response.html_form);
+ } else {
+ alert('JSON response missing form data');
+ $(modal).modal('hide');
+ }
+ },
+ error: function (xhr, ajaxOptions, thrownError) {
+ alert('Error requesting form data:\n' + thrownError);
+ $(modal).modal('hide');
+ }
+ });
+ });
+
+ $(modal).on('submit', '.js-modal-form', function() {
+ var form = $(this);
+
+ $.ajax({
+ url: form.attr('action'),
+ data: form.serialize(),
+ type: form.attr('method'),
+ dataType: 'json',
+ success: function (response) {
+ if (response.form_valid) {
+ alert("Success!");
+ $(modal).modal('hide');
+ }
+ else if (response.html_form) {
+ var target = modal + ' .modal-content';
+ $(target).html(response.html_form);
+ }
+ else {
+ alert('JSON response missing form data');
+ $(modal).modal('hide');
+ }
+ },
+ error: function (xhr, ajaxOptions, thrownError) {
+ alert("Error posting form data:\n" + thrownError);
+ $(modal).modal('hide');
+ }
+ });
+
+ // Override the default form submit functionality
+ return false;
+ });
+}
\ No newline at end of file