Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in...

72
Building a web app in Clojure(Script ) Using the Chestnut leiningen template part 1 * *

Transcript of Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in...

Page 1: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Building a web app in Clojure(Script)

Using the Chestnut leiningen template

part 1

*

*

Page 2: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

Page 3: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project

Page 4: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project using the chestnut template

Page 5: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project using the chestnut template, called clojure-ireland

Page 6: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project using the chestnut template, called clojure-ireland, with the following chestnut options:

Page 7: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project using the chestnut template, called clojure-ireland, with the following chestnut options:

• om-tools Makes using Om a bit easier

Page 8: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New project…

lein new chestnut clojure-ireland -- --om-tools --site-middleware

New leiningen project using the chestnut template, called clojure-ireland, with the following chestnut options:

• om-tools Makes using Om a bit easier

• site-middleware Adds session support and more

Page 9: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Directory Structure

$ ls

env LICENSE Procfile project.clj README.md resources src sytem.properties

Page 10: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Directory Structure

$ ls

env LICENSE Procfile project.clj README.md resources src sytem.properties

Build-specific stuff goes in env

Eg, code you only want in developmentor code you only want in production

Page 11: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Directory Structure

$ ls

env LICENSE Procfile project.clj README.md resources src sytem.properties

Project settings go in project.clj

Eg, dependencies, build profiles,leiningen plugins, etc

Page 12: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Directory Structure

$ ls

env LICENSE Procfile project.clj README.md resources src sytem.properties

Static resources go here

HTML, CSS, javascript, images, fonts, …

Page 13: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Directory Structure

$ ls

env LICENSE Procfile project.clj README.md resources src sytem.properties

All of your code goes into src

Page 14: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Getting Started

$ lein repl

… lots of output …

clojure-ireland.server=> (run)

… more output …

Now open localhost:10555

clojure-ireland.server=>

Page 15: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Page 16: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

ClojureScript source files

Page 17: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Project namespace

Page 18: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Main source file

(application entry point)

Page 19: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(ns clojure-ireland.core

(:require [om.core :as om :include-macros true]

[om-tools.dom :as dom :include-macros true]

[om-tools.core :refer-macros [defcomponent]]))

Page 20: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(ns clojure-ireland.core

(:require [om.core :as om :include-macros true]

[om-tools.dom :as dom :include-macros true]

[om-tools.core :refer-macros [defcomponent]]))

Project namespace declaration

Page 21: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(ns clojure-ireland.core

(:require [om.core :as om :include-macros true]

[om-tools.dom :as dom :include-macros true]

[om-tools.core :refer-macros [defcomponent]]))

Om

Page 22: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(ns clojure-ireland.core

(:require [om.core :as om :include-macros true]

[om-tools.dom :as dom :include-macros true]

[om-tools.core :refer-macros [defcomponent]]))

dom functions, used to generate HTMLEg (dom/div {:class “foo”} “My new div!”)

=

<div class=“foo”>My new div!</div>

Page 23: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(ns clojure-ireland.core

(:require [om.core :as om :include-macros true]

[om-tools.dom :as dom :include-macros true]

[om-tools.core :refer-macros [defcomponent]]))

defcomponent macro = easier Om components

Page 24: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defonce app-state (atom {:text “Hello Chestnut!”}))

Page 25: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defonce app-state (atom {:text “Hello Chestnut!”}))

This is our applications state

Page 26: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defonce app-state (atom {:text “Hello Chestnut!”}))

defonce means it will NOT get reloaded as you edit code

Page 27: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(dom/h1 (:text data)))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 28: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(dom/h1 (:text data)))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 29: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(dom/h1 (:text data)))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 30: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(dom/h1 (:text data)))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 31: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

A New Component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

Page 32: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

A New Component

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(om/build container data))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 33: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

A New Component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

Hit save!

And like magic…

Page 34: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

Page 35: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

Component Tree

Page 36: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

Our component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

Page 37: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

The div

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

Page 38: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

Componentproperties andstate

Page 39: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

React Dev Tools

Components viewof app-state

Page 40: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

An om-tools shortcut

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(->container data))))

app-state

{:target (. js/document (getElementById “app”))}))

Page 41: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

The CSS

.box {

border: 1px solid;

margin: 10px;

padding: 5px;

}

.box.inline div {

display: inline;

padding-right: 10px;

}

Page 42: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

(defcomponent container

[data]

(render [_]

(dom/div

(dom/div

{:class “box”}

“A”)

(dom/div

{:class “box”}

“B”))))

Page 43: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

(defcomponent container

[data]

(render [_]

(dom/div

(dom/div

{:class “box”}

“A”)

(dom/div

{:class “box”}

“B”))))

Page 44: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

New app-state

(defonce app-state (atom {:a {:label “A”

:value “a”}

:b {:label “B”

:value “b”}}))

Press refresh!

Page 45: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

A new component

(defcomponent labelled-data

[data]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)}))))

Page 46: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Updated container

(defcomponent container

[data]

(render [_]

(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data)))))

Page 47: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data component

(defcomponent labelled-data

[data owner]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)}))))

Page 48: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render

(defcomponent labelled-data

[data owner]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)}))))

Page 49: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(.-value (om/get-node owner “val”))}))

Page 50: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(.-value (om/get-node owner “val”))}))

Page 51: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(str “*” (:label data) “*”))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(.-value (om/get-node owner “val”))}))

Page 52: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(str “*” (:label data) “*”))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(.-value (om/get-node owner “val”))}))

Page 53: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

One last change to app-state

(defonce app-state (atom {:a {:label “Planning”

:value 0}

:b {:label “Design”

:value 0}}))

Press refresh!

Page 54: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(js/parseInt

(.-value (om/get-node owner “val”)))}))

Page 55: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:ref “val”

:onChange #(om/update!

data :value

(js/parseInt

(.-value (om/get-node owner “val”)))}))

Page 56: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Helper function(defn percent

[{v1 :value} {v2 :value}]

(str (* (/ v1 (+ v1 v2)) 100) “%”))

Page 57: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-value component(defcomponent labelled-value

[data]

(render-state [_ state]

(dom/div

{:class “box inline”}

(dom/div

(:label state))

(dom/div

data))))

Page 58: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

“Test”

{:state {:label “Planning”}}))

Page 59: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

“Test”

{:state {:label “Planning”}}))

Page 60: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”}}))

Page 61: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”}}))

Page 62: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”}})

(->labelled-value

(percent (:b data) (:a data))

{:state {:label “Design”}}))

Page 63: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”}})

(->labelled-value

(percent (:b data) (:a data))

{:state {:label “Design”}}))

Page 64: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container component(defcomponent container

[data]

(did-update [_ pp ps]

(if (> (get-in data [:a :value])

(get-in data [:b :value]))

(om/update! data [:a :warn] true)

(om/update! data [:a :warn] false)))

(render [_]

Page 65: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:style {:color (if (:warn data) “red” “black”)}

:ref “val”

:onChange #(om/update!

data :value

(js/parseInt

(.-value (om/get-node owner “val”)))}))

Page 66: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-data render(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

{:value (:value data)

:style {:color (if (:warn data) “red” “black”)}

:ref “val”

:onChange #(om/update!

data :value

(js/parseInt

(.-value (om/get-node owner “val”)))}))

Page 67: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

labelled-value component(defcomponent labelled-value

[data]

(render-state [_ state]

(dom/div

{:class “box inline”

:style {:color (if (:warn state) “red” “black”)}}

(dom/div

(:label state))

(dom/div

data))))

Page 68: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”

:warn (get-in data [:a :warn])}})

(->labelled-value

(percent (:b data) (:a data))

{:state {:label “Design”}}))

Page 69: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

(percent (:a data) (:b data))

{:state {:label “Planning”

:warn (get-in data [:a :warn])}})

(->labelled-value

(percent (:b data) (:a data))

{:state {:label “Design”}}))

Page 70: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template
Page 71: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template
Page 72: Building a web app in Clojure(Script) - Meetupfiles.meetup.com/10978482/Building a web app in Clojure...Building a web app in Clojure(Script) Using the Chestnut leiningen template

Building a web app in Clojure(Script)

Using the Chestnut leiningen template

End of Part One