Skip to content

Instantly share code, notes, and snippets.

@StephenWakely
Created October 9, 2014 22:22
Show Gist options
  • Save StephenWakely/aa6527505c24f7e2c83f to your computer and use it in GitHub Desktop.
Save StephenWakely/aa6527505c24f7e2c83f to your computer and use it in GitHub Desktop.
Select Tags Om component
(ns acme.selecttags
(:require-macros [cljs.core.async.macros :refer [go alt!]])
(:require [cljs.core.async :refer [put! <! >! chan timeout]]
[om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[acme.shared-controls :as shared]
[cljs-http.client :as http]
[acme.server :as server]
[clojure.string :as str]))
(defn- text->tags
[text]
"Converts our csv of tags to a seq of tags."
(str/split text #","))
(defn- delete-tag
[cursor field tag]
"Delete the given tag from our list."
(fn [e]
(om/transact! cursor field (fn [text]
(let [tags (text->tags text)
filtered (filter #(not= tag %) tags)
joined (apply str (interleave filtered (repeat ",")))]
joined)))))
(defn- build-tags
[cursor field]
(let [tags (field cursor)]
(map (fn [tag] (dom/span #js {:className "tag"}
tag
(dom/button #js {:onClick (delete-tag cursor field tag)} "X")))
(text->tags tags))))
(defn- add-tag
[cursor field owner]
"Adds a newly entered tag to our list."
; update the field
(let [completed (om/get-state owner :text)]
(om/transact! cursor field
(fn [val] (str
val
(if (empty? val) "" ",")
completed))))
; clear out the entry field
(om/set-state! owner :text "")
(om/set-state! owner :autocompletions [])
(om/set-state! owner :selected nil))
(defn- denullify [value]
"Converts a null value to a -1.
Used as an initial index for when we have no item selected."
(or value -1))
(defn- handle-keydown
[owner cursor field]
"React to key presses to control the autocomplete list."
(fn [e]
(let [total (count (om/get-state owner :autocompletions))
keycode (. e -keyCode)]
(case keycode
; enter
13 (add-tag cursor field owner)
; escape
27 (om/set-state! owner :autocompletions [])
; up
38 (om/update-state! owner :selected (fn [s] (let [ss (denullify s)]
(if (<= ss 0) (dec total) (dec ss)))))
;down
40 (om/update-state! owner :selected (fn [s] (let [ss (denullify s)]
(mod (inc ss) total))))
nil))))
(defn- remove-used
[autocompletions used-tags]
"Removes tags from the list that have already been used."
(let [split (text->tags used-tags)]
(filter (fn [tag] (not (some #(= tag %) split))) autocompletions)))
(defn- handle-input
[cursor field owner]
"Handler for the text changing from our input field."
(fn [e]
(let [text (.. e -target -value)]
(do
; update the field
(om/set-state! owner :text text)
; update the autocompletions
(go (let [result (<! (server/fetch-tags text))
all-autocompletions (map :name result)
used-tags (field @cursor)
autocompletions (remove-used all-autocompletions used-tags)]
(om/set-state! owner :autocompletions autocompletions)))))))
(defn- autocomplete-tag
[cursor field owner {:keys [idx selected value]}]
(dom/li #js {:className (if (= idx selected) "selected" "")
:onMouseOver (fn [e] (om/set-state! owner :selected idx))
:onClick (fn [e] (add-tag cursor field owner))}
value))
(defn- build-autocomplete
[cursor field owner autocompletions selected]
(apply dom/ul nil
(map-indexed (fn [i val] (autocomplete-tag cursor field owner {:idx i :selected selected :value val}))
autocompletions)))
(defn select
[cursor owner {:keys [field]}]
(reify
om/IInitState
(init-state [_]
{:text ""
:autocompletions []
:selected nil})
om/IWillUpdate
(will-update [_ _ _]
(when-let [selected (om/get-state owner :selected)]
(let [autocompletions (om/get-state owner :autocompletions)]
(when (< selected (count autocompletions))
(let [selected-text (nth autocompletions selected)]
(om/set-state! owner :text selected-text))))))
om/IRenderState
(render-state [_ state]
(dom/div nil
(apply dom/div nil (build-tags cursor field))
(dom/input #js {:value (:text state)
:onChange (handle-input cursor field owner)
:onKeyDown (handle-keydown owner cursor field)})
(when-not (empty? (:autocompletions state))
(dom/div #js {:className "autocomplete"}
(build-autocomplete cursor field owner (:autocompletions state) (:selected state))))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment