Skip to content

Instantly share code, notes, and snippets.

@fogus
Forked from minikomi/tumblrsearch.cljs
Created August 26, 2014 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fogus/0e38da839dc5ee15d074 to your computer and use it in GitHub Desktop.
Save fogus/0e38da839dc5ee15d074 to your computer and use it in GitHub Desktop.
(ns tumblrsearch.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [clojure.browser.repl]
[cljs.core.async :as async :refer [put! chan <!]]
[om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[figwheel.client :as fw :include-macros true]
)
(:import [goog.net Jsonp]
[goog Uri]
))
(enable-console-print!)
;; state ------------------------------------------
(defonce initial-state
(atom {:current-state :search
:current-search ""
:current-items []
}))
(defn reset-state [state]
(assoc state
:current-state :search
:current-search ""
:current-items []))
;; Search ------------------------------------------------
;;
(def ENTER 13)
(def ESC 27)
(defn key-event->keycode [e] (.-keyCode e))
(def legal-key #{ENTER ESC})
(defn handle-change [e owner {:keys [text]}]
(om/set-state! owner :text (.. e -target -value)))
(defn gen-request [tag]
(str "http://api.tumblr.com/v2/tagged?tag="
tag
"&api_key=?"
))
(defn async-search [data ajax-chan]
(let [search-term (:current-search data)
uri (gen-request search-term)
req (Jsonp. (Uri. uri))
]
(.send req nil
#(if (= (.. % -meta -status) 200)
; 200 response - ok
(put! ajax-chan {:error false
:search-term search-term
:items (js->clj (.. % -response) :keywordize-keys true)})
; otherwise error
(put! ajax-chan {:error true})))))
(defn search [data state owner]
(when (not (empty? (:text state)))
(om/transact!
data #(assoc %
:current-state :loading
:current-search (:text state)
:current-items []
))
(om/set-state! owner :text "")
(go (async-search @data (:ajax-chan state)))))
(defn render-search [data owner]
(reify
om/IInitState
(init-state [_]
{:text ""})
om/IRenderState
(render-state [this state]
(dom/div nil
(dom/h1 nil "Tumblr Search")
; input
(dom/input
#js {:type "text"
:value (:text state)
:onChange #(handle-change % owner state)
:onKeyDown (fn [e]
(let [k (key-event->keycode e)]
(when (legal-key k)
(condp = k
ENTER (search data state owner)
ESC (om/set-state! owner :text "")
))))})
; search button
(dom/button
#js {:onClick (fn [e]
(.preventDefault e)
(search data state owner))}
"Search")
; clear button
(dom/button
#js {:onClick (fn [e]
(.preventDefault e)
(om/set-state! owner :text "")
(om/transact! data reset-state)
)}
"Clear")))
))
;; Loading ------------------------------------------------
(defn render-loading [data owner]
(om/component
(dom/div nil
(dom/h2 nil "loading")
(dom/button
#js {:onClick (fn [e]
(.preventDefault e)
(om/transact! data reset-state ))}
"Cancel"))))
;; loaded ------------------------------------------------
(defn photo-view [item owner]
(reify
om/IRender
(render [_]
(dom/p nil
(dom/h2 nil (:slug item))
(dom/a #js {:href (:post_url item) :target "_blank"}
(dom/img #js {:src (-> item :photos first :alt_sizes second :url)}))
))))
(defn render-loaded [data owner]
(om/component
(dom/div nil
(dom/h1 nil (str "Current search: "
(:current-search data)))
(dom/button
#js {:onClick (fn [e]
(.preventDefault e)
(om/transact! data reset-state))}
"Reset")
(apply dom/div nil
(om/build-all photo-view
(filter #(= (:type %) "photo") (:current-items data))))
)))
;; error ------------------------------------------------
(defn render-error [data owner]
(om/component
(dom/div nil
(dom/h1 nil "error")
(dom/button
#js {:onClick (fn [e]
(.preventDefault e)
(om/transact! data reset-state ))}
"Cancel")
)))
;; App -----------------------------------------------
(defn async-response-loop [data ajax-chan]
(go
(while true
(let [response (<! ajax-chan)]
(when (= (:current-state @data) :loading)
(cond
; loading and error
(:error response)
(om/transact! data
#(assoc %
:current-state :error
:current-search ""
:current-items []))
; loading, not error, search term match
(= (:current-search @data) (:search-term response))
(om/transact! data
#(assoc %
:current-state :loaded
:current-items (:items response))
)))))))
(om/root
(fn [data owner]
(reify
om/IWillMount
(will-mount [_]
(let [ajax-chan (chan 1)]
(async-response-loop data ajax-chan)
(om/set-state! owner :ajax-chan ajax-chan)
))
om/IRenderState
(render-state [this local-state]
(dom/div nil
(case (:current-state data)
:search (om/build render-search data
{:state {:ajax-chan (om/get-state owner :ajax-chan)}})
:loading (om/build render-loading data)
:loaded (om/build render-loaded data)
(om/build render-error data)
))
)))
initial-state
{:target (. js/document (getElementById "app"))})
;; figwheel -----------------------------------------------
(fw/watch-and-reload
:websocket-url "ws://localhost:3449/figwheel-ws"
:jsload-callback (fn [] (print "reloaded")))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment