From 5b42ab73325d0649d65cee738ad4c30aedcd01fd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 13 Aug 2021 21:48:48 +1000 Subject: [PATCH] Add "groups" to API forms --- InvenTree/InvenTree/static/css/inventree.css | 7 ++ InvenTree/part/templates/part/category.html | 1 + InvenTree/templates/js/translated/forms.js | 117 ++++++++++++++++++- InvenTree/templates/js/translated/part.js | 55 ++++++--- 4 files changed, 161 insertions(+), 19 deletions(-) diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 592bef396a..74b3b63169 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -730,6 +730,13 @@ padding: 10px; } +.form-panel { + border-radius: 5px; + border: 1px solid #ccc; + padding: 5px; +} + + .modal input { width: 100%; } diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index b149fd28ed..af07952a7e 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -276,6 +276,7 @@ constructForm('{% url "api-part-list" %}', { method: 'POST', fields: fields, + groups: partGroups(), title: '{% trans "Create Part" %}', onSuccess: function(data) { // Follow the new part diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 4b41623fbf..914eb93dec 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -264,6 +264,10 @@ function constructForm(url, options) { // Default HTTP method options.method = options.method || 'PATCH'; + // Default "groups" definition + options.groups = options.groups || {}; + options.current_group = null; + // Construct an "empty" data object if not provided if (!options.data) { options.data = {}; @@ -413,6 +417,11 @@ function constructFormBody(fields, options) { fields[field].choices = field_options.choices; } + // Group + if (field_options.group) { + fields[field].group = field_options.group; + } + // Field prefix if (field_options.prefix) { fields[field].prefix = field_options.prefix; @@ -465,8 +474,12 @@ function constructFormBody(fields, options) { html += constructField(name, field, options); } - // TODO: Dynamically create the modals, - // so that we can have an infinite number of stacks! + if (options.current_group) { + // Close out the current group + html += ``; + + console.log(`finally, ending group '${console.current_group}'`); + } // Create a new modal if one does not exists if (!options.modal) { @@ -535,6 +548,8 @@ function constructFormBody(fields, options) { submitFormData(fields, options); } }); + + initializeGroups(fields, options); } @@ -960,6 +975,49 @@ function addClearCallback(name, field, options) { } +// Initialize callbacks and initial states for groups +function initializeGroups(fields, options) { + + var modal = options.modal; + + // Callback for when the group is expanded + $(modal).find('.form-panel-content').on('show.bs.collapse', function() { + + var panel = $(this).closest('.form-panel'); + var group = panel.attr('group'); + + var icon = $(modal).find(`#group-icon-${group}`); + + icon.removeClass('fa-angle-right'); + icon.addClass('fa-angle-up'); + }); + + // Callback for when the group is collapsed + $(modal).find('.form-panel-content').on('hide.bs.collapse', function() { + + var panel = $(this).closest('.form-panel'); + var group = panel.attr('group'); + + var icon = $(modal).find(`#group-icon-${group}`); + + icon.removeClass('fa-angle-up'); + icon.addClass('fa-angle-right'); + }); + + // Set initial state of each specified group + for (var group in options.groups) { + + var group_options = options.groups[group]; + + if (group_options.collapsed) { + $(modal).find(`#form-panel-content-${group}`).collapse("hide"); + } else { + $(modal).find(`#form-panel-content-${group}`).collapse("show"); + } + } +} + + function initializeRelatedFields(fields, options) { var field_names = options.field_names; @@ -1353,6 +1411,8 @@ function renderModelData(name, model, data, parameters, options) { */ function constructField(name, parameters, options) { + var html = ''; + // Shortcut for simple visual fields if (parameters.type == 'candy') { return constructCandyInput(name, parameters, options); @@ -1365,13 +1425,62 @@ function constructField(name, parameters, options) { return constructHiddenInput(name, parameters, options); } + // Are we ending a group? + if (options.current_group && parameters.group != options.current_group) { + html += ``; + + console.log(`ending group '${options.current_group}'`); + + // Null out the current "group" so we can start a new one + options.current_group = null; + } + + // Are we starting a new group? + if (parameters.group) { + + var group = parameters.group; + + var group_options = options.groups[group] || {}; + + // Are we starting a new group? + // Add HTML for the start of a separate panel + if (parameters.group != options.current_group) { + + console.log(`starting group '${group}'`); + + html += ` +
+
`; + if (group_options.collapsible) { + html += ` +
+ + `; + } else { + html += `
`; + } + + html += `

${group_options.title || group}

`; + + if (group_options.collapsible) { + html += `
`; + } + + html += ` +
+
+ `; + } + + // Keep track of the group we are in + options.current_group = group; + } + var form_classes = 'form-group'; if (parameters.errors) { form_classes += ' has-error'; } - - var html = ''; // Optional content to render before the field if (parameters.before) { diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 3d8a21c66a..e777968711 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -13,6 +13,26 @@ function yesNoLabel(value) { } } + +function partGroups(options={}) { + + return { + attributes: { + title: '{% trans "Part Attributes" %}', + collapsible: true, + }, + create: { + title: '{% trans "Part Creation Options" %}', + collapsible: true, + }, + duplicate: { + title: '{% trans "Part Duplication Options" %}', + collapsible: true, + } + } + +} + // Construct fieldset for part forms function partFields(options={}) { @@ -48,36 +68,41 @@ function partFields(options={}) { minimum_stock: { icon: 'fa-boxes', }, - attributes: { - type: 'candy', - html: `

{% trans "Part Attributes" %}


` - }, component: { value: global_settings.PART_COMPONENT, + group: 'attributes', }, assembly: { value: global_settings.PART_ASSEMBLY, + group: 'attributes', }, is_template: { value: global_settings.PART_TEMPLATE, + group: 'attributes', }, trackable: { value: global_settings.PART_TRACKABLE, + group: 'attributes', }, purchaseable: { value: global_settings.PART_PURCHASEABLE, + group: 'attributes', }, salable: { value: global_settings.PART_SALABLE, + group: 'attributes', }, virtual: { value: global_settings.PART_VIRTUAL, + group: 'attributes', }, }; // If editing a part, we can set the "active" status if (options.edit) { - fields.active = {}; + fields.active = { + group: 'attributes' + }; } // Pop expiry field @@ -91,16 +116,12 @@ function partFields(options={}) { // No supplier parts available yet delete fields["default_supplier"]; - fields.create = { - type: 'candy', - html: `

{% trans "Part Creation Options" %}


`, - }; - if (global_settings.PART_CREATE_INITIAL) { fields.initial_stock = { type: 'decimal', label: '{% trans "Initial Stock Quantity" %}', help_text: '{% trans "Initialize part stock with specified quantity" %}', + group: 'create', }; } @@ -109,21 +130,18 @@ function partFields(options={}) { label: '{% trans "Copy Category Parameters" %}', help_text: '{% trans "Copy parameter templates from selected part category" %}', value: global_settings.PART_CATEGORY_PARAMETERS, + group: 'create', }; } // Additional fields when "duplicating" a part if (options.duplicate) { - fields.duplicate = { - type: 'candy', - html: `

{% trans "Part Duplication Options" %}


`, - }; - fields.copy_from = { type: 'integer', hidden: true, value: options.duplicate, + group: 'duplicate', }, fields.copy_image = { @@ -131,6 +149,7 @@ function partFields(options={}) { label: '{% trans "Copy Image" %}', help_text: '{% trans "Copy image from original part" %}', value: true, + group: 'duplicate', }, fields.copy_bom = { @@ -138,6 +157,7 @@ function partFields(options={}) { label: '{% trans "Copy BOM" %}', help_text: '{% trans "Copy bill of materials from original part" %}', value: global_settings.PART_COPY_BOM, + group: 'duplicate', }; fields.copy_parameters = { @@ -145,6 +165,7 @@ function partFields(options={}) { label: '{% trans "Copy Parameters" %}', help_text: '{% trans "Copy parameter data from original part" %}', value: global_settings.PART_COPY_PARAMETERS, + group: 'duplicate', }; } @@ -191,8 +212,11 @@ function editPart(pk, options={}) { edit: true }); + var groups = partGroups({}); + constructForm(url, { fields: fields, + groups: partGroups(), title: '{% trans "Edit Part" %}', reload: true, }); @@ -221,6 +245,7 @@ function duplicatePart(pk, options={}) { constructForm('{% url "api-part-list" %}', { method: 'POST', fields: fields, + groups: partGroups(), title: '{% trans "Duplicate Part" %}', data: data, onSuccess: function(data) {