|
8 | 8 | from _strptime import TimeRE
|
9 | 9 | from django.conf import settings
|
10 | 10 | from django.forms import widgets
|
| 11 | +from django.urls import reverse |
11 | 12 | from django.utils import formats
|
12 | 13 | from django.utils.translation import gettext_lazy as _
|
13 | 14 | from phonenumber_field.formfields import PhoneNumberField
|
@@ -857,9 +858,7 @@ def __init__(
|
857 | 858 |
|
858 | 859 | def format_date_value(context):
|
859 | 860 | bfield = hg.resolve_lazy(boundfield, context)
|
860 |
| - return bfield.field.widget.format_value( |
861 |
| - hg.resolve_lazy(inputelement_attrs, context).get("value") |
862 |
| - ) |
| 861 | + return bfield.field.widget.format_value(bfield.value()) |
863 | 862 |
|
864 | 863 | super().__init__(
|
865 | 864 | hg.DIV(
|
@@ -1105,6 +1104,112 @@ class LazySelect(Select):
|
1105 | 1104 | django_widget = django_countries.widgets.LazySelect
|
1106 | 1105 |
|
1107 | 1106 |
|
| 1107 | +class AjaxSearchWidget(BaseWidget): |
| 1108 | + carbon_input_error_class = "bx--text-input--invalid" |
| 1109 | + |
| 1110 | + def __init__( |
| 1111 | + self, |
| 1112 | + label=None, |
| 1113 | + help_text=None, |
| 1114 | + errors=None, |
| 1115 | + inputelement_attrs=None, |
| 1116 | + boundfield=None, |
| 1117 | + **attributes, |
| 1118 | + ): |
| 1119 | + inputelement_attrs = inputelement_attrs or {} |
| 1120 | + searchresult_id = hg.format("{}-searchresult", inputelement_attrs.get("id")) |
| 1121 | + super().__init__( |
| 1122 | + label, |
| 1123 | + hg.DIV( |
| 1124 | + hg.If( |
| 1125 | + getattr(errors, "condition", None), |
| 1126 | + Icon( |
| 1127 | + "warning--filled", |
| 1128 | + size=16, |
| 1129 | + _class="bx--text-input__invalid-icon", |
| 1130 | + ), |
| 1131 | + ), |
| 1132 | + hg.DIV( |
| 1133 | + _("Loading..."), |
| 1134 | + id=hg.format("{}-loader", inputelement_attrs.get("id")), |
| 1135 | + _class="htmx-indicator", |
| 1136 | + style="position: absolute;z-index: 1;right: 8px; pointer-events: none", |
| 1137 | + ), |
| 1138 | + hg.INPUT(type="hidden", lazy_attributes=inputelement_attrs), |
| 1139 | + hg.INPUT( |
| 1140 | + _class=hg.BaseElement( |
| 1141 | + "bx--text-input", |
| 1142 | + hg.If( |
| 1143 | + getattr(errors, "condition", False), |
| 1144 | + " bx--text-input--invalid", |
| 1145 | + ), |
| 1146 | + ), |
| 1147 | + data_invalid=hg.If(getattr(errors, "condition", False), True), |
| 1148 | + name="query", |
| 1149 | + type="text", |
| 1150 | + hx_get=reverse(self.url), |
| 1151 | + hx_trigger="input changed delay:100ms", |
| 1152 | + hx_target=hg.format("#{}", searchresult_id), |
| 1153 | + hx_indicator=hg.format("#{}-loader", inputelement_attrs.get("id")), |
| 1154 | + onfocusin="this.parentElement.nextElementSibling.nextElementSibling.style.display = 'block'", |
| 1155 | + style="padding-right: 2.5rem", |
| 1156 | + ), |
| 1157 | + hg.SCRIPT( |
| 1158 | + hg.mark_safe( |
| 1159 | + """ |
| 1160 | + let elem = document.currentScript; |
| 1161 | + document.addEventListener('click', (ev) => { |
| 1162 | + if(!elem.parentElement.parentElement.contains(ev.target)) |
| 1163 | + elem.parentElement.nextElementSibling.nextElementSibling.style.display = 'none' |
| 1164 | + }); |
| 1165 | +
|
| 1166 | + document.addEventListener('htmx:load', (ev) => { |
| 1167 | + $$('.result-item', ev.target)._.bind({'click': (e) => { |
| 1168 | + elem.previousElementSibling.previousElementSibling.value = e.target.value; |
| 1169 | + elem.previousElementSibling.value = ''; |
| 1170 | + elem.parentElement.nextElementSibling.firstElementChild.innerText = e.target.innerText; |
| 1171 | + elem.parentElement.nextElementSibling.style.display = 'flex'; |
| 1172 | + elem.parentElement.nextElementSibling.nextElementSibling.style.display = 'none'; |
| 1173 | + }}) |
| 1174 | + })""" |
| 1175 | + ) |
| 1176 | + ), |
| 1177 | + _class="bx--text-input__field-wrapper", |
| 1178 | + data_invalid=hg.If(getattr(errors, "condition", None), True), |
| 1179 | + ), |
| 1180 | + Tag( |
| 1181 | + hg.F( |
| 1182 | + lambda c: hg.resolve_lazy(boundfield, c).field.to_python( |
| 1183 | + hg.resolve_lazy(boundfield, c).value() |
| 1184 | + ) |
| 1185 | + ), |
| 1186 | + can_delete=hg.F( |
| 1187 | + lambda c: not hg.resolve_lazy(boundfield, c).field.required |
| 1188 | + ), |
| 1189 | + style=hg.If( |
| 1190 | + hg.F(lambda c: not hg.resolve_lazy(boundfield, c).value()), |
| 1191 | + "display: none", |
| 1192 | + ), |
| 1193 | + ondelete=hg.format( |
| 1194 | + """document.getElementById('{}').value = ''; this.parentElement.style.display = 'none'""", |
| 1195 | + inputelement_attrs.get("id"), |
| 1196 | + ), |
| 1197 | + ), |
| 1198 | + hg.DIV( |
| 1199 | + hg.SPAN("...", style="padding: 8px"), |
| 1200 | + id=searchresult_id, |
| 1201 | + style="border-left: solid 1px gray; border-right: solid 1px gray; border-bottom: solid 1px gray; background: white; z-index: 99; display: none", |
| 1202 | + ), |
| 1203 | + errors, |
| 1204 | + help_text, |
| 1205 | + **hg.merge_html_attrs(attributes, {"_class": "bx--text-input-wrapper"}), |
| 1206 | + ) |
| 1207 | + |
| 1208 | + |
| 1209 | +def AjaxSearch(url): |
| 1210 | + return type("SubclassedAjaxSearchWidget", (AjaxSearchWidget,), {"url": url}) |
| 1211 | + |
| 1212 | + |
1108 | 1213 | class MultiWidget(BaseWidget):
|
1109 | 1214 | django_widget = widgets.MultiWidget
|
1110 | 1215 |
|
|
0 commit comments