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

Post on 22-May-2020

31 views 0 download

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

Building a web app in Clojure(Script)

Using the Chestnut leiningen template

part 1

*

*

New project…

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

New project…

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

New leiningen project

New project…

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

New leiningen project using the chestnut template

New project…

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

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

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:

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

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

Directory Structure

$ ls

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

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

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

Directory Structure

$ ls

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

Static resources go here

HTML, CSS, javascript, images, fonts, …

Directory Structure

$ ls

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

All of your code goes into src

Getting Started

$ lein repl

… lots of output …

clojure-ireland.server=> (run)

… more output …

Now open localhost:10555

clojure-ireland.server=>

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

ClojureScript source files

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Project namespace

Making Changes

$ vim src/cljs/clojure_ireland/core.cljs

Main source file

(application entry point)

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]]))

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

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

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>

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

Making Changes

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

Making Changes

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

This is our applications state

Making Changes

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

defonce means it will NOT get reloaded as you edit code

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

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

app-state

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

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

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

app-state

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

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

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

app-state

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

Making Changes

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

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

app-state

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

A New Component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

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”))}))

A New Component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

Hit save!

And like magic…

React Dev Tools

React Dev Tools

Component Tree

React Dev Tools

Our component

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

React Dev Tools

The div

(defcomponent container

[data]

(render [_]

(dom/div

“Test”)))

React Dev Tools

Componentproperties andstate

React Dev Tools

Components viewof app-state

An om-tools shortcut

(defn main []

(om/root

(fn [data owner]

(reify

om/IRender

(render [_]

(->container data))))

app-state

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

The CSS

.box {

border: 1px solid;

margin: 10px;

padding: 5px;

}

.box.inline div {

display: inline;

padding-right: 10px;

}

(defcomponent container

[data]

(render [_]

(dom/div

(dom/div

{:class “box”}

“A”)

(dom/div

{:class “box”}

“B”))))

(defcomponent container

[data]

(render [_]

(dom/div

(dom/div

{:class “box”}

“A”)

(dom/div

{:class “box”}

“B”))))

New app-state

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

:value “a”}

:b {:label “B”

:value “b”}}))

Press refresh!

A new component

(defcomponent labelled-data

[data]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

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

Updated container

(defcomponent container

[data]

(render [_]

(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data)))))

labelled-data component

(defcomponent labelled-data

[data owner]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

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

labelled-data render

(defcomponent labelled-data

[data owner]

(render [_]

(dom/div

{:class “box”}

(dom/div

(:label data))

(dom/input

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

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”))}))

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”))}))

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”))}))

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”))}))

One last change to app-state

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

:value 0}

:b {:label “Design”

:value 0}}))

Press refresh!

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”)))}))

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”)))}))

Helper function(defn percent

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

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

labelled-value component(defcomponent labelled-value

[data]

(render-state [_ state]

(dom/div

{:class “box inline”}

(dom/div

(:label state))

(dom/div

data))))

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

“Test”

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

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

“Test”

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

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

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

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

container render(dom/div

(->labelled-input

(:a data))

(->labelled-input

(:b data))

(->labelled-value

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

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

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”}}))

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”}}))

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 [_]

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”)))}))

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”)))}))

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))))

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”}}))

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”}}))

Building a web app in Clojure(Script)

Using the Chestnut leiningen template

End of Part One