The task
Create a user-friendly form that automatically stores all user input in the browser’s local storage, ensuring that when the user revisits the page or reloads the tab, their previously entered information is conveniently preloaded.
The form
Let’s create a simple form in html as a starting point, before we can enhance it. Don’t think about styling it for now, you can always do that later. Also I left out the field errors, hidden_fields and non_field_errors since they are not relevant for this post, but make sure to include them before going into production.
<form>
{% csrf_token %}
{% for field in form.visible_fields %}
{{ field.label }}: {{ field }}
<p>{{ visible_field.errors }}</p>
{% endfor %}
<button>Submit</button>
</form>
For a form we will just use a classic Django form:
class AwesomeForm(forms.Form):
first_name = forms.CharField()
last_name = forms.CharField()
Introduce Alpine.js
Download the minimized Alpine.js file, put it into your statics folder and load it in your <head>
:
{% load static %}
<head>
...
<script src="{% static 'alpine.min.js' %}" defer></script>
</head>
Now what we want to do is create an Alpine component by setting the x-data attribute to the <form>
element with some random values:
<form x-data="{id_first_name: 'Harry', id_last_name: 'Potter'}">
...
</form>
And the attribute x-model to the input field, which binds the value of an <input>
field to the Alpine data defined above:
<input type="text" id="id_first_name" name="first_name" x-model="id_first_name">
The problem is that by using Django’s templating language to autogenerate the <input>
field,
we cannot set arbitrary attributes from within the template.
Here is where a prominent Django package comes in handy.
Introduce django-widget-tweaks
django-widget-tweaks is a popular package in the Django community that allows you to add attributes for form fields inside the template on the fly.
So to achieve the <input>
field with the attribute as described before, we can do:
{% for field in form.visible_fields %}
{{ field.label }}: {% render_field field x-model=field.id_for_label %}
<p>{{ visible_field.errors }}</p>
{% endfor %}
So now when we load our form, the values “Harry” and “Potter” are prefilled in the input fields.
Introduce localStorage
Now we want to save the value of an <input>
field to the localStorage every time it changes.
For that we can use the x-on:change attribute that calls a function updateLocalStorage
every time an input changes1.
{% render_field field x-model=field.id_for_label x-on::change="updateLocalStorage" %}
function updateLocalStorage(evt) {
const id = evt.target.id
localStorage[id] = evt.target.value
}
Prefill data from localStorage
To do this, we need to change the x-data attribute of the form to:
<form x-data="{id_first_name: localStorage.getItem('id_first_name'), id_last_name: localStorage.getItem('id_last_name')}">
...
</form>
This will get the value for the input field from the local storage and fill it in the <input>
field, since it is bound to that value with x-model.
Limitations
- You have to specify every field separately inside the x-data attribute, but you can extend it to arbitrary fields with some templating skills.
- Currently the
localStorage.getItem(...)
will override any initial values set for the form field. Make sure you account for that too.
-
Note that for django-widget-tweaks we need to set the attribute as
x-on::change
with a double colon. ↩