Add all the relevant search messages

This commit is contained in:
Jake Howard 2022-08-02 21:11:35 +01:00
parent bcc9a2c2f2
commit 996f7b9c2a
Signed by: jake
GPG key ID: 57AFB45680EDD477
6 changed files with 40 additions and 15 deletions

View file

@ -1,10 +1,15 @@
window.addEventListener("load", () => { window.addEventListener("DOMContentLoaded", () => {
const searchResults = document.getElementById("search-results"); const searchResults = document.getElementById("search-results");
const resultsCount = document.getElementById("result-count"); const resultsCountDisplay = document.getElementById("result-count");
function handleSearchResults(event) { function handleSearchResults(event) {
resultsCount.textContent = const resultsCount = event.target.querySelectorAll(".listing-item").length;
event.target.querySelectorAll(".listing-item").length; if (resultsCount) {
resultsCountDisplay.textContent =
`Found ${resultsCount} result` + (resultsCount > 1 ? "s" : "");
} else {
resultsCountDisplay.textContent = "";
}
} }
searchResults.addEventListener("htmx:after-swap", handleSearchResults); searchResults.addEventListener("htmx:afterSwap", handleSearchResults);
}); });

View file

@ -6,6 +6,7 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
body.page-searchpage { body.page-searchpage {
.search-controls { .search-controls {
margin: 2rem auto; margin: 2rem auto;
@ -15,4 +16,9 @@ body.page-searchpage {
#search-indicator { #search-indicator {
animation: search-loading 1.5s linear infinite; animation: search-loading 1.5s linear infinite;
} }
#search-results > p {
font-size: 1.5rem;
text-align: center;
}
} }

View file

@ -1,6 +1,6 @@
from django.core.paginator import EmptyPage, Paginator from django.core.paginator import EmptyPage, Paginator
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.http.response import HttpResponse, HttpResponseBadRequest from django.http.response import HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -24,9 +24,10 @@ class SearchPage(BaseContentMixin, RoutablePageMixin, BasePage): # type: ignore
content_panels = BasePage.content_panels + BaseContentMixin.content_panels content_panels = BasePage.content_panels + BaseContentMixin.content_panels
search_fields = BasePage.search_fields + BaseContentMixin.search_fields search_fields = BasePage.search_fields + BaseContentMixin.search_fields
PAGE_SIZE = 15 PAGE_SIZE = 15
MIN_SEARCH_TERM = 3
class SearchParamsSerializer(serializers.Serializer): class SearchParamsSerializer(serializers.Serializer):
q = serializers.CharField() q = serializers.CharField(min_length=3)
page = serializers.IntegerField(min_value=1, default=1) page = serializers.IntegerField(min_value=1, default=1)
@cached_property @cached_property
@ -44,18 +45,20 @@ class SearchPage(BaseContentMixin, RoutablePageMixin, BasePage): # type: ignore
context = super().get_context(request) context = super().get_context(request)
context["search_query"] = request.GET.get("q", "") context["search_query"] = request.GET.get("q", "")
context["search_url"] = self.reverse_subpage("results") context["search_url"] = self.reverse_subpage("results")
context["MIN_SEARCH_TERM"] = self.MIN_SEARCH_TERM
return context return context
@route(r"^results/$") @route(r"^results/$")
@method_decorator(require_GET) @method_decorator(require_GET)
def results(self, request: HttpRequest) -> HttpResponse: def results(self, request: HttpRequest) -> HttpResponse:
if not request.GET.get("q", None):
return HttpResponse()
serializer = self.SearchParamsSerializer(data=request.GET) serializer = self.SearchParamsSerializer(data=request.GET)
if not serializer.is_valid(): if not serializer.is_valid():
return HttpResponseBadRequest(serializer.errors) return render(
request,
"search/enter-search-term.html",
{"MIN_SEARCH_TERM": self.MIN_SEARCH_TERM},
)
search_query = serializer.validated_data["q"] search_query = serializer.validated_data["q"]
page_num = serializer.validated_data["page"] page_num = serializer.validated_data["page"]

View file

@ -0,0 +1 @@
<p>Enter a search term (of at least {{ MIN_SEARCH_TERM }} characters) to search</p>

View file

@ -21,7 +21,7 @@
name="q" name="q"
placeholder="Search" placeholder="Search"
hx-get="{{ search_url }}" hx-get="{{ search_url }}"
hx-trigger="keyup changed delay:300ms, search{% if search_query %}, revealed{% endif %}" hx-trigger="keyup changed delay:300ms, search{% if search_query %}, load{% endif %}"
hx-target="#search-results" hx-target="#search-results"
autocomplete="off" autocomplete="off"
value="{{ search_query }}" value="{{ search_query }}"
@ -35,11 +35,17 @@
</span> </span>
</p> </p>
</div> </div>
<p>Showing <span id="result-count">0</span> results</p>
</section> </section>
<section class="container" id="search-results"></section> <section class="container" id="search-results-container">
<p id="result-count"></p>
<div id="search-results">
{% if not search_query %}
{% include "search/enter-search-term.html" %}
{% endif %}
</div>
</section>
{% endblock %} {% endblock %}

View file

@ -4,6 +4,10 @@
{% include "common/listing-item.html" %} {% include "common/listing-item.html" %}
{% endfor %} {% endfor %}
{% if not results and page_num == 1 %}
<p>No results found</p>
{% endif %}
{% if results.has_next %} {% if results.has_next %}
<span hx-get="{{ search_url }}{% querystring page=results.next_page_number %}" hx-trigger="revealed" hx-swap="outerHTML"></span> <span hx-get="{{ search_url }}{% querystring page=results.next_page_number %}" hx-trigger="revealed" hx-swap="outerHTML"></span>
{% endif %} {% endif %}