Skip to content

Instantly share code, notes, and snippets.

@vandorjw
Created March 21, 2023 16:05
Show Gist options
  • Save vandorjw/4294a6cd37ea1b861eb31f9e2322d68e to your computer and use it in GitHub Desktop.
Save vandorjw/4294a6cd37ea1b861eb31f9e2322d68e to your computer and use it in GitHub Desktop.
function Formset(element) {
/*
Dynamic Formset handler for Django formsets.
Events:
* init.formset
* add-form.formset
* remove-form.formset
* renumber-form.formset
*/
if (!(this instanceof Formset)) {
return new Formset(element);
}
var formset = this;
var emptyForm = element.querySelector('.empty-form').firstElementChild;
var formsList = element.querySelector('.forms');
var initialForms = element.querySelector('[name$=INITIAL_FORMS]');
var totalForms = element.querySelector('[name$=TOTAL_FORMS]');
var prefix = initialForms.name.replace(/INITIAL_FORMS$/, '');
function addForm(event) {
// Duplicate empty form.
var newForm = emptyForm.cloneNode(true);
// Update all references to __prefix__ in the elements names.
renumberForm(newForm, '__prefix__', totalForms.value);
// Make it able to delete itself.
newForm.querySelector('[data-formset-remove-form]').addEventListener('click', removeForm);
// Append the new form to the formsList.
formsList.insertAdjacentElement('beforeend', newForm);
element.dispatchEvent(new CustomEvent('add-form.formset', {
detail: {
form: newForm,
formset: formset
}
}));
// Update the totalForms.value
totalForms.value = Number(totalForms.value) + 1;
}
function getForm(target) {
var parent = target.parentElement;
if (parent == document) {
return null;
}
if (parent == formsList) {
return target;
}
return getForm(parent);
}
function renumberForm(form, oldValue, newValue) {
var matchValue = prefix + oldValue.toString()
var match = new RegExp(matchValue);
var replace = prefix + newValue.toString();
['name', 'id', 'for'].forEach(function (attr) {
form.querySelectorAll('[' + attr + '*=' + matchValue + ']').forEach(function (el) {
el.setAttribute(attr, el.getAttribute(attr).replace(match, replace));
});
});
element.dispatchEvent(new CustomEvent('renumber-form.formset', {
detail: {
form: form,
oldValue: oldValue,
newValue: newValue,
formset: formset
}
}));
}
function removeForm(event) {
// Find the form "row": the child of formsList that is the parent of the element
// that triggered this event.
var formToRemove = getForm(event.target);
// Renumber the rows that come after us.
var nextElement = formToRemove.nextElementSibling;
var nextElementIndex = Array.prototype.indexOf.call(formsList.children, formToRemove);
while (nextElement) {
renumberForm(nextElement, nextElementIndex + 1, nextElementIndex);
nextElement = nextElement.nextElementSibling;
nextElementIndex = nextElementIndex + 1;
}
// Remove this row.
formToRemove.remove();
element.dispatchEvent(new CustomEvent('remove-form.formset', {
detail: {
form: formToRemove,
formset: formset
}
}));
// Decrement the management form's count.
totalForms.value = Number(totalForms.value) - 1;
}
element.querySelector('[data-formset-add-form]').addEventListener('click', addForm);
// Allow existing forms to remove themselves.
formsList.querySelectorAll('[data-formset-remove-form]').forEach(
rmBtn => {rmBtn.addEventListener('click', removeForm);
});
element.formset = this;
element.dispatchEvent(new CustomEvent('init.formset', {
detail: {
formset: this
}
}));
this.addForm = addForm;
}
@vandorjw
Copy link
Author

Corresponding HTML

<script src="~/js/formset_manager.js"></script>

<h1>File Upload</h1>
<p>Instructions go here</p>




<form id="documentUploadForm" enctype="multipart/form-data" method="post">

    <input type="hidden" name="TOTAL_FORMS" value="1" />
    <input type="hidden" name="PREFIX" value="FileUploadFormSet" />

    <fieldset disabled class="empty-form" style="display:none">
        <fieldset>
            <div class="form-group row">
                <div class="col-sm-10">
                    <label asp-for="EmptyForm.FileUploadField"></label>
                    <input asp-for="EmptyForm.FileUploadField" type="file" accept=".pdf">
                    <span asp-validation-for="EmptyForm.FileUploadField" class="field-validation-error"></span>
                </div>

                <div class="col-sm-2">
                    <button class="btn-danger btn btn-sm" data-formset-remove-form>Remove</button>
                </div>

            </div>
        </fieldset>
    </fieldset>

    <div class="forms">
    @{
        int i = 0;
        @foreach (var form in Model.FileUploadFormSet)
        {
            <fieldset>
                <div class="form-group row">
                    <div class="col-sm-10">
                        <label asp-for="FileUploadFormSet[i].FileUploadField"></label>
                        <input asp-for="FileUploadFormSet[i].FileUploadField" type="file" accept=".pdf">
                        <span asp-validation-for="FileUploadFormSet[i].FileUploadField" class="field-validation-error"></span>
                    </div>
                </div>
            </fieldset>
            i++;
        }
    }
    </div>

    <fieldset class="controls">
        <button class="btn btn-primary btn-sm" type="button" data-formset-add-form>Add Another File</button>
    </fieldset>

    <hr />

    <input asp-page-handler="Upload" class="btn btn-success" type="submit" value="Upload" />
</form>

<script type="text/javascript">
    new Formset(document.querySelector('#documentUploadForm'));
</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment