Skip to content

Instantly share code, notes, and snippets.

@alexanderkiel
Last active August 29, 2015 14:00
Show Gist options
  • Save alexanderkiel/d17e08316796dbfafbc3 to your computer and use it in GitHub Desktop.
Save alexanderkiel/d17e08316796dbfafbc3 to your computer and use it in GitHub Desktop.
An example of a non-trivial but reusable Om component. You need Twitter Bootstrap 3 and Font Awesome 4 CSS.
(ns typeahead-search-field
(:require-macros [cljs.core.async.macros :refer [go-loop]])
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[cljs.core.async :as async :refer [put! chan <! >! alts!]]))
(defn typeahead-search-field
"Renders a standalone typeahead search field.
The search field collects the chars typed and executes a search after
:timeout. It than waits for the search result being available and puts it
onto :result-ch if no other chars were typed in the meantime. This behaviour
ensures that the query always matches the result.
The query string typed into the field is associated to the state under
:query. Blanking the search field is possible by removing the query key from
state.
The following options are available:
:search - a function which takes two arguments, the supplied state and
the query string and returns a channel conveying the result.
The function has to return the channel immediately.
:result-ch - a channel onto which all valid results are put. Valid results
are described above.
:enabled? - a function which takes the supplied state and returns true if
the search field should be enabled. Optional. Defaults to
true.
:timeout - the time after which a search is executed. Optional. Defaults
to 350 ms.
:placeholder - the placeholder of the input field. Optional. Defaults to
\"Search\"."
[state owner {:keys [search result-ch enabled? timeout placeholder]
:or {enabled? (constantly true)
timeout 350
placeholder "Search"}}]
(reify
om/IInitState
(init-state [_]
{:query-ch (chan)
:clear-ch (chan)})
om/IWillMount
(will-mount [_]
(let [query-ch (om/get-state owner :query-ch)]
(go-loop [query nil
tmp-result-ch nil]
(let [timeout-ch (when (and query search) (async/timeout timeout))
[v ch] (alts! (remove nil? [query-ch timeout-ch tmp-result-ch])
:priority true)]
(condp = ch
query-ch
(when v
(om/update! state :query v)
(recur v nil))
timeout-ch
(recur nil (search @state query))
tmp-result-ch
(do
(when result-ch (>! result-ch v))
(recur query nil)))))))
om/IDidMount
(did-mount [_]
(let [clear-ch (om/get-state owner :clear-ch)
query-ch (om/get-state owner :query-ch)
node (om/get-node owner "input")]
(go-loop []
(when (<! clear-ch)
(>! query-ch "")
(.focus node)
(recur)))))
om/IWillUnmount
(will-unmount [_]
(async/close! (om/get-state owner :query-ch))
(async/close! (om/get-state owner :clear-ch)))
om/IRenderState
(render-state [_ {:keys [query-ch clear-ch]}]
(dom/form #js {:className "form" :role "form"}
(dom/div #js {:className "form-group"}
(dom/div #js {:className "input-group"}
(dom/input #js {:type "text"
:ref "input"
:className "form-control"
:placeholder placeholder
:value (or (:query state) "")
:disabled (not (enabled? state))
:onChange #(when-let [query (.. % -target -value)]
(put! query-ch query))})
(dom/span #js {:className "input-group-btn"}
(dom/button #js {:className "btn btn-default"
:type "button"
:onClick #(put! clear-ch :clear)}
(dom/span #js {:className "fa fa-times"})))))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment