Skip to content

Commit

Permalink
add: ajax-search field
Browse files Browse the repository at this point in the history
  • Loading branch information
saemideluxe committed Nov 29, 2024
1 parent cb38765 commit 48ae61d
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 3,137 deletions.
4 changes: 4 additions & 0 deletions basxbread/forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ def _generate_formset_class(

def _formfield_callback_with_request(field, request, model, instance, cache_querysets):
kwargs = getattr(field, "formfield_kwargs", {})
if hasattr(field, "widget"):
kwargs["widget"] = field.widget
choices = None
if hasattr(field, "lazy_choices"):
choices = field.lazy_choices(field, request, instance)
Expand All @@ -250,6 +252,8 @@ def _formfield_callback_with_request(field, request, model, instance, cache_quer
kwargs["initial"] = field.lazy_initial(field, request, instance)

ret = field.formfield(**kwargs)
if hasattr(field, "breadwidget"):
ret.breadwidget = field.breadwidget
if isinstance(choices, models.QuerySet):
ret.queryset = choices

Expand Down
1 change: 1 addition & 0 deletions basxbread/layout/components/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..notification import InlineNotification
from .fields import DEFAULT_FORM_CONTEXTNAME, FormField, FormFieldMarker
from .helpers import HelpText, Submit # noqa
from .widgets import *


class Form(hg.FORM):
Expand Down
3 changes: 3 additions & 0 deletions basxbread/layout/components/forms/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ def wrapper(context):
if suggested_widgetclass is not None:
return suggested_widgetclass

if hasattr(realform[fieldname].field, "breadwidget"):
return realform[fieldname].field.breadwidget

# Auto-detection declared by the bread widget has third priority
return (
django2bread_widgetclass(widgetclass, type(realform[fieldname].field))
Expand Down
111 changes: 108 additions & 3 deletions basxbread/layout/components/forms/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from _strptime import TimeRE
from django.conf import settings
from django.forms import widgets
from django.urls import reverse
from django.utils import formats
from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField
Expand Down Expand Up @@ -857,9 +858,7 @@ def __init__(

def format_date_value(context):
bfield = hg.resolve_lazy(boundfield, context)
return bfield.field.widget.format_value(
hg.resolve_lazy(inputelement_attrs, context).get("value")
)
return bfield.field.widget.format_value(bfield.value())

super().__init__(
hg.DIV(
Expand Down Expand Up @@ -1105,6 +1104,112 @@ class LazySelect(Select):
django_widget = django_countries.widgets.LazySelect


class AjaxSearchWidget(BaseWidget):
carbon_input_error_class = "bx--text-input--invalid"

def __init__(
self,
label=None,
help_text=None,
errors=None,
inputelement_attrs=None,
boundfield=None,
**attributes,
):
inputelement_attrs = inputelement_attrs or {}
searchresult_id = hg.format("{}-searchresult", inputelement_attrs.get("id"))
super().__init__(
label,
hg.DIV(
hg.If(
getattr(errors, "condition", None),
Icon(
"warning--filled",
size=16,
_class="bx--text-input__invalid-icon",
),
),
hg.DIV(
_("Loading..."),
id=hg.format("{}-loader", inputelement_attrs.get("id")),
_class="htmx-indicator",
style="position: absolute;z-index: 1;right: 8px; pointer-events: none",
),
hg.INPUT(type="hidden", lazy_attributes=inputelement_attrs),
hg.INPUT(
_class=hg.BaseElement(
"bx--text-input",
hg.If(
getattr(errors, "condition", False),
" bx--text-input--invalid",
),
),
data_invalid=hg.If(getattr(errors, "condition", False), True),
name="query",
type="text",
hx_get=reverse(self.url),
hx_trigger="input changed delay:100ms",
hx_target=hg.format("#{}", searchresult_id),
hx_indicator=hg.format("#{}-loader", inputelement_attrs.get("id")),
onfocusin="this.parentElement.nextElementSibling.nextElementSibling.style.display = 'block'",
style="padding-right: 2.5rem",
),
hg.SCRIPT(
hg.mark_safe(
"""
let elem = document.currentScript;
document.addEventListener('click', (ev) => {
if(!elem.parentElement.parentElement.contains(ev.target))
elem.parentElement.nextElementSibling.nextElementSibling.style.display = 'none'
});
document.addEventListener('htmx:load', (ev) => {
$$('.result-item', ev.target)._.bind({'click': (e) => {
elem.previousElementSibling.previousElementSibling.value = e.target.value;
elem.previousElementSibling.value = '';
elem.parentElement.nextElementSibling.firstElementChild.innerText = e.target.innerText;
elem.parentElement.nextElementSibling.style.display = 'flex';
elem.parentElement.nextElementSibling.nextElementSibling.style.display = 'none';
}})
})"""
)
),
_class="bx--text-input__field-wrapper",
data_invalid=hg.If(getattr(errors, "condition", None), True),
),
Tag(
hg.F(
lambda c: hg.resolve_lazy(boundfield, c).field.to_python(
hg.resolve_lazy(boundfield, c).value()
)
),
can_delete=hg.F(
lambda c: not hg.resolve_lazy(boundfield, c).field.required
),
style=hg.If(
hg.F(lambda c: not hg.resolve_lazy(boundfield, c).value()),
"display: none",
),
ondelete=hg.format(
"""document.getElementById('{}').value = ''; this.parentElement.style.display = 'none'""",
inputelement_attrs.get("id"),
),
),
hg.DIV(
hg.SPAN("...", style="padding: 8px"),
id=searchresult_id,
style="border-left: solid 1px gray; border-right: solid 1px gray; border-bottom: solid 1px gray; background: white; z-index: 99; display: none",
),
errors,
help_text,
**hg.merge_html_attrs(attributes, {"_class": "bx--text-input-wrapper"}),
)


def AjaxSearch(url):
return type("SubclassedAjaxSearchWidget", (AjaxSearchWidget,), {"url": url})


class MultiWidget(BaseWidget):
django_widget = widgets.MultiWidget

Expand Down
17 changes: 9 additions & 8 deletions basxbread/layout/components/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ def __init__(self, *label, can_delete=False, tag_color=None, **kwargs):
kwargs.setdefault(
"type", "button"
) # prevents this from trying to submit a form when inside a FORM element
kwargs["_class"] = (
kwargs.get("_class", "")
+ " bx--tag"
+ (" bx--tag--filter" if can_delete else "")
+ (f" bx--tag--{tag_color}" if tag_color else "")
kwargs["_class"] = hg.BaseElement(
kwargs.get("_class", ""),
" bx--tag",
hg.If(can_delete, " bx--tag--filter"),
(f" bx--tag--{tag_color}" if tag_color else ""),
)
if can_delete:
kwargs.setdefault("title", _("Remove"))

on_del = kwargs.pop("ondelete", None)
super().__init__(
hg.SPAN(*label, _class="bx--tag__label"),
*([Icon("close", size=16)] if can_delete else []),
hg.If(
can_delete, Icon("close", size=16, onclick=on_del, title=_("Remove"))
),
**kwargs,
)
2 changes: 1 addition & 1 deletion basxbread/static/js/basxbread.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 48ae61d

Please sign in to comment.