-
Notifications
You must be signed in to change notification settings - Fork 361
DataScript Integration Tutorial
In this short tutorial we'll see how easy it is to integrate custom data sources into Om Next applications.
This tutorial uses Leiningen, Figwheel, and Google Chrome. You should install Leiningen and Google Chrome before proceeding. Leiningen is a standard tool for managing Clojure and ClojureScript library dependencies. Figwheel is a ClojureScript build tool and REPL that enables an expressive live programming model well suited for interactive application development. Figwheel also plays well with text editors that make traditional REPL integration more challenging.
You can of course use any web browser, but this tutorial only includes relevant instructions for Chrome to avoid tangential material.
Create a new project and switch into it:
mkdir om-datascript
cd om-datascript
Inside your project directory create a project.clj
:
touch project.clj
Make it look like the following:
(defproject om-datascript "0.1.0-SNAPSHOT"
:description "My first Om program!"
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.145"]
[org.omcljs/om "1.0.0-alpha14"]
[datascript "0.13.1"]
[figwheel-sidecar "0.4.0" :scope "provided"]])
A Leiningen project.clj
file simply allows you to declare a variety
of properties about your project. In our the case the most important
is the list of :dependencies
.
Now create a file script/figwheel.clj
.
mkdir script
touch script/figwheel.clj
Change script/figwheel.clj
to look like the following:
(require '[figwheel-sidecar.repl :as r]
'[figwheel-sidecar.repl-api :as ra])
(ra/start-figwheel!
{:figwheel-options {}
:build-ids ["dev"]
:all-builds
[{:id "dev"
:figwheel true
:source-paths ["src"]
:compiler {:main 'om-datascript.core
:asset-path "js"
:output-to "resources/public/js/main.js"
:output-dir "resources/public/js"
:verbose true}}]})
(ra/cljs-repl)
This file describes how to build your ClojureScript project and starts a REPL. If you are new to ClojureScript you may find this file a bit overwhelming. If you would like to know more, after this tutorial you may want to work through the ClojureScript Quick Start to re-inforce fundamental ClojureScript concepts encountered in this tutorial.
We now need to provide some basic markup to host our ClojureScript application.
Make a file resources/public/index.html
:
mkdir -p resources/public
touch resources/public/index.html
Change the contents of this file to the following:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Om + DataScript!</title>
</head>
<body>
<div id="app"></div>
<script src="js/main.js"></script>
</body>
</html>
Create a file src/om_datascript/core.cljs
:
mkdir -p src/om_datascript
touch src/om_datascript/core.cljs
Edit its contents to look like the following:
(ns om-datascript.core
(:require [goog.dom :as gdom]
[om.next :as om :refer-macros [defui]]
[om.dom :as dom]))
(enable-console-print!)
(println "Hello world!")
Start Figwheel:
lein run -m clojure.main script/figwheel.clj
For enhanced REPL behavior it's recommended that you install rlwrap. Under OS X this can be easily done with brew.
If you have rlwrap
installed you can then launch with:
rlwrap lein run -m clojure.main script/figwheel.clj
Point your browser at http://localhost:3449. You should see a blank page with the title "Om + DataScript!" visible on your browser tab.
Open the Chrome Developer Tools with the View > Developer >
JavaScript Console menu. In the JavaScript Console you should see
Hello, world!
printed out.
Let's begin!
Make src/om_datascript/core.cljs
look like the following:
(ns om-datascript.core
(:require [goog.dom :as gdom]
[om.next :as om :refer-macros [defui]]
[om.dom :as dom]
[datascript.core :as d]))
(enable-console-print!)
(def conn (d/create-conn {}))
(d/transact! conn
[{:db/id -1
:app/title "Hello, DataScript!"
:app/count 0}])
(defmulti read om/dispatch)
(defmethod read :app/counter
[{:keys [state selector]} _ _]
{:value (d/q '[:find [(pull ?e ?selector) ...]
:in $ ?selector
:where [?e :app/title]]
(d/db state) selector)})
(defmulti mutate om/dispatch)
(defmethod mutate 'app/increment
[{:keys [state]} _ entity]
{:value [:app/counter]
:action (fn [] (d/transact! state
[(update-in entity [:app/count] inc)]))})
(defui Counter
static om/IQuery
(query [this]
[{:app/counter [:db/id :app/title :app/count]}])
Object
(render [this]
(let [{:keys [app/title app/count] :as entity}
(get-in (om/props this) [:app/counter 0])]
(dom/div nil
(dom/h2 nil title)
(dom/span nil (str "Count: " count))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(app/increment ~entity)]))}
"Click me!")))))
(def reconciler
(om/reconciler
{:state conn
:parser (om/parser {:read read :mutate mutate})}))
(om/add-root! reconciler
Counter (gdom/getElement "app"))
By now large portions of the program should look familiar to you. By
putting DataScript reads and mutations behind the parser, the Counter
component is freed from the precise details of the data source.
Instead of an atom the DataScript database is now our state
source. This is what we receive as the :state
key in our env
parameter in our read and mutation functions.
Try clicking the button a few times. Once again you'll see logs dropped into the Chrome JavaScript Console. Grab one of the UUIDs and try the following at the Figwheel REPL (your UUID and DataScript state will not be the same):
(in-ns 'om-datascript.core)
(om/from-history reconciler
#uuid "c362be68-2867-46da-a8e5-5c107398e49d")
;; =>
;; #datascript/DB {:schema {},
;; :datoms [[1 :app/count 6 536870919]
;; [1 :app/title "Hello, DataScript!" 536870913]]}
That's all there is to it! Due to it's support for Datomic Pull Syntax, DataScript is a natural fit for Om Next applications especially if they do not or cannot have significant remote service integration.