Instantly share code, notes, and snippets.
Created
October 9, 2014 22:22
-
Star
(2)
2
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save StephenWakely/aa6527505c24f7e2c83f to your computer and use it in GitHub Desktop.
Select Tags Om component
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 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