Last active
August 29, 2015 14:00
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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