├── .gitignore
├── README.md
├── deps.edn
├── dev.cljs.edn
├── figwheel-main.edn
├── project.clj
├── resources
└── public
│ ├── css
│ └── main.css
│ └── index.html
└── src
└── hello
├── core.cljs
├── t01_syntax.cljs
├── t02_macros.cljs
├── t03_data_types.cljs
├── t04_control_flow.cljs
├── t05_data_structures.cljs
├── t06_transforming_collections.cljs
├── t07_state.cljs
├── t08_js_interop.cljs
├── t09_modules.cljs
├── t10_ui.cljs
└── timer.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | .nightlight.edn
2 | .cpcache
3 | node_modules
4 | resources/public/js
5 | .lein-repl-history
6 | target
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Setup
2 |
3 | _Please do this before workshop. Ping me on Slack if you have any problems with setup._
4 |
5 | 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
6 | 2. [Install Clojure CLI](https://clojure.org/guides/getting_started#_clojure_installer_and_cli_tools) or [Leiningen](https://leiningen.org/) if you are on Windows
7 | 3. Clone [workshop repository](https://github.com/roman01la/amsterdamjs-clojurescript-workshop) that we are going to work with
8 | 4. `cd` into repo's directory and execute the following commands
9 |
10 | * `clojure -m figwheel.main -b dev -r` or `lein do server` to start dev server
11 | * `clj -m nightlight.core --url "http://localhost:3000"` or `lein do ide` to start IDE server, which we will work in
12 |
13 | 5. Verify build: once initial compilation is done you should see a running app at [localhost:3000](http://localhost:3000)
14 | 6. Verify IDE: IDE starts at [localhost:4000](http://localhost:4000), you should see editor UI there
15 |
16 | ## Topics
17 |
18 | * Syntax, variables, functions and macros
19 | * Threading macro (pipeline operator)
20 | * Primitive data types
21 | * Control flow
22 | * Data structures
23 | * Collections transformation
24 | * State
25 | * Interop with JavaScript
26 | * Namespaces
27 | * Building UIs
28 |
29 | ## Tips
30 |
31 | * Use [ClojureDocs](https://clojuredocs.org/) during the workshop to lookup functions from standard library
32 | * Have [ClojureScript Cheatsheet](http://cljs.info/cheatsheet/) open as a quick guide
33 |
34 | ## Useful links
35 |
36 | * [ClojureScript Synonyms](https://kanaka.github.io/clojurescript/web/synonym.html) — translation of common things from JavaScript into ClojureScript
37 | * [ClojureScript Cheatsheet](http://cljs.info/cheatsheet/) — a quick reference to a standard library of the language
38 | * [ClojureDocs](https://clojuredocs.org/) — documentation website
39 | * [Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide) — a style guide to writing idiomatic Clojure code
40 | * [clojurescript.org](https://clojurescript.org/) — ClojureScript documentaion website
41 | * [Community Resources](http://clojure.org/community/resources)
42 | * [ClojureScript API Docs](http://cljs.github.io/api/)
43 | * [Quickref for Clojure Core](https://clojuredocs.org/quickref)
44 | * [ClojureScript Tutorial](https://www.niwi.nz/cljs-workshop/)
45 | * [ClojureScript Koans](http://clojurescriptkoans.com/)
46 | * [Transforming Data with ClojureScript](http://langintro.com/cljsbook/)
47 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {org.clojure/clojure {:mvn/version "1.9.0"}
2 | org.clojure/clojurescript {:mvn/version "1.10.238"}
3 | reagent {:mvn/version "0.8.1"}
4 | com.bhauman/figwheel-main {:mvn/version "0.1.0-SNAPSHOT"}
5 | nightlight {:mvn/version "RELEASE"}}
6 | :paths ["src" "resources"]}
7 |
--------------------------------------------------------------------------------
/dev.cljs.edn:
--------------------------------------------------------------------------------
1 | {:main hello.core
2 | :output-to "resources/public/js/bundle.js"
3 | :output-dir "resources/public/js"
4 | :asset-path "/js"}
5 |
--------------------------------------------------------------------------------
/figwheel-main.edn:
--------------------------------------------------------------------------------
1 | {:ring-server-options {:port 3000}}
2 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject amsterdamjs.workshop "0.1.0-SNAPSHOT"
2 | :dependencies [[org.clojure/clojure "1.9.0"]
3 | [org.clojure/clojurescript "1.10.238"]
4 | [reagent "0.8.1"]
5 | [com.bhauman/figwheel-main "0.1.4"]
6 | [nightlight "RELEASE"]
7 | [com.cemerick/piggieback "0.2.2"]]
8 |
9 | :source-paths ["src" "resources"]
10 |
11 | :aliases {"server" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
12 | "ide" ["run" "-m" "nightlight.core" "--url" "http://localhost:3000"]}
13 |
14 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]})
15 |
--------------------------------------------------------------------------------
/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: #fff;
19 | color: #161616;
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: #ff2e74;
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(255, 46, 116, 0.3);
45 | }
46 |
47 | input {
48 | border: 1px solid #ff2e74;
49 | border-radius: 5px;
50 | background: #fff;
51 | padding: 8px;
52 | color: #242424;
53 | font-size: 18px;
54 | margin: 0 0 16px;
55 | }
56 |
57 | button:hover,
58 | input[type='submit']:hover {
59 | box-shadow: 0 2px 16px rgba(255, 46, 116, 0.5);
60 | cursor: pointer;
61 | }
62 | button:active {
63 | margin-top: 1px;
64 | }
65 | button:focus,
66 | input:focus {
67 | outline: none;
68 | box-shadow: 0 0 4px rgba(255, 46, 116, 0.2);
69 | }
70 | label {
71 | display: block;
72 | font-size: 11px;
73 | color: #525252;
74 | text-transform: uppercase;
75 | font-weight: 700;
76 | margin: 0 0 2px;
77 | }
78 | .form {
79 | margin: 32px 0;
80 | }
81 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AmsterdamJS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 automatically
14 | (do
15 | (+ 1 2) ;; <- evaluated but skipped
16 | (+ 1 3)) ;; <- returned
17 |
18 | ;; When representing source code in Clojure,
19 | ;; the first element of an S-expression is a function name
20 |
21 | (inc 1)
22 |
23 | ;; or a special form (syntax)
24 |
25 | (if true "true" "false")
26 |
27 | ;; or a macro
28 |
29 | (defn f [x] x)
30 |
31 | ;; and all remaining elements are arguments.
32 |
33 | ;; This is called “prefix notation” or “Polish notation”.
34 |
35 |
36 | ;; ## Naming
37 | ;;
38 |
39 | ;; Idiomatic Clojure/Lisp naming is to use kebab case:
40 | ;; user-name my-fancy-function
41 |
42 | ;; Predicate functions are suffixed with `?` character:
43 |
44 | (even? 2)
45 |
46 |
47 | ;; ## Variables
48 | ;;
49 |
50 | ;; There's two types of vars in Clojure: global and local.
51 |
52 |
53 | ;; ### Global vars
54 | ;;
55 |
56 | ;; A global var is declared globally in current namespace,
57 | ;; no matter where in code it is defined.
58 |
59 | (def x 1) ;; #'hello.01-syntax/x
60 |
61 | ;; What you see in the result of evaluation is a fully qualified name of the var.
62 | ;; The part before slash (hello.01-syntax) is the name of the namespace
63 | ;; where that var is defined.
64 |
65 |
66 | ;; ### Local bindings
67 | ;;
68 |
69 | ;; Local variables are called local bindings in Clojure.
70 | ;; You can bind values to symbols and use refer to their values in the scope of the binding.
71 |
72 | (let [x 1
73 | y 2
74 | x (+ x 1)]
75 | (+ x y)
76 | (* x x))
77 |
78 |
79 | ;; ## Function definition
80 | ;;
81 |
82 | (defn add [a b c]
83 | (+ a b c))
84 |
85 | (add 3 4 5)
86 |
87 | ;; In fact `defn` is a macro, which expands into... `def` ✨
88 |
89 | (def add
90 | (fn [a b c] (+ a b c)))
91 |
92 | ;; macros are user defined syntax constructs
93 | ;; Clojure itself is built out of many macros
94 |
95 | ;; We can verify that `defn` is a macro by forcing it to expand
96 |
97 | (macroexpand '(defn add [a b c]
98 | (+ a b c)))
99 |
100 | ;; Here's a simplified implementation of `defn` macro
101 | ;; When `defn` is called it returns another Lisp form in a form of data.
102 | ;; In the body of the macro the form looks like a template
103 | ;; where missing spots are being replaced with the arguments.
104 |
105 | (defmacro -defn [name args body]
106 | `(def ~name (fn ~args ~body)))
107 |
108 |
109 | ;; ### Anonymous functions (lambdas)
110 | ;;
111 |
112 | ;; `fn` form stands for anonymous function.
113 | ;; Because Clojure is a functional language and its standard library
114 | ;; provides many higher-order functions,
115 | ;; you'll often find yourself writing anonymous functions.
116 | ;; For this purpose Clojure has a shorthand notation for lambdas (anonymous functions).
117 |
118 | ;; Let's map a list of numbers into a list of same numbers + 10.
119 | ;; `map` is a higher-order function which takes mapping function
120 | ;; and a collection of values and produces a new collection of values
121 | ;; transformed using provided function.
122 |
123 | (map (fn [v] (+ v 10)) [0 1 2 3])
124 |
125 | ;; This form can be written down much more concise using shorthand syntax
126 |
127 | (map #(+ % 10) [0 1 2 3])
128 |
129 | ;; Shorthand syntax is function body prepended with # character.
130 | ;; Arguments doesn't have names, instead they are aliased with % character.
131 | ;; If there are more than one argument it is possible to refer them with indexed alias
132 | ;; such as %1, %2 and so on (index starts from 1).
133 |
134 | (reduce #(* %1 %2) (range 1 10))
135 |
136 | ;; Nesting lambdas is not allowed.
137 |
--------------------------------------------------------------------------------
/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/t03_data_types.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t03-data-types)
2 |
3 | ;; ## Primitive data types
4 | ;;
5 |
6 | ;; Clojure(Script) 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 program to refer to function parameters,
56 | ;; let bindings, class names and global vars.
57 |
58 | 'this-is-a-symbol
59 |
60 | ;; You may wonder why symbols are prefixed 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/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/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 | ;; In particular, since “modification” operations yield new collections,
8 | ;; the new collection might not have the same concrete type as the source collection,
9 | ;; but will have the same logical (interface) type.
10 |
11 | ;; ## List
12 | ;;
13 |
14 | ;; List is a classic singly linked list data structure. Lisp code itself is made out of lists.
15 | ;; Lists support fast append to the beginning and retrieval of the head (first) element.
16 | '(1 2 3)
17 | (list? '(1 2 3))
18 |
19 |
20 | ;; ## Vector
21 | ;;
22 |
23 | ;; Vector is an indexed collection (like JavaScript's Array).
24 | ;; Vectors support fast lookup by index and fast append to the end of collection.
25 | [1 2 3]
26 | (vector? [1 2 3])
27 |
28 | ;; ## Map
29 | ;;
30 |
31 | ;; A Map is a collection that maps keys to values.
32 | {:a 1 :b 2 :c 3}
33 | (map? {:a 1})
34 |
35 | ;; Keys can be arbitrary values
36 | {1 :number
37 | "1" :string
38 | :a :keyword
39 | [1] :vector
40 | {:a 1} :map}
41 |
42 | ;; ## Set
43 | ;;
44 |
45 | ;; Sets are collections of unique values.
46 |
47 | #{1 2 3 4}
48 | (set? #{1 2 3})
49 |
--------------------------------------------------------------------------------
/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 | ;; Create a sequence of values from `1` to `100`,
37 | ;; `filter` out `odd` values
38 | ;; and `reduce` collection to a `product` of all its values.
39 | ;; Hint: use threa-last macro.
40 |
--------------------------------------------------------------------------------
/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 not loose 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/t08_js_interop.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t08-js-interop)
2 |
3 | ;; ClojureScript provides a simple access to host platform
4 | ;; (Browser or Node.js) 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/t09_modules.cljs:
--------------------------------------------------------------------------------
1 | (ns hello.t09-modules)
2 |
3 | ;; A namespace is a unit of modularity in Clojure
4 |
5 | (comment
6 | (require '[clojure.string :as cstr]))
7 | ;; import * as cstr from "clojure/string"
8 |
9 | (comment
10 | (require '[clojure.string :refer [join]]))
11 | ;; import { join } from "clojure/string"
12 |
--------------------------------------------------------------------------------
/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 [on-click text]
26 | [:button {:on-click on-click} text])
27 |
28 | (defn input-field [{:keys [label value on-change]}]
29 | [:div
30 | [:label label]
31 | [:input {:value value
32 | :on-change on-change}]])
33 |
34 | ;; This is how to render a component into the DOM
35 | (r/render [button #(js/alert "hello!") "alert"] (gdom/getElement "button-target"))
36 |
37 | ;; Components can be combined together to form more complex ones
38 | (defn subscribe-form []
39 | [:form
40 | [input-field {:label "email"
41 | :value ""
42 | :on-change identity}]
43 | [button identity "Subscribe"]])
44 |
45 |
46 | ;; State in Reagent is constructed with Atom-like type
47 | ;; when `email` is updated, component will be re-rendered automatically
48 |
49 | (def email (r/atom ""))
50 |
51 | (defn ajax-subscribe-form []
52 | [:div.form
53 | [input-field {:label "email"
54 | :value @email
55 | :on-change #(reset! email (.. % -target -value))}]
56 | [button #(js/alert @email) "Subscribe"]])
57 |
58 | (r/render [ajax-subscribe-form] (gdom/getElement "form-target"))
59 |
60 | ;; Local state in components is also supported.
61 | ;; Such stateful component is created as a function that returns rendering function
62 | ;; which closes over its state and re-renders every time local state is updated
63 | (defn ajax-subscribe-form-with-state []
64 | (let [email (r/atom "")]
65 | (fn []
66 | [:div.form
67 | [input-field {:label "email"
68 | :value @email
69 | :on-change #(reset! email (.. % -target -value))}]
70 | [button #(js/alert @email) "Subscribe"]])))
71 |
72 | (r/render [ajax-subscribe-form-with-state] (gdom/getElement "form-local-target"))
73 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------