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