dosync Archive Pages Categories Tags

Time Travel

31 December 2013

In my previous post I claimed it was trivial to implement undo in Om. You can see it in action here.

I added the following 13 lines of code:

(def app-history (atom [@app-state]))

(add-watch app-state :history
  (fn [_ _ _ n]
    (when-not (= (last @app-history) n)
      (swap! app-history conj n))
    (set! (.-innerHTML (.getElementById js/document "message"))
      (let [c (count @app-history)]
        (str c " Saved " (pluralize c "State"))))))

(aset js/window "undo"
  (fn [e]
    (when (> (count @app-history) 1)
      (swap! app-history pop)
      (reset! app-state (last @app-history)))))

Again in Om we always have access to the entire app state so we just need to save it on every serious change in app state. Then undo is simply loading a previous snapshot. Because of immutable data structures React can re-render just as quickly going back in time as it does going forward in time. While it may appear that storing the app state like this would consume a lot of memory, it doesn't because ClojureScript data structures work via structural sharing.

Much more powerful undo/redo capability can be easily added with a little more effort. It's worth considering how much work it would take to accomplish the same thing in a traditional JavaScript MVC.

Full source here.

Happy New Years!