I have a Django
formset within a form that is build using django-crispy-forms
and bootstrap 4.0.0-alpha.6
. It looks like so:
{% block content %}
<div>
<h1 class="text-center">Create New Activity</h1>
<div class="row">
<div class="col"></div>
<div class="col-md-8 col-lg-8">
<form role="form" method="post">
{% csrf_token %}
{{ form|crispy }}
{{ activitykeycharacteristics_formset|crispy }}
<hr>
<button class="primaryAction btn btn-primary pull-right ml-1" type="submit">{% trans "Submit" %}</button>
<a class="btn btn-secondary pull-right" href="{{ request.META.HTTP_REFERER }}" role="button">{% trans "Cancel" %}</a>
</form>
</div>
<div class="col"></div>
</div>
</div>
{% endblock content %}
What I have been trying to do is include add and delete buttons so that I can add or delete forms from the formset. At the moment it renders with one form in the formset and so only an add button should be seen, but once there is more than one a delete button should also be seen.
I'm pretty sure the best way to do this is to use jQuery
to add and remove the forms but I haven't been able to get this to work properly. I thought that I had the add button working using Dave's answer in this SO question. But I couldn't get it to correctly update the indices of the inputs. I couldn't get the accepted answer in that question to work, and I'm not sure why.
The form in the formset is composed of two django-autocomplete-light dropdowns.
If anyone could help me with this I would really appreciate it.
Thanks for your time.
--UPDATE--
Here is the js code with an updated template:
js file:
function updateElementIndex(el, prefix, ndx) {
let id_regex = new RegExp('(' + prefix + '-\\d+)');
let replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.key_characteristic) el.key_characteristic = el.key_characteristic.replace(id_regex, replacement);
if (el.data_type) el.data_type = el.data_type.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
let newElement = $(selector).clone(true);
let total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
// Not sure how to adapt this to work with two inputs
let id;
let key_characteristic = $(this).attr('key_characteristic');
let data_type = $(this).attr('data_type');
if (key_characteristic) {
key_characteristic.replace('-' + (total-1) + '-', '-' + total + '-');
id = 'id_' + key_characteristic;
} else if (data_type) {
data_type.replace('-' + (total-1) + '-', '-' + total + '-');
id = 'id_' + data_type;
}
$(this).attr({'key_characteristic': key_characteristic, 'data_type': data_type, 'id': id}).val('').removeAttr('checked');
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
}
function deleteForm(prefix, btn) {
let total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('#formset').remove();
let forms = $('#formset');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (let i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
}
$('#add-form').on('click', function(e) {
e.preventDefault();
cloneMore('#formset:last', 'form');
});
$('#remove-form').on('click', function(e) {
e.preventDefault();
deleteForm('form', $(this));
});
Updated Template:
{% block content %}
<div>
<h1 class="text-center">Create New Activity</h1>
<div class="row">
<div class="col"></div>
<div class="col-md-8 col-lg-8">
<form role="form" method="post">
{% csrf_token %}
{{ form|crispy }}
<div id="formset">
{{ activitykeycharacteristics_formset.management_form }}
{% for form in activitykeycharacteristics_formset.forms %}
{{ form|crispy }}
{% if forloop.counter != 1 %}
<button class="btn btn-primary" id="add-form"><i class="fa fa-plus"></i></button>
<button class="btn btn-danger" id="remove-form"><i class="fa fa-minus"></i></button>
{% else %}
<button class="btn btn-primary" id="add-form"><i class="fa fa-plus"></i></button>
{% endif %}
{% endfor %}
</div>
{# {{ activitykeycharacteristics_formset|crispy }}#}
<hr>
<button class="primaryAction btn btn-primary pull-right ml-1" type="submit">{% trans "Submit" %}</button>
<a class="btn btn-secondary pull-right" href="{{ request.META.HTTP_REFERER }}" role="button">{% trans "Cancel" %}</a>
</form>
</div>
<div class="col"></div>
</div>
</div>
{% endblock content %}