├── .gitignore
├── dev.cljs.edn
├── src
└── hello
│ ├── t09_modules.cljs
│ ├── core.cljs
│ ├── timer.cljs
│ ├── t08_js_interop.cljs
│ ├── t04_control_flow.cljs
│ ├── t02_macros.cljs
│ ├── t05_data_structures.cljs
│ ├── t03_data_types.cljs
│ ├── t07_state.cljs
│ ├── t06_transforming_collections.cljs
│ ├── t10_ui.cljs
│ └── t01_syntax.cljs
├── project.clj
├── resources
└── public
│ ├── index.html
│ └── css
│ └── main.css
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .nrepl-port
2 | .cpcache
3 | .idea
4 | *.iml
5 | target
6 | .nightlight.edn
7 | resources/public/js
8 |
--------------------------------------------------------------------------------
/dev.cljs.edn:
--------------------------------------------------------------------------------
1 | ^{:ring-server-options {:port 3000}
2 | :open-url false}
3 | {:main hello.core
4 | :output-to "resources/public/js/bundle.js"
5 | :output-dir "resources/public/js"
6 | :asset-path "/js"}
7 |
--------------------------------------------------------------------------------
/src/hello/t09_modules.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t09-modules)
2 |
3 | ;; A namespace is a unit of modularity in Clojure
4 |
5 | ;; [namespace :as alias :refer [...vars]]
6 |
7 | (comment
8 | (require '[clojure.string :as cstr]))
9 | ;; import * as cstr from "clojure/string"
10 |
11 | (comment
12 | (require '[clojure.string :refer [join]]))
13 | ;; import { join } from "clojure/string"
14 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject rc18.workshop "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.10.0-beta4"]
3 | [org.clojure/clojurescript "1.10.520"]
4 | [reagent "0.8.1"]
5 | [com.bhauman/figwheel-main "0.2.1-SNAPSHOT"]
6 | [nightlight "RELEASE"]]
7 |
8 | :source-paths ["src" "resources"]
9 |
10 | :aliases {"server" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
11 | "ide" ["run" "-m" "nightlight.core" "--url" "http://localhost:3000"]})
12 |
--------------------------------------------------------------------------------
/src/hello/core.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.core
2 | (:require [nightlight.repl-server]
3 | [hello.t01-syntax]
4 | [hello.t02-macros]
5 | [hello.t03-data-types]
6 | [hello.t04-control-flow]
7 | [hello.t05-data-structures]
8 | [hello.t06-transforming-collections]
9 | [hello.t07-state]
10 | [hello.t08-js-interop]
11 | [hello.t09-modules]
12 | [hello.t10-ui]
13 | [hello.timer]))
14 |
15 | ;; Entry point namespace
16 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ReactiveConf '18
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/hello/timer.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.timer
2 | (:require [reagent.core :as r]
3 | [goog.dom :as gdom]))
4 |
5 | ;; Build Timer app that displays current time every second
6 | ;; and allows changing the color of the text via input field
7 |
8 | ;; Hints:
9 | ;; - use `defonce` form to define a var that preserves it's value between hot-reloads
10 | ;; - use `.toLocaleTimeString` method on `js/Date` object to format tim string
11 |
12 | (defn app []
13 | [:div
14 | "Render timer app here..."])
15 |
16 | (r/render [app] (gdom/getElement "app-root"))
17 |
--------------------------------------------------------------------------------
/src/hello/t08_js_interop.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t08-js-interop)
2 |
3 | ;; ClojureScript provides a simple access to host platform
4 | ;; (Browser, Node.js, etc.) via `js` namespace
5 |
6 | ;; `js` namespace maps to `window` object when running in a browser
7 |
8 | ;; ### Calling functions
9 | ;;
10 |
11 | (js/setTimeout #(js/console.log "Hello!") 1000)
12 | ;; setTimeout(() => alert("Hello!"), 1000)
13 |
14 |
15 | ;; ### Calling methods
16 | ;;
17 |
18 | (.setItem js/localStorage "key" "value")
19 | ;; localStorage.setItem("key", "value")
20 |
21 | (js/localStorage.getItem "key")
22 | ;; localStorage.getItem("key")
23 |
24 |
25 | ;; ### JS Object and Array
26 | ;;
27 |
28 | #js {:a 1 :b 2}
29 |
30 | #js [1 2 3]
31 |
32 |
33 | ;; ### get/set JS object properties
34 | ;;
35 |
36 | (def obj #js {})
37 |
38 | (set! obj.key #js {:value "my-value"})
39 | (.-key obj)
40 |
41 | ;; or nested properties
42 |
43 | (.. obj -key -value)
44 | (set! (.. obj -key -value) "new-value")
45 |
46 |
47 | ;; ### Converting between Clojure's and JavaScript's data
48 | ;;
49 |
50 | (clj->js {:a 1 :b [2]})
51 |
52 | (js->clj #js {:a 1 :b #js [2]})
53 |
--------------------------------------------------------------------------------
/src/hello/t04_control_flow.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t04-control-flow)
2 |
3 | ;; ## Falsy & Truthy values
4 | ;;
5 |
6 | ;; In Clojure `false` and `nil` are the only falsy values,
7 | ;; everything else evaluates to logical `true`.
8 |
9 | (some? nil)
10 | (some? 0)
11 | (some? "")
12 |
13 |
14 | ;; ## Comparison operators
15 | ;;
16 |
17 | (= 1 1)
18 | (not= 1 2)
19 |
20 | (> 3 2 1)
21 |
22 | ;; ...
23 |
24 |
25 | ;; ## Conditionals
26 | ;;
27 |
28 |
29 | (if (zero? 1)
30 | "zero!"
31 | "not zero")
32 | ;; if (1 === 0) {
33 | ;; return "zero!";
34 | ;; } else {
35 | ;; return "not zero";
36 | ;; }
37 |
38 | (when (empty? "string")
39 | "string is empty")
40 | ;; if (isEmpty("string")) {
41 | ;; return "string is empty";
42 | ;; }
43 |
44 | (case 2
45 | 1 "uno"
46 | 2 "dos"
47 | "else")
48 | ;; switch (2) {
49 | ;; case 1:
50 | ;; return "uno";
51 | ;; case 2:
52 | ;; return "dos";
53 | ;; default:
54 | ;; return "else";
55 | ;; }
56 |
57 | (cond
58 | (zero? 1) "zero!"
59 | (pos? 2) "positive!"
60 | :else "else")
61 | ;; if (1 === 0) {
62 | ;; return "zero!";
63 | ;; } else if (2 > 0) {
64 | ;; return "positive!";
65 | ;; } else {
66 | ;; return "else";
67 | ;; }
68 |
--------------------------------------------------------------------------------
/src/hello/t02_macros.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t02-macros)
2 |
3 | ;; ## Fighting parens
4 | ;;
5 |
6 | ;; Sometimes it can be hard to read and understand code because of many nested expressions.
7 | ;; Clojure provides a set of macros, called threading macros, to solve this problem.
8 | ;; ->> or threading macro is also known as pipeline operator in other languages.
9 |
10 | ;; Macro ->> is called thread-last, because it inserts the first expression
11 | ;; as an argument in the next expression in the very last position.
12 |
13 | ;; Let's see how it transforms the code step by step.
14 | ;; This is how you write it down:
15 |
16 | (->> (/ 9 3) (* 10 2) (+ 3))
17 |
18 | ;; The first expression goes into the last position in arguments list of the second expression
19 | (->> (* 10 2 (/ 9 3)) (+ 3))
20 |
21 | ;; Repeat the same
22 | (->> (+ 3 (* 10 2 (/ 9 3))))
23 |
24 | ;; And here's the final expression in the form that we saw it first
25 | (+ 3 (* 10 2 (/ 9 3)))
26 |
27 | ;; We can verify expansion
28 | (macroexpand '(->> (/ 9 3) (* 10 2) (+ 3)))
29 |
30 | ;; Threading macros become useful when building pipelines of transformations.
31 | ;; For example ->> macro suits for building collection transformations pipeline.
32 | (->> (range 10)
33 | (filter odd?)
34 | (map inc)
35 | (reduce * 1))
36 |
37 | ;; or -> (thread-first) to pipe a value through multiple transformations
38 | (-> {:fname "John" :age 31}
39 | (assoc :lname "Doe")
40 | (update :age inc))
41 |
--------------------------------------------------------------------------------
/src/hello/t05_data_structures.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t05-data-structures)
2 |
3 | ;; Clojure provides a rich set of immutable and persistent data structures,
4 | ;; which support efficient creation of “modified” versions, by utilizing structural sharing.
5 |
6 | ;; Collections are represented by abstractions, and there may be one or more concrete implementations.
7 |
8 |
9 | ;; In particular, since “modification” operations yield new collections,
10 | ;; the new collection might not have the same concrete type as the source collection,
11 | ;; but will have the same logical (interface) type.
12 |
13 | ;; ## List
14 | ;;
15 |
16 | ;; List is a classic singly linked list data structure. Lisp code itself is made out of lists.
17 | ;; Lists support fast append to the beginning and retrieval of the head (first) element.
18 |
19 | '(1 2 3)
20 |
21 | (list? '(1 2 3))
22 |
23 | (first '(1 2 3))
24 | (conj '(1 2) 3)
25 |
26 |
27 | ;; ## Vector
28 | ;;
29 |
30 | ;; Vector is an indexed collection (like JavaScript's Array).
31 | ;; Vectors support fast lookup by index and fast append to the end of collection.
32 |
33 | [1 2 3]
34 |
35 | (vector? [1 2 3])
36 |
37 | (nth [1 2 3] 1)
38 | (conj [1 2] 3)
39 |
40 |
41 | ;; ## Map
42 | ;;
43 |
44 | ;; A Map is a collection that maps keys to values.
45 | {:a 1 :b 2 :c 3}
46 |
47 | (map? {:a 1})
48 |
49 | (assoc {:a 1} :b 2)
50 | (assoc-in {:a 1} [:b :c] 2)
51 |
52 | ;; Keys can be of any type
53 | {1 :number
54 | "1" :string
55 | :a :keyword
56 | [1] :vector
57 | {:a 1} :map}
58 |
59 | ;; ## Set
60 | ;;
61 |
62 | ;; Sets are collections of unique values.
63 |
64 | #{1 2 3 4}
65 | (set? #{1 2 3})
66 |
--------------------------------------------------------------------------------
/src/hello/t03_data_types.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t03-data-types)
2 |
3 | ;; ## Primitive data types
4 | ;;
5 |
6 | ;; Clojure is relying on primitive data types provided by host platform
7 | ;; - Java's in JVM Clojure
8 | ;; - JavaScript's in ClojureScript
9 |
10 |
11 | ;; Boolean
12 | ;;
13 |
14 | true
15 | false
16 |
17 |
18 | ;; Nil
19 | ;;
20 | ;; nil means nothing :)
21 |
22 | nil
23 |
24 |
25 | ;; Number
26 | ;;
27 |
28 | 1
29 | 3.5
30 |
31 |
32 | ;; String
33 | ;;
34 |
35 | "Hello!"
36 |
37 | "multiline
38 | string"
39 |
40 |
41 | ;; Keyword
42 | ;;
43 |
44 | ;; Keywords are symbolic identifiers that evaluate to themselves.
45 | ;; Keywords are often used as keys in maps.
46 | ;; Because keywords implement IFn protocol, they can be used as getters on maps.
47 |
48 | (:user-name {:user-name "rich hickey"})
49 |
50 |
51 | ;; Symbol
52 | ;;
53 |
54 | ;; Symbols are identifiers that are normally used to refer to something else.
55 | ;; They can be used in function parameters,
56 | ;; let bindings, class names and global vars.
57 |
58 | 'this-is-a-symbol
59 |
60 | ;; You may wonder why symbol above is prefixed (quoted) with ' character.
61 | ;; The reason is that because symbols are used to refer to values in the code,
62 | ;; normally evaluating a symbol would trigger runtime to find the value,
63 | ;; which is being referenced by the symbol.
64 |
65 | ;; In case when we want a symbol itself, as a value, we have to quote it,
66 | ;; so the compiler knows that this symbol should be treated as data,
67 | ;; rather than a reference to a value in memory.
68 |
69 | (def user-name "clojure")
70 |
71 | (string? user-name)
72 |
73 | (symbol? 'user-name)
74 |
--------------------------------------------------------------------------------
/src/hello/t07_state.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t07-state)
2 |
3 | ;; Clojure separates concepts of state and identity.
4 | ;; A single identity can have different states over time,
5 | ;; but states themselves doesn't change, because they are immutable values.
6 |
7 | ;; Let's describe a user as a hash map.
8 |
9 | (def user
10 | {:first-name "John"
11 | :last-name "Doe"
12 | :age 31})
13 |
14 | ;; Now if we want to update user's age, we have to perform the actual update
15 | ;; and get a new value back
16 |
17 | (update user :age inc)
18 |
19 | ;; Since values are immutable, the result of an update should be stored somewhere
20 | ;; in order to keep track of it
21 |
22 | ;; In other words we want an identity that is represented by
23 | ;; one of those values at a single point in time
24 |
25 | ;; Atom is a special reference type (identity) that holds immutable value (state)
26 |
27 | (def user' (atom {:first-name "John"
28 | :last-name "Doe"
29 | :age 31}))
30 |
31 | ;; We can read current state of the atom by dereferencing it (taking a value by reference)
32 | @user'
33 |
34 | ;; Updating state of the atom is done in a functional manner
35 | (swap! user' #(update % :age inc))
36 |
37 | ;; Or setting a new value directly
38 | (reset! user' {:first-name "John"
39 | :last-name "Doe"
40 | :age 32})
41 |
42 | ;; Now a new value is stored in the atom
43 | @user'
44 |
45 |
46 | ;; ### Watching state changes over time
47 | ;;
48 |
49 | ;; Because values are immutable, we can observe different states of an identity over time.
50 |
51 | (add-watch user' :logger (fn [key ref old-state new-state]
52 | (js/alert (str new-state))))
53 |
54 | ;; Try to update `user` again and see what happens
55 |
--------------------------------------------------------------------------------
/src/hello/t06_transforming_collections.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t06-transforming-collections)
2 |
3 | ;; Clojure provides a rich set of functions to manipulate collections.
4 |
5 | ;; map/filter/reduce
6 |
7 | (map inc [1 2 3]) ;; [1, 2, 3].map(n => n + 1)
8 | (filter odd? [1 2 3]) ;; [1, 2, 3].filter(n => n % 2)
9 | (reduce + 0 [1 2 3]) ;; [1, 2, 3].reduce((sum, n) => n + sum, 0)
10 |
11 |
12 | ;; ### Creating collections
13 | ;;
14 |
15 | ;; These functions can create infinite sequences of values.
16 | ;; Normally an infinite sequence won't evaluate immediately,
17 | ;; because collections in Clojure are lazy.
18 |
19 | (range 5 10)
20 | (repeat 10 1)
21 |
22 |
23 | ;; ### Transforming collection types
24 | ;;
25 |
26 | (into [] (list 1 2 3)) ;; [1 2 3]
27 |
28 | (into {} [[:a 1] [:b 2]]) ;; {:a 1, :b 2}
29 |
30 | (into #{} [1 2 3 4 4]) ;; #{1 2 3 4}
31 |
32 |
33 | ;; ### Exercise
34 | ;;
35 |
36 | ;; Given a list of bank accounts
37 | ;; where an account has a log of transactions
38 | ;; compute current account balance.
39 |
40 | ;; Output a list of strings where every string is: "$first-name $last-name: $balance"
41 |
42 | (def accounts
43 | [{:first-name "Derek"
44 | :last-name "Redmond"
45 | :transactions [188 289 -168 867 -493 -410 942 686 834 -867]}
46 | {:first-name "Tiya"
47 | :last-name "Everett"
48 | :transactions [778 -241 -90 -345 560 615 -603 -236 435 -90]}
49 | {:first-name "Mathilda"
50 | :last-name "Donald"
51 | :transactions [963 -848 320 -456 128 769 -740 -349 -790 -849]}
52 | {:first-name "Taran"
53 | :last-name "Barrera"
54 | :transactions [338 -670 -842 582 -446 -826 85 878 869 -345]}])
55 |
56 | (comment
57 | ;; This should be the result
58 | '("Derek Redmond: 1868"
59 | "Tiya Everett: 783"
60 | "Mathilda Donald: -1852"
61 | "Taran Barrera: -377"))
62 |
--------------------------------------------------------------------------------
/resources/public/css/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | html * {
6 | box-sizing: inherit;
7 | }
8 |
9 | html,
10 | body {
11 | width: 100%;
12 | height: 100%;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | padding: 32px;
18 | background: linear-gradient(#060b24, #175169, #56b36d);
19 | color: #55af6a;
20 | font: normal 20px "Helvetica Neue", Helvetica, Arial, sans-serif;
21 | letter-spacing: 0;
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | -moz-font-feature-settings: "liga" on;
25 | line-height: 1.4;
26 | text-rendering: optimizeLegibility;
27 | }
28 |
29 | button {
30 | border: none;
31 | border-radius: 18px;
32 | background-color: #55af6a;
33 | background-image: none;
34 | padding: 8px 24px;
35 | color: #fff;
36 | -webkit-appearance: none;
37 | -moz-appearance: none;
38 | appearance: none;
39 | font-weight: 500;
40 | text-transform: lowercase;
41 | font-size: 16px;
42 | line-height: 15px;
43 | height: 37px;
44 | box-shadow: 0 2px 16px rgba(85, 175, 106, 0.2);
45 | }
46 | button:disabled {
47 | background-color: #cccccc;
48 | }
49 | button:disabled:hover {
50 | box-shadow: none;
51 | }
52 |
53 | input {
54 | border: none;
55 | border-radius: 5px;
56 | background: #fff;
57 | padding: 8px;
58 | color: #242424;
59 | font-size: 18px;
60 | margin: 0 0 16px;
61 | }
62 |
63 | button:hover,
64 | input[type="submit"]:hover {
65 | box-shadow: 0 2px 16px rgba(85, 175, 106, 0.5);
66 | cursor: pointer;
67 | }
68 | button:active {
69 | margin-top: 1px;
70 | }
71 | button:focus,
72 | input:focus {
73 | outline: none;
74 | box-shadow: 0 0 4px rgba(85, 175, 106, 0.2);
75 | }
76 | label {
77 | display: block;
78 | font-size: 11px;
79 | color: #fff;
80 | text-transform: uppercase;
81 | font-weight: 700;
82 | margin: 0 0 2px;
83 | }
84 | .form {
85 | margin: 32px 0;
86 | }
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Setup
2 |
3 | 1. If you don't have it yet, download and install [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) on your machine
4 | 2. Install [Leiningen](https://leiningen.org/)
5 | 3. Clone this repository
6 | 4. `cd` into repo's directory and execute the following commands
7 | - `lein do server` to start dev server
8 | - `lein do ide` to start IDE
9 |
10 | 5. Verify build: once initial compilation is done you should see a running app at [localhost:3000](http://localhost:3000)
11 | 6. Verify IDE: IDE starts at [localhost:4000](http://localhost:4000), you should see editor UI there
12 |
13 | ## Topics
14 |
15 | - Syntax, variables, functions and macros
16 | - Threading macro (pipeline operator)
17 | - Primitive data types
18 | - Control flow
19 | - Data structures
20 | - Working with collections
21 | - State
22 | - Interop with JavaScript
23 | - Namespaces
24 | - Building UIs
25 |
26 | ## Tips
27 |
28 | - Use [ClojureDocs](https://clojuredocs.org/) during the workshop to lookup functions from standard library
29 | - Have [ClojureScript Cheatsheet](http://cljs.info/cheatsheet/) open as a quick guide
30 |
31 | ## Useful links
32 |
33 | - [ClojureScript Synonyms](https://kanaka.github.io/clojurescript/web/synonym.html) — translation of common things from JavaScript into ClojureScript
34 | - [ClojureScript Cheatsheet](http://cljs.info/cheatsheet/) — a quick reference to a standard library of the language
35 | - [ClojureDocs](https://clojuredocs.org/) — documentation website
36 | - [Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide) — a style guide to writing idiomatic Clojure code
37 | - [clojurescript.org](https://clojurescript.org/) — ClojureScript documentaion website
38 | - [Community Resources](http://clojure.org/community/resources)
39 | - [ClojureScript API Docs](http://cljs.github.io/api/)
40 | - [Quickref for Clojure Core](https://clojuredocs.org/quickref)
41 | - [ClojureScript Tutorial](https://www.niwi.nz/cljs-workshop/)
42 | - [ClojureScript Koans](http://clojurescriptkoans.com/)
43 | - [Transforming Data with ClojureScript](http://langintro.com/cljsbook/)
44 |
--------------------------------------------------------------------------------
/src/hello/t10_ui.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t10-ui
2 | (:require [reagent.core :as r]
3 | [goog.dom :as gdom]))
4 |
5 | ;; In most ClojureScript wrappers for React components are described
6 | ;; with a special syntax called Hiccup,
7 | ;; where elements are built out of Clojure's data structures
8 |
9 | [:button ;; tag/component-name
10 | {:on-click #(js/alert "Click!")} ;; attributes/props (optional)
11 | "press"] ;; children
12 |
13 | ;;
14 |
15 | ;; In Hiccup a tag can also define HTML class and id attributes
16 | [:div#id.class1.class2]
17 |
18 |
19 | ;; Components
20 | ;;
21 |
22 | ;; Reagent (http://reagent-project.github.io/) component
23 | ;; is a function that receives props as arguments and returns Hiccup
24 |
25 | (defn button [{:keys [on-click disabled?]} text]
26 | [:button {:on-click on-click
27 | :disabled disabled?}
28 | text])
29 |
30 | (defn input-field [{:keys [label value on-change invalid?]}]
31 | [:div
32 | [:label label]
33 | [:input {:value value
34 | :on-change on-change
35 | :style {:border (when invalid? "1px solid #ff0000")}}]])
36 |
37 | ;; This is how to render a component into the DOM
38 | (r/render [button {:on-click #(js/alert "hello!")} "alert"]
39 | (gdom/getElement "button-target"))
40 |
41 | ;; Components can be combined together to form more complex ones
42 | (defn subscribe-form []
43 | [:form
44 | [input-field {:label "email"
45 | :value ""
46 | :on-change identity}]
47 | [button {} "Subscribe"]])
48 |
49 |
50 | ;; State in Reagent is constructed with Atom-like type
51 | ;; when `email` is updated, component will be re-rendered automatically
52 |
53 | (def email (r/atom ""))
54 |
55 | (defn ajax-subscribe-form []
56 | [:div.form
57 | [input-field {:label "email"
58 | :value @email
59 | :on-change #(reset! email (.. % -target -value))}]
60 | [button {:on-click #(js/alert @email)} "Subscribe"]])
61 |
62 | (r/render [ajax-subscribe-form] (gdom/getElement "form-target"))
63 |
64 | ;; Local state in components is also supported.
65 | ;; Such stateful component is created as a function that returns rendering function
66 | ;; which closes over its state and re-renders every time local state is updated
67 | (defn ajax-subscribe-form-with-state []
68 | (let [email (r/atom "")]
69 | (fn []
70 | (let [value @email
71 | short? (> (count value) 3)
72 | not-email? (not (re-matches #".+@.+\..+" value))
73 | invalid? (and short? not-email?)
74 | disabled? not-email?]
75 | [:div.form
76 | [input-field {:label "email (with validation)"
77 | :value value
78 | :invalid? invalid?
79 | :on-change #(reset! email (.. % -target -value))}]
80 | [button {:on-click #(js/alert value)
81 | :disabled? disabled?}
82 | "Subscribe"]]))))
83 |
84 | (r/render [ajax-subscribe-form-with-state] (gdom/getElement "form-local-target"))
85 |
--------------------------------------------------------------------------------
/src/hello/t01_syntax.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t01-syntax)
2 |
3 | ;; ## Syntax
4 | ;;
5 |
6 | ;; Clojure code is made of S-expressions (symbolic expressions),
7 | ;; symbols enclosed with parentheses.
8 |
9 | (+ 1 2) ;; <- `1 + 2` in JavaScript
10 |
11 | ;; In Lisps everything is an expression,
12 | ;; there's no statements in the language (like `delete` in JavaScript).
13 | ;; The result of evaluation of the last expression is returned
14 | (do
15 | (+ 1 2) ;; <- evaluated but result is skipped
16 | (+ 1 3)) ;; <- returned
17 |
18 | ;; The first element of an S-expression is a function name
19 |
20 | (inc 1)
21 |
22 | ;; or a special form (syntax)
23 |
24 | (if true "true" "false")
25 |
26 | ;; or a macro
27 |
28 | (defn f [x] x)
29 |
30 | ;; and all remaining elements are arguments.
31 |
32 | ;; This is called “prefix notation” or “Polish notation”.
33 |
34 |
35 | ;; ## Naming
36 | ;;
37 |
38 | ;; Idiomatic Clojure naming is to use kebab case:
39 | ;; user-name my-fancy-function
40 |
41 | ;; Predicate functions are suffixed with `?` character:
42 |
43 | (even? 2)
44 |
45 | ;; Sometimes, side-effecting functions are suffixed with `!` character
46 |
47 | (reset! (atom 0) 1)
48 |
49 |
50 | ;; ## Variables
51 | ;;
52 |
53 | ;; There's two types of vars in Clojure: global and local.
54 |
55 |
56 | ;; ### Global vars
57 | ;;
58 |
59 | ;; A global var is declared globally in current namespace,
60 | ;; no matter where in code it is defined.
61 |
62 | (def x 1) ;; #'hello.01-syntax/x
63 |
64 | ;; What you see in the result of evaluation is a fully qualified name of the var.
65 | ;; The part before slash (hello.01-syntax) is the name of the namespace
66 | ;; where that var is defined.
67 |
68 |
69 | ;; ### Local bindings
70 | ;;
71 |
72 | ;; Local variables are called local bindings in Clojure.
73 | ;; You can bind values to symbols and refer to their values in the scope of the binding.
74 |
75 | (let [x 1
76 | y 2
77 | x (+ x 1)]
78 | (+ x y)
79 | (* x x))
80 |
81 |
82 | ;; ## Function definition
83 | ;;
84 |
85 | (defn add [a b c]
86 | (+ a b c))
87 |
88 | (add 3 4 5)
89 |
90 | ;; In fact `defn` is a macro, which expands into `def` + `fn`
91 |
92 | (def add
93 | (fn [a b c] (+ a b c)))
94 |
95 | ;; macros are user defined syntax constructs
96 | ;; Clojure itself is built out of many macros
97 |
98 | ;; We can verify that `defn` is a macro by forcing it to expand
99 | ;; note that `macroexpand` takes a form as data (quoted with ')
100 |
101 | (macroexpand '(defn add [a b c]
102 | (+ a b c)))
103 |
104 | ;; Here's a simplified implementation of `defn` macro
105 | ;; When `defn` is called it returns another Lisp form in a form of data.
106 | ;; In the body of the macro the form looks like a template
107 | ;; where missing spots are being replaced with the arguments.
108 |
109 | (defmacro -defn [name args body]
110 | `(def ~name (fn ~args ~body)))
111 |
112 |
113 | ;; ### Anonymous functions (lambdas)
114 | ;;
115 |
116 | ;; `fn` form stands for anonymous function.
117 | ;; Because Clojure is a functional language and its standard library
118 | ;; provides many higher-order functions,
119 | ;; you'll often find yourself writing anonymous functions.
120 | ;; To make it more convenient Clojure has a shorthand notation for lambdas (anonymous functions).
121 |
122 | ;; Let's map a list of numbers into a list of same numbers + 10.
123 | ;; `map` is a higher-order function which takes mapping function
124 | ;; and a collection of values and produces a new collection of values
125 | ;; transformed using provided function.
126 |
127 | (map (fn [v] (+ v 10)) [0 1 2 3])
128 |
129 | ;; This form can be written down using shorthand syntax
130 |
131 | (map #(+ % 10) [0 1 2 3])
132 |
133 | ;; Shorthand syntax consists of a function body prepended with # character.
134 | ;; Arguments doesn't have names, instead they are aliased with % character.
135 | ;; If there are more than one argument it is possible to refer them with indexed alias
136 | ;; such as %1, %2 and so on (index starts from 1).
137 |
138 | (reduce #(* %1 %2) (range 1 10))
139 |
140 | ;; Nesting lambdas in a shorthand notation is not allowed.
141 |
--------------------------------------------------------------------------------