├── .gitignore ├── CHANGES.org ├── README.org ├── project.clj ├── samples ├── README.org ├── contacts │ ├── .gitignore │ ├── common-src │ │ └── contacts │ │ │ └── formatter.cljx │ ├── externs │ │ └── jquery.js │ ├── project.clj │ ├── resources │ │ └── contacts-config.edn │ ├── src │ │ └── contacts │ │ │ └── service │ │ │ └── handler.clj │ └── ui-src │ │ └── contacts │ │ └── ui │ │ └── app.cljs ├── counter │ ├── .gitignore │ ├── README.org │ ├── externs │ │ └── jquery.js │ ├── project.clj │ ├── resources │ │ └── counter-config.edn │ ├── src │ │ └── flow_counter │ │ │ └── service │ │ │ └── handler.clj │ └── ui-src │ │ └── flow-counter │ │ └── ui │ │ └── app.cljs ├── flow-sample │ ├── .gitignore │ ├── externs │ │ └── jquery.js │ ├── project.clj │ ├── resources │ │ └── flow-sample-config.edn │ ├── src │ │ └── flow_sample │ │ │ └── service │ │ │ └── handler.clj │ └── ui-src │ │ └── flow_sample │ │ └── ui │ │ └── app.cljs └── todomvc │ ├── .gitignore │ ├── README.org │ ├── project.clj │ ├── resources │ ├── img │ │ └── tick.png │ ├── todomvc-config.edn │ └── todomvc │ │ ├── bg.png │ │ └── todomvc.css │ ├── src │ └── flow │ │ └── todomvc │ │ └── service │ │ └── handler.clj │ └── ui-src │ └── flow │ └── todomvc │ └── ui │ ├── app.cljs │ ├── todomvc_model.cljs │ └── todomvc_widget.cljs ├── slides └── clojurex-2014-presentation.pdf ├── src └── flow │ ├── compiler.clj │ ├── core.cljx │ ├── cursors.cljx │ ├── deps.cljx │ ├── dom │ ├── attributes.clj │ ├── attributes.cljs │ ├── children.cljx │ ├── diff.cljx │ ├── elements.clj │ └── elements.cljs │ ├── el.cljx │ ├── expand.clj │ ├── forms │ ├── bindings.clj │ ├── case.cljx │ ├── collections.cljx │ ├── cursors.cljx │ ├── do.cljx │ ├── fn_calls.cljx │ ├── fn_decls.cljx │ ├── for.cljx │ ├── if.cljx │ ├── let.cljx │ ├── list.cljx │ ├── node.cljx │ ├── primitive.cljx │ ├── sub_component.cljx │ ├── symbols.cljx │ └── text.cljx │ ├── render.clj │ ├── render.cljs │ └── state.cljx └── test └── flow ├── deps_test.clj ├── forms ├── case_test.clj ├── do_test.clj ├── fn_call_test.clj ├── if_test.clj ├── let_test.clj └── node │ └── node_basics.clj └── render_harness.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /CHANGES.org: -------------------------------------------------------------------------------- 1 | * Changelog 2 | ** 0.3.x 3 | 4 | Moving to 0.3.0 due to the amount of change between 0.2.0-beta2 and 5 | 0.2.0-beta3. 6 | 7 | *** 0.3.0-alpha3 8 | 9 | Adding 'on-mount' lifecycle callback 10 | 11 | *** 0.3.0-alpha2 12 | 13 | Couple of bugfixes - =f/root= now clears the container, and 14 | =f/keyed-at= now works with lazy-seqs. 15 | 16 | *** 0.3.0-alpha1 17 | 18 | Change to use =f/keyed-at= function (rather than =^::f/pk= on the 19 | meta) to let Flow know of the PK of a collection. 20 | 21 | Many more bugfixes and performance optimisations 22 | 23 | ** 0.2.x (complete re-write) 24 | 25 | The 0.2.x branch was abandoned - after the big re-write between beta2 26 | and beta3 I decided that it was probably best to bump the version to 27 | 0.3.0, and start again with SNAPSHOTs/alphas. 28 | 29 | *** 0.2.0-beta6 30 | 31 | Performance improvements + bug fixes 32 | 33 | No breaking changes. 34 | 35 | *** 0.2.0-beta5 36 | 37 | Performance improvements - ensuring that all of our caching works 38 | 39 | No breaking changes. 40 | 41 | *** 0.2.0-beta4 42 | 43 | Bug in f/on - fixed. 44 | 45 | No breaking changes 46 | 47 | *** 0.2.0-beta3 48 | 49 | Complete re-write from 0.2.0-beta2 - massive simplification of the 50 | internal workings. 51 | 52 | One breaking change from b2: sub-components now need to be 'called' 53 | with vectors, rather than parens: 54 | 55 | #+BEGIN_SRC clojure 56 | (defn render-list-item [elem] 57 | (f/el 58 | [:li elem])) 59 | 60 | (defn render-list [coll] 61 | (f/el 62 | [:ul 63 | (for [elem coll] 64 | ;; previously: (render-list-item elem) 65 | [render-list-item elem])])) 66 | 67 | #+END_SRC 68 | 69 | *** 0.2.0-beta2 70 | 71 | Bugfix to macro-expansion stage - needs to be a prewalk rather than a postwalk 72 | 73 | *** 0.2.0-beta1 74 | 75 | Lots of documentation and tutorials. 76 | 77 | Also, more bugfixes. 78 | 79 | *** 0.2.0-alpha5 80 | 81 | More minor bugfixes as a result of using Flow in other projects 82 | 83 | *** 0.2.0-alpha4 84 | 85 | Minor typo. :( 86 | 87 | *** 0.2.0-alpha3 88 | 89 | Minor bugfix - lens atoms weren't being updated when their parent 90 | atoms changed. 91 | 92 | *** 0.2.0-alpha2 93 | 94 | Added: 95 | - Read-Write support for lenses 96 | - Batching of updates 97 | - Using =requestAnimationFrame= if available 98 | 99 | Still to do: 100 | 101 | - Docs 102 | - Sample apps 103 | - Even more copious testing 104 | 105 | *** 0.2.0-alpha1 106 | 107 | Complete re-write of the Flow library (it's probably best thought of 108 | as a different library tbh!) 109 | 110 | Flow is now a compiled DSL - the compiler analyses all of the 111 | dependencies at compile-time, and only updates what is necessary at 112 | run-time. The new DSL is a lot more expressive than the old =let<<= / 113 | =for<<= combination. 114 | 115 | Still missing before I can go to 0.2.0: 116 | 117 | - Updated docs 118 | - Updating sample apps 119 | - Currently only read-only wrapped lenses 120 | - Copious testing 121 | 122 | A big thanks to [[https://github.com/lsnape][Luke Snape]] for all his help in design/implementation 123 | discussions - it really helped in getting my head around all of this! 124 | 125 | ** 0.1.0 126 | 127 | Initial release - thanks to [[https://github.com/henrygarner][Henry Garner]], [[https://github.com/matlux][Mathieu Gauthron]], [[https://github.com/n8dawgrr][Nathan 128 | Matthews]] and [[https://github.com/malcolmsparks][Malcolm Sparks]], whose advice and feedback made it happen. 129 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Flow 2 | 3 | Flow is a library to help you write dynamic ClojureScript webapps in a 4 | declarative style, without worrying how/when the DOM needs to be 5 | updated. 6 | 7 | #+BEGIN_SRC clojure 8 | [jarohen/flow "0.3.0-alpha3"] 9 | 10 | (:require [flow.core :as f :include-macros true]) 11 | #+END_SRC 12 | 13 | For an 'all-batteries-included' template (including Flow), you might 14 | want to try SPLAT: 15 | 16 | #+BEGIN_SRC shell 17 | lein new splat 18 | #+END_SRC 19 | 20 | ** The Flow DSL 21 | 22 | The main part of Flow is its dynamic element DSL - a language with the 23 | following main design aims: 24 | 25 | - *It's as close to ClojureScript as possible, to minimise the 26 | learning curve.* =let= (including destructuring), =for= *, =case=, 27 | =if=, =when=, =when-let=, etc are all designed to work as they do in 28 | ClojureScript, albeit having been extended to handle continuous 29 | dynamic updates. If you know CLJS, the Flow DSL should be a natural 30 | extension. /(see 'Current Limitations' for a couple of minor 31 | differences')/ 32 | - *Its element syntax is based on Hiccup* - again, a well-known and 33 | widely used DSL in itself. 34 | - *It only adds two new constructs to the language* - =<<=, to extract 35 | a value from a vanilla CLJS atom; and =!<<=, to turn an extracted 36 | value back into an atom. This is covered in more detail below. 37 | - *Flow is entirely declarative* - so there's no need for you to 38 | specify *how* or *when* to update the elements; simply state *what* 39 | they should look like given a certain state, and Flow will do the 40 | rest. 41 | 42 | *** Tutorials 43 | 44 | If you want to dive straight into tutorials, there are a number of 45 | tutorials/example apps in this repository: 46 | 47 | - [[samples/counter][Flow in 5 Minutes]] - writing a 'Hello World'/simple counter 48 | application 49 | - Colour Picker (to come) - an intermediate tutorial introducing =!<<= 50 | - Contacts (to come) - an intermediate tutorial covering splitting 51 | your Flow application into components 52 | - [[samples/todomvc][TodoMVC]] example application - no webapp library would be complete 53 | without it! 54 | 55 | *** Basic Elements 56 | 57 | Flow elements are created inside the =f/el= macro - this transpiles 58 | the declarative Flow DSL into the imperative ClojureScript required to 59 | create and update the elements. 60 | 61 | Elements are created in the traditional Hiccup style: 62 | 63 | #+BEGIN_SRC clojure 64 | (f/el 65 | [:p "Hello world!"]) 66 | #+END_SRC 67 | 68 | The result of =f/el= is a standard, albeit possibly dynamic, DOM 69 | element and can be passed around as such. 70 | 71 | Flow also includes =f/root=, a helper function to include an element 72 | in the page. =f/root= first clears the container element, and then 73 | attaches the Flow component: 74 | 75 | #+BEGIN_SRC clojure 76 | (f/root js/document.body 77 | (f/el 78 | [:p "Hello world!"])) 79 | #+END_SRC 80 | 81 | Elements can be given an ID by appending it to the tag keyword with a 82 | hash: 83 | 84 | #+BEGIN_SRC clojure 85 | (f/root js/document.body 86 | (f/el 87 | [:p#hello "Hello world!"])) 88 | #+END_SRC 89 | 90 | *** =<<= - Reacting to dynamic state 91 | 92 | Flow uses standard ClojureScript atoms to store state, so I'll assume 93 | here we have an atom containing the current value of a 'counter': 94 | 95 | #+BEGIN_SRC clojure 96 | (def !counter 97 | (atom 0)) 98 | #+END_SRC 99 | 100 | The '!' before the name is an optional naming convention - it doesn't 101 | have any effect on Flow. Personally, as a developer, I like using it 102 | because it makes a clear distinction in my code between stateful 103 | variables and immutable values. 104 | 105 | We now need to tell Flow to include the current value of the counter 106 | in our element, which we do using Flow's =(<< ...)= operator. It's 107 | similar in nature to '@'/'deref', and it's used as follows: 108 | 109 | #+BEGIN_SRC clojure 110 | (f/el 111 | [:p "The current value of the counter is " (<< !counter)]) 112 | #+END_SRC 113 | 114 | As it's part of the Flow DSL, =<<= only works inside the =f/el= macro. 115 | 116 | When I'm reading Flow code, I'll usually read =(<< ...)= as 'comes 117 | from', or 'unwrap'. 118 | 119 | Flow is fundamentally declarative in nature. We don't specify any 120 | imperative behaviour here; no 'when the counter updates, then update 121 | this element' - we simply say 'this element contains the up-to-date 122 | value of my atom' and Flow does the rest. 123 | 124 | *** =::f/styles= & =::f/classes= - Styling 125 | 126 | Classes can be added in one of two ways. Static classes can be added 127 | in the same way as Hiccup, by appending them to the tag keyword (after 128 | the ID, if present): 129 | 130 | #+BEGIN_SRC clojure 131 | (f/el 132 | [:div.container 133 | [:p#hello.copy "Hello world!"]]) 134 | #+END_SRC 135 | 136 | If a class should only be applied to an element in certain situations, 137 | use =::f/classes=. Using the example in the previous section, if we 138 | wanted the counter to be of class =.even= when the counter is even, 139 | we'd write: 140 | 141 | #+BEGIN_SRC clojure 142 | (f/el 143 | (let [counter (<< !counter)] 144 | [:p {::f/classes [(when (even? counter) 145 | "even")]} 146 | "The value of the counter is " counter])) 147 | #+END_SRC 148 | 149 | Notice here that we've also moved the =(<< counter)= into a 'let' 150 | block, the same way as we would in vanilla Clojure. Even though 151 | they're not wrapped with =(<< ...)=, Flow knows that the 'counter' 152 | variable came from an atom, and therefore that it needs to update the 153 | classes and the text when the =!counter= atom changes. 154 | 155 | Finally, for inline styles, we use =::f/style=: 156 | 157 | #+BEGIN_SRC clojure 158 | (f/el 159 | [:p {::f/style {:text-align :center 160 | :margin "1em 0"}} 161 | "Hello world!"]) 162 | #+END_SRC 163 | 164 | Style values here can be keywords or strings. 165 | 166 | *N.B. Each of the Flow keywords is namespace-qualified, so uses 167 | a double colon* 168 | 169 | *** =::f/on= - Event Listeners 170 | 171 | Event listeners are attached with the =::f/on= attribute: 172 | 173 | #+BEGIN_SRC clojure 174 | (f/el 175 | [:button {::f/on {:click #(js/alert "Hello!")}} 176 | "Click me!"]) 177 | #+END_SRC 178 | 179 | There is also a helper function, =f/bind-value!=, which binds the 180 | value of an HTML input to a dynamic value or atom: 181 | 182 | #+BEGIN_SRC clojure 183 | (let [!textbox-value (atom nil)] 184 | (f/el 185 | [:input {:type :text 186 | ::f/on {:keyup (f/bind-value! !textbox-value)}}])) 187 | #+END_SRC 188 | 189 | *** Lifecycle callbacks 190 | 191 | Lifecycle callbacks can also be added to the =::f/on= attribute. Flow 192 | only supports one lifecycle callback at the moment, =::f/mount=, which 193 | is called once whenever the element is first mounted. 194 | 195 | It expects a function of one argument - the DOM element that is about 196 | to be mounted: 197 | 198 | #+BEGIN_SRC clojure 199 | (f/el 200 | [:p {::f/on {::f/mount (fn [$p-element] 201 | ;; ... 202 | )}} 203 | "Hello world!"]) 204 | #+END_SRC 205 | 206 | *** Subcomponents 207 | 208 | Flow components can be easily composed - simply call them as you would 209 | a normal function (with any necessary parameters) but, rather than 210 | parens, enclose them in a vector: 211 | 212 | #+BEGIN_SRC clojure 213 | (defn render-list-item [elem] 214 | (f/el 215 | [:li 216 | [:span "Item: " elem]])) 217 | 218 | (defn render-list [elems] 219 | (f/el 220 | [:ul 221 | (for [elem elems] 222 | [render-list-item elem])])) 223 | #+END_SRC 224 | 225 | 226 | 227 | *** =!<<= - Passing dynamic values outside of the =f/el= 228 | 229 | The values extracted from an atom with =<<= are only dynamic within 230 | the scope of the =f/el= in which they are extracted. To ensure that 231 | subcomponents also react to dynamic values, we re-wrap them using 232 | =!<<=: 233 | 234 | #+BEGIN_SRC clojure 235 | (defn render-todo-item [!todo] 236 | ;; '!todo' is an atom here 237 | (f/el 238 | (let [{:keys [caption]} (<< !todo)] 239 | [:li caption]))) 240 | 241 | (defn render-todo-list [!todos] 242 | (f/el 243 | (for [todo (<< !todos)] 244 | [render-todo-item (!<< todo)]))) 245 | #+END_SRC 246 | 247 | Notice here that we don't need to re-wrap the original atom for the 248 | dynamic value to be propagated - we can just as easily wrap sub-keys 249 | of a map, or elements in a vector. 250 | 251 | Unfortunately, Flow can only re-wrap maps, vectors, lists and sets in 252 | this way. For primitives, you can use =!<<= to wrap the containing 253 | collection, and provide an extra key to find the corresponding value: 254 | 255 | #+BEGIN_SRC clojure 256 | (defn render-todo-item [!caption] 257 | ;; Here '!caption' is an atom containing a string. 258 | (f/el 259 | [:li caption])) 260 | 261 | (defn render-todo-list [!todos] 262 | (f/el 263 | (for [todo (<< !todos)] 264 | ;; we only want to pass the ':caption' key as a dynamic value 265 | [render-todo-item (!<< todo [:caption])]))) 266 | #+END_SRC 267 | 268 | Here, we can't wrap 'caption', because it's a string, so we tell 269 | =!<<= that it needs to wrap the ':caption' key of 'todo'. 270 | 271 | *** Collection keys - =f/keyed-by= 272 | 273 | We can also specify the key of a collection, using =f/keyed-by=. This 274 | helps Flow with calculating the difference between one state and the 275 | next - it means that Flow can track individual elements in the 276 | collection in the presence of insertions, deletes and shuffles. 277 | 278 | #+BEGIN_SRC clojure 279 | (defn render-todo-item [!todo] 280 | (f/el 281 | (let [{:keys [caption]} (<< !todo)] 282 | [:li caption]))) 283 | 284 | (defn render-todo-list [!todos] 285 | (f/el 286 | (for [todo (->> (<< !todos) 287 | (f/keyed-by :todo-id))] 288 | [render-todo-item (!<< todo)]))) 289 | #+END_SRC 290 | 291 | =f/keyed-by= takes a key function, and a collection cursor. 292 | 293 | ** Current Limitations 294 | 295 | There are a couple of features still to be implemented: 296 | 297 | - Flow's 'for' doesn't yet support ':when', ':let' or ':while' 298 | clauses - support for this will be added in the future. 299 | 300 | ** Questions/suggestions/bug fixes? 301 | 302 | Yes please! 303 | 304 | If you have any questions, or would like to get involved with Flow's 305 | development, please get in touch! I can be contacted either through 306 | Github, or on Twitter at [[https://twitter.com/jarohen][@jarohen]]. 307 | 308 | Thanks! 309 | 310 | ** Thanks 311 | 312 | A big thanks to [[https://github.com/malcolmsparks][Malcolm Sparks]], [[https://github.com/lsnape][Luke Snape]] and [[https://github.com/henrygarner][Henry Garner]] for their 313 | feedback and advice on early versions of the library, and their help 314 | through numerous design/implementation conversations. Thanks also to 315 | Henry for his excellent suggestion of the =<<= syntax. 316 | 317 | Also, thanks to [[https://github.com/matlux][Mathieu Gauthron]] and [[https://github.com/n8dawgrr][Nathan Matthews]] whose feedback on 318 | Clidget helped shape the direction of Flow. 319 | 320 | Cheers! 321 | 322 | James 323 | 324 | ** License 325 | 326 | Copyright © 2014 James Henderson 327 | 328 | Distributed under the Eclipse Public License, the same as Clojure 329 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject jarohen/flow "0.3.0-alpha3" 2 | :description "Lightweight library to help you write dynamic CLJS webapps" 3 | 4 | :url "https://github.com/james-henderson/flow" 5 | 6 | :license {:name "Eclipse Public License" 7 | :url "http://www.eclipse.org/legal/epl-v10.html"} 8 | 9 | :dependencies [[org.clojure/clojure "1.7.0-alpha4"]] 10 | 11 | :exclusions [org.clojure/clojure] 12 | 13 | :plugins [[com.keminglabs/cljx "0.4.0"]] 14 | 15 | :source-paths ["src" "target/generated/clj"] 16 | 17 | :hooks [cljx.hooks] 18 | 19 | :filespecs [{:type :path 20 | :path "target/generated/cljs"}] 21 | 22 | :cljx {:builds [{:source-paths ["src"] 23 | :output-path "target/generated/clj" 24 | :rules :clj} 25 | 26 | {:source-paths ["src"] 27 | :output-path "target/generated/cljs" 28 | :rules :cljs}]}) 29 | -------------------------------------------------------------------------------- /samples/README.org: -------------------------------------------------------------------------------- 1 | * Flow Tutorials 2 | 3 | Here are a number of Flow tutorials and example applications: 4 | 5 | - [[counter/][Flow in 5 Minutes]] - writing a 'Hello World'/simple counter 6 | application 7 | - Colour Picker (to come) - an intermediate tutorial introducing =!<<= 8 | - Contacts (to come) - an intermediate tutorial covering splitting 9 | your Flow application into components 10 | - [[todomvc/][TodoMVC]] example application - no webapp library would be complete 11 | without it! 12 | -------------------------------------------------------------------------------- /samples/contacts/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .nrepl-port 14 | /out/ 15 | /.repl/ 16 | -------------------------------------------------------------------------------- /samples/contacts/common-src/contacts/formatter.cljx: -------------------------------------------------------------------------------- 1 | (ns contacts.formatter 2 | (:require [clojure.string :as s])) 3 | 4 | ;; Copied verbatim from the Om tutorial at 5 | ;; https://github.com/swannodette/om/wiki/Tutorial 6 | 7 | (defn parse-contact [contact-str] 8 | (let [[first middle last :as parts] (s/split contact-str #"\s+") 9 | [first last middle] (if (nil? last) [first middle] [first last middle]) 10 | middle (when middle (s/replace middle "." "")) 11 | c (if middle (count middle) 0)] 12 | (when (>= (reduce + (map #(if % 1 0) parts)) 2) 13 | (cond-> {:first first :last last} 14 | (== c 1) (assoc :middle-initial middle) 15 | (>= c 2) (assoc :middle middle))))) 16 | 17 | (defn middle-name [{:keys [middle middle-initial]}] 18 | (cond 19 | middle (str " " middle) 20 | middle-initial (str " " middle-initial "."))) 21 | 22 | (defn display-name [{:keys [first last] :as contact}] 23 | (str last ", " first (middle-name contact))) 24 | -------------------------------------------------------------------------------- /samples/contacts/externs/jquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The Closure Compiler Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * @fileoverview Externs for jQuery 1.8.2 19 | * 20 | * Note that some functions use different return types depending on the number 21 | * of parameters passed in. In these cases, you may need to annotate the type 22 | * of the result in your code, so the JSCompiler understands which type you're 23 | * expecting. For example: 24 | * var elt = /** @type {Element} * / (foo.get(0)); 25 | * 26 | * @see http://api.jquery.com/ 27 | * @externs 28 | */ 29 | 30 | /** 31 | * @typedef {(Window|Document|Element|Array.|string|jQuery| 32 | * NodeList)} 33 | */ 34 | var jQuerySelector; 35 | 36 | /** @typedef {function(...)|Array.} */ 37 | var jQueryCallback; 38 | 39 | /** 40 | * @constructor 41 | * @param {(jQuerySelector|Element|Object|Array.|jQuery|string| 42 | * function())=} arg1 43 | * @param {(Element|jQuery|Document| 44 | * Object.)=} arg2 45 | * @return {!jQuery} 46 | */ 47 | function jQuery(arg1, arg2) {} 48 | 49 | /** 50 | * @constructor 51 | * @extends {jQuery} 52 | * @param {(jQuerySelector|Element|Object|Array.|jQuery|string| 53 | * function())=} arg1 54 | * @param {(Element|jQuery|Document| 55 | * Object.)=} arg2 56 | * @return {!jQuery} 57 | */ 58 | function $(arg1, arg2) {} 59 | 60 | /** 61 | * @param {(jQuerySelector|Array.|string|jQuery)} arg1 62 | * @param {Element=} context 63 | * @return {!jQuery} 64 | * @nosideeffects 65 | */ 66 | jQuery.prototype.add = function(arg1, context) {}; 67 | 68 | /** 69 | * @param {(jQuerySelector|Array.|string|jQuery)} arg1 70 | * @return {!jQuery} 71 | * @nosideeffects 72 | */ 73 | jQuery.prototype.addBack = function(arg1) {}; 74 | 75 | /** 76 | * @param {(string|function(number,String))} arg1 77 | * @return {!jQuery} 78 | */ 79 | jQuery.prototype.addClass = function(arg1) {}; 80 | 81 | /** 82 | * @param {(string|Element|jQuery|function(this:Element,number))} arg1 83 | * @param {...(string|Element|jQuery)} var_args 84 | * @return {!jQuery} 85 | */ 86 | jQuery.prototype.after = function(arg1, var_args) {}; 87 | 88 | /** 89 | * @param {(string|Object.)} arg1 90 | * @param {Object.=} settings 91 | * @return {jQuery.jqXHR} 92 | */ 93 | jQuery.ajax = function(arg1, settings) {}; 94 | 95 | /** 96 | * @param {(string|Object.)} arg1 97 | * @param {Object.=} settings 98 | * @return {jQuery.jqXHR} 99 | */ 100 | $.ajax = function(arg1, settings) {}; 101 | 102 | /** 103 | * @param {function(!jQuery.event,XMLHttpRequest,Object.)} handler 104 | * @return {!jQuery} 105 | */ 106 | jQuery.prototype.ajaxComplete = function(handler) {}; 107 | 108 | /** 109 | * @param {function(!jQuery.event,jQuery.jqXHR,Object.,*)} handler 110 | * @return {!jQuery} 111 | */ 112 | jQuery.prototype.ajaxError = function(handler) {}; 113 | 114 | /** 115 | * @param {(string| 116 | * function(Object.,Object.,jQuery.jqXHR))} dataTypes 117 | * @param {function(Object.,Object.,jQuery.jqXHR)=} handler 118 | */ 119 | jQuery.ajaxPrefilter = function(dataTypes, handler) {}; 120 | 121 | /** 122 | * @param {(string| 123 | * function(Object.,Object.,jQuery.jqXHR))} dataTypes 124 | * @param {function(Object.,Object.,jQuery.jqXHR)=} handler 125 | */ 126 | $.ajaxPrefilter = function(dataTypes, handler) {}; 127 | 128 | /** 129 | * @param {function(!jQuery.event,jQuery.jqXHR,Object.)} handler 130 | * @return {!jQuery} 131 | */ 132 | jQuery.prototype.ajaxSend = function(handler) {}; 133 | 134 | /** @const */ 135 | jQuery.ajaxSettings = {}; 136 | 137 | /** @const */ 138 | $.ajaxSettings = {}; 139 | 140 | /** @type {Object.} */ 141 | jQuery.ajaxSettings.accepts = {}; 142 | 143 | /** @type {Object.} */ 144 | $.ajaxSettings.accepts = {}; 145 | 146 | /** @type {boolean} */ 147 | jQuery.ajaxSettings.async; 148 | 149 | /** @type {boolean} */ 150 | $.ajaxSettings.async; 151 | 152 | /** @type {Object.} */ 153 | jQuery.ajaxSettings.contents = {}; 154 | 155 | /** @type {Object.} */ 156 | $.ajaxSettings.contents = {}; 157 | 158 | /** @type {string} */ 159 | jQuery.ajaxSettings.contentType; 160 | 161 | /** @type {string} */ 162 | $.ajaxSettings.contentType; 163 | 164 | /** @type {Object.} */ 165 | jQuery.ajaxSettings.converters = {}; 166 | 167 | /** @type {Object.} */ 168 | $.ajaxSettings.converters = {}; 169 | 170 | /** @type {Object.} */ 171 | jQuery.ajaxSettings.flatOptions = {}; 172 | 173 | /** @type {Object.} */ 174 | $.ajaxSettings.flatOptions = {}; 175 | 176 | /** @type {boolean} */ 177 | jQuery.ajaxSettings.global; 178 | 179 | /** @type {boolean} */ 180 | $.ajaxSettings.global; 181 | 182 | /** @type {boolean} */ 183 | jQuery.ajaxSettings.isLocal; 184 | 185 | /** @type {boolean} */ 186 | $.ajaxSettings.isLocal; 187 | 188 | /** @type {boolean} */ 189 | jQuery.ajaxSettings.processData; 190 | 191 | /** @type {boolean} */ 192 | $.ajaxSettings.processData; 193 | 194 | /** @type {Object.} */ 195 | jQuery.ajaxSettings.responseFields = {}; 196 | 197 | /** @type {Object.} */ 198 | $.ajaxSettings.responseFields = {}; 199 | 200 | /** @type {boolean} */ 201 | jQuery.ajaxSettings.traditional; 202 | 203 | /** @type {boolean} */ 204 | $.ajaxSettings.traditional; 205 | 206 | /** @type {string} */ 207 | jQuery.ajaxSettings.type; 208 | 209 | /** @type {string} */ 210 | $.ajaxSettings.type; 211 | 212 | /** @type {string} */ 213 | jQuery.ajaxSettings.url; 214 | 215 | /** @type {string} */ 216 | $.ajaxSettings.url; 217 | 218 | /** @return {XMLHttpRequest|ActiveXObject} */ 219 | jQuery.ajaxSettings.xhr = function() {}; 220 | 221 | /** @return {XMLHttpRequest|ActiveXObject} */ 222 | $.ajaxSettings.xhr = function() {}; 223 | 224 | /** @param {Object.} options */ 225 | jQuery.ajaxSetup = function(options) {}; 226 | 227 | /** @param {Object.} options */ 228 | $.ajaxSetup = function(options) {}; 229 | 230 | /** 231 | * @param {function()} handler 232 | * @return {!jQuery} 233 | */ 234 | jQuery.prototype.ajaxStart = function(handler) {}; 235 | 236 | /** 237 | * @param {function()} handler 238 | * @return {!jQuery} 239 | */ 240 | jQuery.prototype.ajaxStop = function(handler) {}; 241 | 242 | /** 243 | * @param {function(!jQuery.event,XMLHttpRequest,Object.)} handler 244 | * @return {!jQuery} 245 | */ 246 | jQuery.prototype.ajaxSuccess = function(handler) {}; 247 | 248 | /** 249 | * @return {!jQuery} 250 | * @nosideeffects 251 | */ 252 | jQuery.prototype.andSelf = function() {}; 253 | 254 | /** 255 | * @param {Object.} properties 256 | * @param {(string|number|function()|Object.)=} arg2 257 | * @param {(string|function())=} easing 258 | * @param {function()=} complete 259 | * @return {!jQuery} 260 | */ 261 | jQuery.prototype.animate = function(properties, arg2, easing, complete) {}; 262 | 263 | /** 264 | * @param {(string|Element|jQuery|function(this:Element,number,string))} arg1 265 | * @param {...(string|Element|jQuery)} var_args 266 | * @return {!jQuery} 267 | */ 268 | jQuery.prototype.append = function(arg1, var_args) {}; 269 | 270 | /** 271 | * @param {(jQuerySelector|Element|jQuery)} target 272 | * @return {!jQuery} 273 | */ 274 | jQuery.prototype.appendTo = function(target) {}; 275 | 276 | /** 277 | * @param {(string|Object.)} arg1 278 | * @param {(string|number|boolean|function(number,string))=} arg2 279 | * @return {(string|!jQuery)} 280 | */ 281 | jQuery.prototype.attr = function(arg1, arg2) {}; 282 | 283 | /** 284 | * @param {(string|Element|jQuery|function(this:Element,number))} arg1 285 | * @param {...(string|Element|jQuery)} var_args 286 | * @return {!jQuery} 287 | */ 288 | jQuery.prototype.before = function(arg1, var_args) {}; 289 | 290 | /** 291 | * @param {(string|Object.)} arg1 292 | * @param {(Object.|function(!jQuery.event=)|boolean)=} eventData 293 | * @param {(function(!jQuery.event=)|boolean)=} arg3 294 | * @return {!jQuery} 295 | */ 296 | jQuery.prototype.bind = function(arg1, eventData, arg3) {}; 297 | 298 | /** 299 | * @param {(function(!jQuery.event=)|Object.)=} arg1 300 | * @param {function(!jQuery.event=)=} handler 301 | * @return {!jQuery} 302 | */ 303 | jQuery.prototype.blur = function(arg1, handler) {}; 304 | 305 | /** @type {boolean} */ 306 | jQuery.boxModel; 307 | 308 | /** @type {boolean} */ 309 | $.boxModel; 310 | 311 | /** @type {Object.} */ 312 | jQuery.browser; 313 | 314 | /** @type {Object.} */ 315 | $.browser; 316 | 317 | /** 318 | * @type {boolean} 319 | * @const 320 | */ 321 | jQuery.browser.mozilla; 322 | 323 | /** 324 | * @type {boolean} 325 | * @const 326 | */ 327 | $.browser.mozilla; 328 | 329 | /** 330 | * @type {boolean} 331 | * @const 332 | */ 333 | jQuery.browser.msie; 334 | 335 | /** 336 | * @type {boolean} 337 | * @const 338 | */ 339 | $.browser.msie; 340 | 341 | /** 342 | * @type {boolean} 343 | * @const 344 | */ 345 | jQuery.browser.opera; 346 | 347 | /** 348 | * @type {boolean} 349 | * @const 350 | */ 351 | $.browser.opera; 352 | 353 | /** 354 | * @deprecated 355 | * @type {boolean} 356 | * @const 357 | */ 358 | jQuery.browser.safari; 359 | 360 | /** 361 | * @deprecated 362 | * @type {boolean} 363 | * @const 364 | */ 365 | $.browser.safari; 366 | 367 | /** @type {string} */ 368 | jQuery.browser.version; 369 | 370 | /** @type {string} */ 371 | $.browser.version; 372 | 373 | /** 374 | * @type {boolean} 375 | * @const 376 | */ 377 | jQuery.browser.webkit; 378 | 379 | /** 380 | * @type {boolean} 381 | * @const 382 | */ 383 | $.browser.webkit; 384 | 385 | /** 386 | * @constructor 387 | * @private 388 | */ 389 | jQuery.callbacks = function () {}; 390 | 391 | /** 392 | * @param {string=} flags 393 | * @return {jQuery.callbacks} 394 | */ 395 | jQuery.Callbacks = function (flags) {}; 396 | 397 | /** @param {function()} callbacks */ 398 | jQuery.callbacks.prototype.add = function(callbacks) {}; 399 | 400 | /** @return {undefined} */ 401 | jQuery.callbacks.prototype.disable = function() {}; 402 | 403 | /** @return {undefined} */ 404 | jQuery.callbacks.prototype.empty = function() {}; 405 | 406 | /** @param {...*} var_args */ 407 | jQuery.callbacks.prototype.fire = function(var_args) {}; 408 | 409 | /** @return {boolean} */ 410 | jQuery.callbacks.prototype.fired = function() {}; 411 | 412 | /** @param {...*} var_args */ 413 | jQuery.callbacks.prototype.fireWith = function(var_args) {}; 414 | 415 | /** 416 | * @param {function()} callback 417 | * @return {boolean} 418 | * @nosideeffects 419 | */ 420 | jQuery.callbacks.prototype.has = function(callback) {}; 421 | 422 | /** @return {undefined} */ 423 | jQuery.callbacks.prototype.lock = function() {}; 424 | 425 | /** @return {boolean} */ 426 | jQuery.callbacks.prototype.locked = function() {}; 427 | 428 | /** @param {function()} callbacks */ 429 | jQuery.callbacks.prototype.remove = function(callbacks) {}; 430 | 431 | /** 432 | * @param {(function(!jQuery.event=)|Object.)=} arg1 433 | * @param {function(!jQuery.event=)=} handler 434 | * @return {!jQuery} 435 | */ 436 | jQuery.prototype.change = function(arg1, handler) {}; 437 | 438 | /** 439 | * @param {jQuerySelector=} selector 440 | * @return {!jQuery} 441 | * @nosideeffects 442 | */ 443 | jQuery.prototype.children = function(selector) {}; 444 | 445 | /** 446 | * @param {string=} queueName 447 | * @return {!jQuery} 448 | */ 449 | jQuery.prototype.clearQueue = function(queueName) {}; 450 | 451 | /** 452 | * @param {(function(!jQuery.event=)|Object.)=} arg1 453 | * @param {function(!jQuery.event=)=} handler 454 | * @return {!jQuery} 455 | */ 456 | jQuery.prototype.click = function(arg1, handler) {}; 457 | 458 | /** 459 | * @param {boolean=} withDataAndEvents 460 | * @param {boolean=} deepWithDataAndEvents 461 | * @return {!jQuery} 462 | * @suppress {checkTypes} see issue 583 463 | */ 464 | jQuery.prototype.clone = function(withDataAndEvents, deepWithDataAndEvents) {}; 465 | 466 | /** 467 | * @param {(jQuerySelector|jQuery|Element|string|Array.)} arg1 468 | * @param {Element=} context 469 | * @return {!jQuery} 470 | * @nosideeffects 471 | */ 472 | jQuery.prototype.closest = function(arg1, context) {}; 473 | 474 | /** 475 | * @param {Element} container 476 | * @param {Element} contained 477 | * @return {boolean} 478 | */ 479 | jQuery.contains = function(container, contained) {}; 480 | 481 | /** 482 | * @param {Element} container 483 | * @param {Element} contained 484 | * @return {boolean} 485 | */ 486 | $.contains = function(container, contained) {}; 487 | 488 | /** 489 | * @return {!jQuery} 490 | * @nosideeffects 491 | */ 492 | jQuery.prototype.contents = function() {}; 493 | 494 | /** @type {Element} */ 495 | jQuery.prototype.context; 496 | 497 | /** 498 | * @param {(string|Object.)} arg1 499 | * @param {(string|number|function(number,*))=} arg2 500 | * @return {(string|!jQuery)} 501 | */ 502 | jQuery.prototype.css = function(arg1, arg2) {}; 503 | 504 | /** @type {Object.} */ 505 | jQuery.cssHooks; 506 | 507 | /** @type {Object.} */ 508 | $.cssHooks; 509 | 510 | /** 511 | * @param {Element} elem 512 | * @param {string=} key 513 | * @param {*=} value 514 | * @return {*} 515 | */ 516 | jQuery.data = function(elem, key, value) {}; 517 | 518 | /** 519 | * @param {(string|Object.)=} arg1 520 | * @param {*=} value 521 | * @return {*} 522 | */ 523 | jQuery.prototype.data = function(arg1, value) {}; 524 | 525 | /** 526 | * @param {Element} elem 527 | * @param {string=} key 528 | * @param {*=} value 529 | * @return {*} 530 | */ 531 | $.data = function(elem, key, value) {}; 532 | 533 | /** 534 | * @param {(function(!jQuery.event=)|Object.)=} arg1 535 | * @param {function(!jQuery.event=)=} handler 536 | * @return {!jQuery} 537 | */ 538 | jQuery.prototype.dblclick = function(arg1, handler) {}; 539 | 540 | /** 541 | * @constructor 542 | * @implements {jQuery.Promise} 543 | * @param {function()=} opt_fn 544 | * @see http://api.jquery.com/category/deferred-object/ 545 | */ 546 | jQuery.deferred = function(opt_fn) {}; 547 | 548 | /** 549 | * @constructor 550 | * @extends {jQuery.deferred} 551 | * @param {function()=} opt_fn 552 | * @return {jQuery.Deferred} 553 | */ 554 | jQuery.Deferred = function(opt_fn) {}; 555 | 556 | /** 557 | * @constructor 558 | * @extends {jQuery.deferred} 559 | * @param {function()=} opt_fn 560 | * @see http://api.jquery.com/category/deferred-object/ 561 | */ 562 | $.deferred = function(opt_fn) {}; 563 | 564 | /** 565 | * @constructor 566 | * @extends {jQuery.deferred} 567 | * @param {function()=} opt_fn 568 | * @return {jQuery.deferred} 569 | */ 570 | $.Deferred = function(opt_fn) {}; 571 | 572 | /** 573 | * @override 574 | * @param {jQueryCallback} alwaysCallbacks 575 | * @param {jQueryCallback=} alwaysCallbacks2 576 | * @return {jQuery.deferred} 577 | */ 578 | jQuery.deferred.prototype.always 579 | = function(alwaysCallbacks, alwaysCallbacks2) {}; 580 | 581 | /** 582 | * @override 583 | * @param {jQueryCallback} doneCallbacks 584 | * @param {jQueryCallback=} doneCallbacks2 585 | * @return {jQuery.deferred} 586 | */ 587 | jQuery.deferred.prototype.done = function(doneCallbacks, doneCallbacks2) {}; 588 | 589 | /** 590 | * @override 591 | * @param {jQueryCallback} failCallbacks 592 | * @param {jQueryCallback=} failCallbacks2 593 | * @return {jQuery.deferred} 594 | */ 595 | jQuery.deferred.prototype.fail = function(failCallbacks, failCallbacks2) {}; 596 | 597 | /** 598 | * @param {...*} var_args 599 | * @return {jQuery.deferred} 600 | */ 601 | jQuery.deferred.prototype.notify = function(var_args) {}; 602 | 603 | /** 604 | * @param {Object} context 605 | * @param {...*} var_args 606 | * @return {jQuery.deferred} 607 | */ 608 | jQuery.deferred.prototype.notifyWith = function(context, var_args) {}; 609 | 610 | /** 611 | * @override 612 | * @param {function()=} doneFilter 613 | * @param {function()=} failFilter 614 | * @param {function()=} progressFilter 615 | * @return {jQuery.Promise} 616 | */ 617 | jQuery.deferred.prototype.pipe 618 | = function(doneFilter, failFilter, progressFilter) {}; 619 | 620 | /** 621 | * @param {function()} progressCallbacks 622 | * @return {jQuery.deferred} 623 | */ 624 | jQuery.deferred.prototype.progress = function(progressCallbacks) {}; 625 | 626 | /** 627 | * @param {Object=} target 628 | * @return {jQuery.Promise} 629 | */ 630 | jQuery.deferred.prototype.promise = function(target) {}; 631 | 632 | /** 633 | * @param {...*} var_args 634 | * @return {jQuery.deferred} 635 | */ 636 | jQuery.deferred.prototype.reject = function(var_args) {}; 637 | 638 | /** 639 | * @param {Object} context 640 | * @param {Array.<*>=} args 641 | * @return {jQuery.deferred} 642 | */ 643 | jQuery.deferred.prototype.rejectWith = function(context, args) {}; 644 | 645 | /** 646 | * @param {...*} var_args 647 | * @return {jQuery.deferred} 648 | */ 649 | jQuery.deferred.prototype.resolve = function(var_args) {}; 650 | 651 | /** 652 | * @param {Object} context 653 | * @param {Array.<*>=} args 654 | * @return {jQuery.deferred} 655 | */ 656 | jQuery.deferred.prototype.resolveWith = function(context, args) {}; 657 | 658 | /** @return {string} */ 659 | jQuery.deferred.prototype.state = function() {}; 660 | 661 | /** 662 | * @override 663 | * @param {jQueryCallback} doneCallbacks 664 | * @param {jQueryCallback=} failCallbacks 665 | * @param {jQueryCallback=} progressCallbacks 666 | * @return {jQuery.deferred} 667 | */ 668 | jQuery.deferred.prototype.then 669 | = function(doneCallbacks, failCallbacks, progressCallbacks) {}; 670 | 671 | /** 672 | * @param {number} duration 673 | * @param {string=} queueName 674 | * @return {!jQuery} 675 | */ 676 | jQuery.prototype.delay = function(duration, queueName) {}; 677 | 678 | /** 679 | * @param {string} selector 680 | * @param {(string|Object.)} arg2 681 | * @param {(function(!jQuery.event=)|Object.)=} arg3 682 | * @param {function(!jQuery.event=)=} handler 683 | * @return {!jQuery} 684 | */ 685 | jQuery.prototype.delegate = function(selector, arg2, arg3, handler) {}; 686 | 687 | /** 688 | * @param {Element} elem 689 | * @param {string=} queueName 690 | */ 691 | jQuery.dequeue = function(elem, queueName) {}; 692 | 693 | /** 694 | * @param {string=} queueName 695 | * @return {!jQuery} 696 | */ 697 | jQuery.prototype.dequeue = function(queueName) {}; 698 | 699 | /** 700 | * @param {Element} elem 701 | * @param {string=} queueName 702 | */ 703 | $.dequeue = function(elem, queueName) {}; 704 | 705 | /** 706 | * @param {jQuerySelector=} selector 707 | * @return {!jQuery} 708 | */ 709 | jQuery.prototype.detach = function(selector) {}; 710 | 711 | /** 712 | * @deprecated 713 | * @param {(string|Object.)=} arg1 714 | * @param {string=} handler 715 | * @return {!jQuery} 716 | */ 717 | jQuery.prototype.die = function(arg1, handler) {}; 718 | 719 | /** 720 | * @param {Object} collection 721 | * @param {function(number,?)} callback 722 | * @return {Object} 723 | */ 724 | jQuery.each = function(collection, callback) {}; 725 | 726 | /** 727 | * @param {function(number,Element)} fnc 728 | * @return {!jQuery} 729 | */ 730 | jQuery.prototype.each = function(fnc) {}; 731 | 732 | /** 733 | * @param {Object} collection 734 | * @param {function(number,?)} callback 735 | * @return {Object} 736 | */ 737 | $.each = function(collection, callback) {}; 738 | 739 | /** @return {!jQuery} */ 740 | jQuery.prototype.empty = function() {}; 741 | 742 | /** 743 | * @return {!jQuery} 744 | * @nosideeffects 745 | */ 746 | jQuery.prototype.end = function() {}; 747 | 748 | /** 749 | * @param {number} arg1 750 | * @return {!jQuery} 751 | */ 752 | jQuery.prototype.eq = function(arg1) {}; 753 | 754 | /** @param {string} message */ 755 | jQuery.error = function(message) {}; 756 | 757 | /** 758 | * @deprecated 759 | * @param {(function(!jQuery.event=)|Object.)} arg1 760 | * @param {function(!jQuery.event=)=} handler 761 | * @return {!jQuery} 762 | */ 763 | jQuery.prototype.error = function(arg1, handler) {}; 764 | 765 | /** @param {string} message */ 766 | $.error = function(message) {}; 767 | 768 | /** 769 | * @constructor 770 | * @param {string} eventType 771 | */ 772 | jQuery.event = function(eventType) {}; 773 | 774 | /** 775 | * @constructor 776 | * @extends {jQuery.event} 777 | * @param {string} eventType 778 | * @param {Object=} properties 779 | * @return {jQuery.Event} 780 | */ 781 | jQuery.Event = function(eventType, properties) {}; 782 | 783 | /** 784 | * @constructor 785 | * @extends {jQuery.event} 786 | * @param {string} eventType 787 | */ 788 | $.event = function(eventType) {}; 789 | 790 | /** 791 | * @constructor 792 | * @extends {jQuery.event} 793 | * @param {string} eventType 794 | * @param {Object=} properties 795 | * @return {$.Event} 796 | */ 797 | $.Event = function(eventType, properties) {}; 798 | 799 | /** @type {Element} */ 800 | jQuery.event.prototype.currentTarget; 801 | 802 | /** @type {Object.} */ 803 | jQuery.event.prototype.data; 804 | 805 | /** @type {Element} */ 806 | jQuery.event.prototype.delegateTarget; 807 | 808 | /** 809 | * @return {boolean} 810 | * @nosideeffects 811 | */ 812 | jQuery.event.prototype.isDefaultPrevented = function() {}; 813 | 814 | /** 815 | * @return {boolean} 816 | * @nosideeffects 817 | */ 818 | jQuery.event.prototype.isImmediatePropagationStopped = function() {}; 819 | 820 | /** 821 | * @return {boolean} 822 | * @nosideeffects 823 | */ 824 | jQuery.event.prototype.isPropagationStopped = function() {}; 825 | 826 | /** @type {string} */ 827 | jQuery.event.prototype.namespace; 828 | 829 | /** @type {Event} */ 830 | jQuery.event.prototype.originalEvent; 831 | 832 | /** @type {number} */ 833 | jQuery.event.prototype.pageX; 834 | 835 | /** @type {number} */ 836 | jQuery.event.prototype.pageY; 837 | 838 | /** @return {undefined} */ 839 | jQuery.event.prototype.preventDefault = function() {}; 840 | 841 | /** @type {Object.} */ 842 | jQuery.event.prototype.props; 843 | 844 | /** @type {Element} */ 845 | jQuery.event.prototype.relatedTarget; 846 | 847 | /** @type {*} */ 848 | jQuery.event.prototype.result; 849 | 850 | /** @return {undefined} */ 851 | jQuery.event.prototype.stopImmediatePropagation = function() {}; 852 | 853 | /** @return {undefined} */ 854 | jQuery.event.prototype.stopPropagation = function() {}; 855 | 856 | /** @type {Element} */ 857 | jQuery.event.prototype.target; 858 | 859 | /** @type {number} */ 860 | jQuery.event.prototype.timeStamp; 861 | 862 | /** @type {string} */ 863 | jQuery.event.prototype.type; 864 | 865 | /** @type {number} */ 866 | jQuery.event.prototype.which; 867 | 868 | /** 869 | * @param {(Object|boolean)} arg1 870 | * @param {...*} var_args 871 | * @return {Object} 872 | */ 873 | jQuery.extend = function(arg1, var_args) {}; 874 | 875 | /** 876 | * @param {(Object|boolean)} arg1 877 | * @param {...*} var_args 878 | * @return {Object} 879 | */ 880 | jQuery.prototype.extend = function(arg1, var_args) {}; 881 | 882 | /** 883 | * @param {(Object|boolean)} arg1 884 | * @param {...*} var_args 885 | * @return {Object} 886 | */ 887 | $.extend = function(arg1, var_args) {}; 888 | 889 | /** 890 | * @param {(string|number|function())=} duration 891 | * @param {(function()|string)=} arg2 892 | * @param {function()=} callback 893 | * @return {!jQuery} 894 | */ 895 | jQuery.prototype.fadeIn = function(duration, arg2, callback) {}; 896 | 897 | /** 898 | * @param {(string|number|function())=} duration 899 | * @param {(function()|string)=} arg2 900 | * @param {function()=} callback 901 | * @return {!jQuery} 902 | */ 903 | jQuery.prototype.fadeOut = function(duration, arg2, callback) {}; 904 | 905 | /** 906 | * @param {(string|number)} duration 907 | * @param {number} opacity 908 | * @param {(function()|string)=} arg3 909 | * @param {function()=} callback 910 | * @return {!jQuery} 911 | */ 912 | jQuery.prototype.fadeTo = function(duration, opacity, arg3, callback) {}; 913 | 914 | /** 915 | * @param {(string|number|function())=} duration 916 | * @param {(string|function())=} easing 917 | * @param {function()=} callback 918 | * @return {!jQuery} 919 | */ 920 | jQuery.prototype.fadeToggle = function(duration, easing, callback) {}; 921 | 922 | /** 923 | * @param {(jQuerySelector|function(number)|Element|jQuery)} arg1 924 | * @return {!jQuery} 925 | */ 926 | jQuery.prototype.filter = function(arg1) {}; 927 | 928 | /** 929 | * @param {(jQuerySelector|jQuery|Element)} arg1 930 | * @return {!jQuery} 931 | * @nosideeffects 932 | */ 933 | jQuery.prototype.find = function(arg1) {}; 934 | 935 | /** @return {!jQuery} */ 936 | jQuery.prototype.first = function() {}; 937 | 938 | /** @see http://docs.jquery.com/Plugins/Authoring */ 939 | jQuery.fn; 940 | 941 | /** @see http://docs.jquery.com/Plugins/Authoring */ 942 | $.fn; 943 | 944 | /** 945 | * @param {(function(!jQuery.event=)|Object.)=} arg1 946 | * @param {function(!jQuery.event=)=} handler 947 | * @return {!jQuery} 948 | */ 949 | jQuery.prototype.focus = function(arg1, handler) {}; 950 | 951 | /** 952 | * @param {(function(!jQuery.event=)|Object.)} arg1 953 | * @param {function(!jQuery.event=)=} handler 954 | * @return {!jQuery} 955 | */ 956 | jQuery.prototype.focusin = function(arg1, handler) {}; 957 | 958 | /** 959 | * @param {(function(!jQuery.event=)|Object.)} arg1 960 | * @param {function(!jQuery.event=)=} handler 961 | * @return {!jQuery} 962 | */ 963 | jQuery.prototype.focusout = function(arg1, handler) {}; 964 | 965 | /** @const */ 966 | jQuery.fx = {}; 967 | 968 | /** @const */ 969 | $.fx = {}; 970 | 971 | /** @type {number} */ 972 | jQuery.fx.interval; 973 | 974 | /** @type {number} */ 975 | $.fx.interval; 976 | 977 | /** @type {boolean} */ 978 | jQuery.fx.off; 979 | 980 | /** @type {boolean} */ 981 | $.fx.off; 982 | 983 | /** 984 | * @param {string} url 985 | * @param {(Object.|string| 986 | * function(string,string,jQuery.jqXHR))=} data 987 | * @param {(function(string,string,jQuery.jqXHR)|string)=} success 988 | * @param {string=} dataType 989 | * @return {jQuery.jqXHR} 990 | */ 991 | jQuery.get = function(url, data, success, dataType) {}; 992 | 993 | /** 994 | * @param {number=} index 995 | * @return {(Element|Array.)} 996 | * @nosideeffects 997 | */ 998 | jQuery.prototype.get = function(index) {}; 999 | 1000 | /** 1001 | * @param {string} url 1002 | * @param {(Object.|string| 1003 | * function(string,string,jQuery.jqXHR))=} data 1004 | * @param {(function(string,string,jQuery.jqXHR)|string)=} success 1005 | * @param {string=} dataType 1006 | * @return {jQuery.jqXHR} 1007 | */ 1008 | $.get = function(url, data, success, dataType) {}; 1009 | 1010 | /** 1011 | * @param {string} url 1012 | * @param {(Object.|function(string,string,jQuery.jqXHR))=} data 1013 | * @param {function(string,string,jQuery.jqXHR)=} success 1014 | * @return {jQuery.jqXHR} 1015 | */ 1016 | jQuery.getJSON = function(url, data, success) {}; 1017 | 1018 | /** 1019 | * @param {string} url 1020 | * @param {(Object.|function(string,string,jQuery.jqXHR))=} data 1021 | * @param {function(string,string,jQuery.jqXHR)=} success 1022 | * @return {jQuery.jqXHR} 1023 | */ 1024 | $.getJSON = function(url, data, success) {}; 1025 | 1026 | /** 1027 | * @param {string} url 1028 | * @param {function(Node,string,jQuery.jqXHR)=} success 1029 | * @return {jQuery.jqXHR} 1030 | */ 1031 | jQuery.getScript = function(url, success) {}; 1032 | 1033 | /** 1034 | * @param {string} url 1035 | * @param {function(Node,string,jQuery.jqXHR)=} success 1036 | * @return {jQuery.jqXHR} 1037 | */ 1038 | $.getScript = function(url, success) {}; 1039 | 1040 | /** @param {string} code */ 1041 | jQuery.globalEval = function(code) {}; 1042 | 1043 | /** @param {string} code */ 1044 | $.globalEval = function(code) {}; 1045 | 1046 | /** 1047 | * @param {Array.<*>} arr 1048 | * @param {function(*,number)} fnc 1049 | * @param {boolean=} invert 1050 | * @return {Array.<*>} 1051 | */ 1052 | jQuery.grep = function(arr, fnc, invert) {}; 1053 | 1054 | /** 1055 | * @param {Array.<*>} arr 1056 | * @param {function(*,number)} fnc 1057 | * @param {boolean=} invert 1058 | * @return {Array.<*>} 1059 | */ 1060 | $.grep = function(arr, fnc, invert) {}; 1061 | 1062 | /** 1063 | * @param {(string|Element)} arg1 1064 | * @return {!jQuery} 1065 | * @nosideeffects 1066 | */ 1067 | jQuery.prototype.has = function(arg1) {}; 1068 | 1069 | /** 1070 | * @param {string} className 1071 | * @return {boolean} 1072 | * @nosideeffects 1073 | */ 1074 | jQuery.prototype.hasClass = function(className) {}; 1075 | 1076 | /** 1077 | * @param {Element} elem 1078 | * @return {boolean} 1079 | * @nosideeffects 1080 | */ 1081 | jQuery.hasData = function(elem) {}; 1082 | 1083 | /** 1084 | * @param {Element} elem 1085 | * @return {boolean} 1086 | * @nosideeffects 1087 | */ 1088 | $.hasData = function(elem) {}; 1089 | 1090 | /** 1091 | * @param {(string|number|function(number,number))=} arg1 1092 | * @return {(number|!jQuery)} 1093 | */ 1094 | jQuery.prototype.height = function(arg1) {}; 1095 | 1096 | /** 1097 | * @param {(string|number|function())=} duration 1098 | * @param {(function()|string)=} arg2 1099 | * @param {function()=} callback 1100 | * @return {!jQuery} 1101 | */ 1102 | jQuery.prototype.hide = function(duration, arg2, callback) {}; 1103 | 1104 | /** @param {boolean} hold */ 1105 | jQuery.holdReady = function(hold) {}; 1106 | 1107 | /** @param {boolean} hold */ 1108 | $.holdReady = function(hold) {}; 1109 | 1110 | /** 1111 | * @param {function(!jQuery.event=)} arg1 1112 | * @param {function(!jQuery.event=)=} handlerOut 1113 | * @return {!jQuery} 1114 | */ 1115 | jQuery.prototype.hover = function(arg1, handlerOut) {}; 1116 | 1117 | /** 1118 | * @param {(string|function(number,string))=} arg1 1119 | * @return {(string|!jQuery)} 1120 | */ 1121 | jQuery.prototype.html = function(arg1) {}; 1122 | 1123 | /** 1124 | * @param {*} value 1125 | * @param {Array.<*>} arr 1126 | * @param {number=} fromIndex 1127 | * @return {number} 1128 | * @nosideeffects 1129 | */ 1130 | jQuery.inArray = function(value, arr, fromIndex) {}; 1131 | 1132 | /** 1133 | * @param {*} value 1134 | * @param {Array.<*>} arr 1135 | * @param {number=} fromIndex 1136 | * @return {number} 1137 | * @nosideeffects 1138 | */ 1139 | $.inArray = function(value, arr, fromIndex) {}; 1140 | 1141 | /** 1142 | * @param {(jQuerySelector|Element|jQuery)=} arg1 1143 | * @return {number} 1144 | */ 1145 | jQuery.prototype.index = function(arg1) {}; 1146 | 1147 | /** 1148 | * @return {number} 1149 | * @nosideeffects 1150 | */ 1151 | jQuery.prototype.innerHeight = function() {}; 1152 | 1153 | /** 1154 | * @return {number} 1155 | * @nosideeffects 1156 | */ 1157 | jQuery.prototype.innerWidth = function() {}; 1158 | 1159 | /** 1160 | * @param {(jQuerySelector|Element|jQuery)} target 1161 | * @return {!jQuery} 1162 | */ 1163 | jQuery.prototype.insertAfter = function(target) {}; 1164 | 1165 | /** 1166 | * @param {(jQuerySelector|Element|jQuery)} target 1167 | * @return {!jQuery} 1168 | */ 1169 | jQuery.prototype.insertBefore = function(target) {}; 1170 | 1171 | /** 1172 | * @param {(jQuerySelector|function(number)|jQuery|Element)} arg1 1173 | * @return {boolean} 1174 | */ 1175 | jQuery.prototype.is = function(arg1) {}; 1176 | 1177 | /** 1178 | * @param {*} obj 1179 | * @return {boolean} 1180 | * @nosideeffects 1181 | */ 1182 | jQuery.isArray = function(obj) {}; 1183 | 1184 | /** 1185 | * @param {*} obj 1186 | * @return {boolean} 1187 | * @nosideeffects 1188 | */ 1189 | $.isArray = function(obj) {}; 1190 | 1191 | /** 1192 | * @param {Object} obj 1193 | * @return {boolean} 1194 | * @nosideeffects 1195 | */ 1196 | jQuery.isEmptyObject = function(obj) {}; 1197 | 1198 | /** 1199 | * @param {Object} obj 1200 | * @return {boolean} 1201 | * @nosideeffects 1202 | */ 1203 | $.isEmptyObject = function(obj) {}; 1204 | 1205 | /** 1206 | * @param {*} obj 1207 | * @return {boolean} 1208 | * @nosideeffects 1209 | */ 1210 | jQuery.isFunction = function(obj) {}; 1211 | 1212 | /** 1213 | * @param {*} obj 1214 | * @return {boolean} 1215 | * @nosideeffects 1216 | */ 1217 | $.isFunction = function(obj) {}; 1218 | 1219 | /** 1220 | * @param {*} value 1221 | * @return {boolean} 1222 | * @nosideeffects 1223 | */ 1224 | jQuery.isNumeric = function(value) {}; 1225 | 1226 | /** 1227 | * @param {*} value 1228 | * @return {boolean} 1229 | * @nosideeffects 1230 | */ 1231 | $.isNumeric = function(value) {}; 1232 | 1233 | /** 1234 | * @param {Object} obj 1235 | * @return {boolean} 1236 | * @nosideeffects 1237 | */ 1238 | jQuery.isPlainObject = function(obj) {}; 1239 | 1240 | /** 1241 | * @param {Object} obj 1242 | * @return {boolean} 1243 | * @nosideeffects 1244 | */ 1245 | $.isPlainObject = function(obj) {}; 1246 | 1247 | /** 1248 | * @param {*} obj 1249 | * @return {boolean} 1250 | * @nosideeffects 1251 | */ 1252 | jQuery.isWindow = function(obj) {}; 1253 | 1254 | /** 1255 | * @param {*} obj 1256 | * @return {boolean} 1257 | * @nosideeffects 1258 | */ 1259 | $.isWindow = function(obj) {}; 1260 | 1261 | /** 1262 | * @param {Element} node 1263 | * @return {boolean} 1264 | * @nosideeffects 1265 | */ 1266 | jQuery.isXMLDoc = function(node) {}; 1267 | 1268 | /** 1269 | * @param {Element} node 1270 | * @return {boolean} 1271 | * @nosideeffects 1272 | */ 1273 | $.isXMLDoc = function(node) {}; 1274 | 1275 | /** @type {string} */ 1276 | jQuery.prototype.jquery; 1277 | 1278 | /** 1279 | * @constructor 1280 | * @extends {XMLHttpRequest} 1281 | * @implements {jQuery.Promise} 1282 | * @private 1283 | * @see http://api.jquery.com/jQuery.ajax/#jqXHR 1284 | */ 1285 | jQuery.jqXHR = function () {}; 1286 | 1287 | /** 1288 | * @override 1289 | * @param {jQueryCallback} alwaysCallbacks 1290 | * @param {jQueryCallback=} alwaysCallbacks2 1291 | * @return {jQuery.jqXHR} 1292 | */ 1293 | jQuery.jqXHR.prototype.always = 1294 | function(alwaysCallbacks, alwaysCallbacks2) {}; 1295 | 1296 | /** 1297 | * @deprecated 1298 | * @param {function()} callback 1299 | * @return {jQuery.jqXHR} 1300 | */ 1301 | jQuery.jqXHR.prototype.complete = function (callback) {}; 1302 | 1303 | /** 1304 | * @override 1305 | * @param {jQueryCallback} doneCallbacks 1306 | * @return {jQuery.jqXHR} 1307 | */ 1308 | jQuery.jqXHR.prototype.done = function(doneCallbacks) {}; 1309 | 1310 | /** 1311 | * @deprecated 1312 | * @param {function()} callback 1313 | * @return {jQuery.jqXHR} 1314 | */ 1315 | jQuery.jqXHR.prototype.error = function (callback) {}; 1316 | 1317 | /** 1318 | * @override 1319 | * @param {jQueryCallback} failCallbacks 1320 | * @return {jQuery.jqXHR} 1321 | */ 1322 | jQuery.jqXHR.prototype.fail = function(failCallbacks) {}; 1323 | 1324 | /** 1325 | * @deprecated 1326 | * @override 1327 | */ 1328 | jQuery.jqXHR.prototype.onreadystatechange = function (callback) {}; 1329 | 1330 | /** 1331 | * @override 1332 | * @param {function()=} doneFilter 1333 | * @param {function()=} failFilter 1334 | * @param {function()=} progressFilter 1335 | * @return {jQuery.jqXHR} 1336 | */ 1337 | jQuery.jqXHR.prototype.pipe = 1338 | function(doneFilter, failFilter, progressFilter) {}; 1339 | 1340 | /** 1341 | * @deprecated 1342 | * @param {function()} callback 1343 | * @return {jQuery.jqXHR} 1344 | */ 1345 | jQuery.jqXHR.prototype.success = function (callback) {}; 1346 | 1347 | /** 1348 | * @override 1349 | * @param {jQueryCallback} doneCallbacks 1350 | * @param {jQueryCallback=} failCallbacks 1351 | * @param {jQueryCallback=} progressCallbacks 1352 | * @return {jQuery.jqXHR} 1353 | */ 1354 | jQuery.jqXHR.prototype.then = 1355 | function(doneCallbacks, failCallbacks, progressCallbacks) {}; 1356 | 1357 | /** 1358 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1359 | * @param {function(!jQuery.event=)=} handler 1360 | * @return {!jQuery} 1361 | */ 1362 | jQuery.prototype.keydown = function(arg1, handler) {}; 1363 | 1364 | /** 1365 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1366 | * @param {function(!jQuery.event=)=} handler 1367 | * @return {!jQuery} 1368 | */ 1369 | jQuery.prototype.keypress = function(arg1, handler) {}; 1370 | 1371 | /** 1372 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1373 | * @param {function(!jQuery.event=)=} handler 1374 | * @return {!jQuery} 1375 | */ 1376 | jQuery.prototype.keyup = function(arg1, handler) {}; 1377 | 1378 | /** @return {!jQuery} */ 1379 | jQuery.prototype.last = function() {}; 1380 | 1381 | /** @type {number} */ 1382 | jQuery.prototype.length; 1383 | 1384 | /** 1385 | * @deprecated 1386 | * @param {(string|Object)} arg1 1387 | * @param {(function(!jQuery.event=)|Object)=} arg2 1388 | * @param {function(!jQuery.event=)=} handler 1389 | * @return {!jQuery} 1390 | */ 1391 | jQuery.prototype.live = function(arg1, arg2, handler) {}; 1392 | 1393 | /** 1394 | * @deprecated 1395 | * @param {(function(!jQuery.event=)|Object.|string)} arg1 1396 | * @param {(function(!jQuery.event=)|Object.|string)=} arg2 1397 | * @param {function(string,string,XMLHttpRequest)=} complete 1398 | * @return {!jQuery} 1399 | */ 1400 | jQuery.prototype.load = function(arg1, arg2, complete) {}; 1401 | 1402 | /** 1403 | * @param {*} obj 1404 | * @return {Array.<*>} 1405 | */ 1406 | jQuery.makeArray = function(obj) {}; 1407 | 1408 | /** 1409 | * @param {*} obj 1410 | * @return {Array.<*>} 1411 | */ 1412 | $.makeArray = function(obj) {}; 1413 | 1414 | /** 1415 | * @param {(Array.<*>|Object.)} arg1 1416 | * @param {(function(*,number)|function(*,(string|number)))} callback 1417 | * @return {Array.<*>} 1418 | */ 1419 | jQuery.map = function(arg1, callback) {}; 1420 | 1421 | /** 1422 | * @param {function(number,Element)} callback 1423 | * @return {!jQuery} 1424 | */ 1425 | jQuery.prototype.map = function(callback) {}; 1426 | 1427 | /** 1428 | * @param {(Array.<*>|Object.)} arg1 1429 | * @param {(function(*,number)|function(*,(string|number)))} callback 1430 | * @return {Array.<*>} 1431 | */ 1432 | $.map = function(arg1, callback) {}; 1433 | 1434 | /** 1435 | * @param {Array.<*>} first 1436 | * @param {Array.<*>} second 1437 | * @return {Array.<*>} 1438 | */ 1439 | jQuery.merge = function(first, second) {}; 1440 | 1441 | /** 1442 | * @param {Array.<*>} first 1443 | * @param {Array.<*>} second 1444 | * @return {Array.<*>} 1445 | */ 1446 | $.merge = function(first, second) {}; 1447 | 1448 | /** 1449 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1450 | * @param {function(!jQuery.event=)=} handler 1451 | * @return {!jQuery} 1452 | */ 1453 | jQuery.prototype.mousedown = function(arg1, handler) {}; 1454 | 1455 | /** 1456 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1457 | * @param {function(!jQuery.event=)=} handler 1458 | * @return {!jQuery} 1459 | */ 1460 | jQuery.prototype.mouseenter = function(arg1, handler) {}; 1461 | 1462 | /** 1463 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1464 | * @param {function(!jQuery.event=)=} handler 1465 | * @return {!jQuery} 1466 | */ 1467 | jQuery.prototype.mouseleave = function(arg1, handler) {}; 1468 | 1469 | /** 1470 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1471 | * @param {function(!jQuery.event=)=} handler 1472 | * @return {!jQuery} 1473 | */ 1474 | jQuery.prototype.mousemove = function(arg1, handler) {}; 1475 | 1476 | /** 1477 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1478 | * @param {function(!jQuery.event=)=} handler 1479 | * @return {!jQuery} 1480 | */ 1481 | jQuery.prototype.mouseout = function(arg1, handler) {}; 1482 | 1483 | /** 1484 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1485 | * @param {function(!jQuery.event=)=} handler 1486 | * @return {!jQuery} 1487 | */ 1488 | jQuery.prototype.mouseover = function(arg1, handler) {}; 1489 | 1490 | /** 1491 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1492 | * @param {function(!jQuery.event=)=} handler 1493 | * @return {!jQuery} 1494 | */ 1495 | jQuery.prototype.mouseup = function(arg1, handler) {}; 1496 | 1497 | /** 1498 | * @param {jQuerySelector=} selector 1499 | * @return {!jQuery} 1500 | * @nosideeffects 1501 | */ 1502 | jQuery.prototype.next = function(selector) {}; 1503 | 1504 | /** 1505 | * @param {string=} selector 1506 | * @return {!jQuery} 1507 | * @nosideeffects 1508 | */ 1509 | jQuery.prototype.nextAll = function(selector) {}; 1510 | 1511 | /** 1512 | * @param {(jQuerySelector|Element)=} arg1 1513 | * @param {jQuerySelector=} filter 1514 | * @return {!jQuery} 1515 | * @nosideeffects 1516 | */ 1517 | jQuery.prototype.nextUntil = function(arg1, filter) {}; 1518 | 1519 | /** 1520 | * @param {boolean=} removeAll 1521 | * @return {Object} 1522 | */ 1523 | jQuery.noConflict = function(removeAll) {}; 1524 | 1525 | /** 1526 | * @param {boolean=} removeAll 1527 | * @return {Object} 1528 | */ 1529 | $.noConflict = function(removeAll) {}; 1530 | 1531 | /** 1532 | * @return {function()} 1533 | * @nosideeffects 1534 | */ 1535 | jQuery.noop = function() {}; 1536 | 1537 | /** 1538 | * @return {function()} 1539 | * @nosideeffects 1540 | */ 1541 | $.noop = function() {}; 1542 | 1543 | /** 1544 | * @param {(jQuerySelector|Array.|function(number)|jQuery)} arg1 1545 | * @return {!jQuery} 1546 | */ 1547 | jQuery.prototype.not = function(arg1) {}; 1548 | 1549 | /** 1550 | * @return {number} 1551 | * @nosideeffects 1552 | */ 1553 | jQuery.now = function() {}; 1554 | 1555 | /** 1556 | * @return {number} 1557 | * @nosideeffects 1558 | */ 1559 | $.now = function() {}; 1560 | 1561 | /** 1562 | * @param {(string|Object.)} arg1 1563 | * @param {(string|function(!jQuery.event=))=} selector 1564 | * @param {function(!jQuery.event=)=} handler 1565 | * @return {!jQuery} 1566 | */ 1567 | jQuery.prototype.off = function(arg1, selector, handler) {}; 1568 | 1569 | /** 1570 | * @param {({left:number,top:number}| 1571 | * function(number,{top:number,left:number}))=} arg1 1572 | * @return {({left:number,top:number}|!jQuery)} 1573 | */ 1574 | jQuery.prototype.offset = function(arg1) {}; 1575 | 1576 | /** 1577 | * @return {!jQuery} 1578 | * @nosideeffects 1579 | */ 1580 | jQuery.prototype.offsetParent = function() {}; 1581 | 1582 | /** 1583 | * @param {(string|Object.)} arg1 1584 | * @param {*=} selector 1585 | * @param {*=} data 1586 | * @param {function(!jQuery.event=)=} handler 1587 | * @return {!jQuery} 1588 | */ 1589 | jQuery.prototype.on = function(arg1, selector, data, handler) {}; 1590 | 1591 | /** 1592 | * @param {(string|Object.)} arg1 1593 | * @param {*=} arg2 1594 | * @param {*=} arg3 1595 | * @param {function(!jQuery.event=)=} handler 1596 | * @return {!jQuery} 1597 | */ 1598 | jQuery.prototype.one = function(arg1, arg2, arg3, handler) {}; 1599 | 1600 | /** 1601 | * @param {boolean=} includeMargin 1602 | * @return {number} 1603 | * @nosideeffects 1604 | */ 1605 | jQuery.prototype.outerHeight = function(includeMargin) {}; 1606 | 1607 | /** 1608 | * @param {boolean=} includeMargin 1609 | * @return {number} 1610 | * @nosideeffects 1611 | */ 1612 | jQuery.prototype.outerWidth = function(includeMargin) {}; 1613 | 1614 | /** 1615 | * @param {(Object.|Array.>)} obj 1616 | * @param {boolean=} traditional 1617 | * @return {string} 1618 | */ 1619 | jQuery.param = function(obj, traditional) {}; 1620 | 1621 | /** 1622 | * @param {(Object.|Array.>)} obj 1623 | * @param {boolean=} traditional 1624 | * @return {string} 1625 | */ 1626 | $.param = function(obj, traditional) {}; 1627 | 1628 | /** 1629 | * @param {jQuerySelector=} selector 1630 | * @return {!jQuery} 1631 | * @nosideeffects 1632 | */ 1633 | jQuery.prototype.parent = function(selector) {}; 1634 | 1635 | /** 1636 | * @param {jQuerySelector=} selector 1637 | * @return {!jQuery} 1638 | * @nosideeffects 1639 | */ 1640 | jQuery.prototype.parents = function(selector) {}; 1641 | 1642 | /** 1643 | * @param {(jQuerySelector|Element)=} arg1 1644 | * @param {jQuerySelector=} filter 1645 | * @return {!jQuery} 1646 | * @nosideeffects 1647 | */ 1648 | jQuery.prototype.parentsUntil = function(arg1, filter) {}; 1649 | 1650 | /** 1651 | * @param {string} json 1652 | * @return {Object.} 1653 | */ 1654 | jQuery.parseJSON = function(json) {}; 1655 | 1656 | /** 1657 | * @param {string} json 1658 | * @return {Object.} 1659 | */ 1660 | $.parseJSON = function(json) {}; 1661 | 1662 | /** 1663 | * @param {string} data 1664 | * @return {Document} 1665 | */ 1666 | jQuery.parseXML = function(data) {}; 1667 | 1668 | /** 1669 | * @param {string} data 1670 | * @return {Document} 1671 | */ 1672 | $.parseXML = function(data) {}; 1673 | 1674 | /** 1675 | * @return {{left:number,top:number}} 1676 | * @nosideeffects 1677 | */ 1678 | jQuery.prototype.position = function() {}; 1679 | 1680 | /** 1681 | * @param {string} url 1682 | * @param {(Object.|string| 1683 | * function(string,string,jQuery.jqXHR))=} data 1684 | * @param {(function(string,string,jQuery.jqXHR)|string)=} success 1685 | * @param {string=} dataType 1686 | * @return {jQuery.jqXHR} 1687 | */ 1688 | jQuery.post = function(url, data, success, dataType) {}; 1689 | 1690 | /** 1691 | * @param {string} url 1692 | * @param {(Object.|string| 1693 | * function(string,string,jQuery.jqXHR))=} data 1694 | * @param {(function(string,string,jQuery.jqXHR)|string)=} success 1695 | * @param {string=} dataType 1696 | * @return {jQuery.jqXHR} 1697 | */ 1698 | $.post = function(url, data, success, dataType) {}; 1699 | 1700 | /** 1701 | * @param {(string|Element|jQuery|function(this:Element,number,string))} arg1 1702 | * @param {...(string|Element|jQuery)} var_args 1703 | * @return {!jQuery} 1704 | */ 1705 | jQuery.prototype.prepend = function(arg1, var_args) {}; 1706 | 1707 | /** 1708 | * @param {(jQuerySelector|Element|jQuery)} target 1709 | * @return {!jQuery} 1710 | */ 1711 | jQuery.prototype.prependTo = function(target) {}; 1712 | 1713 | /** 1714 | * @param {jQuerySelector=} selector 1715 | * @return {!jQuery} 1716 | * @nosideeffects 1717 | */ 1718 | jQuery.prototype.prev = function(selector) {}; 1719 | 1720 | /** 1721 | * @param {jQuerySelector=} selector 1722 | * @return {!jQuery} 1723 | * @nosideeffects 1724 | */ 1725 | jQuery.prototype.prevAll = function(selector) {}; 1726 | 1727 | /** 1728 | * @param {(jQuerySelector|Element)=} arg1 1729 | * @param {jQuerySelector=} filter 1730 | * @return {!jQuery} 1731 | * @nosideeffects 1732 | */ 1733 | jQuery.prototype.prevUntil = function(arg1, filter) {}; 1734 | 1735 | /** 1736 | * @param {(string|Object)=} type 1737 | * @param {Object=} target 1738 | * @return {jQuery.Promise} 1739 | */ 1740 | jQuery.prototype.promise = function(type, target) {}; 1741 | 1742 | /** 1743 | * @interface 1744 | * @private 1745 | * @see http://api.jquery.com/Types/#Promise 1746 | */ 1747 | jQuery.Promise = function () {}; 1748 | 1749 | /** 1750 | * @param {jQueryCallback} alwaysCallbacks 1751 | * @param {jQueryCallback=} alwaysCallbacks2 1752 | * @return {jQuery.Promise} 1753 | */ 1754 | jQuery.Promise.prototype.always = 1755 | function(alwaysCallbacks, alwaysCallbacks2) {}; 1756 | 1757 | /** 1758 | * @param {jQueryCallback} doneCallbacks 1759 | * @return {jQuery.Promise} 1760 | */ 1761 | jQuery.Promise.prototype.done = function(doneCallbacks) {}; 1762 | 1763 | /** 1764 | * @param {jQueryCallback} failCallbacks 1765 | * @return {jQuery.Promise} 1766 | */ 1767 | jQuery.Promise.prototype.fail = function(failCallbacks) {}; 1768 | 1769 | /** 1770 | * @param {function()=} doneFilter 1771 | * @param {function()=} failFilter 1772 | * @param {function()=} progressFilter 1773 | * @return {jQuery.Promise} 1774 | */ 1775 | jQuery.Promise.prototype.pipe = 1776 | function(doneFilter, failFilter, progressFilter) {}; 1777 | 1778 | /** 1779 | * @param {jQueryCallback} doneCallbacks 1780 | * @param {jQueryCallback=} failCallbacks 1781 | * @param {jQueryCallback=} progressCallbacks 1782 | * @return {jQuery.Promise} 1783 | */ 1784 | jQuery.Promise.prototype.then = 1785 | function(doneCallbacks, failCallbacks, progressCallbacks) {}; 1786 | 1787 | /** 1788 | * @param {(string|Object.)} arg1 1789 | * @param {(string|number|boolean|function(number,String))=} arg2 1790 | * @return {(string|boolean|!jQuery)} 1791 | */ 1792 | jQuery.prototype.prop = function(arg1, arg2) {}; 1793 | 1794 | /** 1795 | * @param {...*} var_args 1796 | * @return {function()} 1797 | */ 1798 | jQuery.proxy = function(var_args) {}; 1799 | 1800 | /** 1801 | * @param {...*} var_args 1802 | * @return {function()} 1803 | */ 1804 | $.proxy = function(var_args) {}; 1805 | 1806 | /** 1807 | * @param {Array.} elements 1808 | * @param {string=} name 1809 | * @param {Array.<*>=} args 1810 | * @return {!jQuery} 1811 | */ 1812 | jQuery.prototype.pushStack = function(elements, name, args) {}; 1813 | 1814 | /** 1815 | * @param {(string|Array.|function(function()))=} queueName 1816 | * @param {(Array.|function(function()))=} arg2 1817 | * @return {(Array.|!jQuery)} 1818 | */ 1819 | jQuery.prototype.queue = function(queueName, arg2) {}; 1820 | 1821 | /** 1822 | * @param {Element} elem 1823 | * @param {string=} queueName 1824 | * @param {(Array.|function())=} arg3 1825 | * @return {(Array.|!jQuery)} 1826 | */ 1827 | jQuery.queue = function(elem, queueName, arg3) {}; 1828 | 1829 | /** 1830 | * @param {Element} elem 1831 | * @param {string=} queueName 1832 | * @param {(Array.|function())=} arg3 1833 | * @return {(Array.|!jQuery)} 1834 | */ 1835 | $.queue = function(elem, queueName, arg3) {}; 1836 | 1837 | /** 1838 | * @param {function()} handler 1839 | * @return {!jQuery} 1840 | */ 1841 | jQuery.prototype.ready = function(handler) {}; 1842 | 1843 | /** 1844 | * @param {string=} selector 1845 | * @return {!jQuery} 1846 | */ 1847 | jQuery.prototype.remove = function(selector) {}; 1848 | 1849 | /** 1850 | * @param {string} attributeName 1851 | * @return {!jQuery} 1852 | */ 1853 | jQuery.prototype.removeAttr = function(attributeName) {}; 1854 | 1855 | /** 1856 | * @param {(string|function(number,string))=} arg1 1857 | * @return {!jQuery} 1858 | */ 1859 | jQuery.prototype.removeClass = function(arg1) {}; 1860 | 1861 | /** 1862 | * @param {(string|Array.)=} arg1 1863 | * @return {!jQuery} 1864 | */ 1865 | jQuery.prototype.removeData = function(arg1) {}; 1866 | 1867 | /** 1868 | * @param {Element} elem 1869 | * @param {string=} name 1870 | * @return {!jQuery} 1871 | */ 1872 | jQuery.removeData = function(elem, name) {}; 1873 | 1874 | /** 1875 | * @param {Element} elem 1876 | * @param {string=} name 1877 | * @return {!jQuery} 1878 | */ 1879 | $.removeData = function(elem, name) {}; 1880 | 1881 | /** 1882 | * @param {string} propertyName 1883 | * @return {!jQuery} 1884 | */ 1885 | jQuery.prototype.removeProp = function(propertyName) {}; 1886 | 1887 | /** 1888 | * @param {jQuerySelector} target 1889 | * @return {!jQuery} 1890 | */ 1891 | jQuery.prototype.replaceAll = function(target) {}; 1892 | 1893 | /** 1894 | * @param {(string|Element|jQuery|function())} arg1 1895 | * @return {!jQuery} 1896 | */ 1897 | jQuery.prototype.replaceWith = function(arg1) {}; 1898 | 1899 | /** 1900 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1901 | * @param {function(!jQuery.event=)=} handler 1902 | * @return {!jQuery} 1903 | */ 1904 | jQuery.prototype.resize = function(arg1, handler) {}; 1905 | 1906 | /** 1907 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1908 | * @param {function(!jQuery.event=)=} handler 1909 | * @return {!jQuery} 1910 | */ 1911 | jQuery.prototype.scroll = function(arg1, handler) {}; 1912 | 1913 | /** 1914 | * @param {number=} value 1915 | * @return {(number|!jQuery)} 1916 | */ 1917 | jQuery.prototype.scrollLeft = function(value) {}; 1918 | 1919 | /** 1920 | * @param {number=} value 1921 | * @return {(number|!jQuery)} 1922 | */ 1923 | jQuery.prototype.scrollTop = function(value) {}; 1924 | 1925 | /** 1926 | * @param {(function(!jQuery.event=)|Object.)=} arg1 1927 | * @param {function(!jQuery.event=)=} handler 1928 | * @return {!jQuery} 1929 | */ 1930 | jQuery.prototype.select = function(arg1, handler) {}; 1931 | 1932 | /** 1933 | * @return {string} 1934 | * @nosideeffects 1935 | */ 1936 | jQuery.prototype.serialize = function() {}; 1937 | 1938 | /** 1939 | * @return {Array.>} 1940 | * @nosideeffects 1941 | */ 1942 | jQuery.prototype.serializeArray = function() {}; 1943 | 1944 | /** 1945 | * @param {(string|number|function())=} duration 1946 | * @param {(function()|string)=} arg2 1947 | * @param {function()=} callback 1948 | * @return {!jQuery} 1949 | */ 1950 | jQuery.prototype.show = function(duration, arg2, callback) {}; 1951 | 1952 | /** 1953 | * @param {jQuerySelector=} selector 1954 | * @return {!jQuery} 1955 | * @nosideeffects 1956 | */ 1957 | jQuery.prototype.siblings = function(selector) {}; 1958 | 1959 | /** 1960 | * @deprecated 1961 | * @return {number} 1962 | * @nosideeffects 1963 | */ 1964 | jQuery.prototype.size = function() {}; 1965 | 1966 | /** 1967 | * @param {number} start 1968 | * @param {number=} end 1969 | * @return {!jQuery} 1970 | */ 1971 | jQuery.prototype.slice = function(start, end) {}; 1972 | 1973 | /** 1974 | * @param {(Object.|string|number)=} optionsOrDuration 1975 | * @param {(function()|string)=} completeOrEasing 1976 | * @param {function()=} complete 1977 | * @return {!jQuery} 1978 | */ 1979 | jQuery.prototype.slideDown = 1980 | function(optionsOrDuration, completeOrEasing, complete) {}; 1981 | 1982 | /** 1983 | * @param {(Object.|string|number)=} optionsOrDuration 1984 | * @param {(function()|string)=} completeOrEasing 1985 | * @param {function()=} complete 1986 | * @return {!jQuery} 1987 | */ 1988 | jQuery.prototype.slideToggle = 1989 | function(optionsOrDuration, completeOrEasing, complete) {}; 1990 | 1991 | /** 1992 | * @param {(Object.|string|number)=} optionsOrDuration 1993 | * @param {(function()|string)=} completeOrEasing 1994 | * @param {function()=} complete 1995 | * @return {!jQuery} 1996 | */ 1997 | jQuery.prototype.slideUp = 1998 | function(optionsOrDuration, completeOrEasing, complete) {}; 1999 | 2000 | /** 2001 | * @param {(boolean|string)=} arg1 2002 | * @param {boolean=} arg2 2003 | * @param {boolean=} jumpToEnd 2004 | * @return {!jQuery} 2005 | */ 2006 | jQuery.prototype.stop = function(arg1, arg2, jumpToEnd) {}; 2007 | 2008 | /** 2009 | * @deprecated 2010 | * @return {!jQuery} 2011 | * @nosideeffects 2012 | */ 2013 | jQuery.sub = function() {}; 2014 | 2015 | /** 2016 | * @deprecated 2017 | * @return {!jQuery} 2018 | * @nosideeffects 2019 | */ 2020 | $.sub = function() {}; 2021 | 2022 | /** 2023 | * @param {(function(!jQuery.event=)|Object.)=} arg1 2024 | * @param {function(!jQuery.event=)=} handler 2025 | * @return {!jQuery} 2026 | */ 2027 | jQuery.prototype.submit = function(arg1, handler) {}; 2028 | 2029 | /** @type {Object.} */ 2030 | jQuery.support; 2031 | 2032 | /** @type {Object.} */ 2033 | $.support; 2034 | 2035 | /** @type {boolean} */ 2036 | jQuery.support.boxModel; 2037 | 2038 | /** @type {boolean} */ 2039 | $.support.boxModel; 2040 | 2041 | /** @type {boolean} */ 2042 | jQuery.support.changeBubbles; 2043 | 2044 | /** @type {boolean} */ 2045 | $.support.changeBubbles; 2046 | 2047 | /** @type {boolean} */ 2048 | jQuery.support.cssFloat; 2049 | 2050 | /** @type {boolean} */ 2051 | $.support.cssFloat; 2052 | 2053 | /** @type {boolean} */ 2054 | jQuery.support.fixedPosition; 2055 | 2056 | /** @type {boolean} */ 2057 | $.support.fixedPosition; 2058 | 2059 | /** @type {boolean} */ 2060 | jQuery.support.hrefNormalized; 2061 | 2062 | /** @type {boolean} */ 2063 | $.support.hrefNormalized; 2064 | 2065 | /** @type {boolean} */ 2066 | jQuery.support.htmlSerialize; 2067 | 2068 | /** @type {boolean} */ 2069 | $.support.htmlSerialize; 2070 | 2071 | /** @type {boolean} */ 2072 | jQuery.support.leadingWhitespace; 2073 | 2074 | /** @type {boolean} */ 2075 | $.support.leadingWhitespace; 2076 | 2077 | /** @type {boolean} */ 2078 | jQuery.support.noCloneEvent; 2079 | 2080 | /** @type {boolean} */ 2081 | $.support.noCloneEvent; 2082 | 2083 | /** @type {boolean} */ 2084 | jQuery.support.opacity; 2085 | 2086 | /** @type {boolean} */ 2087 | $.support.opacity; 2088 | 2089 | /** @type {boolean} */ 2090 | jQuery.support.scriptEval; 2091 | 2092 | /** @type {boolean} */ 2093 | $.support.scriptEval; 2094 | 2095 | /** @type {boolean} */ 2096 | jQuery.support.style; 2097 | 2098 | /** @type {boolean} */ 2099 | $.support.style; 2100 | 2101 | /** @type {boolean} */ 2102 | jQuery.support.submitBubbles; 2103 | 2104 | /** @type {boolean} */ 2105 | $.support.submitBubbles; 2106 | 2107 | /** @type {boolean} */ 2108 | jQuery.support.tbody; 2109 | 2110 | /** @type {boolean} */ 2111 | $.support.tbody; 2112 | 2113 | /** 2114 | * @param {(string|function(number,string))=} arg1 2115 | * @return {(string|!jQuery)} 2116 | */ 2117 | jQuery.prototype.text = function(arg1) {}; 2118 | 2119 | /** 2120 | * @return {Array.} 2121 | * @nosideeffects 2122 | */ 2123 | jQuery.prototype.toArray = function() {}; 2124 | 2125 | /** 2126 | * @deprecated 2127 | * @param {(function(!jQuery.event=)|string|number|function()|boolean)=} arg1 2128 | * @param {(function(!jQuery.event=)|function()|string)=} arg2 2129 | * @param {(function(!jQuery.event=)|function())=} arg3 2130 | * @return {!jQuery} 2131 | */ 2132 | jQuery.prototype.toggle = function(arg1, arg2, arg3) {}; 2133 | 2134 | /** 2135 | * @param {(string|boolean|function(number,string,boolean))=} arg1 2136 | * @param {boolean=} flag 2137 | * @return {!jQuery} 2138 | */ 2139 | jQuery.prototype.toggleClass = function(arg1, flag) {}; 2140 | 2141 | /** 2142 | * @param {(string|jQuery.event)} arg1 2143 | * @param {...*} var_args 2144 | * @return {!jQuery} 2145 | */ 2146 | jQuery.prototype.trigger = function(arg1, var_args) {}; 2147 | 2148 | /** 2149 | * @param {string|jQuery.event} eventType 2150 | * @param {Array.<*>=} extraParameters 2151 | * @return {*} 2152 | */ 2153 | jQuery.prototype.triggerHandler = function(eventType, extraParameters) {}; 2154 | 2155 | /** 2156 | * @param {string} str 2157 | * @return {string} 2158 | * @nosideeffects 2159 | */ 2160 | jQuery.trim = function(str) {}; 2161 | 2162 | /** 2163 | * @param {string} str 2164 | * @return {string} 2165 | * @nosideeffects 2166 | */ 2167 | $.trim = function(str) {}; 2168 | 2169 | /** 2170 | * @param {*} obj 2171 | * @return {string} 2172 | * @nosideeffects 2173 | */ 2174 | jQuery.type = function(obj) {}; 2175 | 2176 | /** 2177 | * @param {*} obj 2178 | * @return {string} 2179 | * @nosideeffects 2180 | */ 2181 | $.type = function(obj) {}; 2182 | 2183 | /** 2184 | * @param {(string|function(!jQuery.event=)|jQuery.event)=} arg1 2185 | * @param {(function(!jQuery.event=)|boolean)=} arg2 2186 | * @return {!jQuery} 2187 | */ 2188 | jQuery.prototype.unbind = function(arg1, arg2) {}; 2189 | 2190 | /** 2191 | * @param {string=} arg1 2192 | * @param {(string|Object.)=} arg2 2193 | * @param {function(!jQuery.event=)=} handler 2194 | * @return {!jQuery} 2195 | */ 2196 | jQuery.prototype.undelegate = function(arg1, arg2, handler) {}; 2197 | 2198 | /** 2199 | * @param {Array.} arr 2200 | * @return {Array.} 2201 | */ 2202 | jQuery.unique = function(arr) {}; 2203 | 2204 | /** 2205 | * @param {Array.} arr 2206 | * @return {Array.} 2207 | */ 2208 | $.unique = function(arr) {}; 2209 | 2210 | /** 2211 | * @deprecated 2212 | * @param {(function(!jQuery.event=)|Object.)} arg1 2213 | * @param {function(!jQuery.event=)=} handler 2214 | * @return {!jQuery} 2215 | */ 2216 | jQuery.prototype.unload = function(arg1, handler) {}; 2217 | 2218 | /** @return {!jQuery} */ 2219 | jQuery.prototype.unwrap = function() {}; 2220 | 2221 | /** 2222 | * @param {(string|Array.|function(number,*))=} arg1 2223 | * @return {(string|number|Array.|!jQuery)} 2224 | */ 2225 | jQuery.prototype.val = function(arg1) {}; 2226 | 2227 | /** 2228 | * @param {jQuery.deferred} deferred 2229 | * @param {...jQuery.deferred} deferreds 2230 | * @return {jQuery.Promise} 2231 | */ 2232 | jQuery.when = function(deferred, deferreds) {}; 2233 | 2234 | /** 2235 | * @param {jQuery.deferred} deferred 2236 | * @param {...jQuery.deferred} deferreds 2237 | * @return {jQuery.Promise} 2238 | */ 2239 | $.when = function(deferred, deferreds) {}; 2240 | 2241 | /** 2242 | * @param {(string|number|function(number,number))=} arg1 2243 | * @return {(number|!jQuery)} 2244 | */ 2245 | jQuery.prototype.width = function(arg1) {}; 2246 | 2247 | /** 2248 | * @param {(string|jQuerySelector|Element|jQuery|function(number))} arg1 2249 | * @return {!jQuery} 2250 | */ 2251 | jQuery.prototype.wrap = function(arg1) {}; 2252 | 2253 | /** 2254 | * @param {(string|jQuerySelector|Element|jQuery)} wrappingElement 2255 | * @return {!jQuery} 2256 | */ 2257 | jQuery.prototype.wrapAll = function(wrappingElement) {}; 2258 | 2259 | /** 2260 | * @param {(string|function(number))} arg1 2261 | * @return {!jQuery} 2262 | */ 2263 | jQuery.prototype.wrapInner = function(arg1) {}; 2264 | -------------------------------------------------------------------------------- /samples/contacts/project.clj: -------------------------------------------------------------------------------- 1 | (defproject jarohen/flow.contacts "" 2 | 3 | :license {:name "Eclipse Public License" 4 | :url "http://www.eclipse.org/legal/epl-v10.html"} 5 | 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | 8 | [ring/ring-core "1.2.0"] 9 | [compojure "1.1.6"] 10 | [hiccup "1.0.5"] 11 | 12 | [jarohen/flow "0.3.0-SNAPSHOT"] 13 | 14 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 15 | [org.clojure/clojurescript "0.0-2371"]] 16 | 17 | :plugins [[jarohen/lein-frodo "0.4.1"] 18 | [jarohen/simple-brepl "0.1.2"] 19 | [lein-cljsbuild "1.0.3"] 20 | [lein-pdo "0.1.1"] 21 | 22 | [com.keminglabs/cljx "0.4.0"] 23 | [lein-shell "0.4.0"]] 24 | 25 | :frodo/config-resource "contacts-config.edn" 26 | 27 | :source-paths ["src" "target/generated/clj"] 28 | 29 | :resource-paths ["resources" "target/resources"] 30 | 31 | :cljx {:builds [{:source-paths ["common-src"] 32 | :output-path "target/generated/clj" 33 | :rules :clj} 34 | 35 | {:source-paths ["common-src"] 36 | :output-path "target/generated/cljs" 37 | :rules :cljs}]} 38 | 39 | :cljsbuild {:builds {:dev 40 | {:source-paths ["ui-src" "target/generated/cljs" "../../src" "../../target/generated/cljs"] 41 | :compiler {:output-to "target/resources/js/contacts.js" 42 | :output-dir "target/resources/js/" 43 | :optimizations :none 44 | :pretty-print true}} 45 | 46 | :prod 47 | {:source-paths ["ui-src"] 48 | :compiler {:output-to "target/resources/js/contacts.js" 49 | :optimizations :advanced 50 | 51 | :pretty-print true}}}} 52 | 53 | :aliases {"dev" ["do" 54 | ["shell" "mkdir" "-p" 55 | "target/generated/clj" 56 | "target/generated/cljs" 57 | "target/resources"] 58 | ["cljx" "once"] 59 | ["pdo" 60 | ["cljx" "auto"] 61 | ["cljsbuild" "auto" "dev"] 62 | "frodo"]] 63 | 64 | "start" ["do" 65 | ["cljx" "once"] 66 | ["cljsbuild" "once" "prod"] 67 | ["trampoline" "frodo"]]}) 68 | -------------------------------------------------------------------------------- /samples/contacts/resources/contacts-config.edn: -------------------------------------------------------------------------------- 1 | {:frodo/config {:web {:port 3001 2 | :app contacts.service.handler/app} 3 | :nrepl {:port 7889}}} 4 | -------------------------------------------------------------------------------- /samples/contacts/src/contacts/service/handler.clj: -------------------------------------------------------------------------------- 1 | (ns contacts.service.handler 2 | (:require [compojure.core :refer [routes GET]] 3 | [compojure.handler :refer [api]] 4 | [compojure.route :refer [resources]] 5 | [clojure.java.io :as io] 6 | [frodo.web :refer [App]] 7 | [hiccup.page :refer [html5 include-css include-js]] 8 | [ring.util.response :refer [response]] 9 | [simple-brepl.service :refer [brepl-js]])) 10 | 11 | (defn page-frame [] 12 | (html5 13 | [:head 14 | [:title "Flow - Contacts Demo"] 15 | 16 | [:script (brepl-js)] 17 | 18 | (include-js "//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js") 19 | (include-js "//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js") 20 | (include-css "//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css") 21 | 22 | (if-let [cljs-base (io/resource "js/goog/base.js")] 23 | (list (include-js "/js/goog/base.js") 24 | (include-js "/js/contacts.js") 25 | [:script "goog.require('contacts.ui.app');"]) 26 | 27 | (include-js "/js/contacts.js"))] 28 | [:body])) 29 | 30 | (defn app-routes [] 31 | (routes 32 | (GET "/" [] (response (page-frame))) 33 | (resources "/js" {:root "js"}))) 34 | 35 | (def app 36 | (reify App 37 | (start! [_] 38 | {:frodo/handler (-> (app-routes) 39 | api)}) 40 | (stop! [_ system]))) 41 | -------------------------------------------------------------------------------- /samples/contacts/ui-src/contacts/ui/app.cljs: -------------------------------------------------------------------------------- 1 | (ns contacts.ui.app 2 | (:require [clojure.string :as s] 3 | [cljs.core.async :as a] 4 | [goog.events.KeyCodes :as kc] 5 | [contacts.formatter :as cf] 6 | [flow.core :as f :include-macros true]) 7 | (:require-macros [cljs.core.async.macros :refer [go-loop]])) 8 | 9 | (enable-console-print!) 10 | 11 | (defn contact-widget [contact event-ch] 12 | (f/el 13 | [:li 14 | [:span (cf/display-name contact)] 15 | [:button.btn.btn-link {::f/on {:click #(a/put! event-ch {:type :delete 16 | :contact contact})}} 17 | "[delete]"]])) 18 | 19 | (defn contact-list-widget [!contacts event-ch] 20 | (f/el 21 | [:div 22 | [:h1 "Contact List:"] 23 | [:ul 24 | (for [contact (->> (<< !contacts) 25 | (f/keyed-by (juxt :first :last)) 26 | (sort-by :last))] 27 | [contact-widget contact event-ch])] 28 | 29 | [:div 30 | (let [contact-count (count (<< !contacts))] 31 | [:span contact-count " " 32 | (if (= contact-count 1) 33 | "contact" 34 | "contacts") 35 | "."])]])) 36 | 37 | (defn new-contact-box [event-ch] 38 | (let [!new-contact-name (atom nil)] 39 | (f/el 40 | [:div {::f/style {:margin-top "1em"}} 41 | [:input#new-contact.form-control {:type "text" 42 | :placeholder "New Contact" 43 | ::f/style {:display :inline 44 | :width "15em"} 45 | :autofocus true 46 | :value (<< !new-contact-name) 47 | ::f/on {:keyup (juxt (f/bind-value! !new-contact-name) 48 | (fn [e] 49 | (when (= kc/ENTER (.-keyCode e)) 50 | (a/put! event-ch {:type :create 51 | :name @!new-contact-name}) 52 | (reset! !new-contact-name ""))))}}]]))) 53 | 54 | (defn handle-events! [event-ch !contacts] 55 | (go-loop [] 56 | (when-let [{:keys [type] :as event} (a/"] 39 | #+END_SRC 40 | 41 | to your project dependencies, and 42 | 43 | #+BEGIN_SRC clojure 44 | [flow.core :as f :include-macros true] 45 | #+END_SRC 46 | 47 | to your ClojureScript file. 48 | 49 | Once you've run =lein new splat flow-counter=, =cd flow-counter= and 50 | run =lein dev=. 51 | 52 | You should see an nREPL started on port 7888, a web server opened on 53 | port 3000, and your ClojureScript should be automatically re-compiled 54 | when you save your CLJS files. 55 | 56 | Head over to [[http://localhost:3000]] and you should see the SPLAT 57 | welcome page. Make sure you come back here when you're done :) 58 | 59 | ** 'Hello world!' 60 | 61 | No self-respecting tutorial would be complete without printing 62 | 'Hello World', so let's see how this is done in Flow: 63 | 64 | In =ui-src/flow-counter/ui/app.cljs=, you should see the code for the 65 | SPLAT introduction - feel free to delete this, and replace it with: 66 | 67 | #+BEGIN_SRC clojure 68 | (ns flow-counter.ui.app 69 | (:require [flow.core :as f :include-macros true] 70 | simple-brepl.client)) 71 | 72 | (enable-console-print!) 73 | 74 | (set! (.-onload js/window) 75 | (fn [] 76 | (f/root js/document.body 77 | (f/el 78 | [:p "Hello world!"])))) 79 | #+END_SRC 80 | 81 | We've kept =simple-brepl.client= so that we can connect to the browser 82 | REPL (later), and =(enable-console-print!)= so that when we call 83 | =(println ...)= etc, our messages appear in your browser's JS console. 84 | 85 | What's this doing? 86 | 87 | When the window loads, we're telling Flow that the == tag 88 | should contain the element =

Hello world!

=. There's a couple of 89 | Flow concepts introduced here: 90 | 91 | - =f/root= - function that takes a parent element and a Flow 92 | component, clears the parent element, and adds the component to the 93 | parent. 94 | - =f/el= - macro that turns a component, defined using Flow's 95 | declarative component DSL, into a DOM element. We'll cover Flow's 96 | DSL in more detail throughout this tutorial. 97 | - =[:p "Hello world!"]= - for those unfamiliar with Hiccup, this 98 | creates a =

= tag containing "Hello world!" 99 | 100 | The Flow DSL is designed to be as close to ClojureScript as possible, 101 | so you hopefully shouldn't have any surprises about how it works! 102 | There are a couple of additions to ClojureScript in order to handle 103 | dynamic behaviour, but we'll cover those as we go. 104 | 105 | When you save the file, you should see the CLJS compiler re-compile 106 | your project (should take less than a couple of seconds). Then, when 107 | you refresh your browser, you should see that most clichéd of lines! 108 | 109 | ** (Optional) Connecting to the browser REPL 110 | 111 | You can execute ClojureScript commands through a ClojureScript 112 | browser REPL. Essentially, the REPL compiles each command to JS, 113 | and sends it to the browser. The browser then runs your command, and 114 | sends the result back. 115 | 116 | With *simple-brepl*, connecting to a browser REPL is, well, simple! 117 | 118 | Once you've connected to a standard Clojure REPL, probably through 119 | your favourite editor, you can then run =(user/simple-brepl)= to 120 | transform your Clojure REPL into a ClojureScript REPL. 121 | 122 | Once the REPL server has started, refresh your browser to connect it 123 | to the REPL, and then you should be able to execute ClojureScript 124 | commands, the same way as you would with a Clojure REPL. A couple of 125 | my favourite smoke tests are: 126 | 127 | - =(+ 1 1)= - just checking Maths still works! 128 | - =(js/alert "Hello world!")= 129 | - =(set! (.-backgroundColor js/document.body.style) "green")= 130 | 131 | If you're easily pleased by colourful/shiny things (like me), this can 132 | be quite a time sink. See you in a bit! 133 | 134 | You can then run =(in-ns 'flow-counter.ui.app)= (or, if you're using 135 | Emacs, =C-c M-n= from your =app.cljs= file) to change the REPL to the 136 | =flow-counter.ui.app= namespace. You might also need to evaluate the 137 | =(ns ...)= form - you can do this with =C-c C-n=. 138 | 139 | ** Displaying a static counter 140 | 141 | First, we'll need some way of storing the current value of the 142 | counter. Flow uses standard ClojureScript atoms to hold state, so 143 | we'll declare an atom at the top-level in our =app.cljs= file: 144 | 145 | #+BEGIN_SRC clojure 146 | (def !counter 147 | (atom 0)) 148 | #+END_SRC 149 | 150 | The '!' before the name is an optional naming convention - it doesn't 151 | have any effect on Flow. Personally, as a developer, I like using it 152 | because it makes a clear distinction in my code between stateful 153 | variables and immutable values. 154 | 155 | We now need to tell Flow to include the current value of the counter 156 | in our element, which we do using Flow's =(<< ...)= operator. It's 157 | similar in nature to '@'/'deref', and it's used as follows: 158 | 159 | #+BEGIN_SRC clojure 160 | (f/el 161 | [:p "The current value of the counter is " (<< !counter)]) 162 | #+END_SRC 163 | 164 | As it's part of the Flow DSL, =<<= only works inside the =f/el= macro. 165 | 166 | Flow is fundamentally declarative in nature. We don't specify any 167 | imperative behaviour here; no 'when the counter updates, then update 168 | this element' - we simply say 'this element contains the up-to-date 169 | value of my atom' and Flow does the rest. 170 | 171 | *If Flow's done its job correctly, you shouldn't ever have to write 172 | imperative code to update the DOM.* (If you do, please let me know!) 173 | 174 | With this in place, we can save the file, refresh the browser, and we 175 | should see 'The current value of the counter is 0'. 176 | 177 | ** Updating the atom using the REPL 178 | 179 | If you connected to the browser REPL earlier, you should now be able 180 | to update the atom, and see the change effected immediately in your 181 | browser. You can run, for example, =@!counter= to see the current 182 | value, or =(swap! !counter inc)= to increment it. 183 | 184 | ** It wouldn't complete without a button, though... 185 | 186 | You're quite right. 187 | 188 | We can add a button by using the =[:button]= element, but first, we 189 | have to wrap the component in a =[:div]= - =f/el= expects a single 190 | top-level element. 191 | 192 | #+BEGIN_SRC clojure 193 | (f/el 194 | [:div 195 | [:p "The value of the counter is " (<< !counter)] 196 | [:p [:button "Increment me!"]]]) 197 | #+END_SRC 198 | 199 | To add a listener to the button, we add an attribute to the button, 200 | with an anonymous function to update the atom: 201 | 202 | #+BEGIN_SRC clojure 203 | (f/el 204 | [:div 205 | [:p "The value of the counter is " (<< !counter)] 206 | [:p [:button {::f/on {:click #(swap! !counter inc)}} 207 | "Increment me!"]]]) 208 | #+END_SRC 209 | 210 | As you can see, we add listeners through the =::f/on= attribute (note 211 | the double colon!). We can add any number of DOM listeners to this map, 212 | for example =:change=, =:keyup= or =:mouseover=. Each listener is 213 | just a function - anything you'd pass to =(map ...)= or =(filter 214 | ...)= works here too. 215 | 216 | *You should now have a working counter!* 217 | 218 | ** Give it some style! 219 | 220 | The SPLAT template includes Bootstrap by default, so we can apply 221 | Bootstrap's styles in the traditional Hiccup way, by adding them to 222 | the tag keyword: 223 | 224 | #+BEGIN_SRC clojure 225 | (f/el 226 | [:div.container 227 | [:p "The value of the counter is " (<< !counter)] 228 | 229 | [:p 230 | [:button.btn.btn-default 231 | "Increment me!"]]]) 232 | #+END_SRC 233 | 234 | To add inline styles, use the =::f/style= attribute. For example: 235 | 236 | #+BEGIN_SRC clojure 237 | (f/el 238 | [:div.container {::f/style {:margin-top "2em"}} 239 | [:p "The value of the counter is " (<< !counter)] 240 | 241 | [:p [:button.btn.btn-default {::f/on {:click #(swap! !counter inc)}} 242 | "Increment me!"]]]) 243 | #+END_SRC 244 | 245 | Style values can also be keywords, e.g. ={:text-align :right}=. 246 | 247 | Finally, you can also add dynamic classes, using =::f/classes= - to 248 | add a style only when the counter is even, you might do something 249 | like: 250 | 251 | #+BEGIN_SRC clojure 252 | (let [counter (<< !counter)] 253 | [:p {::f/classes [(when (even? counter) 254 | "even")]} 255 | "The value of the counter is " counter]) 256 | #+END_SRC 257 | 258 | Here we're introducing a =let= binding in the same way as we would in 259 | vanilla Clojure - Flow will analyse the data dependencies here and 260 | update the DOM elements as required. 261 | 262 | ** Extending the 'counter' example 263 | 264 | The Flow DSL is designed to work as much like ClojureScript as 265 | possible, in order to minimise the learning curve. So far, we've seen 266 | one addition to ClojureScript, =<<=, but you can also use Clojure's 267 | standard =let= (including destructuring), =for=, =if=, =case=, =when=, 268 | etc - all of which have been extended to cope with Flow's declarative 269 | dynamic behaviours. 270 | 271 | They should all work as you'd expect - again, no surprises! 272 | 273 | The one exception to this rule (there's always one!) is that Flow's 274 | 'for' doesn't currently support ':when', ':let' or ':while' clauses 275 | (although support is planned in a future release). 276 | 277 | ** What's next? 278 | 279 | In future tutorials, we'll look at how to split Flow applications into 280 | separate components, and introduce =<<='s brother (the only other 281 | addition to ClojureScript in the DSL), =!<<=. 282 | -------------------------------------------------------------------------------- /samples/counter/project.clj: -------------------------------------------------------------------------------- 1 | (defproject jarohen/flow.counter "" 2 | 3 | :description "An example 'Counter' application for the Flow tutorial" 4 | :url "https://github.com/james-henderson/blob/master/sample/counter" 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | 8 | :dependencies [[org.clojure/clojure "1.6.0"] 9 | 10 | [ring/ring-core "1.2.0"] 11 | [compojure "1.1.6"] 12 | [hiccup "1.0.5"] 13 | 14 | [jarohen/flow "0.3.0-SNAPSHOT"] 15 | 16 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 17 | [org.clojure/clojurescript "0.0-2371"]] 18 | 19 | :plugins [[jarohen/lein-frodo "0.4.1"] 20 | [jarohen/simple-brepl "0.1.2"] 21 | [lein-cljsbuild "1.0.3"] 22 | [lein-pdo "0.1.1"] 23 | [lein-shell "0.4.0"]] 24 | 25 | :frodo/config-resource "counter-config.edn" 26 | 27 | :source-paths ["src" "../../src" "../../target/generated/clj"] 28 | 29 | :resource-paths ["resources" "target/resources"] 30 | 31 | :cljsbuild {:builds {:dev 32 | {:source-paths ["ui-src" "../../src" "../../target/generated/cljs"] 33 | :compiler {:output-to "target/resources/js/counter.js" 34 | :output-dir "target/resources/js/" 35 | :optimizations :none 36 | :pretty-print true}}}} 37 | 38 | :aliases {"dev" ["do" 39 | ["shell" "mkdir" "-p" 40 | "target/resources"] 41 | ["pdo" 42 | ["cljsbuild" "auto" "dev"] 43 | "frodo"]]}) 44 | -------------------------------------------------------------------------------- /samples/counter/resources/counter-config.edn: -------------------------------------------------------------------------------- 1 | {:frodo/config {:web {:port 3000 2 | :app flow-counter.service.handler/app} 3 | :nrepl {:port 7888}}} 4 | -------------------------------------------------------------------------------- /samples/counter/src/flow_counter/service/handler.clj: -------------------------------------------------------------------------------- 1 | (ns flow-counter.service.handler 2 | (:require [frodo.web :refer [App]] 3 | [clojure.java.io :as io] 4 | [compojure.core :refer [routes GET]] 5 | [compojure.handler :refer [api]] 6 | [compojure.route :refer [resources]] 7 | [hiccup.page :refer [html5 include-css include-js]] 8 | [ring.util.response :refer [response]] 9 | [simple-brepl.service :refer [brepl-js]])) 10 | 11 | (defn page-frame [] 12 | (html5 13 | [:head 14 | [:title "Flow Counter Tutorial"] 15 | 16 | [:script (brepl-js)] 17 | 18 | (include-js "//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js") 19 | (include-js "//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js") 20 | (include-css "//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css") 21 | 22 | (if-let [cljs-base (io/resource "js/goog/base.js")] 23 | (list (include-js "/js/goog/base.js") 24 | (include-js "/js/counter.js") 25 | [:script "goog.require('flow_counter.ui.app');"]) 26 | 27 | (include-js "/js/counter.js"))] 28 | [:body])) 29 | 30 | (defn app-routes [] 31 | (routes 32 | (GET "/" [] (response (page-frame))) 33 | (resources "/js" {:root "js"}))) 34 | 35 | (def app 36 | (reify App 37 | (start! [_] 38 | {:frodo/handler (-> (app-routes) 39 | api)}) 40 | (stop! [_ system]))) 41 | -------------------------------------------------------------------------------- /samples/counter/ui-src/flow-counter/ui/app.cljs: -------------------------------------------------------------------------------- 1 | (ns flow-counter.ui.app 2 | (:require [flow.core :as f :include-macros true] 3 | simple-brepl.client)) 4 | 5 | (enable-console-print!) 6 | 7 | (def !counter 8 | (atom 0)) 9 | 10 | (set! (.-onload js/window) 11 | (fn [] 12 | (f/root js/document.body 13 | (f/el 14 | [:div.container {::f/style {:margin-top "2em"}} 15 | (list 16 | [:p "The value of the counter is " (<< !counter)] 17 | 18 | [:p [:button.btn.btn-default {::f/on {:click #(swap! !counter inc)}} 19 | "Increment me!"]])])))) 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/flow-sample/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .nrepl-port 14 | /out/ 15 | /.repl/ -------------------------------------------------------------------------------- /samples/flow-sample/project.clj: -------------------------------------------------------------------------------- 1 | (defproject jarohen/flow.sample "" 2 | 3 | :description "A sample application to demo the Flow library" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :dependencies [[org.clojure/clojure "1.6.0"] 8 | 9 | [ring/ring-core "1.2.0"] 10 | [compojure "1.1.5"] 11 | [hiccup "1.0.4"] 12 | 13 | [prismatic/dommy "0.1.2"] 14 | 15 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 16 | [org.clojure/clojurescript "0.0-2371"] 17 | 18 | [jarohen/flow "0.2.0-beta5"] 19 | 20 | [garden "1.2.1"]] 21 | 22 | :plugins [[jarohen/lein-frodo "0.3.2"] 23 | [jarohen/simple-brepl "0.1.2"] 24 | [lein-cljsbuild "1.0.3"] 25 | [lein-pdo "0.1.1"] 26 | [lein-shell "0.4.0"]] 27 | 28 | :frodo/config-resource "flow-sample-config.edn" 29 | 30 | :resource-paths ["resources" "target/resources"] 31 | 32 | :cljsbuild {:builds {:dev 33 | {:source-paths ["ui-src" "../../src" "../../target/generated/cljs"] 34 | :compiler {:output-to "target/resources/js/flow-sample.js" 35 | :output-dir "target/resources/js/" 36 | :optimizations :none 37 | :pretty-print true}} 38 | 39 | :prod 40 | {:source-paths ["ui-src"] 41 | :compiler {:output-to "target/resources/js/flow-sample.js" 42 | :optimizations :advanced 43 | 44 | :pretty-print true}}}} 45 | 46 | :aliases {"dev" ["do" 47 | ["shell" "mkdir" "-p" 48 | "target/resources"] 49 | ["pdo" 50 | ["cljsbuild" "auto" "dev"] 51 | "frodo"]]}) 52 | -------------------------------------------------------------------------------- /samples/flow-sample/resources/flow-sample-config.edn: -------------------------------------------------------------------------------- 1 | {:frodo/config {:web {:port 3000 2 | :handler-fn flow-sample.service.handler/app} 3 | :nrepl {:port 7888 4 | :cljs-repl? true}}} 5 | -------------------------------------------------------------------------------- /samples/flow-sample/src/flow_sample/service/handler.clj: -------------------------------------------------------------------------------- 1 | (ns flow-sample.service.handler 2 | (:require [clojure.java.io :as io] 3 | [compojure.core :refer [routes GET]] 4 | [compojure.handler :refer [api]] 5 | [compojure.route :refer [resources]] 6 | [hiccup.page :refer [html5 include-css include-js]] 7 | [ring.util.response :refer [response]] 8 | [simple-brepl.service :refer [brepl-js]])) 9 | 10 | (defn page-frame [] 11 | (html5 12 | [:head 13 | [:title "A sample project to demo Flow"] 14 | (include-js "//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js") 15 | (include-js "//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js") 16 | (include-css "//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css") 17 | 18 | [:script (brepl-js)] 19 | 20 | (if-let [cljs-base (io/resource "js/goog/base.js")] 21 | (list (include-js "/js/goog/base.js") 22 | (include-js "/js/flow-sample.js") 23 | [:script "goog.require('flow_sample.ui.app');"]) 24 | 25 | (include-js "/js/flow-sample.js"))] 26 | 27 | [:body])) 28 | 29 | (defn app-routes [] 30 | (routes 31 | (GET "/" [] (response (page-frame))) 32 | (resources "/js" {:root "js"}))) 33 | 34 | (defn app [] 35 | (-> (app-routes) 36 | api)) 37 | -------------------------------------------------------------------------------- /samples/flow-sample/ui-src/flow_sample/ui/app.cljs: -------------------------------------------------------------------------------- 1 | (ns flow-sample.ui.app 2 | (:require [flow.core :as f :include-macros true] 3 | [cljs.core.async :as a] 4 | [garden.color :as c] 5 | simple-brepl.client) 6 | (:require-macros [cljs.core.async.macros :refer [go go-loop]])) 7 | 8 | (enable-console-print!) 9 | 10 | (defn rand-color [] 11 | (c/rgb->hex (c/rgb (rand-int 256) (rand-int 256) (rand-int 256)))) 12 | 13 | (defn render-svg [!colors] 14 | (f/el 15 | [:div {::f/style {:margin "1em 0" 16 | :color "#000"}} 17 | [:h3 "And now for an SVG example:"] 18 | 19 | (let [{:keys [primary secondary]} (<< !colors)] 20 | [:svg 21 | [:rect {:x 10 22 | :y 10 23 | :height 100 24 | :width 100 25 | ::f/style {:stroke primary 26 | :fill secondary}}] 27 | [:circle {:cx 60 28 | :cy 60 29 | :r 40 30 | ::f/style {:stroke secondary 31 | :fill primary}}]])])) 32 | 33 | (defn render-colour-picker [!color] 34 | (f/el 35 | [:p 36 | [:input {:type "color" 37 | :value (<< !color) 38 | ::f/on {:change (f/bind-value! !color)}}]])) 39 | 40 | (defn update-random-numbers! [!random-numbers update-numbers-ch] 41 | (reset! !random-numbers (for [idx (range 5)] 42 | {:id idx 43 | :num (rand-int 1000)})) 44 | 45 | (go-loop [] 46 | (a/> random-numbers 148 | (filter (comp (case selected-filter 149 | "even" even? 150 | "odd" odd? 151 | "all" identity) 152 | :num)) 153 | (sort-by :num))] 154 | [:li num])]])] 155 | 156 | [:div 157 | [:select.form-control {::f/on {:change (f/bind-value! !filter)} 158 | :value (<< !filter) 159 | ::f/style {:width "6em"}} 160 | [:option {:value "odd"} 161 | "Odd"] 162 | [:option {:value "even"} 163 | "Even"] 164 | [:option {:value "all"} 165 | "All"]]] 166 | 167 | [:div 168 | [:h3 "Pick your colours:"] 169 | 170 | [:p "Primary:" [render-colour-picker (!<< colors [:primary])]] 171 | 172 | [:p "Secondary:" [render-colour-picker (!<< colors [:secondary])]]] 173 | 174 | [render-svg !colors]])))))) 175 | 176 | -------------------------------------------------------------------------------- /samples/todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .nrepl-port 14 | /out/ 15 | /.repl/ 16 | -------------------------------------------------------------------------------- /samples/todomvc/README.org: -------------------------------------------------------------------------------- 1 | * Flow TodoMVC example 2 | 3 | This is a larger-scale example application for Flow based on the 4 | [[http://todomvc.com/][TodoMVC]] project. 5 | 6 | All of the concepts here have already been introduced in the [[https://github.com/james-henderson/flow/tree/0.2.0-branch/samples][tutorials]] 7 | or [[https://github.com/james-henderson/flow][README]] - have a look at these if you're unsure about any of the 8 | code: 9 | 10 | To start the TodoMVC application, clone this repository, 'cd' to 11 | 'samples/todomvc', and run =lein dev=. You should then see a web 12 | service start at http://localhost:3000! 13 | 14 | Any questions, please let me know! 15 | 16 | James ([[https://twitter.com/jarohen][@jarohen]]) 17 | -------------------------------------------------------------------------------- /samples/todomvc/project.clj: -------------------------------------------------------------------------------- 1 | (defproject jarohen/flow.todomvc "" 2 | 3 | :description "A sample ToDoMVC app demonstrating Flow" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :dependencies [[org.clojure/clojure "1.6.0"] 8 | 9 | [ring/ring-core "1.2.0"] 10 | [compojure "1.1.5"] 11 | [hiccup "1.0.4"] 12 | 13 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 14 | [org.clojure/clojurescript "0.0-2371"] 15 | 16 | [gaka "0.3.0"] 17 | 18 | [jarohen/flow "0.3.0-SNAPSHOT"]] 19 | 20 | :plugins [[jarohen/lein-frodo "0.4.1"] 21 | [jarohen/simple-brepl "0.1.2"] 22 | [lein-cljsbuild "1.0.3"] 23 | [lein-pdo "0.1.1"] 24 | [lein-shell "0.4.0"]] 25 | 26 | :frodo/config-resource "todomvc-config.edn" 27 | 28 | :resource-paths ["resources" "target/resources"] 29 | 30 | :cljsbuild {:builds {:dev 31 | {:source-paths ["ui-src"] 32 | :compiler {:output-to "target/resources/js/todomvc.js" 33 | :output-dir "target/resources/js/" 34 | :optimizations :none 35 | :pretty-print true}} 36 | 37 | :prod 38 | {:source-paths ["ui-src"] 39 | :compiler {:output-to "target/resources/js/todomvc.js" 40 | :optimizations :advanced}}}} 41 | 42 | :aliases {"dev" ["do" 43 | ["shell" "mkdir" "-p" "target/resources"] 44 | ["pdo" 45 | ["cljsbuild" "auto" "dev"] 46 | "frodo"]]}) 47 | -------------------------------------------------------------------------------- /samples/todomvc/resources/img/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarohen/flow/7cc367bf40836a57ef3bb773935ab3bc5bf97495/samples/todomvc/resources/img/tick.png -------------------------------------------------------------------------------- /samples/todomvc/resources/todomvc-config.edn: -------------------------------------------------------------------------------- 1 | {:frodo/config {:web {:port 3000 2 | :handler-fn flow.todomvc.service.handler/app} 3 | :nrepl {:port 7888}}} 4 | -------------------------------------------------------------------------------- /samples/todomvc/resources/todomvc/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarohen/flow/7cc367bf40836a57ef3bb773935ab3bc5bf97495/samples/todomvc/resources/todomvc/bg.png -------------------------------------------------------------------------------- /samples/todomvc/resources/todomvc/todomvc.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /samples/todomvc/src/flow/todomvc/service/handler.clj: -------------------------------------------------------------------------------- 1 | (ns flow.todomvc.service.handler 2 | (:require [clojure.java.io :as io] 3 | [compojure.core :refer [routes GET]] 4 | [compojure.handler :refer [api]] 5 | [compojure.route :refer [resources]] 6 | [hiccup.page :refer [html5 include-css include-js]] 7 | [ring.util.response :refer [response]] 8 | [simple-brepl.service :refer [brepl-js]])) 9 | 10 | (defn page-frame [] 11 | (html5 12 | [:head 13 | [:title "TodoMVC - A sample Flow application"] 14 | [:meta {:charset "utf-8"}] 15 | 16 | [:script (brepl-js)] 17 | 18 | (if-let [cljs-base (io/resource "js/goog/base.js")] 19 | (list (include-js "/js/goog/base.js") 20 | (include-js "/js/todomvc.js") 21 | [:script "goog.require('flow.todomvc.ui.app');"]) 22 | 23 | (include-js "/js/todomvc.js")) 24 | 25 | (include-css "/todomvc/todomvc.css")] 26 | 27 | [:body 28 | [:div#content]])) 29 | 30 | (defn app-routes [] 31 | (routes 32 | (GET "/" [] (response (page-frame))) 33 | (resources "/js" {:root "js"}) 34 | (resources "/img" {:root "img"}) 35 | (resources "/todomvc" {:root "todomvc"}))) 36 | 37 | (defn app [] 38 | (-> (app-routes) 39 | api)) 40 | -------------------------------------------------------------------------------- /samples/todomvc/ui-src/flow/todomvc/ui/app.cljs: -------------------------------------------------------------------------------- 1 | (ns flow.todomvc.ui.app 2 | (:require [flow.core :as f :include-macros true] 3 | [cljs.core.async :as a] 4 | [flow.todomvc.ui.todomvc-widget :refer [make-todomvc]] 5 | [flow.todomvc.ui.todomvc-model :as model] 6 | simple-brepl.client) 7 | (:require-macros [cljs.core.async.macros :refer [go]])) 8 | 9 | (enable-console-print!) 10 | 11 | (defn test-todos [] 12 | (->> (for [x (range 5)] 13 | [x {:id x 14 | :caption (str "Test todo " x)}]) 15 | (into {}))) 16 | 17 | (defn run-benchmark! [!todos] 18 | (reset! !todos {}) 19 | (go 20 | (let [els 200] 21 | (dotimes [i els] 22 | (swap! !todos 23 | assoc i {:id i 24 | :caption (str "test" i), 25 | :done? false})) 26 | 27 | (dotimes [i els] 28 | (swap! !todos 29 | assoc-in [i :done?] true)) 30 | 31 | #_(dotimes [i els] 32 | (swap! !todos 33 | dissoc i))))) 34 | 35 | (set! (.-onload js/window) 36 | (fn [] 37 | (let [!todos (atom (test-todos)) 38 | events-ch (doto (a/chan) 39 | (model/watch-events! !todos))] 40 | 41 | (f/root js/document.body 42 | (make-todomvc !todos events-ch)) 43 | 44 | (go 45 | (a/> todos 25 | (remove (comp :done? val)) 26 | (into {}))) 27 | 28 | (defmethod apply-event :toggle-all [todos {:keys [done?]}] 29 | (->> todos 30 | (map #(assoc-in % [1 :done?] done?)) 31 | (into {}))) 32 | 33 | (defmethod apply-event nil [todos _] 34 | todos) 35 | 36 | (defn watch-events! [events-ch !todos] 37 | (go-loop [] 38 | (when-let [event (a/> (<< !todos) 83 | (filter (comp (filter-todos (<< !todo-filter)) val)) 84 | (f/keyed-by key))] 85 | [todo-item-widget (!<< todo) events-ch])])) 86 | 87 | (defn stats-widget [!todos] 88 | (f/el 89 | (let [todo-count (->> (<< !todos) 90 | vals 91 | (remove :done?) 92 | count)] 93 | [:span#todo-count 94 | [:strong todo-count] 95 | [:span " items left"]]))) 96 | 97 | (def filter-label 98 | {:all "All" 99 | :active "Active" 100 | :completed "Completed"}) 101 | 102 | (defn filters-widget [!todo-filter] 103 | (f/el 104 | [:ul#filters 105 | (let [todo-filter (<< !todo-filter)] 106 | (for [filter-option [:all :active :completed]] 107 | [:li {::f/style {:cursor :pointer}} 108 | [:a {::f/classes [(when (= todo-filter filter-option) 109 | "selected")] 110 | ::f/on {:click #(reset! !todo-filter filter-option)}} 111 | (filter-label filter-option)]]))])) 112 | 113 | (defn clear-completed-widget [!todos events-ch] 114 | (f/el 115 | (let [completed-count (->> (<< !todos) 116 | vals 117 | (filter :done?) 118 | count)] 119 | [:div 120 | (when-not (zero? completed-count) 121 | [:button#clear-completed {::f/on {:click #(a/put! events-ch {:type :clear-completed})}} 122 | (str "Clear completed " completed-count)])]))) 123 | 124 | (defn make-todomvc [!todos events-ch] 125 | (let [!todo-filter (atom :all)] 126 | (f/el 127 | [:section#todoapp 128 | [:header#header 129 | [:h1 "todos"] 130 | [new-todo-widget events-ch]] 131 | 132 | [:section#main 133 | [toggle-all-widget !todos events-ch] 134 | [:label {:for "toggle-all"} 135 | "Mark all as complete"] 136 | 137 | [todo-list-widget !todos !todo-filter events-ch]] 138 | 139 | [:footer#info 140 | [:p "Double-click to edit a todo"]] 141 | 142 | [:footer#footer 143 | [stats-widget !todos] 144 | [filters-widget !todo-filter] 145 | [clear-completed-widget !todos events-ch]]]))) 146 | -------------------------------------------------------------------------------- /slides/clojurex-2014-presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarohen/flow/7cc367bf40836a57ef3bb773935ab3bc5bf97495/slides/clojurex-2014-presentation.pdf -------------------------------------------------------------------------------- /src/flow/compiler.clj: -------------------------------------------------------------------------------- 1 | (ns flow.compiler 2 | (:require [flow.expand :refer [expand-macros]])) 3 | 4 | (defn resolve-fn [called-fn] 5 | (let [resolved-fn (or (when (symbol? called-fn) 6 | (resolve called-fn)) 7 | called-fn)] 8 | (if (and (var? resolved-fn) 9 | (contains? #{'clojure.core 'cljs.core} 10 | (symbol (str (:ns (meta resolved-fn)))))) 11 | (keyword "core" (str (:name (meta resolved-fn)))) 12 | resolved-fn))) 13 | 14 | (defn fn-call-type [[called-fn & args]] 15 | (condp = (resolve-fn called-fn) 16 | '<< :watch 17 | '!<< :wrap 18 | 19 | 'if :if 20 | 21 | :core/case :case 22 | 'case :case 23 | 24 | :core/list :list 25 | 'list :list 26 | 27 | :core/let :let 28 | 'let :let 29 | 30 | :core/for :for 31 | 'for :for 32 | 33 | 'do :do 34 | 'fn* :fn-decl 35 | 36 | 'quote (throw (UnsupportedOperationException. "(quote ...) not supported in Flow's 'el' (yet?!)")) 37 | 'loop* (throw (UnsupportedOperationException. "loop/recur not supported in Flow's 'el'")) 38 | 39 | :fn-call)) 40 | 41 | (defn form-type [form {:keys [type]}] 42 | (cond 43 | (string? form) (case type 44 | :el :text 45 | :value :primitive) 46 | 47 | (symbol? form) :symbol 48 | 49 | (and (= type :el) 50 | (vector? form)) 51 | (if (keyword? (first form)) 52 | :node 53 | :sub-component) 54 | 55 | (seq? form) (fn-call-type form) 56 | 57 | (map? form) :map 58 | (coll? form) :coll 59 | 60 | :else :primitive)) 61 | 62 | (defmulti compile-el-form 63 | (fn [el-form opts] 64 | (form-type el-form {:type :el}))) 65 | 66 | (defmulti compile-value-form 67 | (fn [value-form opts] 68 | (form-type value-form {:type :value}))) 69 | 70 | (defn compile-el [el-form macro-env] 71 | (-> el-form 72 | (expand-macros macro-env) 73 | (compile-el-form {:bound-syms #{}}))) 74 | -------------------------------------------------------------------------------- /src/flow/core.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.core 2 | (:require [flow.el :as fel] 3 | [flow.cursors :as fcu] 4 | [flow.dom.elements :as fde] 5 | [flow.forms.text] 6 | [flow.forms.node] 7 | [flow.forms.primitive] 8 | [flow.forms.cursors] 9 | [flow.forms.collections] 10 | [flow.forms.list] 11 | [flow.forms.symbols] 12 | [flow.forms.fn-calls] 13 | [flow.forms.fn-decls] 14 | [flow.forms.do] 15 | [flow.forms.if] 16 | [flow.forms.case] 17 | [flow.forms.let] 18 | [flow.forms.for] 19 | [flow.forms.sub-component]) 20 | #+clj (:require [flow.compiler :as fc])) 21 | 22 | (defn root [$container el] 23 | (fel/root $container el)) 24 | 25 | #+clj 26 | (defmacro el [el] 27 | `(fel/render-el ~(fc/compile-el el &env))) 28 | 29 | #+cljs 30 | (defn bind-value! [cursor] 31 | (fde/bind-value! cursor)) 32 | 33 | #+cljs 34 | (defn on [$el event listener] 35 | (fde/add-event-listener! $el event listener)) 36 | 37 | (defn keyed-by [f coll] 38 | (when coll 39 | (fcu/keyed-by coll f))) 40 | -------------------------------------------------------------------------------- /src/flow/cursors.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.cursors) 2 | 3 | ;; Most of this adapted from either from CLJS core or Om, thanks David 4 | ;; Nolan & contributors for the design ideas, inspiration and code :) 5 | 6 | ;; The Clojure implementation is a small subset of the CLJS cursor 7 | ;; functionality (due to the difference in interfaces between Clojure 8 | ;; 1.6 and CLJS). It's really only meant for testing Flow. 9 | 10 | (defprotocol Cursor 11 | (-value [_]) 12 | (-!state [_]) 13 | (-path [_]) 14 | (->atom [_ extra-path])) 15 | 16 | (defprotocol Keyable 17 | (-keyed-by [_ f])) 18 | 19 | (defn cursor? [v] 20 | (satisfies? Cursor v)) 21 | 22 | (defn get-at-path [m [p & more-path :as path]] 23 | (if (and m (seq path)) 24 | (cond 25 | (and (vector? p) (= (first p) ::pk)) 26 | (let [[_ key-fn key-value] p] 27 | (get-at-path (first (filter (comp #(= key-value %) key-fn) m)) more-path)) 28 | 29 | (or #+clj (instance? clojure.lang.ILookup m) 30 | #+cljs (satisfies? #+cljs ILookup m) 31 | (nil? m)) 32 | 33 | (get-at-path (get m p) more-path) 34 | 35 | (number? p) 36 | (get-at-path (nth m p) more-path)) 37 | 38 | m)) 39 | 40 | (defn assoc-at-path [m [p & more-path :as path] v] 41 | (if (seq path) 42 | (cond 43 | (and (vector? p) (= (first p) ::pk)) 44 | (let [[_ key-fn key-value] p] 45 | (into (empty m) (for [el m] 46 | (if (= key-value (key-fn el)) 47 | (assoc-at-path el more-path v) 48 | el)))) 49 | 50 | (or #+clj (instance? clojure.lang.Associative m) 51 | #+cljs (satisfies? #+cljs IAssociative m) 52 | (nil? m)) 53 | (assoc m p (assoc-at-path (get m p) more-path v)) 54 | 55 | (and (seq? m) 56 | (number? p)) 57 | (if (zero? p) 58 | (cons (assoc-at-path (first m) more-path v) 59 | (rest m)) 60 | (cons (first m) 61 | (assoc-at-path (rest m) (cons (dec p) more-path) v)))) 62 | 63 | v)) 64 | 65 | #+clj 66 | (defn cursor->atom [!state path] 67 | (reify 68 | Cursor 69 | (-value [this] (deref this)) 70 | (-!state [_] !state) 71 | (-path [_] path) 72 | (->atom [_ extra-path] 73 | (cursor->atom !state (vec (concat path extra-path)))) 74 | 75 | clojure.lang.IDeref 76 | (deref [this] 77 | (get-at-path @!state path)))) 78 | 79 | #+cljs 80 | (defn cursor->atom [!state path] 81 | ;; This part adapted from CLJS core. 82 | (reify 83 | Cursor 84 | (-value [this] @this) 85 | (-!state [_] !state) 86 | (-path [_] path) 87 | (->atom [_ extra-path] 88 | (cursor->atom !state (vec (concat path extra-path)))) 89 | 90 | IEquiv 91 | (-equiv [this other] 92 | (and (cursor? other) 93 | (identical? !state (-!state other)) 94 | (= path (path other)))) 95 | 96 | IMeta 97 | (-meta [_] 98 | {:!state !state 99 | :path path}) 100 | 101 | IDeref 102 | (-deref [_] 103 | (get-at-path @!state path)) 104 | 105 | IReset 106 | (-reset! [this new-value] 107 | (let [old-value (-deref this)] 108 | (swap! !state assoc-at-path path new-value) 109 | new-value)) 110 | 111 | ISwap 112 | (-swap! [this f] 113 | (reset! this (f (-deref this)))) 114 | (-swap! [this f a1] 115 | (reset! this (f (-deref this) a1))) 116 | (-swap! [this f a1 a2] 117 | (reset! this (f (-deref this) a1 a2))) 118 | (-swap! [this f a1 a2 as] 119 | (reset! this (apply f (-deref this) a1 a2 as))) 120 | 121 | IPrintWithWriter 122 | (-pr-writer [this writer opts] 123 | (-write writer "#")) 126 | 127 | IHash 128 | (-hash [this] 129 | (goog/getUid this)))) 130 | 131 | (declare ->cursor) 132 | 133 | #+clj 134 | (defn map-cursor [value !state path] 135 | (reify 136 | Cursor 137 | (-value [_] value) 138 | (-!state [_] !state) 139 | (-path [_] path) 140 | (->atom [_ extra-path] 141 | (cursor->atom !state (vec (concat path extra-path)))) 142 | 143 | clojure.lang.ILookup 144 | (valAt [this k] 145 | (get this k nil)) 146 | (valAt [this k not-found] 147 | (let [v (get value k not-found)] 148 | (if-not (= v not-found) 149 | (->cursor v !state (conj path k)) 150 | not-found))) 151 | 152 | clojure.lang.IFn 153 | (invoke [this k] 154 | (get this k)) 155 | (invoke [this k not-found] 156 | (get this k not-found)) 157 | 158 | clojure.lang.Seqable 159 | (seq [this] 160 | (when (pos? (count value)) 161 | (map (fn [[k v]] 162 | (clojure.lang.MapEntry. k (->cursor v !state (conj path k)))) 163 | value))) 164 | 165 | clojure.lang.IPersistentMap 166 | (assoc [_ k v] 167 | (map-cursor (assoc value k v) !state path)) 168 | (without [_ k] 169 | (map-cursor (dissoc value k) !state path)))) 170 | 171 | #+cljs 172 | (defn map-cursor [value !state path] 173 | (reify 174 | Cursor 175 | (-value [_] value) 176 | (-!state [_] !state) 177 | (-path [_] path) 178 | (->atom [_ extra-path] 179 | (cursor->atom !state (vec (concat path extra-path)))) 180 | 181 | IWithMeta 182 | (-with-meta [_ new-meta] 183 | (map-cursor (with-meta value new-meta) !state path)) 184 | IMeta 185 | (-meta [_] 186 | (meta value)) 187 | 188 | ICloneable 189 | (-clone [_] 190 | (map-cursor value !state path)) 191 | 192 | ICounted 193 | (-count [_] 194 | (count value)) 195 | 196 | ICollection 197 | (-conj [this o] 198 | (->cursor (-conj value o) !state path)) 199 | 200 | ILookup 201 | (-lookup [this k] 202 | (-lookup this k nil)) 203 | (-lookup [this k not-found] 204 | (let [v (-lookup value k not-found)] 205 | (if-not (= v not-found) 206 | (->cursor v !state (conj path k)) 207 | not-found))) 208 | 209 | IFn 210 | (-invoke [this k] 211 | (-lookup this k)) 212 | (-invoke [this k not-found] 213 | (-lookup this k not-found)) 214 | 215 | ISeqable 216 | (-seq [this] 217 | (when (pos? (count value)) 218 | (map (fn [[k v]] 219 | [k (->cursor v !state (conj path k))]) 220 | value))) 221 | 222 | IAssociative 223 | (-contains-key? [_ k] 224 | (-contains-key? value k)) 225 | (-assoc [_ k v] 226 | (map-cursor (-assoc value k v) !state path)) 227 | 228 | IMap 229 | (-dissoc [_ k] 230 | (map-cursor (-dissoc value k) !state path)) 231 | 232 | IEquiv 233 | (-equiv [_ other] 234 | (if (cursor? other) 235 | (= value (-value other)) 236 | (= value other))) 237 | 238 | IPrintWithWriter 239 | (-pr-writer [_ writer opts] 240 | (-write writer (pr-str value))))) 241 | 242 | #+clj 243 | (defn vec-cursor [value !state path key-fn] 244 | (letfn [(pk [v i] 245 | (if key-fn 246 | [::pk key-fn (key-fn v)] 247 | i))] 248 | (reify 249 | Cursor 250 | (-value [_] value) 251 | (-!state [_] !state) 252 | (-path [_] path) 253 | (->atom [_ extra-path] 254 | (cursor->atom !state (vec (concat path extra-path)))) 255 | 256 | Keyable 257 | (-keyed-by [_ f] 258 | (vec-cursor value !state path f)) 259 | 260 | clojure.lang.Sequential 261 | 262 | clojure.lang.IPersistentCollection 263 | (count [_] 264 | (count value)) 265 | 266 | clojure.lang.ILookup 267 | (valAt [this n] 268 | (nth this n nil)) 269 | (valAt [this n not-found] 270 | (nth this n not-found)) 271 | 272 | clojure.lang.IFn 273 | (invoke [this k] 274 | (get this k)) 275 | (invoke [this k not-found] 276 | (get this k not-found)) 277 | 278 | clojure.lang.Indexed 279 | (nth [this n] 280 | (->cursor (nth value n) !state (conj path n))) 281 | (nth [this n not-found] 282 | (if (< n (count value)) 283 | (->cursor (nth value n) !state (conj path n)) 284 | not-found)) 285 | 286 | clojure.lang.Seqable 287 | (seq [this] 288 | (when (not-empty value) 289 | (map-indexed (fn [i v] 290 | (->cursor v !state (conj path (pk v i)))) 291 | value))) 292 | 293 | clojure.lang.Associative 294 | (assoc [this n v] 295 | (->cursor (assoc value n v) !state path))))) 296 | 297 | #+cljs 298 | (defn vec-cursor [value !state path key-fn] 299 | (letfn [(pk [v i] 300 | (if key-fn 301 | [::pk key-fn (key-fn v)] 302 | i))] 303 | (reify 304 | Cursor 305 | (-value [_] value) 306 | (-!state [_] !state) 307 | (-path [_] path) 308 | (->atom [_ extra-path] 309 | (cursor->atom !state (vec (concat path extra-path)))) 310 | 311 | Keyable 312 | (-keyed-by [_ f] 313 | (vec-cursor value !state path f)) 314 | 315 | ISequential 316 | 317 | IWithMeta 318 | (-with-meta [_ new-meta] 319 | (vec-cursor (with-meta value new-meta) !state path key-fn)) 320 | IMeta 321 | (-meta [_] 322 | (meta value)) 323 | 324 | ICloneable 325 | (-clone [_] 326 | (vec-cursor value !state path key-fn)) 327 | 328 | ICounted 329 | (-count [_] 330 | (count value)) 331 | ICollection 332 | (-conj [_ o] 333 | (vec-cursor (conj value o) !state path key-fn)) 334 | 335 | ILookup 336 | (-lookup [this n] 337 | (-nth this n nil)) 338 | (-lookup [this n not-found] 339 | (-nth this n not-found)) 340 | 341 | IFn 342 | (-invoke [this k] 343 | (-lookup this k)) 344 | (-invoke [this k not-found] 345 | (-lookup this k not-found)) 346 | 347 | IIndexed 348 | (-nth [this n] 349 | (let [v (nth value n)] 350 | (->cursor v !state (conj path (pk v n))))) 351 | 352 | (-nth [this n not-found] 353 | (if (< n (-count value)) 354 | (let [v (nth value n)] 355 | (->cursor v !state (conj path (pk v n)))) 356 | 357 | not-found)) 358 | 359 | ISeqable 360 | (-seq [this] 361 | (when (not-empty value) 362 | (map-indexed (fn [i v] 363 | (->cursor v !state (conj path (pk v i)))) 364 | value))) 365 | 366 | IAssociative 367 | (-contains-key? [_ k] 368 | (-contains-key? value k)) 369 | (-assoc [this n v] 370 | (vec-cursor (-assoc-n value n v) !state path key-fn)) 371 | 372 | IStack 373 | (-peek [this] 374 | (vec-cursor (-peek value) !state path key-fn)) 375 | (-pop [this] 376 | (vec-cursor (-pop value) !state path key-fn)) 377 | 378 | IEquiv 379 | (-equiv [_ other] 380 | (if (cursor? other) 381 | (= value (-value other)) 382 | (= value other))) 383 | 384 | IPrintWithWriter 385 | (-pr-writer [_ writer opts] 386 | (-write writer (pr-str value)))))) 387 | 388 | #+cljs 389 | (defn cloneable->cursor [value !state path] 390 | (specify value 391 | Cursor 392 | (-value [_] value) 393 | (-!state [_] !state) 394 | (-path [_] path) 395 | (->atom [_ extra-path] 396 | (cursor->atom !state (vec (concat path extra-path)))) 397 | 398 | IEquiv 399 | (-equiv [_ other] 400 | (if (cursor? other) 401 | (= val (-value other)) 402 | (= val other))))) 403 | 404 | (defn ->cursor [value !state path] 405 | (cond 406 | (cursor? value) value 407 | 408 | (map? value) (map-cursor value !state path) 409 | (or (coll? value) (seq? value)) (vec-cursor value !state path nil) 410 | 411 | #+cljs (satisfies? ICloneable value) 412 | #+cljs (cloneable->cursor value !state path) 413 | 414 | :else value)) 415 | 416 | (defn keyed-by [f coll] 417 | (cond 418 | (satisfies? Keyable coll) (-keyed-by coll f) 419 | 420 | (or (sequential? coll) 421 | #+cljs (seqable? coll)) 422 | (map (fn [el] 423 | (if (cursor? el) 424 | (->cursor (-value el) 425 | (-!state el) 426 | (concat (butlast (-path el)) [[::pk f (f el)]])) 427 | el)) 428 | coll) 429 | 430 | (nil? coll) nil)) 431 | 432 | -------------------------------------------------------------------------------- /src/flow/deps.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.deps 2 | (:require [flow.cursors :as fc])) 3 | 4 | (defprotocol Context 5 | (-read-dep [_ dep]) 6 | (-mark-deps! [_ deps])) 7 | 8 | (def ^:dynamic *ctx* 9 | (reify Context 10 | (-read-dep [_ dep] 11 | (fc/->cursor @dep dep [])) 12 | (-mark-deps! [_ _]))) 13 | 14 | (defn mark-dep [dep-tree dep value] 15 | (let [state+path (if (satisfies? fc/Cursor dep) 16 | (cons (fc/-!state dep) (fc/-path dep)) 17 | [dep])] 18 | (letfn [(dep-marked? [dep-tree path] 19 | (or (boolean (::value dep-tree)) 20 | (when-let [[p & more] (seq path)] 21 | (dep-marked? (get dep-tree p) more))))] 22 | (if (dep-marked? dep-tree state+path) 23 | dep-tree 24 | (assoc-in dep-tree state+path {::value (if (fc/cursor? value) 25 | (fc/-value value) 26 | value)}))))) 27 | 28 | (defn merge-deps [deps-1 deps-2] 29 | (merge-with (fn [v1 v2] 30 | (cond 31 | (::value v1) v1 32 | (::value v2) v2 33 | :else (merge-deps v1 v2))) 34 | deps-1 deps-2)) 35 | 36 | (defn tree-unchanged? [new-value tree] 37 | (if (and (map? tree) 38 | (contains? tree ::value)) 39 | (= (::value tree) new-value) 40 | 41 | (every? (fn [[path sub-tree]] 42 | (tree-unchanged? (fc/get-at-path new-value [path]) sub-tree)) 43 | tree))) 44 | 45 | (defn deps-unchanged? [deps] 46 | (every? (fn [[!atom tree]] 47 | (tree-unchanged? @!atom tree)) 48 | deps)) 49 | 50 | (defn with-watch-context [f] 51 | (let [!dep-tree (atom {}) 52 | parent-ctx *ctx*] 53 | (binding [*ctx* (reify Context 54 | (-read-dep [_ dep] 55 | (let [value (-read-dep parent-ctx dep)] 56 | (swap! !dep-tree mark-dep dep value) 57 | value)) 58 | (-mark-deps! [_ deps] 59 | (swap! !dep-tree merge-deps deps) 60 | (-mark-deps! parent-ctx deps)))] 61 | 62 | {:result (f) 63 | :deps @!dep-tree}))) 64 | 65 | (defn read-dep [dep] 66 | (-read-dep *ctx* dep)) 67 | 68 | (defn mark-deps! [deps] 69 | (-mark-deps! *ctx* deps)) 70 | 71 | -------------------------------------------------------------------------------- /src/flow/dom/attributes.clj: -------------------------------------------------------------------------------- 1 | (ns flow.dom.attributes) 2 | 3 | (defn set-id! [!el id] 4 | (swap! !el assoc :id id)) 5 | 6 | (defn set-style! [!el attr value] 7 | (swap! !el assoc-in [:style attr] value)) 8 | 9 | (defn set-attr! [!el attr value] 10 | (swap! !el assoc-in [:attrs attr] value)) 11 | 12 | (defn set-classes! [!el classes] 13 | (swap! !el assoc :classes classes)) 14 | -------------------------------------------------------------------------------- /src/flow/dom/attributes.cljs: -------------------------------------------------------------------------------- 1 | (ns flow.dom.attributes 2 | (:require [clojure.string :as s])) 3 | 4 | (defn set-id! [$el id] 5 | (set! (.-id $el) id)) 6 | 7 | (defn set-style! [$el attr value] 8 | (.setProperty (.-style $el) 9 | (name attr) 10 | (cond-> value 11 | (keyword? value) name))) 12 | 13 | (defn set-attr! [$el attr value] 14 | (case attr 15 | :value (set! (.-value $el) value) 16 | :checked (if (boolean value) 17 | (set! (.-checked $el) true) 18 | (set! (.-checked $el) nil)) 19 | 20 | (if-not (nil? value) 21 | (.setAttribute $el (name attr) value) 22 | (.removeAttribute $el (name attr) value)))) 23 | 24 | (defn set-classes! [$el new-classes] 25 | (set! (.-className $el) (s/join " " (map name new-classes)))) 26 | -------------------------------------------------------------------------------- /src/flow/dom/children.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.dom.children 2 | (:require [flow.dom.elements :as fde] 3 | [flow.dom.diff :as fdd])) 4 | 5 | (defn with-lock [obj f] 6 | #+clj (locking obj (f)) 7 | #+cljs (f)) 8 | 9 | (defn new-child-holder! [$parent] 10 | (atom {:$parent $parent 11 | :el-ish ::init})) 12 | 13 | (defn swap-child-seqs! [$parent old-els new-els] 14 | (let [$last-elem (last old-els) 15 | $next-sibling (fde/next-sibling $parent $last-elem) 16 | 17 | diff (fdd/vector-diff old-els new-els)] 18 | (doseq [[action $el] diff] 19 | (when (= action :moved-out) 20 | (fde/remove-child! $parent $el))) 21 | 22 | (reduce (fn [$next-sibling [action $new-el]] 23 | (case action 24 | :kept $new-el 25 | :moved-in (do 26 | (if $next-sibling 27 | (fde/insert-before! $parent $next-sibling $new-el) 28 | (fde/append-child! $parent $new-el)) 29 | $new-el) 30 | :moved-out $next-sibling)) 31 | 32 | $next-sibling 33 | (reverse diff)) 34 | 35 | new-els)) 36 | 37 | (defn swap-child-el! [$parent $old-el $new-el] 38 | (case [(coll? $old-el) (coll? $new-el)] 39 | [false false] (when-not (identical? $old-el $new-el) 40 | (fde/replace-child! $parent $old-el $new-el)) 41 | [false true] (do 42 | (doseq [$el $new-el] 43 | (fde/insert-before! $parent $old-el $el)) 44 | (fde/remove-child! $parent $old-el)) 45 | [true false] (do 46 | (fde/insert-before! $parent (first $old-el) $new-el) 47 | (doseq [$el $old-el] 48 | (fde/remove-child! $parent $el))) 49 | [true true] (swap-child-seqs! $parent $old-el $new-el))) 50 | 51 | (defn replace-child! [!child-holder new-el-ish] 52 | (with-lock !child-holder 53 | (fn [] 54 | (let [{$parent :$parent, old-el-ish :el-ish, $old-el :$el} @!child-holder] 55 | (when-not (= old-el-ish new-el-ish) 56 | (let [$new-el (fde/->el new-el-ish)] 57 | (reset! !child-holder {:$parent $parent 58 | :el-ish new-el-ish 59 | :$el $new-el}) 60 | (swap-child-el! $parent $old-el $new-el))))))) 61 | -------------------------------------------------------------------------------- /src/flow/dom/diff.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.dom.diff 2 | (:require [clojure.set :as set])) 3 | 4 | (def diff-threshold 5 | ;; 5 is a good value here for a lot of use-cases. Inserting/Deleting 6 | ;; doesn't count as it's cheap anyway, moving one element somewhere 7 | ;; else in the array is always less than 2 (top -> bottom = 8 | ;; 2). Shuffling/reversing have a value proportional to the square 9 | ;; of the size which is greater than 5 for n > ~15 10 | 5.0) 11 | 12 | (defn kept-ids [x1 x2] 13 | (let [kept? (set/intersection (set x1) 14 | (set x2))] 15 | [(filter kept? x1) 16 | (filter kept? x2) 17 | kept?])) 18 | 19 | (defn indices [m] 20 | (->> (map vector m (range)) 21 | (into {}))) 22 | 23 | (defn abs [x] 24 | #+clj (Math/abs x) 25 | #+cljs (js/Math.abs x)) 26 | 27 | (defn to-diff? [x1 x2] 28 | (let [[x1-kept x2-kept ids] (kept-ids x1 x2) 29 | [x1-indices x2-indices] (map indices [x1-kept x2-kept])] 30 | (> (* diff-threshold (count ids)) 31 | (->> ids 32 | (map (juxt x1-indices x2-indices)) 33 | (map #(abs (apply - %))) 34 | (apply +))))) 35 | 36 | (defn do-diff [olds news] 37 | (loop [res [] 38 | displaced-olds [] 39 | displaced-news [] 40 | [old-el & more-olds :as olds] olds 41 | [new-el & more-news :as news] news] 42 | 43 | (cond 44 | (and (empty? olds) 45 | (empty? news)) 46 | (concat res 47 | (for [displaced-new displaced-news] 48 | [:moved-in displaced-new]) 49 | (for [displaced-old displaced-olds] 50 | [:moved-out displaced-old])) 51 | 52 | (= old-el new-el) 53 | (recur (concat res 54 | (for [displaced-new displaced-news] 55 | [:moved-in displaced-new]) 56 | (for [displaced-old displaced-olds] 57 | [:moved-out displaced-old]) 58 | [[:kept new-el]]) 59 | [] 60 | [] 61 | more-olds 62 | more-news) 63 | 64 | (contains? (set displaced-olds) new-el) 65 | (let [[moved-out-olds still-displaced-olds] (split-with #(not= new-el %) displaced-olds)] 66 | (recur (concat res 67 | (for [displaced-new displaced-news] 68 | [:moved-in displaced-new]) 69 | (for [moved-out-old moved-out-olds] 70 | [:moved-out moved-out-old]) 71 | [[:kept new-el]]) 72 | (rest still-displaced-olds) 73 | [] 74 | olds 75 | more-news)) 76 | 77 | (contains? (set displaced-news) old-el) 78 | (let [[moved-in-news still-displaced-news] (split-with #(not= old-el %) displaced-news)] 79 | (recur (concat res 80 | (for [displaced-old displaced-olds] 81 | [:moved-out displaced-old]) 82 | (for [moved-in-new moved-in-news] 83 | [:moved-in moved-in-new]) 84 | [[:kept old-el]]) 85 | [] 86 | (rest still-displaced-news) 87 | more-olds 88 | news)) 89 | 90 | (not= old-el new-el) 91 | (recur res 92 | (concat displaced-olds 93 | (when (seq olds) 94 | [old-el])) 95 | (concat displaced-news 96 | (when (seq news) 97 | [new-el])) 98 | more-olds 99 | more-news)))) 100 | 101 | (defn vector-diff [olds news] 102 | (if (to-diff? olds news) 103 | (do-diff olds news) 104 | 105 | (concat (for [old-el olds] 106 | [:moved-out old-el]) 107 | (for [new-el news] 108 | [:moved-in new-el])))) 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/flow/dom/elements.clj: -------------------------------------------------------------------------------- 1 | (ns flow.dom.elements) 2 | 3 | (defn text-el [s] 4 | (atom {:text s})) 5 | 6 | (defn new-element [tag] 7 | (atom {:tag tag})) 8 | 9 | (defn clear! [!el] 10 | (swap! !el update-in [:children] empty)) 11 | 12 | (defn append-child! [!parent !child] 13 | (swap! !parent update-in [:children] (comp #(conj % !child) vec))) 14 | 15 | (defn insert-before! [!parent !sibling !child] 16 | (swap! !parent update-in [:children] #(mapcat (some-fn {!sibling [!child !sibling]} vector) %))) 17 | 18 | (defn remove-child! [!parent !child] 19 | (swap! !parent update-in [:children] #(remove #{!child} %))) 20 | 21 | (defn replace-child! [!parent !old-child !new-child] 22 | (if !old-child 23 | (swap! !parent update-in [:children] #(map (some-fn {!old-child !new-child} identity) %)) 24 | (append-child! !parent !new-child))) 25 | 26 | (defn next-sibling [!parent !child] 27 | (->> (:children @!parent) 28 | (partition-all 2 1) 29 | (filter (comp #{!child} first)) 30 | first 31 | second)) 32 | 33 | (defn add-event-listener! [!el event listener] 34 | (swap! !el assoc-in [:listeners event] listener)) 35 | 36 | (defn null-elem [] 37 | (atom nil)) 38 | 39 | (defn ->el [el-ish] 40 | (cond 41 | (coll? el-ish) (map ->el el-ish) 42 | 43 | (and (= clojure.lang.Atom (class el-ish)) 44 | (or (:text @el-ish) 45 | (:tag @el-ish))) 46 | el-ish 47 | 48 | (or (nil? el-ish) 49 | (and (seq? el-ish) 50 | (empty? el-ish))) 51 | (null-elem) 52 | 53 | :else (text-el (if (string? el-ish) 54 | el-ish 55 | (pr-str el-ish))))) 56 | 57 | -------------------------------------------------------------------------------- /src/flow/dom/elements.cljs: -------------------------------------------------------------------------------- 1 | (ns flow.dom.elements 2 | (:require [flow.dom.attributes :as fda])) 3 | 4 | (defn text-el [s] 5 | (js/document.createTextNode s)) 6 | 7 | ;; pinched from Dommy 8 | ;; https://github.com/Prismatic/dommy/blob/5d75be9d24b0016f419bb1e23fcbf700421be6c7/src/dommy/template.cljs#L6-L7 9 | (def svg-ns "http://www.w3.org/2000/svg") 10 | (def svg-tag? #{"svg" "g" "rect" "circle" "clipPath" "path" "line" "polygon" "polyline" "text" "textPath"}) 11 | 12 | (defn new-element [tag] 13 | (if (svg-tag? tag) 14 | (js/document.createElementNS svg-ns tag) 15 | (js/document.createElement tag))) 16 | 17 | (defn append-child! [$parent $child] 18 | (.appendChild $parent $child)) 19 | 20 | (defn insert-before! [$parent $sibling $child] 21 | (.insertBefore $parent $child $sibling)) 22 | 23 | (defn remove-child! [$parent $child] 24 | (when (and $parent $child) 25 | (.removeChild $parent $child))) 26 | 27 | (defn clear! [$el] 28 | (loop [] 29 | (when-let [$child (.-firstChild $el)] 30 | (remove-child! $el $child) 31 | (recur)))) 32 | 33 | (defn replace-child! [$parent $old-child $new-child] 34 | (if $old-child 35 | (.replaceChild $parent $new-child $old-child) 36 | (append-child! $parent $new-child))) 37 | 38 | (defn next-sibling [$parent $child] 39 | (.-nextSibling $child)) 40 | 41 | (defn add-event-listener! [$el event listener] 42 | (if (exists? js/window.jQuery) 43 | (.. (js/$ $el) (on (name event) listener)) 44 | (.addEventListener $el (name event) listener))) 45 | 46 | (defn value [$el] 47 | (case (.-type $el) 48 | "checkbox" (.-checked $el) 49 | (.-value $el))) 50 | 51 | (defn bind-value! [!atom] 52 | (fn [e] 53 | (reset! !atom (value (.-target e))))) 54 | 55 | (defn null-elem [] 56 | (doto (js/document.createElement "span") 57 | (fda/set-style! :display :none) 58 | (fda/set-attr! :data-flow-placeholder true))) 59 | 60 | (defn ->el [el-ish] 61 | (cond 62 | (map? el-ish) (->el (pr-str el-ish)) 63 | 64 | (coll? el-ish) (flatten (map ->el el-ish)) 65 | 66 | (or (nil? el-ish) 67 | (and (seq? el-ish) 68 | (empty? el-ish))) 69 | (null-elem) 70 | 71 | (.-nodeType el-ish) el-ish 72 | 73 | :else (text-el (if (string? el-ish) 74 | el-ish 75 | (pr-str el-ish))))) 76 | -------------------------------------------------------------------------------- /src/flow/el.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.el 2 | (:require [flow.dom.children :as fdc] 3 | [flow.dom.elements :as fde] 4 | [flow.deps :as fd] 5 | [flow.cursors :as fc] 6 | [flow.state :as fs] 7 | [flow.render :as fr] 8 | [clojure.set :as set])) 9 | 10 | (defn update-watches! [{:keys [old-deps new-deps on-change watch-id]}] 11 | (let [old-atoms (set (keys old-deps)) 12 | new-atoms (set (keys new-deps))] 13 | (doseq [old-atom (set/difference old-atoms new-atoms)] 14 | (remove-watch old-atom watch-id)) 15 | 16 | (doseq [new-atom (set/difference new-atoms old-atoms)] 17 | (add-watch new-atom watch-id 18 | (fn [_ _ old new] 19 | (when-not (identical? old new) 20 | (on-change))))))) 21 | 22 | (defn root [$container el] 23 | (let [!deps (atom {}) 24 | !child (atom el) 25 | el-holder (fdc/new-child-holder! $container) 26 | !dirty? (atom true) 27 | watch-id (gensym "watch")] 28 | (letfn [(update-root! [] 29 | (fr/schedule-rendering-frame 30 | (fn [] 31 | (reset! !dirty? false) 32 | 33 | (let [{:keys [result deps]} (fd/with-watch-context 34 | (fn [] 35 | (@!child)))] 36 | 37 | (let [[$child update-child!] result] 38 | (reset! !child update-child!) 39 | (fdc/replace-child! el-holder $child)) 40 | 41 | (let [old-deps @!deps] 42 | (update-watches! {:old-deps old-deps 43 | :new-deps deps 44 | :watch-id watch-id 45 | :on-change (fn [] 46 | (when (compare-and-set! !dirty? 47 | false 48 | true) 49 | (update-root!)))}) 50 | (reset! !deps deps)) 51 | 52 | $container))))] 53 | 54 | (fde/clear! $container) 55 | (update-root!)))) 56 | 57 | (defn render-el [build-el] 58 | (fn [] 59 | (letfn [(update-el! [{:keys [update-component! $el deps]}] 60 | (let [{:keys [result deps]} (or (when (and update-component! 61 | (fd/deps-unchanged? deps)) 62 | 63 | (fd/mark-deps! deps) 64 | 65 | {:result [$el update-component!] 66 | :deps deps}) 67 | 68 | (fd/with-watch-context 69 | (fn [] 70 | ((or update-component! build-el))))) 71 | 72 | [$el update-component!] result] 73 | 74 | [$el #(update-el! {:$el $el 75 | :update-component! update-component! 76 | :deps deps})]))] 77 | 78 | (update-el! {})))) 79 | -------------------------------------------------------------------------------- /src/flow/expand.clj: -------------------------------------------------------------------------------- 1 | (ns flow.expand 2 | (:require [clojure.walk :as w])) 3 | 4 | (def ^:dynamic *macro-env*) 5 | (def ^:dynamic *macroexpand-1*) 6 | 7 | (defn environment [] 8 | (if (and (find-ns 'cljs.env) 9 | (some-> (ns-resolve 'cljs.env '*compiler*) 10 | deref)) 11 | :cljs 12 | :clj)) 13 | 14 | (defn expand-1 [] 15 | (case (environment) 16 | :clj clojure.core/macroexpand-1 17 | :cljs (eval #((ns-resolve 'cljs.analyzer 'macroexpand-1) *macro-env* %)))) 18 | 19 | (defn expand [form] 20 | (let [call (first form)] 21 | (and (symbol? call) 22 | (= (name call) "el")))) 23 | 24 | (defn prewalk-with-meta [f form] 25 | (w/walk (fn [form] 26 | (let [walked-form (prewalk-with-meta f form)] 27 | (if (instance? clojure.lang.IObj walked-form) 28 | (with-meta walked-form 29 | (meta form)) 30 | 31 | walked-form))) 32 | 33 | identity 34 | 35 | (f form))) 36 | 37 | (def leave-call-alone? 38 | (set (->> (concat (for [fn-ns ['clojure.core 'cljs.core] 39 | :when (find-ns fn-ns) 40 | fn-name ['case 'for 'let 'list]] 41 | (ns-resolve fn-ns fn-name)) 42 | [(ns-resolve (doto 'flow.core create-ns) 'el)] 43 | ['let 'if 'for 'case 'list]) 44 | 45 | (remove nil?)))) 46 | 47 | (defn macroexpand-until-known [form] 48 | (loop [form form] 49 | (if (seq? form) 50 | (let [expanded-form (*macroexpand-1* form)] 51 | (if (or (= form expanded-form) 52 | (leave-call-alone? (or (resolve (first form)) 53 | (first form)))) 54 | form 55 | 56 | (recur expanded-form))) 57 | 58 | form))) 59 | 60 | (defn expand-macros [form macro-env] 61 | (binding [*macro-env* macro-env 62 | *macroexpand-1* (expand-1)] 63 | (prewalk-with-meta macroexpand-until-known form))) 64 | -------------------------------------------------------------------------------- /src/flow/forms/bindings.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.bindings 2 | (:require [flow.compiler :as fc] 3 | [clojure.set :as set])) 4 | 5 | (defn bind-syms [binding] 6 | (letfn [(bind-syms* [binding] (cond 7 | (map? binding) (let [{:keys [as] 8 | ks :keys} binding] 9 | (concat (mapcat bind-syms* ks) 10 | (bind-syms* as) 11 | (->> (dissoc binding :keys :as) 12 | keys 13 | (mapcat bind-syms*)))) 14 | 15 | (vector? binding) (-> (mapcat bind-syms* binding) 16 | set 17 | (disj '& :as)) 18 | 19 | (symbol? binding) [(symbol (name binding))]))] 20 | 21 | (set (bind-syms* binding)))) 22 | 23 | (defn parse-bindings [bindings] 24 | (let [paired-bindings (partition-all 2 bindings)] 25 | (for [[binding value] paired-bindings] 26 | {:binding binding 27 | :bind-syms (bind-syms binding) 28 | :value value}))) 29 | 30 | (defn compile-el-bindings [bindings opts] 31 | (reduce (fn [{:keys [compiled-bindings opts]} {:keys [binding bind-syms value]}] 32 | {:compiled-bindings (conj compiled-bindings 33 | {:value-fn `(fn [] 34 | ~(fc/compile-value-form value opts)) 35 | :destructure-fn `(fn [~binding] 36 | ~(->> (for [bind-sym bind-syms] 37 | [`(quote ~bind-sym) bind-sym]) 38 | (into {})))}) 39 | :opts (update-in opts [:bound-syms] set/union bind-syms)}) 40 | 41 | {:compiled-bindings [] 42 | :opts opts} 43 | 44 | (parse-bindings bindings))) 45 | 46 | (defn compile-value-bindings [bindings opts] 47 | (reduce (fn [{:keys [compiled-bindings opts]} {:keys [binding bind-syms value]}] 48 | {:compiled-bindings (conj compiled-bindings 49 | [binding (fc/compile-value-form value opts)]) 50 | :opts (update-in opts [:bound-syms] set/difference bind-syms)}) 51 | 52 | {:compiled-bindings [] 53 | :opts opts} 54 | 55 | (parse-bindings bindings))) 56 | 57 | -------------------------------------------------------------------------------- /src/flow/forms/case.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.case 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-case [value-fn values build-branch] 5 | (fn [] 6 | (letfn [(update-case [old-value update-current-branch!] 7 | (let [new-value (get values (value-fn) ::default) 8 | 9 | new-branch (if (not= old-value new-value) 10 | (build-branch new-value) 11 | update-current-branch!) 12 | 13 | [$branch-el update-branch!] (new-branch)] 14 | 15 | [$branch-el #(update-case new-value update-branch!)]))] 16 | 17 | (update-case ::init nil)))) 18 | 19 | #+clj 20 | (defn parse-case-clauses [clauses] 21 | (let [paired-clauses (partition-all 2 clauses)] 22 | {:clauses (filter (comp #{2} count) paired-clauses) 23 | :default (let [last-clause (last paired-clauses)] 24 | (when (= 1 (count last-clause)) 25 | (first last-clause)))})) 26 | 27 | #+clj 28 | (defmethod fc/compile-el-form :case [[_ value & case-clauses] opts] 29 | (let [{:keys [clauses default]} (parse-case-clauses case-clauses)] 30 | `(build-case (fn [] 31 | ~(fc/compile-value-form value opts)) 32 | 33 | #{~@(map first clauses)} 34 | 35 | (fn [case-value#] 36 | (case case-value# 37 | ~@(->> (for [[test-value expr] clauses] 38 | [test-value (fc/compile-el-form expr opts)]) 39 | (apply concat)) 40 | 41 | ~@(when default 42 | [::default (fc/compile-el-form default opts)])))))) 43 | 44 | #+clj 45 | (defmethod fc/compile-value-form :case [[_ value & case-clauses] opts] 46 | (let [{:keys [clauses default]} (parse-case-clauses case-clauses)] 47 | `(case ~(fc/compile-value-form value opts) 48 | ~@(->> (for [[test-value expr] clauses] 49 | [test-value (fc/compile-value-form expr opts)]) 50 | (apply concat)) 51 | 52 | ~@(when default 53 | [(fc/compile-value-form default opts)])))) 54 | -------------------------------------------------------------------------------- /src/flow/forms/collections.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.collections 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | #+clj 5 | (defmethod fc/compile-value-form :coll [coll opts] 6 | (->> coll 7 | (map #(fc/compile-value-form % opts)) 8 | (into (empty coll)))) 9 | 10 | #+clj 11 | (defmethod fc/compile-value-form :map [m opts] 12 | (->> (for [[k v] m] 13 | [(fc/compile-value-form k opts) (fc/compile-value-form v opts)]) 14 | (into {}))) 15 | -------------------------------------------------------------------------------- /src/flow/forms/cursors.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.cursors 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.deps :as fd] 4 | [flow.cursors :as fcu])) 5 | 6 | (defn build-watch [build-cursor] 7 | (fn [] 8 | (letfn [(update-cursor! [] 9 | [(fd/read-dep (build-cursor)) update-cursor!])] 10 | (update-cursor!)))) 11 | 12 | #+clj 13 | (defmethod fc/compile-el-form :watch [[_ cursor-sym] opts] 14 | `(build-watch (fn [] 15 | ~(fc/compile-value-form cursor-sym opts)))) 16 | 17 | #+clj 18 | (defmethod fc/compile-value-form :watch [[_ cursor-sym] opts] 19 | `(fd/read-dep ~(fc/compile-value-form cursor-sym opts))) 20 | 21 | #+clj 22 | (defmethod fc/compile-value-form :wrap [[_ cursor-sym extra-path] opts] 23 | `(fcu/->atom ~(fc/compile-value-form cursor-sym opts) ~extra-path)) 24 | -------------------------------------------------------------------------------- /src/flow/forms/do.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.do 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-do [run-side-effects! build-body] 5 | (fn [] 6 | (letfn [(update-do! [body] 7 | (run-side-effects!) 8 | (let [body (or body (build-body)) 9 | [$el update-body!] (body)] 10 | [$el #(update-do! update-body!)]))] 11 | (update-do! nil)))) 12 | 13 | #+clj 14 | (defmethod fc/compile-el-form :do [[_ & body] opts] 15 | (let [side-effects (butlast body) 16 | expr (last body)] 17 | (if (empty? side-effects) 18 | (fc/compile-el-form expr opts) 19 | `(build-do 20 | (fn [] 21 | ~@(map #(fc/compile-value-form % opts) side-effects)) 22 | (fn [] 23 | ~(fc/compile-el-form expr opts)))))) 24 | 25 | #+clj 26 | (defmethod fc/compile-value-form :do [[_ & body] opts] 27 | `(do 28 | ~@(map #(fc/compile-value-form % opts) body))) 29 | -------------------------------------------------------------------------------- /src/flow/forms/fn_calls.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.fn-calls 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-fn-call [call-fn] 5 | (fn [] 6 | (letfn [(update-call! [] 7 | [(call-fn) update-call!])] 8 | 9 | (update-call!)))) 10 | 11 | #+clj 12 | (defmethod fc/compile-el-form :fn-call [call-args opts] 13 | `(build-fn-call (fn [] 14 | ~(map #(fc/compile-value-form % opts) call-args)))) 15 | 16 | #+clj 17 | (defmethod fc/compile-value-form :fn-call [call-args opts] 18 | (map #(fc/compile-value-form % opts) call-args)) 19 | -------------------------------------------------------------------------------- /src/flow/forms/fn_decls.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.fn-decls 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.state :as fs] 4 | [clojure.set :as set])) 5 | 6 | #+clj 7 | (defmethod fc/compile-value-form :fn-decl [[_ & arities] opts] 8 | (let [state-sym (gensym "state")] 9 | `(let [~state-sym fs/*state*] 10 | (fn* 11 | ~@(for [[args & body] (if (seq? (first arities)) 12 | arities 13 | [arities])] 14 | `(~args (binding [fs/*state* ~state-sym] 15 | ~(fc/compile-value-form `(do ~@body) 16 | (update-in opts [:bound-syms] set/difference (set args)))))))))) 17 | 18 | (comment 19 | (fc/compile-value-form (macroexpand '(fn [{:keys [a]}] 20 | (+ a 4))) 21 | {:bound-syms #{}}) 22 | 23 | 24 | ((macroexpand '(fn [{:keys [a]}] 25 | (+ a 4))))) 26 | -------------------------------------------------------------------------------- /src/flow/forms/for.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.for 2 | (:require #+clj [flow.compiler :as fc] 3 | #+clj [flow.forms.bindings :as fb] 4 | [flow.dom.elements :as fde] 5 | [flow.cursors :as fcu] 6 | [flow.state :as fs])) 7 | 8 | (defn value-pk [value] 9 | (if (fcu/cursor? value) 10 | [::cursor (fcu/-!state value) (fcu/-path value)] 11 | value)) 12 | 13 | (defn for-values [compiled-bindings] 14 | (reduce (fn [acc {:keys [value-fn destructure-fn]}] 15 | (->> (for [{:keys [state pks]} acc] 16 | (binding [fs/*state* state] 17 | (->> (for [value (value-fn)] 18 | {:state (merge state 19 | (destructure-fn value)) 20 | :pks (conj pks (value-pk value))}) 21 | doall))) 22 | (apply concat))) 23 | 24 | [{:state fs/*state* 25 | :pks []}] 26 | 27 | compiled-bindings)) 28 | 29 | (defn build-for [compiled-bindings build-body] 30 | (fn [] 31 | (letfn [(update-for! [body-cache] 32 | (let [for-bodies (->> (for [{:keys [state pks]} (for-values compiled-bindings)] 33 | (binding [fs/*state* state] 34 | (let [[$el update-body!] ((or (get body-cache pks) (build-body)))] 35 | {:$el $el 36 | :update! update-body! 37 | :pks pks}))) 38 | doall)] 39 | 40 | [(or (seq (map :$el for-bodies)) 41 | (fde/null-elem)) 42 | #(update-for! (into {} (map (juxt :pks :update!)) for-bodies))]))] 43 | 44 | (update-for! {})))) 45 | 46 | #+clj 47 | (defmethod fc/compile-el-form :for [[_ bindings body] opts] 48 | (let [{:keys [compiled-bindings opts]} (fb/compile-el-bindings bindings opts)] 49 | `(build-for ~(vec compiled-bindings) 50 | (fn [] 51 | ~(fc/compile-el-form body opts))))) 52 | 53 | #+clj 54 | (defmethod fc/compile-value-form :for [[_ bindings body] opts] 55 | (let [{:keys [compiled-bindings opts]} (fb/compile-value-bindings bindings opts)] 56 | `(for [~@(apply concat compiled-bindings)] 57 | ~(fc/compile-value-form body opts)))) 58 | 59 | (comment 60 | (let [!numbers (atom [1 4 5])] 61 | (binding [fs/*state* {'!numbers !numbers}] 62 | (let [[els update-for!] ((eval (fc/compile-el-form '(for [x (range 4) 63 | y (<< !numbers)] 64 | [:span (+ x y)]) 65 | {:bound-syms #{'!numbers}})))] 66 | (swap! !numbers rest) 67 | (let [[new-els update-for!] (update-for!)] 68 | [els new-els] 69 | (identical? (second els) (first new-els))))))) 70 | 71 | -------------------------------------------------------------------------------- /src/flow/forms/if.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.if 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-if [test-fn build-then build-else] 5 | (fn [] 6 | (letfn [(build-branch [test-value] 7 | (if test-value 8 | (build-then) 9 | (build-else))) 10 | 11 | (update-if [old-test-value update-current-branch!] 12 | (let [new-test-value (boolean (test-fn)) 13 | 14 | new-branch (if (and update-current-branch! 15 | (= old-test-value new-test-value)) 16 | update-current-branch! 17 | (build-branch new-test-value)) 18 | 19 | [$branch-el update-branch!] (new-branch)] 20 | 21 | [$branch-el #(update-if new-test-value update-branch!)]))] 22 | 23 | (update-if nil nil)))) 24 | 25 | #+clj 26 | (defmethod fc/compile-el-form :if [[_ test then else] opts] 27 | `(build-if (fn [] ~(fc/compile-value-form test opts)) 28 | (fn [] ~(fc/compile-el-form then opts)) 29 | (fn [] ~(fc/compile-el-form else opts)))) 30 | 31 | #+clj 32 | (defmethod fc/compile-value-form :if [[_ test then else] opts] 33 | `(if ~(fc/compile-value-form test opts) 34 | ~(fc/compile-value-form then opts) 35 | ~(fc/compile-value-form else opts))) 36 | -------------------------------------------------------------------------------- /src/flow/forms/let.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.let 2 | (:require #+clj [flow.compiler :as fc] 3 | #+clj [flow.forms.bindings :as fb] 4 | [flow.state :as fs])) 5 | 6 | (defn build-let [compiled-bindings build-body] 7 | (fn [] 8 | (letfn [(update-let! [body] 9 | (binding [fs/*state* (reduce (fn [state {:keys [value-fn destructure-fn]}] 10 | (binding [fs/*state* state] 11 | (merge state 12 | (destructure-fn (value-fn))))) 13 | fs/*state* 14 | compiled-bindings)] 15 | 16 | (let [[$el update-body!] ((or body (build-body)))] 17 | [$el #(update-let! update-body!)])))] 18 | 19 | (update-let! nil)))) 20 | 21 | #+clj 22 | (defmethod fc/compile-el-form :let [[_ bindings & body] opts] 23 | (let [{:keys [compiled-bindings opts]} (fb/compile-el-bindings bindings opts)] 24 | `(build-let ~(vec compiled-bindings) 25 | (fn [] 26 | ~(fc/compile-el-form `(do ~@body) opts))))) 27 | 28 | #+clj 29 | (defmethod fc/compile-value-form :let [[_ bindings & body] opts] 30 | (let [{:keys [compiled-bindings opts]} (fb/compile-value-bindings bindings opts)] 31 | `(let [~@(apply concat compiled-bindings)] 32 | ~(fc/compile-value-form `(do ~@body) opts)))) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/flow/forms/list.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.list 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-list [elem-builders] 5 | (fn [] 6 | (letfn [(update-list! [elem-updaters] 7 | (let [updated-elems (map #(apply % []) 8 | (or elem-updaters 9 | (map #(apply % []) elem-builders)))] 10 | [(map first updated-elems) #(update-list! (map second updated-elems))]))] 11 | 12 | (update-list! nil)))) 13 | 14 | #+clj 15 | (defmethod fc/compile-el-form :list [[_ & elems] opts] 16 | `(build-list [~@(map (fn [elem] 17 | `(fn [] 18 | ~(fc/compile-el-form elem opts))) 19 | elems)])) 20 | 21 | #+clj 22 | (defmethod fc/compile-value-form :list [[_ & elems] opts] 23 | `(list ~@(map #(fc/compile-value-form % opts) elems))) 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/flow/forms/node.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.node 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.dom.attributes :as fda] 4 | [flow.dom.children :as fdc] 5 | [flow.dom.elements :as fde])) 6 | 7 | (defn update-attrs! [$el attrs] 8 | (->> (for [{:keys [attr-key value-fn] :as attr} attrs] 9 | (let [new-value (value-fn)] 10 | (if-not (= new-value (get attr :previous-value ::nil)) 11 | (do 12 | (fda/set-attr! $el attr-key new-value) 13 | (assoc attr :previous-value new-value)) 14 | 15 | attr))) 16 | doall)) 17 | 18 | (defn update-styles! [$el styles] 19 | (->> (for [{:keys [style-key value-fn] :as style} styles] 20 | (let [new-value (value-fn)] 21 | (if (= new-value (get style :previous-value ::nil)) 22 | style 23 | 24 | (do 25 | (fda/set-style! $el style-key new-value) 26 | (assoc style :previous-value new-value))))) 27 | doall)) 28 | 29 | (defn update-classes! [$el classes] 30 | (when (seq classes) 31 | (let [new-classes (for [{:keys [class-value-fn] :as class} classes] 32 | {:previous-values (let [new-value (class-value-fn)] 33 | (if (coll? new-value) 34 | new-value 35 | [new-value])) 36 | :class-value-fn class-value-fn}) 37 | new-classes-set (->> (mapcat :previous-values new-classes) 38 | (remove nil?) 39 | set)] 40 | (if (= new-classes-set (->> (mapcat :previous-values classes) 41 | (remove nil?) 42 | set)) 43 | classes 44 | 45 | (do 46 | (fda/set-classes! $el new-classes-set) 47 | new-classes))))) 48 | 49 | (defn with-child-holders [$parent children] 50 | (for [child children] 51 | {:child child 52 | :holder (fdc/new-child-holder! $parent)})) 53 | 54 | (defn update-children! [children] 55 | (->> (for [{:keys [child holder]} children] 56 | (let [[$child update-child!] (child)] 57 | (fdc/replace-child! holder $child) 58 | 59 | {:child update-child! 60 | :holder holder})) 61 | 62 | doall)) 63 | 64 | (defn build-listeners! [$el listeners] 65 | (->> (for [{:keys [event build-listener]} listeners] 66 | (let [initial-listener (build-listener) 67 | !listener (atom initial-listener)] 68 | (fde/add-event-listener! $el event (fn [e] 69 | (when-let [listener @!listener] 70 | (listener e)))) 71 | {:!listener !listener 72 | :build-listener build-listener})) 73 | doall)) 74 | 75 | (defn update-listeners! [listeners] 76 | (doseq [{:keys [!listener build-listener]} listeners] 77 | (reset! !listener (build-listener))) 78 | 79 | listeners) 80 | 81 | (defn build-node [{:keys [tag id lifecycle-callbacks] :as node}] 82 | (fn [] 83 | (let [$el (fde/new-element tag)] 84 | (when id 85 | (fda/set-id! $el id)) 86 | 87 | (let [compiled-node (letfn [(update-node! [{:keys [attrs styles children classes listeners]}] 88 | (let [updated-children (update-children! children) 89 | updated-attrs (update-attrs! $el attrs) 90 | updated-styles (update-styles! $el styles) 91 | updated-classes (update-classes! $el classes) 92 | updated-listeners (update-listeners! listeners)] 93 | 94 | [$el #(update-node! {:attrs updated-attrs 95 | :styles updated-styles 96 | :children updated-children 97 | :classes updated-classes 98 | :listeners updated-listeners})]))] 99 | 100 | (update-node! (-> node 101 | (update-in [:children] #(with-child-holders $el %)) 102 | (update-in [:listeners] #(build-listeners! $el %)))))] 103 | 104 | (when-let [on-mount (get lifecycle-callbacks :mount)] 105 | (on-mount $el)) 106 | 107 | compiled-node)))) 108 | 109 | #+clj 110 | (defn parse-node [[tagish possible-attrs & body]] 111 | (let [tagish (name tagish) 112 | attrs (when (map? possible-attrs) 113 | possible-attrs) 114 | 115 | children (if attrs 116 | body 117 | (cond->> body 118 | possible-attrs (cons possible-attrs))) 119 | 120 | tag (second (re-find #"^([^#.]+)" tagish))] 121 | 122 | {:tag tag 123 | 124 | :id (second (re-find #"#([^.]+)" tagish)) 125 | 126 | :classes (->> (:flow.core/classes attrs) 127 | (cons (mapv (comp keyword second) (re-seq #"\.([^.]+)" tagish))) 128 | (remove empty?)) 129 | 130 | :styles (:flow.core/style attrs) 131 | 132 | :listeners (->> (:flow.core/on attrs) 133 | (remove (comp #{"flow.core"} namespace key)) 134 | (into {})) 135 | 136 | :lifecycle-callbacks (->> (:flow.core/on attrs) 137 | (filter (comp #{"flow.core"} namespace key)) 138 | (map #(update-in % [0] (comp keyword name))) 139 | (into {})) 140 | 141 | :attrs (dissoc attrs :flow.core/classes :flow.core/style :flow.core/on) 142 | 143 | :children children})) 144 | 145 | #+clj 146 | (defn compile-node [{:keys [tag id attrs styles classes listeners lifecycle-callbacks children]} opts] 147 | `(build-node ~{:tag tag 148 | 149 | :id id 150 | 151 | :classes (vec (for [class-form classes] 152 | {:class-value-fn `(fn [] 153 | ~(fc/compile-value-form class-form opts))})) 154 | 155 | :attrs (vec (for [[k v] attrs] 156 | {:attr-key k 157 | :value-fn `(fn [] 158 | ~(fc/compile-value-form v opts))})) 159 | 160 | :styles (vec (for [[k v] styles] 161 | {:style-key k 162 | :value-fn `(fn [] 163 | ~(fc/compile-value-form v opts))})) 164 | 165 | :listeners (vec (for [[event listener] listeners] 166 | {:event event 167 | :build-listener `(fn [] 168 | ~(fc/compile-value-form listener opts))})) 169 | 170 | :lifecycle-callbacks lifecycle-callbacks 171 | 172 | :children (vec (map #(fc/compile-el-form % opts) children))})) 173 | 174 | #+clj 175 | (defmethod fc/compile-el-form :node [node opts] 176 | (-> node 177 | parse-node 178 | (compile-node opts))) 179 | 180 | (comment 181 | (let [!number (atom 4)] 182 | (binding [flow.state/*state* {'!number !number}] 183 | (let [[$el update!] ((eval (fc/compile-el-form 184 | '[:div {:flow.core/on {:click (fn [e] 185 | (println (pr-str e) "happened!" 186 | "!number was" (<< !number)))}}] 187 | {:bound-syms #{'!number}}))) 188 | listener-1 (:click (:listeners @$el))] 189 | 190 | 191 | (listener-1 {:a 1 :b 2}) 192 | 193 | (listener-1 {:a 3 :b 2}) 194 | 195 | (reset! !number 5) 196 | 197 | (let [listener-2 (:click (:listeners @$el))] 198 | ((:click (:listeners @$el)) {:a 3 :b 2}) 199 | 200 | (println '(= listener-1 listener-2) (= listener-1 listener-2))))))) 201 | -------------------------------------------------------------------------------- /src/flow/forms/primitive.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.primitive 2 | (:require #+clj [flow.compiler :as fc])) 3 | 4 | (defn build-primitive [primitive] 5 | (fn update-primitive! [] 6 | [primitive update-primitive!])) 7 | 8 | #+clj 9 | (defmethod fc/compile-el-form :primitive [primitive opts] 10 | `(build-primitive ~primitive)) 11 | 12 | #+clj 13 | (defmethod fc/compile-value-form :primitive [primitive opts] 14 | primitive) 15 | -------------------------------------------------------------------------------- /src/flow/forms/sub_component.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.sub-component 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.cursors :as fcu])) 4 | 5 | (defn unchanged? [[old-value new-value]] 6 | (or (and (satisfies? fcu/Cursor old-value) 7 | (satisfies? fcu/Cursor new-value) 8 | 9 | (satisfies? #+clj clojure.lang.IDeref 10 | #+cljs IDeref 11 | old-value) 12 | (satisfies? #+clj clojure.lang.IDeref 13 | #+cljs IDeref 14 | new-value) 15 | 16 | (= (fcu/-!state old-value) (fcu/-!state new-value)) 17 | (= (fcu/-path old-value) (fcu/-path new-value))) 18 | 19 | (= old-value new-value))) 20 | 21 | (defn build-sub-component [args] 22 | (fn [] 23 | (letfn [(build-component [arg-values] 24 | (apply (first arg-values) (rest arg-values))) 25 | 26 | (update-sub-component! [old-arg-values update-component!] 27 | (let [new-arg-values (map #(apply % []) args) 28 | arg-pairs (map vector old-arg-values new-arg-values) 29 | 30 | [$el update-component!] ((if (or (nil? update-component!) 31 | (not= (count old-arg-values) 32 | (count new-arg-values)) 33 | (not (every? unchanged? arg-pairs))) 34 | (build-component new-arg-values) 35 | update-component!))] 36 | 37 | [$el #(update-sub-component! new-arg-values update-component!)]))] 38 | 39 | (update-sub-component! nil nil)))) 40 | 41 | #+clj 42 | (defmethod fc/compile-el-form :sub-component [component-args opts] 43 | `(build-sub-component [~@(map (fn [arg] 44 | `(fn [] 45 | ~(fc/compile-value-form arg opts))) 46 | component-args)])) 47 | 48 | (comment 49 | (let [component (fn [x y] 50 | (letfn [(update! [] 51 | [(+ x y) update!])] 52 | (update!))) 53 | [$el update!] (build-sub-component [(fn [] 54 | component) 55 | (fn [] (rand-int 3)) 56 | (fn [] 5)]) 57 | [$new-el update!] (update!)] 58 | [$el $new-el])) 59 | -------------------------------------------------------------------------------- /src/flow/forms/symbols.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.symbols 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.state :as fs])) 4 | 5 | (defn build-symbol [sym-fn] 6 | (fn [] 7 | (letfn [(update-symbol! [] 8 | [(sym-fn) update-symbol!])] 9 | (update-symbol!)))) 10 | 11 | #+clj 12 | (defn symbol-form [sym bound-syms] 13 | (if (contains? bound-syms sym) 14 | `(get fs/*state* (quote ~sym)) 15 | sym)) 16 | 17 | #+clj 18 | (defmethod fc/compile-el-form :symbol [sym {:keys [bound-syms] :as opts}] 19 | `(build-symbol (fn [] ~(symbol-form sym bound-syms)))) 20 | 21 | #+clj 22 | (defmethod fc/compile-value-form :symbol [sym {:keys [bound-syms] :as opts}] 23 | (symbol-form sym bound-syms)) 24 | -------------------------------------------------------------------------------- /src/flow/forms/text.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.forms.text 2 | (:require #+clj [flow.compiler :as fc] 3 | [flow.dom.elements :refer [text-el]])) 4 | 5 | (defn text-node [s] 6 | (fn [] 7 | (let [$el (text-el s)] 8 | (letfn [(update-text! [] 9 | [$el update-text!])] 10 | [$el update-text!])))) 11 | 12 | #+clj 13 | (defmethod fc/compile-el-form :text [s opts] 14 | `(text-node ~s)) 15 | -------------------------------------------------------------------------------- /src/flow/render.clj: -------------------------------------------------------------------------------- 1 | (ns flow.render) 2 | 3 | (defn schedule-rendering-frame [f] 4 | ;; It's not supported in Clojure, but the tests mock it out. 5 | (println "'schedule-rendering-frame' not properly supported in Clojure yet.") 6 | (f)) 7 | -------------------------------------------------------------------------------- /src/flow/render.cljs: -------------------------------------------------------------------------------- 1 | (ns flow.render) 2 | 3 | (defn schedule-rendering-frame [f] 4 | ;; Run asynchronously 5 | (js/setTimeout f 0)) 6 | -------------------------------------------------------------------------------- /src/flow/state.cljx: -------------------------------------------------------------------------------- 1 | (ns flow.state) 2 | 3 | (def ^:dynamic *state* {}) 4 | -------------------------------------------------------------------------------- /test/flow/deps_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.deps-test 2 | (:require [flow.deps :as fd :refer :all] 3 | [flow.cursors :as fc] 4 | [clojure.test :refer :all])) 5 | 6 | (defn mock-lens [!atom path] 7 | (reify fc/Cursor 8 | (-!state [_] !atom) 9 | (-path [_] path))) 10 | 11 | (def nested-map 12 | {:a {:e 15, :b 45} 13 | :c {:d 54}}) 14 | 15 | (deftest marking-deps-in-the-tree 16 | (let [!atom (atom nested-map)] 17 | (is (= (mark-dep {} !atom nested-map) 18 | {!atom {::fd/value nested-map}})) 19 | 20 | (is (= (mark-dep {} (mock-lens !atom [:a :b]) 45) 21 | {!atom {:a {:b {::fd/value 45}}}})) 22 | 23 | (is (= (-> {} 24 | (mark-dep (mock-lens !atom [:a :b]) 45) 25 | (mark-dep (mock-lens !atom [:c :d]) 54) 26 | (mark-dep (mock-lens !atom [:a]) {:b 45 :e 15})) 27 | {!atom {:c {:d {::fd/value 54}}, 28 | :a {::fd/value {:e 15, :b 45}}}})))) 29 | 30 | (deftest deps-unchanged-test 31 | (let [!atom (atom nested-map) 32 | deps-tree (-> {} 33 | (mark-dep (mock-lens !atom [:a :b]) (get-in @!atom [:a :b])) 34 | (mark-dep (mock-lens !atom [:c :d]) (get-in @!atom [:c :d])) 35 | (mark-dep (mock-lens !atom [:a]) (get-in @!atom [:a])))] 36 | 37 | (is (true? (deps-unchanged? deps-tree))) 38 | 39 | (swap! !atom assoc :f {:g 12}) 40 | 41 | (is (true? (deps-unchanged? deps-tree))) 42 | 43 | (swap! !atom assoc-in [:c :e] 29) 44 | 45 | (is (true? (deps-unchanged? deps-tree))) 46 | 47 | (swap! !atom assoc-in [:a :e] 25) 48 | 49 | (is (false? (deps-unchanged? deps-tree))))) 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/flow/forms/case_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.case-test 2 | (:require [flow.core :as f] 3 | [flow.render-harness :as fr] 4 | [flow.dom.elements :as fde] 5 | [clojure.test :refer :all])) 6 | 7 | (deftest case-flips-attr 8 | (fr/with-render-harness 9 | (let [!parent (atom {}) 10 | !entries (atom [])] 11 | (f/root !parent 12 | (f/el 13 | [:div {:data-count (case (count (<< !entries)) 14 | 0 "none" 15 | 1 "one" 16 | "more")}])) 17 | 18 | (fr/render-frame!) 19 | 20 | (is (= (:attrs (first (:children (fr/el-snapshot !parent)))) 21 | {:data-count "none"})) 22 | 23 | (reset! !entries [:not :empty]) 24 | 25 | (fr/render-frame!) 26 | 27 | (is (= (:attrs (first (:children (fr/el-snapshot !parent)))) 28 | {:data-count "more"})) 29 | 30 | (reset! !entries [:just-one]) 31 | 32 | (fr/render-frame!) 33 | 34 | (is (= (:attrs (first (:children (fr/el-snapshot !parent)))) 35 | {:data-count "one"}))))) 36 | 37 | (deftest case-flips-message 38 | (fr/with-render-harness 39 | (let [!parent (atom {}) 40 | !entries (atom [])] 41 | (letfn [(div-contents [] 42 | (-> (fr/el-snapshot !parent) 43 | :children 44 | first 45 | :children 46 | first))] 47 | (f/root !parent 48 | (f/el 49 | [:div 50 | (case (count (<< !entries)) 51 | 0 [:p "No elements in the atom."] 52 | 1 [:p "One element in the atom."] 53 | [:p "Lots of elements in the atom."])])) 54 | 55 | (fr/render-frame!) 56 | 57 | (is (= {:tag "p" 58 | :children [{:children [], 59 | :text "No elements in the atom."}]} 60 | 61 | (div-contents))) 62 | 63 | (reset! !entries [:plenty :of :elements]) 64 | 65 | (fr/render-frame!) 66 | 67 | (is (= {:tag "p" 68 | :children [{:children [], 69 | :text "Lots of elements in the atom."}]} 70 | 71 | (div-contents))) 72 | 73 | (reset! !entries [:still :plenty :of :elements]) 74 | 75 | (with-redefs [fde/new-element (constantly (atom {:tag "don't create new elements!"}))] 76 | (fr/render-frame!)) 77 | 78 | (is (= {:tag "p" 79 | :children [{:children [], 80 | :text "Lots of elements in the atom."}]} 81 | 82 | (div-contents))))))) 83 | 84 | -------------------------------------------------------------------------------- /test/flow/forms/do_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.do-test 2 | (:require [flow.core :as f] 3 | [flow.render-harness :as fr] 4 | [clojure.test :refer :all])) 5 | 6 | (deftest do-runs-side-effects 7 | (fr/with-render-harness 8 | (let [!parent (atom {}) 9 | !msg (atom "Hello!") 10 | !update-count (atom 0)] 11 | (f/root !parent 12 | (f/el 13 | [:div 14 | (do 15 | (swap! !update-count inc) 16 | (<< !msg))])) 17 | 18 | (fr/render-frame!) 19 | 20 | (is (= (:text (first (:children (first (:children (fr/el-snapshot !parent)))))) 21 | "Hello!")) 22 | 23 | (is (= 1 @!update-count)) 24 | 25 | (reset! !msg "Hi!") 26 | 27 | (fr/render-frame!) 28 | 29 | (is (= (:text (first (:children (first (:children (fr/el-snapshot !parent)))))) 30 | "Hi!")) 31 | (is (= 2 @!update-count))))) 32 | 33 | -------------------------------------------------------------------------------- /test/flow/forms/fn_call_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.fn-call-test 2 | (:require [flow.core :as f] 3 | [flow.render-harness :as fr] 4 | [clojure.test :refer :all])) 5 | 6 | (deftest fn-call-at-root-test 7 | (fr/with-render-harness 8 | (let [!parent (atom {}) 9 | !entries (atom [])] 10 | (f/root !parent 11 | (f/el 12 | (str "Hello" " " "World!"))) 13 | 14 | (fr/render-frame!) 15 | 16 | (is (= {:children [{:children [], 17 | :text "Hello World!"}]} 18 | 19 | (fr/el-snapshot !parent)))))) 20 | 21 | (deftest fn-call-count-test 22 | (fr/with-render-harness 23 | (let [!parent (atom {}) 24 | !entries (atom [])] 25 | (letfn [(div-contents [] 26 | (-> (fr/el-snapshot !parent) 27 | :children 28 | first 29 | :children 30 | first))] 31 | (f/root !parent 32 | (f/el 33 | [:div 34 | [:p (str (count (<< !entries)) " element(s) in the atom.")]])) 35 | 36 | (fr/render-frame!) 37 | 38 | (is (= {:tag "p" 39 | :children [{:children [], 40 | :text "0 element(s) in the atom."}]} 41 | 42 | (div-contents))) 43 | 44 | (reset! !entries [:not :empty]) 45 | 46 | (fr/render-frame!) 47 | 48 | (is (= {:tag "p" 49 | :children [{:children [], 50 | :text "2 element(s) in the atom."}]} 51 | 52 | (div-contents))))))) 53 | 54 | -------------------------------------------------------------------------------- /test/flow/forms/if_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.if-test 2 | (:require [flow.core :as f] 3 | [flow.render-harness :as fr] 4 | [flow.dom.elements :as fde] 5 | [clojure.test :refer :all])) 6 | 7 | (deftest if-flips-attr 8 | (fr/with-render-harness 9 | (let [!parent (atom {}) 10 | !entries (atom [])] 11 | (f/root !parent 12 | (f/el 13 | [:div {:data-empty (if (empty? (<< !entries)) 14 | "empty" 15 | "full")}])) 16 | 17 | (fr/render-frame!) 18 | 19 | (is (= (:attrs (first (:children (fr/el-snapshot !parent)))) 20 | {:data-empty "empty"})) 21 | 22 | (reset! !entries [:not :empty]) 23 | 24 | (fr/render-frame!) 25 | 26 | (is (= (:attrs (first (:children (fr/el-snapshot !parent)))) 27 | {:data-empty "full"}))))) 28 | 29 | (deftest if-flips-message 30 | (fr/with-render-harness 31 | (let [!parent (atom {}) 32 | !entries (atom [])] 33 | (letfn [(div-contents [] 34 | (-> (fr/el-snapshot !parent) 35 | :children 36 | first 37 | :children 38 | first))] 39 | (f/root !parent 40 | (f/el 41 | [:div 42 | (if (empty? (<< !entries)) 43 | [:p "No elements in the atom."] 44 | [:h3 "Some elements!"])])) 45 | 46 | (fr/render-frame!) 47 | 48 | (is (= {:tag "p" 49 | :children [{:children [], 50 | :text "No elements in the atom."}]} 51 | 52 | (div-contents))) 53 | 54 | (reset! !entries [:not :empty]) 55 | 56 | (fr/render-frame!) 57 | 58 | (is (= {:tag "h3" 59 | :children [{:children [], 60 | :text "Some elements!"}]} 61 | 62 | (div-contents))) 63 | 64 | (reset! !entries [:still :not :empty]) 65 | 66 | (with-redefs [fde/new-element (constantly (atom {:tag "don't create new elements!"}))] 67 | (fr/render-frame!)) 68 | 69 | (is (= {:tag "h3" 70 | :children [{:children [], 71 | :text "Some elements!"}]} 72 | 73 | (div-contents))))))) 74 | 75 | -------------------------------------------------------------------------------- /test/flow/forms/let_test.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.let-test 2 | (:require [flow.forms.let :refer :all] 3 | [flow.compiler :as fc] 4 | [flow.state :as fs] 5 | [flow.deps :as fd] 6 | [clojure.test :refer :all])) 7 | 8 | (deftest let-with-destructuring 9 | (let [!test (atom 8)] 10 | (binding [fs/*state* {'!test !test} 11 | fd/*ctx* (reify fd/Context 12 | (-read-dep [_ dep] 13 | (deref dep)))] 14 | (let [[$el update!] ((eval (fc/compile-el-form '(let [x 4 15 | test (<< !test)] 16 | (str "x is " x " and test is " test)) 17 | {:bound-syms #{'!test}})))] 18 | 19 | (reset! !test 9) 20 | 21 | (let [[$new-el update!] (update!)] 22 | 23 | (is (= $el "x is 4 and test is 8")) 24 | (is (= $new-el "x is 4 and test is 9"))))))) 25 | -------------------------------------------------------------------------------- /test/flow/forms/node/node_basics.clj: -------------------------------------------------------------------------------- 1 | (ns flow.forms.node.node-basics 2 | (:require [flow.core :as f] 3 | [flow.render-harness :as fr] 4 | [flow.dom.attributes :as fda] 5 | [clojure.test :refer :all])) 6 | 7 | (deftest static-node 8 | (fr/with-render-harness 9 | (let [!parent (atom {})] 10 | (f/root !parent 11 | (f/el 12 | [:a#the-link {:href "" 13 | ::f/style {:color "#4ff"}} 14 | "Link content"])) 15 | 16 | (fr/render-frame!) 17 | 18 | (is (= (fr/el-snapshot !parent) 19 | {:children [{:tag "a" 20 | :id "the-link" 21 | :style {:color "#4ff"}, 22 | :attrs {:href ""} 23 | :children [{:children [], :text "Link content"}]}]}))))) 24 | 25 | (deftest updating-style 26 | (fr/with-render-harness 27 | (let [!parent (atom {}) 28 | !color (atom "#4ff")] 29 | (f/root !parent 30 | (f/el 31 | [:a {::f/style {:color (<< !color)}} 32 | "Link content"])) 33 | 34 | (fr/render-frame!) 35 | 36 | (is (= (fr/el-snapshot !parent) 37 | {:children [{:tag "a" 38 | :style {:color "#4ff"}, 39 | :children [{:children [], :text "Link content"}]}]})) 40 | 41 | (reset! !color "#4dd") 42 | 43 | (fr/render-frame!) 44 | 45 | (is (= (fr/el-snapshot !parent) 46 | {:children [{:tag "a" 47 | :style {:color "#4dd"}, 48 | :children [{:children [], :text "Link content"}]}]}))))) 49 | 50 | (deftest updating-classes 51 | (fr/with-render-harness 52 | (let [!parent (atom {}) 53 | !class-test (atom :blah) 54 | !dummy-attr (atom :something)] 55 | 56 | (f/root !parent 57 | (f/el 58 | [:div.test-class {::f/classes [(<< !class-test)] 59 | :dummy-attr (<< !dummy-attr)} 60 | "Hello world!"])) 61 | 62 | (fr/render-frame!) 63 | 64 | (is (= #{:test-class :blah} 65 | (:classes (first (:children (fr/el-snapshot !parent)))))) 66 | 67 | (reset! !class-test [:more-than :one-class]) 68 | 69 | (fr/render-frame!) 70 | 71 | (is (= #{:test-class :more-than :one-class} 72 | (:classes (first (:children (fr/el-snapshot !parent)))))) 73 | 74 | (reset! !dummy-attr :something-else) 75 | 76 | (with-redefs [fda/set-classes! (fn [&_] 77 | (throw (Exception. "shouldn't update classes!")))] 78 | (fr/render-frame!)) 79 | 80 | (is (= #{:test-class :more-than :one-class} 81 | (:classes (first (:children (fr/el-snapshot !parent))))))))) 82 | -------------------------------------------------------------------------------- /test/flow/render_harness.clj: -------------------------------------------------------------------------------- 1 | (ns flow.render-harness 2 | (:require [flow.render :refer [schedule-rendering-frame]])) 3 | 4 | (def ^:dynamic *!render-queue* nil) 5 | 6 | (defn render-frame! [] 7 | (let [frame (dosync 8 | (when-let [frame (first @*!render-queue*)] 9 | (alter *!render-queue* subvec 1) 10 | frame))] 11 | (when frame 12 | (frame)))) 13 | 14 | (defn render-all-frames! [] 15 | (doseq [frame (dosync 16 | (let [frames @*!render-queue*] 17 | (ref-set *!render-queue* []) 18 | frames))] 19 | (frame))) 20 | 21 | 22 | (defn with-render-harness* [f] 23 | (with-redefs [schedule-rendering-frame (fn [frame] 24 | (dosync 25 | (alter *!render-queue* conj frame)))] 26 | (binding [*!render-queue* (ref [])] 27 | (f)))) 28 | 29 | (defmacro with-render-harness [& body] 30 | `(with-render-harness* (fn [] ~@body))) 31 | 32 | (defn el-snapshot [!el] 33 | (-> @!el 34 | (update-in [:children] #(map el-snapshot %)) 35 | (dissoc :uid))) 36 | --------------------------------------------------------------------------------