I have a simple app where I want to have a single filter dynamically update the queryset in two different Views at the same time.
In this example, the user can filter based on city/country/continent and I'd like to dynamically update (1) the table showing the relevant objects in the model, and (2) the leaflet map to plot the points.
I think the main issue here is that I need to trigger an update to the filter queryset on multiple 'views' at the same time and not sure how to structure my project to achieve that. Or if that's the right way to think about the problem.
I'm trying to have a filter work with{% for city in cityFilterResults %}
iterator in two different views at the same time.
How can I achieve having two different views update based on a Filter using HTMX?
index.html
:
(Note: My expected behaviour works if I switch the hx-get
URL between either the table or the map. But they don't filter together and this is the issue I'm stuck on.)
<body>
<h3>Filter Controls:</h3><br>
<form hx-get="{% url '_city_table' %}" hx-target="#cityTable">
<!-- <form hx-get="{% url '_leaflet' %}" hx-target="#markers"> -->
{% csrf_token %}
{{ cityFilterForm.form.as_p }}
<button type="submit" class="btn btn-primary"> Filter </button>
</form>
[...]
<!-- Table Body -->
<tbody id="cityTable" hx-get="{% url '_city_table' %}" hx-trigger="load" hx-target="nearest tr"> </tbody>
[...]
<!-- Div to get the markers -->
<div id="markers" hx-get="{% url '_leaflet' %}" hx-trigger="my_custom_trigger from:body"> </div>
</body>
<script>
[... Leaflet stuff ...]
document.getElementById('markers');
</script>
_city_table.html
:
{% for city in cityFilterResults %}
<tr>
<td> {{ city.city }} </td>
<td> {{ city.country }} </td>
<td> {{ city.continent }} </td>
<td> {{ city.latitude|floatformat:4 }} </td>
<td> {{ city.longitude|floatformat:4 }} </td>
</tr>
{% endfor %}
_leaflet.html
:
<script>
if (map.hasLayer(group)) {
console.log('LayerGroup already exists');
group.clearLayers();
map.removeLayer(group);
} else {
console.log('LayerGroup doesn't exist');
}
{% for city in cityFilterResults %}
var marker = L.marker([{{city.latitude}}, {{city.longitude}}]).addTo(group)
.bindPopup("{{city.city}}")
{% endfor %}
group.addTo(map);
</script>
views.py
:
def _city_table(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
response = render(request, '_city_table.html', context)
response['HX-Trigger'] = 'my_custom_trigger'
return response
def _leaflet(request):
city_filter_form = cityFilter(request.GET, queryset=cityModel.objects.all())
city_filter_results = city_filter_form.qs
context={ 'cityModel': cityModel.objects.all(),
'cityFilterResults': city_filter_results }
return render(request, '_leaflet.html', context)
When the hx-get
on Filter form points to the URL for _city_table
template, then the table filters as expected but the map does not:
When the hx-get
on Filter form points to the URL for the _leaflet
template, then the map filters but the table does not:
You can find some solution here: https://htmx.org/examples/update-other-content/
In particular ther are two solutions
swap-oob ( https://htmx.org/examples/update-other-content/#oob )
You have to post your form to the action of your controller. The action return the two element that will be replaced
<button hx-get="/Form/OutOfBandResponse" hx-swap="none">ok</button>
<div id="A"></div>
<div id="B"></div>
This is the response:
<!-- this will replace the element with id A -->
<div id="A" hx-swap-oob="true">
Joe Smith
</div>
<!-- this will replace the element with id B -->
<div id="B" hx-swap-oob="true">
Antony Queen
</div>
In alternative you can use events. ( https://htmx.org/examples/update-other-content/#events )
So you have to create a handler for a event, then when you submit the form you have to response with a header that fire that event. When the event fire, the divs react updating their self.