ClojureScript & Om:写作的最佳实践 document.hash

ClojureScript & Om: Best practice for writing document.hash

我在 ClojureScript 中有一个使用 Om 的 "Hello, World!" 应用程序(从 "Chestnut" lein 模板生成)。

目标是将其设置为:

请注意,我打算让 (:route app-state) 向量成为应用程序当前状态的 唯一 真实来源。改变它的一种机制是用户修改 url.

我应该在哪里以及如何将此行为附加到 Om?


这是我的 "Hello, World!" 应用。

(ns demo.core
  (:require [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]
            [clojure.string :as string]))

(defonce app-state (atom {:text "Hello, World!"
                          :route ["some" "app" "route"]}))

(defn update-location-hash [app owner]
  (reify
    om/IRender
    (render [_]
      (set! js/window.location.hash
            (string/join "/" (flatten ["#" (:route app)])))
            (dom/div nil ""))))

(om.core/root
  update-location-hash
  app-state
  {:target (. js/document (getElementById "app"))})


(defn main []
  (om/root
    (fn [app owner]
      (reify
        om/IRender
        (render [_]
          (dom/h1 nil (:text app)))))
    app-state
    {:target (. js/document (getElementById "app"))}))

这会在页面加载时成功写入 document.hash。最终这将是一个使用散列导航进行视图更改的单页应用程序。

这让我觉得很脏,因为必须 return update-location-hash(render ) 函数中的 DOM 元素除了用于满足 render 函数的要求。

好的,我知道我需要什么了。对我来说它看起来很干净并且可以工作。 为了完整起见,我只需要添加一个页面加载类型的侦听器来检查散列和一个将状态更改呈现到散列的渲染器。这应该非常简单(基于我之前的代码)。

(ns demo.core
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [goog.events :as events]
            [goog.events.EventType :as EventType]
            [cljs.core.async :as async :refer [>! <! put! chan]]
            [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]
            [clojure.string :as string]))

;; Not sure what this does.
(enable-console-print!)

(defn listen
  "An event listener factory.  Given an element and an event type, return a
  channel that can be polled for event outputs."
  [el type]
  (let [out (chan)] (events/listen el type #(put! out %)) out))

(defonce app-state
  (atom {:text "Hello, World!"
         :mouse [0 0]
         :route ["some" "app" "route"]}))

(defn url-to-route
  "Given a url, parse the hash value as an app route.  In general, the
  resultant route has these properties:

  * route components are split on a solidus
  * empty components are ignored (nill or all-whitespace)

  A route like this:

    ['foo' 'bar' 'baz']

  will be produced by all of the following urls (and others):

    http://my-app.com/#/foo/bar/baz
    http://my-app.com/#foo/bar/baz
    http://my-app.com/#/foo// /bar//baz
    http://my-app.com/#/ / / /foo/bar////baz"
  [url]
  ;; Split the url at a hash followed by zero or more slashes and
  ;; whitespace.  Then take anything after the hash and split it on one or
  ;; more slashes (ignoring whitespace).
  (string/split (second (string/split url #"#[/\s]{0,}" 2)) #"[/\s]+"))

(defn layout
  "The central application layout component.  This registers global event
  listeners and renders the application's root DOM nodes."
  [app owner]
  (reify
    om/IWillMount
    (will-mount [_]
      ;; Handle various events.  When an event is triggered, format the
      ;; response.
      (let [;; Listen for changes to the mouse position
            mouse-chan (async/map
                         (fn [event] [(.-clientX event) (.-clientY event)])
                         [(listen js/window EventType/MOUSEMOVE)])

            ;; Listen for changes to the URL's hash
            hash-chan (async/map
                        (fn [event] (url-to-route (-> event .-event_ .-newURL)))
                        [(listen js/window EventType/HASHCHANGE)])]
        ;; Watch the stream and update the application state whenever
        ;; anything changes.
        (do
          (go (while true (om/update! app :route (<! hash-chan))))
          (go (while true (om/update! app :mouse (<! mouse-chan)))))))

    om/IRender
    (render [_]
      (dom/div
        nil
        (dom/div nil (when-let [route (:route app)] (pr-str (:route app))))
        (dom/div nil (when-let [pos (:mouse app)] (pr-str (:mouse app))))
        (dom/h1 nil (:text app))))))

(defn main []
  (om/root
    layout
    app-state
    {:target (. js/document (getElementById "app"))}))