diff --git a/autocomplete/autocomplete.py b/autocomplete/autocomplete.py index ff0b6cf..964878e 100644 --- a/autocomplete/autocomplete.py +++ b/autocomplete/autocomplete.py @@ -75,6 +75,8 @@ class Meta: placeholder (str or None): The placeholder text used on the component Defaults to None. + required (bool): If set the control is marked as required. + no_result_text (str): The string displayed when no results are found. Defaults to "No results found." @@ -108,6 +110,9 @@ class Meta: # The placeholder text. (Typically something like "Type to search...") placeholder = None + # If set to True the HTML control will be marked as required + required = False + # The minimum search length to perform a search and show the dropdown. minimum_search_length = 3 @@ -428,11 +433,12 @@ def put(self, request, method): template.render( { "name": self.name, + "required": self.required, "no_result_text": self.no_result_text, "narrow_search_text": self.narrow_search_text, "route_name": self.get_route_name(), "multiselect": self.multiselect, - "values": self.item_values(items, True), + "values": list(self.item_values(items, True)), "item": target_item, "toggle": items, "swap_oob": data.get("remove", False), @@ -497,12 +503,13 @@ def get(self, request, method): template.render( { "name": self.name, + "required": self.required, "route_name": self.get_route_name(), "label": self.label, "placeholder": self.placeholder, "multiselect": self.multiselect, - "values": self.item_values(selected_options), - "selected_items": selected_options, + "values": list(self.item_values(selected_options)), + "selected_items": list(selected_options), "no_result_text": self.no_result_text, "narrow_search_text": self.narrow_search_text, }, @@ -527,11 +534,12 @@ def get(self, request, method): template.render( { "name": self.name, + "required": self.required, "no_result_text": self.no_result_text, "narrow_search_text": self.narrow_search_text, "route_name": self.get_route_name(), "show": show, - "items": items, + "items": list(items), "total_results": total_results, }, request, diff --git a/autocomplete/static/autocomplete/css/autocomplete.css b/autocomplete/static/autocomplete/css/autocomplete.css index 0e72f77..1b9b84c 100644 --- a/autocomplete/static/autocomplete/css/autocomplete.css +++ b/autocomplete/static/autocomplete/css/autocomplete.css @@ -101,6 +101,11 @@ border-radius: 0; } +.phac_aspc_form_autocomplete .ac_required_input { + opacity: 0; + position: absolute; +} + .phac_aspc_form_autocomplete ul.ac_container li.search-indicator svg{ max-width:25px; max-height:25px; diff --git a/autocomplete/static/autocomplete/js/autocomplete.js b/autocomplete/static/autocomplete/js/autocomplete.js index ae656fc..f54ed9c 100644 --- a/autocomplete/static/autocomplete/js/autocomplete.js +++ b/autocomplete/static/autocomplete/js/autocomplete.js @@ -7,9 +7,7 @@ function phac_aspc_autocomplete_blur_handler(name, sync=false) { if (!sync) { el.value = ''; } else { - console.log('setting value!'); el.value = data_el.getAttribute('data-phac-aspc-autocomplete'); - console.log(el.value); } document.getElementById(name + '__items').classList.remove('show'); } diff --git a/autocomplete/templates/autocomplete/chip.html b/autocomplete/templates/autocomplete/chip.html index 69cd996..e59b153 100644 --- a/autocomplete/templates/autocomplete/chip.html +++ b/autocomplete/templates/autocomplete/chip.html @@ -6,8 +6,9 @@ {{ item.label }} @@ -17,4 +18,4 @@ - \ No newline at end of file + diff --git a/autocomplete/templates/autocomplete/component.html b/autocomplete/templates/autocomplete/component.html index d179343..2cccb20 100644 --- a/autocomplete/templates/autocomplete/component.html +++ b/autocomplete/templates/autocomplete/component.html @@ -5,6 +5,7 @@
{# Hidden input elements used to maintain the component's state #} {# and used when submitting forms #} @@ -40,9 +41,6 @@ - {% comment %}
- -
{% endcomment %}
{# This code snippet loads the required CSS and JS if not already loaded. #} diff --git a/autocomplete/templates/autocomplete/item.html b/autocomplete/templates/autocomplete/item.html index 2d36f0c..3270cc4 100644 --- a/autocomplete/templates/autocomplete/item.html +++ b/autocomplete/templates/autocomplete/item.html @@ -1,16 +1,18 @@ +{% load autocomplete %} {{ item.label }} diff --git a/autocomplete/templates/autocomplete/textinput.html b/autocomplete/templates/autocomplete/textinput.html index 04e0efb..f7e62e9 100644 --- a/autocomplete/templates/autocomplete/textinput.html +++ b/autocomplete/templates/autocomplete/textinput.html @@ -11,9 +11,12 @@ {% if not multiselect and selected_items|length == 1 %} value="{{ selected_items.0.label }}" {% endif %} + {% if selected_items|length == 0 %} + required + {% endif %} hx-get="{% url route_name method='items' %}" hx-trigger="keyup changed delay:250ms,click,focus,search" - hx-include="#{{ route_name }} > input[name='{{ name }}']" + hx-include="#{{ route_name }}" hx-target="#{{ route_name }}__items" hx-swap="outerHTML" {% if swap_oob %} diff --git a/autocomplete/templates/autocomplete/values.html b/autocomplete/templates/autocomplete/values.html index e5eda84..758735d 100644 --- a/autocomplete/templates/autocomplete/values.html +++ b/autocomplete/templates/autocomplete/values.html @@ -3,3 +3,6 @@ {% for value in values %} {% endfor %} +{% if required and values|length == 0 %} + +{% endif %} \ No newline at end of file diff --git a/autocomplete/templatetags/autocomplete.py b/autocomplete/templatetags/autocomplete.py index 621ecda..aca5ebb 100644 --- a/autocomplete/templatetags/autocomplete.py +++ b/autocomplete/templatetags/autocomplete.py @@ -1,14 +1,22 @@ """ Django template tags to facilitate rendering of the component """ +import hashlib + from django import template from django import urls from django.utils.html import format_html from django.utils.http import urlencode from django.template import loader +from django.template.defaultfilters import stringfilter register = template.Library() +@register.filter +@stringfilter +def make_id(value): + """Generate an ID given a string, to use as element IDs in HTML""" + return hashlib.sha1(value.encode('utf-8')).hexdigest() @register.simple_tag def autocomplete(name, selected=None): diff --git a/autocomplete/widgets.py b/autocomplete/widgets.py index 945587a..57e77c6 100644 --- a/autocomplete/widgets.py +++ b/autocomplete/widgets.py @@ -14,6 +14,7 @@ class Autocomplete(Widget): name (str): The name of the component (must be unique) options (dict): See [autocomplete.py](../autocomplete.py) for more info label (str) + required (bool) Defaults to false placeholder (str) no_result_text (str) Defaults to "No results found." narrow_search_text (str) Defaults to @@ -41,6 +42,7 @@ def __init__( super().__init__(attrs) config = { 'name': name, + 'required': opts.get('required', None), 'route_name': opts.get('route_name', None), 'label': opts.get('label', None), 'placeholder': opts.get('placeholder', None), @@ -98,11 +100,12 @@ def get_context(self, name, value, attrs): ) context['name'] = self.a_c.name + context['required'] = self.a_c.required context['route_name'] = self.a_c.get_route_name() context['label'] = self.a_c.label context['placeholder'] = self.a_c.placeholder context['multiselect'] = self.a_c.multiselect - context['values'] = self.a_c.item_values(self.a_c, selected_options) - context['selected_items'] = selected_options + context['values'] = list(self.a_c.item_values(self.a_c, selected_options)) + context['selected_items'] = list(selected_options) return context diff --git a/setup.cfg b/setup.cfg index 0a1f137..f3871f5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = django-htmx-autocomplete -version = 0.1.1 +version = 0.1.2 description = A Django autocomplete component powered by htmx long_description = file: README.md long_description_content_type = text/markdown