Add "groups" to API forms

This commit is contained in:
Oliver Walters 2021-08-13 21:48:48 +10:00
parent cd4a797e71
commit 5b42ab7332
4 changed files with 161 additions and 19 deletions

View File

@ -730,6 +730,13 @@
padding: 10px; padding: 10px;
} }
.form-panel {
border-radius: 5px;
border: 1px solid #ccc;
padding: 5px;
}
.modal input { .modal input {
width: 100%; width: 100%;
} }

View File

@ -276,6 +276,7 @@
constructForm('{% url "api-part-list" %}', { constructForm('{% url "api-part-list" %}', {
method: 'POST', method: 'POST',
fields: fields, fields: fields,
groups: partGroups(),
title: '{% trans "Create Part" %}', title: '{% trans "Create Part" %}',
onSuccess: function(data) { onSuccess: function(data) {
// Follow the new part // Follow the new part

View File

@ -264,6 +264,10 @@ function constructForm(url, options) {
// Default HTTP method // Default HTTP method
options.method = options.method || 'PATCH'; options.method = options.method || 'PATCH';
// Default "groups" definition
options.groups = options.groups || {};
options.current_group = null;
// Construct an "empty" data object if not provided // Construct an "empty" data object if not provided
if (!options.data) { if (!options.data) {
options.data = {}; options.data = {};
@ -413,6 +417,11 @@ function constructFormBody(fields, options) {
fields[field].choices = field_options.choices; fields[field].choices = field_options.choices;
} }
// Group
if (field_options.group) {
fields[field].group = field_options.group;
}
// Field prefix // Field prefix
if (field_options.prefix) { if (field_options.prefix) {
fields[field].prefix = field_options.prefix; fields[field].prefix = field_options.prefix;
@ -465,8 +474,12 @@ function constructFormBody(fields, options) {
html += constructField(name, field, options); html += constructField(name, field, options);
} }
// TODO: Dynamically create the modals, if (options.current_group) {
// so that we can have an infinite number of stacks! // Close out the current group
html += `</div></div>`;
console.log(`finally, ending group '${console.current_group}'`);
}
// Create a new modal if one does not exists // Create a new modal if one does not exists
if (!options.modal) { if (!options.modal) {
@ -535,6 +548,8 @@ function constructFormBody(fields, options) {
submitFormData(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) { function initializeRelatedFields(fields, options) {
var field_names = options.field_names; var field_names = options.field_names;
@ -1353,6 +1411,8 @@ function renderModelData(name, model, data, parameters, options) {
*/ */
function constructField(name, parameters, options) { function constructField(name, parameters, options) {
var html = '';
// Shortcut for simple visual fields // Shortcut for simple visual fields
if (parameters.type == 'candy') { if (parameters.type == 'candy') {
return constructCandyInput(name, parameters, options); return constructCandyInput(name, parameters, options);
@ -1365,13 +1425,62 @@ function constructField(name, parameters, options) {
return constructHiddenInput(name, parameters, options); return constructHiddenInput(name, parameters, options);
} }
// Are we ending a group?
if (options.current_group && parameters.group != options.current_group) {
html += `</div></div>`;
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 += `
<div class='panel form-panel' id='form-panel-${group}' group='${group}'>
<div class='panel-heading form-panel-heading' id='form-panel-heading-${group}'>`;
if (group_options.collapsible) {
html += `
<div data-toggle='collapse' data-target='#form-panel-content-${group}'>
<a href='#'><span id='group-icon-${group}' class='fas fa-angle-up'></span>
`;
} else {
html += `<div>`;
}
html += `<h4 style='display: inline;'>${group_options.title || group}</h4>`;
if (group_options.collapsible) {
html += `</a>`;
}
html += `
</div></div>
<div class='panel-content form-panel-content' id='form-panel-content-${group}'>
`;
}
// Keep track of the group we are in
options.current_group = group;
}
var form_classes = 'form-group'; var form_classes = 'form-group';
if (parameters.errors) { if (parameters.errors) {
form_classes += ' has-error'; form_classes += ' has-error';
} }
var html = '';
// Optional content to render before the field // Optional content to render before the field
if (parameters.before) { if (parameters.before) {

View File

@ -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 // Construct fieldset for part forms
function partFields(options={}) { function partFields(options={}) {
@ -48,36 +68,41 @@ function partFields(options={}) {
minimum_stock: { minimum_stock: {
icon: 'fa-boxes', icon: 'fa-boxes',
}, },
attributes: {
type: 'candy',
html: `<hr><h4><i>{% trans "Part Attributes" %}</i></h4><hr>`
},
component: { component: {
value: global_settings.PART_COMPONENT, value: global_settings.PART_COMPONENT,
group: 'attributes',
}, },
assembly: { assembly: {
value: global_settings.PART_ASSEMBLY, value: global_settings.PART_ASSEMBLY,
group: 'attributes',
}, },
is_template: { is_template: {
value: global_settings.PART_TEMPLATE, value: global_settings.PART_TEMPLATE,
group: 'attributes',
}, },
trackable: { trackable: {
value: global_settings.PART_TRACKABLE, value: global_settings.PART_TRACKABLE,
group: 'attributes',
}, },
purchaseable: { purchaseable: {
value: global_settings.PART_PURCHASEABLE, value: global_settings.PART_PURCHASEABLE,
group: 'attributes',
}, },
salable: { salable: {
value: global_settings.PART_SALABLE, value: global_settings.PART_SALABLE,
group: 'attributes',
}, },
virtual: { virtual: {
value: global_settings.PART_VIRTUAL, value: global_settings.PART_VIRTUAL,
group: 'attributes',
}, },
}; };
// If editing a part, we can set the "active" status // If editing a part, we can set the "active" status
if (options.edit) { if (options.edit) {
fields.active = {}; fields.active = {
group: 'attributes'
};
} }
// Pop expiry field // Pop expiry field
@ -91,16 +116,12 @@ function partFields(options={}) {
// No supplier parts available yet // No supplier parts available yet
delete fields["default_supplier"]; delete fields["default_supplier"];
fields.create = {
type: 'candy',
html: `<hr><h4><i>{% trans "Part Creation Options" %}</i></h4><hr>`,
};
if (global_settings.PART_CREATE_INITIAL) { if (global_settings.PART_CREATE_INITIAL) {
fields.initial_stock = { fields.initial_stock = {
type: 'decimal', type: 'decimal',
label: '{% trans "Initial Stock Quantity" %}', label: '{% trans "Initial Stock Quantity" %}',
help_text: '{% trans "Initialize part stock with specified quantity" %}', help_text: '{% trans "Initialize part stock with specified quantity" %}',
group: 'create',
}; };
} }
@ -109,21 +130,18 @@ function partFields(options={}) {
label: '{% trans "Copy Category Parameters" %}', label: '{% trans "Copy Category Parameters" %}',
help_text: '{% trans "Copy parameter templates from selected part category" %}', help_text: '{% trans "Copy parameter templates from selected part category" %}',
value: global_settings.PART_CATEGORY_PARAMETERS, value: global_settings.PART_CATEGORY_PARAMETERS,
group: 'create',
}; };
} }
// Additional fields when "duplicating" a part // Additional fields when "duplicating" a part
if (options.duplicate) { if (options.duplicate) {
fields.duplicate = {
type: 'candy',
html: `<hr><h4><i>{% trans "Part Duplication Options" %}</i></h4><hr>`,
};
fields.copy_from = { fields.copy_from = {
type: 'integer', type: 'integer',
hidden: true, hidden: true,
value: options.duplicate, value: options.duplicate,
group: 'duplicate',
}, },
fields.copy_image = { fields.copy_image = {
@ -131,6 +149,7 @@ function partFields(options={}) {
label: '{% trans "Copy Image" %}', label: '{% trans "Copy Image" %}',
help_text: '{% trans "Copy image from original part" %}', help_text: '{% trans "Copy image from original part" %}',
value: true, value: true,
group: 'duplicate',
}, },
fields.copy_bom = { fields.copy_bom = {
@ -138,6 +157,7 @@ function partFields(options={}) {
label: '{% trans "Copy BOM" %}', label: '{% trans "Copy BOM" %}',
help_text: '{% trans "Copy bill of materials from original part" %}', help_text: '{% trans "Copy bill of materials from original part" %}',
value: global_settings.PART_COPY_BOM, value: global_settings.PART_COPY_BOM,
group: 'duplicate',
}; };
fields.copy_parameters = { fields.copy_parameters = {
@ -145,6 +165,7 @@ function partFields(options={}) {
label: '{% trans "Copy Parameters" %}', label: '{% trans "Copy Parameters" %}',
help_text: '{% trans "Copy parameter data from original part" %}', help_text: '{% trans "Copy parameter data from original part" %}',
value: global_settings.PART_COPY_PARAMETERS, value: global_settings.PART_COPY_PARAMETERS,
group: 'duplicate',
}; };
} }
@ -191,8 +212,11 @@ function editPart(pk, options={}) {
edit: true edit: true
}); });
var groups = partGroups({});
constructForm(url, { constructForm(url, {
fields: fields, fields: fields,
groups: partGroups(),
title: '{% trans "Edit Part" %}', title: '{% trans "Edit Part" %}',
reload: true, reload: true,
}); });
@ -221,6 +245,7 @@ function duplicatePart(pk, options={}) {
constructForm('{% url "api-part-list" %}', { constructForm('{% url "api-part-list" %}', {
method: 'POST', method: 'POST',
fields: fields, fields: fields,
groups: partGroups(),
title: '{% trans "Duplicate Part" %}', title: '{% trans "Duplicate Part" %}',
data: data, data: data,
onSuccess: function(data) { onSuccess: function(data) {