Whenever I try a new web library, framework, component, I look how to implement this: a server-based interactive autocomplete / typeahead / select2-like search input. I want an ajax-based search input without writing any client-side JavaScript. That's my graal.
The user starts typing something, you start searching after 300 milliseconds, the widget calls your server, the search is done server-side (because you'll call a database and maybe filter millions of rows).
I have used AngularUI's typeahead (it was actually good :) ), I still use select2 (JQuery-based, you need a JS config, a jS post-process if needed, and an API endpoint that returns JSON (that's doable ;) ).
There's the native datalist, only it's very limited: on Firefox it only shows 6 results by default, you can't style each result. I want to display a little image in each result. It's also a client-side widget, but you can plug in HTMX or Datastar.
And I'm not really a front-end guy, so please share your favorite widget for this use-case! (Maybe ex-shoelace's Web Awesome combobox? It's proprietary though).
Anyway, let's have a quick recap how this works.
HTML - with the unknown Dream-UI
As I wandered into the HTMX Discord, looking for this exact widget, I found someone who made it possible: the person behind dream-html-ui. A "set of components" (it currently has 2) that work fine with HTMX, and Bulma CSS.
Here it is in action:
Grab its small JS file and use its dh-combobox component:
<dh-combobox class=dropdown>
<div class="control dropdown-trigger has-icons-left">
<input class=input type="search"
id="q"
name="q"
autocomplete="off"
data-bind:search
data-on:input__debounce.500ms="@get('/search')"
placeholder="Type a number (one, two…)">
<span class="icon is-small is-left">🔍</span>
</div>
<div class="dropdown-menu" role="menu" id="fruit-dropdown-menu">
<div id="datastar-search-results" class="dropdown-content">
…
We use two Datastar attributes:
data-bind:search
data-on:input__debounce.500ms="@get('/search')"
This fires a signal named "search" on our /search endpoint.
We must return HTML that will replace our id="datastar-search-results" node.
Common Lisp and Datastar-cl
We use https://github.com/fsmunoz/datastar-cl (follow the links and it has a comprehensive guide)
That's our endpoint:
(easy-routes:defroute datastar-dream-ui ("/search") ()
(let* ((signals (datastar-cl:read-signals hunchentoot:*request*))
(q (gethash "search" signals)))
(log:info signals)
(when (and signals
(str:non-blank-string-p q))
(let ((html (render-datalist q)))
(log:info html)
(datastar-cl:with-sse (resp hunchentoot:*request*)
(datastar-cl:patch-elements resp html))))))
They key parts are: read-signals, patch elements with datastar-cl:with-sse and patch-elements. We return HTML with our render-datalist function.
(defun render-datalist (q &optional (n 50))
"Return HTML (string): a button by matching result.
Each button displays a small image.
The HTML fits in dream-ui's Bulma dropdown style."
(with-output-to-string (s)
(format s "<div id=\"datastar-search-results\" class=\"dropdown-content\">")
(loop for option in (gen-data n)
for i = 0 then (incf i)
when (str:containsp q option)
collect (markup:write-html-to-stream
<button class="dropdown-item"
tabindex=i
value=option
role="menuitem">
<table>
<tr>
<td>
<img src="https://lispcookbook.github.io/cl-cookbook/orly-cover.png"
style="max-width: 50px"/>
</td>
<td style="padding-left: 5px">
,(progn option)
</td>
</tr>
</table>
</button>
s))
(format s "~&</div>")
))
Our fake search only filters a string like "one" inside a list of numbers (written in plain english) with:
(defun gen-data (&optional (n 50))
(loop for i below n
collect (format nil "~R" i)))
Notice how we reference our DOM id we want to replace:
(format s "<div id=\"datastar-search-results\" class=\"dropdown-content\">")
And, that's it.
The snippet you can try: https://github.com/vindarel/bacalisp/blob/master/web-stuff/datastar-dream-ui.lisp
It's quickly thrown together. I'll surely update and fix obvious stuff later. Hey, it works.
They keyboard selection works. Press up and down keys, press Enter.
I have an interactive, server-based, select2-like search input that doesn't require any JavaScript.
I'll keep you updated when I use it in the outside…
Again, I'll be very grateful if you have front-end tips for this. Thanks.

Top comments (0)