Add all the relevant search messages
This commit is contained in:
parent
bcc9a2c2f2
commit
996f7b9c2a
6 changed files with 40 additions and 15 deletions
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
1
website/search/templates/search/enter-search-term.html
Normal file
1
website/search/templates/search/enter-search-term.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<p>Enter a search term (of at least {{ MIN_SEARCH_TERM }} characters) to search</p>
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue