├── .gitignore ├── Readme.md ├── build.boot ├── screenshot.png ├── src └── tutorial_cljs │ ├── core.cljs │ ├── editor.cljs │ ├── inline_eval.cljs │ ├── quil_complete.cljs │ ├── quil_docs.cljs │ ├── quil_sketch.cljs │ ├── repl.cljs │ └── text │ ├── cljs.cljs │ ├── quil.cljs │ ├── reagent.cljs │ └── webgl.cljs ├── static ├── index.html ├── js │ └── main.cljs.edn └── main.css └── vendor ├── clojure-parinfer.js ├── inlet.css └── inlet.js /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pages/ 3 | dev/ 4 | build/ 5 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Interactive ClojureScript Tutorial Environment 2 | 3 | ## [Try it Out](https://jaredforsyth.com/tutorial-cljs) 4 | 5 | [](https://www.youtube.com/watch?v=DQSdW27Esto) 6 | [Screencast](https://www.youtube.com/watch?v=DQSdW27Esto) 7 | 8 | ## To Build 9 | 10 | Requires [boot](http://boot-clj.com). 11 | 12 | - start a dev server `boot dev` 13 | - build the project for hosting `boot doit` 14 | 15 | ## Powered By 16 | 17 | - [Clojurescript](https://github.com/clojure/clojurescript) 18 | - [Reepl](https://github.com/jaredly/reepl) 19 | - [Boot](http://boot-clj.com/) 20 | - [Reagent](http://reagent-project.github.io/) (which uses [React](https://facebook.github.io/react/)) 21 | - [Cljs-Devtools](https://github.com/binaryage/cljs-devtools/) 22 | - [Parinfer](https://shaunlebron.github.io/parinfer/) 23 | - [Replumb](https://github.com/ScalaConsultants/replumb/) 24 | - [Quil](http://quil.info) 25 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :source-paths #{"src"} 3 | :resource-paths #{"html"} 4 | :dependencies 5 | '[ 6 | [adzerk/boot-cljs "1.7.228-1" :scope "test"] 7 | [adzerk/boot-cljs-repl "0.3.0" :scope "test"] 8 | [adzerk/boot-reload "0.4.4" :scope "test"] 9 | [pandeiro/boot-http "0.6.3" :scope "test"] 10 | [crisptrutski/boot-cljs-test "0.2.1" :scope "test"] 11 | [zilti/boot-typed "0.1.1" :scope "test"] 12 | [adzerk/bootlaces "0.1.13"] 13 | 14 | [replumb "0.1.5-SNAPSHOT"] 15 | [parinfer "0.2.3"] 16 | [reagent "0.5.1"] 17 | [re-frame "0.6.0"] 18 | [binaryage/devtools "0.4.1"] 19 | [cljsjs/codemirror "5.10.0-0"] 20 | [quil "2.3.0"] 21 | 22 | [reepl "1.0.1.1"] 23 | 24 | [ajchemist/boot-figwheel "0.5.0-0"] ;; latest release 25 | [com.cemerick/piggieback "0.2.1" :scope "test"] 26 | [figwheel-sidecar "0.5.0-2" :scope "test"] 27 | 28 | [weasel "0.7.0" :scope "test"] 29 | [org.clojure/tools.nrepl "0.2.12" :scope "test"] 30 | 31 | [org.clojure/tools.reader "1.0.0-alpha2"] 32 | [org.clojure/clojure "1.7.0"] 33 | [org.clojure/core.typed "0.3.18"] 34 | [org.clojure/clojurescript "1.7.228"] 35 | ]) 36 | 37 | (require 38 | '[adzerk.bootlaces :refer :all] 39 | '[adzerk.boot-cljs :refer [cljs]] 40 | '[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]] 41 | '[adzerk.boot-reload :refer [reload]] 42 | '[crisptrutski.boot-cljs-test :refer [test-cljs]] 43 | '[pandeiro.boot-http :refer [serve]]) 44 | 45 | (require 'boot-figwheel) 46 | (refer 'boot-figwheel :rename '{cljs-repl fw-cljs-repl}) 47 | 48 | (def +version+ "1.0") 49 | (bootlaces! +version+) 50 | 51 | (task-options! 52 | pom {:project 'jaredly/tutorial-cljs 53 | :version +version+ 54 | :description "An interactive clojurescript tutorial" 55 | :url "https://github.com/jaredly/tutorial-cljs" 56 | :scm {:url "https://github.com/jaredly/tutorial-cljs"} 57 | :license {"ISC License" "https://opensource.org/licenses/ISC"}} 58 | figwheel {:build-ids ["dev"] 59 | :all-builds [{:id "dev" 60 | #_:compiler #_{:main 'tutorial-cljs.core 61 | :output-to "main.js"} 62 | :figwheel {:build-id "dev" 63 | :on-jsload "tutorial-cljs.core/main" 64 | :heads-up-display true 65 | :autoload true 66 | :debug false}}] 67 | :figwheel-options {:repl true 68 | :http-server-root "dev" 69 | :css-dirs ["dev"] 70 | :open-file-command "emacsclient"}}) 71 | 72 | (def foreign-libs 73 | [{:file "vendor/clojure-parinfer.js" 74 | :provides ["parinfer.codemirror.mode.clojure.clojure-parinfer"]}]) 75 | 76 | (defn add-jar-thing [package path] 77 | (let [rx (re-pattern path)] 78 | (comp 79 | (sift :add-jar 80 | {package rx}) 81 | (sift :move 82 | {rx (str "js/main.out/" path)})))) 83 | 84 | (defn cljs-and-stuff [optimizations] 85 | (comp 86 | (cljs :source-map true 87 | :compiler-options {:foreign-libs foreign-libs} 88 | :optimizations optimizations) 89 | (sift :add-jar 90 | {'cljsjs/codemirror 91 | #"cljsjs/codemirror/development/codemirror.css"}) 92 | (sift :move 93 | {#"cljsjs/codemirror/development/codemirror.css" 94 | "vendor/codemirror/codemirror.css"}) 95 | (add-jar-thing 96 | 'org.clojure/tools.reader 97 | "cljs/tools/reader/reader_types.clj") 98 | )) 99 | 100 | (deftask dev [] 101 | (set-env! :source-paths #{"src"}) 102 | (set-env! :resource-paths #{"static"}) 103 | (comp 104 | (serve :dir "dev" :port 3003) 105 | (watch) 106 | ;; (speak) 107 | (reload :on-jsload 'tutorial-cljs.core/main) 108 | (cljs-repl) 109 | (cljs-and-stuff :none) 110 | (target :dir #{"dev"}) 111 | )) 112 | 113 | (deftask devfw [] 114 | (set-env! :source-paths #(into % ["src"])) 115 | (comp (repl) (figwheel))) 116 | 117 | (deftask doit [] 118 | (set-env! :source-paths #{"src"}) 119 | (set-env! :resource-paths #{"static"}) 120 | (comp 121 | (cljs-and-stuff :simple) 122 | (target :dir #{"doit"}) 123 | )) 124 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredly/tutorial-cljs/897ab76c448552c50ec15223a55ceeff61f7f9be/screenshot.png -------------------------------------------------------------------------------- /src/tutorial_cljs/core.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.core 2 | (:require [reepl.core :as reepl] 3 | [reepl.replumb :as reepl-replumb] 4 | [replumb.core :as replumb] 5 | 6 | [tutorial-cljs.repl :as repl] 7 | [tutorial-cljs.inline-eval :as inline-eval] 8 | [tutorial-cljs.editor :as editor] 9 | [tutorial-cljs.quil-sketch :as quil-sketch] 10 | 11 | [tutorial-cljs.text.cljs :as text-cljs] 12 | [tutorial-cljs.text.quil :as text-quil] 13 | [tutorial-cljs.text.webgl :as text-webgl] 14 | [tutorial-cljs.text.reagent :as text-reagent] 15 | 16 | [parinfer-codemirror.editor :as parinfer] 17 | 18 | [reepl.show-function :as show-function] 19 | [reepl.show-devtools :as show-devtools] 20 | [reepl.show-value :as show-value] 21 | 22 | [devtools.core :as devtools] 23 | 24 | [quil.core] 25 | [quil.middleware] 26 | 27 | [cljs.tools.reader :as reader] 28 | [cljsjs.codemirror] 29 | [cljs.js :as jsc] 30 | 31 | [reagent.core :as r] 32 | )) 33 | 34 | (def reagent-tag #{:div :span :button :a :table :li :ul :ol :tr :td :input :textarea}) 35 | 36 | (defn valid-reagent-start [val] 37 | (or (reagent-tag val) 38 | (= js/Function (type val)))) 39 | 40 | (defn reagent-shower [val] 41 | (when (and (vector? val) 42 | (valid-reagent-start (first val))) 43 | val)) 44 | 45 | (def tutorials 46 | {:cljs {:text text-cljs/text 47 | :title "ClojureScript" 48 | :prelude "(ns tutorial.cljs (:require [clojure.string :as str]))"} 49 | :quil {:text text-quil/text 50 | :title "Quil" 51 | :special-forms 52 | {'makesketch 53 | #(quil-sketch/handle-make-sketch 54 | (replumb.repl/current-ns) 55 | (reader/read-string %))} 56 | :prelude "(ns tutorial.quil (:require [quil.core :as q]))"} 57 | :reagent {:text text-reagent/text 58 | :title "Reagent" 59 | :showers [reagent-shower] 60 | :prelude "(ns tutorial.reagent (:require [reagent.core :as r]))"} 61 | #_:glsl #_{:text text-webgl/text 62 | :title "WebGL" 63 | :special-forms 64 | {'makegl 65 | identity} 66 | :prelude "(ns tutorial.webgl (:require [gamma.api :as g] [gamma.program :as p]))"} 67 | }) 68 | 69 | ;; TODO "rewind" state when a sketch is paused 70 | ;; TODO persist the text across reloads 71 | ;; TODO auto-pause when an error occurs 72 | ;; TODO pause via keyboard shortcut 73 | ;; TODO keyboard everything; allow "focus" the canvas 74 | ;; TODO control framerate w/ slider 75 | 76 | (defn main []) 77 | 78 | 79 | ;; TODO get reagent docs in here, and gamma 80 | (swap! jsc/*loaded* conj 81 | 'org.processingjs.Processing 82 | 'quil.core 83 | 'quil.middleware 84 | 'quil.util 85 | 'quil.sketch 86 | 'reagent.core 87 | ;; TODO make this real 88 | 'gamma.api 89 | 'gamma.program) 90 | 91 | (defonce -general-setup 92 | (do 93 | (devtools/install!) 94 | (parinfer/start-editor-sync!))) 95 | 96 | (defn maybe-fn-docs [fn] 97 | (let [doc (reepl-replumb/doc-from-sym fn)] 98 | (when (:forms doc) 99 | (with-out-str 100 | (reepl-replumb/print-doc doc))))) 101 | 102 | (defn keyname [name] 103 | (str "text-" name)) 104 | 105 | (defn get-saved [name] 106 | (aget js/localStorage (keyname name))) 107 | 108 | (defn save-text [name text] 109 | (aset js/localStorage (keyname name) text)) 110 | 111 | (def default-showers 112 | [show-devtools/show-devtools 113 | (partial show-function/show-fn-with-docs maybe-fn-docs)]) 114 | 115 | (defn go-to-tutorial [tutorial] 116 | (aset js/location "hash" (name tutorial))) 117 | 118 | (declare setup-tutorial) 119 | 120 | (defn go-from-hash [] 121 | (let [name (.slice (.-hash js/location) 1) 122 | kwd (when (not (empty? name)) 123 | (keyword name))] 124 | (setup-tutorial (if (and (tutorials kwd) kwd) kwd :quil)))) 125 | 126 | (.addEventListener js/window "hashchange" 127 | go-from-hash) 128 | 129 | (defn setup-tutorial [name] 130 | (js/console.log "setup" name) 131 | (let [{:keys [title text prelude special-forms showers] :as tutorial} (tutorials name)] 132 | 133 | (editor/render-text 134 | (or (get-saved name) 135 | text) 136 | (concat showers default-showers) 137 | special-forms 138 | (partial save-text name)) 139 | 140 | ;; # Render REPL 141 | ;; TODO pass in custom completers too 142 | (let [repl-el (js/document.getElementById "repl")] 143 | (r/render [repl/repl-view 144 | name 145 | (map #(assoc (second %) :name (first %)) tutorials) 146 | #(go-to-tutorial %) 147 | (concat showers default-showers) 148 | #(do 149 | (save-text name text) 150 | (setup-tutorial name))] repl-el)) 151 | 152 | (reepl-replumb/run-repl prelude 153 | repl/replumb-opts 154 | identity))) 155 | 156 | (go-from-hash) 157 | -------------------------------------------------------------------------------- /src/tutorial_cljs/editor.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.editor 2 | (:require 3 | [reepl.core :as reepl] 4 | [tutorial-cljs.repl :as repl] 5 | [tutorial-cljs.inline-eval :as inline-eval] 6 | )) 7 | 8 | (defn render-text [initial-text showers special-forms on-change] 9 | (let [text-el (js/document.getElementById "text") 10 | complete-cmd (reepl/make-complete-cmd repl/auto-complete repl/complete-atom)] 11 | 12 | (aset text-el "innerHTML" "") 13 | (def text-mirror 14 | (js/CodeMirror. 15 | text-el 16 | #js {:lineNumbers true 17 | :matchBrackets true 18 | :cursorScrollMargin 5 19 | :value initial-text 20 | :keyMap (if (:vim @repl/settings) "vim" "default") 21 | :extraKeys #js {"Shift-Cmd-Enter" (fn [_] (inline-eval/hide-display)) 22 | "Shift-Ctrl-Enter" (fn [_] (inline-eval/hide-display)) 23 | "Ctrl-Enter" (fn [cm] 24 | (inline-eval/eval-current-form cm showers special-forms)) 25 | "Cmd-Enter" (fn [cm] 26 | (inline-eval/eval-current-form cm showers special-forms))} 27 | :autoCloseBrackets true 28 | :mode "clojure" 29 | #_(if (:parinfer @settings) 30 | "clojure-parinfer" 31 | "clojure")})) 32 | ;; TODO debounce? 33 | (.on text-mirror "change" #(on-change (.getValue text-mirror))) 34 | 35 | ;; TODO parinfer is way too slow for large files -- once it speeds up, it 36 | ;; would be awesome to have that here. 37 | #_(if (:parinfer @settings) 38 | (parinfer/parinferize! text-mirror 39 | (str "text" (swap! pi-count inc)) 40 | :indent-mode 41 | initial-text)) 42 | 43 | (.on text-mirror "keydown" 44 | (fn [inst evt] 45 | (case (.-keyCode evt) 46 | (17 18 91 93) ((complete-cmd :show-all)) 47 | 9 ((complete-cmd :cycle) (.-shiftKey evt) text-mirror evt) 48 | :none))) 49 | 50 | (.on text-mirror "keyup" 51 | (fn [inst evt] 52 | (reepl.code-mirror/complete-keyup complete-cmd (.-keyCode evt) inst))) 53 | 54 | (.addEventListener text-el "mouseup" 55 | (fn [evt] 56 | ((complete-cmd :set) (reepl.code-mirror/get-word-and-range text-mirror)))) 57 | )) 58 | 59 | -------------------------------------------------------------------------------- /src/tutorial_cljs/inline_eval.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.inline-eval 2 | (:require 3 | [reagent.core :as r] 4 | [cljs.tools.reader :as reader] 5 | [reepl.replumb :as reepl-replumb] 6 | [tutorial-cljs.repl :as repl] 7 | [tutorial-cljs.quil-sketch :as quil-sketch] 8 | )) 9 | 10 | (def styles 11 | { 12 | :output-wrapper {:position :relative 13 | :top "-1.5em" 14 | :left "2em" 15 | :background-color :white 16 | :box-shadow "0 0 5px #aaa" 17 | ;; :opacity 0.9 18 | :z-index 10000 19 | :padding 5 20 | :border-radius 5} 21 | :error {:background-color :white 22 | :color :red} 23 | :error-cause {:margin-left 10} 24 | }) 25 | 26 | (defn to-pos [obj] 27 | {:line (.-line obj) 28 | :ch (.-ch obj)}) 29 | 30 | ;; TODO if you are at the start of a token, then return that one... 31 | (defn get-form-at-cursor 32 | "Returns [form-as-string end-pos]" 33 | [cm cursor] 34 | (let [token (.getTokenAt cm cursor)] 35 | (js/console.log "token" token) 36 | (case (.-type token) 37 | "bracket" (let [rr (.findMatchingBracket cm cursor) 38 | [from to] (if (.-forward rr) 39 | [(.-from rr) (.-to rr)] 40 | [(.-to rr) (.-from rr)]) 41 | to #js {:line (.-line to) 42 | :ch (inc (.-ch to))}] 43 | [(.getRange cm from to) to]) 44 | ("comment" nil) (if (or (= 0 (.-start token)) 45 | (= 0 (count (.-string token)))) 46 | (when-not (= 0 (.-line cursor)) 47 | (js/console.log token) 48 | (let [line (.getLine cm (dec (.-line cursor)))] 49 | (recur cm #js {:line (dec (.-line cursor)) 50 | :ch (count line)}))) 51 | (recur cm #js {:line (.-line cursor) 52 | :ch (.-start token)})) 53 | "string" [(str \" (.-string token)) #js {:line (.-line cursor) 54 | :ch (.-end token)}] 55 | [(.-string token) #js {:line (.-line cursor) 56 | :ch (.-end token)}]))) 57 | 58 | (defn get-active-form 59 | "Returns [form-as-string end-pos]" 60 | [cm] 61 | (let [selection (first (.listSelections cm)) 62 | start (to-pos (.-anchor selection)) 63 | end (to-pos (.-head selection))] 64 | (if (= start end) 65 | (get-form-at-cursor cm (.getCursor cm)) 66 | [(.getSelection cm) (clj->js end)]))) 67 | 68 | 69 | (def display-el (js/document.createElement "div")) 70 | 71 | (defn hide-display [] 72 | (when (.-parentNode display-el) 73 | (.removeChild (.-parentNode display-el) display-el))) 74 | 75 | (defn show-error [error] 76 | (let [cause (.-cause error)] 77 | [:div {:style (:error styles)} 78 | (.-message error) 79 | (when cause 80 | [:span {:style (:error-cause styles)} 81 | (.-message cause)])])) 82 | 83 | (defn show-output-view [showers success? value] 84 | [:div {:style (:output-wrapper styles)} 85 | (if-not success? 86 | [show-error value] 87 | [reepl.show-value/show-value 88 | value 89 | nil 90 | {:showers showers}])]) 91 | 92 | (defn show-output [cm showers pos success? value] 93 | ;; TODO display things cooler. using cljs-devtools, etc. 94 | ;; (js* "debugger;") 95 | (r/render (show-output-view showers success? value) display-el) 96 | (.addWidget cm pos display-el true)) 97 | 98 | (defn eval-current-form [cm showers special-forms] 99 | (when-let [[form pos] (get-active-form cm) 100 | ] 101 | (let [form (.trim form) 102 | is-list (= \( (first form)) 103 | first-item (and is-list 104 | (reader/read-string (.slice form 1))) 105 | special-handle (and special-forms (special-forms first-item))] 106 | (if special-handle 107 | (special-handle form) 108 | (reepl-replumb/run-repl 109 | form 110 | repl/replumb-opts 111 | (partial show-output cm showers pos)))))) 112 | -------------------------------------------------------------------------------- /src/tutorial_cljs/quil_complete.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.quil-complete 2 | (:require 3 | [tutorial-cljs.quil-docs :as quil-docs] 4 | [reepl.replumb :as reepl-replumb] 5 | [replumb.repl] 6 | [replumb.ast] 7 | )) 8 | 9 | (def quil-names 10 | (sort (map :name quil-docs/docs))) 11 | 12 | (def quil-map 13 | (into {} (map #(-> [(:name %) (assoc % :type :normal :name (symbol 'quil.core (:name %)))]) quil-docs/docs))) 14 | 15 | (defn quil-prefix [] 16 | (let [nss (:requires (replumb.ast/namespace @replumb.repl/st (replumb.repl/current-ns))) 17 | qss (filter #(= 'quil.core (second %)) nss) 18 | name (first (first qss))] 19 | name)) 20 | 21 | (defn quil-complete [prefix text] 22 | (let [name (second (.split text "/"))] 23 | (->> quil-names 24 | (filter 25 | #(not (= -1 (.indexOf (str %) name)))) 26 | (map str) 27 | (sort (partial reepl-replumb/compare-completion name)) 28 | (map 29 | #(-> [(symbol prefix %) (str (symbol prefix %)) (str (symbol prefix %)) %])) 30 | ))) 31 | 32 | (defn quil-doc [text] 33 | (let [name (second (.split text "/"))] 34 | (with-out-str 35 | (reepl-replumb/print-doc (quil-map (symbol name)))))) 36 | -------------------------------------------------------------------------------- /src/tutorial_cljs/quil_sketch.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.quil-sketch 2 | (:require org.processingjs.Processing 3 | [quil.core :as q] 4 | [quil.middleware :as m] 5 | [quil.sketch] 6 | [reagent.core :as r] 7 | )) 8 | 9 | (defn parse-make-sketch [form] 10 | {:post [(every? (comp not nil?) 11 | (map % [:setup :update :draw :size]))]} 12 | (try 13 | (let [name (second form) 14 | opts (apply hash-map (drop 2 form))] 15 | (assoc opts :name name)) 16 | (catch js/Error e 17 | (throw (js/Error. "Invalid makesketch form"))))) 18 | 19 | (defonce current-sketch (atom nil)) 20 | (defonce current-applet (atom nil)) 21 | 22 | (defonce applets (atom {})) 23 | 24 | (def styles 25 | { 26 | :container {:position :absolute 27 | :z-index 10000 28 | :box-shadow "0 0 5px #999" 29 | :border-radius 2 30 | :background-color :white 31 | } 32 | :handle {:cursor :pointer 33 | :display :flex 34 | :justify-content :center 35 | :align-items :center 36 | } 37 | :title {:flex 1 38 | :padding "5px 10px" 39 | :font-weight :bold} 40 | :close-button {:cursor :pointer 41 | :background-color :transparent 42 | :border :none 43 | :font-weight :bold 44 | :font-size "1.2em" 45 | :padding "6px 10px 8px" 46 | :line-height "10px" 47 | :color "#999"} 48 | :play-pause-button {:cursor :pointer 49 | :background-color :transparent 50 | :border :none 51 | :font-weight :bold 52 | :font-size "1em" 53 | :padding "6px 10px 8px" 54 | :line-height "10px" 55 | :color "black"} 56 | :pause-symbol {:border-width "0 4px 0 4px" 57 | :border-style "solid" 58 | :width 2 59 | :display "inline-block" 60 | :height 12 61 | :margin "0 2px 0px 2px" 62 | :border-color "#aaa"} 63 | }) 64 | 65 | (defn canvas [[width height] on-canvas] 66 | (r/create-class 67 | {:component-did-mount 68 | (fn [this] 69 | (on-canvas (r/dom-node this))) 70 | :reagent-render 71 | (fn [] 72 | [:canvas {:width width 73 | :tab-index 0 74 | :height height}])})) 75 | 76 | (defn mobile-sketch [name title size loop-atom pos-atom on-canvas on-close] 77 | (let [{:keys [left top]} @pos-atom 78 | state (r/atom {:top top 79 | :left left 80 | :moving false 81 | :dx 0 82 | :dy 0}) 83 | move-listener (fn [evt] 84 | (.preventDefault evt) 85 | (swap! state (fn [{:keys [dx dy] :as state}] 86 | (assoc state 87 | :top (- (.-clientY evt) dy) 88 | :left (- (.-clientX evt) dx))))) 89 | up-listener (fn up-listener [evt] 90 | (let [{:keys [top left]} @state] 91 | (swap! pos-atom assoc :top top :left left)) 92 | (.preventDefault evt) 93 | (.removeEventListener js/window "mouseup" up-listener) 94 | (.removeEventListener js/window "mousemove" move-listener) 95 | (swap! state assoc :moving false)) 96 | ] 97 | (fn [] 98 | (let [{:keys [top left moving]} @state] 99 | [:div 100 | {:style (merge (:container styles) 101 | {:top top 102 | :left left})} 103 | [canvas size on-canvas] 104 | [:div 105 | {:style (:handle styles) 106 | :on-mouse-down 107 | (fn [evt] 108 | (.preventDefault evt) 109 | (.addEventListener js/window "mouseup" up-listener) 110 | (.addEventListener js/window "mousemove" move-listener) 111 | (swap! state assoc 112 | :dx (- (.-clientX evt) left) 113 | :dy (- (.-clientY evt) top) 114 | :moving true)) 115 | } 116 | ;; [:span {:style (:name styles)} name] 117 | [:button {:style (:play-pause-button styles) 118 | :title "Play/Pause" 119 | :on-click #(swap! loop-atom not)} 120 | (if @loop-atom 121 | [:span {:style (:pause-symbol styles)}] 122 | #_"◼" 123 | "▶")] 124 | [:span {:style (:title styles)} title] 125 | [:button {:on-click on-close 126 | :title "Close" 127 | :style (:close-button styles)} 128 | "×"] 129 | ]])))) 130 | 131 | (defn get-in-ns [ns sym] 132 | (let [parts (vec (map munge (.split (str ns) ".")))] 133 | (reduce (fnil aget (js-obj)) 134 | js/window 135 | (conj parts (str sym))))) 136 | 137 | (defn get-fn [ns sym] 138 | (fn [state] 139 | (let [f (get-in-ns ns sym) 140 | res (when (and f (= js/Function (type f))) 141 | (f state))] 142 | (or res state)))) 143 | 144 | (defn handle-make-sketch [ns form] 145 | (js/console.log "make sketch" form) 146 | (let [opts (parse-make-sketch form) 147 | old-data (@applets (:name opts)) 148 | _ (js/console.log (:name opts) old-data @applets) 149 | [width height] (:size opts) 150 | node (or (:node old-data) 151 | (js/document.createElement "div")) 152 | pos-atom (or (:pos-atom old-data) 153 | (atom {:top 20 154 | :left (- (.-innerWidth js/window) width 20)})) 155 | loop-atom (or (:loop-atom old-data) 156 | (r/atom true)) 157 | _ (js/React.unmountComponentAtNode node) 158 | sketch (quil.sketch/make-sketch 159 | ;; TODO get actual values for title & size... 160 | {:title (:title opts) 161 | :setup (fn [] 162 | (let [new-applet (quil.sketch/current-applet) 163 | old-loop (.bind (.-loop new-applet) new-applet) 164 | old-no-loop (.bind (.-noLoop new-applet) new-applet) 165 | ] 166 | (aset new-applet "loop" #(do (reset! loop-atom true) 167 | (old-loop))) 168 | (aset new-applet "noLoop" #(do (reset! loop-atom false) 169 | (old-no-loop))) 170 | (swap! applets update (:name opts) 171 | (fn [old] 172 | (when old (.exit (:applet old))) 173 | {:node node 174 | :pos-atom pos-atom 175 | :loop-atom loop-atom 176 | :applet new-applet})) 177 | (add-watch loop-atom :wat 178 | (fn [_ _ _ new-val] 179 | (if new-val 180 | (old-loop) 181 | (old-no-loop)))) 182 | (reset! current-applet new-applet) 183 | ;; Allow q/* functions to be used from the REPL 184 | (js/setTimeout #(set! quil.sketch/*applet* new-applet) 5) 185 | ((get-fn ns (:setup opts))))) 186 | :draw (get-fn ns (:draw opts)) 187 | :update (get-fn ns (:update opts)) 188 | :middleware [m/fun-mode] 189 | :size (:size opts)})] 190 | (js/document.body.appendChild node) 191 | (r/render 192 | [mobile-sketch 193 | (str (:name opts)) 194 | (:title opts) 195 | (:size opts) 196 | loop-atom 197 | pos-atom 198 | #(js/Processing. % sketch) 199 | (fn [] 200 | (when-let [data (@applets (:name opts))] 201 | (.exit (:applet data)) 202 | (when-let [parent (.-parentNode (:node data))] 203 | (.removeChild parent (:node data))) 204 | (swap! applets dissoc (:name opts))))] 205 | node) 206 | (reset! current-sketch sketch))) 207 | -------------------------------------------------------------------------------- /src/tutorial_cljs/repl.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.repl 2 | (:require [reepl.core :as reepl] 3 | [reepl.replumb :as reepl-replumb] 4 | [replumb.core :as replumb] 5 | 6 | [reepl.show-function :as show-function] 7 | [reepl.show-devtools :as show-devtools] 8 | [reepl.show-value :as show-value] 9 | 10 | [devtools.core :as devtools] 11 | 12 | [tutorial-cljs.quil-complete :as quil-complete] 13 | 14 | [parinfer-codemirror.editor :as parinfer] 15 | [parinfer.codemirror.mode.clojure.clojure-parinfer] 16 | [reagent.core :as r] 17 | )) 18 | 19 | (def styles 20 | {:container {:display :flex 21 | :flex-direction :column 22 | :flex 1 23 | :align-self :stretch 24 | :justify-content :center 25 | :align-items :center} 26 | :choose-container { 27 | :flex 1 28 | :flex-direction :row 29 | :align-items :center 30 | :margin-bottom 5 31 | :padding-left 10 32 | } 33 | :chooser-title {:font-size "1.5em" 34 | :margin-right 10} 35 | :chooser {:font-size "1.5em" 36 | :margin-right 5} 37 | :repl {:display :flex 38 | :flex 1 39 | :align-self :stretch 40 | :margin-left 10 41 | :background-color :white 42 | :border-radius 5} 43 | :bottom {:display :flex 44 | :align-self :stretch 45 | :margin-left 10 46 | :flex-direction :row 47 | :align-items :center 48 | :margin-bottom 5 49 | :color "#ddd"} 50 | :label {:margin "0 5px" 51 | :display :flex 52 | :flex-direction :row 53 | :align-items :center 54 | :font-size ".5em" 55 | :cursor :pointer} 56 | :checkbox {:margin-right 5 57 | :font-size ".5em"} 58 | 59 | :reset-button {:border :none 60 | :background-color :transparent 61 | :cursor :pointer 62 | :color "#faa" 63 | :margin-right 20 64 | :font-size ".8em" 65 | :padding "0 5px"} 66 | 67 | :link {:color "#aaa" 68 | :text-decoration :none 69 | :margin "2px 10px 0"} 70 | }) 71 | 72 | (def replumb-opts 73 | (merge (replumb/browser-options 74 | ["/js/main.out"] 75 | reepl-replumb/fetch-file!))) 76 | 77 | ;; Used to make the repl reload-tolerant 78 | (defonce repl-state 79 | (r/atom reepl/initial-state)) 80 | 81 | (defonce complete-atom 82 | (r/atom nil)) 83 | 84 | (defn maybe-fn-docs [fn] 85 | (let [doc (reepl-replumb/doc-from-sym fn)] 86 | (when (:forms doc) 87 | (with-out-str 88 | (reepl-replumb/print-doc doc))))) 89 | 90 | (def default-settings 91 | {:vim false 92 | :warning-as-error true 93 | :parinfer true}) 94 | 95 | (defn get-settings [] 96 | (let [val js/localStorage.reeplSettings] 97 | (if-not val 98 | default-settings 99 | (try 100 | (js->clj (js/JSON.parse val) :keywordize-keys true) 101 | (catch js/Error _ 102 | default-settings))))) 103 | 104 | (defn save-settings [settings] 105 | (let [str (js/JSON.stringify (clj->js settings))] 106 | (aset js/localStorage "reeplSettings" str))) 107 | 108 | (defonce 109 | settings (r/atom (get-settings))) 110 | 111 | (defonce pi-count (atom 0)) 112 | 113 | 114 | (defn checkbox [keyword title] 115 | [:label 116 | {:style (:label styles)} 117 | [:input {:type "checkbox" 118 | :checked (keyword @settings) 119 | :style (:checkbox styles) 120 | :on-change #(swap! settings update keyword not) 121 | }] 122 | title]) 123 | 124 | ;; TODO abstract out the quil-complete stuff 125 | (defn auto-complete [sym] 126 | (let [text (str sym) 127 | quil (quil-complete/quil-prefix)] 128 | (if (and quil (= 0 (.indexOf text (str quil "/")))) 129 | (quil-complete/quil-complete quil text) 130 | (reepl-replumb/process-apropos sym)))) 131 | 132 | (defn get-docs [sym] 133 | (let [text (str sym) 134 | quil (quil-complete/quil-prefix)] 135 | (if (and quil (= 0 (.indexOf text (str quil "/")))) 136 | (quil-complete/quil-doc text) 137 | (reepl-replumb/process-doc sym)))) 138 | 139 | (defn repl-view [current-tutorial tutorials on-tutorial showers reset-text] 140 | [:div 141 | {:style (:container styles)} 142 | [:div {:style (styles :bottom)} 143 | [:div {:style (styles :choose-container)} 144 | [:span {:style (:chooser-title styles)} 145 | "Tutorial"] 146 | (into 147 | 148 | [:select {:style (:chooser styles) 149 | :value (name current-tutorial) 150 | :on-change #(on-tutorial (keyword (.-target.value %)))}] 151 | (map #(-> [:option {:value (name (:name %))} 152 | (:title %)]) tutorials)) 153 | 154 | [:button {:style (:reset-button styles) 155 | :on-click reset-text} 156 | "Revert Text"]] 157 | [checkbox :vim "Vim"] 158 | [checkbox :parinfer "Parinfer"] 159 | [checkbox :warning-as-error "Warning as error"] 160 | [:a {:href "https://github.com/jaredly/tutorial-cljs" 161 | :target :_blank 162 | :style (:link styles)} 163 | "Github"] 164 | ] 165 | [:div {:style (:repl styles)} 166 | [reepl/repl 167 | :execute #(reepl-replumb/run-repl %1 168 | (merge replumb-opts 169 | {:warning-as-error (:warning-as-error @settings)}) %2) 170 | :complete-word auto-complete 171 | :get-docs get-docs 172 | :state repl-state 173 | :complete-atom complete-atom 174 | :show-value-opts 175 | {:showers showers} 176 | :js-cm-opts {:mode "clojure-parinfer" 177 | :keyMap (if (:vim @settings) "vim" "default") 178 | :showCursorWhenSelecting true} 179 | :on-cm-init #(parinfer/parinferize! % (swap! pi-count inc) 180 | :indent-mode (.getValue %))]]]) 181 | 182 | (add-watch settings :settings #(do 183 | ;; (render-text) 184 | (save-settings %4))) 185 | -------------------------------------------------------------------------------- /src/tutorial_cljs/text/cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.text.cljs) 2 | 3 | (def text 4 | ";; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 | ;; +++++++++++++++ +++++++++++++++ 7 | ;; An Interactive Introduction to ClojureScript 8 | ;; +++++++++++++++ +++++++++++++++ 9 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | 12 | ;; Adapted from David Nolen's tutorial for Light Table users 13 | ;; https://github.com/swannodette/lt-cljs-tutorial/blob/master/lt-cljs-tutorial.cljs 14 | 15 | ;; Using this Tutorial 16 | ;; ============================================================================ 17 | 18 | ;; # Evaluating forms 19 | ;; 20 | ;; You can evaluate forms in this tutorial by putting your cursor at the end of 21 | ;; the form and pressing \"Cmd-Enter\". The output will be displayed to the 22 | ;; right of your cursor. You can dismiss the output view (if it's in your way) 23 | ;; with \"Cmd-Shift-Enter\". 24 | ;; 25 | ;; Try evaluating this: 26 | 27 | (+ 1 2) ; <- put your cursor right after the closing ) and press Cmd-Enter 28 | 29 | ;; ^ You can also put your cursor on the following line and press Cmd-Enter 30 | 31 | ;; Ok, that was cool, but how about some data that's more complicated? 32 | 33 | {:some 10 34 | :other 20 35 | :list (range 10)} ; evaluate this, and you'll be able to interact with the result 36 | 37 | ;; # Documentation + auto-complete 38 | ;; 39 | ;; If you click `range` in that code above, the documentation for the range 40 | ;; function will appear in the bottom-right corner of this page. You can type 41 | ;; into this document, and documentation + auto-complete suggestions will 42 | ;; appear. Press Tab (and Shift-Tab) to cycle through the suggestions. 43 | ;; 44 | ;; Go ahead, put your cursor at the end of `map`, and see what other functions 45 | ;; have `map` in the name. 46 | 47 | map 48 | 49 | ;; # The REPL 50 | ;; 51 | ;; The right hand pane is a REPL where you can type in clojurescript code and 52 | ;; see the results. It will show you documentation + auto-complete suggestions 53 | ;; as well. 54 | 55 | 56 | ;; Basics 57 | ;; ============================================================================ 58 | 59 | ;; Declaring a namespaces 60 | ;; ---------------------------------------------------------------------------- 61 | 62 | ;; ClojureScript supports modularity via namespaces. They allow you to group 63 | ;; logical definitions together. 64 | 65 | (ns lt-cljs-tutorial.main 66 | (:require [clojure.string :as str])) 67 | 68 | ;; NOTE: We've evaluated this namespace definition for you already, so if you 69 | ;; type ::hello in the REPL, you'll see `:lt-cljs-tutorial.main/hello' 70 | 71 | ;; :require is how you can import functionality from a different namespace into 72 | ;; the current one. Here we are requiring `clojure.string` and giving it an 73 | ;; alias. We could write the following: 74 | 75 | (clojure.string/blank? \"\") 76 | 77 | ;; But that's really verbose compared to: 78 | 79 | (str/blank? \"\") 80 | 81 | 82 | ;; Comments 83 | ;; ---------------------------------------------------------------------------- 84 | 85 | ;; There are three ways to create comments in ClojureScript. The first way is 86 | ;; by preceding a line with a semi-colon, just like the lines you are reading 87 | ;; now. 88 | 89 | ;; The second way is by preceding a form with `#_`. This causes ClojureScript 90 | ;; to skip the evaluation of only the form immediately following, without 91 | ;; affecting the evaluation of the surrounding forms. 92 | 93 | ;; Try to reveal the secret message below: 94 | 95 | (str \"The secret word is \" #_(str/reverse \"tpircSerujolC\")) 96 | 97 | ;; Finally, you can also create a comment using the `comment` macro. One common 98 | ;; technique is to use the `comment` macro to include code to be evaluated in a 99 | ;; REPL, but which you do not normally want to be included in the compiled 100 | ;; source. 101 | 102 | ;; For example, try placing your cursor after the last `)` below and type 103 | ;; Command-ENTER: 104 | 105 | (comment 106 | 107 | (str/upper-case \"This is only a test...\") 108 | 109 | ) 110 | 111 | ;; The `comment` macro makes the whole form return `nil`. Now go back and 112 | ;; place your cursor after the middle line, then type Command-ENTER. In this way 113 | ;; you can include code samples or quick tests in-line with the rest of 114 | ;; your code. 115 | 116 | 117 | ;; Definitions 118 | ;; ---------------------------------------------------------------------------- 119 | 120 | ;; Once you have a namespace, you can start creating top level definitions in 121 | ;; that namespace. 122 | 123 | ;; You can define a top level with `def`. 124 | 125 | (def x 1) 126 | 127 | x 128 | 129 | ;; You can also refer to top level definitions by fully qualifying them. 130 | 131 | lt-cljs-tutorial.main/x 132 | 133 | ;; This means top levels can never be shadowed by locals and function 134 | ;; parameters. 135 | 136 | (let [x 2] 137 | lt-cljs-tutorial.main/x) 138 | 139 | ;; One way to define a function is like this. 140 | 141 | (def y (fn [] 1)) 142 | 143 | (y) 144 | 145 | ;; Defining functions in ClojureScript is common enough that `defn` sugar is 146 | ;; provided and idiomatic. 147 | 148 | (defn z [] 1) 149 | 150 | (z) 151 | 152 | 153 | ;; Literal data types 154 | ;; ---------------------------------------------------------------------------- 155 | 156 | ;; ClojureScript comes out of the box with the usual useful data literals. 157 | 158 | ;; Booleans 159 | 160 | (def a-boolean true) 161 | 162 | ;; Strings 163 | 164 | (def a-string \"Hello!\") 165 | 166 | ;; Regular Expressions 167 | 168 | (def a-regexp #\"\\d{3}-?\\d{3}-?\\d{4}\") 169 | 170 | ;; Numbers 171 | 172 | (def a-number 1) 173 | 174 | 175 | ;; Function literals 176 | ;; ---------------------------------------------------------------------------- 177 | 178 | ;; ClojureScript also supports a shorthand function literal which is useful 179 | ;; You can use the % and %N placeholders to represent function arguments. 180 | 181 | ;; You should not abuse the function literal notation as it degrades readability 182 | ;; outside of simple cases. It is nice for simple functional cases such as 183 | ;; the following. You could map over a ClojureScript vector like this: 184 | 185 | (map (fn [n] (* n 2)) [1 2 3 4 5]) 186 | 187 | ;; Or you can save typing a few characters like this: 188 | 189 | (map #(* % 2) [1 2 3 4 5]) 190 | 191 | 192 | ;; JavaScript data type literals 193 | ;; ---------------------------------------------------------------------------- 194 | 195 | ;; You can construct a JavaScript array with the `array` function. 196 | 197 | (def an-array (array 1 2 3)) 198 | 199 | ;; But ClojureScript also supports JavaScript data literals via the `#js` 200 | ;; reader literal. 201 | 202 | (def another-array #js [1 2 3]) 203 | 204 | ;; Similarly, you can create simple JavaScript objects with `js-obj`. 205 | 206 | (def an-object (js-obj \"foo\" \"bar\")) 207 | 208 | ;; But again you can save a few characters with `#js`. 209 | 210 | (def another-object #js {\"foo\" \"bar\"}) 211 | 212 | ;; It's important to note that `#js` is shallow, the contents of `#js` will be 213 | ;; ClojureScript data unless preceded by `#js`. 214 | 215 | ;; This is a mutable JavaScript object with an immutable ClojureScript vector 216 | ;; inside. 217 | 218 | (def shallow #js {\"foo\" [1 2 3]}) 219 | 220 | 221 | ;; Constructing a type 222 | ;; ---------------------------------------------------------------------------- 223 | 224 | ;; Of course some JavaScript data types you will want to create with a 225 | ;; constructor. 226 | 227 | ;; (js/Date.) is equivalent to new Date(). 228 | 229 | (def a-date (js/Date.)) 230 | 231 | (def another-date #inst \"2014-01-15\") 232 | 233 | ;; Note the above returns an `#inst` data literal. 234 | 235 | (def another-regexp (js/RegExp. \"\\\\d{3}-?\\\\d{3}-?\\\\d{4}\")) 236 | 237 | ;; Handy 238 | 239 | ;; NOTE: js/Foo is how you refer to global JavaScript entities of any kind. 240 | 241 | js/Date 242 | 243 | js/RegExp 244 | 245 | js/requestAnimationFrame 246 | 247 | ;; If you're curious about other JavaScript interop jump to the bottom of this 248 | ;; tutorial. 249 | 250 | 251 | ;; ClojureScript data types 252 | ;; ============================================================================ 253 | 254 | ;; Unless there is a good reason, you should generally write your ClojureScript 255 | ;; programs with ClojureScript data types. They have many advantages over 256 | ;; JavaScript data types - they present a uniform API and they are immutable. 257 | 258 | ;; Vectors 259 | ;; ---------------------------------------------------------------------------- 260 | 261 | ;; Instead of arrays, ClojureScript programmers use persistent vectors. They are 262 | ;; like arrays - they support efficient random access, efficient update 263 | ;; and efficient addition to the end. 264 | 265 | (def a-vector [1 2 3 4 5]) 266 | 267 | ;; We can get the length of a vector in constant time via `count`. 268 | 269 | (count a-vector) 270 | 271 | ;; We can add an element to the end. 272 | 273 | (def another-vector (conj a-vector 6)) 274 | 275 | ;; Note this does not mutate the array! `a-vector` will be left 276 | ;; unchanged. 277 | 278 | a-vector 279 | 280 | another-vector 281 | 282 | ;; Hallelujah! Here is where some ClojureScript magic 283 | ;; happens. `another-vector` appears to be a completely new vector 284 | ;; compared to `a-vector`. But it is not really so. Internally, the new 285 | ;; vector efficiently shares the `a-vector` structure. In this way, you 286 | ;; get the benefits of immutability without paying in performance. 287 | 288 | ;; We can access any element in a vector with `nth`. The following 289 | ;; will return the second element. 290 | 291 | (nth a-vector 1) 292 | 293 | (nth [\"foo\" \"bar\" \"baz\"] 1) 294 | 295 | ;; Or with `get`... 296 | 297 | (get a-vector 0) 298 | 299 | ;; ...which allows you to return an alternate value when the index is 300 | ;; out-of bounds. 301 | 302 | (get a-vector -1 :out-of-bounds) 303 | (get a-vector (count a-vector) :out-of-bounds) 304 | 305 | ;; Surprisingly, vectors can be treated as functions. This is actually 306 | ;; a very useful property for associative data structures to have as 307 | ;; we'll see below with sets. 308 | 309 | (a-vector 1) 310 | 311 | ([\"foo\" \"bar\" \"baz\"] 1) 312 | 313 | 314 | ;; Maps 315 | ;; ---------------------------------------------------------------------------- 316 | 317 | ;; Along with vectors, maps are the most common data type in ClojureScript. 318 | ;; Map usage is analogous to the usage of Object in JavaScript, but 319 | ;; ClojureScript maps are immutable and considerably more flexible. 320 | 321 | ;; Let's define a simple map. Note `:foo` is a ClojureScript keyword. 322 | ;; ClojureScript programmers prefer to use keywords for keys instead 323 | ;; of strings. They are more distinguishable from the rest of the 324 | ;; code, more efficient than plain strings, and they can be used in 325 | ;; function position (i.e. first position after the open parens), as 326 | ;; we'll see in a moment. 327 | 328 | (def a-map {:foo \"bar\" :baz \"woz\"}) 329 | 330 | ;; We can get the number of key-value pairs in constant time. 331 | 332 | (count a-map) 333 | 334 | ;; We can access a particular value for a key with `get`. 335 | 336 | (get a-map :foo) 337 | 338 | ;; and return an alternative value when the key is not present 339 | 340 | (get a-map :bar :not-found) 341 | 342 | ;; We can add a new key-value pair with `assoc`. 343 | 344 | (def another-map (assoc a-map :noz \"goz\")) 345 | 346 | ;; Again a-map is unchanged! Same magic as before for vectors 347 | 348 | a-map 349 | 350 | another-map 351 | 352 | ;; We can remove a key-value pair with `dissoc`. 353 | 354 | (dissoc a-map :foo) 355 | 356 | ;; Again a-map is unchanged! 357 | 358 | a-map 359 | 360 | ;; Like vectors, maps can act like functions. 361 | 362 | (a-map :foo) 363 | 364 | ;; However ClojureScript keywords themselves can act like functions and the 365 | ;; following is more idiomatic. 366 | 367 | (:foo a-map) 368 | 369 | ;; We can check if a map contains a key, with `contains?`. 370 | 371 | (contains? a-map :foo) 372 | 373 | ;; We can get all the keys in a map with `keys`. 374 | 375 | (keys a-map) 376 | 377 | ;; And all of the values with `vals`. 378 | 379 | (vals a-map) 380 | 381 | ;; We can put a lot of things in a map, even other maps 382 | (def a-nested-map {:customer-id 1e6 383 | :preferences {:nickname \"Bob\" 384 | :avatar \"http://en.gravatar.com/userimage/0/0.jpg\"} 385 | :services {:alerts {:daily true}}}) 386 | 387 | ;; and navigate its keys to get the nested value you're interested in 388 | 389 | (get-in a-nested-map [:preferences :nickname]) 390 | (get-in a-nested-map [:services :alerts :daily]) 391 | 392 | ;; or just find a top level key-value pair (i.e. MapEntry) by key 393 | 394 | (find a-nested-map :customer-id) 395 | (find a-nested-map :services) 396 | 397 | ;; There are many cool ways to create maps. 398 | 399 | (zipmap [:foo :bar :baz] [1 2 3]) 400 | 401 | (hash-map :foo 1 :bar 2 :baz 3) 402 | 403 | (apply hash-map [:foo 1 :bar 2 :baz 3]) 404 | 405 | (into {} [[:foo 1] [:bar 2] [:baz 3]]) 406 | 407 | ;; Unlike JavaScript objects, ClojureScript maps support complex keys. 408 | 409 | (def complex-map {[1 2] :one-two [3 4] :three-four}) 410 | 411 | (get complex-map [3 4]) 412 | 413 | 414 | ;; Keyword digression 415 | ;; ---------------------------------------------------------------------------- 416 | 417 | ;; Let's take a moment to digress about keywords as they are so ubiquitous 418 | ;; in ClojureScript code. 419 | 420 | (identity :foo) 421 | 422 | ;; If you add an additional preceding colon you'll get a namespaced keyword. 423 | 424 | (identity ::foo) 425 | 426 | ;; What good is this for? It allows you to put data into collections without 427 | ;; fear of namespace clashes without the tedium of manual namespacing them 428 | ;; in your source. 429 | 430 | (identity {:user/foo ::foo}) 431 | 432 | 433 | ;; Sets 434 | ;; ---------------------------------------------------------------------------- 435 | 436 | ;; ClojureScript also supports sets. 437 | 438 | (def a-set #{:cat :dog :bird}) 439 | 440 | ;; `:cat` is already in `a-set`, so it will be unchanged. 441 | 442 | (conj a-set :cat) 443 | 444 | ;; But `:zebra` isn't. 445 | 446 | (conj a-set :zebra) 447 | 448 | ;; If you haven't guessed already, `conj` is a \"polymorphic\" function that adds 449 | ;; an item to a collection. This is some of the uniformity we alluded to 450 | ;; earlier. 451 | 452 | ;; `contains?` works on sets just like it does on maps. 453 | 454 | (contains? a-set :cat) 455 | 456 | ;; Like vectors and maps, sets can also act as functions. If the argument 457 | ;; exists in the set it will be returned, otherwise the set will return nil. 458 | 459 | (#{:cat :dog :bird} :cat) 460 | 461 | ;; This is powerful when combined with conditionals. 462 | 463 | (defn check [x] 464 | (if (#{:cat :dog :bird} x) 465 | :valid 466 | :invalid)) 467 | 468 | (check :cat) 469 | (check :zebra) 470 | 471 | 472 | ;; Lists 473 | ;; ---------------------------------------------------------------------------- 474 | 475 | ;; A less common ClojureScript data structure is lists. This may be 476 | ;; surprising as ClojureScript is a Lisp, but maps, vectors and sets 477 | ;; are the 'go-to' data structures for most applications. Still, lists are sometimes 478 | ;; useful—especially when dealing with code (i.e. code is data). 479 | 480 | (def a-list '(:foo :bar :baz)) 481 | 482 | ;; `conj` is \"polymorphic\" on lists as well, and it's smart enough to 483 | ;; add the new item in the most efficient way on the basis of the 484 | ;; collection type. 485 | (conj a-list :front) 486 | 487 | ;; and lists are immutable as well 488 | 489 | a-list 490 | 491 | ;; You can get the first element of a list 492 | 493 | (first a-list) 494 | 495 | ;; or the tail of a list 496 | 497 | (rest a-list) 498 | 499 | ;; which allows you to easly verify how ClojureScript shares data 500 | ;; structure instead of inefficiently copying data for supporting 501 | ;; immutability. 502 | 503 | (def another-list (conj a-list :front)) 504 | 505 | another-list 506 | 507 | a-list 508 | 509 | (identical? (rest another-list) a-list) 510 | 511 | ;; `identical?` checks whether two things are represented by the same 512 | ;; thing in memory. 513 | 514 | 515 | ;; Equality 516 | ;; ============================================================================ 517 | 518 | ;; ClojureScript has a much simpler notion of equality than what is present 519 | ;; in JavaScript. In ClojureScript equality is always deep equality. 520 | 521 | (= {:one 1 :two \"2\"} {:one 1 :two \"2\"}) 522 | 523 | ;; Maps are not ordered. 524 | 525 | (= {:one 1 :two \"2\"} {:two \"2\" :one 1}) 526 | 527 | ;; For sequential collections, equality just works. 528 | 529 | (= [1 2 3] '(1 2 3)) 530 | 531 | ;; Again, it is possible to check whether two things are represented 532 | ;; by the same thing in memory with `identical?`. 533 | 534 | (def my-vec [1 2 3]) 535 | (def your-vec [1 2 3]) 536 | 537 | (identical? my-vec your-vec) 538 | 539 | 540 | ;; Control 541 | ;; ============================================================================ 542 | 543 | ;; In order to write useful programs, we need to be able to express 544 | ;; control flow. ClojureScript provides the usual control constructs, 545 | ;; however truth-y and false-y values are not the same as in 546 | ;; JavaScript so it's worth reviewing. 547 | 548 | ;; if 549 | ;; ---------------------------------------------------------------------------- 550 | 551 | ;; 0 is not a false-y value. 552 | 553 | (if 0 554 | \"Zero is not false-y\" 555 | \"Yuck\") 556 | 557 | ;; Nor is the empty string. 558 | 559 | (if \"\" 560 | \"An empty string is not false-y\" 561 | \"Yuck\") 562 | 563 | ;; the empty vector 564 | 565 | (if [] 566 | \"An empty vector is not false-y\" 567 | \"Yuck\") 568 | 569 | ;; the empty list 570 | 571 | (if () 572 | \"An empty list is not false-y\" 573 | \"Yuck\") 574 | 575 | ;; the empty map 576 | 577 | (if {} 578 | \"An empty map is not false-y\" 579 | \"Yuck\") 580 | 581 | ;; the empty set 582 | 583 | (if #{} 584 | \"An empty set is not false-y\" 585 | \"Yuck\") 586 | 587 | ;; and even the empty regexp 588 | 589 | (if #\"\" 590 | \"An empty regexp is not false-y\" 591 | \"Yuck\") 592 | 593 | ;; The only false-y values in ClojureScript are `nil` and `false`. `undefined` 594 | ;; is not really a valid ClojureScript value and is generally coerced to `nil`. 595 | 596 | 597 | ;; cond 598 | ;; ---------------------------------------------------------------------------- 599 | 600 | ;; Nesting `if` tends to be noisy and hard to read so ClojureScript 601 | ;; provides a `cond` macro to deal with this. 602 | 603 | (cond 604 | nil \"Not going to return this\" 605 | false \"Nope not going to return this either\" 606 | :else \"Default case\") 607 | 608 | 609 | ;; loop/recur 610 | ;; ---------------------------------------------------------------------------- 611 | 612 | ;; The most primitive looping construct in ClojureScript is `loop`/`recur`. 613 | ;; Like `let`, `loop` establishes bindings and allows you to set their initial values. 614 | ;; Like `let`, you may have a sequence of forms for the body. In tail 615 | ;; positions, you may write a `recur` statement that will set the bindings for 616 | ;; the next iteration of the `loop`. Using `loop`/`recur` is usually considered bad 617 | ;; style if a reasonable functional solution via `map`/`filter`/`reduce` or a list 618 | ;; comprehension is possible. 619 | 620 | ;; While you might write this in JavaScript: 621 | ;; 622 | ;; var ret = []; 623 | ;; for(var i = 0; i < 10; i++) ret.push(i) 624 | ;; 625 | ;; In ClojureScript you would write `loop`/`recur` like so: 626 | 627 | (loop [i 0 ret []] 628 | (if (< i 10) 629 | (recur (inc i) (conj ret i)) 630 | ret)) 631 | 632 | ;; Again avoid `loop`/`recur` unless you really need it. The loop above would 633 | ;; be better expressed as the following: 634 | 635 | (into [] (range 10)) 636 | 637 | 638 | ;; Moar functions 639 | ;; ============================================================================ 640 | 641 | ;; Functions are the essence of any significant ClojureScript program, so 642 | ;; we will dive into features that are unique to ClojureScript functions that 643 | ;; might be unfamiliar. 644 | 645 | ;; Here is a simple function that takes two arguments and adds them. 646 | 647 | (defn foo1 [a b] 648 | (+ a b)) 649 | 650 | (foo1 1 2) 651 | 652 | ;; Functions can have multiple arities. 653 | 654 | (defn foo2 655 | ([a b] (+ a b)) 656 | ([a b c] (* a b c))) 657 | 658 | (foo2 3 4) 659 | (foo2 3 4 5) 660 | 661 | ;; Multiple arities can be used to supply default values. 662 | 663 | (defn defaults 664 | ([x] (defaults x :default)) 665 | ([x y] [x y])) 666 | 667 | (defaults :explicit) 668 | (defaults :explicit1 :explicit2) 669 | 670 | ;; Functions support rest arguments. 671 | 672 | (defn foo3 [a b & d] 673 | [a b d]) 674 | 675 | (foo3 1 2) 676 | (foo3 1 2 3 4) 677 | 678 | ;; You can apply functions. 679 | 680 | (apply + [1 2 3 4 5]) 681 | 682 | 683 | ;; multimethods 684 | ;; ---------------------------------------------------------------------------- 685 | 686 | ;; Often when you need some polymorphism, and performance isn't an issue, 687 | ;; multimethods will suffice. Multimethods are functions that allow open 688 | ;; extension, but instead of limiting dispatch to type, dispatch is controlled 689 | ;; by whatever value the dispatch fn originally supplied to `defmulti` returns. 690 | 691 | ;; Here is the simplest multimethod you can write. It simply dispatches on 692 | ;; the value received. 693 | 694 | (defmulti simple-multi identity) 695 | 696 | ;; Now we can define methods for particular values. 697 | 698 | (defmethod simple-multi 1 699 | [value] \"Dispatched on 1\") 700 | 701 | (simple-multi 1) 702 | 703 | (defmethod simple-multi \"foo\" 704 | [value] \"Dispatched on foo\") 705 | 706 | (simple-multi \"foo\") 707 | 708 | ;; However we haven't defined a case for \"bar\" 709 | ; (Highlight and evaluate the `simple-multi` form below) 710 | (comment 711 | (simple-multi \"bar\") 712 | ) 713 | 714 | 715 | ;; Here is a function that takes a list. It dispatches on the first element 716 | ;; of the list! 717 | ;; Note that this example uses destructuring, which is covered later. 718 | 719 | (defmulti parse (fn [[f & r :as form]] f)) 720 | 721 | (defmethod parse 'if 722 | [form] {:op :if}) 723 | 724 | (defmethod parse 'let 725 | [form] {:op :let}) 726 | 727 | (parse '(if a b c)) 728 | (parse '(let [x 1] x)) 729 | 730 | 731 | ;; Scoping 732 | ;; ============================================================================ 733 | 734 | ;; Unlike JavaScript, there is no hoisting in ClojureScript. ClojureScript 735 | ;; has lexical scoping. 736 | 737 | (def some-x 1) 738 | 739 | (let [some-x 2] 740 | some-x) 741 | 742 | some-x 743 | 744 | ;; Closures 745 | ;; ---------------------------------------------------------------------------- 746 | 747 | ;; Could a language with such a name miss closures? Surely it can't. You 748 | ;; may be already familiar with them in JavaScript, even if it's a 749 | ;; variable scoped language. 750 | 751 | (let [a 1e3] 752 | (defn foo [] 753 | (* a a)) 754 | (defn bar [] 755 | (+ (foo) a))) 756 | 757 | ;; Above we defined `foo` and `bar` functions inside the scope of a 758 | ;; `let` form and they both know about `a` (i.e. they close over `a`) 759 | ;; Note, even if defined inside a `let`, `foo` and `bar` are available 760 | ;; in the outer scope. This is because all `def` expressions are always 761 | ;; top level. See the footnote at the end of this section. 762 | 763 | 764 | (foo) 765 | (bar) 766 | 767 | ;; And Nobody else. 768 | 769 | (comment 770 | (defn baz [] 771 | (type a)) 772 | (baz) 773 | ) 774 | 775 | ;; That's why some people say that closures are the poor man's objects. 776 | ;; They encapsulate the information as well. 777 | 778 | ;; But in ClojureScript, functions' parameters and let bindings' locals 779 | ;; are not mutable! That goes for loop locals, too! 780 | 781 | (let [fns (loop [i 0 ret []] 782 | (if (< i 10) 783 | (recur (inc i) (conj ret (fn [] i))) 784 | ret))] 785 | (map #(%) fns)) 786 | 787 | ;; In JavaScript you would see a list of ten 9s. In ClojureScript we 788 | ;; see the expected numbers from 0 to 9. 789 | 790 | ;; FOOTNOTE: 791 | ;; 792 | ;; `def` expressions (including `defn`) are always top level. People familiar 793 | ;; with Scheme or other Lisps often mistakenly write the following in Clojure: 794 | 795 | (defn not-scheme [] 796 | (defn no-no-no [])) 797 | 798 | ;; This is almost always incorrect. If you need to write a local function just 799 | ;; do it with a let binding. 800 | 801 | (defn outer-fn [] 802 | (let [inner-fn (fn [])])) 803 | 804 | 805 | ;; Destructuring 806 | ;; ============================================================================ 807 | 808 | ;; In any serious ClojureScript program, there will be significant amounts of 809 | ;; data manipulation. Again, we will see that ClojureScript's uniformity 810 | ;; pays off. 811 | 812 | ;; In ClojureScript anywhere bindings are allowed (like `let` or function 813 | ;; parameters), destructuring is allowed. This is similar to the destructuring 814 | ;; proposed for ES6, but the system provided in ClojureScript benefits from 815 | ;; all the collections supporting uniform access. 816 | 817 | 818 | ;; Sequence destructuring 819 | ;; ---------------------------------------------------------------------------- 820 | 821 | ;; Destructuring sequential types is particularly useful. 822 | 823 | (let [[f & r] '(1 2 3)] 824 | f) 825 | 826 | (let [[f & r] '(1 2 3)] 827 | r) 828 | 829 | (let [[r g b] [255 255 150]] 830 | g) 831 | 832 | ;; _ is just a convention for saying that you are not interested in the 833 | ;; item at the corresponding position. It has no other special meaning. 834 | ;; Here we're only interested in the third local variable named `b`. 835 | 836 | (let [[_ _ b] [255 255 150]] 837 | b) 838 | 839 | ;; destructuring function arguments works just as well. Here we are 840 | ;; only interested in the second argument `g`. 841 | 842 | (defn green [[_ g _]] g) 843 | 844 | (green [255 255 150]) 845 | 846 | 847 | ;; Map destructuring 848 | ;; ---------------------------------------------------------------------------- 849 | 850 | ;; Map destructuring is also useful. Here we destructure the value for the 851 | ;; `:foo` key and bind it to a local `f`, and the value for `:baz` key 852 | ;; and bind it to a local `b`. 853 | 854 | (let [{f :foo b :baz} {:foo \"bar\" :baz \"woz\"}] 855 | [f b]) 856 | 857 | ;; If we don't want to rename, we can just use `:keys`. 858 | 859 | (let [{:keys [first last]} {:first \"Bob\" :last \"Smith\"}] 860 | [first last]) 861 | 862 | ; We can also destructure a nested map 863 | 864 | (let [{:keys [first last] {:keys [addr1 addr2]} :address} {:first \"Bob\" :last \"Smith\" :address {:addr1 \"123\" :addr2 \"Main street\"}}] 865 | [first last addr1 addr2]) 866 | 867 | ; Similar to :keys for keyword, :strs and :syms directives are available for matching string and symbol :keys 868 | 869 | (let [{:strs [first last]} {\"first\" \"Bob\" \"last\" \"Smith\"}] 870 | [first last]) 871 | 872 | (let [{:syms [first last]} {'first \"Bob\" 'last \"Smith\"}] 873 | [first last]) 874 | 875 | ;; The above map destructuring form is very useful when you need to 876 | ;; define a function with optional, non positional and defaulted 877 | ;; arguments. 878 | 879 | (defn magic [& {:keys [k g h] 880 | :or {k 1 881 | g 2 882 | h 3}}] 883 | (hash-map :k k 884 | :g g 885 | :h h)) 886 | 887 | (magic) 888 | (magic :k 10) 889 | (magic :g 100) 890 | (magic :h 1000) 891 | (magic :k 10 :g 100 :h 1000) 892 | (magic :h 1000 :k 10 :g 100) 893 | 894 | ;; Sequences 895 | ;; ============================================================================ 896 | 897 | ;; We said that ClojureScript data structures are to be preferred as they 898 | ;; provide a uniform interface. All ClojureScript collections satisfy 899 | ;; the ISeqable protocol, which means iteration is uniform 900 | ;; (i.e. polymorphic) for all collection types. 901 | 902 | 903 | ;; Map / Filter / Reduce 904 | ;; ---------------------------------------------------------------------------- 905 | 906 | ;; ClojureScript supports the same bells and whistles out of the box that you may 907 | ;; be familiar with from other functional programming languages or JavaScript 908 | ;; libraries such as Underscore.js 909 | 910 | (map inc [0 1 2 3 4 5 6 7 8 9]) 911 | 912 | (filter even? (range 10)) 913 | 914 | (remove odd? (range 10)) 915 | 916 | ;; ClojureScript's `map` and `filter` operations are lazy. You can stack up 917 | ;; operations without getting too concerned about multiple traversals. 918 | 919 | (map #(* % %) (filter even? (range 20))) 920 | 921 | (reduce + (range 100)) 922 | 923 | 924 | ;; List comprehensions 925 | ;; ---------------------------------------------------------------------------- 926 | 927 | ;; ClojureScript supports the list comprehensions you might know from various 928 | ;; languages. List comprehensions are sometimes more natural or more readable 929 | ;; than a chain of `map` and `filter` operations. 930 | 931 | (for [x (range 1 10) 932 | y (range 1 10)] 933 | [x y]) 934 | 935 | (for [x (range 1 10) 936 | y (range 1 10) 937 | :when (and (zero? (rem x y)) 938 | (even? (quot x y)))] 939 | [x y]) 940 | 941 | (for [x (range 1 10) 942 | y (range 1 10) 943 | :let [prod (* x y)]] 944 | [x y prod]) 945 | 946 | 947 | ;; Seqable collections 948 | ;; ---------------------------------------------------------------------------- 949 | 950 | ;; Most ClojureScript collections can be coerced into sequences. 951 | 952 | (seq {:foo \"bar\" :baz \"woz\"}) 953 | (seq #{:cat :dog :bird}) 954 | (seq [1 2 3 4 5]) 955 | (seq '(1 2 3 4 5)) 956 | 957 | ;; Many ClojureScript functions will call `seq` on their arguments in order to 958 | ;; provide the expected behavior. The following demonstrates that you can 959 | ;; uniformly iterate over all the ClojureScript collections! 960 | 961 | (first {:foo \"bar\" :baz \"woz\"}) 962 | (rest {:foo \"bar\" :baz \"woz\"}) 963 | 964 | (first #{:cat :dog :bird}) 965 | (rest #{:cat :dog :bird}) 966 | 967 | (first [1 2 3 4 5]) 968 | (rest [1 2 3 4 5]) 969 | 970 | (first '(1 2 3 4 5)) 971 | (rest '(1 2 3 4 5)) 972 | 973 | 974 | ;; Metadata 975 | ;; ============================================================================ 976 | 977 | ;; All of the ClojureScript standard collections support metadata. Metadata 978 | ;; is a useful way to annotate data without affecting equality. The 979 | ;; ClojureScript compiler uses this language feature to great effect. 980 | 981 | ;; You can add metadata to a ClojureScript collection with `with-meta`. The 982 | ;; metadata must be a map. 983 | 984 | (def plain-data [0 1 2 3 4 5 6 7 8 9]) 985 | 986 | (def decorated-data (with-meta plain-data {:url \"http://lighttable.com\"})) 987 | 988 | ;; Metadata has no effect on equality. 989 | 990 | (= plain-data decorated-data) 991 | 992 | ;; You can access metadata with `meta`. 993 | 994 | (meta decorated-data) 995 | 996 | 997 | ;; Error Handling 998 | ;; ============================================================================ 999 | 1000 | ;; Error handling in ClojureScript is relatively straightforward and more or 1001 | ;; less similar to what is offered in JavaScript. 1002 | 1003 | ;; You can construct an error like this. 1004 | 1005 | (js/Error. \"Oops\") 1006 | 1007 | ;; You can throw an error like this. 1008 | ;; (Highlight and evaluate the `throw` form below) 1009 | 1010 | (comment 1011 | (throw (js/Error. \"Oops\")) 1012 | ) 1013 | 1014 | ;; You can catch an error like this. 1015 | 1016 | (try 1017 | (throw (js/Error. \"Oops\")) 1018 | (catch js/Error e 1019 | e)) 1020 | 1021 | ;; JavaScript unfortunately allows you to throw anything. You can handle 1022 | ;; this in ClojureScript with the following. 1023 | 1024 | (try 1025 | (throw (js/Error. \"Oops\")) 1026 | (catch :default e 1027 | e)) 1028 | 1029 | ;; Catches are optional. You can also use multiple forms to handle different types of errors. 1030 | 1031 | (try 1032 | (throw (js/Error. \"Oops\")) 1033 | (catch js/Error e 1034 | e) 1035 | (catch Error e 1036 | e) 1037 | (finally 1038 | \"Cleanup here\")) 1039 | 1040 | 1041 | ;; Mutation 1042 | ;; ============================================================================ 1043 | 1044 | ;; Atoms 1045 | ;; ---------------------------------------------------------------------------- 1046 | 1047 | ;; A little bit of mutability goes a long way. ClojureScript does not offer 1048 | ;; any traditional mutable data structures, however it does support identities 1049 | ;; that can evolve over time via `atom`. 1050 | 1051 | (def x (atom 1)) 1052 | 1053 | ;; You can dereference the value of an atom with `@`. 1054 | 1055 | @x 1056 | 1057 | ;; This is equivalent to calling `deref`. 1058 | 1059 | (deref x) 1060 | 1061 | ;; If you want to change the value of an atom you can use `reset!` which returns 1062 | ;; the new value. It's idiomatic to add the bang char `!` at the end of function 1063 | ;; names mutating objects. 1064 | 1065 | (reset! x 2) 1066 | 1067 | x 1068 | 1069 | @x 1070 | 1071 | ;; swap! 1072 | ;; ------------------------------------------------------------------------------ 1073 | 1074 | ;; If you want to change the value of an atom on the basis of its current value, 1075 | ;; you can use `swap!`. In its simplest form, `swap!` accepts as a first argument 1076 | ;; the atom itself and as a second argument an updating function of one argument 1077 | ;; which will be instantiated with the current value of the atom. `swap!` returns 1078 | ;; the new value of the atom. 1079 | 1080 | (swap! x inc) 1081 | 1082 | x 1083 | 1084 | @x 1085 | 1086 | ;; If your updating function needs extra arguments to calculate the new value, you 1087 | ;; have to pass them as extra arguments to `swap!` after the updating function 1088 | ;; itself. 1089 | 1090 | (swap! x (fn [old extra-arg] 1091 | (+ old extra-arg)) 39) 1092 | 1093 | x 1094 | 1095 | @x 1096 | 1097 | ;; As usual when anonymous functions are simple enough, it's idiomatic to use 1098 | ;; the condensed form. 1099 | 1100 | (swap! x #(- %1 %2) 42) 1101 | 1102 | x 1103 | 1104 | @x 1105 | 1106 | ;; Note that the updating function has to be free of side-effects because a 1107 | ;; waiting writer could call it more than once in a spin loop. 1108 | 1109 | 1110 | ;; set! 1111 | ;; ---------------------------------------------------------------------------- 1112 | 1113 | ;; Sometimes you need to mutate existing JavaScript objects. For this you 1114 | ;; have `set!`. 1115 | 1116 | (def c (.createElement js/document \"canvas\")) 1117 | (def ctxt (.getContext c \"2d\")) 1118 | 1119 | ;; We can use property access with `set!` to change the fill color of a 1120 | ;; a canvas rendering context. 1121 | 1122 | (set! (.-fillColor ctxt) \"#ffffff\") 1123 | 1124 | 1125 | ;; The ClojureScript Standard Library 1126 | ;; ============================================================================ 1127 | 1128 | ;; The ClojureScript standard library largely mirrors the Clojure standard 1129 | ;; library with the exception of functionality that assumes a multithreaded 1130 | ;; environment, first class namespaces, and Java numerics. 1131 | 1132 | ;; Here are some highlights and patterns that newcomers to ClojureScript might 1133 | ;; find useful. Remember you can type Control-Shift-D at anytime to bring up 1134 | ;; the documentation panel to see what any of these function do. 1135 | 1136 | (apply str (interpose \", \" [\"Bob\" \"Mary\" \"George\"])) 1137 | 1138 | ((juxt :first :last) {:first \"Bob\" :last \"Smith\"}) 1139 | 1140 | (def people [{:first \"John\" :last \"McCarthy\"} 1141 | {:first \"Alan\" :last \"Kay\"} 1142 | {:first \"Joseph\" :last \"Licklider\"} 1143 | {:first \"Robin\" :last \"Milner\"}]) 1144 | 1145 | (map :first people) 1146 | 1147 | (take 5 (repeat \"red\")) 1148 | 1149 | (take 5 (repeat \"blue\")) 1150 | 1151 | (take 5 (interleave (repeat \"red\") (repeat \"blue\"))) 1152 | 1153 | (take 10 (cycle [\"red\" \"white\" \"blue\"])) 1154 | 1155 | (partition 2 [:a 1 :b 2 :c 3 :d 4 :e 5]) 1156 | 1157 | (partition 2 1 [:a 1 :b 2 :c 3 :d 4 :e 5]) 1158 | 1159 | (take-while #(< % 5) (range 10)) 1160 | 1161 | (drop-while #(< % 5) (range 10)) 1162 | 1163 | 1164 | ;; Protocols 1165 | ;; ============================================================================ 1166 | 1167 | ;; The ClojureScript language is constructed on a rich set of protocols. The 1168 | ;; same uniformity provided by ClojureScript collections can be extended to 1169 | ;; your own types or even types that you do not control! 1170 | 1171 | ;; A lot of the uniform power we saw early was because the ClojureScript 1172 | ;; collections are implemented in terms of protocols. Collections can be 1173 | ;; coerced into sequences because they implement ISeqable. You can use `get` 1174 | ;; on vectors and maps because they implement ILookup. 1175 | 1176 | (get {:foo \"bar\"} :foo) 1177 | (get [:cat :bird :dog] 1) 1178 | 1179 | ;; Map destructuring actually desugars into `get` calls. That means if you extend 1180 | ;; your type to ILookup it will also support map destructuring! 1181 | 1182 | 1183 | ;; extend-type 1184 | ;; ---------------------------------------------------------------------------- 1185 | 1186 | ;; ClojureScript supports custom extension to types that avoid many of the 1187 | ;; pitfalls that you encounter in other languages. For example imagine we have 1188 | ;; some awesome polymorphic functionality in mind. 1189 | 1190 | (defprotocol MyProtocol (awesome [this])) 1191 | 1192 | ;; It's idiomatic to name the first argument of a protocol's functions 1193 | ;; as `this` which reminds you that it is the argument used by 1194 | ;; ClojureScript to dispatch the right function implementation on the 1195 | ;; basis of the type of the value of `this` 1196 | 1197 | ;; Now imagine we want JavaScript strings to participate. We can do this 1198 | ;; simply. 1199 | 1200 | (extend-type string 1201 | MyProtocol 1202 | (awesome [this] (vector this \"Totally awesome!\"))) 1203 | 1204 | (awesome \"Is this awesome?\") 1205 | 1206 | 1207 | ;; extend-protocol 1208 | ;; ---------------------------------------------------------------------------- 1209 | 1210 | ;; Sometimes you want to extend several types to a protocol at once. You can 1211 | ;; use extend-protocol for this. extend-protocol simply desugars into multiple 1212 | ;; extend-type forms. 1213 | 1214 | ;; As said while learning about `let` special form, when we're not 1215 | ;; interested in the value of an argument it's idiomatic to use the 1216 | ;; underscore as a placeholder like above. 1217 | 1218 | (extend-protocol MyProtocol 1219 | js/Date 1220 | (awesome [_] \"Having an awesome time!\") 1221 | number 1222 | (awesome [_] \"I'm an awesome number!\")) 1223 | 1224 | (awesome #inst \"2014\") 1225 | (awesome 5) 1226 | 1227 | 1228 | ;; reify 1229 | ;; ---------------------------------------------------------------------------- 1230 | 1231 | ;; Sometimes it's useful to make an anonymous type which implements various 1232 | ;; protocols. 1233 | 1234 | ;; For example say we want a JavaScript object to support ILookup. Now we don't 1235 | ;; want to blindly `extend-type object`, that would pollute the behavior of plain 1236 | ;; JavaScript objects for everyone. 1237 | 1238 | ;; Instead we can provide a helper function that takes an object and returns 1239 | ;; something that provides this functionality. 1240 | 1241 | (defn ->lookup [obj] 1242 | (reify 1243 | ILookup 1244 | (-lookup [this k] 1245 | (-lookup this k nil)) 1246 | (-lookup [this k not-found] 1247 | (let [k (name k)] 1248 | (if (.hasOwnProperty obj k) 1249 | (aget obj k) 1250 | not-found))))) 1251 | 1252 | ;; We can then selectively make JavaScript objects work with `get`. 1253 | 1254 | (get (->lookup #js {\"foo\" \"bar\"}) :foo) 1255 | 1256 | ;; But this also means we get destructuring on JavaScript objects. 1257 | 1258 | (def some-object #js {\"foo\" \"bar\" \"baz\" \"woz\"}) 1259 | 1260 | (let [{:keys [foo baz]} (->lookup some-object)] 1261 | [foo baz]) 1262 | 1263 | 1264 | ;; specify 1265 | ;; ---------------------------------------------------------------------------- 1266 | 1267 | ;; TODO fill this out 1268 | 1269 | ;; Macros 1270 | ;; ============================================================================ 1271 | 1272 | 1273 | ;; Types & Records 1274 | ;; ============================================================================ 1275 | 1276 | ;; deftype 1277 | ;; ---------------------------------------------------------------------------- 1278 | 1279 | ;; Sometimes a map will simply not suffice, in these cases you will want to 1280 | ;; make your own custom type. 1281 | 1282 | (deftype Foo [a b]) 1283 | 1284 | ;; It's idiomatic to use CamelCase to name a `deftype`. You can instantiate a 1285 | ;; deftype instance using the same constructor pattern we've already discussed. 1286 | 1287 | (Foo. 1 2) 1288 | 1289 | ;; You can access properties of a deftype instance using property access 1290 | ;; syntax. 1291 | 1292 | (.-a (Foo. 1 2)) 1293 | 1294 | ;; You can implement protocol methods on a deftype. Note that the first 1295 | ;; argument to any deftype or defrecord method is the instance itself. 1296 | ;; The dash in `-count` has no special meaning. It's just a convention for 1297 | ;; the core ClojureScript protocols. You need not adopt it. 1298 | 1299 | (deftype Foo [a b] 1300 | ICounted 1301 | (-count [this] 2)) 1302 | 1303 | (count (Foo. 1 2)) 1304 | 1305 | ;; Sometimes it's useful to implement methods directly on the deftype. 1306 | 1307 | (deftype Foo [a b] 1308 | Object 1309 | (toString [this] (str a \", \" b))) 1310 | 1311 | (.toString (Foo. 1 2)) 1312 | 1313 | ;; deftype fields are immutable unless specified. The following will not compile. 1314 | ;; (To prove it to yourself, highlight and evaluate the `deftype` form below.) 1315 | 1316 | (comment 1317 | 1318 | (deftype Foo [a ^:mutable b] 1319 | Object 1320 | (setA [this val] (set! a val))) 1321 | 1322 | ) 1323 | 1324 | ;; The following will compile. 1325 | 1326 | (deftype Foo [a ^:mutable b] 1327 | Object 1328 | (setB [this val] (set! b val))) 1329 | 1330 | 1331 | ;; defrecord 1332 | ;; ---------------------------------------------------------------------------- 1333 | 1334 | ;; `deftype` doesn't provide much out of the box. Often what you want to do is 1335 | ;; have a domain object that acts more or less like a map. This is what 1336 | ;; `defrecord` is for. 1337 | 1338 | ;; Like `deftype`, it's idiomatic to use CamelCase to name a `defrecord`. 1339 | 1340 | (defrecord Person [first last]) 1341 | 1342 | ;; You can construct an instance in the usual way. 1343 | 1344 | (Person. \"Bob\" \"Smith\") 1345 | 1346 | ;; Or you can use the provided constructors. 1347 | 1348 | (->Person \"Bob\" \"Smith\") 1349 | 1350 | (map->Person {:first \"Bob\" :last \"Smith\"}) 1351 | 1352 | ;; It's considered idiomatic (and recommended) to define a factory function 1353 | ;; which returns the created instance of a defrecord/deftype. It's idiomatic to use 1354 | ;; dash-case for factories names. 1355 | 1356 | (defn person [first last] 1357 | (->Person first last)) 1358 | 1359 | ;; records work like maps 1360 | 1361 | (seq (person \"Bob\" \"Smith\")) 1362 | 1363 | (:first (person \"Bob\" \"Smith\")) 1364 | 1365 | (keys (person \"Bob\" \"Smith\")) 1366 | 1367 | (vals (person \"Bob\" \"Smith\")) 1368 | 1369 | ;; both deftype and defrecord are open to dynamic extensions (i.e. open class) 1370 | 1371 | (keys (assoc (person \"Bob\" \"Smith\") :age 18)) 1372 | 1373 | 1374 | ;; Records & Protocols 1375 | ;; ---------------------------------------------------------------------------- 1376 | 1377 | ;; You can extend a defrecord to satisfy a protocol as you do with deftype. 1378 | 1379 | (extend-type Person 1380 | MyProtocol 1381 | (awesome [this] 1382 | (str (:last this) \", \" (:first this)))) 1383 | 1384 | (awesome (person \"Bob\" \"Smith\")) 1385 | 1386 | (satisfies? MyProtocol (person \"Bob\" \"Smith\")) 1387 | 1388 | ;; Or you can extend a protocol on a defrecord. 1389 | 1390 | (extend-protocol MyProtocol 1391 | Person 1392 | (awesome [this] 1393 | (str (:last this) \", \" (:first this)))) 1394 | 1395 | (awesome (person \"Bob\" \"Smith\")) 1396 | 1397 | (satisfies? MyProtocol (person \"Bob\" \"Smith\")) 1398 | 1399 | ;; If you need a more sophisticated form of polymorphism consider multimethods. 1400 | 1401 | ;; If you mix types/records with protocols you are modeling your problem with an 1402 | ;; object oriented approach, which is sometimes useful. 1403 | 1404 | ;; Note ClojureScript does not offer a direct form of inheritance. Instead, 1405 | ;; reuse/extension by composition is encouraged. It's best to avoid 1406 | ;; deftype/defrecord and model your problem with plain maps. You can easily 1407 | ;; switch to records later on down the line. 1408 | 1409 | (defrecord Contact [person email]) 1410 | 1411 | ;; Even if it's not required, remember to define a factory function to create 1412 | ;; instances of the new Contact record type by internally calling the factory 1413 | ;; function for the Person record type. 1414 | 1415 | (defn contact [first last email] 1416 | (->Contact (person first last) email)) 1417 | 1418 | (contact \"Bob\" \"Smith\" \"bob.smith@acme.com\") 1419 | 1420 | ;; And extend the protocol on defrecord as well. 1421 | 1422 | (extend-protocol MyProtocol 1423 | Contact 1424 | (awesome [this] 1425 | (str (awesome (:person this)) \", \" (:email this)))) 1426 | 1427 | (awesome (contact \"Bob\" \"Smith\" \"bob.smith@acme.com\")) 1428 | 1429 | ;; To change the value of a nested key you use 'assoc-in', like with maps. 1430 | 1431 | (assoc-in (contact \"Bob\" \"Smith\" \"bob.smith@acme.com\") 1432 | [:person :first] \"Robert\") 1433 | 1434 | ;; If you need to use the previous value of a nested field for calculating the 1435 | ;; new one, you can use 'update-in', like with maps. 1436 | 1437 | (update-in (contact \"Bob\" \"Smith\" \"bob.smith@acme.com\") 1438 | [:person :first] #(str/replace %1 #\"Bob\" %2) \"Robert\") 1439 | 1440 | ;; As said, the main difference with the majority of OO languages is that your 1441 | ;; instances of deftypes/defrecords are immutable. 1442 | 1443 | (def bob (contact \"Bob\" \"Smith\" \"bob.smith@acme.com\")) 1444 | 1445 | (update-in bob [:person :first] #(str/replace %1 #\"Bob\" %2) \"Robert\") 1446 | 1447 | (get-in bob [:person :first]) 1448 | 1449 | 1450 | ;; JavaScript Interop 1451 | ;; ============================================================================ 1452 | 1453 | ;; Property Access 1454 | ;; ---------------------------------------------------------------------------- 1455 | 1456 | (def a-date (js/Date.)) 1457 | 1458 | ;; You can access properties with the `.-` property access syntax. 1459 | 1460 | (.-getSeconds a-date) 1461 | 1462 | 1463 | ;; Method Calls 1464 | ;; ---------------------------------------------------------------------------- 1465 | 1466 | ;; Methods can be invoked with the `.` syntax. 1467 | 1468 | (.getSeconds a-date) 1469 | 1470 | ;; The above desugars into the following. 1471 | 1472 | (. a-date (getSeconds)) 1473 | 1474 | ;; For example, you can write a `console.log` call like so. 1475 | 1476 | (. js/console (log \"Interop!\")) 1477 | 1478 | 1479 | ;; Primitive Array Operations 1480 | ;; ---------------------------------------------------------------------------- 1481 | 1482 | ;; When writing performance sensitive code, sometimes dealing with mutable 1483 | ;; arrays is unavoidable. ClojureScript provides a variety of functions for 1484 | ;; creating and manipulating JavaScript arrays. 1485 | 1486 | ;; You can make an array of specific size with `make-array` 1487 | 1488 | (make-array 32) 1489 | 1490 | ;; You can access an element of an array with `aget`. 1491 | 1492 | (aget #js [\"one\" \"two\" \"three\"] 1) 1493 | 1494 | ;; You can access nested arrays with `aget`. 1495 | 1496 | (aget #js [#js [\"one\" \"two\" \"three\"]] 0 1) 1497 | 1498 | ;; You can set the contents of an array with aset. 1499 | 1500 | (def yucky-stuff #js [1 2 3]) 1501 | 1502 | (aset yucky-stuff 1 4) 1503 | 1504 | yucky-stuff 1505 | ") 1506 | -------------------------------------------------------------------------------- /src/tutorial_cljs/text/quil.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.text.quil) 2 | 3 | (def text 4 | ";; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 | ;; +++++++++++++++ ++++++++++++++++ 7 | ;; An Interactive Introduction to Quil 8 | ;; +++++++++++++++ ++++++++++++++++ 9 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | 12 | ;; ============================================================================ 13 | ;; Using this Tutorial 14 | ;; ============================================================================ 15 | 16 | ;; # Evaluating forms 17 | ;; 18 | ;; You can evaluate forms in this tutorial by putting your cursor at the end of 19 | ;; the form and pressing \"Cmd-Enter\". The output will be displayed to the 20 | ;; right of your cursor. You can dismiss the output view (if it's in your way) 21 | ;; with \"Cmd-Shift-Enter\". 22 | ;; 23 | ;; Try evaluating this: 24 | 25 | (+ 1 2) ; <- put your cursor right after the closing ) and press Cmd-Enter 26 | 27 | ;; ^ You can also put your cursor on the following line and press Cmd-Enter 28 | 29 | ;; Ok, that was cool, but how about some data that's more complicated? 30 | {:some 10 31 | :other 20 32 | :list (range 10)} ; evaluate this, and you'll be able to interact with the result 33 | 34 | ;; # Documentation + auto-complete 35 | ;; 36 | ;; If you click `range` in that code above, the documentation for the range 37 | ;; function will appear in the bottom-right corner of this page. You can type 38 | ;; into this document, and documentation + auto-complete suggestions will 39 | ;; appear. Press Tab (and Shift-Tab) to cycle through the suggestions. 40 | ;; 41 | ;; Go ahead, put your cursor at the end of `map`, and see what other functions 42 | ;; have `map` in the name. 43 | 44 | map 45 | 46 | ;; # The REPL 47 | ;; 48 | ;; The right hand pane is a REPL where you can type in clojurescript code and 49 | ;; see the results. It will show you documentation + auto-complete suggestions 50 | ;; as well. 51 | 52 | 53 | ;; ============================================================================ 54 | ;; The Setup 55 | ;; ============================================================================ 56 | 57 | ;; We've already evaluated this for you, so you're already in the tutorial.quil ns 58 | (ns tutorial.quil 59 | (:require [quil.core :as q])) 60 | 61 | ;; We start with a fairly simple setup function, no state, and empty update & 62 | ;; draw functions so we can experiment at the REPL 63 | 64 | (defn setup [] 65 | (q/smooth) 66 | (q/frame-rate 10) 67 | (q/background 255) 68 | {}) 69 | 70 | (defn q-update [state] 71 | state) 72 | 73 | (defn draw [state] 74 | ) 75 | 76 | ;; In normal quil, this `makesketch` would be `q/defsketch`, but we're doing some 77 | ;; magic so that it will work in this tutorial enviornment. The API is the same, 78 | ;; although we automatically enable the `fun-mode` middleware for you. 79 | (makesketch example 80 | :title \"My Sketch\" 81 | :setup setup 82 | :update q-update 83 | :draw draw 84 | :size [323 200]) 85 | 86 | ;; When you evaluate this (Cmd-Enter on the line below it), a new quil sketch 87 | ;; will open. If you evaluate it again, the sketch will reload (setup will run 88 | ;; again, etc.). If you change `example` to some other name, or create a new 89 | ;; `(makesketch)` form with a different name, another sketch will open. The most 90 | ;; recent sketch you've created will also be available to the REPL -- if you 91 | ;; execute `q/rect` in the REPL or in this page, it will be evaluated on the most 92 | ;; recently created sketch. 93 | 94 | 95 | ;; ============================================================================ 96 | ;; Drawing 97 | ;; ============================================================================ 98 | ;; Once you've made the sketch, we can start drawing on it. 99 | 100 | ;; # Solids & fill 101 | 102 | (q/rect 20 20 200 200) 103 | 104 | ;; You can change the fill 105 | (q/fill 200) ; one argument (0-255) means gray 106 | 107 | (q/rect 20 20 200 200) ; now evalute again, and see that the color has changed 108 | 109 | (q/fill 0 255 100) ; three arguments (0-255) mean red, green, blue 110 | 111 | (q/rect 20 20 200 200) 112 | 113 | (q/ellipse 100 100 60 30) ; unlike the rect, the ellipse is *centered* around the first two args 114 | 115 | ;; # Lines & Stroke 116 | 117 | (q/line 0 0 200 100) 118 | 119 | (q/stroke 255 0 100) ; you can also pass one arg for gray 120 | (q/stroke-weight 20) 121 | 122 | (q/line 0 0 200 100) ;; now try that line ^ again 123 | 124 | ;; You can clear the canvas at any time with this 125 | (q/background 255) ; 255 = white 126 | 127 | ;; Now lets use a clojurescript loop to do something more fun 128 | (dorun 129 | (for [angle (range 0 360 10)] 130 | (let [x (q/cos (q/radians angle)) 131 | y (q/sin (q/radians angle))] 132 | (q/stroke (/ angle 2)) 133 | (q/line 100 100 (+ 100 (* x 100)) (+ 100 (* y 100)))))) 134 | 135 | ;; ============================================================================ 136 | ;; Animating 137 | ;; ============================================================================ 138 | ;; Here we only have to change the draw function, and the 139 | ;; sketch automatically updates to show the changes. 140 | 141 | (defn draw [state] 142 | (q/stroke (q/random 255)) 143 | ;; (q/stroke (q/random 255) (q/random 255) (q/random 255)) 144 | (q/stroke-weight (q/random 10)) 145 | (q/fill (q/random 255)) 146 | 147 | (let [diam (q/random 100) 148 | x (q/random (q/width)) 149 | y (q/random (q/height))] 150 | (q/ellipse x y diam diam))) 151 | ;; (put your cursor here and hit Cmd-Enter) 152 | 153 | ;; Now try taking that `(dorun)` form from above and putting it in the draw 154 | ;; function here; then you'll always have that wheel of lines on top of the 155 | ;; circles that are drawn. 156 | 157 | ;; There's a `q/stroke` call in that draw function that's commented out -- 158 | ;; remove the `;;' to make your sketch more colorful. 159 | ;; Can you make the fill colorful as well? 160 | 161 | 162 | ;; TODO more to come :) 163 | ") 164 | 165 | -------------------------------------------------------------------------------- /src/tutorial_cljs/text/reagent.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.text.reagent) 2 | 3 | (def text 4 | ";; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 | ;; +++++++++++++++ ++++++++++++++++ 7 | ;; An Interactive Introduction to Reagent 8 | ;; +++++++++++++++ ++++++++++++++++ 9 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | 12 | ;; ============================================================================ 13 | ;; Using this Tutorial 14 | ;; ============================================================================ 15 | 16 | ;; # Evaluating forms 17 | ;; 18 | ;; You can evaluate forms in this tutorial by putting your cursor at the end of 19 | ;; the form and pressing \"Cmd-Enter\". The output will be displayed to the 20 | ;; right of your cursor. You can dismiss the output view (if it's in your way) 21 | ;; with \"Cmd-Shift-Enter\". 22 | ;; 23 | ;; Try evaluating this: 24 | 25 | (+ 1 2) ; <- put your cursor right after the closing ) and press Cmd-Enter 26 | 27 | ;; ^ You can also put your cursor on the following line and press Cmd-Enter 28 | 29 | ;; Ok, that was cool, but how about some data that's more complicated? 30 | {:some 10 31 | :other 20 32 | :list (range 10)} ; evaluate this, and you'll be able to interact with the result 33 | 34 | ;; # Documentation + auto-complete 35 | ;; 36 | ;; If you click `range` in that code above, the documentation for the range 37 | ;; function will appear in the bottom-right corner of this page. You can type 38 | ;; into this document, and documentation + auto-complete suggestions will 39 | ;; appear. Press Tab (and Shift-Tab) to cycle through the suggestions. 40 | ;; 41 | ;; Go ahead, put your cursor at the end of `map`, and see what other functions 42 | ;; have `map` in the name. 43 | 44 | map 45 | 46 | ;; # The REPL 47 | ;; 48 | ;; The right hand pane is a REPL where you can type in clojurescript code and 49 | ;; see the results. It will show you documentation + auto-complete suggestions 50 | ;; as well. 51 | 52 | 53 | ;; ============================================================================ 54 | ;; Basic forms 55 | ;; ============================================================================ 56 | 57 | ;; We've already evaluated this for you, so you're already in the tutorial.reagent ns 58 | (ns tutorial.reagent 59 | (:require [reagent.core :as r])) 60 | 61 | [:span \"Hello\"] 62 | 63 | [:div \"one\" \"two\"] 64 | 65 | [:span {:style {:color :red}} \"Red text\"] 66 | 67 | [:button {:on-click #(js/alert \"clicked!\")} \"Click for a message\"] 68 | 69 | ;; ============================================================================ 70 | ;; Components 71 | ;; ============================================================================ 72 | 73 | (defn my-component [name] 74 | [:span (str \"Hello \" name)]) 75 | 76 | [my-component \"Julie\"] 77 | 78 | 79 | 80 | 81 | 82 | ;; ============================================================================ 83 | ;; Components 84 | ;; ============================================================================ 85 | 86 | 87 | (defn my-stateful-component [initial-count] 88 | (let [count (r/atom initial-count)] 89 | (fn [] 90 | [:button {:on-click #(swap! count inc)} 91 | (str \"Clicked \" @count \" times\")]))) 92 | 93 | [my-stateful-component 10] 94 | 95 | 96 | ;; TODO more to come :) 97 | ") 98 | 99 | (def unready 100 | " 101 | 102 | ;; ============================================================================ 103 | ;; Render Windows 104 | ;; ============================================================================ 105 | 106 | ;; TODO do we even need this??? 107 | ;; Basically: how do I make this be devcards? 108 | 109 | 110 | ;; In normal reagent, this would be `r/render`, but we're doing some magic so that 111 | ;; it will work in this tutorial enviornment. 112 | (makerender 113 | :example 114 | \"My Awesome Button\" 115 | [:button nil \"Click and despair\" ]) 116 | 117 | ;; When you evaluate this (Cmd-Enter on the line below it), a new reagent render 118 | ;; will open. If you evaluate it again, the render will reload. 119 | ;; If you change `:example` to some other name, or create a new 120 | ;; `(makerender)` form with a different name, another render will open. 121 | 122 | ") 123 | -------------------------------------------------------------------------------- /src/tutorial_cljs/text/webgl.cljs: -------------------------------------------------------------------------------- 1 | (ns tutorial-cljs.text.webgl) 2 | 3 | (def text 4 | ";; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 | ;; +++++++++++++++ +++++++++++++++ 7 | ;; An Interactive Introduction to Gamma (WebGL) 8 | ;; +++++++++++++++ +++++++++++++++ 9 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | ;; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | 12 | ;; ============================================================================ 13 | ;; Using this Tutorial 14 | ;; ============================================================================ 15 | 16 | ;; # Evaluating forms 17 | ;; 18 | ;; You can evaluate forms in this tutorial by putting your cursor at the end of 19 | ;; the form and pressing \"Cmd-Enter\". The output will be displayed to the 20 | ;; right of your cursor. You can dismiss the output view (if it's in your way) 21 | ;; with \"Cmd-Shift-Enter\". 22 | ;; 23 | ;; Try evaluating this: 24 | 25 | (+ 1 2) ; <- put your cursor right after the closing ) and press Cmd-Enter 26 | 27 | ;; ^ You can also put your cursor on the following line and press Cmd-Enter 28 | 29 | ;; Ok, that was cool, but how about some data that's more complicated? 30 | {:some 10 31 | :other 20 32 | :list (range 10)} ; evaluate this, and you'll be able to interact with the result 33 | 34 | ;; # Documentation + auto-complete 35 | ;; 36 | ;; If you click `range` in that code above, the documentation for the range 37 | ;; function will appear in the bottom-right corner of this page. You can type 38 | ;; into this document, and documentation + auto-complete suggestions will 39 | ;; appear. Press Tab (and Shift-Tab) to cycle through the suggestions. 40 | ;; 41 | ;; Go ahead, put your cursor at the end of `map`, and see what other functions 42 | ;; have `map` in the name. 43 | 44 | map 45 | 46 | ;; # The REPL 47 | ;; 48 | ;; The right hand pane is a REPL where you can type in clojurescript code and 49 | ;; see the results. It will show you documentation + auto-complete suggestions 50 | ;; as well. 51 | 52 | 53 | ;; ============================================================================ 54 | ;; Basic forms 55 | ;; ============================================================================ 56 | 57 | ") 58 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |