Allow users to toggle advanced fields
All checks were successful
Tests / test (push) Successful in 58s
All checks were successful
Tests / test (push) Successful in 58s
ref #204
This commit is contained in:
parent
cd886df05b
commit
3101829885
4 changed files with 142 additions and 1 deletions
|
|
@ -327,6 +327,19 @@ class CrdModelFormMixin:
|
|||
field.widget = forms.HiddenInput()
|
||||
field.required = False
|
||||
|
||||
# Mark advanced fields with a CSS class and data attribute
|
||||
advanced_fields = getattr(self, "ADVANCED_FIELDS", [])
|
||||
for name, field in self.fields.items():
|
||||
if name in advanced_fields:
|
||||
field.widget.attrs.update(
|
||||
{
|
||||
"class": (
|
||||
field.widget.attrs.get("class", "") + " advanced-field"
|
||||
).strip(),
|
||||
"data-advanced": "true",
|
||||
}
|
||||
)
|
||||
|
||||
if self.instance and self.instance.pk:
|
||||
self.fields["name"].disabled = True
|
||||
self.fields["name"].help_text = _("Name cannot be changed after creation.")
|
||||
|
|
@ -513,7 +526,7 @@ class CrdModelFormMixin:
|
|||
pass
|
||||
|
||||
|
||||
def generate_model_form_class(model):
|
||||
def generate_model_form_class(model, advanced_fields=None):
|
||||
meta_attrs = {
|
||||
"model": model,
|
||||
"fields": "__all__",
|
||||
|
|
@ -521,6 +534,7 @@ def generate_model_form_class(model):
|
|||
fields = {
|
||||
"Meta": type("Meta", (object,), meta_attrs),
|
||||
"__module__": "crd_models",
|
||||
"ADVANCED_FIELDS": advanced_fields or [],
|
||||
}
|
||||
class_name = f"{model.__name__}ModelForm"
|
||||
return ModelFormMetaclass(class_name, (CrdModelFormMixin, ModelForm), fields)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
{% load i18n %}
|
||||
{% load get_field %}
|
||||
{% load static %}
|
||||
<form class="form form-vertical crd-form"
|
||||
method="post"
|
||||
{% if form_action %}action="{{ form_action }}"{% endif %}>
|
||||
{% csrf_token %}
|
||||
{% include "frontend/forms/errors.html" %}
|
||||
{% if form.ADVANCED_FIELDS %}
|
||||
<div class="mb-3">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary ml-auto d-block"
|
||||
id="advanced-toggle"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target=".advanced-field-group"
|
||||
aria-expanded="false">
|
||||
<i class="bi bi-gear"></i> {% translate "Show Advanced Options" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
{% for fieldset in form.get_fieldsets %}
|
||||
{% if not fieldset.hidden %}
|
||||
|
|
@ -54,3 +67,4 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<script defer src="{% static 'js/advanced-fields.js' %}"></script>
|
||||
|
|
|
|||
|
|
@ -302,3 +302,33 @@ html[data-bs-theme="dark"] .crd-form .nav-tabs .nav-link .mandatory-indicator {
|
|||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.ml-auto {
|
||||
margin-left: auto !important
|
||||
}
|
||||
|
||||
/* Advanced fields tab flash animation */
|
||||
@keyframes tab-pulse {
|
||||
0%, 100% {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
50% {
|
||||
background-color: var(--brand-light);
|
||||
box-shadow: 0 0 10px rgba(154, 99, 236, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
html[data-bs-theme="dark"] @keyframes tab-pulse {
|
||||
0%, 100% {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(154, 99, 236, 0.2);
|
||||
box-shadow: 0 0 10px rgba(154, 99, 236, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.tab-flash {
|
||||
animation: tab-pulse 1s ease-in-out 2;
|
||||
}
|
||||
|
|
|
|||
83
src/servala/static/js/advanced-fields.js
Normal file
83
src/servala/static/js/advanced-fields.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Advanced Fields Toggle
|
||||
* Handles showing/hiding advanced fields in CRD forms
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function flashTabsWithAdvancedFields() {
|
||||
const advancedGroups = document.querySelectorAll('.advanced-field-group');
|
||||
const tabsToFlash = new Set();
|
||||
advancedGroups.forEach(function(group) {
|
||||
const tabPane = group.closest('.tab-pane');
|
||||
if (tabPane) {
|
||||
const tabId = tabPane.getAttribute('id');
|
||||
if (tabId) {
|
||||
const tabButton = document.querySelector(`[data-bs-target="#${tabId}"]`);
|
||||
if (tabButton && !tabButton.classList.contains('active')) {
|
||||
tabsToFlash.add(tabButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tabsToFlash.forEach(function(tab) {
|
||||
tab.classList.add('tab-flash');
|
||||
setTimeout(function() {
|
||||
tab.classList.remove('tab-flash');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeAdvancedFields() {
|
||||
const advancedInputs = document.querySelectorAll('[data-advanced="true"]');
|
||||
|
||||
if (advancedInputs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
advancedInputs.forEach(function(input) {
|
||||
const formGroup = input.closest('.form-group, .mb-3, .col-12, .col-md-6');
|
||||
if (formGroup) {
|
||||
formGroup.classList.add('advanced-field-group', 'collapse');
|
||||
}
|
||||
});
|
||||
|
||||
const toggleButton = document.getElementById('advanced-toggle');
|
||||
if (toggleButton) {
|
||||
let isExpanded = false;
|
||||
|
||||
document.querySelectorAll('.advanced-field-group').forEach(function(group) {
|
||||
group.addEventListener('shown.bs.collapse', function() {
|
||||
toggleButton.innerHTML = '<i class="bi bi-gear-fill"></i> Hide Advanced Options';
|
||||
if (!isExpanded) {
|
||||
isExpanded = true;
|
||||
setTimeout(flashTabsWithAdvancedFields, 100);
|
||||
}
|
||||
});
|
||||
|
||||
group.addEventListener('hidden.bs.collapse', function() {
|
||||
const anyVisible = Array.from(document.querySelectorAll('.advanced-field-group')).some(
|
||||
g => g.classList.contains('show')
|
||||
);
|
||||
if (!anyVisible) {
|
||||
toggleButton.innerHTML = '<i class="bi bi-gear"></i> Show Advanced Options';
|
||||
isExpanded = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializeAdvancedFields);
|
||||
} else {
|
||||
initializeAdvancedFields();
|
||||
}
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
if (event.detail.target.id === 'service-form' || event.detail.target.closest('.crd-form')) {
|
||||
setTimeout(initializeAdvancedFields, 100);
|
||||
}
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue