├── .gitignore ├── 01-drumkit ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ ├── index.html │ │ ├── sounds │ │ ├── boom.wav │ │ ├── clap.wav │ │ ├── hihat.wav │ │ ├── kick.wav │ │ ├── openhat.wav │ │ ├── ride.wav │ │ ├── snare.wav │ │ ├── tink.wav │ │ └── tom.wav │ │ └── style.css └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 02-clock ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 03-css-variables ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 04-array-cardio ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 05-flex-panels-image-gallery ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 06-ajax-type-ahead ├── .gitignore ├── README.md ├── README.org ├── deps.edn ├── resources │ ├── index.html │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 07-array-cardio-2 ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 08-html5-canvas ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 09-dev-tool-tricks └── README.md ├── 10-check-multiple-checkboxes ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 11-html5-video-player ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── styles.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 12-key-sequence-detection ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 13-slide-in-on-scroll ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 14-object-and-arrays └── README.md ├── 15-localstorage-and-event-delegation ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 16-mousemove-text-shadows ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 17-sort-without-articles ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 18-string-times ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 19-unreal-webcam-fun ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── styles.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 20-native-speech-recognition ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 21-geolocation ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 22-follow-along-links ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── styles.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 23-speech-synthesis ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── style.css └── src │ └── speech_synthesis │ ├── core.cljs │ └── macros.clj ├── 24-sticky-nav ├── .gitignore ├── README.md ├── deps.edn ├── resources │ ├── index.html │ └── style.css └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 25-event-delegation ├── .gitignore ├── README.md ├── deps.edn ├── resources │ └── index.html └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 26-stripe-follow-along-dropdown ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ └── index.html ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 27-click-and-drag-to-scroll ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ ├── index.html │ │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 28-countdown-clock ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ ├── index.html │ │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 29-video-speed-controller-ui ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ ├── index.html │ │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj ├── 30-whack-a-mole-game ├── .gitignore ├── README.md ├── deps.edn ├── dev.cljs.edn ├── resources │ └── public │ │ ├── dirt.svg │ │ ├── index.html │ │ ├── mole.svg │ │ └── style.css ├── ro.edn └── src │ └── app │ ├── core.cljs │ └── macros.clj └── README.md /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/.gitignore -------------------------------------------------------------------------------- /01-drumkit/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | .cpcache 3 | target 4 | -------------------------------------------------------------------------------- /01-drumkit/README.md: -------------------------------------------------------------------------------- 1 | # Drumkit 2 | 3 | --- 4 | 5 | - [Intro](#intro) 6 | - [Quick Start](#quick-start) 7 | - [Lessons Learned](#lessons-learned) 8 | - [Protocols](#Protocols) 9 | - [defs](#defs) 10 | - [Formatting Strings](#formatting-strings) 11 | - [Final Thoughts](#final-thoughts) 12 | - [Resources](#resources) 13 | 14 | ## Quick Start 15 | 16 | ```bash 17 | clj -M:dev 18 | ``` 19 | 20 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: See [CHANGELOG] 21 | 22 | ```bash 23 | ➜ clj -h 24 | Version: 1.10.3.855 # this is the version your on 25 | ``` 26 | 27 | ## Intro 28 | 29 | I originally built this example using [3 different tools](https://github.com/tkjone/clojurescript-30/tree/59e6afa3342ba5d596948d331f1c61231be97259) to see how the experience compared. Since then, I have moved away from showing all three in the same repo. If you want to see them, visit the link above. With this in mind, some of my original assumptions still hold true: 30 | 31 | **Assumptions:** 32 | 33 | 1. Keep things simple when teaching a new language 34 | 35 | 2. The REPL is more confusing in the beginning then helpful. Consider, you test and rapidly develop with it, but when you write code, its in a file. 36 | 37 | 3. Don't introduce too much tooling right away. One of the reasons I liked JS30 is because it approaches education of JS and Web Development without tooling. Just create an HTML file, open it in the browser and your good to go. CLJS is compiled, so its not as simple, but with tools like `deps-cli` it has become a lot easier. 38 | 39 | # Lessons Learned 40 | 41 | ## Protocols 42 | 43 | Originally I thought I needed this: 44 | 45 | ```clojure 46 | ;; protocol 47 | (extend-type js/NodeList 48 | ISeqable 49 | (-seq [node-list] (array-seq node-list))) 50 | ``` 51 | 52 | But really I could just do it inline. Difference is that 53 | 54 | - Easier for others to read and understand 55 | 56 | - A little complex for such a small program 57 | 58 | I would love to delve into this a little further. See [This Commit](https://github.com/tkjone/clojurescript-30/commit/148a5744caa180c948598cf9234c4928939f7e9e) for the example of the code that was there, which I later removed 59 | 60 | ## Naming Conventions 61 | 62 | `core.cljs` is a naming convention. This is the equivalent of `drumkit/index.js` in JS world. You could call it anything you want really. 63 | 64 | ## defs 65 | 66 | when you use `def` these become global variables in Clojure. For this reason, we usually put defs at the top. One of the reasons for this is because Clojure is read from bottom to top. 67 | 68 | ## Formatting strings 69 | 70 | There are different ways to format strings. One way is like this: 71 | 72 | ```clojure 73 | (:require [goog.string :as gstr] goog.string.format) 74 | 75 | data-selector (gstr/format "audio[data-key=\"%s\"]" key-code) 76 | ``` 77 | 78 | An alternative is like this 79 | 80 | ```clojure 81 | data-selector (str "audio[data-key=" "\"" key-code "\"" "]") 82 | ``` 83 | 84 | Difference between the two? 85 | 86 | 1. The second option can be used in `clojure` and `clojurescript` 87 | 2. The first option can be considered more readable + easier to maintain 88 | 89 | Just keep in mind there are other ways to format strings, so this is not an exhaustive overview. 90 | 91 | ## Final Thoughts 92 | 93 | > This was actually annoying as fuck. Where is the context? The resources are not well documented, there is no, here is something bsic to get you going. 94 | 95 | The above is what I wrote when I finished this exercise. No idea what I was referring to at the time. Checkout the [Initial Commit](https://github.com/tkjone/clojurescript-30/commit/34b151e6a2d0fc86fe3f6b34ee0fefaee88c5b94) for more. 96 | 97 | # Resources 98 | 99 | - https://www.andrewhfarmer.com/webpack-hmr-tutorial/ 100 | - https://github.com/bhauman/lein-figwheel 101 | 102 | [CHANGELOG]: https://github.com/clojure/brew-install/blob/1.10.1/CHANGELOG.md 103 | -------------------------------------------------------------------------------- /01-drumkit/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}} 4 | 5 | :aliases {:dev 6 | {:main-opts ["--main" "figwheel.main" 7 | "--build" "dev" 8 | "--repl"]}}} 9 | -------------------------------------------------------------------------------- /01-drumkit/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /01-drumkit/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS Drum Kit 6 | 7 | 8 | 9 |
10 |
11 | A clap 12 |
13 |
14 | S hihat 15 |
16 |
17 | D kick 18 |
19 |
20 | F openhat 21 |
22 |
23 | G boom 24 |
25 |
26 | H ride 27 |
28 |
29 | J snare 30 |
31 |
32 | K tom 33 |
34 |
35 | L tink 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/boom.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/boom.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/clap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/clap.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/hihat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/hihat.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/kick.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/openhat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/openhat.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/ride.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/ride.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/snare.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/tink.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/tink.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/sounds/tom.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/athomasoriginal/clojurescript-30/b99c45fd2bb89b5042d3b617d325ec8743147bab/01-drumkit/resources/public/sounds/tom.wav -------------------------------------------------------------------------------- /01-drumkit/resources/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 10px; 3 | background: url(http://i.imgur.com/b9r5sEL.jpg) bottom center; 4 | background-size: cover; 5 | } 6 | body, 7 | html { 8 | margin: 0; 9 | padding: 0; 10 | font-family: sans-serif; 11 | } 12 | 13 | .keys { 14 | display: flex; 15 | flex: 1; 16 | min-height: 100vh; 17 | align-items: center; 18 | justify-content: center; 19 | } 20 | 21 | .key { 22 | border: 0.4rem solid black; 23 | border-radius: 0.5rem; 24 | margin: 1rem; 25 | font-size: 1.5rem; 26 | padding: 1rem 0.5rem; 27 | transition: all 0.07s ease; 28 | width: 10rem; 29 | text-align: center; 30 | color: white; 31 | background: rgba(0, 0, 0, 0.4); 32 | text-shadow: 0 0 0.5rem black; 33 | } 34 | 35 | .playing { 36 | transform: scale(1.1); 37 | border-color: #ffc600; 38 | box-shadow: 0 0 1rem #ffc600; 39 | } 40 | 41 | kbd { 42 | display: block; 43 | font-size: 4rem; 44 | } 45 | 46 | .sound { 47 | font-size: 1.2rem; 48 | text-transform: uppercase; 49 | letter-spacing: 0.1rem; 50 | color: #ffc600; 51 | } 52 | -------------------------------------------------------------------------------- /01-drumkit/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; Event Handlers 7 | 8 | (defn play-sound 9 | [e] 10 | (let [key-code (.-keyCode e) 11 | data-selector (str "audio[data-key=" "\"" key-code "\"" "]") 12 | key-selector (str ".key[data-key=" "\"" key-code "\"" "]") 13 | audio-element (.querySelector js/document data-selector) 14 | key-element (.querySelector js/document key-selector)] 15 | (when-not (.isNull js/goog audio-element) 16 | (set! (.-currentTime audio-element) 0) 17 | (.play audio-element) 18 | (.classList/add key-element "playing")))) 19 | 20 | 21 | (defn remove-transition 22 | [e] 23 | (this-as this 24 | (when (= (.-propertyName e) "transform") 25 | (.remove (.-classList this) "playing")))) 26 | 27 | 28 | ;; Init 29 | 30 | (let [el-keys (.querySelectorAll js/document ".key")] 31 | (doseq [el-key (array-seq el-keys)] 32 | (.addEventListener el-key "transitionend" remove-transition))) 33 | 34 | (.addEventListener js/window "keydown" play-sound) 35 | -------------------------------------------------------------------------------- /01-drumkit/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /02-clock/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .cpcache 3 | -------------------------------------------------------------------------------- /02-clock/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["--main" "cljs.main" 5 | "--repl-opts" "ro.edn" 6 | "--watch" "src" 7 | "--compile" "app.core" 8 | "--repl"]}}} 9 | -------------------------------------------------------------------------------- /02-clock/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS + CSS Clock 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /02-clock/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /02-clock/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [cljs.spec.alpha :as s]) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | ;; specs 7 | 8 | (s/def ::time-unit #{:sec :min :hour}) 9 | 10 | (s/def ::date inst?) 11 | 12 | 13 | ;; globals 14 | 15 | (def duration 16 | {:sec 1000 17 | :min 60000 18 | :hour 3600000}) 19 | 20 | (def time-unit->class 21 | {:sec ".second-hand" 22 | :min ".min-hand" 23 | :hour ".hour-hand"}) 24 | 25 | 26 | ;; Helpers 27 | 28 | (defn- set-transform-style! 29 | "Sets the transform-style css property of the specified hand." 30 | [css-class transform-val] 31 | (set! (.-transform (.-style (.querySelector js/document css-class))) transform-val)) 32 | 33 | (s/fdef set-transform-style! 34 | :args (s/and (s/cat :css-class ::time-unit :transform-val int?)) 35 | :ret nil?) 36 | 37 | 38 | (defn duration->degrees 39 | "Converts the duration provided into a degree." 40 | [duration-type duration] 41 | (cond 42 | (or (= duration-type :sec) (= duration-type :min)) 43 | (+ (* (/ duration 60) 360) 90) 44 | 45 | (= duration-type :hour) 46 | (+ (* (/ duration 12) 360) 90) 47 | 48 | :else 49 | nil)) 50 | 51 | (s/fdef duration->degrees 52 | :args (s/and (s/cat :duration-type ::time-unit :duration int?)) 53 | :ret int?) 54 | 55 | 56 | (defn get-time 57 | "Returns the time-unit based on the current time provided." 58 | [now time-unit] 59 | (cond 60 | (= time-unit :sec) (.getSeconds now) 61 | (= time-unit :min) (.getMinutes now) 62 | :else (.getHours now))) 63 | 64 | (s/fdef get-time 65 | :args (s/and (s/cat :now ::date :time-unit ::time-unit)) 66 | :ret inst?) 67 | 68 | 69 | (defn set-hand! 70 | "Sets the clock hand the the correct degree." 71 | [time-unit] 72 | (let [now (js/Date.) 73 | time (get-time now time-unit) 74 | degrees (duration->degrees time-unit time) 75 | transform-val (str "rotate(" degrees "deg)") 76 | css-class (time-unit->class time-unit)] 77 | (set-transform-style! css-class transform-val))) 78 | 79 | (s/fdef set-hand! 80 | :args (s/cat :time-unit ::time-unit) 81 | :ret nil?) 82 | 83 | 84 | ;; Start 85 | 86 | ; set all clock hands to the correct positions when app loads 87 | (set-hand! :sec) 88 | (set-hand! :min) 89 | (set-hand! :hour) 90 | 91 | ; update clock hands at 1 sec, min and hour intervals 92 | (js/setInterval #(set-hand! :sec) (:sec duration)) 93 | (js/setInterval #(set-hand! :min) (:min duration)) 94 | (js/setInterval #(set-hand! :hour) (:hour duration)) 95 | -------------------------------------------------------------------------------- /02-clock/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /03-css-variables/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /03-css-variables/README.md: -------------------------------------------------------------------------------- 1 | # 03 CSS Variables 2 | 3 | - [Housekeeping](#housekeepings) 4 | - [Quickstart](#quickstart) 5 | - [Lessons Learned](#lessons-learned) 6 | - [doseq](#doseq) 7 | - [js interop](#js-interop) 8 | - [Double Dot](#double-dot) 9 | - [setProperty](#setproperty) 10 | 11 | ## Housekeeping 12 | 13 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) 14 | 15 | ## Quickstart 16 | 17 | Run the following comamnds from the root of the `05-css-variables` repo 18 | 19 | **1. Build and watch the project** 20 | 21 | ```bash 22 | clj -M:dev 23 | ``` 24 | 25 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 26 | 27 | ```bash 28 | ➜ clj -h 29 | Version: 1.10.3.855 # this is the version your on 30 | ``` 31 | 32 | ## Lessons Learned 33 | 34 | ### doseq 35 | 36 | At the time, the question I had was which one of the following is preferable: 37 | 38 | ```clojure 39 | ;; Option 1 40 | (doseq [input inputs] 41 | (.addEventListener input "change" handle-update)) 42 | 43 | (doseq [input inputs] 44 | (.addEventListener input "mousemove" handle-update)) 45 | 46 | ;; Option 2 47 | (doseq [input inputs] 48 | (.addEventListener input "change" handle-update) 49 | (.addEventListener input "mousemove" handle-update)) 50 | ``` 51 | 52 | I would go with option two. No need to run a second `for` after the first one. 53 | 54 | ### js interop 55 | 56 | **Double Dot** 57 | 58 | It was at this point where I realized the `..` macro might be a solid all around default when working with JS Interop. If we compare this app to some of the other ones I did previously, we can see the benefits: 59 | 60 | ```clojure 61 | ;; option 1 62 | (let [suffix (.. this -dataset -sizing) 63 | name (.. this -name) 64 | val (.. this -value) 65 | 66 | ;; option 2 67 | (let [key-code (.-keyCode e) 68 | ``` 69 | 70 | `Option 1` reads a little more easily, we very clearly understand that it is JS interop, it becomes easier to write as the form becomes more verbose and when comparing / reference to js it feels a little more natural. 71 | 72 | **setProperty** 73 | 74 | What does it look like to work with `setProperty`? 75 | 76 | ```clojure 77 | (.. js/document -documentElement -style (setProperty formatted-name formatted-value) 78 | ``` 79 | -------------------------------------------------------------------------------- /03-css-variables/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /03-css-variables/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scoped CSS Variables and JS 6 | 7 | 8 |

Update CSS Variables with JS

9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /03-css-variables/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /03-css-variables/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | 5 | (defn update-style [] 6 | (this-as this 7 | (let [suffix (.. this -dataset -sizing) 8 | name (.. this -name) 9 | val (.. this -value) 10 | formatted-name (str "--" name) 11 | formatted-value (str val suffix)] 12 | (.. js/document -documentElement -style (setProperty formatted-name formatted-value))))) 13 | 14 | 15 | ;; Start app 16 | 17 | (let [inputs (.. js/document (querySelectorAll ".controls input"))] 18 | (doseq [input (array-seq inputs)] 19 | (.. input (addEventListener "change" update-style)) 20 | (.. input (addEventListener "mousemove" update-style)))) 21 | -------------------------------------------------------------------------------- /03-css-variables/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /04-array-cardio/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /04-array-cardio/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /04-array-cardio/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Array Cardio 💪 6 | 7 | 8 |

Psst: have a look at the JavaScript Console 💁

9 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /04-array-cardio/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /04-array-cardio/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.object]) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | (p "---------------------------- Array.prototype.filter() ---------------------------------") 8 | 9 | ;; 1. Filter the list of inventors for those who were born in the 1500's) 10 | ;; http://blog.jenkster.com/2013/11/clojure-less-than-greater-than-tip.html 11 | ;; we are asking if the number is larger than 1500 and smaller than 1600 12 | ;; is 1500 less than or equal to 1501? 13 | ;; is 1501 less than or equal to 1600? 14 | 15 | (p "Original Data Structure") 16 | (p js/inventors) 17 | 18 | (def tranformed-ds (clj->js (filter #(<= 1500 (goog.object/get % "year") 1600) js/inventors))) 19 | 20 | (p "Transformed Data Structure") 21 | (p tranformed-ds) 22 | 23 | 24 | (p "---------------------------- Array.prototype.map() ------------------------------------") 25 | 26 | ;; 2. Give us an array of the inventors' first and last names 27 | 28 | (defn full-name [inventor] 29 | (str (goog.object/get inventor "first") " " (goog.object/get inventor "last"))) 30 | 31 | (def tranformed-ds-two (clj->js (map full-name js/inventors))) 32 | 33 | (p "Transformed Data Structure") 34 | (p tranformed-ds-two) 35 | 36 | 37 | (p "---------------------------- Array.prototype.sort() ------------------------------------") 38 | 39 | ;; 3. Sort the inventors by birthdate, oldest to youngest 40 | 41 | (def tranformed-ds-three (clj->js (sort-by #(goog.object/get % "year") js/inventors))) 42 | 43 | (p "Transformed Data Structure") 44 | (p tranformed-ds-three) 45 | 46 | 47 | (p "---------------------------- reduce -----------------------------------") 48 | 49 | ;; 4. How many years did all the inventors live? 50 | ;; subtract the year and passed date 51 | ;; add the above to a total tracker 52 | 53 | (def tranformed-ds-four (clj->js (reduce #(+ %1 (- (goog.object/get %2 "passed") (goog.object/get %2 "year")) ) 0 js/inventors))) 54 | 55 | (p "Transformed Data Structure") 56 | (p tranformed-ds-four) 57 | 58 | 59 | (p "-----------------------------sort again-------------------------------------") 60 | 61 | ;; 5. Sort the inventors by years lived 62 | 63 | ;; sorted from youngest to oldest 64 | 65 | (def tranformed-ds-five (clj->js (sort-by #(- (goog.object/get % "passed") (goog.object/get % "year")) js/inventors))) 66 | 67 | ;; sorted from oldest to youngest 68 | 69 | (def tranformed-ds-six (clj->js (sort-by #(- (goog.object/get % "passed") (goog.object/get % "year")) > js/inventors))) 70 | 71 | (p tranformed-ds-five) 72 | (p tranformed-ds-six) 73 | 74 | 75 | (p "---------------------------- filter again ------------------------------------") 76 | 77 | ;; 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name 78 | 79 | (def tranformed-ds-seven (clj->js (filter #(re-find #"de" %) js/people))) 80 | 81 | (p tranformed-ds-seven) 82 | 83 | 84 | (p "---------------------------- sort again ------------------------------------") 85 | 86 | ;; Sort the people alphabetically by last name 87 | 88 | ;; you could also do > (split % #",") which is a little cleaner and more conventional 89 | 90 | (def tranformed-ds-eight (clj->js (sort-by #(get (re-find #", (.*)" %) 1) js/people))) 91 | 92 | (p tranformed-ds-eight) 93 | 94 | (p "-----------------------------reduce again-------------------------------------") 95 | 96 | ;; Sum up the instances of each of these - in other words, creating a word frequency 97 | ;; map 98 | 99 | ;; So this one was fun because clojure actually provides a function that does 100 | ;; exactly this. 101 | 102 | (def tranformed-ds-nine (clj->js (frequencies js/data))) 103 | 104 | ;; Just for fun, I have also done the above using reduce. See below. 105 | 106 | ;; reduce over each, keep some kind of map available and increment the maps number 107 | ;; when a word in the map appears again. 108 | 109 | (def tranformed-ds-ten (clj->js (reduce #(assoc %1 %2 (inc (%1 %2 0))) {} js/data))) 110 | 111 | (p tranformed-ds-nine) 112 | (p tranformed-ds-ten) 113 | -------------------------------------------------------------------------------- /04-array-cardio/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/README.md: -------------------------------------------------------------------------------- 1 | # 05 Flex Panels Image Gallery 2 | 3 | - [Housekeeping](#housekeepings) 4 | - [Quickstart](#quickstart) 5 | - [Lessons Learned](#lessons-learned) 6 | - [Double Dot](#double-dot) 7 | - [Parens](#parens) 8 | 9 | ## Housekeeping 10 | 11 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) 12 | 13 | ## Quickstart 14 | 15 | Run the following comamnds from the root of the `07-flex-panels-image-gallery` repo 16 | 17 | **1. Build and watch the project** 18 | 19 | ```bash 20 | clj -M:dev 21 | ``` 22 | 23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 24 | 25 | ```bash 26 | ➜ clj -h 27 | Version: 1.10.3.855 # this is the version your on 28 | ``` 29 | 30 | 31 | ## Lessons Learned 32 | 33 | ### Double Dot 34 | 35 | If you review the [Double Dot](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/..) macro's documentation, they even provide the reasoinnig which is `easier to read, write etc`. Why note this? Because the clojure docs are truly a wealth of info, they just sometimes, often times, take a little bit of practice to learn where that treasure lives. 36 | 37 | ### Parens 38 | 39 | Another big reminder from this is always watch your parens. Likely that if the syntax looks correct, your parens are probably fucking with you. 40 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flex Panels 💪 6 | 7 | 8 | 9 | 100 | 101 | 102 |
103 |
104 |

Hey

105 |

Let's

106 |

Dance

107 |
108 |
109 |

Give

110 |

Take

111 |

Receive

112 |
113 |
114 |

Experience

115 |

It

116 |

Today

117 |
118 |
119 |

Give

120 |

All

121 |

You can

122 |
123 |
124 |

Life

125 |

In

126 |

Motion

127 |
128 |
129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; helpers 7 | (defn toggle-open [e] 8 | (this-as this 9 | (.. this -classList (toggle "open")))) 10 | 11 | (defn toggle-active [e] 12 | (this-as this 13 | (when (.. e -propertyName (includes "flex")) 14 | (.. this -classList (toggle "open-active"))))) 15 | 16 | 17 | ;; start 18 | 19 | (let [panels (.. js/document (querySelectorAll ".panel"))] 20 | (doseq [panel (array-seq panels)] 21 | (.addEventListener panel "click" toggle-open) 22 | (.addEventListener panel "transitionend" toggle-active))) 23 | -------------------------------------------------------------------------------- /05-flex-panels-image-gallery/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/README.md: -------------------------------------------------------------------------------- 1 | # 08 Ajax Type Ahead 2 | 3 | - [Housekeeping](#housekeepings) 4 | - [Quickstart](#quickstart) 5 | - [Lessons Learned](#lessons-learned) 6 | - [JS Interop](#js-interop) 7 | - [Fetch](#fetch) 8 | - [Clojure](#clojure) 9 | - [Spread Operator](#spread-operator) 10 | - [Regexes](#regexes) 11 | - [Concat V Into](#concat-v-into) 12 | 13 | ## Housekeeping 14 | 15 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) 16 | 17 | ## Quickstart 18 | 19 | Run the following comamnds from the root of the `08-ajax-type-ahead` repo 20 | 21 | **1. Build and watch the project** 22 | 23 | ```bash 24 | clj -M:dev 25 | ``` 26 | 27 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 28 | 29 | ```bash 30 | ➜ clj -h 31 | Version: 1.10.3.855 # this is the version your on 32 | ``` 33 | 34 | ## Lessons Learned 35 | 36 | This lesson brought some new pain. Some of the big challenges were 37 | 38 | 1. JS Interop - fetch 39 | 1. Clojure - regex, lazy-seqs, maps, string formatting 40 | 1. Debugging Tools 41 | 42 | ### JS Interop 43 | 44 | #### Fetch 45 | 46 | What does the fetch `api signature` look like in CLJS? 47 | 48 | ```clojure 49 | (-> (js/fetch "http://10.0.3.2:3000/" (clj->js {:method "POST"})) 50 | (.then #(.json %)) ;; warning: `.json` returns a promise that resolves to the parsed json body 51 | (.then js->clj) 52 | (.then yourhandlerhere) 53 | (.catch #(js/console.error %))) 54 | ``` 55 | 56 | One question is whether calling this the `API signature` is correct? 57 | 58 | Lets see what the above looks like when I don't use `->` which is a good examle of how to implement idiomatic cljs 59 | 60 | I was a little stumped on how to think about this one from a clojure perspective. Consider what we are tying to do here: 61 | 62 | 1. get data 63 | 2. store data is some global var (is this an atom in clojure?) 64 | 3. have other functions that can operate on this global data store? 65 | 66 | So what would this look like? 67 | 68 | ```clojure 69 | (def cities (atom nil)) 70 | 71 | (-> (js/fetch endpoint) 72 | (.then (fn [res] (reset! cities (seq (.json res)))))) 73 | ``` 74 | 75 | A great piece of advice from `noisesmith` was 76 | 77 | > in cljs you can use google closure, goog.object has things that make dealing with values a lot easier 78 | 79 | I think the nicer way would be to use `swap` 80 | 81 | ### Clojure 82 | 83 | #### Spread Operator 84 | 85 | In JS we do this: 86 | 87 | ``` 88 | a1 = [1, 2, 3] 89 | a2 = [4] 90 | a3 = [...a1, ...a2] // [1, 2, 3, 4] 91 | ``` 92 | 93 | Would be good to understand how the above works in CLJS 94 | 95 | #### Regexes 96 | 97 | If we want to dynamically look for a word, we also have to use the `re-pattern` which lets us create a pattern 98 | 99 | `(re-pattern (str "" some-string))` 100 | 101 | Something to remember here is that regexes seemed a lot more difficult when performing this excercise then they ever have and for me, I felt it was because of the syntax. It make it confusing to follow and the whole thing is confusing anyways. 102 | 103 | 2. How regex's actually work 104 | 3. debugging a filter to see what it is doing 105 | 4. lazySeq not allowing me to see WTF is in the array-seq - how to realize them. in our case, using join does the trick, but maybe explore other options 106 | 5. Not being able to iterate over a map - obvi 107 | 6. Adding html in pur strings is wicked hard - maybe a look at how to add an HTML helper like hiccup 108 | 7. What if the cities object has no vals cause the the promise has not resolved? add a timeout to illustrate 109 | 110 | ## Concat v Into 111 | 112 | > noisesmith 113 | 114 | in clojure, you could do this: 115 | 116 | if a1 is a clojure type 117 | or `(into [] cat [a1 a2])` if not 118 | 119 | or just `(concat a1 a2)` - depending on what you are trying to do 120 | 121 | concat is lazy, into is strict 122 | 123 | > val_waeselynck 124 | 125 | Either `(concat a1 a2)` - yields a lazy seq - or `(into a1 a2)` (yields a collection of the same type as `a1`) 126 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/README.org: -------------------------------------------------------------------------------- 1 | * Fetch 2 | 3 | What does the fetch `api signature` look like in cljs? 4 | 5 | #+BEGIN_SRC clojure 6 | (defn "This is clojure") 7 | #+END_SRC 8 | 9 | What's the deal with this? Lets start by understanding `->`in cljs 10 | 11 | * Concurrency v. Parallelism 12 | 13 | 14 | 15 | * Concurrency 16 | 17 | Concurrency is about correctly, safely and efficiently sharing resources. First person to get coffee, would make a pot of coffee. This can be seen as a policy we setup and we use primitives to represent this. 18 | 19 | We have 5 employees. Each of the employees can be considered a thread. Each person wants to get some coffee and there is only one coffee pot. 20 | 21 | ``` 22 | (dotimes [x 5] 23 | (.start (Thread (fn [] 24 | (Thread/sleep (rand-int 10000) 25 | (println x "Im here") 26 | (println x "I'm Grabbing coffee") 27 | @coffee 28 | (println x "sip")))))) 29 | ``` 30 | 31 | @coffee === (deref coffee) 32 | 33 | * Regexs 34 | 35 | These are a little fucked syntatically. After a lot of searching, this resource - https://cljs.github.io/api/syntax/regex - was the first to actually explain, just a little, that in CLJ there is a special syntax to make this work. 36 | 37 | Learn more about Atoms with https://purelyfunctional.tv/lesson/keeping-state-consistent-with-atoms/ video 38 | 39 | I asked the question about how to debug inside of *filter* and realized I am a fool. Here is a good answer from eggsyntax - 40 | 41 | *I’d start by calling the predicate function on some individual values, from the REPL. If you need to dig deeper than that (eg if the predicate is really complicated), it’s a great candidate for running in a debugger. 42 | 43 | eg if I’m doing `(filter even? (range 5))`, I’d start in the repl by calling `(even? 0)`, `(even? 1)`, etc, until the behavior is totally clear. (edited) 44 | 45 | Also you can definitely cause side effects from within `filter`. eg you could do `(filter #(do (println %) (even? %)) (range 5))`* 46 | 47 | If you run into an error like this: Uncaught (in promise) Error: [object Object] is not ISeqable --> consider that it means you are trying to loop over an object that - can't do that! 48 | 49 | The BIG takeaway is that you can use *do*. This would be a great time in the docs to illustrate how to debug this in different ways! 50 | 51 | There is not global regex concept in clojure or clojurescript so you should use *re-seq* over *re-matches* if this is the desired effect...kind of odd 52 | 53 | How to get a specific item from a list 54 | () 55 | 56 | How to access a property in a map (object) 57 | 58 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Type Ahead 👀 6 | 7 | 8 | 9 | 10 |
11 | 12 | 16 |
17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/resources/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | background: #ffc600; 4 | font-family: 'helvetica neue'; 5 | font-size: 20px; 6 | font-weight: 200; 7 | } 8 | *, 9 | *:before, 10 | *:after { 11 | box-sizing: inherit; 12 | } 13 | input { 14 | width: 100%; 15 | padding: 20px; 16 | } 17 | 18 | .search-form { 19 | max-width: 400px; 20 | margin: 50px auto; 21 | } 22 | 23 | input.search { 24 | margin: 0; 25 | text-align: center; 26 | outline: 0; 27 | border: 10px solid #f7f7f7; 28 | width: 120%; 29 | left: -10%; 30 | position: relative; 31 | top: 10px; 32 | z-index: 2; 33 | border-radius: 5px; 34 | font-size: 40px; 35 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19); 36 | } 37 | 38 | .suggestions { 39 | margin: 0; 40 | padding: 0; 41 | position: relative; 42 | /*perspective:20px;*/ 43 | } 44 | .suggestions li { 45 | background: white; 46 | list-style: none; 47 | border-bottom: 1px solid #d8d8d8; 48 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.14); 49 | margin: 0; 50 | padding: 20px; 51 | transition: background 0.2s; 52 | display: flex; 53 | justify-content: space-between; 54 | text-transform: capitalize; 55 | } 56 | 57 | .suggestions li:nth-child(even) { 58 | transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001); 59 | background: linear-gradient(to bottom, #ffffff 0%, #efefef 100%); 60 | } 61 | .suggestions li:nth-child(odd) { 62 | transform: perspective(100px) rotateX(-3deg) translateY(3px); 63 | background: linear-gradient(to top, #ffffff 0%, #efefef 100%); 64 | } 65 | 66 | span.population { 67 | font-size: 15px; 68 | } 69 | 70 | .hl { 71 | background: #ffc600; 72 | } 73 | 74 | a { 75 | color: black; 76 | background: rgba(0, 0, 0, 0.1); 77 | text-decoration: none; 78 | } 79 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; Globals 7 | 8 | (def cities (atom nil)) 9 | 10 | (def endpoint "https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json") 11 | 12 | (def suggestions (.querySelector js/document ".suggestions")) 13 | 14 | 15 | ;; Helpers 16 | 17 | (defn find-matches 18 | [word-to-match, cities] 19 | (filter #(re-seq (re-pattern (str "(?i)" word-to-match)) (.. % -city)) (.. cities -state))) 20 | 21 | 22 | ;; Event Handlers 23 | 24 | (defn display-matches! 25 | [e] 26 | (this-as this 27 | (let [matchArray (find-matches (.. this -value) cities) 28 | html (clojure.string/join "" (map #(str "
  • " (.. % -city ) "
  • ") matchArray))] 29 | (set! (.. suggestions -innerHTML) html)))) 30 | 31 | 32 | ;; Start 33 | 34 | (-> (js/fetch endpoint) 35 | (.then (fn [res] (.. res json))) 36 | (.then (fn [res] (reset! cities res)))) 37 | 38 | (let [search-input (.. js/document (querySelector ".search"))] 39 | (.. search-input (addEventListener "change" display-matches!)) 40 | (.. search-input (addEventListener "keyup" display-matches!))) 41 | -------------------------------------------------------------------------------- /06-ajax-type-ahead/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /07-array-cardio-2/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /07-array-cardio-2/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /07-array-cardio-2/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Array Cardio 💪💪 6 | 7 | 8 |

    Psst: have a look at the JavaScript Console 💁

    9 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /07-array-cardio-2/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /07-array-cardio-2/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | (p "---------------------------- some ---------------------------------") 8 | 9 | ;; is at least one person 19 or older? 10 | 11 | (p js/people) 12 | (p js/comments) 13 | 14 | (p (some #(> 19 (- (.. % -year) 2017)) 15 | js/people)) 16 | 17 | (p "---------------------------- every ---------------------------------") 18 | 19 | ;; is everyone 19 or older 20 | (p (every? #(> 19 (- (.. % -year) 2017)) 21 | js/people)) 22 | 23 | (p "---------------------------- find ---------------------------------") 24 | 25 | ;; find the comment with the ID of 823423 26 | 27 | ;; another way to write the below (first (filter (fn [v] (= (:id v) ??)) book-list)) 28 | 29 | (p (first (filter #(= (.. % -id) 823423) 30 | js/comments))) 31 | 32 | (p "---------------------------- findIndex ---------------------------------") 33 | 34 | ;; Find the comment with this ID - 823423 35 | 36 | (def indexOfComments (keep-indexed 37 | (fn [index item] 38 | (when (= (.. item -id) 823423) 39 | index)) js/comments)) 40 | 41 | (p (first indexOfComments)) 42 | 43 | ;; took me about two hours to find a good way to do this :) 44 | -------------------------------------------------------------------------------- /07-array-cardio-2/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /08-html5-canvas/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /08-html5-canvas/README.md: -------------------------------------------------------------------------------- 1 | # 08 HTML5 Canvas 2 | 3 | - [Housekeeping](#housekeepings) 4 | - [Quickstart](#quickstart) 5 | - [Lessons Learned](#lessons-learned) 6 | - [Functions](#functions) 7 | - [Atoms](#atoms) 8 | - [State](#state) 9 | 10 | # Housekeeping 11 | 12 | Before trying out this repo please ensure you have a cljs environment setup. See the [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) 13 | 14 | ## Quickstart 15 | 16 | Run the following comamnds from the root of the `10-html-canvas` repo 17 | 18 | **1. Build and watch the project** 19 | 20 | ```bash 21 | clj -M:dev 22 | ``` 23 | 24 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 25 | 26 | ```bash 27 | ➜ clj -h 28 | Version: 1.10.3.855 # this is the version your on 29 | ``` 30 | 31 | 32 | # Lessons Learned 33 | 34 | ## Functions 35 | 36 | I was playing with the idea that I could avoid storing DOM elements with `vars` and instead use functions which, when called, would return the required DOM elements. At this stage in my learning, this felt a little more idiomatic. 37 | 38 | For example: 39 | 40 | ``` 41 | (defn get-canvas [] 42 | (.querySelector js/document "#draw")) 43 | 44 | (get-canvas) 45 | ``` 46 | 47 | would be better than this: 48 | 49 | ``` 50 | (def canvas (.querySelector js/document "#draw")) 51 | 52 | canvas 53 | ``` 54 | 55 | Furthering to this point, how exactly to invoke a function was not clear. I believe one of the challenges was that in a language like jsvascript, you have a tail of parens `myFunction()` but in clojure, its the first item in the list. And most times, you are working with examples which does noy give a lot of time to focus on what a function invocation looks like. Example: 56 | 57 | ```clojure 58 | # works 59 | (fn [] (setDrawing true)) 60 | 61 | # does not work 62 | (fn [] setDrawing true) 63 | ``` 64 | 65 | ## Atoms 66 | 67 | This course was also the first time I used atoms. 68 | 69 | ```clojure 70 | (get @appState :isDrawing) 71 | ``` 72 | 73 | When first coming across this I did not realize that in order to access the contents of an Atom you would have to use `@` symbol. This threw me off quite a bit. I ended up figuring it out by looking at the [atom problem](http://www.lispcast.com/atom-problem) 74 | 75 | ## State 76 | 77 | It was also at this point where I realized how wonderful `Atoms` are and how we can think of them as appState - which is nice because we are building a small program, one that is often not considered stateful. This is a light intro to state. 78 | -------------------------------------------------------------------------------- /08-html5-canvas/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /08-html5-canvas/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML5 Canvas 6 | 7 | 8 | 9 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /08-html5-canvas/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /08-html5-canvas/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require [goog.string :as gstr] goog.string.format) 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; App State 7 | 8 | (def appState (atom { :isDrawing false :lastX 0 :lastY 0 :hue 0})) 9 | 10 | 11 | ;; DOM Helpers 12 | 13 | (defn get-canvas [] 14 | (.querySelector js/document "#draw")) 15 | 16 | (defn get-ctx [] 17 | (.getContext (get-canvas) "2d")) 18 | 19 | (defn get-window-innerWidth [] 20 | (.. js/window -innerWidth)) 21 | 22 | (defn get-window-innerHeight [] 23 | (.. js/window -innerHeight)) 24 | 25 | (defn toggle-drawing [b] 26 | (swap! appState assoc :isDrawing b)) 27 | 28 | 29 | ;; Event Handlers 30 | 31 | (defn draw [e] 32 | (when (get @appState :isDrawing) 33 | (do 34 | (.beginPath (get-ctx)) 35 | ;; this is interesting because if I remove this line the color does not 36 | ;; rainbow, however, when I have it hear it does - does an atom not update 37 | ;; unless it is being accessed again? 38 | (p (get @appState :hue)) 39 | (set! (.. (get-ctx) -strokeStyle) (str "hsl(" (get @appState :hue) ", 100%, 50%")) 40 | (.moveTo (get-ctx) (get @appState :lastX) (get @appState :lastY)) 41 | (.lineTo (get-ctx) (.. e -offsetX) (.. e -offsetY)) 42 | (.stroke (get-ctx)) 43 | (swap! appState assoc :lastX (.. e -offsetX)) 44 | (swap! appState assoc :lastY (.. e -offsetY)) 45 | (swap! appState update-in [:hue] inc)))) 46 | 47 | 48 | ;; Start 49 | 50 | ;; set canvas to be width and height of window 51 | (set! (.. (get-canvas) -width) (get-window-innerWidth)) 52 | (set! (.. (get-canvas) -height) (get-window-innerHeight)) 53 | 54 | ;; set drawing style of canvas 55 | (set! (.. (get-ctx) -lineJoin) "round") 56 | (set! (.. (get-ctx) -lineCap) "round") 57 | (set! (.. (get-ctx) -lineWidth) 50) 58 | 59 | 60 | (.addEventListener (get-canvas) "mousemove" draw) 61 | (.addEventListener (get-canvas) "mousedown" (fn [e] (do 62 | (toggle-drawing true) 63 | (swap! appState assoc :lastX (.. e -offsetX)) 64 | (swap! appState assoc :lastY (.. e -offsetY))))) 65 | 66 | 67 | (.addEventListener (get-canvas) "mouseup" (fn [] (toggle-drawing false))) 68 | -------------------------------------------------------------------------------- /08-html5-canvas/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /09-dev-tool-tricks/README.md: -------------------------------------------------------------------------------- 1 | # Dev Tool Tricks 2 | 3 | I will dive into this one, but it feels that the lessons learned here are more about converting the JS logs to CLJS. So instead of running though this exercise right now, here are some resources for debugging ClojureScript: 4 | 5 | - [The Power of Clojure Debugging](https://cambium.consulting/articles/2018/2/8/the-power-of-clojure-debugging) 6 | - [Debugging ClojureScript](https://medium.com/@roman01la/debugging-clojurescript-6e4d903e831) 7 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hold Shift to Check Multiple Checkboxes 6 | 7 | 8 | 56 | 62 |
    63 |
    64 | 65 |

    This is an inbox layout.

    66 |
    67 |
    68 | 69 |

    Check one item

    70 |
    71 |
    72 | 73 |

    Hold down your Shift key

    74 |
    75 |
    76 | 77 |

    Check a lower item

    78 |
    79 |
    80 | 81 |

    Everything inbetween should also be set to checked

    82 |
    83 |
    84 | 85 |

    Try do it with out any libraries

    86 |
    87 |
    88 | 89 |

    Just regular JavaScript

    90 |
    91 |
    92 | 93 |

    Good Luck!

    94 |
    95 |
    96 | 97 |

    Don't forget to tweet your result!

    98 |
    99 |
    100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | 5 | (def app-state (atom { :last-checked nil})) 6 | 7 | 8 | ;; DOM helper 9 | 10 | (defn get-checkboxes [] 11 | (.. js/document (querySelectorAll ".inbox input[type=\"checkbox\"]"))) 12 | 13 | 14 | ;; Event Handler 15 | 16 | (defn handle-check [e] 17 | (this-as this 18 | (when (and (.. e -shiftKey) (.. this -checked)) 19 | (let [select-checkbox? (fn [in-between? checkbox] 20 | (cond 21 | ;; 1. checkbox user shift-clicked 22 | (identical? checkbox this) 23 | (do 24 | (set! (.. checkbox -checked) true) 25 | (not in-between?)) 26 | 27 | ;; 2. checkbox last checked 28 | (identical? (get @app-state :last-checked) checkbox) 29 | (do 30 | (set! (.. checkbox -checked) true) 31 | (not in-between?)) 32 | 33 | ;; 3. in between the last checked and shift-clicked checkbox 34 | (true? in-between?) 35 | (do 36 | (set! (.. checkbox -checked) true) 37 | true) 38 | 39 | ;; 4. outside of last checked and shift-clicked checkbox 40 | :else false))] 41 | (reduce select-checkbox? false (array-seq (get-checkboxes))))) 42 | (swap! app-state assoc :last-checked this))) 43 | 44 | ;; start 45 | 46 | (doseq [checkbox (array-seq (get-checkboxes))] 47 | (.addEventListener checkbox "click" handle-check)) 48 | -------------------------------------------------------------------------------- /10-check-multiple-checkboxes/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /11-html5-video-player/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /11-html5-video-player/README.md: -------------------------------------------------------------------------------- 1 | # 11 Custom HTML5 Video Player 2 | 3 | Customize the HTML5 Video Player with CLJS. 4 | 5 | - [Quickstart](#quickstart) 6 | - [Learnings](#Learnings) 7 | - [Ternary v. If](#ternary-v-if) 8 | - [Imperative v. Functional](#imperative-v-functional) 9 | - [JS Hot Reloading](#js-hot-reloading) 10 | 11 | ## Quickstart 12 | 13 | ```bash 14 | clj -M:dev 15 | ``` 16 | 17 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 18 | 19 | ```bash 20 | ➜ clj -h 21 | Version: 1.10.3.855 # this is the version your on 22 | ``` 23 | 24 | Visit site at `http://localhost:3000` 25 | 26 | ## Learnings 27 | 28 | ### Ternary V If 29 | 30 | In Clojure, the idiomatic way of getting this functionality is an If statement. 31 | 32 | ## Imperative v Functional 33 | 34 | I was required to write this: 35 | 36 | ```clojure 37 | (defn toggle-play [] 38 | (if (.-paused video) 39 | (.play video) 40 | (.pause video))) 41 | ``` 42 | 43 | And as I was looking at it I could not help but think that this can't be the best way. The issue I see is that this function: 44 | 45 | - Accesses stuff from the global namespace - is there a way to not do this, or is it okay? Not everything has to be reusable, but this just does not feel functional. 46 | 47 | This brings up some bigger questions like: 48 | 49 | - When should I be using side effects and how can I know if I am using them too often or not enough? 50 | - When is something just not functional? 51 | - When should something be pure? 52 | - Are we supposed to be separating functions in some way that have side effects? 53 | 54 | Part of the answer to this is knowing that not everything has to be reusable and that you are building programs so there are going to be domain specific things as part of this. If this is the case, we can start to make it data structure oriented and then the functions are acting on these data structures 55 | 56 | One better way for the above might be to just 57 | 58 | ```clojure 59 | (map paused? ("play", "pause")) ;;returns true or false and than based on this we toggle the play or not 60 | ``` 61 | 62 | The above seems nicer, aside from the fact that map is not correct, because we now have a function that only cares if it finds a true or false in our domain of the video player app. I was inspired to do the above in the [predicates](https://github.com/mynomoto/lt-clojure-tutorial/blob/master/conditionals.clj) section of the above 63 | 64 | ### JS Hot Reloading 65 | 66 | If you are running this app and live coding it you are going to see that hot reloading is not really working. This is because to make something hot reloadable you have to write your code to be hot reloadable. 67 | 68 | # Musings 69 | 70 | I am keeping these around because I had issues with these so others might as well and they could be good topics to discuss in the future. 71 | 72 | - What do people like when it comes to `this-as`? From a style convention or a when to use perspective? 73 | - At which point do you make the anonymous function its own named function? 74 | - when grabbing DOM elements, which one would be preferred `defn` or `def` 75 | -------------------------------------------------------------------------------- /11-html5-video-player/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /11-html5-video-player/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML Video Player 6 | 7 | 8 | 9 | 10 |
    11 | 12 | 13 |
    14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 |
    23 |
    24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /11-html5-video-player/resources/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, 6 | *:before, 7 | *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | display: flex; 15 | background: #7a419b; 16 | min-height: 100vh; 17 | background: linear-gradient(135deg, #7c1599 0%, #921099 48%, #7e4ae8 100%); 18 | background-size: cover; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | .player { 24 | max-width: 750px; 25 | border: 5px solid rgba(0, 0, 0, 0.2); 26 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); 27 | position: relative; 28 | font-size: 0; 29 | overflow: hidden; 30 | } 31 | 32 | /* This css is only applied when fullscreen is active. */ 33 | .player:fullscreen { 34 | max-width: none; 35 | width: 100%; 36 | } 37 | 38 | .player:-webkit-full-screen { 39 | max-width: none; 40 | width: 100%; 41 | } 42 | 43 | .player__video { 44 | width: 100%; 45 | } 46 | 47 | .player__button { 48 | background: none; 49 | border: 0; 50 | line-height: 1; 51 | color: white; 52 | text-align: center; 53 | outline: 0; 54 | padding: 0; 55 | cursor: pointer; 56 | max-width: 50px; 57 | } 58 | 59 | .player__button:focus { 60 | border-color: #ffc600; 61 | } 62 | 63 | .player__slider { 64 | width: 10px; 65 | height: 30px; 66 | } 67 | 68 | .player__controls { 69 | display: flex; 70 | position: absolute; 71 | bottom: 0; 72 | width: 100%; 73 | transform: translateY(100%) translateY(-5px); 74 | transition: all 0.3s; 75 | flex-wrap: wrap; 76 | background: rgba(0, 0, 0, 0.1); 77 | } 78 | 79 | .player:hover .player__controls { 80 | transform: translateY(0); 81 | } 82 | 83 | .player:hover .progress { 84 | height: 15px; 85 | } 86 | 87 | .player__controls > * { 88 | flex: 1; 89 | } 90 | 91 | .progress { 92 | flex: 10; 93 | position: relative; 94 | display: flex; 95 | flex-basis: 100%; 96 | height: 5px; 97 | transition: height 0.3s; 98 | background: rgba(0, 0, 0, 0.5); 99 | cursor: ew-resize; 100 | } 101 | 102 | .progress__filled { 103 | width: 50%; 104 | background: #ffc600; 105 | flex: 0; 106 | flex-basis: 50%; 107 | } 108 | 109 | /* unholy css to style input type="range" */ 110 | 111 | input[type='range'] { 112 | -webkit-appearance: none; 113 | background: transparent; 114 | width: 100%; 115 | margin: 0 5px; 116 | } 117 | input[type='range']:focus { 118 | outline: none; 119 | } 120 | input[type='range']::-webkit-slider-runnable-track { 121 | width: 100%; 122 | height: 8.4px; 123 | cursor: pointer; 124 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); 125 | background: rgba(255, 255, 255, 0.8); 126 | border-radius: 1.3px; 127 | border: 0.2px solid rgba(1, 1, 1, 0); 128 | } 129 | input[type='range']::-webkit-slider-thumb { 130 | height: 15px; 131 | width: 15px; 132 | border-radius: 50px; 133 | background: #ffc600; 134 | cursor: pointer; 135 | -webkit-appearance: none; 136 | margin-top: -3.5px; 137 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); 138 | } 139 | input[type='range']:focus::-webkit-slider-runnable-track { 140 | background: #bada55; 141 | } 142 | input[type='range']::-moz-range-track { 143 | width: 100%; 144 | height: 8.4px; 145 | cursor: pointer; 146 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); 147 | background: #ffffff; 148 | border-radius: 1.3px; 149 | border: 0.2px solid rgba(1, 1, 1, 0); 150 | } 151 | input[type='range']::-moz-range-thumb { 152 | box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); 153 | height: 15px; 154 | width: 15px; 155 | border-radius: 50px; 156 | background: #ffc600; 157 | cursor: pointer; 158 | } 159 | -------------------------------------------------------------------------------- /11-html5-video-player/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /11-html5-video-player/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | ;; constants 7 | 8 | (def player (.querySelector js/document ".player")) 9 | 10 | (def video (.querySelector player ".viewer")) 11 | 12 | (def toggle (.querySelector player ".toggle")) 13 | 14 | (def progress (.querySelector player ".progress")) 15 | 16 | (def progress-bar (.querySelector player ".progress__filled")) 17 | 18 | (def skip-buttons (.querySelectorAll player "[data-skip]")) 19 | 20 | (def ranges (.querySelectorAll player ".player__slider")) 21 | 22 | ;; state 23 | 24 | (def app-state (atom {:mouse-down? false})) 25 | 26 | ;; protocols 27 | 28 | (extend-type js/NodeList 29 | ISeqable 30 | (-seq [node-list] (array-seq node-list))) 31 | 32 | 33 | ;; event functions 34 | 35 | (defn toggle-mouse-down [] 36 | (let [current-mouse-down (get @app-state :mouse-down?)] 37 | (swap! app-state assoc :mouse-down? (not current-mouse-down)))) 38 | 39 | 40 | (defn toggle-play [e] 41 | (if (.-paused video) 42 | (.play video) 43 | (.pause video))) 44 | 45 | 46 | (defn toggle-play-btn-icon [e] 47 | (this-as this 48 | (let [icon (if (.-paused this) "►" "❚ ❚")] 49 | (set! (.-textContent toggle) icon)))) 50 | 51 | 52 | (defn skip [e] 53 | (this-as this 54 | (let [skip-count (.. this -dataset -skip) 55 | skip-count-str (js/parseFloat skip-count) 56 | next-skip-count (+ (.-currentTime video) skip-count-str)] 57 | (set! (.-currentTime video) next-skip-count)))) 58 | 59 | 60 | (defn handle-range-update [e] 61 | (this-as this 62 | (let [volume-slider? (= (.-name this) "volume") 63 | playback-rate? (= (.-name this) "playbackRate")] 64 | (when volume-slider? 65 | (set! (.-volume video) (.-value this))) 66 | (when playback-rate? 67 | (set! (.-playbackRate this) (.-value this)))))) 68 | 69 | 70 | (defn handle-progress [e] 71 | (let [percent (* (/ (.-currentTime video) (.-duration video)) 100) 72 | next-flex-basis (str percent "%")] 73 | (p next-flex-basis) 74 | (set! (.. progress-bar -style -flexBasis) next-flex-basis))) 75 | 76 | 77 | (defn scrub [e] 78 | (let [scrub-time (* (/ (.-offsetX e) (.-offsetWidth progress)) (.-duration video))] 79 | (set! (.-currentTime video) scrub-time))) 80 | 81 | 82 | ;; event listeners 83 | 84 | (.addEventListener video "click" toggle-play) 85 | 86 | (.addEventListener video "play" toggle-play-btn-icon) 87 | 88 | (.addEventListener video "pause" toggle-play-btn-icon) 89 | 90 | (.addEventListener video "timeupdate" handle-progress) 91 | 92 | (.addEventListener progress "click" scrub) 93 | 94 | (.addEventListener progress "mousemove" #(and (get @app-state :mouse-down?) scrub)) 95 | 96 | (.addEventListener progress "mousedown" toggle-mouse-down) 97 | 98 | (.addEventListener progress "mouseup" toggle-mouse-down) 99 | 100 | (.addEventListener toggle "click" toggle-play) 101 | 102 | (doseq [btn skip-buttons] 103 | (.addEventListener btn "click" skip)) 104 | 105 | (doseq [range ranges] 106 | (.addEventListener range "change" handle-range-update)) 107 | 108 | (doseq [range ranges] 109 | (.addEventListener range "mousemove" handle-range-update)) 110 | -------------------------------------------------------------------------------- /11-html5-video-player/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /12-key-sequence-detection/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /12-key-sequence-detection/README.md: -------------------------------------------------------------------------------- 1 | # 12 Key Sequence Detection 2 | 3 | In this excercise you will review the following concepts 4 | 5 | - [Lessons](#lessons) 6 | - [Slice](#slice) 7 | - [Queue](#queue) 8 | - [Code Cleanup](#code-cleanup) 9 | 10 | # Requirements 11 | 12 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 13 | 14 | # Quickstart 15 | 16 | Run the following comamnds from the root of the `14-key-sequence-detection` 17 | 18 | **1. Run the projcet** 19 | 20 | ```bash 21 | clj -M:dev 22 | ``` 23 | 24 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 25 | 26 | ```bash 27 | ➜ clj -h 28 | Version: 1.10.3.855 # this is the version your on 29 | ``` 30 | 31 | **2. Visit the app** 32 | 33 | http://localhost:3000/ 34 | 35 | You are going to see a blank screen. Try typing in the word `secret` :) 36 | 37 | # Lessons 38 | 39 | Its all about the Data with clojure. As a result though, you will be asked to think more about the data structures you choose in clojure as opposed to JS which until recently, only really had the `Object` and `Array` as options. Thus, Figuring out how you are going to interact with the data is important. 40 | 41 | This leads to the lesson which is to really question everything you write when you write clojure/script. Especially if coming from the JS world. It really is a different way of thinking. 42 | 43 | ## Slice 44 | 45 | The primary goal of this excerise it do the following: 46 | 47 | > When the user types in the word `secret` we trigger some event 48 | 49 | The solution provided by wes in JavaScript 30 is to use `splice`. However, when you try to find the equivalent of `splice` you will notice it does not exist. One reason is because splice is going to mutate the original data structure, so your not going to find an equivalent of `splice`. What we would be looking for is `slice`. In Clojure you are going to find some gnarly solutions to do a `slice`, unless you are using a vector, which is meant to have `slice` like operations done on it, and in this case the method you are looking for is `subvec` as seen below 50 | 51 | ```clojure 52 | (swap! keys-pressed subvec keys (- (count keys) 6) (count keys)) 53 | ``` 54 | 55 | This is totally fine and works. However, when we consider what we are trying to do, which is leave off the left most item when our data structure has more than 6 items, we might actually be more interested in a `queue`. 56 | 57 | ## Queue 58 | 59 | In my solution I opted to use a `queue` instead of a `vector` or a `list`. The reason I chose a `queue` is because we are leaving off the left most items after a minimum of 6 key strokes. Thus, the above `subvec` is replaced with the following: 60 | 61 | ```clojure 62 | (swap! keys-pressed pop keys-pressed)) 63 | ``` 64 | 65 | The fact that this code is less verbose should not be the big take away, the big takeaway is that this is the _ideal_ data structure for the job. 66 | 67 | This does not mean that using a vector is wrong, especially given that in this situation, your vector will never really contain a performance hindering number of items. 68 | 69 | For more details on this, please see [Joy of Clojure](https://www.manning.com/books/the-joy-of-clojure-second-edition) section 5.2.7 70 | 71 | ## Code Cleanup 72 | 73 | After I finished the first draft of the code, I took a few steps to make this cleaner: 74 | 75 | **Atom** 76 | 77 | Instead of doing this: 78 | 79 | ```clojure 80 | (def app-state (atom {:keys-pressed #queue []})) 81 | 82 | ;; get 83 | (get @app-state :keys-pressed)) 84 | ``` 85 | 86 | I changed it to this: 87 | 88 | ```clojure 89 | (def keys-pressed (atom #queue [])) 90 | 91 | ;; get 92 | @app-state 93 | ``` 94 | 95 | The benefit was that getting the value of `keys-pressed` becomes less code. 96 | 97 | **Higher Order Function** 98 | 99 | My next question was whether I could make my `keyup-handler` function align to more of the principles of functional programming. A route that I chose was to turn `keyup-handler` into a `higher order function`. In this case, it is a function that returns a function. This is a very simple HOF. 100 | 101 | ```clojure 102 | (defn create-hof 103 | [name] 104 | (fn [] 105 | (p name))) 106 | ``` 107 | 108 | In this example, calling `(create-hof "Terry")` would return another function that when called would console log "terry". In the case of our function, we created a function that would return the event handler, looking like this: 109 | 110 | ```clojure 111 | (create-keyup-handler 112 | [secret f] 113 | (fn [e])) 114 | ``` 115 | 116 | This helps because now we have an event handler where the secret and the event are customizable. We are still working with an atom, which could be affected by other functions, so this is not a truly pure function. 117 | 118 | **naming conventions** 119 | 120 | You could use `set` or `create` to name the function, from conversations on clojurians, one approach is to reserve `set` or `register` for functions which call the `addEventListener`. In our case, we are creating a function, so we can prefix with the word `create`. 121 | 122 | # Resources 123 | 124 | - [Atom v. Refs](http://tarynsauer.tumblr.com/post/77631451200/clojure-should-i-use-atoms-or-refs) 125 | https://learnxinyminutes.com/docs/clojure/ 126 | - [Understanding persistent vectors](http://hypirion.com/musings/understanding-persistent-vector-pt-1) 127 | - [Why clojure goes fast](http://clojure-goes-fast.com/) 128 | -------------------------------------------------------------------------------- /12-key-sequence-detection/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /12-key-sequence-detection/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Key Detection 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /12-key-sequence-detection/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /12-key-sequence-detection/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | ;; App state 7 | 8 | (def keys-pressed (atom #queue [])) 9 | 10 | 11 | ;; Event handlers 12 | 13 | (defn create-keyup-handler 14 | [secret f] 15 | (fn 16 | [e] 17 | ;; add recently pressed key to our queue 18 | (swap! keys-pressed conj (.-key e)) 19 | 20 | ;; store as many items as the length of the secret 21 | (when (> (count @keys-pressed) (count secret)) 22 | (swap! keys-pressed pop keys-pressed)) 23 | 24 | ;; check if user typed secret 25 | (when (and (= (count @keys-pressed) (count secret)) 26 | (= (apply str @keys-pressed) secret)) 27 | (f)))) 28 | 29 | 30 | ;; Event listeners 31 | 32 | (.addEventListener js/window "keyup" (create-keyup-handler "secret" (.-cornify_add js/window))) 33 | -------------------------------------------------------------------------------- /12-key-sequence-detection/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/README.md: -------------------------------------------------------------------------------- 1 | # 13 Slide in on scroll 2 | 3 | I found that this lesson did not contain anything new, but it did build upon concepts explored earlier 4 | 5 | - [Higher Order Functions](#higher-order-function) 6 | - [doseq](doseq) 7 | 8 | # Requirements 9 | 10 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 11 | 12 | # Quickstart 13 | 14 | Run the following comamnds from the root of the `15-slide-in-on-scroll` 15 | 16 | **1. Run the projcet** 17 | 18 | ```bash 19 | clj -M:dev 20 | ``` 21 | 22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 23 | 24 | ```bash 25 | ➜ clj -h 26 | Version: 1.10.3.855 # this is the version your on 27 | ``` 28 | 29 | 30 | **2. Visit the app** 31 | 32 | http://localhost:3000/ 33 | 34 | ## Higher Order Functions 35 | 36 | In this case we are using the global function `debounce` and passing our `check-slide` function to it. 37 | 38 | ## doseq 39 | 40 | We are not modifying the collection, we are only getting side effects. `for` can also be used for side effects, but it is going to return another list and this is not what we want to happen, so we use `doseq`. `for` is what's known as a `list comprehension`. 41 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | ;; Constants 8 | 9 | (def slide-images (.querySelectorAll js/document ".slide-in")) 10 | 11 | 12 | ;; Protocols 13 | 14 | (extend-type js/NodeList 15 | ISeqable 16 | (-seq [node-list] (array-seq node-list))) 17 | 18 | 19 | ;; Event handlers 20 | 21 | (defn check-slide 22 | [e] 23 | (doseq [image slide-images] 24 | (let [scroll-y (.-scrollY js/window) 25 | win-inner-height (.-innerHeight js/window) 26 | image-height (.-height image) 27 | image-offsetTop (.-offsetTop image) 28 | slide-in-at (- (+ scroll-y win-inner-height) (/ image-height 2)) 29 | image-bottom (+ image-offsetTop image-height) 30 | half-shown? (> slide-in-at image-offsetTop) 31 | not-scrolled-past? (< scroll-y image-bottom)] 32 | (if (and half-shown? not-scrolled-past?) 33 | (.. image -classList (add "active")) 34 | (.. image -classList (remove "active")))))) 35 | 36 | 37 | ;; Event Listeners 38 | 39 | (.addEventListener js/window "scroll" (.debounce js/window check-slide)) 40 | -------------------------------------------------------------------------------- /13-slide-in-on-scroll/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /14-object-and-arrays/README.md: -------------------------------------------------------------------------------- 1 | # Object and Array 2 | 3 | Wes took a look at `assignment v. reference` in JavaScript for this lesson. I used this lesson to explore the equivalent concept in CLJ/S: 4 | 5 | 0. [Terminology](#terminology) 6 | 1. [Symbols](#symbols) 7 | 2. [Vars](#vars) 8 | 3. [Assignment vs. Binding](#assignment-vs-binding) 9 | 10 | # Lesson 11 | 12 | I work as a developer professionally and I found this lesson refreshing because it can be easy to forget how these seemingly "basic" concepts are difficult for professional and new developers alike to fully grasp. This being said, these are notes that helped me sort this out. Consider the challenge that is explaining the difference between a **key**, **Symbol** and **Var**. I believe a core challenge here is that someone new is looking at them they would immediatley think they are all just **Strings** With this said, this lesson is not done and I will likely revisit this several more times to improve and clarify where possible, but for now, I feel it is a good start. 13 | 14 | 15 | # Resources 16 | 17 | Getting into the nitty-gritty's of all this can be very challenging. What I have provided below is a summation of my understanding of these things. For stronger technical explainations see the following resources: 18 | 19 | * https://clojure.org/reference/vars 20 | * http://fasttrackclojure.blogspot.ca/2010/10/what-no-variables-in-clojure.html 21 | * https://8thlight.com/blog/aaron-lahey/2016/07/20/relationship-between-clojure-functions-symbols-vars-namespaces.html 22 | 23 | # Terminology 24 | 25 | These are the terms used in Clojure. They are not interchangeable, rather they each refer to one clearly defined idea. The spelling is important. If I get the spelling wrong in this document, I likely messed up. 26 | 27 | * Var 28 | * Symbol 29 | * Unqualified Symbol 30 | * Data Type 31 | * resolved Symbol 32 | * value 33 | * root binding 34 | * dynamic thread-local binding 35 | * unbound 36 | * local scope 37 | * global scope 38 | * scope 39 | 40 | # Vars 41 | 42 | This is how you declare a **Var** in Clojure: 43 | 44 | ```clojure 45 | (def my-name "Jerald") 46 | 47 | ;; #'user/my-name 48 | ``` 49 | 50 | * A **Var** is a mutable container 51 | * A **Var** declared with `def` is going to always become global in your namespace 52 | * A **Var** references its _binding_ (_value_). In the above, `my-name` (_var_) refernces `"Jerald"` (_binding_) 53 | * A **Var** can have two types of binding: _root binding_ and _dynamic thread-local binding_ 54 | * A **Var** can be of several sub-types: _static_, _dynamic_, _free_ 55 | 56 | > The root binding 57 | 58 | All **Vars** and their subtypes have a **root binding**. A root binding is the initial value associated with your variable. For example: 59 | 60 | ```clojure 61 | (def name "Thomas") 62 | ``` 63 | 64 | In the above, "Thomas" is the **root binding**. Having said this, you do not need to bind a **Var** with a `root value`. Consider; 65 | 66 | ```clojure 67 | (def name) 68 | ``` 69 | 70 | In the above example, the **Var** above was not bound to a `value` when it was created, so we consider this **unbound**. 71 | 72 | > Var search order 73 | 74 | 75 | We will look in **local scope** first and than move to the **global scope** 76 | 77 | > Re-bind Vars 78 | 79 | ```clojure 80 | (def name "Thomas") 81 | 82 | (def name "Kate") 83 | ``` 84 | 85 | The variable `"name"` is not destroyed and re-created, but rather the **Var** is re-bound to `"Kate"`. 86 | 87 | 88 | # Symbols 89 | 90 | A **Symbol** is [Data Type](https://clojure.org/reference/data_structures). Here are some examples of **Symbols**: 91 | 92 | ```clojure 93 | (def my-name "Jerald") 94 | 95 | (+ 1 2) 96 | ``` 97 | 98 | In the above, `+` and `my-name` are **Symbols**. Comparatively, `1` and `2` are **Numbers** which are a different kind of **Data Type**. This could be confusing because in above section we said that `my-name` is a **Var**. The truth is that the reaility is a little more complex and for most of your development process, does not really matter, but keep the following in mind: 99 | 100 | * `my-name` is a **Symbol** which references a **Var** 101 | * When we reference a **Symbol** the symbol knows where to find a **Var** and the **Var** is going to return it's binding. 102 | 103 | This distinction is going to help when / if you really need to dive into CLJ/S code. For a great overview of this, please see [this article](https://8thlight.com/blog/aaron-lahey/2016/07/20/relationship-between-clojure-functions-symbols-vars-namespaces.html) 104 | 105 | 106 | # Assignment vs Binding 107 | 108 | When we talk about variables in clojure we do not say **assigning**, we actually say **binding**. I like to think that one of the key differences is how assigning vs. binding looks. In JS, you would do this to create a variable: 109 | 110 | ```javascript 111 | var myName = "Jay" 112 | ``` 113 | 114 | In the above example, the `=` is an example of an [assignment operator](https://www.w3schools.com/js/js_assignment.asp). Clojure does not have assignment operators as you can see in the way we declare a **Var**. 115 | 116 | ```clojure 117 | (def my-name "Jay") 118 | ``` 119 | 120 | This seems to be the only way to see the differene in syntax. When it comes to Clojure internals, there is likely other differences, but for now, the takeaway is we don't assign in CLJ/S we bind. 121 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/README.md: -------------------------------------------------------------------------------- 1 | # 15 Local Storage 2 | 3 | For this excercise, I focused on the JS interop aspects of CLJS. By this I mean that I did not use immutable datastructures in favor of things like `array.push`. The goal was to see how this would feel. With this said, here are some of the larger topics touched on: 4 | 5 | - [apply](#apply) 6 | - [map-indexed](#map-indexed) 7 | - [how to not do HTML in CLJS](#how-to-not-do-html) 8 | 9 | # Requirements 10 | 11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 12 | 13 | # Quickstart 14 | 15 | Run the following comamnds from the root of the `17-localstorage-and-event-delegation` 16 | 17 | **1. Run the projcet** 18 | 19 | ```bash 20 | clj -M:dev 21 | ``` 22 | 23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 24 | 25 | ```bash 26 | ➜ clj -h 27 | Version: 1.10.3.855 # this is the version your on 28 | ``` 29 | 30 | 31 | **2. Visit the app** 32 | 33 | http://localhost:3000/ 34 | 35 | # Apply 36 | 37 | Wes used `array.join('')` in the video series. There is nothing quite like this in clojurescript. We can do something similar using reduce or apply. I opted to use apply because I have not had the opportunity thus far. This is what my code looked like 38 | 39 | ```clojure 40 | (apply str (map-indexed #(make-plate-html %1 %2) plates)) 41 | ``` 42 | 43 | Lets break the above down in the order they are evaluated so its a little easier to grasp: 44 | 45 | ```clojure 46 | ;; map over each item in plates (array) and for each item return an HTML string 47 | (map #(make-plate-html (.-text %1)) plates)) 48 | 49 | ;; the above returns something like this - I am writing HTML as a placeholder for 50 | ;; the list item which is actually in there 51 | ["HTML" "HTML" "HTML"] 52 | 53 | ;; think: apply the function str over each element which is the equivalent of 54 | ;; writing ( str "HTML" "HTML" "HTML") 55 | (apply str ["HTML" "HTML" "HTML"]) 56 | ``` 57 | 58 | # map-indexed 59 | 60 | I used `map-indexed` because I needed to know the current index of the current element I was mapping over. JS provides this natively with the map function, but in CLJ you have to explicitly use `map-indexed`. 61 | 62 | # How to not do HTML 63 | 64 | You will notice in the `make-plate-html` function I have a big-ass string concatenation fiesta going on. I would strongly suggest never doing this like I have in a production app. This code is difficult to maintain, hard to read, and likely a security risk. I did this as I have just complete the excercise without introducing a third party library. Having said this, in a production app I would just use something like [hiccup](https://github.com/weavejester/hiccup) to handle this in a smart way. 65 | 66 | # Resources 67 | 68 | http://www.spacjer.com/blog/2014/09/12/clojurescript-javascript-interop/ 69 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LocalStorage 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 |
    17 |

    LOCAL TAPAS

    18 |

    19 | 22 |
    23 | 24 | 25 |
    26 |
    27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/resources/style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | box-sizing: border-box; 4 | background:url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat; 5 | background-size:cover; 6 | min-height:100vh; 7 | display:flex; 8 | justify-content: center; 9 | align-items: center; 10 | text-align: center; 11 | font-family: Futura,"Trebuchet MS",Arial,sans-serif 12 | } 13 | *, *:before, *:after {box-sizing: inherit; } 14 | 15 | svg { 16 | fill:white; 17 | background: rgba(0,0,0,0.1); 18 | padding: 20px; 19 | border-radius: 50%; 20 | width:200px; 21 | margin-bottom: 50px; 22 | } 23 | 24 | .wrapper { 25 | padding: 20px; 26 | max-width: 350px; 27 | background: rgba(255,255,255,0.95); 28 | box-shadow: 0 0 0 10px rgba(0,0,0,0.1); 29 | } 30 | 31 | h2 { 32 | text-align: center; 33 | margin: 0; 34 | font-weight: 200; 35 | } 36 | 37 | .plates { 38 | margin: 0; 39 | padding: 0; 40 | text-align: left; 41 | list-style: none; 42 | } 43 | 44 | .plates li { 45 | border-bottom: 1px solid rgba(0,0,0,0.2); 46 | padding: 10px 0; 47 | font-weight: 100; 48 | display: flex; 49 | } 50 | 51 | .plates label { 52 | flex:1; 53 | cursor: pointer; 54 | 55 | } 56 | 57 | .plates input { 58 | display: none; 59 | } 60 | 61 | .plates input + label:before { 62 | content: '⬜️'; 63 | margin-right: 10px; 64 | } 65 | 66 | .plates input:checked + label:before { 67 | content: '🌮'; 68 | } 69 | 70 | .add-items { 71 | margin-top: 20px; 72 | } 73 | 74 | .add-items input { 75 | padding:10px; 76 | outline:0; 77 | border:1px solid rgba(0,0,0,0.1); 78 | } 79 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | ;; Actions 8 | 9 | (defn create-tapa 10 | [text done?] 11 | #js {:text text :done done?}) 12 | 13 | 14 | ;; Helpers 15 | 16 | (defn make-plate-html 17 | "Builds an HTML string. The HTML is a list item which contains an input and 18 | a label tag" 19 | [index action] 20 | (str "
  • " 21 | "" 22 | "" 23 | "
  • ")) 24 | 25 | (defn make-plate-list 26 | [plates] 27 | (apply str (map-indexed #(make-plate-html %1 %2) plates))) 28 | 29 | (defn save-items 30 | "save a food item in the browsers local storage" 31 | [items] 32 | (.setItem js/localStorage "items" items)) 33 | 34 | (defn update-items-list 35 | "Update the innerHTML of the items list" 36 | [prev-list next-list] 37 | (set! (.-innerHTML prev-list) (make-plate-list next-list))) 38 | 39 | (defn update-item-done-state 40 | "Toggle the done state of an item" 41 | [item] 42 | (set! (.-done item) (not (.-done item)))) 43 | 44 | 45 | ;; Event handlers 46 | 47 | (defn add-item 48 | [e] 49 | (.preventDefault e) 50 | 51 | (this-as this 52 | (let [text (.. this (querySelector "[name=item]") -value) 53 | item (create-tapa text false)] 54 | (.push js/items item) 55 | (update-items-list js/itemsList js/items) 56 | (save-items (.stringify js/JSON js/items)) 57 | (.reset this)))) 58 | 59 | (defn toggle-done 60 | [e] 61 | (when (.. e -target (matches "input")) 62 | (let [element (.-target e) 63 | index (.. element -dataset -index) 64 | item (aget js/items index)] 65 | (update-item-done-state item) 66 | (save-items (.stringify js/JSON js/items)) 67 | (update-items-list js/itemsList js/items)))) 68 | 69 | 70 | ;; Event listeners 71 | 72 | (.addEventListener js/addItems "submit" add-item) 73 | 74 | (.addEventListener js/itemsList "click" toggle-done) 75 | 76 | 77 | ;; Populate the list of items on page load 78 | 79 | (update-items-list js/itemsList js/items) 80 | -------------------------------------------------------------------------------- /15-localstorage-and-event-delegation/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/README.md: -------------------------------------------------------------------------------- 1 | # 16 Mouse Move 2 | 3 | This lesson presented a good opportunity to use `Math` and challenge the imperative approach to programming with the functional approach. 4 | 5 | - [Math](#math) 6 | - [Functional Thinking](#functional-thinking) 7 | - [Destructuring Clojurscript](#destructuring-javascript) 8 | 9 | # Requirements 10 | 11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 12 | 13 | # Quickstart 14 | 15 | Run the following comamnds from the root of the `18-mousemove-and-text-shadows` 16 | 17 | **1. Run the projcet** 18 | 19 | ```bash 20 | clj -M:dev 21 | ``` 22 | 23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 24 | 25 | ```bash 26 | ➜ clj -h 27 | Version: 1.10.3.855 # this is the version your on 28 | ``` 29 | 30 | 31 | **2. Visit the app** 32 | 33 | http://localhost:9000 34 | 35 | # Math 36 | 37 | `Math` is short for `java.lang.Math` which is one of the Java classes automatically imported into your namespace for you by Clojure. This is the reason you can access `Math` in any of your namespaces by just using something like `(Math/round 8.837372)`. You will use `Math` when you need functions like `round`, `abs`, `exp`. There is a lot more to this, so I recommend taking a look at [Clojure in Action - pg 11](https://www.manning.com/books/clojure-in-action-second-edition). 38 | 39 | If you want to research this more, this falls into the **Java Interop** category. 40 | 41 | # Functional Thinking 42 | 43 | This lesson introduces an opportunity to think like a functional programmer. Wes writes his code like this: 44 | 45 | ```javascript 46 | function moveShadow(e) { 47 | let x = e.target.offsetHeight; 48 | let y = e.target.offsetWidth; 49 | 50 | if (condition) { 51 | x += 10; //update value 52 | y += 15; // update value 53 | } 54 | 55 | doSomethingWithSideEffects(x, y); 56 | } 57 | ``` 58 | 59 | In the above we have local mutable state, or state that changes within the function. This is an imperative approach. There is nothing wrong with this for imperative languages and you can do something like this in Clojure, but clojure will fight you. The result will be code that is confusing and unmaintainable. So rather than imperative, we can try a more functional approach. 60 | 61 | A functional approach could be like this: 62 | 63 | ```clojure 64 | (defn get-xy 65 | [e this] 66 | (let [x (.-offsetX e) 67 | y (.-offsetY e)] 68 | (if (not= this (.-target e)) 69 | [(+ x (.. e -target -offsetLeft)) (+ y (.. e -target -offsetTop))] 70 | [x y]))) 71 | ``` 72 | 73 | Here is what the above looks like in JS if it makes it a little clearer 74 | 75 | ```javascript 76 | function getXY (e this) { 77 | const { offsetX: x, offsetY: y } = e; 78 | 79 | if ( this !== e.target ) { 80 | const modx = x + e.target.offsetLeft 81 | const mody = y + e.target.offsetTop 82 | return [modx, mody] 83 | } 84 | 85 | return [x, y] 86 | } 87 | ``` 88 | 89 | `get-xy` is the rewrite of the variable re-assignment we see in the JS solution. In this example, we choose to not set variables that are re-assigned, but create a function that returns the original `x and y` or the modified `x and y`. We can now use the above function inside of our `shadow-move` function. 90 | 91 | # Destructuring Clojurscript 92 | 93 | Destructuring is a nice way of writing more concise code. This lesson was my first opporunity to use destructuring as seen here: 94 | 95 | ```clojure 96 | (let [[x y] (get-xy e this)]) 97 | ``` 98 | 99 | You will also notice that in the Functional Thinking example above, I did not destructure the `offsetX` and `offsetY` in the let: 100 | 101 | ```clojure 102 | (let [x (.-offsetX e) 103 | y (.-offsetY e)]) 104 | ``` 105 | 106 | We did not do this because under the hood `let` is using `clojure.core/destructure` to create its binding form and does not expand access macros like that. 107 | 108 | If you did want to enable Clojure to do this, you could replacing let with something that uses an expanded destructure, or having a separate macro meant to be used inside a let block. I will likely not take on this challenge at the moment, but its a good example/opportunity to explore writing macros. To start this excercise, take a look at the [destructure macro](<(https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L4355)>) 109 | 110 | For more info on destructuring, check out the [clojure destructuring documentation](https://clojure.org/guides/destructuring) and there is also a handy [destructuring cheatsheet](https://gist.github.com/john2x/e1dca953548bfdfb9844) and [another guide here](http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/) 111 | 112 | and here is a great over of [syntax in clojurescript](https://cljs.github.io/api/syntax/) 113 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mouse Shadow 6 | 7 | 8 | 9 |
    10 |

    🔥WOAH!

    11 |
    12 | 13 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | ;; globals 7 | 8 | (def hero (.querySelector js/document ".hero")) 9 | 10 | (def text (.querySelector hero "h1")) 11 | 12 | (def walk 100) 13 | 14 | 15 | ;; Helpers 16 | 17 | (defn calc-x-walk 18 | [x walk width] 19 | (int (- (* (/ x width) walk) (/ walk 2)))) 20 | 21 | (defn calc-y-walk 22 | [y walk height] 23 | (int (- (* (/ y height) walk) (/ walk 2)))) 24 | 25 | (defn make-text-shadow 26 | [x y] 27 | (str x"px " y"px " "0 red")) 28 | 29 | (defn get-xy 30 | [e this] 31 | (let [x (.-offsetX e) 32 | y (.-offsetY e)] 33 | (if (not= this (.-target e)) 34 | [(+ x (.. e -target -offsetLeft)) (+ y (.. e -target -offsetTop))] 35 | [x y]))) 36 | 37 | 38 | ;; Event handlers 39 | 40 | (defn move-shadow 41 | [e] 42 | (this-as this 43 | (let [width (.-offsetWidth hero) 44 | height (.-offsetHeight hero) 45 | [x y] (get-xy e this) 46 | x-walk (calc-x-walk x walk width) 47 | y-walk (calc-y-walk y walk height)] 48 | (set! (.. text -style -textShadow) (make-text-shadow x-walk y-walk))))) 49 | 50 | 51 | ;; Event listeners 52 | 53 | (.addEventListener hero "mousemove" move-shadow) 54 | -------------------------------------------------------------------------------- /16-mousemove-text-shadows/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /17-sort-without-articles/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /17-sort-without-articles/README.md: -------------------------------------------------------------------------------- 1 | # 17 Sort Without Articles 2 | 3 | This lesson goes over things like regexes and comparators 4 | 5 | - [Sorting](#sort) 6 | - [Strip](#strip) 7 | - [Regex](#regex) 8 | 9 | # Requirements 10 | 11 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 12 | 13 | # Quickstart 14 | 15 | Run the following comamnds from the root of the `18-mousemove-and-text-shadows` 16 | 17 | **1. Run the projcet** 18 | 19 | ```bash 20 | clj -M:dev 21 | ``` 22 | 23 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 24 | 25 | ```bash 26 | ➜ clj -h 27 | Version: 1.10.3.855 # this is the version your on 28 | ``` 29 | 30 | 31 | **2. Visit the app** 32 | 33 | http://localhost:9000/ 34 | 35 | # Sorting 36 | 37 | Sorting is not difficult in JS or Clojure, but clojure definitely provides a more terse syntax. 38 | 39 | ```clojure 40 | (def bands ["The Plot in You", "The Devil Wears Prada", "Pierce the Veil"]) 41 | 42 | (sort bands) 43 | ``` 44 | 45 | The above will alphabetically sort things for you. In JS you would have to pass a [comparator](https://clojure.org/guides/comparators) (a function that tells sort how to sort things). Having said this, the task we are trying to execute does require we pass `sort` a `comparator` even in clojure. 46 | 47 | # Regex 48 | 49 | Regex always feels oddly different when I write them in clojure, so I wanted to note how to write case-insensitive regexes here: 50 | 51 | ```clojure 52 | #"(?i)a |the |an " 53 | ``` 54 | 55 | As you can see, you prefix with `(?i)`. Most everything else about regexes is usually the same, but you get odd moments like the above where your just left wondering. 56 | 57 | # Strip 58 | 59 | This can also be known as `trimming`, but the idea is the same: Remove a type or series of characters from a string. The most common case is usually `trimming` whitespace. This is likely why clojure actually provides a `trim` function. 60 | -------------------------------------------------------------------------------- /17-sort-without-articles/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /17-sort-without-articles/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sort Without Articles 6 | 7 | 8 | 9 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /17-sort-without-articles/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /17-sort-without-articles/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | ;; helpers 8 | 9 | (defn strip-article 10 | [word] 11 | (clojure.string/replace word #"(?i)a |the |an " "")) 12 | 13 | (defn make-band-listItem 14 | [band-name] 15 | (str "
  • "band-name"
  • ")) 16 | 17 | (defn make-band-list 18 | [bands] 19 | (apply str (map make-band-listItem bands))) 20 | 21 | 22 | ;; comparator 23 | 24 | (defn alphabetically 25 | [a b] 26 | (compare (strip-article a) 27 | (strip-article b))) 28 | 29 | 30 | ;; event listener 31 | 32 | (set! (.-innerHTML (.querySelector js/document "#bands")) (make-band-list (sort alphabetically js/bands))) 33 | -------------------------------------------------------------------------------- /17-sort-without-articles/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /18-string-times/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /18-string-times/README.md: -------------------------------------------------------------------------------- 1 | # String Times 2 | 3 | I liked this lesson because it provided an opportunity to explore how strings and numbers respond to math in Clojure. 4 | 5 | - [Quick Start](#quick-start) 6 | - [Multiplication and Strings](#multiplication-and-strings) 7 | 8 | # Requirements 9 | 10 | Please ensure you have a clojure environment running locally - see [Getting Started Guide](https://github.com/tkjone/clojurescript-30#getting-started) if you need to set one up. 11 | 12 | # Quick Start 13 | 14 | Run the following comamnds from the root of `20-string-times` 15 | 16 | **1. Run the projcet** 17 | 18 | ```bash 19 | clj -M:dev 20 | ``` 21 | 22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 23 | 24 | ```bash 25 | ➜ clj -h 26 | Version: 1.10.3.855 # this is the version your on 27 | ``` 28 | 29 | 30 | **2. Visit the app** 31 | 32 | http://localhost:9000/ 33 | 34 | # Multiplication and Strings 35 | 36 | In Clojure, you cannot multiple a string and a number. Whay even make this a note? Because in JS you can and it is a source of great pain if you are not careful. For example: 37 | 38 | ```clojure 39 | (* "5" 5) 40 | ``` 41 | 42 | The above is going to result in a `ClassCastException` error. What would happen in JS? 43 | 44 | ```javascript 45 | var hot = "5" * 5; // 25 46 | 47 | typeof hot; // 'number' 48 | ``` 49 | 50 | So in JS, it will actually result in a number. Not so much with Clojure. So what does this mean? Well, we have to be sure that we are always doing math against numbers. So how do we **convert a string to a number**? 51 | 52 | From my research and adivce from **clojurians** the suggested approach is to use `Integer/parseInt` / `Double/parseDouble`. Another approach is to use `cljs.reader/read-string`. Regarding the later, the advice is to not opt for `read-string` first because it does some interesting things under the hood that will not be what you are expecting. There is also the fact that there are no less than three `read-string` functions in clojure, so it is confusing. Here is an article that outlines [some of the differences](https://coderwall.com/p/8krwqg/clojure-script-compatibility-magic). 53 | -------------------------------------------------------------------------------- /18-string-times/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}} 3 | 4 | :aliases {:dev {:main-opts ["-m" "cljs.main" 5 | "-ro" "ro.edn" 6 | "-w" "src" 7 | "-c" "app.core" 8 | "-r"]}}} 9 | -------------------------------------------------------------------------------- /18-string-times/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Videos 6 | 7 | 8 | 184 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /18-string-times/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /18-string-times/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp]])) 5 | 6 | 7 | ;; globals 8 | 9 | (def node-list (.querySelectorAll js/document "[data-time]")) 10 | 11 | 12 | ;; protocols 13 | 14 | (extend-type js/NodeList 15 | ISeqable 16 | (-seq [node-list] (array-seq node-list))) 17 | 18 | 19 | ;; helpers 20 | 21 | (defn get-dataset-time 22 | [node] 23 | (.. node -dataset -time)) 24 | 25 | 26 | (defn split-time-string 27 | "Takes a string like 5:45, splits it, and returns a vector of numbers lie 28 | [5 45]" 29 | [timestring] 30 | (map #(js/parseInt %1) (clojure.string/split timestring #":"))) 31 | 32 | 33 | ;; Helpers 34 | 35 | (defn get-seconds-from-node 36 | [acc node] 37 | (let [time-string (get-dataset-time node) 38 | [mins secs] (split-time-string time-string) 39 | total-seconds (+ (* mins 60) secs)] 40 | (+ acc total-seconds))) 41 | 42 | 43 | (defn get-total-seconds-from-nodes 44 | [nodelist] 45 | (reduce get-seconds-from-node 0 node-list)) 46 | 47 | 48 | (defn get-hours-min-seconds 49 | [seconds] 50 | (let [hours (Math/floor (/ seconds 3600)) 51 | mins (Math/floor (/ (mod seconds 3600) 60)) 52 | secondsLeft (mod (mod seconds 3600) 60)] 53 | [hours mins secondsLeft])) 54 | 55 | 56 | (pp (get-hours-min-seconds (get-total-seconds-from-nodes node-list))) 57 | -------------------------------------------------------------------------------- /18-string-times/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/README.md: -------------------------------------------------------------------------------- 1 | # Unreal Webcam Fun 2 | 3 | For this one I did not add the rgb effects and only focused on showing the webcam in the video element and taking a picture. The other items have been done before and I spent more time researching more idiomatic clojure code 4 | 5 | - [Quick Start](#quick-start) 6 | - [Working with Native JS Functions](#working-with-native-js-functions) 7 | - [Thread v. Double Dot Macro](#thread-v-dot-dot-macro) 8 | - [Require from Google Closure](#require-from-google-closure) 9 | - [Time Intervals](#time-intervals) 10 | - [Add to JS Global Scope](#add-to-js-global-scope) 11 | 12 | # Quick Start 13 | 14 | Run the following comamnds from the root of `20-string-times` 15 | 16 | **1. Run the projcet** 17 | 18 | ```bash 19 | clj -M:dev 20 | ``` 21 | 22 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 23 | 24 | ```bash 25 | ➜ clj -h 26 | Version: 1.10.3.855 # this is the version your on 27 | ``` 28 | 29 | 30 | **2. Visit the app** 31 | 32 | http://localhost:9000/ 33 | 34 | # Working with Native JS Functions 35 | 36 | This is more of a note, but sometimes you might try to use a JS method and be confused by the fact that passing a CLJS object will not work with it. In cases like this, remember to switch to a JS object instead. For example: 37 | 38 | ```clojure 39 | (-> js/navigator 40 | .-mediaDevices 41 | (.getUserMedia #js {:video true :audio false}) ;; <-- native js object 42 | (.then handle-promise) 43 | (.catch handle-promise-err))) 44 | ``` 45 | 46 | By using the `#js` macro, I am creating and passing a JS object to `.getUserMedia` 47 | 48 | # Thread v dot dot macro 49 | 50 | You are going to notice that I use the `->` macro for the first time in this series. I am going to opt for this going forward. It appears to be the preferred option based on conversation I have had with other **clojurians**. 51 | 52 | # Require from Google Closure 53 | 54 | Take a look at [this article](https://www.martinklepsch.org/posts/requiring-closure-namespaces.html) for more information. 55 | 56 | # Time Intervals 57 | 58 | Lets say you want to `console.log` something once every 2 seconds. To do this in JavaScript, you would use something like this: 59 | 60 | ```javascript 61 | setInterval(() => console.log("tick"), 1000); 62 | ``` 63 | 64 | How would you achieve the same thing in clojure? Turns out there are no less than 3 approaches to this. Below, I will show examples of all three of the options, but I would say that either **option 1** or **option 2** are your best bets. `core.async` is a beast of a library to use for such a simple task. 65 | 66 | **1. js/setInterval** 67 | 68 | ```clojure 69 | (js/setInterval #(p "tick") 1000) 70 | ``` 71 | 72 | **2. goog.timer** 73 | 74 | To use this, you need to require `goog timer` into your namespace like this: 75 | 76 | ```clojure 77 | (:require [goog.events :as events] 78 | [goog.Timer :as timer]) 79 | (:import [goog Timer]) 80 | ``` 81 | 82 | and than you can use it like this 83 | 84 | ```clojure 85 | (def my-timer (Timer.)) 86 | 87 | (events/listen my-timer timer/TICK (fn [e] (p "hi"))) 88 | 89 | (.start my-timer) 90 | ``` 91 | 92 | Working with google closure can be a little confusing, so I recommend reviewing the source code when stuck. For example, when learning how to start the above timer, I reviewed [google closure timer source code](https://github.com/google/closure-library/tree/master/closure/goog/timer). Specifically, checkout `timer_test.js`. 93 | 94 | **3. core.async** 95 | 96 | This is the heavy duty option. To start using this approach, you have to add it to your projects dependencies. See `build.boot` for this line: 97 | 98 | ```clojure 99 | [org.clojure/core.async "0.3.443"] 100 | ``` 101 | 102 | > My version might become out-of-date, checkout the [official github repo](https://github.com/clojure/core.async) for the most current version 103 | 104 | You then need to import core.async into your namespace. This would look like this: 105 | 106 | ```clojure 107 | (:require [cljs.core.async :as async]) 108 | (:require-macros [cljs.core.async.macros :as m :refer [go]])) 109 | ``` 110 | 111 | > Take note that you are importing from `cljs` and not `clj`. 112 | 113 | Now we can make ourselves a little setInterval timer core.async style: 114 | 115 | ```clojure 116 | (go 117 | (loop [] 118 | (async/ 3 | 4 | 5 | 6 | Get User Media Code Along! 7 | 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | 34 |
    35 | 36 | 37 | 38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/resources/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, *:before, *:after { 6 | box-sizing: inherit; 7 | } 8 | 9 | html { 10 | font-size: 10px; 11 | background:#ffc600; 12 | } 13 | 14 | .photobooth { 15 | background:white; 16 | max-width:150rem; 17 | margin: 2rem auto; 18 | border-radius:2px; 19 | } 20 | 21 | /*clearfix*/ 22 | .photobooth:after { 23 | content: ''; 24 | display: block; 25 | clear: both; 26 | } 27 | 28 | .photo { 29 | width:100%; 30 | float:left; 31 | } 32 | 33 | .player { 34 | position: absolute; 35 | top:20px; 36 | right: 20px; 37 | width:200px; 38 | } 39 | 40 | /* 41 | Strip! 42 | */ 43 | 44 | .strip { 45 | padding:2rem; 46 | } 47 | .strip img { 48 | width:100px; 49 | overflow-x: scroll; 50 | padding:0.8rem 0.8rem 2.5rem 0.8rem; 51 | box-shadow:0 0 3px rgba(0,0,0,0.2); 52 | background:white; 53 | } 54 | 55 | .strip a:nth-child(5n+1) img { transform: rotate(10deg); } 56 | .strip a:nth-child(5n+2) img { transform: rotate(-2deg); } 57 | .strip a:nth-child(5n+3) img { transform: rotate(8deg); } 58 | .strip a:nth-child(5n+4) img { transform: rotate(-11deg); } 59 | .strip a:nth-child(5n+5) img { transform: rotate(12deg); } 60 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [cljs.core.async :as async]) 4 | (:require-macros [app.macros :refer [p pp]] 5 | [cljs.core.async.macros :as m :refer [go]])) 6 | 7 | 8 | ;; Globals 9 | 10 | (def video (.querySelector js/document ".player")) 11 | 12 | (def canvas (.querySelector js/document ".photo")) 13 | 14 | (def ctx (.getContext canvas "2d")) 15 | 16 | (def strip (.querySelector js/document ".strip")) 17 | 18 | (def snap (.querySelector js/document ".snap")) 19 | 20 | 21 | ;; Webcam helpers 22 | 23 | (defn handle-promise 24 | [local-media-stream] 25 | (set! (.-src video) (.. js/window -URL (createObjectURL local-media-stream))) 26 | (.play video)) 27 | 28 | 29 | (defn handle-promise-err 30 | [err] 31 | (p "You did not give me permission to use the video")) 32 | 33 | 34 | (defn get-video 35 | [] 36 | (-> js/navigator 37 | .-mediaDevices 38 | (.getUserMedia #js {:video true :audio false}) 39 | (.then handle-promise) 40 | (.catch handle-promise-err))) 41 | 42 | 43 | (defn paint-to-canvas 44 | [] 45 | (let [height (.-videoHeight video) 46 | width (.-videoWidth video)] 47 | (set! (.-width canvas) width) 48 | (set! (.-height canvas) height) 49 | (go 50 | (loop [] 51 | (async/")) 67 | (.insertBefore strip link (.-firstChild strip))))) 68 | 69 | 70 | (get-video) 71 | 72 | (.addEventListener video "canplay" paint-to-canvas) 73 | -------------------------------------------------------------------------------- /19-unreal-webcam-fun/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /20-native-speech-recognition/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /20-native-speech-recognition/README.md: -------------------------------------------------------------------------------- 1 | # Native Speech Recognition 2 | 3 | Customize the HTML5 Video Player with CLJS. 4 | 5 | - [Quickstart](#quickstart) 6 | - [Learnings](#learnings) 7 | - [JS Hot Reloading](#js-hot-reloading) 8 | - [Global Functions](#global-functions) 9 | 10 | ## Quickstart 11 | 12 | ```bash 13 | clj -M:dev 14 | ``` 15 | 16 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 17 | 18 | ```bash 19 | ➜ clj -h 20 | Version: 1.10.3.855 # this is the version your on 21 | ``` 22 | 23 | 24 | Visit site at `http://localhost:3000` 25 | 26 | ## Learnings 27 | 28 | ### JS Hot Reloading 29 | 30 | This would be a good example of how to perform hot reloading with vanilla javascript as you get an error every time you hit save in this project. 31 | 32 | ### Global Functions 33 | 34 | In this lesson, all the global functions had to be in the HTML file vs my CLJS file. I don't actually think this is required, but to keep this exercise focused, I have left it as such. Would like to go back and change this up. The goal is to define `words` and `speech` object in the CLJS file. 35 | -------------------------------------------------------------------------------- /20-native-speech-recognition/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | org.clojure/core.async {:mvn/version "1.3.610"}} 4 | 5 | :aliases {:dev {:main-opts ["-m" "cljs.main" 6 | "-ro" "ro.edn" 7 | "-w" "src" 8 | "-c" "app.core" 9 | "-r"]}}} 10 | -------------------------------------------------------------------------------- /20-native-speech-recognition/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Speech Detection 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 |
    14 | 15 | 16 | 27 | 28 | 29 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /20-native-speech-recognition/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /20-native-speech-recognition/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; Globals 7 | 8 | 9 | ;; Handlers 10 | 11 | (defn write-msg! [msg final?] 12 | ; Add new paragraph 13 | (when final? 14 | (p "NEW PARA") 15 | (set! js/paragraph (.createElement js/document "p")) 16 | (.. js/words (appendChild js/paragraph))) 17 | 18 | ; Add to existing paragraph 19 | (when-not final? 20 | (p "ORIGINAL") 21 | (set! (.-textContent js/paragraph) (clojure.string/trim msg)))) 22 | 23 | 24 | (defn- handle-results [results] 25 | (let [index (.-resultIndex results) 26 | final? (.-isFinal (aget (.-results results) 0)) 27 | message (.-transcript (aget (.-results results) index 0)) 28 | message-o (.-transcript (aget (.-results results) 0 0))] 29 | (if (not (and (= 1 index) (= message-o message))) 30 | (write-msg! (clojure.string/trim message) final?)))) 31 | 32 | 33 | ;; Configure speech recognition 34 | 35 | (set! (.-interimResults js/speech) true) 36 | 37 | ;; Start 38 | 39 | (.addEventListener js/speech "result" handle-results) 40 | 41 | (.addEventListener js/speech "end" #(.start js/speech)) 42 | 43 | (.start js/speech) 44 | -------------------------------------------------------------------------------- /20-native-speech-recognition/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | (defmacro p 6 | "Print and return native JavaScript argument." 7 | [x] 8 | `(let [res# ~x] 9 | (.log js/console res#) 10 | res#)) 11 | 12 | 13 | (defmacro pp 14 | "Pretty print and return argument (uses `prn-str` internally)." 15 | [x] 16 | `(let [res# ~x] 17 | (.log js/console (prn-str res#)) 18 | res#)) 19 | -------------------------------------------------------------------------------- /21-geolocation/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /21-geolocation/README.md: -------------------------------------------------------------------------------- 1 | # Geolocation 2 | 3 | Have not started this one just yet. Working out HTTPS options for figwheel. 4 | 5 | - [Quickstart](#quickstart) 6 | 7 | ## Quick Start 8 | 9 | ```bash 10 | clojure -m figwheel.main -b dev -r 11 | ``` 12 | -------------------------------------------------------------------------------- /21-geolocation/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /21-geolocation/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /21-geolocation/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /21-geolocation/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | (def last-hole (atom 0)) 5 | (def time-up (atom false)) 6 | (def score (atom 0)) 7 | 8 | (def holes (-> js/document (.querySelectorAll ".hole"))) 9 | (def score-board (-> js/document (.querySelector ".score"))) 10 | (def moles (-> js/document (.querySelectorAll ".mole"))) 11 | 12 | 13 | (defn random-time 14 | "Generate a random time in miliseconds" 15 | [min max] 16 | (as-> (- max min) time 17 | (* time (-> js/Math (.random))) 18 | (+ time min) 19 | (-> js/Math (.round time)))) 20 | 21 | 22 | (defn random-hole 23 | "Select a hole at random" 24 | [holes] 25 | (let [index (as-> (count holes) hole-count 26 | (* hole-count (-> js/Math (.random))) 27 | (-> js/Math (.floor hole-count))) 28 | hole (nth holes index)] 29 | 30 | (when (= index last-hole) 31 | (random-hole holes) 32 | (p "same hole")) 33 | 34 | (reset! last-hole index) 35 | 36 | hole)) 37 | 38 | 39 | (defn peep 40 | "Show a mole" 41 | [holes] 42 | (let [time (random-time 200, 1000) 43 | hole (random-hole holes)] 44 | 45 | (-> hole .-classList (.add "up")) 46 | 47 | (js/setTimeout 48 | (fn [] 49 | (-> hole .-classList (.remove "up")) 50 | (when-not @time-up 51 | (peep holes))) 52 | time))) 53 | 54 | 55 | (defn bonk 56 | "Hide mole when user clicks of them" 57 | [e] 58 | (this-as this 59 | (if-not (-> e .-isTrusted) 60 | nil 61 | (do 62 | (swap! score inc) 63 | (-> this .-classList (.remove "up")) 64 | (set! (-> score-board .-textContent) @score))))) 65 | 66 | 67 | 68 | 69 | (defn startGame 70 | "You know what it is" 71 | [] 72 | (set! (-> score-board .-textContent) 0) 73 | 74 | (reset! time-up false) 75 | 76 | (reset! score 0) 77 | 78 | (peep (array-seq holes)) 79 | 80 | (js/setTimeout #(reset! time-up true) 10000)) 81 | 82 | 83 | (doseq [mole (array-seq moles)] 84 | (-> mole (.addEventListener "click" bonk))) 85 | -------------------------------------------------------------------------------- /21-geolocation/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /22-follow-along-links/.gitignore: -------------------------------------------------------------------------------- 1 | # clojure 2 | out 3 | .cpcache 4 | -------------------------------------------------------------------------------- /22-follow-along-links/README.md: -------------------------------------------------------------------------------- 1 | # Follow Along Links 2 | 3 | - [Quickstart](#quickstart) 4 | - [Learnings](#learnings) 5 | - [Macros](#macro) 6 | - [Resources](#resources) 7 | 8 | ## Quickstart 9 | 10 | ```bash 11 | clj -M:dev 12 | ``` 13 | 14 | > `-M` assumes your using a Clojure Tools version greater than `1.10.3.855`. Not sure what version your on? Run `clj -h` and you should see output near the top of the output like: 15 | 16 | ```bash 17 | ➜ clj -h 18 | Version: 1.10.3.855 # this is the version your on 19 | ``` 20 | 21 | 22 | Visit site at `http://localhost:9000` 23 | 24 | ## Learnings 25 | 26 | ### Macros 27 | 28 | ```clojure 29 | (-> highlight .-style .-width) 30 | ``` 31 | 32 | I found myself writing the above a bunch and wondered if I could explore different ways of doing this in Clojure. I found two options that I wanted to explore: 33 | 34 | 1. Macros 35 | 2. Google Closure Library 36 | 37 | Here what each of the solutions looked like: 38 | 39 | **Macro** 40 | 41 | To start, don't jump to macros. I was just trying to understand them better and compare against other solutions, but its a powerful tool that we can reserve for other times. What was interesting with Macros is I found it hard to get it to work initially. So here is an overview: 42 | 43 | _Broken Macro_ 44 | 45 | ```clojure 46 | ;; I do not work 47 | (defmacro get-attr 48 | [el attr] 49 | `(let [element# ~el 50 | attribute# ~attr] 51 | (attribute# element# ))) 52 | ``` 53 | 54 | _Working Macro_ 55 | 56 | ```clojure 57 | ;; I work 58 | (defmacro get-attr 59 | [el attr] 60 | `(~attr ~el)) 61 | 62 | ;; I work 63 | (defmacro get-attr [i e] 64 | (let [prop-sym i 65 | obj-sym e] 66 | (list prop-sym obj-sym))) 67 | ``` 68 | 69 | Lets break it down the first one that works 70 | 71 | 1. The list is **quoted** - essentially telling clojure compiler not to evaluate the list or its items. Just return itself. 72 | 2. At compile time, evaluate `~attr` and `~el` Which means our code now looks like this at the end of compile time: 73 | 74 | ```clojure 75 | (.-style element) 76 | ``` 77 | 78 | Okay, now compare to the very first example. So what was happening in the first example? Again, the whole thing is quoted, but the error happens here: 79 | 80 | ``` 81 | (let [element# ~el 82 | attribute# ~attr]) 83 | ``` 84 | 85 | Essentially the above is the equivalent of trying to do 86 | 87 | ```clojure 88 | (let [my-local .-style]) 89 | 90 | or 91 | 92 | (def my-global .-style) 93 | ``` 94 | 95 | What is the problem with the above? Well, its because we were trying to evaluate an [instanceField](https://clojure.org/reference/java_interop#_the_dot_special_form) 96 | 97 | ```clojure 98 | (def my-global .-style) 99 | ``` 100 | 101 | The abvoe is going to break because each of the items is evaluated one at a time. first, the `def`. then the `my-global` and then `.-style`. Normally, the evaluator expects something with `.-` to be at the front of a list and have 102 | 103 | The question is, what is `.-`. The answer is that the `.` dot part of it is special and reserved in clojure for interop. That's why it's not going to work. 104 | 105 | After all the above, I believe the cleanest way might be the `doto` 106 | 107 | ```clojure 108 | (set! (-> highlight .-style .-width) (make-hw-val width)) 109 | (set! (-> highlight .-style .-height) (make-hw-val height)) 110 | (set! (-> highlight .-style .-transform) (make-trans-val left top))))) 111 | ``` 112 | 113 | ```clojure 114 | (doto 115 | (-> highlight .-style) 116 | (set! -width (make-hw-val width)) 117 | (set! -height (make-hw-val height)) 118 | (set! -transform (make-trans-val left top)))))) 119 | ``` 120 | 121 | ## Resources 122 | 123 | https://cljs.github.io/api/syntax/dot 124 | 125 | http://clojurescriptmadeeasy.com/blog/js-interop-property-access.html 126 | 127 | https://clojure.org/guides/weird_characters 128 | -------------------------------------------------------------------------------- /22-follow-along-links/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | org.clojure/core.async {:mvn/version "1.3.610"}} 4 | 5 | :aliases {:dev {:main-opts ["-m" "cljs.main" 6 | "-ro" "ro.edn" 7 | "-w" "src" 8 | "-c" "app.core" 9 | "-r"]}}} 10 | -------------------------------------------------------------------------------- /22-follow-along-links/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 👀👀👀Follow Along Nav 6 | 7 | 8 | 9 | 10 | 19 | 20 |
    21 |

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!

    22 |

    Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.

    23 |

    Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.

    24 |

    Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.

    25 |

    Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!

    26 |
    27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /22-follow-along-links/resources/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | } 7 | body { 8 | min-height: 100vh; 9 | margin: 0; /* Important! */ 10 | font-family: sans-serif; 11 | background: 12 | linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%), 13 | linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%), 14 | linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%), 15 | linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%); 16 | } 17 | 18 | .wrapper { 19 | margin:0 auto; 20 | max-width:500px; 21 | font-size: 20px; 22 | line-height: 2; 23 | position: relative; 24 | } 25 | 26 | a { 27 | text-decoration: none; 28 | color:black; 29 | background:rgba(0,0,0,0.05); 30 | border-radius: 20px 31 | } 32 | 33 | .highlight { 34 | transition: all 0.2s; 35 | border-bottom:2px solid white; 36 | position: absolute; 37 | top:0; 38 | background:white; 39 | left:0; 40 | z-index: -1; 41 | border-radius:20px; 42 | display: block; 43 | box-shadow: 0 0 10px rgba(0,0,0,0.2) 44 | } 45 | 46 | .menu { 47 | padding: 0; 48 | display: flex; 49 | list-style: none; 50 | justify-content: center; 51 | margin:100px 0; 52 | } 53 | 54 | .menu a { 55 | display: inline-block; 56 | padding:5px; 57 | margin:0 20px; 58 | color:black; 59 | } 60 | -------------------------------------------------------------------------------- /22-follow-along-links/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /22-follow-along-links/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | ;; create main project namespace 2 | (ns app.core 3 | (:require [goog.string :as gstr] goog.string.format) 4 | (:require-macros [app.macros :refer [p pp get-attr]])) 5 | 6 | ;; Globals 7 | 8 | (def triggers (.querySelectorAll js/document "a")) 9 | 10 | 11 | (def highlight (.createElement js/document "span")) 12 | 13 | ;; utils 14 | 15 | (defn make-hw-val 16 | "hw stands for Height and Width" 17 | [coordinate] 18 | (str coordinate"px")) 19 | 20 | 21 | (defn make-trans-val 22 | [left top] 23 | (str "translate(" left"px ," top"px)")) 24 | 25 | ; (defn hello 26 | ; [symbol el] 27 | ; (p symbol) 28 | ; (p (-> el symbol))) 29 | ; 30 | (pp (macroexpand '(.-style hello))) 31 | 32 | 33 | ;; Event handlers 34 | 35 | (defn highlight-link 36 | [e] 37 | (this-as this 38 | (let [link-coords (.getBoundingClientRect this) 39 | width (.-width link-coords) 40 | height (.-height link-coords) 41 | left (+ (.-left link-coords) (.-scrollX js/window)) 42 | top (+ (.-top link-coords) (.-scrollY js/window))] 43 | 44 | (doto (-> highlight .-style) 45 | (set! -width (make-hw-val width)) 46 | (set! -height (make-hw-val height)) 47 | (set! -transform (make-trans-val left top)))))) 48 | 49 | 50 | ;; Setup 51 | 52 | (-> highlight .-classList (.add "highlight")) 53 | 54 | 55 | (-> js/document .-body (.append highlight)) 56 | 57 | 58 | (doseq [trigger (array-seq triggers)] 59 | (.addEventListener trigger "mouseenter" highlight-link)) 60 | -------------------------------------------------------------------------------- /22-follow-along-links/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | ;; create macros namespace 2 | (ns app.macros) 3 | 4 | 5 | ; (defmacro get-attr 6 | ; "" 7 | ; [el attr] 8 | ; `(let [element ~el 9 | ; attribute ~attr] 10 | ; (attribute element))) 11 | 12 | ; (defmacro get-attr 13 | ; [el attr] 14 | ; `(-> ~el ~attr)) 15 | 16 | ; (defmacro get-attr 17 | ; [el attr] 18 | ; `(~attr ~el)) 19 | 20 | (defmacro get-attr [i e] 21 | (let [prop-sym i 22 | obj-sym e] 23 | (list prop-sym obj-sym))) 24 | 25 | 26 | (defmacro p 27 | "Print and return native JavaScript argument." 28 | [x] 29 | `(let [res# ~x] 30 | (.log js/console res#) 31 | res#)) 32 | 33 | 34 | (defmacro pp 35 | "Pretty print and return argument (uses `prn-str` internally)." 36 | [x] 37 | `(let [res# ~x] 38 | (.log js/console (prn-str res#)) 39 | res#)) 40 | -------------------------------------------------------------------------------- /23-speech-synthesis/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | -------------------------------------------------------------------------------- /23-speech-synthesis/README.md: -------------------------------------------------------------------------------- 1 | # Speech Synthesis 2 | 3 | ## Quickstart 4 | 5 | Run the following commands from the root of the `25-speech-synthesis` repo 6 | 7 | * **1. Build and watch the project** 8 | 9 | ```bash 10 | clj --main cljs.main --repl-opts '{:static-dir ["." "out" "resources"]}' --watch src --compile speech-synthesis.core --repl 11 | ``` 12 | 13 | ## Learnings 14 | 15 | ### aset and aget 16 | 17 | [Great article that should be read](https://clojurescript.org/news/2017-07-14-checked-array-access) 18 | 19 | ### CLJ Tool 20 | 21 | This is the first project where I worked start to finish using the CLJ tool. Things to keep in mind: 22 | 23 | * Refreshing the browser 24 | 25 | Using Boot w/ reload or figwheel means that your files are being watched for changes and when changes are seen, your browser automatically refreshes to pickup these changes. However, if we are just using the CLJ tool, your browser is not automatically refreshed. You have to do this manually. This is not a big deal, just an FYI. 26 | 27 | * Understanding Errors 28 | 29 | When you make mistakes in your code, and boot w/ reload and are figwheel properly setup, both systems give you some indication of what you might be doing wrong. This is not exactly how it works with the CLJ tool. Clojure will tell you whats wrong, but it logs this to a file in your `out` directory called `watch.log`. For intermediate developers this is fine, but this is actually a bit of a deal breaker for new developers. It is challenging enough to understand the CLJS errors, but now you have to go find the file, try to read through a wall of unformatted text and then decipher. I am sure there is something we can do about this, but that would like be an extra step or configuration and at that point, we might as well use figwheel. 30 | 31 | This is not a critique of the CLJ Tool, just something to watch out for and consider as a new learner. 32 | -------------------------------------------------------------------------------- /23-speech-synthesis/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}} 3 | -------------------------------------------------------------------------------- /23-speech-synthesis/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Speech Synthesis 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |

    The Voiceinator 5000

    14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /23-speech-synthesis/resources/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 10px; 3 | box-sizing: border-box; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | font-family: sans-serif; 16 | background-color: #3bc1ac; 17 | display: flex; 18 | min-height: 100vh; 19 | align-items: center; 20 | 21 | background-image: radial-gradient( 22 | circle at 100% 150%, 23 | #3bc1ac 24%, 24 | #42d2bb 25%, 25 | #42d2bb 28%, 26 | #3bc1ac 29%, 27 | #3bc1ac 36%, 28 | #42d2bb 36%, 29 | #42d2bb 40%, 30 | transparent 40%, 31 | transparent 32 | ), 33 | radial-gradient( 34 | circle at 0 150%, 35 | #3bc1ac 24%, 36 | #42d2bb 25%, 37 | #42d2bb 28%, 38 | #3bc1ac 29%, 39 | #3bc1ac 36%, 40 | #42d2bb 36%, 41 | #42d2bb 40%, 42 | transparent 40%, 43 | transparent 44 | ), 45 | radial-gradient( 46 | circle at 50% 100%, 47 | #42d2bb 10%, 48 | #3bc1ac 11%, 49 | #3bc1ac 23%, 50 | #42d2bb 24%, 51 | #42d2bb 30%, 52 | #3bc1ac 31%, 53 | #3bc1ac 43%, 54 | #42d2bb 44%, 55 | #42d2bb 50%, 56 | #3bc1ac 51%, 57 | #3bc1ac 63%, 58 | #42d2bb 64%, 59 | #42d2bb 71%, 60 | transparent 71%, 61 | transparent 62 | ), 63 | radial-gradient( 64 | circle at 100% 50%, 65 | #42d2bb 5%, 66 | #3bc1ac 6%, 67 | #3bc1ac 15%, 68 | #42d2bb 16%, 69 | #42d2bb 20%, 70 | #3bc1ac 21%, 71 | #3bc1ac 30%, 72 | #42d2bb 31%, 73 | #42d2bb 35%, 74 | #3bc1ac 36%, 75 | #3bc1ac 45%, 76 | #42d2bb 46%, 77 | #42d2bb 49%, 78 | transparent 50%, 79 | transparent 80 | ), 81 | radial-gradient( 82 | circle at 0 50%, 83 | #42d2bb 5%, 84 | #3bc1ac 6%, 85 | #3bc1ac 15%, 86 | #42d2bb 16%, 87 | #42d2bb 20%, 88 | #3bc1ac 21%, 89 | #3bc1ac 30%, 90 | #42d2bb 31%, 91 | #42d2bb 35%, 92 | #3bc1ac 36%, 93 | #3bc1ac 45%, 94 | #42d2bb 46%, 95 | #42d2bb 49%, 96 | transparent 50%, 97 | transparent 98 | ); 99 | background-size: 100px 50px; 100 | } 101 | 102 | .voiceinator { 103 | padding: 2rem; 104 | width: 50rem; 105 | margin: 0 auto; 106 | border-radius: 1rem; 107 | position: relative; 108 | background: white; 109 | overflow: hidden; 110 | z-index: 1; 111 | box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.1); 112 | } 113 | 114 | h1 { 115 | width: calc(100% + 4rem); 116 | margin: -2rem 0 2rem -2rem; 117 | padding: 0.5rem; 118 | background: #ffc600; 119 | border-bottom: 5px solid #f3c010; 120 | text-align: center; 121 | font-size: 5rem; 122 | font-weight: 100; 123 | font-family: 'Pacifico', cursive; 124 | text-shadow: 3px 3px 0 #f3c010; 125 | } 126 | 127 | .voiceinator input, 128 | .voiceinator button, 129 | .voiceinator select, 130 | .voiceinator textarea { 131 | width: 100%; 132 | display: block; 133 | margin: 10px 0; 134 | padding: 10px; 135 | border: 0; 136 | font-size: 2rem; 137 | background: #f7f7f7; 138 | outline: 0; 139 | } 140 | 141 | textarea { 142 | height: 20rem; 143 | } 144 | 145 | input[type='select'] { 146 | } 147 | 148 | .voiceinator button { 149 | background: #ffc600; 150 | border: 0; 151 | width: 49%; 152 | float: left; 153 | font-family: 'Pacifico', cursive; 154 | margin-bottom: 0; 155 | font-size: 2rem; 156 | border-bottom: 5px solid #f3c010; 157 | cursor: pointer; 158 | position: relative; 159 | } 160 | 161 | .voiceinator button:active { 162 | top: 2px; 163 | } 164 | 165 | .voiceinator button:nth-of-type(1) { 166 | margin-right: 2%; 167 | } 168 | -------------------------------------------------------------------------------- /23-speech-synthesis/src/speech_synthesis/core.cljs: -------------------------------------------------------------------------------- 1 | (ns speech-synthesis.core 2 | (:require 3 | [goog.object]) 4 | (:require-macros 5 | [speech-synthesis.macros :refer [p pp]])) 6 | 7 | 8 | (defn voice-option 9 | [voice] 10 | (str "")) 13 | 14 | 15 | (defn toggle 16 | "Cancels speaker and starts new speaker. 17 | 18 | Args: 19 | ----- 20 | 21 | ::startover - boolean - defaults to true. When false, cancels and does not 22 | startover the speaker" 23 | ([] 24 | (-> js/speechSynthesis .cancel) 25 | (-> js/speechSynthesis (.speak js/msg))) 26 | 27 | ([startover] 28 | (-> js/speechSynthesis .cancel) 29 | (when startover 30 | (-> js/speechSynthesis (.speak js/msg))))) 31 | 32 | 33 | ;; Event Handlers 34 | 35 | (defn populate-voices! 36 | "Populates the Voice selection input and globally populates js/voices array." 37 | [] 38 | (this-as this 39 | (set! js/voices (-> this (.getVoices))) 40 | 41 | (let [voice-options (apply str (map voice-option js/voices))] 42 | (set! (.-innerHTML js/voicesDropdown) voice-options)))) 43 | 44 | 45 | (defn set-voice! [] 46 | (this-as this 47 | (let [voice (first (filter #(= (goog.object/get % "name") (.-value this)) js/voices))] 48 | (set! (.-voice js/msg) voice) 49 | (toggle)))) 50 | 51 | 52 | (defn set-option! [] 53 | (this-as this 54 | (let [name (-> this .-name) 55 | value (-> this .-value)] 56 | (goog.object/set js/msg name value)))) 57 | 58 | ;; Start 59 | 60 | (set! (.-text js/msg) (-> js/document (.querySelector "[name=text]") .-value)) 61 | 62 | (-> js/speechSynthesis (.addEventListener "voiceschanged" populate-voices!)) 63 | 64 | (-> js/voicesDropdown (.addEventListener "change" set-voice!)) 65 | 66 | (doseq [option (array-seq js/options)] 67 | (.addEventListener option "change" set-option!)) 68 | 69 | (-> js/speakButton (.addEventListener "click" toggle)) 70 | 71 | (-> js/stopButton (.addEventListener "click" #(toggle false))) 72 | -------------------------------------------------------------------------------- /23-speech-synthesis/src/speech_synthesis/macros.clj: -------------------------------------------------------------------------------- 1 | (ns speech-synthesis.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /24-sticky-nav/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | -------------------------------------------------------------------------------- /24-sticky-nav/README.md: -------------------------------------------------------------------------------- 1 | # Sticky Nav 2 | 3 | ## Quickstart 4 | 5 | Run the following commands from the root of the `26-sticky-nav` repo 6 | 7 | * **1. Build and watch the project** 8 | 9 | ```bash 10 | clj --main cljs.main --repl-opts '{:static-dir ["." "out" "resources"]}' --watch src --compile app.core --repl 11 | ``` 12 | -------------------------------------------------------------------------------- /24-sticky-nav/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}} 3 | -------------------------------------------------------------------------------- /24-sticky-nav/resources/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | background: #eeeeee; 4 | font-family: 'helvetica neue'; 5 | font-size: 20px; 6 | font-weight: 200; 7 | } 8 | body { 9 | margin: 0; 10 | } 11 | *, 12 | *:before, 13 | *:after { 14 | box-sizing: inherit; 15 | } 16 | 17 | .site-wrap { 18 | max-width: 700px; 19 | margin: 70px auto; 20 | background: white; 21 | padding: 40px; 22 | text-align: justify; 23 | box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.05); 24 | transform: scale(0.98); 25 | transition: transform 0.5s; 26 | } 27 | 28 | .fixed-nav .site-wrap { 29 | transform: scale(1); 30 | } 31 | 32 | header { 33 | text-align: center; 34 | height: 50vh; 35 | background: url(http://wes.io/iEgP/wow-so-deep.jpg) bottom center no-repeat; 36 | background-size: cover; 37 | display: flex; 38 | align-items: center; 39 | justify-content: center; 40 | } 41 | 42 | h1 { 43 | color: white; 44 | font-size: 7vw; 45 | text-shadow: 3px 4px 0 rgba(0, 0, 0, 0.2); 46 | } 47 | 48 | nav { 49 | background: black; 50 | top: 0; 51 | width: 100%; 52 | transition: all 0.5s; 53 | position: relative; 54 | z-index: 1; 55 | } 56 | 57 | .fixed-nav nav { 58 | position: fixed; 59 | box-shadow: 0 5px rgba(0, 0, 0, 0.1); 60 | } 61 | 62 | nav ul { 63 | margin: 0; 64 | padding: 0; 65 | list-style: none; 66 | display: flex; 67 | } 68 | 69 | nav li { 70 | flex: 1; 71 | text-align: center; 72 | display: flex; 73 | justify-content: center; 74 | align-items: center; 75 | } 76 | 77 | li.logo { 78 | max-width: 0; 79 | overflow: hidden; 80 | background: white; 81 | transition: all 0.5s; 82 | font-weight: 600; 83 | font-size: 30px; 84 | } 85 | 86 | .fixed-nav li.logo { 87 | max-width: 500px; 88 | } 89 | 90 | li.logo a { 91 | color: black; 92 | } 93 | 94 | nav a { 95 | text-decoration: none; 96 | padding: 20px; 97 | display: inline-block; 98 | color: white; 99 | transition: all 0.2s; 100 | text-transform: uppercase; 101 | } 102 | -------------------------------------------------------------------------------- /24-sticky-nav/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require 3 | [goog.object]) 4 | (:require-macros 5 | [app.macros :refer [p pp]])) 6 | 7 | 8 | ;; Globals 9 | 10 | (def nav (-> js/document (.querySelector "#main"))) 11 | 12 | (def top-of-nav (-> nav .-offsetTop)) 13 | 14 | (def nav-height (-> nav .-offsetHeight)) 15 | 16 | 17 | ;; Event Handlers 18 | 19 | 20 | (defn fix-nav! 21 | [] 22 | (if (< top-of-nav (-> js/window .-scrollY)) 23 | (do 24 | (set! (-> js/document .-body .-style .-paddingTop) (str nav-height "px")) 25 | (-> js/document .-body .-classList (.add "fixed-nav"))) 26 | (do 27 | (set! (-> js/document .-body .-style .-paddingTop) 0) 28 | (-> js/document .-body .-classList (.remove "fixed-nav"))))) 29 | 30 | 31 | ;; Start 32 | 33 | (-> js/document 34 | (.addEventListener "scroll" fix-nav!)) 35 | -------------------------------------------------------------------------------- /24-sticky-nav/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /25-event-delegation/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | -------------------------------------------------------------------------------- /25-event-delegation/README.md: -------------------------------------------------------------------------------- 1 | # 27 Event Delegation 2 | 3 | ## Quickstart 4 | 5 | Run the following commands from the root of the `27-event-delegation` repo 6 | 7 | * **1. Build and watch the project** 8 | 9 | ```bash 10 | clj --main cljs.main --repl-opts '{:static-dir ["." "out" "resources"]}' --watch src --compile app.core --repl 11 | ``` 12 | -------------------------------------------------------------------------------- /25-event-delegation/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"}}} 3 | -------------------------------------------------------------------------------- /25-event-delegation/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Understanding JavaScript's Capture 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /25-event-delegation/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require 3 | [goog.object]) 4 | (:require-macros 5 | [app.macros :refer [p pp]])) 6 | 7 | (def divs (-> js/document (.querySelectorAll "div"))) 8 | 9 | 10 | (defn log-text 11 | [e] 12 | (this-as this 13 | (js/console.log (-> this .-classList .-value)))) 14 | 15 | 16 | (doseq [div (array-seq divs)] 17 | (-> div (.addEventListener "click" log-text))) 18 | -------------------------------------------------------------------------------- /25-event-delegation/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/README.md: -------------------------------------------------------------------------------- 1 | # Stripe Follow Along Dropdown 2 | 3 | ## Quick Start 4 | 5 | ```bash 6 | clojure -m figwheel.main -b dev -r 7 | ``` 8 | 9 | ## Learnings 10 | 11 | [Alternative to Threading](https://github.com/tkjone/clojurescript-30/commit/e03045488ccff239c6651f29383da376eda5d652) seems to be an interesting way to cut down on repeating a common variable. 12 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require [goog.object]) 3 | (:require-macros [app.macros :refer [p pp]])) 4 | 5 | 6 | ;; Event Handlers 7 | ;; ----------------------------------------------------------------------------- 8 | 9 | 10 | (defn handle-enter 11 | [] 12 | (this-as this 13 | (let [background (-> js/document (.querySelector ".dropdownBackground")) 14 | nav (-> js/document (.querySelector ".top")) 15 | dropdown (-> this (.querySelector ".dropdown")) 16 | dropdown-coords (-> dropdown (.getBoundingClientRect)) 17 | nav-coords (-> nav (.getBoundingClientRect)) 18 | ;; TODO there should be a better way to do this in clojure land 19 | coords {:height (goog.object/get dropdown-coords "height") 20 | :width (goog.object/get dropdown-coords "width") 21 | :top (- (goog.object/get dropdown-coords "top") 22 | (goog.object/get nav-coords "top")) 23 | :left (goog.object/get dropdown-coords "left")}] 24 | 25 | 26 | (-> this .-classList (.add "trigger-enter")) 27 | (js/setTimeout 28 | (fn [] 29 | (when (-> this .-classList (.contains "trigger-enter")) 30 | (-> this .-classList (.add "trigger-enter-active")), 150))) 31 | 32 | (doto background 33 | .-classList (.add "open") 34 | .-style (.setProperty "width" (str (:width coords) "px")) 35 | .-style (.setProperty "height" (str (:height coords) "px")) 36 | .-style (.setProperty "transform" (str "translate(" (:left coords) "px, " (:top coords) "px" ")")))))) 37 | 38 | 39 | (defn handle-leave 40 | [] 41 | (this-as this 42 | (let [background (-> js/document (.querySelector ".dropdownBackground"))] 43 | (-> this .-classList (.remove "trigger-enter" "trigger-enter-active")) 44 | (-> background .-classList (.remove "open"))))) 45 | 46 | 47 | ;; Start 48 | ;; ----------------------------------------------------------------------------- 49 | 50 | 51 | (let [triggers (-> js/document (.querySelectorAll ".cool > li"))] 52 | (doseq [trigger (array-seq triggers)] 53 | (-> trigger (.addEventListener "mouseenter" handle-enter))) 54 | 55 | (doseq [trigger (array-seq triggers)] 56 | (-> trigger (.addEventListener "mouseleave" handle-leave)))) 57 | -------------------------------------------------------------------------------- /26-stripe-follow-along-dropdown/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/README.md: -------------------------------------------------------------------------------- 1 | # Click and Drag to Scroll 2 | 3 | - [Quickstart](#quickstart) 4 | 5 | ## Quick Start 6 | 7 | ```bash 8 | clojure -m figwheel.main -b dev -r 9 | ``` 10 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Click and Drag 6 | 7 | 8 | 9 |
    10 |
    01
    11 |
    02
    12 |
    03
    13 |
    04
    14 |
    05
    15 |
    06
    16 |
    07
    17 |
    08
    18 |
    09
    19 |
    10
    20 |
    11
    21 |
    12
    22 |
    13
    23 |
    14
    24 |
    15
    25 |
    16
    26 |
    17
    27 |
    18
    28 |
    19
    29 |
    20
    30 |
    21
    31 |
    22
    32 |
    23
    33 |
    24
    34 |
    25
    35 |
    36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/resources/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | background: url('https://source.unsplash.com/NFs6dRTBgaM/2000x2000') fixed; 4 | background-size: cover; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | min-height: 100vh; 15 | display: flex; 16 | justify-content: center; 17 | align-items: center; 18 | font-family: sans-serif; 19 | font-size: 20px; 20 | margin: 0; 21 | } 22 | 23 | .items { 24 | height: 800px; 25 | padding: 100px; 26 | width: 100%; 27 | border: 1px solid white; 28 | overflow-x: scroll; 29 | overflow-y: hidden; 30 | white-space: nowrap; 31 | user-select: none; 32 | cursor: pointer; 33 | transition: all 0.2s; 34 | transform: scale(0.98); 35 | will-change: transform; 36 | position: relative; 37 | background: rgba(255, 255, 255, 0.1); 38 | font-size: 0; 39 | perspective: 500px; 40 | } 41 | 42 | .items.active { 43 | background: rgba(255, 255, 255, 0.3); 44 | cursor: grabbing; 45 | cursor: -webkit-grabbing; 46 | transform: scale(1); 47 | } 48 | 49 | .item { 50 | width: 200px; 51 | height: calc(100% - 40px); 52 | display: inline-flex; 53 | align-items: center; 54 | justify-content: center; 55 | font-size: 80px; 56 | font-weight: 100; 57 | color: rgba(0, 0, 0, 0.15); 58 | box-shadow: inset 0 0 0 10px rgba(0, 0, 0, 0.15); 59 | } 60 | 61 | .item:nth-child(9n + 1) { 62 | background: dodgerblue; 63 | } 64 | .item:nth-child(9n + 2) { 65 | background: goldenrod; 66 | } 67 | .item:nth-child(9n + 3) { 68 | background: paleturquoise; 69 | } 70 | .item:nth-child(9n + 4) { 71 | background: gold; 72 | } 73 | .item:nth-child(9n + 5) { 74 | background: cadetblue; 75 | } 76 | .item:nth-child(9n + 6) { 77 | background: tomato; 78 | } 79 | .item:nth-child(9n + 7) { 80 | background: lightcoral; 81 | } 82 | .item:nth-child(9n + 8) { 83 | background: darkslateblue; 84 | } 85 | .item:nth-child(9n + 9) { 86 | background: rebeccapurple; 87 | } 88 | 89 | .item:nth-child(even) { 90 | transform: scaleX(1.31) rotateY(40deg); 91 | } 92 | .item:nth-child(odd) { 93 | transform: scaleX(1.31) rotateY(-40deg); 94 | } 95 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | 5 | ;; Vars 6 | ;; ----------------------------------------------------------------------------- 7 | 8 | (def slider (.. js/document (querySelector ".items"))) 9 | 10 | ;; State 11 | ;; ----------------------------------------------------------------------------- 12 | 13 | (def down? (atom false)) 14 | (def start-x (atom false)) 15 | (def scroll-left (atom false)) 16 | 17 | 18 | ;; Event Handlers 19 | ;; ----------------------------------------------------------------------------- 20 | 21 | (defn handle-mousedown 22 | [e] 23 | (let [next-scroll-left (.. slider -scrollLeft) 24 | next-start-x (- (.. e -pageX) (.. slider -offsetLeft))] 25 | (reset! down? true) 26 | (.. slider -classList (add "active")) 27 | (reset! start-x next-start-x) 28 | (reset! scroll-left next-scroll-left))) 29 | 30 | 31 | (defn handle-mouseleave 32 | [] 33 | (reset! down? false) 34 | (.. slider -classList (remove "active"))) 35 | 36 | 37 | (defn handle-mouseup 38 | [] 39 | (reset! down? false) 40 | (.. slider -classList (remove "active"))) 41 | 42 | 43 | (defn handle-mousemove 44 | [e] 45 | (.. e (preventDefault)) 46 | 47 | (when-not @down? 48 | (p "not down?")) 49 | 50 | (when @down? 51 | (let [x (- (.. e -pageX) (.. slider -offsetLeft)) 52 | walk (- x @start-x)] 53 | (reset! down? true) 54 | (set! (.. slider -scrollLeft) walk)))) 55 | 56 | ;; Start 57 | ;; ----------------------------------------------------------------------------- 58 | 59 | (.. slider (addEventListener "mousedown" handle-mousedown)) 60 | 61 | (.. slider (addEventListener "mouseleave" handle-mouseleave)) 62 | 63 | (.. slider (addEventListener "mouseup" handle-mouseup)) 64 | 65 | (.. slider (addEventListener "mousemove" handle-mousemove)) 66 | -------------------------------------------------------------------------------- /27-click-and-drag-to-scroll/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /28-countdown-clock/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /28-countdown-clock/README.md: -------------------------------------------------------------------------------- 1 | # Countdown Clock 2 | 3 | - [Quickstart](#quickstart) 4 | - [Learnings](#learnings) 5 | - [as->](#as->) 6 | - [Declare](#declare) 7 | 8 | ## QuickStart 9 | 10 | ```bash 11 | clojure -m figwheel.main -b dev -r 12 | ``` 13 | 14 | ## Learnings 15 | 16 | ### as-> 17 | 18 | Use this when we want to have finer control over where our result goes. For example, if we look at how set interval need to work: 19 | 20 | ```clojure 21 | ; original 22 | (js/Math (.round (/ (- then (-> js/Date (.now))) 1000))) 23 | 24 | ;; next iteration 25 | (->> (-> js/Date (.now)) 26 | (- then) 27 | (/ 1000) ;; oops, we need the value from the above to come before :( 28 | (-> js/Math .round)) 29 | 30 | ;; another iteration 31 | (-> (-> js/Date (.now)) 32 | (/ (- then ??) 1000) ;; okay, we need it to come after next :( 33 | (-> js/Math .round)) 34 | 35 | ;; good stuff 36 | (as-> (-> js/Date (.now)) time 37 | (- then time) 38 | (/ time 1000) 39 | (-> js/Math (.round time))) 40 | ``` 41 | 42 | At the end, we have something a lot cleaner and easier to read 43 | 44 | ## Declare 45 | 46 | One thing done in the video is wes declared `display-time-left` below the function that uses it. This is no bueno in clojure. But how could we do this if we wanted to? 47 | 48 | 1. Move `display-time-left` above `timer` 49 | 2. Declare `display-time-left` 50 | 51 | > This is all because a function has to be defined before we use it. This is often why you start reading Clojure from the bottom up. 52 | 53 | To perform the second you need to `declare` the function before it is used. This is often reserved for specific scenarios, but I have done it here just as an example: 54 | 55 | ```clojure 56 | (declare display-time-left) 57 | 58 | (defn timer [seconds] 59 | ;; ... 60 | (display-time-left seconds)) 61 | 62 | (defn display-time-left [seconds] 63 | ;; do stuff) 64 | ``` 65 | -------------------------------------------------------------------------------- /28-countdown-clock/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /28-countdown-clock/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /28-countdown-clock/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Countdown Timer 6 | 7 | 8 | 9 | 10 |
    11 |
    12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 |

    23 |

    24 |
    25 |
    26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /28-countdown-clock/resources/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 10px; 4 | background: #8e24aa; 5 | background: linear-gradient(45deg, #42a5f5 0%, #478ed1 50%, #0d47a1 100%); 6 | } 7 | 8 | *, 9 | *:before, 10 | *:after { 11 | box-sizing: inherit; 12 | } 13 | 14 | body { 15 | margin: 0; 16 | text-align: center; 17 | font-family: 'Inconsolata', monospace; 18 | } 19 | 20 | .display__time-left { 21 | font-weight: 100; 22 | font-size: 20rem; 23 | margin: 0; 24 | color: white; 25 | text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.05); 26 | } 27 | 28 | .timer { 29 | display: flex; 30 | min-height: 100vh; 31 | flex-direction: column; 32 | } 33 | 34 | .timer__controls { 35 | display: flex; 36 | } 37 | 38 | .timer__controls > * { 39 | flex: 1; 40 | } 41 | 42 | .timer__controls form { 43 | flex: 1; 44 | display: flex; 45 | } 46 | 47 | .timer__controls input { 48 | flex: 1; 49 | border: 0; 50 | padding: 2rem; 51 | } 52 | 53 | .timer__button { 54 | background: none; 55 | border: 0; 56 | cursor: pointer; 57 | color: white; 58 | font-size: 2rem; 59 | text-transform: uppercase; 60 | background: rgba(0, 0, 0, 0.1); 61 | border-bottom: 3px solid rgba(0, 0, 0, 0.2); 62 | border-right: 1px solid rgba(0, 0, 0, 0.2); 63 | padding: 1rem; 64 | font-family: 'Inconsolata', monospace; 65 | } 66 | 67 | .timer__button:hover, 68 | .timer__button:focus { 69 | background: rgba(0, 0, 0, 0.2); 70 | outline: 0; 71 | } 72 | 73 | .display { 74 | flex: 1; 75 | display: flex; 76 | flex-direction: column; 77 | align-items: center; 78 | justify-content: center; 79 | } 80 | 81 | .display__end-time { 82 | font-size: 4rem; 83 | color: white; 84 | } 85 | -------------------------------------------------------------------------------- /28-countdown-clock/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /28-countdown-clock/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | ;; Globals 5 | ;; ----------------------------------------------------------------------------- 6 | 7 | ; will hold the setInterval reference 8 | (defonce countdown (atom 0)) 9 | 10 | (def timer-display (-> js/document (.querySelector ".display__time-left"))) 11 | 12 | (def end-time-display (-> js/document (.querySelector ".display__end-time"))) 13 | 14 | (def buttons (-> js/document (.querySelectorAll "[data-time]"))) 15 | 16 | ; declaring here as an example of how to declare a name earlier so we can use 17 | ; it earlier than where it is defined 18 | (declare display-time-left) 19 | 20 | (declare display-end-time) 21 | 22 | 23 | ;; Event Handlers 24 | ;; ----------------------------------------------------------------------------- 25 | 26 | (defn timer 27 | "Provided seconds will subtract 1 second every second" 28 | [seconds] 29 | (js/clearInterval @countdown) 30 | (let [now (-> js/Date (.now)) 31 | then (+ now (* seconds 1000)) 32 | handle-countdown (fn [] 33 | (let [seconds-left (as-> (-> js/Date (.now)) time 34 | (- then time) 35 | (/ time 1000) 36 | (-> js/Math (.round time))) 37 | stop-timer? (<= seconds-left 0)] 38 | (when stop-timer? 39 | (js/clearInterval @countdown)) 40 | 41 | (display-time-left seconds-left)))] 42 | 43 | (display-time-left seconds) 44 | (display-end-time then) 45 | 46 | (reset! countdown (js/setInterval handle-countdown 1000)))) 47 | 48 | 49 | (defn display-time-left 50 | "Display time remaining as ??:??" 51 | [seconds] 52 | (let [minutes (-> js/Math (.floor (/ seconds 60))) 53 | remainder-seconds (mod seconds 60) 54 | format-seconds? (< remainder-seconds 10) 55 | formatted-seconds (if format-seconds? "0" (str "")) 56 | display (str minutes ":" formatted-seconds remainder-seconds)] 57 | (set! (-> timer-display .-textContent) display))) 58 | 59 | 60 | (defn display-end-time 61 | "Display the expected endtime as as `Be back at ??:??`" 62 | [timestamp] 63 | (let [end (js/Date. timestamp) 64 | _ (p "sad pony") 65 | _ (p end) 66 | hour (-> end (.getHours)) 67 | minutes (-> end (.getMinutes)) 68 | display (str "Be back at " hour ":" minutes)] 69 | (set! (-> end-time-display .-textContent) display))) 70 | 71 | 72 | (defn start-timer [] 73 | (this-as this 74 | (timer (js/parseInt (-> this .-dataset .-time))))) 75 | 76 | 77 | ;; start 78 | ;; ----------------------------------------------------------------------------- 79 | 80 | 81 | (doseq [button (array-seq buttons)] 82 | (.addEventListener button "click" start-timer)) 83 | -------------------------------------------------------------------------------- /28-countdown-clock/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/README.md: -------------------------------------------------------------------------------- 1 | # Video Speed Controls UI 2 | 3 | ## Quick Start 4 | 5 | ```bash 6 | clojure -m figwheel.main -b dev -r 7 | ``` 8 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video Speed Scrubber 6 | 7 | 8 | 9 | 10 |
    11 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/resources/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | min-height: 100vh; 7 | background: #4c4c4c url('https://unsplash.it/1500/900?image=1021'); 8 | background-size: cover; 9 | font-family: sans-serif; 10 | } 11 | .wrapper { 12 | width: 850px; 13 | display: flex; 14 | } 15 | video { 16 | box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1); 17 | } 18 | 19 | .speed { 20 | background: #efefef; 21 | flex: 1; 22 | display: flex; 23 | align-items: flex-start; 24 | margin: 10px; 25 | border-radius: 50px; 26 | box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1); 27 | overflow: hidden; 28 | } 29 | .speed-bar { 30 | width: 100%; 31 | background: linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%); 32 | text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | padding: 2px; 37 | color: white; 38 | height: 16.3%; 39 | } 40 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | ;; Globals 5 | ;; ----------------------------------------------------------------------------- 6 | 7 | 8 | (def speed (-> js/document (.querySelector ".speed"))) 9 | 10 | (def bar (-> speed (.querySelector ".speed-bar"))) 11 | 12 | 13 | ;; Event Handlers 14 | ;; ----------------------------------------------------------------------------- 15 | 16 | (defn move-bar! 17 | [e] 18 | (this-as this 19 | (let [video (-> js/document (.querySelector ".flex")) 20 | y (- (-> e .-pageY) (-> this .-offsetTop)) 21 | percent (/ y (-> this .-offsetHeight)) 22 | min 0.4 23 | max 4 24 | height (-> js/Math (.round (* percent 100))) 25 | height-attr (str height "%") 26 | playback-rate (* percent (+ (- max min) min)) 27 | playback-attr (str (-> playback-rate (.toFixed 2)) "x")] 28 | (set! (-> bar .-style .-height) height-attr) 29 | (set! (-> bar .-textContent) playback-attr) 30 | (set! (.-playbackRate video) playback-rate)))) 31 | 32 | 33 | ;; Start 34 | ;; ----------------------------------------------------------------------------- 35 | 36 | (doto speed 37 | (.addEventListener "mousemove" move-bar!)) 38 | -------------------------------------------------------------------------------- /29-video-speed-controller-ui/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /.nrepl-history 11 | /.cpcache 12 | /out 13 | /node_modules 14 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/README.md: -------------------------------------------------------------------------------- 1 | # Whack a Mole Game 2 | 3 | - [Quickstart](#quickstart) 4 | 5 | ## Quick Start 6 | 7 | ```bash 8 | clojure -m figwheel.main -b dev -r 9 | ``` 10 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "target" "resources"] 2 | :deps {org.clojure/clojurescript {:mvn/version "1.10.866"} 3 | com.bhauman/figwheel-main {:mvn/version "0.2.13"}}} 4 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main app.core} 2 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Whack A Mole! 6 | 7 | 8 | 9 | 10 | 11 |

    Whack-a-mole! 0

    12 | 13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/resources/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 10px; 4 | background: #ffc600; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | padding: 0; 15 | margin: 0; 16 | font-family: 'Amatic SC', cursive; 17 | } 18 | 19 | h1 { 20 | text-align: center; 21 | font-size: 10rem; 22 | line-height: 1; 23 | margin-bottom: 0; 24 | } 25 | 26 | .score { 27 | background: rgba(255, 255, 255, 0.2); 28 | padding: 0 3rem; 29 | line-height: 1; 30 | border-radius: 1rem; 31 | } 32 | 33 | .game { 34 | width: 600px; 35 | height: 400px; 36 | display: flex; 37 | flex-wrap: wrap; 38 | margin: 0 auto; 39 | } 40 | 41 | .hole { 42 | flex: 1 0 33.33%; 43 | overflow: hidden; 44 | position: relative; 45 | } 46 | 47 | .hole:after { 48 | display: block; 49 | background: url(dirt.svg) bottom center no-repeat; 50 | background-size: contain; 51 | content: ''; 52 | width: 100%; 53 | height: 70px; 54 | position: absolute; 55 | z-index: 2; 56 | bottom: -30px; 57 | } 58 | 59 | .mole { 60 | background: url('mole.svg') bottom center no-repeat; 61 | background-size: 60%; 62 | position: absolute; 63 | top: 100%; 64 | width: 100%; 65 | height: 100%; 66 | transition: all 0.4s; 67 | } 68 | 69 | .hole.up .mole { 70 | top: 0; 71 | } 72 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/ro.edn: -------------------------------------------------------------------------------- 1 | {:static-dir ["." "out" "resources"]} 2 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/src/app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns app.core 2 | (:require-macros [app.macros :refer [p pp]])) 3 | 4 | (def last-hole (atom 0)) 5 | (def time-up (atom false)) 6 | (def score (atom 0)) 7 | 8 | (def holes (-> js/document (.querySelectorAll ".hole"))) 9 | (def score-board (-> js/document (.querySelector ".score"))) 10 | (def moles (-> js/document (.querySelectorAll ".mole"))) 11 | 12 | 13 | (defn random-time 14 | "Generate a random time in miliseconds" 15 | [min max] 16 | (as-> (- max min) time 17 | (* time (-> js/Math (.random))) 18 | (+ time min) 19 | (-> js/Math (.round time)))) 20 | 21 | 22 | (defn random-hole 23 | "Select a hole at random" 24 | [holes] 25 | (let [index (as-> (count holes) hole-count 26 | (* hole-count (-> js/Math (.random))) 27 | (-> js/Math (.floor hole-count))) 28 | hole (nth holes index)] 29 | 30 | (when (= index last-hole) 31 | (random-hole holes) 32 | (p "same hole")) 33 | 34 | (reset! last-hole index) 35 | 36 | hole)) 37 | 38 | 39 | (defn peep 40 | "Show a mole" 41 | [holes] 42 | (let [time (random-time 200, 1000) 43 | hole (random-hole holes)] 44 | 45 | (-> hole .-classList (.add "up")) 46 | 47 | (js/setTimeout 48 | (fn [] 49 | (-> hole .-classList (.remove "up")) 50 | (when-not @time-up 51 | (peep holes))) 52 | time))) 53 | 54 | 55 | (defn bonk 56 | "Hide mole when user clicks of them" 57 | [e] 58 | (this-as this 59 | (if-not (-> e .-isTrusted) 60 | nil 61 | (do 62 | (swap! score inc) 63 | (-> this .-classList (.remove "up")) 64 | (set! (-> score-board .-textContent) @score))))) 65 | 66 | 67 | 68 | 69 | (defn startGame 70 | "You know what it is" 71 | [] 72 | (set! (-> score-board .-textContent) 0) 73 | 74 | (reset! time-up false) 75 | 76 | (reset! score 0) 77 | 78 | (peep (array-seq holes)) 79 | 80 | (js/setTimeout #(reset! time-up true) 10000)) 81 | 82 | 83 | (doseq [mole (array-seq moles)] 84 | (-> mole (.addEventListener "click" bonk))) 85 | -------------------------------------------------------------------------------- /30-whack-a-mole-game/src/app/macros.clj: -------------------------------------------------------------------------------- 1 | (ns app.macros) 2 | 3 | 4 | (defmacro p 5 | "Print and return native JavaScript argument." 6 | [x] 7 | `(let [res# ~x] 8 | (.log js/console res#) 9 | res#)) 10 | 11 | 12 | (defmacro pp 13 | "Pretty print and return argument (uses `prn-str` internally)." 14 | [x] 15 | `(let [res# ~x] 16 | (.log js/console (prn-str res#)) 17 | res#)) 18 | --------------------------------------------------------------------------------