Have you ever been to a site where they have that "+" next to a list of fields or files and wanted to implement that yourself? I was in a situation at work where we wanted to add a bunch of key-value pairs to our database and we didn't know how to do it. As a temporary solution, a team member used formsets with extra=1
, but this required a page refresh for every new entry as well as a database hit (see Adding forms dynamically to a Django formset). Needless to say, I never liked this solution. To make the entry quicker and reduce database hits, I decided to first build the array of key-values in javascript and submit with AJAX.
The simple template I used for a proof of concept was:
<div style="height: 500px" class="w3-container w3-padding-64 w3-theme-l5" id="contact">
<div style="height: 50px"></div>
<form method="post" class="w3-container w3-card-4 w3-display-top w3-padding-16 w3-white" id="form">
{% csrf_token %}
<div id="inputs">
<div class="w3-row">
<div class="w3-quarter">
<input type="text" name="key0" id="key0">
</div>
<div class="w3-quarter">
<input type="text" name="value0" id="value0">
</div>
</div>
</div>
<div class="w3-quarter">
<input type="button" name="add" value="add" onclick="add_another()">
</div>
<div class="w3-quarter">
<input type="button" name="submit_data" value="submit_data" onclick="submit_pairs()">
</div>
</form>
</div>
You can see initial key-value inputs (key0
and value0
) as well as add
and submit
buttons. When the user hits add
, a new key-value pair is added to the DOM and the values in the inputs are saved to the array kv
.
Note that there are better ways to save the data and I'll discuss the one I plan to implement at the end.
Since I already have key0
and value0
in the DOM, I must first add the input values to the array with the current i
value (0) with:
kv.push({key: document.getElementById(key).value, value: document.getElementById(value).value});
I then increment i
to create the new inputs by inserting the elements with the styling I need:
i++;
key = 'key' + i;
value = 'value' + i;
var div = document.createElement("div");
div.innerHTML = '<div class="w3-row">\n' +
'<div class="w3-quarter">\n' +
'<input type="text" name="'+ key + '" id="' + key + '">\n' +
'</div>\n' +
'<div class="w3-quarter">\n' +
'<input type="text" name="' + value + '" id="' + value + '">\n' +
'</div>\n' +
'</div>\n';
document.getElementById("inputs").appendChild(div);
The final result is:
var i = 0;
var kv = [];
function add_another() {
console.log('add');
var key = 'key' + i;
var value = 'value' + i;
kv.push({key: document.getElementById(key).value, value: document.getElementById(value).value});
i++;
key = 'key' + i;
value = 'value' + i;
var div = document.createElement("div");
div.innerHTML = '<div class="w3-row">\n' +
'<div class="w3-quarter">\n' +
'<input type="text" name="'+ key + '" id="' + key + '">\n' +
'</div>\n' +
'<div class="w3-quarter">\n' +
'<input type="text" name="' + value + '" id="' + value + '">\n' +
'</div>\n' +
'</div>\n';
document.getElementById("inputs").appendChild(div);
for (i = 0; i < kv.length; i++) {
console.log("key:- " + kv[i].key + " value:- " + kv[i].value);
}
}
Now the data needs to be submitted. This was my first time using AJAX, but is was much easier than I expected. To submit (using jQuery), I built the function like this:
// AJAX for posting
function submit_pairs() {
console.log("submit is working!"); // sanity check
$.ajax({
url : "", // the endpoint
type : "POST", // http method
data : { 'pairs' : JSON.stringify(kv), 'csrfmiddlewaretoken': '{{csrf_token}}'}, // data sent with the post request
// handle a successful response
success : function(json) {
console.log(json); // log the returned json to the console
console.log("success"); // another sanity check
},
});
}
Here, you can see we are posting the data as JSON to the current URL. The csrf_token must be passed along to prevent Cross-Site Request Forgery.
Handling it in the back-end is much like a normal view:
def pair(request):
if request.method == 'POST':
pairs = request.POST.get('pairs')
pairs = json.loads(pairs)
for pair in pairs:
pair_model, created = Pair.objects.get_or_create(key=pair['key'], value=pair['value'])
print(created)
return JsonResponse({'status':200`, 'created': created})
else:
return render(request, "contributor/pairs.html")
The only difference to a normal view is the pairs = json.loads(pairs)
where the JSON data to converted to a python dictionary. From there the data can be used like any other view.
The only thing left to do is error handling, but I haven't implemented that yet. The error info can be passed to the front end with JsonResponse
to display any problems to the user.
The one thing I will be changing in production is how the data is saved to the array in add_another()
. Right now if the user changes the data after "add" is pressed, the data won't be saved, so it would be best to build the array on submit (in a similar manner).
Any questions or suggestions? Please comment!