From dc67665796c7502cda0280e03476b1825636447c Mon Sep 17 00:00:00 2001 From: alexbainter Date: Tue, 13 Jan 2026 13:14:08 -0600 Subject: [PATCH 1/2] Make formset items collapsible --- core/static/css/site.css | 14 +++++- core/static/js/site.js | 50 +++++++++++++++---- .../core/manage/documentation_item_form.html | 20 +++++--- core/templates/core/manage/schema.html | 4 +- .../core/manage/schema_ref_form.html | 18 +++++-- 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/core/static/css/site.css b/core/static/css/site.css index d0685fe..7ef790c 100644 --- a/core/static/css/site.css +++ b/core/static/css/site.css @@ -510,10 +510,20 @@ a.button--primary:hover { padding-top: 0; } -.formset .formset__close-trigger { +.formset--collapsed button[data-formset-expand-collapse-toggle] { + transform: rotate(180deg); +} + +.formset--collapsed .formset-content--collapsible { + display: none; +} + +.schema-ref-formset__actions { position: absolute; right: 0.75rem; - top: 0.75rem; + top: 1rem; + display: flex; + gap: 1rem; } .formset-controls { diff --git a/core/static/js/site.js b/core/static/js/site.js index 84f5f46..edfc460 100644 --- a/core/static/js/site.js +++ b/core/static/js/site.js @@ -23,14 +23,24 @@ /** @param {HTMLElement} formsetElement */ const attachFormsetControlHandlers = (formsetElement) => { - const closeTriggerElements = Array.from( - formsetElement.getElementsByClassName('formset__close-trigger') - ); - closeTriggerElements.forEach((element) => { + Array.from( + formsetElement.querySelectorAll('[data-formset-close-trigger]') + ).forEach((element) => { element.addEventListener('click', () => { formsetElement.remove(); }); }); + Array.from( + formsetElement.querySelectorAll('[data-formset-expand-collapse-toggle]') + ).forEach((element) => { + element.addEventListener('click', () => { + if (formsetElement.classList.contains('formset--collapsed')) { + formsetElement.classList.remove('formset--collapsed'); + return; + } + formsetElement.classList.add('formset--collapsed'); + }); + }); }; /** @@ -45,7 +55,7 @@ * * @param {HTMLElement} formsetListElement */ - const initializeFormsetListManagementForm = (formsetListElement) => { + const initializeFormsetList = (formsetListElement) => { const formsetListId = formsetListElement.getAttribute( 'data-formset-list-id' ); @@ -73,14 +83,32 @@ ); return; } - totalFormInput.value = formsetListElement - .getElementsByClassName('formset') - .length.toString(); - // If nodes were not added, we're done. Otherwise, wire up the handlers. + const formsetElements = + formsetListElement.getElementsByClassName('formset'); + totalFormInput.value = formsetElements.length.toString(); + // If the number of formset items changed, + // insert the new count for each. + if ( + (mutation.addedNodes && mutation.addedNodes.length) || + (mutation.removedNodes && mutation.removedNodes.length) + ) { + Array.from(formsetElements).forEach((formsetElement, index) => { + const count = (index + 1).toString(); + formsetElement + .querySelectorAll('[data-formset-count]') + .forEach((countElement) => { + if (countElement instanceof HTMLElement) { + countElement.innerText = count; + } + }); + }); + } + // If nodes were not added, we're done. + // Otherwise, wire up the handlers and insert the correct count. if (!mutation.addedNodes || !mutation.addedNodes.length) { return; } - mutation.addedNodes.forEach((node) => { + Array.from(mutation.addedNodes).forEach((node) => { if ( node instanceof HTMLElement && node.classList.contains('formset') @@ -202,7 +230,7 @@ if (!(formsetListElement instanceof HTMLElement)) { return; } - initializeFormsetListManagementForm(formsetListElement); + initializeFormsetList(formsetListElement); Array.from( formsetListElement.getElementsByClassName('formset') ).forEach((formsetElement) => { diff --git a/core/templates/core/manage/documentation_item_form.html b/core/templates/core/manage/documentation_item_form.html index 524181f..c53e8e8 100644 --- a/core/templates/core/manage/documentation_item_form.html +++ b/core/templates/core/manage/documentation_item_form.html @@ -1,12 +1,20 @@ -
  • - -
    +
  • +
    + + +
    +
    + Documentation Item {{ count }} +
    +
    {% include "core/manage/field.html" with field=form.name %} {% include "core/manage/field.html" with field=form.role %}
    -
    +
    {% include "core/manage/field.html" with field=form.url %} {% include "core/manage/field.html" with field=form.format %}
    diff --git a/core/templates/core/manage/schema.html b/core/templates/core/manage/schema.html index fca7cb8..b77f85a 100644 --- a/core/templates/core/manage/schema.html +++ b/core/templates/core/manage/schema.html @@ -24,7 +24,7 @@

    Definition URL

      {{ form.schema_refs_formset.management_form }} {% for subform in form.schema_refs_formset %} - {% include "core/manage/schema_ref_form.html" with form=subform %} + {% include "core/manage/schema_ref_form.html" with form=subform count=forloop.counter %} {% endfor %}
    @@ -42,7 +42,7 @@

    Additional documentation

      {{ form.additional_documentation_items_formset.management_form }} {% for subform in form.additional_documentation_items_formset %} - {% include "core/manage/documentation_item_form.html" with form=subform %} + {% include "core/manage/documentation_item_form.html" with form=subform count=forloop.counter %} {% endfor %}
    diff --git a/core/templates/core/manage/schema_ref_form.html b/core/templates/core/manage/schema_ref_form.html index eb271f6..1c4b611 100644 --- a/core/templates/core/manage/schema_ref_form.html +++ b/core/templates/core/manage/schema_ref_form.html @@ -1,8 +1,16 @@ -
  • - -
    +
  • +
    + + +
    +
    + Schema {{ count }} +
    +
    {% include "core/manage/field.html" with field=form.name %} {% include "core/manage/field.html" with field=form.url %}
    From d8455d9d8d0459f401e2097143320f2e23242dd0 Mon Sep 17 00:00:00 2001 From: alexbainter Date: Tue, 13 Jan 2026 13:26:56 -0600 Subject: [PATCH 2/2] Update documentation --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 692fd36..ef260ea 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,10 @@ We have JavaScript support for dynamic formsets as part of a form. For an exampl 1. Pass a custom `prefix` to the formset, e.g: `formset = FormsetFactory(prefix="")`. 2. Create a `ul` element for the formset elements with the attribute `data-formset-list-id=""`. 3. Render the formset's `empty_form` somewhere on the page inside an element with the attribute `data-formset-template-for-id=""`. -4. To use buttons to add and remove formset items to and from the formset, use the attribute `data-formset-append-to-list-id=""` on the append button and `data-formset-remove-from-list-id=""` on the remove button. +4. Formset item templates should be fully contained within an element with the ".formset" class. +5. To use buttons to add and remove formset items to and from the formset, use the attribute `data-formset-append-to-list-id=""` on the append button and `data-formset-remove-from-list-id=""` on the remove button. +6. Inside a formset item, elements with the `data-formset-count` attribute will have their contents replaced with the current count of the formset item (e.g. "1" for the first formset item, "2" for the second, etc). +7. To make formset items collapsible, add the `data-formset-expand-collapse-toggle` to some clickable element. This will toggle the ".formset--collapsible" class on the formset. Items within the formset which should be hidden when collapsed should have the ".formset-content--collapsible" class. ### URL Format Select Field