├── .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 | --------------------------------------------------------------------------------