├── .gitignore ├── README.md ├── dev-resources └── public │ ├── css │ └── test.css │ ├── index.html │ ├── templates │ ├── template1.html │ └── test-grid.html │ ├── test_advanced.html │ ├── test_simple.html │ └── test_ws.html ├── profiles.clj ├── project.clj ├── runners └── phantomjs.js ├── src ├── brepl │ └── enfocus │ │ └── connect.cljs ├── clj │ └── enfocus │ │ ├── enlive │ │ └── syntax.clj │ │ └── macros.clj └── cljs │ └── enfocus │ ├── bind.cljs │ ├── core.cljs │ ├── effects.cljs │ └── events.cljs ├── temp ├── enfocus │ └── testing.cljs └── testing │ ├── project.clj │ ├── resources │ └── public │ │ ├── css │ │ └── test.css │ │ ├── templates │ │ ├── template1.html │ │ └── test-grid.html │ │ └── test.html │ └── src │ └── enfocus │ └── ring.clj └── test ├── cljs └── enfocus │ ├── bind_test.cljs │ ├── core_test.cljs │ └── events_test.cljs ├── cljx └── enfocus │ └── enlive │ └── syntax_test.cljx └── tools ├── enfocus └── reporting │ ├── report_generator.cljs │ └── test_setup.clj └── server.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | .lein-failures 4 | .lein-deps-sum 5 | .lein-repl-history 6 | .repl 7 | repl 8 | out 9 | target 10 | .settings 11 | .metadata 12 | .project 13 | .classpath 14 | dev-resources/public/js/*.js 15 | *.*# 16 | .#* 17 | *.*~ 18 | .DS_Store 19 | dev-resources/.DS_Store 20 | testing/resources/public 21 | project/.generated 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enfocus 2 | 3 | Enfocus is a `DOM` manipulation and templating library for 4 | ClojureScript. Inspired by Christophe Grand's [Enlive][1], Enfocus' 5 | primary purpose is providing a base for building rich interfaces in 6 | ClojureScript. 7 | 8 | If you are unfamiliar with enlive I also recommend taking a look at 9 | these links. 10 | 11 | David Nolen wrote a [nice tutorial][2] 12 | 13 | Another [tutorial][3] by Brian Marick. 14 | 15 | 16 | # Documentation & Examples 17 | 18 | [Documentation & Demo Site][4] 19 | 20 | [Example Website][5] 21 | 22 | # Where do I get support? 23 | 24 | [On the group][6] 25 | 26 | # Quick Start 27 | 28 | ## Setup 29 | 30 | From any leiningen project file: 31 | 32 | [![Clojars Project](http://clojars.org/enfocus/latest-version.svg)](http://clojars.org/enfocus) 33 | 34 | For the best development experience, use [lein-cljsbuild][7] 35 | 36 | ### The Basics 37 | 38 | Every great journey starts with "Hello world!" 39 | 40 | ```clj 41 | (ns my.namespace 42 | (:require [enfocus.core :as ef] 43 | [enfocus.events :as events] 44 | [enfocus.effects :as effects]) 45 | (:require-macros [enfocus.macros :as em])) 46 | 47 | (defn start [] 48 | (ef/at js/document 49 | ["body"] (ef/content "Hello enfocus!"))) 50 | 51 | (set! (.-onload js/window) start) 52 | ``` 53 | 54 | See [hello-enfocus][15] repo. 55 | 56 | ## The `at` form 57 | 58 | At the core to understanding Enfocus is the `at` form used in the 59 | "Hello world!" example above. It comes in two basic flavors listed below: 60 | 61 | A single transform 62 | 63 | ```clj 64 | (at a-node (transform arg1 ...)) 65 | 66 | ;or with implied js/document 67 | 68 | (at selector (transform arg1 ...)) 69 | ``` 70 | 71 | and a series of transforms 72 | 73 | ```clj 74 | (at a-node 75 | [selector1] (transform1 arg1 ...) 76 | [selector2] (transform2 arg1 ...)) 77 | 78 | ;or with implied js/document 79 | 80 | (at [selector1] (transform1 arg1 ...) 81 | [selector2] (transform2 arg1 ...)) 82 | ``` 83 | 84 | In the first case `at` is passed a node, node set or selector and a 85 | transform. This form of `at` calls the transform on each element in 86 | the node set. 87 | 88 | A `transform` is nothing more than a function that takes a set of 89 | arguments and returns a function that takes a set of nodes. In case 90 | of the `"Hello World!"` example above, we see the use of `(em/content 91 | "Hello world!")` this call returns a function that takes node or node 92 | set and replaces the content with `"Hello world!"` 93 | 94 | In the second case, we see `at` is optionally passed a node or node 95 | set and a set of selector/transform pairs. The selectors are scoped by 96 | the node, node set or js/document, if a node is not passed in, and the 97 | results of each selector is passed on to its partner transformation. 98 | 99 | A `selector` is a string representing a [CSS3 compliant selector][8] 100 | 101 | ### Handling Events 102 | 103 | Enfocus has event handling. Below is a simple example to add an 104 | `onclick` event handler to a button. 105 | 106 | ```clj 107 | (em/defaction change [msg] 108 | ["#button1"] (ef/content msg)) 109 | 110 | (em/defaction setup [] 111 | ["#button1"] (events/listen :click #(change "I have been clicked"))) 112 | 113 | (set! (.-onload js/window) setup) 114 | ``` 115 | 116 | The `defaction` construct is use here instead `defn`. `defaction` 117 | creates a function that calls the `at` form like discussed above and 118 | passes in `js/document` as the node to be transformed. 119 | 120 | ### Effects 121 | 122 | Enfocus has the concept of effects. Effects are nothing more than 123 | transformations over a period of time. Below is a simple example of a 124 | resize effect. Notice how the effects can be chained. 125 | 126 | ```clj 127 | (em/defaction resize-div [width] 128 | ["#rz-demo"] (effects/chain 129 | (effects/resize width :curheight 500 20) 130 | (effects/resize 5 :curheight 500 20))) 131 | 132 | (em/defaction setup [] 133 | ["#button2"] (events/listen #(resize-div 200))) 134 | 135 | (set! (.-onload js/window) setup) 136 | ``` 137 | 138 | ### Actions, templates and snippets 139 | 140 | A snippet is a function that returns a seq of nodes, it can be used as 141 | a building block for more complex templates or actions. 142 | 143 | You define a snippet by providing a remote resource, a selector and 144 | series of transformations. 145 | 146 | The snippet definition below selects a table body from the remote 147 | resource `templates/template1.html` and grabs the first row. It then 148 | fills the content of the row. 149 | 150 | ```clj 151 | (em/defsnippet snippet2 "templates/template1.html" ["tbody > *:first-child"] 152 | [fruit quantity] 153 | ["tr > *:first-child"] (ef/content fruit) 154 | ["tr > *:last-child"] (ef/content (str quantity))) 155 | ``` 156 | 157 | A template is very similar to a snippet except it does not require a 158 | selector to grap a sub section, instead the entire remote resource is 159 | used as the dom. If the remote resource is a full html document only 160 | what is inside the body tag is brought into the template. 161 | 162 | ```clj 163 | (em/deftemplate template2 "/templates/template1.html" [fruit-data] 164 | ["#heading1"] (ef/content "fruit") 165 | ["thead tr > *:last-child"] (ef/content "quantity") 166 | ["tbody"] (ef/content 167 | (map #(snippit2 % (fruit-data %)) (keys fruit-data)))) 168 | ``` 169 | 170 | Normally, snippets and templates are loaded via an AJAX request, but 171 | you can also create `:compiled` templates, which will be inlined in to 172 | resulting code at compile time: 173 | 174 | ```clj 175 | (em/deftemplate template2 :compiled "/templates/template1.html" [fruit-data] 176 | ["#heading1"] (ef/content "fruit") 177 | ["thead tr > *:last-child"] (ef/content "quantity") 178 | ["tbody"] (ef/content 179 | (map #(snippit2 % (fruit-data %)) (keys fruit-data)))) 180 | ``` 181 | 182 | If, snippets and/or templates are loaded via AJAX it is important to 183 | make sure the content has been loaded before calling the template or 184 | snippit function. Enfocus provides a convient macro that works like 185 | an onload callback but for AJAX driven snippets and templates. 186 | 187 | ```clj 188 | (em/wait-for-load (render-page)) 189 | ``` 190 | 191 | An action is a set of transforms that take place on the live dom. 192 | below is a definition of a an action. 193 | 194 | ```clj 195 | (em/defaction action2 [] 196 | [".cool[foo=false]"] (ef/content (template2 {"banana" 5 "pineapple" 10})) 197 | ["#heading1"] (ef/set-attr :id "new-heading1")) 198 | ``` 199 | 200 | Enfocus also support [hiccup][11] style emitters introduced in 201 | [enlive "1.1.0"][1]. 202 | 203 | ```clj 204 | (defn hiccup-template [arg1] 205 | (ef/html 206 | [:h1#hiccup.clazz {:width arg1} "Hiccup Emitters are Cool"])) 207 | ``` 208 | 209 | ## Transformations 210 | 211 | A transformation is a function that returns either a node or 212 | collection of node. 213 | 214 | ### Enfocus defines several helper functions for transformations: 215 | 216 | Supported Enlive Transformations 217 | 218 | ```clj 219 | content (content "xyz" a-node "abc") 220 | html-content (html-content "please no") 221 | set-attr (set-attr :attr1 "val1" :attr2 "val2") 222 | remove-attr (remove-attr :attr1 :attr2) 223 | add-class (add-class "foo" "bar") 224 | remove-class (remove-class "foo" "bar") 225 | do-> (do-> transformation1 transformation2) 226 | append (append "xyz" a-node "abc") 227 | prepend (prepend "xyz" a-node "abc") 228 | after (after "xyz" a-node "abc") 229 | before (before "xyz" a-node "abc") 230 | substitute (substitute "xyz" a-node "abc") 231 | clone-for (clone-for [item items] transformation) 232 | or (clone-for [item items] 233 | selector1 transformation1 234 | selector2 transformation2) 235 | wrap (wrap :div) or (wrap :div {:class "foo"}) 236 | unwrap (unwrap) 237 | replace-vars (replace-vars {:var1 "value" :var2 "value") 238 | ``` 239 | 240 | New Transformations 241 | 242 | ```clj 243 | focus (focus) 244 | blur (blur) 245 | set-prop (set-prop :value "testing") 246 | set-style (set-style :font-size "10px" :background "#fff") 247 | remove-style (remove-style :font-size :background) 248 | listen (listen :mouseover (fn [event] ...)) 249 | remove-listeners (remove-listeners :mouseover :mouseout) 250 | fade-in (fade-in time) 251 | or (fade-in time callback) 252 | or (fade-in time callback accelerator) 253 | fade-out (fade-out time) 254 | or (fade-out time callback) 255 | resize (resize width height ttime) 256 | or (resize width height ttime callback) 257 | or (resize width height ttime callback accelerator) 258 | move (move x y ttime) 259 | or (move x y ttime callback) 260 | or (move x y ttime callback accelerator) 261 | scroll (scroll x y ttime) 262 | or (scroll x y ttime callback) 263 | or (scroll x y ttime callback accelerator) 264 | chain (chain (fade-in ttime) ;serialize async effects 265 | (move x y ttime) 266 | (fade-out ttime) 267 | ...) 268 | set-data (set-data key value) 269 | ``` 270 | 271 | Currently there is one transformation that is supported by Enlive but 272 | not Enfocus. (Patches very welcome!!) 273 | 274 | ```clj 275 | move (move [:.footnote] [:#footnotes] content) 276 | ;this will be called relocate in enfocus 277 | ``` 278 | 279 | 280 | ## Selectors 281 | 282 | Enfocus supports both [CSS3][8] and [XPath][12] selectors: 283 | 284 | ```clj 285 | (ns example 286 | (:require [enfocus.core :as ef]) 287 | (:require-macros [enfocus.macros :as em])) 288 | 289 | (em/defaction action2 [] 290 | [".cool[foo=false]"] (ef/content ....)) ;CSS3 291 | (ef/xpath "//tr[@id='1']/th[2]") (ef/set-attr :id "new-heading1")) ;XPATH 292 | ``` 293 | 294 | 295 | ## The `from` form 296 | 297 | The `from` form is how we get data from the dom in enfocus. It comes 298 | in two basic flavors listed below: 299 | 300 | This form returns the result of the extractor. 301 | 302 | ```clj 303 | (from a-node (extractor arg1 ...)) 304 | 305 | ;or with selector 306 | 307 | (from selector (extractor arg1 ...)) 308 | ``` 309 | 310 | and this form returns a map of the form `{:key1 result1 :key2 result2}` 311 | 312 | ```clj 313 | (from a-node 314 | :key1 [selector1] (extractor arg1 ...) 315 | :key2 [selector2] (extractor arg1 ...)) 316 | 317 | ;or implied js/documnt 318 | 319 | (from 320 | :key1 [selector1] (extractor arg1 ...) 321 | :key2 [selector2] (extractor arg1 ...)) 322 | ``` 323 | 324 | 325 | ## Extractors 326 | 327 | An extractor is a function that takes a node and returns information 328 | about. 329 | 330 | ### Enfocus defines several helper fuctions for extractors: 331 | 332 | An extractor is a funciton that takes a node and returns information about 333 | 334 | ```clj 335 | get-attr (get-attr :attr1) 336 | get-text (get-text) 337 | get-prop (get-prop :value) 338 | get-data (get-data :key1) 339 | read-form (read-form) - returns {:field1 ("val1" "val2") :field2 val} 340 | ``` 341 | 342 | ## Contributing 343 | 344 | * Download [leiningen][9] 345 | 346 | ### Compile Enfocus 347 | 348 | ```bash 349 | git clone git://github.com/ckirkendall/enfocus.git 350 | cd enfocus 351 | lein do cljx once, compile 352 | ``` 353 | 354 | ### Test Enfocus 355 | 356 | After having compiled down `enfocus` as explained above 357 | 358 | ```bash 359 | lein test 360 | ``` 361 | 362 | ### bREPLing with Enfocus 363 | 364 | After having compiled down `enfocus` as explained above issue the 365 | following `lein` task: 366 | 367 | ```bash 368 | lein repl 369 | ``` 370 | 371 | Then run the http server as follows 372 | 373 | ```clj 374 | user=> (run) 375 | 2013-10-15 12:34:33.082:INFO:oejs.Server:jetty-7.6.8.v20121106 376 | 2013-10-15 12:34:33.138:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000 377 | # 378 | ``` 379 | 380 | Next launch the browser-connected REPL as follows: 381 | 382 | ```clj 383 | user=> (browser-repl) 384 | Browser-REPL ready @ http://localhost:55211/4347/repl/start 385 | Type `:cljs/quit` to stop the ClojureScript REPL 386 | nil 387 | cljs.user=> 388 | ``` 389 | 390 | Finally, visit the [http://localhost:3000/][10] URI 391 | 392 | The page is empty and it used for bREPLin only. Wait few moments while 393 | the connection is established and then you can start bREPLing with 394 | Enfocus. 395 | 396 | ```clj 397 | cljs.user=> (js/alert "Hello, Enfocus!") 398 | ``` 399 | 400 | ### Final note 401 | 402 | All the [Austin][16] use cases should work as expected. 403 | 404 | ## License 405 | 406 | Copyright (C) 2012-2013 Creighton Kirkendall 407 | 408 | Distributed under the Eclipse Public License, the same as Clojure. 409 | 410 | ## Special Thanks! 411 | 412 | [Christophe Grand][14] for creating [enlive][1] and building a simple 413 | api for dom manipulation. 414 | 415 | [1]: http://github.com/cgrand/enlive 416 | [2]: http://github.com/swannodette/enlive-tutorial/ 417 | [3]: https://github.com/cgrand/enlive/wiki/Table-and-Layout-Tutorial,-Part-1:-The-Goal 418 | [4]: http:/ckirkendall.github.com/enfocus-site 419 | [5]: http://github.com/ckirkendall/The-Great-Todo 420 | [6]: http://groups.google.com/group/enfocus 421 | [7]: https://github.com/emezeske/lein-cljsbuild 422 | [8]: http://www.w3schools.com/cssref/css_selectors.asp 423 | [9]: https://github.com/technomancy/leiningen 424 | [10]: http://localhost:3000/ 425 | [11]: https://github.com/weavejester/hiccup 426 | [12]: http://www.w3schools.com/xpath/xpath_syntax.asp 427 | [13]: https://github.com/cemerick/piggieback 428 | [14]: https://github.com/cgrand 429 | [15]: https://github.com/magomimmo/hello-enfocus 430 | [16]: https://github.com/cemerick/austin 431 | -------------------------------------------------------------------------------- /dev-resources/public/css/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #555555; 3 | font-size: 14px; 4 | font-family: Verdana, Arial, Helvetica, SunSans-Regular, Sans-Serif; 5 | color:#564b47; 6 | } 7 | 8 | div { 9 | display: inline-block; 10 | padding: 5px; 11 | margin: 2px; 12 | border: 1px solid #cfcfcf; 13 | border-radius: 5px; 14 | width: 30%; 15 | } 16 | 17 | .fail { 18 | background: #ffcfcf; 19 | } 20 | 21 | .pass { 22 | background: #99ff99; 23 | } 24 | 25 | .error{ 26 | background #ff3333; 27 | } 28 | -------------------------------------------------------------------------------- /dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bREPL Connection 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dev-resources/public/templates/template1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
heading1heading2
bannan10
apple5
pear2
sum:10
34 | 35 | 36 | -------------------------------------------------------------------------------- /dev-resources/public/templates/test-grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test grid for enfocus 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
TestResult
loading a remote templatesuccess
Adding a template to live domsuccess
Bring in a content with a snippitfail
Replacing content using contentfail
Replacing content using html-contentfail
Setting attributefail
Remove attribute
Add class
Remove class
do-> remove and afterfail
do-> remove and beforefail
substitutefail
do-> content appendfail
do-> content prependfail
wrap transformationfail
unwrap transformation 80 | 81 | success 82 | if green 83 | 84 |
xpath selectorfail
this-node selectorfail
string selectorfail
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /dev-resources/public/test_advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 21 | 22 | -------------------------------------------------------------------------------- /dev-resources/public/test_simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /dev-resources/public/test_ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 21 | 22 | -------------------------------------------------------------------------------- /profiles.clj: -------------------------------------------------------------------------------- 1 | {:dev {:resources-paths ["dev-resources"] 2 | :test-paths ["test/clj" 3 | "target/test/clj" 4 | "test/tools" 5 | 6 | ;; from cljs 7 | "test/cljs" 8 | "target/test/cljs" 9 | "src/brepl"] 10 | 11 | :dependencies [[ring "1.2.1"] 12 | [compojure "1.1.6"] 13 | [enlive "1.1.4"]] 14 | 15 | :plugins [[com.cemerick/clojurescript.test "0.2.1"] 16 | [com.cemerick/austin "0.1.3"] 17 | [com.keminglabs/cljx "0.3.0"]] 18 | 19 | :cljx {:builds [{:source-paths ["test/cljx"] 20 | :output-path "target/test/clj" 21 | :rules :clj} 22 | 23 | {:source-paths ["test/cljx"] 24 | :output-path "target/test/cljs" 25 | :rules :cljs}]} 26 | 27 | :cljsbuild 28 | {:builds {:whitespace 29 | {:source-paths ["test/tools" "src/cljs" "test/cljs" "target/test/cljs" "src/brepl"] 30 | :compiler 31 | {:output-to "dev-resources/public/js/whitespace.js" 32 | :optimizations :whitespace 33 | :pretty-print true}} 34 | 35 | :simple 36 | {:source-paths ["test/tools" "src/cljs" "test/cljs" "target/test/cljs" "src/brepl"] 37 | :compiler 38 | {:output-to "dev-resources/public/js/simple.js" 39 | :optimizations :simple 40 | :pretty-print false}} 41 | 42 | :advanced 43 | {:source-paths ["test/tools" "src/cljs" "test/cljs" "target/test/cljs"] 44 | :compiler 45 | {:output-to "dev-resources/public/js/advanced.js" 46 | :optimizations :advanced 47 | :pretty-print false}}} 48 | 49 | :test-commands {"slimerjs-ws" 50 | ["slimerjs" 51 | :runner 52 | "dev-resources/public/js/whitespace.js"] 53 | 54 | "slimerjs-simple" 55 | ["slimerjs" 56 | :runner 57 | "dev-resources/public/js/simple.js"] 58 | 59 | "slimerjs-advanced" 60 | ["slimerjs" 61 | :runner 62 | "dev-resources/public/js/advanced.js"] 63 | 64 | "phantomjs-ws" 65 | ["phantomjs" 66 | :runner 67 | "dev-resources/public/js/whitespace.js"] 68 | 69 | "phantomjs-simple" 70 | ["phantomjs" 71 | :runner 72 | "dev-resources/public/js/simple.js"] 73 | 74 | "phantomjs-advanced" 75 | ["phantomjs" 76 | :runner 77 | "dev-resources/public/js/advanced.js"] 78 | 79 | ;; for reporting 80 | "whitespace" 81 | ["runners/phantomjs.js" 82 | "dev-resources/public/test_ws.html"] 83 | 84 | "simple" 85 | ["runners/phantomjs.js" 86 | "dev-resources/public/test_simple.html"] 87 | 88 | "advanced" 89 | ["runners/phantomjs.js" 90 | "dev-resources/public/test_advanced.html"]}} 91 | :injections [(require '[server :as http :refer [run]] 92 | 'cemerick.austin.repls) 93 | (defn browser-repl [] 94 | (cemerick.austin.repls/cljs-repl (reset! cemerick.austin.repls/browser-repl-env 95 | (cemerick.austin/repl-env))))]}} 96 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject enfocus "2.1.2-SNAPSHOT" 2 | :description "DOM manipulation tool for clojurescript inspired by Enlive" 3 | :author "Creighton Kirkendall" 4 | :url "http://ckirkendall.github.io/enfocus-site" 5 | :license {:name "Eclipse Public License - v 1.0" 6 | :url "http://www.eclipse.org/legal/epl-v10.html" 7 | :distribution :repo} 8 | 9 | :min-lein-version "2.2.0" 10 | 11 | :source-paths ["src/clj" "src/cljs"] 12 | 13 | :dependencies [[org.clojure/clojure "1.6.0"] 14 | [org.clojure/clojurescript "0.0-2234"] 15 | [fresnel "0.2.2"] 16 | [domina "1.0.3"] 17 | [org.jsoup/jsoup "1.7.2"]] 18 | 19 | :plugins [[lein-cljsbuild "1.0.4-SNAPSHOT"]] 20 | 21 | :hooks [leiningen.cljsbuild] 22 | 23 | :cljsbuild 24 | {:crossovers [enfocus.enlive.syntax] 25 | :crossover-jar true 26 | 27 | :builds {:deploy 28 | {:source-paths ["src/cljs"] 29 | ;:jar true ; no needed anymore 30 | :compiler 31 | {:output-to "dev-resources/public/js/deploy.js" 32 | :optimizations :whitespace 33 | :pretty-print true}}}}) 34 | -------------------------------------------------------------------------------- /runners/phantomjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env phantomjs 2 | 3 | // reusable phantomjs script for running clojurescript.test tests 4 | // see http://github.com/cemerick/clojurescript.test for more info 5 | system = require('system'); 6 | 7 | if (system.args.length != 1) { 8 | console.log('Expected a target URL parameter.'); 9 | phantom.exit(1); 10 | } 11 | 12 | var p = require('webpage').create(); 13 | var url = system.args[0]; 14 | 15 | //var p = require('webpage').create(); 16 | //p.injectJs(require('system').args[1]); 17 | 18 | p.onConsoleMessage = function (x) { console.log(x); }; 19 | 20 | p.open(url, function (status) { 21 | if (status != "success") { 22 | console.log('Failed to open ' + url); 23 | phantom.exit(1); 24 | } 25 | console.log("opened: " + url); 26 | 27 | /* p.evaluate(function () { 28 | cemerick.cljs.test.set_print_fn_BANG_(function(x) { 29 | x = x.replace(/\n/g, ""); 30 | console.log(x); 31 | }); 32 | }); 33 | var success = p.evaluate(function () { 34 | var results = cemerick.cljs.test.run_all_tests(); 35 | console.log(results); 36 | return cemerick.cljs.test.successful_QMARK_(results); 37 | }); 38 | */ 39 | setTimeout(function(){phantom.exit(0);}, 1000); 40 | }); 41 | -------------------------------------------------------------------------------- /src/brepl/enfocus/connect.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.connect 2 | (:require [clojure.browser.repl :as repl])) 3 | -------------------------------------------------------------------------------- /src/clj/enfocus/enlive/syntax.clj: -------------------------------------------------------------------------------- 1 | (ns enfocus.enlive.syntax) 2 | (declare sel-to-string) 3 | 4 | (defn sel-to-str [input] 5 | (let [item (first input) 6 | rest (rest input) 7 | end (if (empty? rest) '(()) (sel-to-str rest))] 8 | (cond 9 | (keyword? item) (map #(conj % (name item)) end) 10 | (string? item) (map #(conj % item) end) 11 | (set? item) (reduce (fn [r1 it] 12 | (concat r1 (map #(conj % it) end))) 13 | [] (flatten (sel-to-str item))) 14 | (coll? item) (let [x1 (sel-to-str item) 15 | sub (map #(apply str %) (sel-to-str item))] 16 | (for [s sub e end] 17 | (do (println s e) 18 | (conj e s)))) 19 | :default input))) 20 | 21 | (defn convert [sel] 22 | (if (string? sel) 23 | sel 24 | (if-let [ors (sel-to-str sel)] 25 | (apply str (interpose " " (apply concat (interpose "," ors))))))) 26 | 27 | (defn- attr-pairs [op elms] 28 | (let [ts (fn [[x y]] (str "[" (name x) op "='" y "']"))] 29 | (apply str (map ts (partition 2 elms))))) 30 | 31 | (defn attr? [& elms] 32 | (apply str (map #(str "[" (name %) "]") elms))) 33 | 34 | (defn attr= [& elms] (attr-pairs "" elms)) 35 | 36 | (defn attr-has [x & vals] 37 | (let [ts (fn [y] (str "[" (name x) "~='" y "']"))] 38 | (apply str (map ts vals)))) 39 | 40 | (defn attr-starts [& elms] (attr-pairs "^" elms)) 41 | 42 | (defn attr-ends [& elms] (attr-pairs "$" elms)) 43 | 44 | (defn attr-contains [& elms] (attr-pairs "*" elms)) 45 | 46 | (defn attr|= [& elms] (attr-pairs "|" elms)) 47 | 48 | (defn- nth-op 49 | ([op x] (str ":nth-" op "(" x ")")) 50 | ([op x y] (str ":nth-" op "(" x "n" (when (pos? y) "+") y))) 51 | 52 | (defn nth-child 53 | ([x] (nth-op "child" x)) 54 | ([x y] (nth-op "child" x y))) 55 | 56 | (defn nth-last-child 57 | ([x] (nth-op "last-child" x)) 58 | ([x y] (nth-op "last-child" x y))) 59 | 60 | (defn nth-of-type 61 | ([x] (nth-op "of-type" x)) 62 | ([x y] (nth-op "of-type" x y))) 63 | 64 | (defn nth-last-of-type 65 | ([x] (nth-op "last-of-type" x)) 66 | ([x y] (nth-op "last-of-type" x y))) 67 | 68 | (defn but [& sel] 69 | (str "not(" (convert sel) ")")) 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/clj/enfocus/macros.clj: -------------------------------------------------------------------------------- 1 | (ns enfocus.macros 2 | (:refer-clojure :exclude [filter delay]) 3 | (:require [clojure.java.io :as io] 4 | [enfocus.enlive.syntax :as syn]) 5 | (:import [org.jsoup.Jsoup])) 6 | 7 | 8 | (defmacro create-dom-action [sym nod args & forms] 9 | (let [id-sym (gensym "id-sym") 10 | pnode-sym (gensym "pnod") 11 | new-form `(enfocus.core/i-at ~id-sym ~pnode-sym ~@forms)] 12 | `(defn ~sym ~args 13 | (let [[~id-sym ~pnode-sym] (~nod) 14 | ~pnode-sym (enfocus.core/create-hidden-dom ~pnode-sym)] 15 | ~new-form 16 | (enfocus.core/reset-ids ~id-sym ~pnode-sym) 17 | (enfocus.core/remove-node-return-child ~pnode-sym))))) 18 | 19 | (defn find-url 20 | "Given a string, returns a URL. Attempts to resolve as a classpath-relative 21 | path, then as a path relative to the working directory or a URL string" 22 | [path-or-url] 23 | (or (io/resource path-or-url) 24 | (try (io/as-url path-or-url) 25 | (catch java.net.MalformedURLException e 26 | false)) 27 | (io/as-url (io/as-file path-or-url)))) 28 | 29 | 30 | (defn ensure-mode 31 | " 'mode' argument is not required and defaults to :remote, for 32 | backward compatability" 33 | [[mode & _ :as body]] 34 | (if (keyword? mode) 35 | body 36 | (cons :remote body))) 37 | 38 | 39 | (def url-cache (ref {})) 40 | 41 | (defn get-key [key] 42 | (dosync 43 | (when-not (@url-cache key) 44 | (let [id (str (gensym "en") "_")] 45 | (alter url-cache assoc key id)))) 46 | (@url-cache key)) 47 | 48 | 49 | 50 | (defn load-local-dom 51 | "same as 'load-remote-dom', but work for local files" 52 | ([path dom-key] (load-local-dom path dom-key nil)) 53 | ([path dom-key sel] 54 | (let [text (slurp (io/reader (find-url path))) 55 | id-mask (get-key dom-key) 56 | text (if sel 57 | (-> (org.jsoup.Jsoup/parse text) 58 | (.select (syn/convert sel)) 59 | (.outerHtml)) 60 | text)] 61 | `(when (nil? (@enfocus.core/tpl-cache ~dom-key)) 62 | (let [[sym# txt#] (enfocus.core/replace-ids ~id-mask ~text)] 63 | (swap! enfocus.core/tpl-cache assoc ~dom-key [sym# txt#])))))) 64 | 65 | (defn load-remote-dom 66 | "returns macro code for loading remote dom" 67 | [uri dom-key] 68 | `(do 69 | (enfocus.core/load-remote-dom ~uri ~dom-key ~(get-key dom-key)) 70 | (when (nil? (@enfocus.core/tpl-cache ~dom-key)) 71 | (swap! enfocus.core/tpl-cache assoc ~dom-key ["" "NOT_LOADED"])))) 72 | 73 | (defmacro deftemplate [sym & body] 74 | (let [[mode uri args & forms] (ensure-mode body) 75 | dom-key (str (name mode) uri)] 76 | `(do 77 | ~(case mode 78 | :remote (load-remote-dom uri dom-key) 79 | :compiled (load-local-dom uri dom-key)) 80 | (enfocus.macros/create-dom-action 81 | ~sym 82 | #(enfocus.core/get-cached-dom ~dom-key) 83 | ~args ~@forms)))) 84 | 85 | 86 | (defmacro defsnippet [sym & body] 87 | (let [[mode uri sel args & forms] (ensure-mode body) 88 | dom-key (case mode 89 | :remote (str (name mode) uri) 90 | :compiled (str (name mode) uri sel))] 91 | `(do 92 | ~(case mode 93 | :remote (load-remote-dom uri dom-key) 94 | :compiled (load-local-dom uri dom-key sel)) 95 | (enfocus.macros/create-dom-action 96 | ~sym 97 | ~(case mode 98 | :remote `(fn [] (enfocus.core/get-cached-snippet ~dom-key ~sel)) 99 | :compiled `(fn [] (enfocus.core/get-cached-dom ~dom-key))) 100 | ~args ~@forms)))) 101 | 102 | 103 | (defmacro defaction [sym args & forms] 104 | `(defn ~sym ~args (enfocus.core/at js/document ~@forms))) 105 | 106 | 107 | (defmacro transform 108 | ([nod trans] `(enfocus.core/at ~nod ~trans)) 109 | ([nod sel trans] `(enfocus.core/at ~nod ~sel ~trans))) 110 | 111 | 112 | (defmacro wait-for-load [& forms] 113 | `(enfocus.core/setTimeout (fn check# [] 114 | (if (zero? (deref enfocus.core/tpl-load-cnt)) 115 | (do ~@forms) 116 | (enfocus.core/setTimeout #(check#) 10))) 0)) 117 | 118 | 119 | (defmacro clone-for [[sym lst] & forms] 120 | `(fn [pnod#] 121 | (let [div# (enfocus.core/create-hidden-dom 122 | (. js/document (~(symbol "createDocumentFragment"))))] 123 | (doseq [~sym ~lst] 124 | (do 125 | (enfocus.core/at div# (enfocus.core/append (. pnod# (~(symbol "cloneNode") true)))) 126 | (enfocus.core/at (enfocus.core/last-element-child div#) ~@forms))) 127 | (enfocus.core/log-debug div#) 128 | (enfocus.core/at 129 | pnod# 130 | (enfocus.core/do-> (enfocus.core/after (enfocus.core/remove-node-return-child div#)) 131 | (enfocus.core/remove-node)))))) 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/cljs/enfocus/bind.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.bind 2 | (:require 3 | [enfocus.core :as ef :refer [from at set-attr get-attr 4 | read-form-input set-form-input 5 | ITransform apply-transform 6 | nodes->coll set-form]] 7 | [enfocus.events :as ev :refer [listen]] 8 | [fresnel.lenses :as seg :refer [Lens -fetch -putback fetch putback]] 9 | [clojure.set :as set :refer [map-invert]] 10 | [goog.object :as gobj])) 11 | 12 | 13 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 14 | ;; HELPER FUNCTIONS 15 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 16 | (def default-bindings-opts {:binding-type :two-way ;:from,:two-way 17 | :event :blur 18 | ;;lenses are fresnel composable lenses 19 | :lens nil 20 | :delay nil}) 21 | 22 | (extend-type Keyword 23 | Lens 24 | (-fetch [seg value] 25 | (if (satisfies? ILookup value) 26 | (get value seg) 27 | (aget value (name seg)))) 28 | (-putback [seg value subval] 29 | (if (associative? value) 30 | (assoc value seg subval) 31 | (do (aset value (name seg) subval) value)))) 32 | 33 | (extend-type string 34 | Lens 35 | (-fetch [seg value] 36 | (if (satisfies? ILookup value) 37 | (-lookup value seg) 38 | (aget value seg))) 39 | (-putback [seg value subval] 40 | (if (associative? value) 41 | (assoc value seg subval) 42 | (do (aset value seg subval) value)))) 43 | 44 | 45 | 46 | (defn- key-or-props [obj] 47 | (if (map? obj) 48 | (keys obj) 49 | (seq (gobj/getKeys obj)))) 50 | 51 | 52 | (defn- build-key [id] 53 | (str "__EVB:" id)) 54 | 55 | (defn bind-view-watch-fn [id render-func mapping] 56 | (fn [ctx ref oval nval] 57 | (let [node (.getElementById js/document id)] 58 | (if node 59 | (let [[omval nmval] (if mapping 60 | [(fetch oval mapping) 61 | (fetch nval mapping)] 62 | [oval nval])] 63 | (when-not (and (or (coll? nval) 64 | (number? nval) 65 | (string? nval)) 66 | (= omval nmval)) 67 | (render-func node nmval))) 68 | (remove-watch ref (build-key id)))))) 69 | 70 | 71 | (defn bind-view 72 | ([atm render-func] (bind-view atm render-func nil)) 73 | ([atm render-func lens] 74 | (fn [node] 75 | (let [id (from node (get-attr :id)) 76 | nid (if (empty? id) (gensym "_EVB_") id) 77 | val (if lens (fetch @atm lens) @atm)] 78 | (when-not (= id nid) (at node (set-attr :id nid))) 79 | (render-func node val) 80 | (add-watch atm 81 | (build-key nid) 82 | (bind-view-watch-fn nid 83 | render-func 84 | lens)))))) 85 | 86 | 87 | (defn- bind-input-render-fn [mapping] 88 | (fn [node val] 89 | (let [nval (if mapping 90 | (fetch val mapping) 91 | val)] 92 | (when-not (= (from node (read-form-input)) nval) 93 | (at node (set-form-input nval)))))) 94 | 95 | 96 | (defn- bind-input-update-atm [atm node-group field delay-tracker] 97 | (let [delay (when delay-tracker @delay-tracker) 98 | update-fn (fn [e] 99 | (let [val (from node-group 100 | (read-form-input))] 101 | (swap! atm #(cond 102 | (vector? field) (putback % field val) 103 | field (-putback field % val) 104 | :else val))))] 105 | (fn [e] 106 | (if delay-tracker 107 | (do 108 | (reset! delay-tracker (.now js/Date)) 109 | (js/setTimeout #(when (>= (- (.now js/Date) 110 | @delay-tracker) 111 | delay) 112 | (update-fn e) delay))) 113 | (update-fn e))))) 114 | 115 | 116 | (defn bind-input 117 | ([atm] (bind-input atm nil)) 118 | ([atm opt-map] 119 | (let [{:keys [lens mapping binding-type event delay]} 120 | (merge default-bindings-opts opt-map) 121 | mapping (or lens mapping) 122 | trans (fn [nodes chain] 123 | (let [nod-col (nodes->coll nodes)] 124 | (when (= binding-type :two-way) 125 | (at nodes 126 | (bind-view atm 127 | (bind-input-render-fn mapping)))) 128 | (let [tracker (when (pos? delay) (atom delay))] 129 | (at nodes 130 | (listen event 131 | (bind-input-update-atm atm 132 | nod-col 133 | mapping 134 | tracker))))) 135 | (if chain (apply-transform chain nodes)))] 136 | 137 | (reify 138 | ITransform 139 | (apply-transform [_ nodes] (trans nodes nil)) 140 | (apply-transform [_ nodes chain] (trans nodes chain)) 141 | IFn 142 | (-invoke [_ nodes] (trans nodes nil)) 143 | (-invoke [_ nodes chain] (trans nodes chain))) 144 | ))) 145 | 146 | 147 | 148 | 149 | (defn- save-form-to-atm 150 | ([atm form] (save-form-to-atm atm form nil)) 151 | ([atm form lens] 152 | (let [form-vals (ef/from form (ef/read-form))] 153 | (swap! atm 154 | (fn [cur] (if lens 155 | (putback cur lens form-vals) 156 | (if (associative? cur) 157 | (merge cur form-vals) 158 | (reduce (fn [i [k v]] (aset i (name k) v) i) cur form-vals)))))))) 159 | 160 | 161 | (defn mapping-to-lens [mapping] 162 | (when mapping 163 | (reify Lens 164 | (-fetch [_ val] 165 | (into {} (map (fn [[k l]] [k (fetch val l)]) mapping))) 166 | (-putback [_ val sub-val] 167 | (reduce (fn [v [k l]] (putback v l (sub-val k))) val mapping))))) 168 | 169 | 170 | 171 | (defn bind-form 172 | ([atm] (bind-form atm nil)) 173 | ([atm opt-map] 174 | (let [{:keys [lens mapping binding-type]} 175 | (merge default-bindings-opts opt-map) 176 | lens (or lens (mapping-to-lens mapping)) 177 | inv-mapping (map-invert mapping)] 178 | (fn [form-node] 179 | (when (= binding-type :two-way) 180 | (at form-node 181 | (bind-view atm 182 | (fn [node val] 183 | (let [val-map :not-needed] 184 | (at node (set-form val)))) 185 | lens))) 186 | (at form-node 187 | (listen :submit 188 | (fn [e] 189 | (.preventDefault e) 190 | (save-form-to-atm atm 191 | (.-currentTarget e) 192 | lens)))))))) 193 | -------------------------------------------------------------------------------- /src/cljs/enfocus/core.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.core 2 | (:refer-clojure :exclude [filter delay]) 3 | (:require [enfocus.enlive.syntax :as en] 4 | [goog.net.XhrIo :as xhr] 5 | [goog.dom.query :as query] 6 | [goog.style :as style] 7 | [goog.events :as events] 8 | [goog.dom :as dom] 9 | [goog.dom.classes :as classes] 10 | [goog.dom.ViewportSizeMonitor :as vsmonitor] 11 | [goog.async.Delay :as gdelay] 12 | [goog.Timer :as timer] 13 | [goog.dom.forms :as form] 14 | [clojure.string :as string] 15 | [domina :as domina] 16 | [domina.css :as dcss] 17 | [domina.xpath :as xpath]) 18 | (:require-macros [enfocus.macros :as em] 19 | [domina.macros :as dm])) 20 | (declare css-syms css-select select create-sel-str at from match?) 21 | 22 | ;################################################### 23 | ; Selector Protocol 24 | ;################################################### 25 | (defprotocol ISelector 26 | (select [this] [this root] [this root id-mask] 27 | "takes root node and returns a domina node list")) 28 | 29 | (defprotocol ITransform 30 | (apply-transform [this nodes] [this nodes callback] 31 | "takes a set of nodes and performs a transform on them")) 32 | 33 | ;#################################################### 34 | ; Utility functions 35 | ;#################################################### 36 | (def debug false) 37 | 38 | (defn log-debug [mesg] 39 | (when (and debug (not (= (.-console js/window) js/undefined))) 40 | (.log js/console mesg))) 41 | 42 | (defn setTimeout [func ttime] 43 | (timer/callOnce func ttime)) 44 | 45 | (defn node? [tst] 46 | (dom/isNodeLike tst)) 47 | 48 | (defn nodelist? [tst] 49 | (instance? js/NodeList tst)) 50 | 51 | (defn nodes->coll 52 | "coverts a nodelist, node into a collection" 53 | [nl] 54 | (if (identical? nl js/window) 55 | [nl] 56 | (domina/nodes nl))) 57 | 58 | (defn- flatten-nodes-coll [values] 59 | "takes a set of nodes and nodelists and flattens them" 60 | (mapcat #(cond (string? %) [(dom/createTextNode %)] 61 | :else (nodes->coll %)) values)) 62 | 63 | 64 | (defn- style-set 65 | "Sets property name to a value on a element and Returns the original object" 66 | [obj values] 67 | (do (doseq [[attr value] (apply hash-map values)] 68 | (style/setStyle obj (name attr) value)) 69 | obj)) 70 | 71 | (defn- style-remove 72 | "removes the property value from an elements style obj." 73 | [obj values] 74 | (doseq [attr values] 75 | (if (.-IE goog/userAgent) 76 | (style/setStyle obj (name attr) "") 77 | (. (.-style obj) (removeProperty (name attr)))))) 78 | 79 | (defn get-eff-prop-name [etype] 80 | (str "__ef_effect_" etype)) 81 | 82 | (defn get-mills [] (. (js/Date.) (getMilliseconds))) 83 | 84 | (defn pix-round [step] 85 | (if (neg? step) (Math/floor step) (Math/ceil step))) 86 | 87 | (defn add-map-attrs 88 | ([elem ats] 89 | (when elem 90 | (when (map? ats) 91 | (do 92 | (doseq [[k v] ats] 93 | (add-map-attrs elem k v)) 94 | elem)))) 95 | ([elem k v] 96 | (. elem (setAttribute (name k) v)) 97 | elem)) 98 | 99 | ;#################################################### 100 | ; The following functions are used to manage the 101 | ; remote dom features for templates and snippets 102 | ;#################################################### 103 | 104 | (def tpl-load-cnt 105 | "this is incremented everytime a remote template is loaded and decremented when 106 | it is added to the dom cache" 107 | (atom 0)) 108 | 109 | 110 | (def tpl-cache 111 | "cache for the remote templates" 112 | (atom {})) 113 | 114 | (def hide-style (.-strobj {"style" "display: none; width: 0px; height: 0px"})) 115 | 116 | (defn create-hidden-dom 117 | "Add a hidden div to hold the dom as we are transforming it this has to be done 118 | because css selectors do not work unless we have it in the main dom" 119 | [child] 120 | (let [div (dom/createDom "div" hide-style)] 121 | (if (= (.-nodeType child) 11) 122 | (dom/appendChild div child) 123 | (do 124 | (log-debug (count (domina/nodes child))) 125 | (doseq [node (domina/nodes child)] 126 | (dom/appendChild div node)))) 127 | (dom/appendChild (.-documentElement (dom/getDocument)) div) 128 | div)) 129 | 130 | (defn remove-node-return-child 131 | "removes the hidden div and returns the children" 132 | [div] 133 | (let [child (.-childNodes div) 134 | frag (. js/document (createDocumentFragment))] 135 | (dom/append frag child) 136 | (dom/removeNode div) 137 | frag)) 138 | 139 | (defn last-element-child [node] 140 | "last child node that is an element" 141 | (dom/getLastElementChild node)) 142 | 143 | 144 | (defn replace-ids 145 | "replaces all the ids in a string html fragement/template with a generated 146 | symbol appended on to a existing id this is done to make sure we don't have 147 | id colisions during the transformation process" 148 | ([text] (replace-ids (str (name (gensym "id")) "_") text)) 149 | ([id-mask text] 150 | (let [re (js/RegExp. "(<.*?\\sid=['\"])(.*?)(['\"].*?>)" "g")] 151 | [id-mask (.replace text re (fn [a b c d] (str b id-mask c d)))]))) 152 | 153 | 154 | (defn reset-ids 155 | "before adding the dom back into the live dom we reset the masked ids to orig vals" 156 | [sym nod] 157 | (let [id-nodes (css-select nod "*[id]") 158 | nod-col (nodes->coll id-nodes)] 159 | (doall (map #(let [id (. % (getAttribute "id")) 160 | rid (. id (replace sym ""))] 161 | (. % (setAttribute "id" rid))) nod-col)))) 162 | 163 | 164 | (defn load-remote-dom 165 | "loads a remote file into the cache, and masks the ids to avoid collisions" 166 | [uri dom-key id-mask] 167 | (when (nil? (@tpl-cache dom-key)) 168 | (swap! tpl-load-cnt inc) 169 | (let [req (new goog.net.XhrIo) 170 | callback (fn [req] 171 | (let [text (. req (getResponseText)) 172 | [sym txt] (replace-ids id-mask text)] 173 | (swap! tpl-cache assoc dom-key [sym txt] )))] 174 | (events/listen req js/goog.net.EventType.COMPLETE 175 | #(do 176 | (callback req) 177 | (swap! tpl-load-cnt dec))) 178 | (. req (send uri "GET"))))) 179 | 180 | (defn html-to-dom [html] 181 | (let [dfa (nodes->coll (domina/html-to-dom html)) 182 | frag (. js/document (createDocumentFragment))] 183 | (log-debug (count dfa)) 184 | (doseq [df dfa] 185 | (dom/append frag df)) 186 | frag)) 187 | 188 | 189 | (defn get-cached-dom 190 | "returns and dom from the cache and symbol used to scope the ids" 191 | [uri] 192 | (let [nod (@tpl-cache uri)] 193 | (when nod [(first nod) (html-to-dom (second nod))]))) 194 | 195 | (defn get-cached-snippet 196 | "returns the cached snippet or creates one and adds it to the cache if needed" 197 | [uri sel] 198 | (let [sel-str (create-sel-str sel) 199 | cache (@tpl-cache (str uri sel-str))] 200 | (if cache [(first cache) (html-to-dom (second cache))] 201 | (let [[sym tdom] (get-cached-dom uri) 202 | dom (create-hidden-dom tdom) 203 | tsnip (domina/nodes (css-select sym dom sel)) 204 | html_snip (apply str (map #(.-outerHTML %) tsnip))] 205 | (remove-node-return-child dom) 206 | (swap! tpl-cache assoc (str uri sel-str) [sym html_snip]) 207 | [sym (html-to-dom html_snip)])))) 208 | 209 | 210 | 211 | ;#################################################### 212 | ; The following functions are used to transform the 213 | ; dom structure. each function returns a function 214 | ; taking the a set of nodes from a selector 215 | ;#################################################### 216 | 217 | (defn extr-multi-node 218 | "wrapper function for extractors that maps the extraction to 219 | all nodes returned by the selector" 220 | ([func] (extr-multi-node func nil)) 221 | ([func filt] 222 | (let [trans (fn trans 223 | [pnodes chain] 224 | (let [pnod-col (nodes->coll pnodes) 225 | result (map func pnod-col) 226 | result (if filt 227 | (cljs.core/filter filt result) 228 | result)] 229 | (if (<= (count result) 1) (first result) result)))] 230 | 231 | (reify 232 | ITransform 233 | (apply-transform [_ nodes] (trans nodes nil)) 234 | (apply-transform [_ nodes chain] (trans nodes chain)) 235 | IFn 236 | (-invoke [_ nodes] (trans nodes nil)) 237 | (-invoke [_ nodes chain] (trans nodes chain)))))) 238 | 239 | 240 | (defn multi-node-chain 241 | "Allows standard domina functions to be chainable" 242 | ([func] 243 | (let [trans (fn [nodes chain] 244 | (let [val (func nodes)] 245 | (if chain (apply-transform chain nodes) val)))] 246 | (reify 247 | ITransform 248 | (apply-transform [_ nodes] (trans nodes nil)) 249 | (apply-transform [_ nodes chain] (trans nodes chain)) 250 | IFn 251 | (-invoke [_ nodes] (trans nodes nil)) 252 | (-invoke [_ nodes chain] (trans nodes chain))))) 253 | ([values func] 254 | (let [trans (fn [nodes chain] 255 | (let [vnodes (mapcat #(domina/nodes %) values) 256 | val (func nodes vnodes)] 257 | (if chain (apply-transform chain nodes) val)))] 258 | (reify 259 | ITransform 260 | (apply-transform [_ nodes] (trans nodes nil)) 261 | (apply-transform [_ nodes chain] (trans nodes chain)) 262 | IFn 263 | (-invoke [_ nodes] (trans nodes nil)) 264 | (-invoke [_ nodes chain] (trans nodes chain)))))) 265 | 266 | 267 | ;;TODO need to figure out how to make sure this stay just as 268 | ;;text and not convert to html. 269 | (defn content 270 | "Replaces the content of the element. Values can be nodes or collection of nodes." 271 | [& values] 272 | (multi-node-chain values #(do 273 | (domina/destroy-children! %1) 274 | (domina/append! %1 %2)))) 275 | 276 | (defn text-content 277 | "Replaces the content of the element escaping html." 278 | [text] 279 | (content (string/escape text {\< "<", 280 | \> ">", 281 | \& "&", 282 | \' "'", 283 | \" """}))) 284 | 285 | (defn html-content 286 | "Replaces the content of the element with the dom structure represented by the html string passed" 287 | [txt] 288 | (multi-node-chain #(domina/set-html! % txt))) 289 | 290 | 291 | (defn set-attr 292 | "Assocs attributes and values on the selected element." 293 | [& values] 294 | (let [pairs (partition 2 values)] 295 | (multi-node-chain 296 | #(doseq [[name value] pairs] (domina/set-attr! % name value))))) 297 | 298 | (defn remove-attr 299 | "Dissocs attributes on the selected element." 300 | [& values] 301 | (multi-node-chain #(doseq [name values] (domina/remove-attr! % name)))) 302 | 303 | 304 | (defn set-prop [& forms] 305 | (fn [node] 306 | (let [h (mapcat (fn [[n v]](list (name n) v)) (partition 2 forms))] 307 | (dom/setProperties node (apply js-obj h))))) 308 | 309 | 310 | (defn- has-class 311 | "returns true if the element has a given class" 312 | [el cls] 313 | (classes/hasClass el cls)) 314 | 315 | 316 | (defn add-class 317 | "Adds the specified classes to the selected element." 318 | [ & values] 319 | (multi-node-chain 320 | #(doseq [val values] (domina/add-class! % val)))) 321 | 322 | 323 | (defn remove-class 324 | "Removes the specified classes from the selected element." 325 | [ & values] 326 | (multi-node-chain 327 | #(doseq [val values] (domina/remove-class! % val)))) 328 | 329 | 330 | (defn set-class 331 | "Sets the specified classes on the selected element" 332 | [ & values] 333 | (multi-node-chain #(domina/set-classes! % values))) 334 | 335 | 336 | (defn do-> [ & forms] 337 | "Chains (composes) several transformations. Applies functions from left to right." 338 | (fn [pnod] (doseq [fun forms] (apply-transform fun pnod)))) 339 | 340 | (defn append 341 | "Appends the content of the element. Values can be nodes or collection of nodes." 342 | [& values] 343 | (multi-node-chain values #(domina/append! %1 %2))) 344 | 345 | 346 | (defn prepend 347 | "Prepends the content of the element. Values can be nodes or collection of nodes." 348 | [& values] 349 | (multi-node-chain values #(domina/prepend! %1 %2))) 350 | 351 | 352 | (defn before 353 | "inserts the content before the selected node. Values can be nodes or collection of nodes" 354 | [& values] 355 | (multi-node-chain values #(domina/insert-before! %1 %2))) 356 | 357 | 358 | (defn after 359 | "inserts the content after the selected node. Values can be nodes or collection of nodes" 360 | [& values] 361 | (multi-node-chain values #(domina/insert-after! %1 %2))) 362 | 363 | 364 | (defn substitute 365 | "substitutes the content for the selected node. Values can be nodes or collection of nodes" 366 | [& values] 367 | (multi-node-chain values #(domina/swap-content! %1 %2))) 368 | 369 | 370 | (defn remove-node 371 | "removes the selected nodes from the dom" 372 | [] 373 | (multi-node-chain #(domina/detach! %1))) 374 | 375 | 376 | (defn wrap 377 | "wrap and element in a new element defined as :div {:class 'temp'}" 378 | [elm mattr] 379 | (fn [pnod] 380 | (let [elem (dom/createElement (name elm))] 381 | (add-map-attrs elem mattr) 382 | (at elem (content (.cloneNode pnod true))) 383 | (at pnod (do-> (after elem) 384 | (remove-node)))))) 385 | 386 | (defn unwrap 387 | "replaces a node with all its children" 388 | [] 389 | (fn [pnod] 390 | (let [frag (. js/document (createDocumentFragment))] 391 | (dom/append frag (.-childNodes pnod)) 392 | (dom/replaceNode frag pnod)))) 393 | 394 | 395 | (defn set-style 396 | "set a list of style elements from the selected nodes" 397 | [& values] 398 | (let [pairs (partition 2 values)] 399 | (multi-node-chain 400 | #(doseq [[name value] pairs] (domina/set-style! % name value))))) 401 | 402 | 403 | (defn remove-style 404 | "remove a list style elements from the selected nodes. note: you can only remove styles that are inline" 405 | [& values] 406 | (fn [pnod] (style-remove pnod values))) 407 | 408 | (defn focus 409 | "calls the focus function on the selected node" 410 | [] 411 | (fn [node] (.focus node))) 412 | 413 | (defn blur 414 | "calls the blur function on the selected node" 415 | [] 416 | (fn [node] (.blur node))) 417 | 418 | 419 | (defn set-data 420 | "addes key value pair of data to the selected nodes. Only use clojure data structures when setting" 421 | [ky val] 422 | (multi-node-chain #(domina/set-data! % ky val))) 423 | 424 | 425 | (defn delay 426 | "delays and action by a set timeout, note this is an async operations" 427 | [ttime & funcs] 428 | (fn [pnod] (setTimeout #(apply at pnod funcs) ttime))) 429 | 430 | 431 | (defn- str-var-replace 432 | "this function replaces #{var1} with values in vars map. It has 433 | two arities because of an incompatable change in clojurescript 1.7.145" 434 | [vars] 435 | (fn 436 | ([[_ m]] (get vars (keyword m))) 437 | ([_ m & extra] (get vars (keyword m))))) 438 | 439 | (defn replace-vars 440 | "replaces entries like the following ${var1} in attributes and text" 441 | [vars] 442 | (letfn [(rep-str [text] 443 | (string/replace text #"\$\{\s*(\S+)\s*}" (str-var-replace vars)))] 444 | (fn rep-node [pnod] 445 | (when (.-attributes pnod) 446 | (doseq [idx (range (.-length (.-attributes pnod)))] 447 | (let [attr (.item (.-attributes pnod) idx)] 448 | (when (.-specified attr) 449 | (set! (.-value attr) (rep-str (.-value attr))))))) 450 | (if (= (.-nodeType pnod) 3) 451 | (set! (.-nodeValue pnod) (rep-str (.-nodeValue pnod))) 452 | (doseq [cnode (nodes->coll (.-childNodes pnod))] 453 | (rep-node cnode)))))) 454 | 455 | 456 | (defn- exists-in? [col-or-val val] 457 | (if (coll? col-or-val) 458 | (some #{val} col-or-val) 459 | (= col-or-val val))) 460 | 461 | (defn set-form-input 462 | "sets the value of a form input (text,select,checkbox,etc...) 463 | format (at node (set-form-input value))" 464 | [val] 465 | (fn [el] 466 | (if (or (= (.-type el) "checkbox") 467 | (= (.-type el) "radio")) 468 | (set! (.-checked el) (exists-in? val (.-value el))) 469 | (let [nval (if (and (coll? val) 470 | (not (string? val))) 471 | (vec val) 472 | (if (= (.-type el) "select-multiple") [val] val))] 473 | (form/setValue el (clj->js nval)))))) 474 | 475 | 476 | (defn set-form 477 | "sets the values of a form based on a map 478 | (set-form {:val1 'val' :val2 'val'})" 479 | [value-map] 480 | (fn [form-node] 481 | (when (= (.-nodeName form-node) "FORM") 482 | (doseq [idx (range (.-length form-node))] 483 | (let [el (aget (.-elements form-node) idx) 484 | ky (keyword (.-name el)) 485 | val (ky value-map)] 486 | (when (contains? value-map ky) 487 | (let [val (if val val "")] 488 | ((set-form-input val) el)))))))) 489 | 490 | 491 | 492 | ;################################################################## 493 | ; hiccup style emitter 494 | ;################################################################## 495 | 496 | 497 | (defn html 498 | "takes clojure data structure and emits a document element" 499 | [node-spec] 500 | (cond 501 | (string? node-spec) (.createTextNode js/document node-spec) 502 | (vector? node-spec) 503 | (let [[tag & [m & ms :as more]] node-spec 504 | [tag-name & segments] (.split (name tag) #"(?=[#.])") 505 | id (some (fn [seg] 506 | (when (= \# (.charAt seg 0)) (subs seg 1))) segments) 507 | classes (keep (fn [seg] 508 | (when (= \. (.charAt seg 0)) (subs seg 1))) 509 | segments) 510 | attrs (if (map? m) m {}) 511 | attrs (if id (assoc attrs :id id) attrs) 512 | attrs (if-not (empty? classes) 513 | (assoc attrs :class (apply str (interpose " " classes))) 514 | attrs) 515 | content (flatten (map html (if (map? m) ms more))) 516 | node (.createElement js/document tag-name)] 517 | (doseq [[key val] attrs] 518 | (if (= 0 (.indexOf (name key) "on")) 519 | (aset node (name key) val) ;;set event handlers 520 | (.setAttribute node (name key) val))) 521 | (when content (domina/append! node content))) 522 | (sequential? node-spec) (flatten (map html node-spec)) 523 | :else (.createTextNode js/document (str node-spec)))) 524 | 525 | 526 | 527 | ;################################################################## 528 | ; data extractors 529 | ;################################################################## 530 | 531 | (defn get-attr 532 | "returns the attribute on the selected element or elements. 533 | in cases where more than one element is selected you will 534 | receive a list of values" 535 | [attr] 536 | (extr-multi-node 537 | (fn[pnod] 538 | (. pnod (getAttribute (name attr)))))) 539 | 540 | (defn get-text 541 | "returns the text value of the selected element or elements. 542 | in cases where more than one element is selected you will 543 | receive a list of values" 544 | [] 545 | (extr-multi-node 546 | (fn[pnod] 547 | (dom/getTextContent pnod)))) 548 | 549 | (defn get-data 550 | "returns the data on a selected node for a given key. If bubble is set will look at parent" 551 | ([ky] (get-data ky false)) 552 | ([ky bubble] 553 | (extr-multi-node 554 | (fn [node] 555 | (domina/get-data node ky bubble))))) 556 | 557 | 558 | (defn get-prop 559 | "returns the property on the selected element or elements. 560 | in cases where more than one element is selected you will 561 | receive a list of values" 562 | [prop] 563 | (extr-multi-node 564 | (fn [pnod] 565 | (aget pnod (name prop))))) 566 | 567 | 568 | (defn- merge-form-val 569 | "this function takes a map, key and value. It will check if 570 | the value exists and create a seq of values if one exits." 571 | [form-map ky val] 572 | (let [mval (form-map ky)] 573 | (if val 574 | (cond 575 | (and (coll? mval) 576 | (coll? val)) (assoc form-map ky (into mval val)) 577 | (coll? mval) (assoc form-map ky (conj mval val)) 578 | mval (assoc form-map ky #{val mval}) 579 | :else (assoc form-map ky val)) 580 | form-map))) 581 | 582 | 583 | (defn read-form-input 584 | "returns the value of a given form input (text,select,checkbox,etc...) If more than one value exists it will return a set of values." 585 | [] 586 | (let [trans (fn [nodes chain] 587 | (let [nod-col (nodes->coll nodes) 588 | result (reduce 589 | #(let [vals (js->clj (form/getValue %2))] 590 | (if (and 591 | (not (string? vals)) 592 | (coll? vals)) 593 | (into %1 vals) 594 | (if vals (conj %1 vals) %1))) 595 | #{} 596 | nod-col)] 597 | (cond 598 | (empty? result) nil 599 | (and (= 1 (count result)) 600 | (not (#{"checkbox" "select-multiple"} 601 | (.-type (first nod-col))))) (first result) 602 | :else result)))] 603 | (reify 604 | ITransform 605 | (apply-transform [_ nodes] (trans nodes nil)) 606 | (apply-transform [_ nodes chain] (trans nodes chain)) 607 | IFn 608 | (-invoke [_ nodes] (trans nodes nil)) 609 | (-invoke [_ nodes chain] (trans nodes chain))))) 610 | 611 | 612 | 613 | (defn read-form 614 | "returns a map of the form values tied to name of input fields. 615 | {:name1 'value1' name2 #{'select1' 'select2'}}" 616 | [] 617 | (extr-multi-node 618 | (fn [node] 619 | (let [inputs (.-elements node)] 620 | (reduce 621 | #(if-not (empty? (.-name (.item inputs %2))) 622 | (merge-form-val %1 623 | (keyword (.-name (.item inputs %2))) 624 | ((read-form-input) (.item inputs %2))) 625 | %1) 626 | {} (range (.-length inputs))))))) 627 | 628 | ;################################################################## 629 | ; filtering - these funcitons are to make up for choosing 630 | ; css3 selectors as our selectors, not everything can 631 | ; be selected with css selectors in all browser so this 632 | ; provides an abstract way to add additional selection 633 | ; criteria 634 | ;################################################################## 635 | 636 | ;registerd filter that can be refrenced by keyword 637 | (def reg-filt (atom {})) 638 | 639 | (defn filter 640 | "filter allows you to apply function to futhur scope down what is returned by a selector" 641 | [tst trans] 642 | (multi-node-chain 643 | (fn filt 644 | ([pnodes] (filt pnodes nil)) 645 | ([pnodes chain] 646 | (let [pnod-col (nodes->coll pnodes) 647 | ttest (if (keyword? tst) (@reg-filt tst) tst) 648 | res (clojure.core/filter ttest pnod-col)] 649 | (if (nil? chain) 650 | (apply-transform trans res) 651 | (apply-transform trans res chain))))))) 652 | 653 | (defn register-filter 654 | "registers a filter for a given keyword" 655 | [ky func] 656 | (swap! reg-filt assoc ky func)) 657 | 658 | (defn selected-options 659 | "takes a list of options and returns the selected ones. " 660 | [pnod] 661 | (.-selected pnod)) 662 | 663 | (defn checked-radio-checkbox 664 | "takes a list of radio or checkboxes and returns the checked ones" 665 | [pnod] 666 | (.-checked pnod)) 667 | 668 | (register-filter :selected selected-options) 669 | (register-filter :checked checked-radio-checkbox) 670 | 671 | (defn match? [selector] 672 | (fn [node] 673 | (cond 674 | (aget node "matches") (.matches node selector) 675 | (aget node "matchesSelector") (.matchesSelector node selector) 676 | (aget node "msMatchesSelector") (.msMatchesSelector node selector) 677 | (aget node "mozMatchesSelector") (.mozMatchesSelector node selector) 678 | (aget node "webkitMatchesSelector") (.webkitMatchesSelector node selector) 679 | :else (some #{node} (nodes->coll (select node)))))) 680 | 681 | 682 | ;################################################################## 683 | ; functions involved in processing the selectors 684 | ;################################################################## 685 | 686 | (defn- create-sel-str 687 | "converts keywords, symbols and strings used in the enlive selector 688 | syntax to a string representing a standard css selector. It also 689 | applys id masking if mask provided" 690 | ([css-sel] (create-sel-str "" css-sel)) 691 | ([id-mask-sym css-sel] 692 | (apply str (map #(cond 693 | (symbol? %) (css-syms %) 694 | (keyword? %) (str " " (. (name %) (replace "#" (str "#" id-mask-sym)))) 695 | (vector? %) (create-sel-str %) 696 | (string? %) (.replace % "#" (str "#" id-mask-sym))) 697 | css-sel)))) 698 | 699 | (defn css-select 700 | "takes either an enlive selector or a css3 selector and returns a set of nodes that match the selector" 701 | ([css-sel] (css-select "" js/document css-sel)) 702 | ([dom-node css-sel] (css-select "" dom-node css-sel)) 703 | ([id-mask-sym dom-node css-sel] 704 | (let [sel (string/trim (en/convert (create-sel-str id-mask-sym css-sel))) 705 | ret (dcss/sel dom-node sel)] 706 | ret))) 707 | 708 | ;############################################### 709 | ; Core functions and supporting fuctions for 710 | ; for "at" and "from" 711 | ;############################################### 712 | 713 | (defn nil-t [func] 714 | (or func remove-node)) 715 | 716 | (defn i-at [id-mask node & trans] 717 | (let [cnt (count trans) 718 | sel? (and (not (nil? node)) (satisfies? ISelector node))] 719 | (if (and (not sel?) (= 1 cnt)) 720 | (apply-transform (first trans) node) 721 | (let [[node trans] (if sel? 722 | (list js/document (conj trans node)) 723 | (list node trans))] 724 | (doseq [[sel t] (partition 2 trans)] 725 | (apply-transform (nil-t t) (select sel node id-mask))))))) 726 | 727 | 728 | (defn at [node & trans] 729 | (apply i-at "" node trans)) 730 | 731 | 732 | (defn from [node & trans] 733 | (let [cnt (count trans) 734 | sel? (satisfies? ISelector node)] 735 | (cond 736 | (and sel? (= 1 cnt)) (apply-transform (first trans) (select node)) 737 | (= 1 cnt) (apply-transform (first trans) node) 738 | :else (let [[node trans] (if sel? 739 | (list js/document (conj trans node)) 740 | (list node trans))] 741 | (apply hash-map 742 | (mapcat (fn [[ky sel ext]] 743 | [ky (apply-transform ext (select sel node ""))]) 744 | (partition 3 trans))))))) 745 | 746 | 747 | 748 | ;########################################## 749 | ; XPATH - allow (xpath "@id 750 | ;########################################## 751 | (defn xpath [path] 752 | (fn [root id-mask] 753 | (if (empty? id-mask) 754 | (xpath/xpath root path) 755 | (let [tmp (.replace path "@ID='" (str "@ID='" id-mask)) 756 | mpath (.replace path "@id='" (str "@id='" id-mask))] 757 | (xpath/xpath root mpath))))) 758 | 759 | 760 | ;######################################### 761 | ; Special Selectors 762 | ;######################################### 763 | (defn this-node [root id-mask] root) 764 | 765 | 766 | ;;domina extentions to work with enfocus 767 | (if (dm/defined? js/Text) 768 | (extend-protocol domina/DomContent 769 | js/Text 770 | (nodes [content] [content]) 771 | (single-node [content] content))) 772 | 773 | 774 | (extend-protocol ISelector 775 | function 776 | (select 777 | ([this] (select this js/document "")) 778 | ([this root] (select this root "")) 779 | ([this root id-mask] (this root id-mask))) 780 | PersistentVector 781 | (select 782 | ([this] (select this js/document "")) 783 | ([this root] (select this root "")) 784 | ([this root id-mask] (css-select id-mask root this))) 785 | string 786 | (select 787 | ([this] (select this js/document "")) 788 | ([this root] (select this root "")) 789 | ([this root id-mask] (css-select id-mask root [this]))) 790 | nil 791 | (select 792 | ([this] '()) 793 | ([this root] '()) 794 | ([this root id-mask] '()))) 795 | 796 | 797 | (extend-protocol ITransform 798 | function 799 | (apply-transform 800 | ([trans nodes] (doall (map trans (nodes->coll nodes)))) 801 | ([trans nodes chain] 802 | (let [pnod-col (nodes->coll nodes) 803 | val (doall (map trans pnod-col))] 804 | (if chain 805 | (apply-transform chain nodes) 806 | val)))) 807 | nil 808 | (apply-transform 809 | ([trans nodes] nodes) 810 | ([trans nodes chain] nodes))) 811 | -------------------------------------------------------------------------------- /src/cljs/enfocus/effects.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.effects 2 | (:require [enfocus.core :as ef] 3 | [goog.fx :as fx] 4 | [goog.fx.dom :as fx-dom] 5 | [goog.dom.query :as query] 6 | [goog.events :as events] 7 | [goog.style :as style])) 8 | 9 | (declare apply-transform) 10 | 11 | (defn chainable-effect 12 | "wrapper function for effects, maps the effect to all nodes returned by the 13 | selector and provides chaining and callback functionality" 14 | [func callback] 15 | (let [trans (fn [pnodes chain] 16 | (let [pnod-col (ef/nodes->coll pnodes) 17 | cnt (atom (count pnod-col)) 18 | partial-cback (fn [] 19 | (swap! cnt dec) 20 | (when (= 0 @cnt) 21 | (when callback (ef/apply-transform callback pnodes)) 22 | (when chain (ef/apply-transform chain pnodes))))] 23 | (doseq [pnod pnod-col] (func pnod partial-cback))))] 24 | (reify ef/ITransform 25 | (apply-transform [_ nodes] (trans nodes nil)) 26 | (apply-transform [_ nodes chain] (trans nodes chain))))) 27 | 28 | 29 | ;#################################################### 30 | ; effect based transforms 31 | ;#################################################### 32 | 33 | (defn chain 34 | "chains a series of effects and trasforms in sequences" 35 | [func & chains] 36 | (if (empty? chains) 37 | (fn [pnod] (ef/apply-transform func pnod)) 38 | (fn [pnod] (ef/apply-transform func pnod (apply chain chains))))) 39 | 40 | 41 | 42 | (defn fade-out 43 | "fade the selected nodes over a set of steps" 44 | ([ttime] (fade-out ttime nil nil)) 45 | ([ttime callback] (fade-out ttime callback nil)) 46 | ([ttime callback accel] 47 | (chainable-effect 48 | (fn [pnod pcallback] 49 | (let [anim (fx-dom/FadeOut. pnod ttime accel)] 50 | (when pcallback 51 | (events/listen anim js/goog.fx.Animation.EventType.END pcallback)) 52 | (. anim (play)))) 53 | callback))) 54 | 55 | (defn fade-in 56 | "fade the selected nodes over a set of steps" 57 | ([ttime] (fade-in ttime nil nil)) 58 | ([ttime callback] (fade-in ttime callback nil)) 59 | ([ttime callback accel] 60 | (chainable-effect 61 | (fn [pnod pcallback] 62 | (let [anim (fx-dom/FadeIn. pnod ttime accel)] 63 | (when pcallback 64 | (events/listen anim js/goog.fx.Animation.EventType.END pcallback)) 65 | (. anim (play)))) 66 | callback))) 67 | 68 | (defn resize 69 | "resizes the selected elements to a width and height in px optional time series data" 70 | ([wth hgt] (resize wth hgt 0 nil nil)) 71 | ([wth hgt ttime] (resize wth hgt ttime nil nil)) 72 | ([wth hgt ttime callback] (resize wth hgt ttime callback nil)) 73 | ([wth hgt ttime callback accel] 74 | (chainable-effect 75 | (fn [pnod pcallback] 76 | (let [csize (style/getContentBoxSize pnod) 77 | start (array (.-width csize) (.-height csize)) 78 | wth (if (= :curwidth wth) (.-width csize) wth) 79 | hgt (if (= :curheight hgt) (.-height csize) hgt) 80 | end (array wth hgt) 81 | anim (fx-dom/Resize. pnod start end ttime accel)] 82 | (when pcallback 83 | (events/listen anim js/goog.fx.Animation.EventType.END pcallback)) 84 | (. anim (play)))) 85 | callback))) 86 | 87 | (defn move 88 | "moves the selected elements to a x and y in px optional time series data " 89 | ([xpos ypos] (move xpos ypos 0 nil nil)) 90 | ([xpos ypos ttime] (move xpos ypos ttime nil nil)) 91 | ([xpos ypos ttime callback] (move xpos ypos ttime callback nil)) 92 | ([xpos ypos ttime callback accel] 93 | (chainable-effect 94 | (fn [pnod pcallback] 95 | (let [cpos (style/getPosition pnod) 96 | start (array (.-x cpos) (.-y cpos)) 97 | xpos (if (= :curx xpos) (.-x cpos) xpos) 98 | ypos (if (= :cury ypos) (.-y cpos) ypos) 99 | end (array xpos ypos) 100 | anim (fx-dom/Slide. pnod start end ttime accel)] 101 | (when pcallback 102 | (events/listen anim js/goog.fx.Animation.EventType.END pcallback)) 103 | (. anim (play)))) 104 | callback))) 105 | 106 | (defn scroll 107 | "scrolls selected elements to a x and y in px optional time series data" 108 | ([xpos ypos] (scroll xpos ypos 0 nil nil)) 109 | ([xpos ypos ttime] (scroll xpos ypos ttime nil nil)) 110 | ([xpos ypos ttime callback] (scroll xpos ypos ttime callback nil)) 111 | ([xpos ypos ttime callback accel] 112 | (chainable-effect 113 | (fn [pnod pcallback] 114 | (let [start (array (.-scrollLeft pnod) (.-scrollTop pnod)) 115 | xpos (if (= :curx xpos) (.-scrollLeft pnod) xpos) 116 | ypos (if (= :cury ypos) (.-scrollTop pnod) ypos) 117 | end (array xpos ypos) 118 | anim (fx-dom/Scroll. pnod start end ttime accel)] 119 | (when pcallback 120 | (events/listen anim js/goog.fx.Animation.EventType.END pcallback)) 121 | (. anim (play)))) 122 | callback))) 123 | 124 | 125 | ;############################################# 126 | ; basic accelerators/easing functions 127 | ;############################################# 128 | 129 | (defn liner [t] t) 130 | (defn ease-in-quad [t] (* t t)) 131 | (defn ease-out-quad [t] (* -1 (* t (- t 2)))) 132 | (defn ease-in-out-quad [t] 133 | (let [nt (* t 2)] 134 | (if (< nt 1) 135 | (* .5 nt nt) 136 | (* -0.5 (- (* (- nt 1) (- nt 2)) 1))))) 137 | (defn ease-in-cubic [t] (* t t t)) 138 | (defn ease-out-cubic [t] 139 | (let [nt (- t 1)] 140 | (+ (* nt nt nt) 1))) 141 | (defn ease-in-out-cubic [t] 142 | (let [nt (* t 2)] 143 | (if (< nt 1) 144 | (* .5 nt nt nt) 145 | (let [mt (- nt 2)] 146 | (* .5 (+ 2 (* mt mt mt))))))) 147 | (defn ease-in-quart [t] (* t t t t)) 148 | (defn ease-out-quart [t] 149 | (let [nt (- t 1)] 150 | (* -1 (- (* nt nt nt nt) 1)))) 151 | (defn ease-in-out-quart [t] 152 | (let [nt (* t 2)] 153 | (if (< nt 1) 154 | (* .5 nt nt nt nt) 155 | (let [mt (- nt 2)] 156 | (* .5 (+ 2 (* mt mt mt mt))))))) 157 | (defn ease-in-quint [t] (* t t t t)) 158 | (defn ease-out-quint [t] 159 | (let [nt (- t 1)] 160 | (+ (* nt nt nt nt) 1))) 161 | (defn ease-in-out-quint [t] 162 | (let [nt (* t 2)] 163 | (if (< nt 1) 164 | (* .5 nt nt nt nt nt) 165 | (let [mt (- nt 2)] 166 | (* .5 (+ 2 (* mt mt mt mt mt))))))) 167 | (defn sign-in [t] 168 | (+ (* -1 (.cos js/Math (* 0.5 (.-PI js/Math) t))) 1)) 169 | (defn sign-out [t] 170 | (.sin js/Math (* t (.-PI js/Math) 0.5))) 171 | (defn sign-in-out [t] 172 | (* -0.5 (- (.cos js/Math (* (.-PI js/Math) t)) 1))) 173 | (defn expo-in [t] 174 | (if (= t 0) 0 (.pow js/Math 2 (* 10 (- t 1))))) 175 | (defn expo-out [t] 176 | (if (= t 0) 1 (+ (* -1 (.pow js/Math 2 (* -10 t))) 1))) 177 | (defn expo-in-out [t] 178 | (cond 179 | (= t 0) 0 180 | (= t 1) 1 181 | (< t 1) (* 0.5 (.pow js/Math 2 (* 10 (- t 1)))) 182 | :else (* 0.5 (+ (* -1 (.pow js/Math 2 (* -10 (dec t)))) 2)))) 183 | (defn circular-in [t] 184 | (* -1 (- (.sqrt js/Math (- 1 (.pow js/Math t 2))) 1))) 185 | (defn circular-out [t] 186 | (let [nt (- t 1)] 187 | (.sqrt js/Math (- 1 (.pow js/Math nt 2))))) 188 | (defn circular-in-out [t] 189 | (let [nt (* t 2)] 190 | (if (< t 1) 191 | (* -0.5 (- (.sqrt js/Math (- 1 (.pow js/Math nt 2))) 1)) 192 | (let [mt (- nt 2)] 193 | (* -0.5 (+ (.sqrt js/Math (- 1 (.pow js/Math nt 2))) 1)))))) 194 | -------------------------------------------------------------------------------- /src/cljs/enfocus/events.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.events 2 | (:require [goog.events :as events] 3 | [goog.dom :as dom] 4 | [enfocus.core :as ef] 5 | [goog.object :as obj])) 6 | 7 | (declare child-of? mouse-enter-leave) 8 | 9 | ;#################################################### 10 | ; event based transforms 11 | ;#################################################### 12 | 13 | (def view-port-monitor (atom nil)) 14 | 15 | (defn get-vp-monitor 16 | "needed to support window :resize" 17 | [] 18 | (if @view-port-monitor @view-port-monitor 19 | (do 20 | (swap! view-port-monitor #(new goog.dom.ViewportSizeMonitor)) 21 | @view-port-monitor))) 22 | 23 | 24 | (defn gen-enter-leave-wrapper [event] 25 | (let [obj (new js/Object)] 26 | (set! (.-listen obj) 27 | (fn [elm func opt-cap opt-scope opt-handler] 28 | (let [callback (mouse-enter-leave func)] 29 | (set! (.-listen callback) func) 30 | (set! (.-scope callback) opt-scope) 31 | (if opt-handler 32 | (.listen opt-handler elm (name event) callback) 33 | (events/listen elm (name event) callback))))) 34 | (set! (.-unlisten obj) 35 | (fn [elm func opt-cap opt-scope opt-handler] 36 | (let [listeners (events/getListeners elm (name event) false)] 37 | (doseq [obj listeners] 38 | (let[listener (.-listener obj)] 39 | (when (and (or (not func) (= (.-listen listener) func)) 40 | (or (not opt-scope) (= (.-scope listener) opt-scope))) 41 | (if opt-handler 42 | (.unlisten opt-handler elm (name event) listener) 43 | (events/unlisten elm (name event) listener))))) listeners))) 44 | obj)) 45 | 46 | (def wrapper-register {:mouseenter (gen-enter-leave-wrapper :mouseover) 47 | :mouseleave (gen-enter-leave-wrapper :mouseout)}) 48 | 49 | 50 | (defn listen 51 | "adding an event to the selected nodes" 52 | [event func] 53 | (let [wrapper (wrapper-register event)] 54 | (fn [pnod] 55 | (if (and (= :resize event) (identical? js/window pnod)) 56 | (events/listen (get-vp-monitor) "resize" func) 57 | (if (nil? wrapper) 58 | (events/listen pnod (name event) func) 59 | (events/listenWithWrapper pnod wrapper func)))))) 60 | 61 | (defn remove-listeners 62 | "removing all listeners of a given event type from the selected nodes" 63 | [& event-list] 64 | (let [get-name #(name (cond 65 | (= % :mouseenter) :mouseover 66 | (= % :mouseleave) :mouseout 67 | :else %))] 68 | (fn [pnod] 69 | (doseq [ev event-list] (events/removeAll pnod (get-name ev)))))) 70 | 71 | 72 | (defn unlisten 73 | "removing a specific event from the selected nodes" 74 | ([event] (remove-listeners event)) 75 | ([event func] 76 | (let [wrapper (wrapper-register event)] 77 | (fn [pnod] 78 | (if (nil? wrapper) 79 | (events/unlisten pnod (name event) func) 80 | (events/unlistenWithWrapper pnod wrapper func)))))) 81 | 82 | 83 | (defn- get-node-chain [top node] 84 | (if (or (nil? node) (= node top)) 85 | () 86 | (conj (get-node-chain top (.-parentNode node)) node))) 87 | 88 | (defn- create-event [cur cur-event] 89 | (let [event (obj/clone cur-event)] 90 | (set! (.-currentTarget event) cur) 91 | event)) 92 | 93 | (defn listen-live [event selector func] 94 | (fn [node] 95 | (ef/at node 96 | (listen event 97 | #(doseq [el (get-node-chain node (.-target %))] 98 | (ef/at el 99 | (ef/filter (ef/match? selector) 100 | (fn [node] 101 | (let [event-copy (create-event el %)] 102 | (func event-copy) 103 | (when (.-defaultPrevented event-copy) 104 | (.preventDefault %)) 105 | (when (.-propagationStopped event-copy) 106 | (.stopPropagation %))))))))))) 107 | 108 | 109 | ;################################################### 110 | ; utilies 111 | ;################################################### 112 | 113 | 114 | (defn child-of? 115 | "returns true if the node(child) is a child of parent" 116 | [parent child] 117 | (cond 118 | (not child) false 119 | (identical? parent child) false 120 | (identical? (.-parentNode child) parent) true 121 | :else (recur parent (.-parentNode child)))) 122 | 123 | 124 | (defn mouse-enter-leave 125 | "this is used to build cross browser versions of :mouseenter and :mouseleave events" 126 | [func] 127 | (fn [e] 128 | (let [re (.-relatedTarget e) 129 | this (.-currentTarget e)] 130 | (when (and 131 | (not (identical? re this)) 132 | (not (child-of? this re))) 133 | (func e))))) 134 | -------------------------------------------------------------------------------- /temp/enfocus/testing.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.testing 2 | (:use [enfocus.enlive.syntax :only [attr=]]) 3 | (:require [enfocus.core :as ef] 4 | [enfocus.effects :as effects] 5 | [enfocus.events :as events]) 6 | (:require-macros [enfocus.macros :as em])) 7 | 8 | 9 | 10 | (defn test-from [] 11 | (let [form-vals (ef/from js/document 12 | :test1 ["#ftest1"] (ef/get-prop :value) 13 | :test2 ["#ftest2"] (ef/get-prop :value) 14 | :test3 ["#from-form > input[type=checkbox]"] (ef/filter #(.-checked %) 15 | (ef/get-prop :value)) 16 | :test4 ["#from-form option"] (ef/filter :selected 17 | (ef/get-prop :value))) 18 | form-read (ef/from "#from-form" (ef/read-form)) 19 | from-val (ef/from "#ftest1" (ef/get-attr :value))] 20 | (ef/at "#test-from-div" (ef/do-> (ef/content (pr-str form-vals) 21 | (ef/html [:br]) 22 | (pr-str form-read) 23 | (ef/html [:br])) 24 | (ef/append from-val))))) 25 | 26 | (defn test-get-text [] 27 | (let [from-text (ef/from (ef/select ["#ftext-test"]) (ef/get-text))] 28 | (ef/log-debug (str "from-text:" from-text)) 29 | (ef/at js/document 30 | ["#ftext-test-result"] (ef/content from-text)))) 31 | 32 | 33 | 34 | (defn test-callback [pnods] 35 | (ef/at pnods (ef/do-> 36 | (ef/content "callback successful") 37 | (ef/set-style :color "#fff")))) 38 | 39 | (em/defsnippet snippet2 "templates/template1.html" ["tbody > *:first-child"] 40 | [fruit quantity] 41 | ["tr > *:first-child"] (ef/content fruit) 42 | ["tr > *:last-child"] (ef/content (str quantity))) 43 | 44 | (em/deftemplate template2 "templates/template1.html" [fruit-data] 45 | ["#heading1"] (ef/content "fruit") 46 | ["thead tr > *:last-child"] (ef/content "quantity") 47 | ["tbody"] (ef/content 48 | (map #(snippet2 % (fruit-data %)) (keys fruit-data)))) 49 | 50 | (em/deftemplate template3 :compiled "../testing/resources/public/templates/template1.html" [fruit-data] 51 | ["#heading1"] (ef/content "fruit") 52 | ["thead tr > *:last-child"] (ef/content "quantity") 53 | ["tbody > tr:not(:first-child)"] (ef/remove-node) 54 | ["tbody"] #(ef/at % ["tr:first-of-type"] 55 | (em/clone-for [[f q] (vec fruit-data)] 56 | ["*:first-child"] (ef/content f) 57 | ["*:last-child"] (ef/content (str q))))) 58 | 59 | 60 | (em/defsnippet success :compiled "enfocus/html/test-grid.html" 61 | ["tbody > *:first-child > td span"] []) 62 | 63 | (em/defsnippet row "templates/test-grid.html" ["tbody > *:first-child"] 64 | [test-desc value] 65 | ["tr > *:first-child"] (ef/content test-desc) 66 | ["tr > *:last-child > span"] (ef/content value)) 67 | 68 | 69 | (em/deftemplate test-cases "templates/test-grid.html" [] 70 | ["#test3 > *:last-child"] (ef/content (success)) 71 | ["#test4 > *:last-child"] (ef/content (success)) 72 | ["#test5 > *:last-child"] (ef/html-content "success") 73 | ["#test6 > *:last-child"] (ef/set-attr :test6 "cool") 74 | ["td[test6='cool']"] (ef/content (success)) 75 | ;[[:td (attr= :test6 "cool")]] (ef/content (success)) 76 | ["#test7"] (ef/remove-attr :foo) 77 | ["#test7 > *:last-child"] (ef/content (success)) 78 | ["tr[foo]"] (ef/html-content "fail") ;should do nothing 79 | ["#test8 > *:last-child"] (ef/add-class "test8") 80 | [".test8"] (ef/content (success)) 81 | ["#test9"] (ef/remove-class "bad") 82 | ["#test9 > *:last-child"] (ef/content (success)) 83 | [".bad > *:last-child"] (ef/html-content "fail") ;should do nothing 84 | ["#test10 td span"] (ef/do-> 85 | (ef/after (success)) 86 | (ef/remove-node)) 87 | ["#test11 td span"] (ef/do-> 88 | (ef/before (success)) 89 | (ef/remove-node)) 90 | ["#test12 td span"] (ef/substitute(success)) 91 | ["#test13 > *:last-child"] (ef/do-> 92 | (ef/content "a:") 93 | (ef/append (success))) 94 | ["#test14 > *:last-child"] (ef/do-> 95 | (ef/content ":p") 96 | (ef/prepend (success))) 97 | ["#wrap-span"] (ef/wrap :span {:class "success"}) 98 | ["#test15 > *:last-child > .success > span"] (ef/content "success") 99 | ["#wrapper"] (ef/unwrap) 100 | (ef/xpath "//tr[@id='test17']/td[2]") (ef/content (success)) 101 | ["#test18 > *:last-child"] #(ef/at % ef/this-node (ef/content (success))) 102 | "#test19 > *:last-child" (ef/content (success))) 103 | 104 | (defn fade-in [event] 105 | (ef/at (.-currentTarget event) (effects/fade-in 500))) 106 | 107 | (defn fade-out [event] 108 | (ef/at (.-currentTarget event) (effects/fade-out 500))) 109 | 110 | 111 | (em/defaction test-grid [] 112 | ["#test-content"] (ef/content (test-cases)) 113 | ["#test-content tbody tr:nth-of-type(even)"] (ef/add-class "even") 114 | ["body"] (events/listen-live 115 | :mouseover 116 | "#test-content tbody tr" 117 | #(ef/at (.-currentTarget %) (ef/add-class "highlight"))) 118 | ["body"] (events/listen-live 119 | :mouseout 120 | "#test-content tbody tr" 121 | #(ef/at (.-currentTarget %) (ef/remove-class "highlight"))) 122 | ["#test-content2"] (ef/content (template2 {"banana" 5 "pineapple" 10 "apple" 5})) 123 | ["#heading1"] (ef/set-attr :id "new-heading1") 124 | ["#heading2"] (ef/set-attr :id "new-heading2") 125 | ["#test-content2 tfoot tr > *:last-child"] (ef/content (str 20)) 126 | ["#test-content3"] (ef/content (template3 {"pear" 22 "banana" 12 "apple" 6})) 127 | ["#test-content4"] (ef/set-style :background "#00dd00" :font-size "10px") 128 | ["#test-content5"] (ef/set-style :background "#dd0000" :font-size "10px") 129 | ["#test-content5"] (ef/remove-style :background :font-size) 130 | ["#test-content6"] (events/listen :mouseover fade-out) 131 | ["#test-content6"] (events/listen :mouseout fade-in) 132 | ["#test-remove-listeners"] (events/listen 133 | :click 134 | #(ef/at js/document 135 | ["#test-content6"] (events/remove-listeners :mouseover :mouseout))) 136 | ["#test-content6_5"] (events/listen :mouseenter fade-out) 137 | ["#test-content6_5"] (events/listen :mouseleave fade-in) 138 | ["#test-unlisten"] (events/listen 139 | :click 140 | #(ef/at js/document 141 | ["#test-content6_5"] (ef/do-> 142 | (events/unlisten :mouseenter fade-out) 143 | (events/unlisten :mouseleave fade-in)))) 144 | ["#click"] (events/listen 145 | :click 146 | #(ef/at js/document 147 | ["#sz-tst"] (effects/chain 148 | (effects/resize 2 30 500) 149 | (effects/resize 200 30 500 test-callback)))) 150 | ["#delay-click"] (events/listen 151 | :click 152 | #(ef/at js/document 153 | ["#dly-tst"] (effects/chain 154 | (effects/resize 2 30 500) 155 | (ef/delay 2000 (effects/resize 200 30 500))))) 156 | ["#mclick"] (events/listen 157 | :click 158 | #(ef/at js/document 159 | ["#mv-tst"] (effects/move 300 305 500 160 | (effects/move 0 0 500)))) 161 | ["#ftest2"] (ef/focus) 162 | ["#test-from"] (events/listen :click test-from) 163 | ["#test-get-text"] (events/listen :click test-get-text) 164 | ["#cb1"] (ef/set-prop :checked true) 165 | ["#test-content tbody"] (ef/append (ef/html [:tr#test20.even '([:td "hiccup emmiter"] [:td.success "success"])])) 166 | ["#test-content tbody"] (ef/append (ef/html [:tr#test21.odd '([:td "replace-vars"] [:td {:class "${val}"} "${val}"])])) 167 | ["#test21"] (ef/replace-vars {:val "success"})) 168 | 169 | ;(ef/defaction test-suite []) 170 | 171 | 172 | 173 | (defn funtimes [msg] 174 | (ef/at js/window (events/listen :resize #(ef/log-debug (str "you resized your window:" %)))) 175 | (ef/at js/document 176 | [:.heading (attr= :foo "true")] (ef/content msg)) 177 | (em/wait-for-load (test-grid))) 178 | 179 | (set! (.-onload js/window) #(funtimes "THIS IS A TEST")) 180 | -------------------------------------------------------------------------------- /temp/testing/project.clj: -------------------------------------------------------------------------------- 1 | (defproject enfocus "0.1.0-SNAPSHOT" 2 | :description "DOM manipulation tool for clojurescript inspired by Enlive" 3 | :dependencies [[ring "1.0.0-RC1"]] 4 | :dev-dependencies [[lein-eclipse "1.0.0"]]) 5 | -------------------------------------------------------------------------------- /temp/testing/resources/public/css/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #555555; 3 | font-size: 14px; 4 | font-family: Verdana, Arial, Helvetica, SunSans-Regular, Sans-Serif; 5 | color:#564b47; 6 | margin: 20px 140px 20px 140px; 7 | text-align: center; 8 | } 9 | 10 | #content { 11 | text-align: left; 12 | background-color: #fff; 13 | overflow: auto; 14 | padding: 10px; 15 | } 16 | 17 | 18 | .heading { 19 | font-size: 18px; 20 | border-bottom: 1px solid #000; 21 | margin-bottom: 10px; 22 | } 23 | 24 | #test-content { 25 | width: 400px; 26 | } 27 | #test-content table { 28 | width: 100%; 29 | } 30 | 31 | th{ 32 | background: #cfcfcf; 33 | } 34 | 35 | .even { 36 | background: #efefef; 37 | } 38 | 39 | .success { 40 | font-weight: bold; 41 | color: #009900; 42 | } 43 | 44 | .fail { 45 | font-weight: bold; 46 | color: #dd0000; 47 | } 48 | 49 | #test-content2 { 50 | width: 200px; 51 | margin-top: 20px; 52 | } 53 | 54 | #test-content2 table { 55 | width: 100%; 56 | } 57 | 58 | #test-content3 { 59 | width: 200px; 60 | margin-top: 20px; 61 | } 62 | 63 | #test-content3 table { 64 | width: 100%; 65 | } 66 | 67 | #test-content4 { 68 | width: 200px; 69 | margin-top: 20px; 70 | } 71 | 72 | #test-content5 { 73 | width: 200px; 74 | margin-top: 20px; 75 | } 76 | 77 | .highlight { 78 | background-color: #00cc00; 79 | } 80 | 81 | #test-content6 { 82 | background-color: #009900; 83 | font-size: 18px; 84 | color: white; 85 | height: 50px; 86 | width: 200px; 87 | margin-top: 20px; 88 | } 89 | 90 | #test-content6_5 { 91 | background-color: #009900; 92 | font-size: 12px; 93 | color: white; 94 | height: 75px; 95 | width: 200px; 96 | margin-top: 20px; 97 | } 98 | 99 | #sz-tst { 100 | width: 10px; 101 | height: 10px; 102 | overflow: hidden; 103 | background-color: #009900; 104 | } 105 | 106 | #dly-tst { 107 | width: 10px; 108 | height: 10px; 109 | overflow: hidden; 110 | background-color: #009900; 111 | } 112 | 113 | #mv-tst { 114 | position:absolute; 115 | width:10px; 116 | height: 10px; 117 | top: 0px; 118 | left: 0px; 119 | background-color: #009900; 120 | } 121 | 122 | #test15 > td:last-child { 123 | color: #dd0000; 124 | font-weight: bold; 125 | } 126 | -------------------------------------------------------------------------------- /temp/testing/resources/public/templates/template1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
heading1heading2
bannan10
apple5
pear2
sum:10
34 | 35 | 36 | -------------------------------------------------------------------------------- /temp/testing/resources/public/templates/test-grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test grid for enfocus 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
TestResult
loading a remote templatesuccess
Adding a template to live domsuccess
Bring in a content with a snippitfail
Replacing content using contentfail
Replacing content using html-contentfail
Setting attributefail
Remove attribute
Add class
Remove class
do-> remove and afterfail
do-> remove and beforefail
substitutefail
do-> content appendfail
do-> content prependfail
wrap transformationfail
unwrap transformation 80 | 81 | success 82 | if green 83 | 84 |
xpath selectorfail
this-node selectorfail
string selectorfail
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /temp/testing/resources/public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Insert title here 10 | 11 | 12 |
13 |
this should change
14 | 15 | 16 | 20 | 22 | 23 | 24 | 26 | 35 | 36 |
17 |

18 | The tests will show success or fail 19 |

21 |

The tests are hard to show in pass or fail terms

25 |
27 |
28 |
29 |
testing setting style
30 |
testing removing style
31 |
testing fading
32 | 33 |

test fading

with :mouseenter and :mouseleave
34 |
37 | 38 |
this is testing resize
39 | 40 |
this is testing delay
41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | one 50 | two 51 | three
52 | 58 | 59 | 60 |
61 | 62 |
63 |
64 |
65 |
This text should show up below the button when you click it.
66 | 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /temp/testing/src/enfocus/ring.clj: -------------------------------------------------------------------------------- 1 | (ns enfocus.ring 2 | (:use ring.middleware.file 3 | ring.handler.dump)) 4 | 5 | 6 | (def app 7 | (wrap-file handle-dump "resources/public")) 8 | 9 | -------------------------------------------------------------------------------- /test/cljs/enfocus/bind_test.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.bind-test 2 | (:require 3 | [enfocus.core :as ef :refer [at from content get-text html 4 | set-form-input read-form-input do-> 5 | set-prop read-form set-form set-attr]] 6 | [enfocus.bind :as bind :refer [bind-view key-or-props save-form-to-atm 7 | bind-input bind-form mapping-to-lens]] 8 | [domina.events :as de :refer [dispatch!]] 9 | [fresnel.lenses :as seg :refer [Lens -fetch -putback fetch putback]] 10 | [cemerick.cljs.test :as t]) 11 | (:require-macros 12 | [enfocus.macros :as em] 13 | [fresnel.lenses :as lens :refer [deflens]] 14 | [cemerick.cljs.test :refer (is are deftest testing use-fixtures)])) 15 | 16 | (defn each-fixture [f] 17 | ;; initialize the environment 18 | (let [div (.createElement js/document "div")] 19 | (.setAttribute div "id" "test-id") 20 | (.setAttribute div "class" "test-class") 21 | (.appendChild (.-body js/document) div) 22 | ;; execute the unit test 23 | (f) 24 | ;; clear the environment 25 | (.removeChild (.-body js/document) div))) 26 | 27 | (use-fixtures :each each-fixture) 28 | 29 | (defn by-id [id] (.getElementById js/document id)) 30 | 31 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 32 | ;; HELPER FUNC TESTS 33 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 34 | 35 | 36 | (deftest key-or-prop-test 37 | (testing "getting the keys from an obj or map" 38 | (is (= [:a :b] (key-or-props {:a 2 :b 3}))) 39 | (is (= ["a" "b"] (key-or-props (js-obj "a" 2 "b" 3)))))) 40 | 41 | 42 | 43 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 44 | ;; MAIN TESTS 45 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 46 | (deftest bind-view-test 47 | (testing "binding a view to an atom" 48 | (let [atm (atom "initial")] 49 | (at "#test-id" (bind-view atm #(at %1 (content %2)))) 50 | (testing "initial value set" 51 | (is (= "initial" (from "#test-id" (get-text))))) 52 | (testing "updated value set" 53 | (reset! atm "updated") 54 | (is (= "updated" (from "#test-id" (get-text)))))) 55 | (testing "complex map with mapping" 56 | (let [atm (atom {:a {:b {:bb 1}} :c 2}) 57 | count (atom 0)] 58 | (at "#test-id" (content (html [:input {:value "_"}])) 59 | "input" (bind-view atm 60 | #(do 61 | (at %1 (set-form-input (:bb %2))) 62 | (swap! count inc)) 63 | [:a :b])) 64 | ;initial population of view 65 | (is (= @count 1)) 66 | (is (= "1" (from "input" (read-form-input)))) 67 | ;update atom non mapped val 68 | (swap! atm assoc :c 3) 69 | (is (= @count 1)) 70 | ;update atom mapped val 71 | (swap! atm assoc-in [:a :b] {:bb 2}) 72 | (is (= @count 2)) 73 | (is (= "2" (from "input" (read-form-input)))))) 74 | (testing "complex obj with mapping" 75 | (let [atm (atom #js {:a #js {:b #js {:bb 1}} :c 2}) 76 | count (atom 0)] 77 | (at "#test-id" (content (html [:input {:value "_"}])) 78 | "input" (bind-view atm 79 | #(do 80 | (at %1 (set-form-input (aget %2 "bb"))) 81 | (swap! count inc)) 82 | [:a :b])) 83 | ;;initial population of view 84 | (is (= @count 1)) 85 | (is (= "1" (from "input" (read-form-input)))) 86 | ;;update atom non mapped val 87 | (swap! atm #(do (aset % "c" 3) %)) 88 | (is (= @count 2)) ;should increment for obj 89 | ;;update atom mapped val 90 | (swap! atm #(do (aset % "a" "b" #js {:bb 2}) %)) 91 | (is (= @count 3)) 92 | (is (= "2" (from "input" (read-form-input)))))))) 93 | 94 | 95 | (deftest bind-input-test 96 | (let [input-frag (html 97 | [:div 98 | [:input {:type "text" :name "a" :value "_"}] 99 | [:textarea {:name "b"} "_"] 100 | [:input {:name "c" :type "checkbox" :value "c1"}] 101 | [:input {:name "c" :type "checkbox" :value "c2"}] 102 | [:input {:name "c" :type "checkbox" :value "c3"}] 103 | [:select {:name "d"} 104 | [:option {:value "d1"}] 105 | [:option {:value "d2"}] 106 | [:option {:value "d3"}] 107 | [:option {:value "d4"}]]])] 108 | (at "#test-id" (content input-frag)) 109 | (testing "binding the value of atm to a form input field" 110 | (testing "binding a simple value to a text field :to-way" 111 | (let [atm (atom "a")] 112 | (at "input[name='a']" (bind-input atm {:event :change})) 113 | (is (= "a" (from "input[name='a']" (read-form-input)))) 114 | (reset! atm "b") 115 | (is (= "b" (from "input[name='a']" (read-form-input)))) 116 | (at "input[name='a']" (do-> (set-form-input "c") 117 | #(dispatch! % :change 118 | {:currentTarget %}))) 119 | (is (= "c" @atm)))) 120 | (testing "binding a simple value to a textarea field :to-way" 121 | (let [atm (atom "a")] 122 | (at "textarea" (bind-input atm {:event :change})) 123 | (is (= "a" (from "textarea" (read-form-input)))) 124 | (reset! atm "b") 125 | (is (= "b" (from "textarea" (read-form-input)))) 126 | (at "textarea" (do-> (set-form-input "c") 127 | #(dispatch! % :change 128 | {:currentTarget %}))) 129 | (is (= "c" @atm)))) 130 | (testing "binding a simple value to a checkbox field :to-way" 131 | (let [atm (atom "c1")] 132 | (at "input[name='c']" (bind-input atm {:event :change})) 133 | (is (= #{"c1"} (from "input[name='c']" (read-form-input)))) 134 | (reset! atm #{"c3"}) 135 | (is (= #{"c3"} (from "input[name='c']" (read-form-input)))) 136 | (reset! atm #{"c1" "c2"}) 137 | (is (= #{"c1" "c2"} (from "input[name='c']" 138 | (read-form-input)))) 139 | (at "input[name='c']" (do-> (set-form-input ["c2" "c3"]) 140 | #(dispatch! % :change 141 | {:currentTarget %}))) 142 | (is (= #{"c2" "c3"} @atm)))) 143 | (testing "binding a simple value to a select field :to-way" 144 | (let [atm (atom "d1")] 145 | (at "select" (bind-input atm {:event :change})) 146 | (is (= "d1" (from "select" (read-form-input)))) 147 | (reset! atm "d2") 148 | (is (= "d2" (from "select" (read-form-input)))) 149 | (at "select" (set-attr :multiple "multiple")) 150 | (reset! atm #{"d3" "d4"}) 151 | (is (= #{"d3" "d4"} (from "select" (read-form-input)))) 152 | (at "select" (do-> (set-form-input ["d2" "d1"]) 153 | #(dispatch! % :change 154 | {:currentTarget %}))) 155 | (is (= #{"d2" "d1"} @atm))))))) 156 | 157 | 158 | 159 | (deftest save-form-to-atm-test 160 | (let [form-frag (html [:form {:name "my-form" 161 | :id "my-form"} 162 | [:input {:name "a" :value "a"}] 163 | [:input {:name "b" :value "b"}]])] 164 | (at "#test-id" (content form-frag)) 165 | (testing "atoms with maps" 166 | (testing "straight form to map mapping" 167 | (let [atm (atom {:a "_" :b "_" :c "c"})] 168 | (save-form-to-atm atm (by-id "my-form")) 169 | (is (= {:a "a" :b "b" :c "c"} @atm)))) 170 | (testing "field mapping for simple map" 171 | (let [atm (atom {:a "_" :b "_" :c "c"})] 172 | (save-form-to-atm atm (by-id "my-form") (mapping-to-lens {:a :b :b :a})) 173 | (is (= {:a "b" :b "a" :c "c"} @atm)))) 174 | (testing "field mapping for complex map" 175 | (let [atm (atom {:a "a" :b {:aa "aa" :bb "bb"} :c "c"})] 176 | (save-form-to-atm atm (by-id "my-form") (mapping-to-lens {:a [:b :aa] 177 | :b [:b :bb]})) 178 | (is (= {:a "a" :b {:aa "a" :bb "b"} :c "c"} @atm))))) 179 | (testing "atoms as js-objs" 180 | (testing "straight form to obj mapping" 181 | (let [atm (atom (js-obj "a" "_" "b" "_" "c" "c"))] 182 | (save-form-to-atm atm (by-id "my-form")) 183 | (is (= "a" (aget @atm "a"))) 184 | (is (= "b" (aget @atm "b"))) 185 | (is (= "c" (aget @atm "c"))))) 186 | (testing "field mapping form to simple obj" 187 | (let [atm (atom (js-obj "a" "_" "b" "_" "c" "c"))] 188 | (save-form-to-atm atm (by-id "my-form") (mapping-to-lens {:a :b :b :a})) 189 | (is (= "b" (aget @atm "a"))) 190 | (is (= "a" (aget @atm "b"))) 191 | (is (= "c" (aget @atm "c"))))) 192 | (testing "field mapping form to complex obj" 193 | (let [atm (atom (js-obj "a" "a" 194 | "b" (js-obj "aa" "aa" "bb" "bb") 195 | "c" "c"))] 196 | (save-form-to-atm atm (by-id "my-form") (mapping-to-lens {:a [:b :aa] 197 | :b [:b :bb]})) 198 | (is (= "a" (aget @atm "a"))) 199 | (is (= "a" (aget @atm "b" "aa"))) 200 | (is (= "b" (aget @atm "b" "bb"))) 201 | (is (= "c" (aget @atm "c")))))))) 202 | 203 | 204 | 205 | (deftest bind-form-test 206 | (let [input-frag (html 207 | [:form {:id "my-form" :name "my-form"} 208 | [:input {:type "text" :name "a" :value "_"}] 209 | [:textarea {:name "b"} "_"] 210 | [:input {:name "c" :type "checkbox" :value "c1"}] 211 | [:input {:name "c" :type "checkbox" :value "c2"}] 212 | [:input {:name "c" :type "checkbox" :value "c3"}] 213 | [:select {:name "d" :multiple "multiple"} 214 | [:option {:value "d1"}] 215 | [:option {:value "d2"}] 216 | [:option {:value "d3"}] 217 | [:option {:value "d4"}]]])] 218 | (at "#test-id" (content input-frag)) 219 | (testing "binding a form to a simple map" 220 | (let [atm (atom {:a "a" :b "b" :c #{"c1" "c2"} :d #{"d3" "d4"}})] 221 | (testing "initial bind" 222 | (at "form" (bind-form atm)) 223 | (is (= @atm (from "form" (read-form))))) 224 | (testing "updating atom" 225 | (swap! atm #(assoc % :a "aa" :b "bb" :c #{"c3"} :d #{"d1" "d2"})) 226 | (is (= @atm (from "form" (read-form))))) 227 | (testing "updating form" 228 | (let [val-map {:a "a_" :b "b_" :c #{"c1" "c3"} :d #{"d3" "d4"}}] 229 | (at "form" (do-> (set-form val-map) 230 | #(dispatch! % :submit 231 | {:currentTarget %}))) 232 | (is (= @atm val-map)) 233 | (is (= @atm (from "form" (read-form)))))))) 234 | (at "#test-id" (content input-frag)) 235 | (testing "binding a form to a complex map " 236 | (let [atm (atom {:a "a" 237 | :b {:bb "b" :c #{"c1" "c2"}} 238 | :d #{"d3" "d4"}})] 239 | (testing "initial bind" 240 | (at "form" (bind-form atm {:lens (mapping-to-lens {:a [:a] 241 | :b [:b :bb] 242 | :c [:b :c] 243 | :d [:d]})})) 244 | (is (= {:a "a" 245 | :b "b" 246 | :c #{"c1" "c2"} 247 | :d #{"d3" "d4"}} (from "form" (read-form))))) 248 | (testing "updating atom" 249 | (swap! atm #(-> % 250 | (assoc-in [:b :bb] "bb") 251 | (assoc-in [:b :c] #{"c1" "c3"}) 252 | (assoc :a "aa" :d #{"d1" "d2"}))) 253 | (is (= {:a "aa" 254 | :b "bb" 255 | :c #{"c1" "c3"} 256 | :d #{"d1" "d2"}} (from "form" (read-form))))) 257 | (testing "updating form" 258 | (let [val-map {:a "a_" :b "b_" :c #{"c1" "c3"} :d #{"d3" "d4"}}] 259 | (at "form" (do-> (set-form val-map) 260 | #(dispatch! % :submit 261 | {:currentTarget %}))) 262 | (is (= @atm {:a "a_" 263 | :b {:bb "b_" :c #{"c1" "c3"}} 264 | :d #{"d3" "d4"}})))))))) 265 | -------------------------------------------------------------------------------- /test/cljs/enfocus/core_test.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.core-test 2 | (:require 3 | [enfocus.core :as ef] 4 | [cemerick.cljs.test :as t] 5 | [enfocus.reporting.report-generator :refer (each-fixture)]) 6 | (:require-macros 7 | [enfocus.macros :as em] 8 | [enfocus.reporting.test-setup :refer (setup-tests)] 9 | [cemerick.cljs.test :refer (is are deftest testing use-fixtures)])) 10 | 11 | 12 | 13 | (setup-tests) 14 | (use-fixtures :each each-fixture) 15 | 16 | ;;;;;;;;;;;;;;;;;;;;;;;; 17 | ;; 18 | ;; Helper Functions 19 | ;; 20 | ;;;;;;;;;;;;;;;;;;;;;;;; 21 | 22 | (defn by-id [id] 23 | (.getElementById js/document id)) 24 | 25 | (defn elem [typ] 26 | (.createElement js/document typ)) 27 | 28 | (defn- build-form [] 29 | (ef/html [:form 30 | [:input {:name "f1" :type "text" 31 | :value "testing1"}] 32 | [:select {:name "f2" 33 | :multiple "multiple"} 34 | [:option {:value "o1" :selected true}] 35 | [:option {:value "o2" :selected true}] 36 | [:option {:value "o3"}]] 37 | [:input {:name "f3" :type "checkbox" 38 | :value "c1" :checked true}] 39 | [:input {:name "f3" :type "checkbox" 40 | :value "c2"}]])) 41 | 42 | 43 | ;;;;;;;;;;;;;;;;;;;;;;;; 44 | ;; 45 | ;; at & from forms 46 | ;; 47 | ;;;;;;;;;;;;;;;;;;;;;;;; 48 | 49 | (deftest at-test 50 | (testing "Unit Test for the (at ...) form\n" 51 | 52 | (testing "Border Cases\n" 53 | 54 | (testing "(at selector (transform arg1 ...))\n" 55 | (are [expected actual] (= expected actual) 56 | 57 | ;; the transformer is nil 58 | nil (ef/at "body" nil) 59 | nil (ef/at "div" nil) 60 | nil (ef/at "p" nil) 61 | nil (ef/at "#test-id" nil) 62 | nil (ef/at ".test-class" nil) 63 | ;; the selector is nil too 64 | nil (ef/at nil nil))) 65 | 66 | (testing "(at a-node (transform arg1 ...))\n" 67 | (are [expected actual] (= expected actual) 68 | ;; the transformer is nil 69 | js/document (ef/at js/document nil) 70 | "Error" (try 71 | (ef/at js/not-a-node nil) 72 | (catch js/Error e 73 | "Error")) ; Can't find variable: not-a-node 74 | )) 75 | 76 | (testing "(at a-node\n\t[selector1] (transform1 arg1 ...)\n\t[selector2] (transform2 arg1 ...))\n" 77 | (are [expected actual] (= expected actual) 78 | 79 | ;; the node is nil 80 | nil (ef/at nil [] nil) 81 | nil (ef/at nil ["body"] nil) 82 | nil (ef/at nil [] (ef/content "which ever content")) 83 | 84 | ;; the selector is nil 85 | nil (ef/at js/document nil (ef/content "which ever content")) 86 | nil (ef/at js/document nil nil) 87 | "Error" (try (ef/at js/not-a-node nil (ef/content "which ever content")) 88 | (catch js/Error e 89 | "Error")) ; Can't find variable: not a node 90 | 91 | ;; the transformer is nil 92 | nil (ef/at js/document [] nil) 93 | nil (ef/at js/document ["body"] nil) 94 | nil (ef/at js/document ["not a selector"] nil) ;should rise an exception? 95 | "Error" (try (ef/at js/not-a-node ["body"] nil) 96 | (catch js/Error e 97 | "Error")) ; Can't find variable: not a node 98 | 99 | ;; every arg is nil 100 | nil (ef/at nil nil nil))) 101 | 102 | (testing "(at [selector1] (transform1 arg1 ...)\n\t[selector2] (transform2 arg1 ...))\n" 103 | (are [expected actual] (= expected actual) 104 | 105 | ;; the tranformer is nil 106 | nil (ef/at ["body"] nil) 107 | nil (ef/at ["div"] nil) 108 | nil (ef/at [""] nil) 109 | nil (ef/at ["not a selector"] nil) ;should rise an exception? 110 | nil (ef/at [nil] nil)))) 111 | 112 | (testing "Standard Cases\n" 113 | (testing "simple node test" 114 | (ef/at (by-id "test-id") (ef/content "testing1")) 115 | (let [res (.-innerHTML (by-id "test-id"))] 116 | (is (= "testing1" res)))) 117 | 118 | (ef/at "#test-id" (ef/content "")) 119 | (testing "js/document single selector" 120 | (ef/at js/document "#test-id" (ef/content "testing2")) 121 | (let [res (.-innerHTML (by-id "test-id"))] 122 | (is (= "testing2" res)))) 123 | 124 | (ef/at "#test-id" (ef/content "")) 125 | (testing "js/documnt w/ 3 sub selectors" 126 | (ef/at js/document 127 | "#test-id" (ef/content (ef/html [:p])) 128 | "#test-id > p" (ef/content "testing") 129 | "#test-id" (ef/append (ef/html [:span]))) 130 | (let [res (.-innerHTML (by-id "test-id"))] 131 | (is (= "

testing

" res)))) 132 | 133 | (ef/at "#test-id" (ef/content "")) 134 | (testing "single selector" 135 | (ef/at "#test-id" (ef/content "testing3")) 136 | (let [res (.-innerHTML (by-id "test-id"))] 137 | (is (= "testing3" res)))) 138 | 139 | (ef/at "#test-id" (ef/content "")) 140 | (testing "direct 2 sub selectors" 141 | (ef/at "#test-id" (ef/content (ef/html [:p])) 142 | "#test-id > p" (ef/content "testing")) 143 | (let [res (.-innerHTML (by-id "test-id"))] 144 | (is (= "

testing

" res)))) 145 | 146 | (ef/at "#test-id" (ef/content "")) 147 | (testing "single selector 2 sub & custom selector" 148 | (ef/at (by-id "test-id") 149 | ef/this-node (ef/do-> 150 | (ef/content (ef/html [:p])) 151 | (ef/append (ef/html [:span]))) 152 | "p" (ef/content "testing")) 153 | (let [res (.-innerHTML (by-id "test-id"))] 154 | (is (= "

testing

" res))))))) 155 | 156 | 157 | (deftest from-tests 158 | (ef/at "#test-id" (ef/content (build-form))) 159 | (testing "simple node test" 160 | (let [res (ef/from (by-id "test-id") (ef/get-attr :id))] 161 | (is (= "test-id" res)))) 162 | 163 | (testing "simple selector test" 164 | (let [res (ef/from "#test-id" (ef/get-attr :id))] 165 | (is (= "test-id" res)))) 166 | 167 | (testing "node w/ several selectors" 168 | (let [res (ef/from (by-id "test-id") 169 | :f1 "input[name='f1']" (ef/get-prop :value) 170 | :f2 "option" (ef/filter :selected 171 | (ef/get-attr :value)))] 172 | (is (= {:f1 "testing1" :f2 '("o1" "o2")} res))))) 173 | 174 | 175 | 176 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 177 | ;; 178 | ;; Standard Trandsform Tests 179 | ;; 180 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 181 | 182 | 183 | (deftest content-test 184 | (testing "content string" 185 | (ef/at "#test-id" (ef/content "testing")) 186 | (let [res (.-textContent (by-id "test-id"))] 187 | (is (= "testing" res)))) 188 | 189 | (testing "content node" 190 | (ef/at "#test-id" (ef/content (elem "span"))) 191 | (let [res (.-innerHTML (by-id "test-id"))] 192 | (is (= "" res)))) 193 | 194 | (testing "content seq of nodes" 195 | (ef/at "#test-id" (ef/content [(elem "span") (elem "span")])) 196 | (let [res (.-innerHTML (by-id "test-id"))] 197 | (is (= "" res))))) 198 | 199 | 200 | (deftest text-content-test 201 | (testing "text-content not matching html" 202 | (ef/at "#test-id" (ef/text-content "<>&'\"")) 203 | (let [res (.-textContent (by-id "test-id"))] 204 | (is (= "<>&'\"" res)))) 205 | (testing "text-content with tags" 206 | (ef/at "#test-id" (ef/text-content "test")) 207 | (let [res (.-textContent (by-id "test-id"))] 208 | (is (= "test" res)))) 209 | (testing "text-content with entities" 210 | (ef/at "#test-id" (ef/text-content "<>")) 211 | (let [res (.-textContent (by-id "test-id"))] 212 | (is (= "<>" res))))) 213 | 214 | 215 | (deftest html-content-test 216 | (testing "html-content" 217 | (ef/at "#test-id" (ef/html-content "testing")) 218 | (let [res (.-firstChild (by-id "test-id"))] 219 | (is (= "SPAN" (.-nodeName res))) 220 | (is (= "testing" (.-textContent res)))))) 221 | 222 | 223 | (deftest html-hiccup-test 224 | (testing "html(hiccup)" 225 | (ef/at "#test-id" (ef/content 226 | (ef/html [:span {:id "tmp"} "testing"]))) 227 | (let [res (.-innerHTML (by-id "test-id"))] 228 | (is (= "testing" res))))) 229 | 230 | 231 | (deftest attributes-test 232 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 233 | (testing "setting an attribute" 234 | (ef/at "#test-id > p" (ef/set-attr :id "tmp")) 235 | (let [res (.-innerHTML (by-id "test-id"))] 236 | (is (= "

" res)))) 237 | 238 | (testing "removing an attribute" 239 | (ef/at "#test-id > p" (ef/remove-attr :id)) 240 | (let [res (.-innerHTML (by-id "test-id"))] 241 | (is (= "

"))))) 242 | 243 | (deftest prop-test 244 | (testing "setting a property" 245 | (ef/at "#test-id" (ef/set-prop :my-prop "testing")) 246 | (let [res (aget (by-id "test-id") "my-prop")] 247 | (is (= "testing" res))))) 248 | 249 | 250 | (deftest class-test 251 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 252 | (testing "adding a class" 253 | (ef/at "#test-id" (ef/add-class "my-class")) 254 | (let [res (.-className (by-id "test-id"))] 255 | (is (= "test-class my-class" res)))) 256 | 257 | (ef/at "#test-id" (ef/add-class "second-class")) 258 | (let [res1 (.-className (by-id "test-id"))] 259 | (ef/at "#test-id" (ef/remove-class "second-class")) 260 | (testing "adding a second class" 261 | (is (= res1 "test-class my-class second-class"))) 262 | (let [res2 (.-className (by-id "test-id"))] 263 | (testing "removing a class" 264 | (is (= res2 "test-class my-class"))))) 265 | 266 | (testing "setting the class" 267 | (ef/at "#test-id" (ef/set-class "test1" "test2")) 268 | (let [res (.-className (by-id "test-id"))] 269 | (is (= "test1 test2" res))))) 270 | 271 | 272 | (deftest do->test 273 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 274 | (testing "chaining using do->" 275 | (ef/at "#test-id > p" (ef/do-> 276 | (ef/content "testing") 277 | (ef/set-attr :id "tmp"))) 278 | (let [res (.-innerHTML (by-id "test-id"))] 279 | (is (= "

testing

" res))))) 280 | 281 | 282 | (deftest appending-test 283 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 284 | (testing "prepending content" 285 | (ef/at "#test-id" (ef/prepend (ef/html [:span]))) 286 | (let [res (.-innerHTML (by-id "test-id"))] 287 | (is (= "

" res)))) 288 | 289 | (testing "appending content" 290 | (ef/at "#test-id" (ef/append (ef/html [:span]))) 291 | (let [res (.-innerHTML (by-id "test-id"))] 292 | (is (= "

" res))))) 293 | 294 | 295 | (deftest before-after-test 296 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 297 | (testing "adding content before" 298 | (ef/at "#test-id > p" (ef/before (ef/html [:span]))) 299 | (let [res (.-innerHTML (by-id "test-id"))] 300 | (is (= "

" res)))) 301 | 302 | (testing "adding content after" 303 | (ef/at "#test-id > p" (ef/after (ef/html [:span]))) 304 | (let [res (.-innerHTML (by-id "test-id"))] 305 | (is (= "

" res))))) 306 | 307 | 308 | (deftest substitute-test 309 | (testing "substituting content" 310 | (ef/at "#test-id > p" (ef/substitute (ef/html [:span]))) 311 | (let [res (.-innerHTML (by-id "test-id"))] 312 | (is (= "" res))))) 313 | 314 | (deftest remove-test 315 | (testing "removing a node" 316 | (ef/at "#test-id > p" (ef/remove-node)) 317 | (let [res (.-innerHTML (by-id "test-id"))] 318 | (is (= "" res))))) 319 | 320 | (deftest wrap-unwrap-test 321 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 322 | (testing "wrapping content" 323 | (ef/at "#test-id > p" (ef/wrap :span {:id "tmp"})) 324 | (let [res (.-innerHTML (by-id "test-id"))] 325 | (is (= "

" res)))) 326 | 327 | (testing "unwrapping content" 328 | (ef/at "#test-id > span" (ef/unwrap)) 329 | (let [res (.-innerHTML (by-id "test-id"))] 330 | (is (= "

" res))))) 331 | 332 | 333 | (deftest style-tests 334 | (testing "setting a single style" 335 | (ef/at "#test-id" (ef/set-style :background-color"#cfcfcf")) 336 | (let [res (-> (by-id "test-id") 337 | (.-style) 338 | (.-backgroundColor))] 339 | (is (= "rgb(207, 207, 207)" res)))) 340 | 341 | (testing "removing a single style" 342 | (ef/at "#test-id" (ef/remove-style :background)) 343 | (let [res (-> (by-id "test-id") 344 | (.-style) 345 | (.-background))] 346 | (is (= "" res)))) 347 | 348 | (testing "setting a list styles" 349 | (ef/at "#test-id" (ef/set-style :background-color "#cfcfcf" 350 | :width "12px")) 351 | (let [res1 (-> (by-id "test-id") 352 | (.-style) 353 | (.-backgroundColor)) 354 | res2 (-> (by-id "test-id") 355 | (.-style) 356 | (.-width))] 357 | (is (= "rgb(207, 207, 207)" res1)) 358 | (is (= "12px" res2))) 359 | (testing "removing a list of styles" 360 | (ef/at "#test-id" (ef/remove-style :background :width)) 361 | (let [res1 (-> (by-id "test-id") 362 | (.-style) 363 | (.-background)) 364 | res2 (-> (by-id "test-id") 365 | (.-style) 366 | (.-width))] 367 | (is (and (empty? res1) (empty? res2))))))) 368 | 369 | 370 | (deftest focus-blur-test 371 | (testing "setting the focus on an element" 372 | (ef/at "#test-id" (ef/content (ef/html [:input {:id "tmp"}])) 373 | "#tmp" (ef/focus)) 374 | (is (= (by-id "tmp") (.-activeElement js/document)))) 375 | 376 | (testing "setting the focus on an element" 377 | (ef/at "#tmp" (ef/blur)) 378 | (is (not= (by-id "tmp") (.-activeElement js/document))))) 379 | 380 | 381 | (deftest set-data-test 382 | (testing "setting data elements on a node" 383 | (ef/at "#test-id" (ef/set-data :my-data "testing")) 384 | (let [res (pr-str (.-__domina_data (by-id "test-id")))] 385 | (is (= "{:my-data \"testing\"}" res))))) 386 | 387 | 388 | (deftest replace-vars-test 389 | (ef/at "#test-id" (ef/content (ef/html [:p]))) 390 | (testing "replacing vars in a content" 391 | (ef/at "#test-id > p" (ef/do-> 392 | (ef/content "name: ${name}") 393 | (ef/set-attr :tmp "a${id}")) 394 | "#test-id" (ef/replace-vars {:name "CK" :id "tmp"})) 395 | (let [res (.-innerHTML (by-id "test-id"))] 396 | (is (= "

name: CK

" res))))) 397 | 398 | 399 | (deftest set-form-input-test 400 | (ef/at "#test-id" (ef/content (build-form))) 401 | (testing "setting a select input" 402 | (ef/at "select" (ef/set-form-input ["o2" "o3"])) 403 | (let [res (ef/from "select" (ef/read-form-input))] 404 | (is (= #{"o2" "o3"} res)))) 405 | (testing "setting a text input" 406 | (ef/at "input[type='text']" (ef/set-form-input "test")) 407 | (let [res (ef/from "input[type='text']" 408 | (ef/read-form-input))] 409 | (is (= "test" res)))) 410 | (testing "setting a checkbox input" 411 | (ef/at "input[type='checkbox']" (ef/set-form-input "c2")) 412 | (let [res (ef/from "input[type='checkbox']" 413 | (ef/read-form-input))] 414 | (is (= #{"c2"} res))))) 415 | 416 | (deftest set-form-test 417 | (ef/at "#test-id" (ef/content (build-form))) 418 | (testing "setting form values using a map" 419 | (testing "setting single text input value" 420 | (ef/at "#test-id > form" (ef/set-form {:f1 "v1"})) 421 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 422 | (is (= {:f1 "v1" :f2 #{"o1" "o2"} :f3 #{"c1"}} vals)))) 423 | (testing "setting multi value select input" 424 | (ef/at "#test-id > form" (ef/set-form {:f2 ["o1" "o3"]})) 425 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 426 | (is (= {:f1 "v1" :f2 #{"o1" "o3"} :f3 #{"c1"}} vals)))) 427 | (testing "setting multi value check-box input" 428 | (ef/at "#test-id > form" (ef/set-form {:f3 ["c2" "c1"]})) 429 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 430 | (is (= {:f1 "v1" :f2 #{"o1" "o3"} :f3 #{"c1" "c2"}} vals)))) 431 | (testing "setting single value select input" 432 | (ef/at "#test-id > form" (ef/set-form {:f2 "o1"})) 433 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 434 | (is (= {:f1 "v1" :f2 #{"o1"} :f3 #{"c1" "c2"}} vals)))) 435 | (testing "setting single value check-box input" 436 | (ef/at "#test-id > form" (ef/set-form {:f3 "c2"})) 437 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 438 | (is (= {:f1 "v1" :f2 #{"o1"} :f3 #{"c2"}} vals)))) 439 | (testing "setting single value check-box input" 440 | (ef/at "#test-id > form" (ef/set-form {:f1 "n1" 441 | :f2 ["o2" "o1"] 442 | :f3 ["c1"]})) 443 | (let [vals (ef/from "#test-id > form" (ef/read-form))] 444 | (is (= {:f1 "n1" :f2 #{"o1" "o2"} :f3 #{"c1"}} vals)))))) 445 | 446 | 447 | ;;;;;;;;;;;;;;;;;;;;;;;; 448 | ;; 449 | ;; Extractor Tests 450 | ;; 451 | ;;;;;;;;;;;;;;;;;;;;;;;; 452 | 453 | (deftest get-attr-test 454 | (testing "getting an attribute from a node" 455 | (let [res (ef/from "#test-id" (ef/get-attr :id))] 456 | (is (= "test-id" res))))) 457 | 458 | 459 | (deftest get-text-test 460 | (ef/at "#test-id" (ef/content "testing")) 461 | (testing "getting the text from a node" 462 | (let [res (ef/from "#test-id" (ef/get-text))] 463 | (is (= "testing" res))))) 464 | 465 | (deftest get-data-test 466 | (ef/at "#test-id" (ef/set-data :my-data "testing")) 467 | (testing "getting the data from a node" 468 | (let [res (ef/from "#test-id" (ef/get-data :my-data))] 469 | (is (= "testing" res))))) 470 | 471 | (deftest get-prop 472 | (ef/at "#test-id" (ef/set-prop :my-data "testing")) 473 | (testing "getting the a property from a node" 474 | (let [res (ef/from "#test-id" (ef/get-prop :my-data))] 475 | (is (= "testing" res))))) 476 | 477 | (deftest read-form-input-test 478 | (ef/at "#test-id" (ef/content (build-form)) 479 | "select" (ef/after (ef/html [:textarea {:name "t"} "t1"]))) 480 | (testing "reading from a select input" 481 | (let [res (ef/from "select" (ef/read-form-input))] 482 | (is (= #{"o1" "o2"} res)))) 483 | (testing "reading from a text input" 484 | (let [res (ef/from "input[type='text']" 485 | (ef/read-form-input))] 486 | (is (= "testing1" res)))) 487 | (testing "reading from a checkbox input" 488 | (let [res (ef/from "input[type='checkbox']" 489 | (ef/read-form-input))] 490 | (is (= #{"c1"} res)))) 491 | (testing "reading from a textarea input" 492 | (let [res (ef/from "textarea" 493 | (ef/read-form-input))] 494 | (is (= "t1" res))))) 495 | 496 | 497 | (deftest read-form-test 498 | (ef/at "#test-id" (ef/content (build-form))) 499 | (testing "reading a form" 500 | (let [res (ef/from "#test-id > form" (ef/read-form))] 501 | (is (= {:f1 "testing1" :f2 #{"o1" "o2"} :f3 #{"c1"}} res))))) 502 | 503 | 504 | 505 | (deftest filter-test 506 | (ef/at "#test-id" (ef/content (build-form))) 507 | (testing "testing the filter transform" 508 | (let [res (ef/from "option" 509 | (ef/filter #(aget % "selected") 510 | (ef/get-prop :value)))] 511 | (is (= '("o1" "o2") res)))) 512 | 513 | (testing "build in filters :selected" 514 | (let [res (ef/from "option" 515 | (ef/filter :selected (ef/get-prop :value)))] 516 | (is (= '("o1" "o2") res)))) 517 | 518 | (testing "build in filters :checked" 519 | (let [res (ef/from "input[type='checkbox']" 520 | (ef/filter :checked (ef/get-prop :value)))] 521 | (is (= "c1" res))))) 522 | 523 | ;;;;;;;;;;;;;;;;;;;;;;;; 524 | ;; 525 | ;; Async Tests 526 | ;; 527 | ;;;;;;;;;;;;;;;;;;;;;;;; 528 | 529 | (deftest delay-test 530 | ;because clojurescript test does not handle 531 | ;async behavor the testing form has to be 532 | ;inside delay statement. 533 | (let [cur (.getMilliseconds (js/Date.))] 534 | (ef/at "#test-id" 535 | (ef/delay 100 536 | #(testing "delay function" 537 | (let [now (.getMilliseconds (js/Date.))] 538 | (println (Math/abs (- (- now cur) 100))) 539 | (is (> 20 (Math/abs (- (- now cur) 100)))))))))) 540 | -------------------------------------------------------------------------------- /test/cljs/enfocus/events_test.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.evets-test 2 | (:require 3 | [enfocus.core :as ef :refer [at from content get-text html 4 | set-form-input read-form-input do-> 5 | set-prop read-form set-form set-attr]] 6 | [enfocus.events :as ev :refer [listen listen-live remove-listeners]] 7 | [domina.events :as de :refer [dispatch! dispatch-browser!]] 8 | [cemerick.cljs.test :as t]) 9 | (:require-macros 10 | [enfocus.macros :as em] 11 | [cemerick.cljs.test :refer (is are deftest testing use-fixtures)])) 12 | 13 | (defn each-fixture [f] 14 | ;; initialize the environment 15 | (let [div (.createElement js/document "div")] 16 | (.setAttribute div "id" "test-id") 17 | (.setAttribute div "class" "test-class") 18 | (.appendChild (.-body js/document) div) 19 | ;; execute the unit test 20 | (f) 21 | ;; clear the environment 22 | (.removeChild (.-body js/document) div))) 23 | 24 | (use-fixtures :each each-fixture) 25 | 26 | (defn by-id [id] (.getElementById js/document id)) 27 | 28 | 29 | (defn simulate-key-event [target] 30 | (let [ev (js/Event. "keydown", #js {"bubbles" true "cancelable" false})] 31 | (.dispatchEvent target ev))) 32 | 33 | 34 | (defn simulate-click-event [target] (.click target)) 35 | 36 | (deftest listen-test 37 | (at "#test-id" (content (html [:span 38 | [:button {:name "test-btn" 39 | :id "test-btn"}] 40 | [:input {:name "test-inp" 41 | :type "text" 42 | :id "test-inp"}]]))) 43 | (testing "testing basic listening for click event" 44 | (let [atm (atom "fail")] 45 | (at "#test-btn" (listen :click #(reset! atm "success"))) 46 | (at "#test-btn" simulate-click-event) 47 | (is (= "success" @atm)))) 48 | (testing "testing basic listening for key event" 49 | (let [atm (atom "fail")] 50 | (at "#test-inp" (listen :keydown #(reset! atm (.-keyCode %)))) 51 | (at "#test-inp" simulate-key-event) 52 | (is (= 0 @atm))))) 53 | 54 | 55 | (deftest listen-live-test 56 | (testing "testing basic listening for click event" 57 | (let [atm (atom "fail")] 58 | (at "body" (listen-live :click "#test-btn" #(reset! atm "success"))) 59 | (at "#test-id" (content (html [:button {:name "test-btn" 60 | :id "test-btn"}]))) 61 | (at "#test-btn" simulate-click-event) 62 | (is (= "success" @atm)))) 63 | (testing "testing basic listening for key event" 64 | (let [atm (atom "fail")] 65 | (at "body" (listen-live :keydown "#test-inp" 66 | #(reset! atm (.-keyCode %)))) 67 | (at "#test-id" (content (html [:input {:name "test-inp" 68 | :type "text" 69 | :id "test-inp"}]))) 70 | (at "#test-inp" simulate-key-event) 71 | (is (= 0 @atm))))) 72 | 73 | 74 | (deftest remove-listeners-test 75 | (testing "removing all listeners of given type" 76 | (let [atm (atom "fail")] 77 | (at "#test-id" (content (html [:button {:id "test-btn"}])) 78 | "#test-btn" (listen :click #(swap! atm (fn [val] 79 | (if (= val "success") 80 | "fail" 81 | "success"))))) 82 | (at "#test-btn" simulate-click-event 83 | "#test-btn" (remove-listeners :click) 84 | "#test-btn" simulate-click-event) 85 | (is (= "success" @atm))))) 86 | 87 | 88 | (deftest html-event-handlers-test 89 | (testing "html function allows binding of event handlers" 90 | (let [atm (atom "fail")] 91 | (at "#test-id" 92 | (content 93 | (html [:button {:id "test-btn" 94 | :onclick #(reset! atm "success")}]))) 95 | (at "#test-btn" simulate-click-event) 96 | (is (= "success" @atm))))) 97 | -------------------------------------------------------------------------------- /test/cljx/enfocus/enlive/syntax_test.cljx: -------------------------------------------------------------------------------- 1 | #+clj (ns enfocus.enlive.syntax-test 2 | (:require [clojure.test :refer [deftest testing are]] 3 | [enfocus.enlive.syntax :refer [convert]])) 4 | 5 | #+cljs (ns enfocus.enlive.syntax-test 6 | (:require-macros [cemerick.cljs.test :refer (deftest is are testing)]) 7 | (:require [cemerick.cljs.test :as t] 8 | [enfocus.enlive.syntax :refer [convert]])) 9 | 10 | (deftest convert-test 11 | (testing "Unit Test for (convert arg)\n" 12 | 13 | (testing "Edge Cases\n" 14 | 15 | (testing "(convert a-val)" 16 | (are [expected actual] (= expected actual) 17 | 18 | ;; extreme values for considered input type 19 | nil (convert nil) 20 | "" (convert "") 21 | " " (convert " ") 22 | "" (convert ()) 23 | "" (convert []) 24 | "" (convert {}) 25 | "" (convert #{})))))) 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/tools/enfocus/reporting/report_generator.cljs: -------------------------------------------------------------------------------- 1 | (ns enfocus.reporting.report-generator) 2 | 3 | (def ^:export dom-report (atom [])) 4 | 5 | (defn ^:export generate-summary-report [] 6 | (let [frag (.createDocumentFragment js/document)] 7 | (doseq [[test res] @dom-report] 8 | (let [div (.createElement js/document "div")] 9 | (set! (.-className div) (name res)) 10 | (set! (.-innerHTML div) test) 11 | (.appendChild frag div))) 12 | (.appendChild (.-body js/document) frag))) 13 | 14 | 15 | (defn each-fixture [f] 16 | ;; initialize the environment 17 | (let [div (.createElement js/document "div") 18 | pc (.createElement js/document "p")] 19 | (.setAttribute div "id" "test-id") 20 | (.setAttribute div "class" "test-class") 21 | (.setAttribute pc "class" "test-class") 22 | (.appendChild div pc) 23 | (.appendChild (.-body js/document) div) 24 | ;; execute the unit test 25 | (f) 26 | ;; clear the environment 27 | (.removeChild (.-body js/document) div))) 28 | -------------------------------------------------------------------------------- /test/tools/enfocus/reporting/test_setup.clj: -------------------------------------------------------------------------------- 1 | (ns enfocus.reporting.test-setup 2 | (:require [cemerick.cljs.test :refer (with-test-out)])) 3 | 4 | (defmacro setup-tests [] 5 | `(do 6 | (defmethod cemerick.cljs.test/report :fail [m#] 7 | (with-test-out 8 | (cemerick.cljs.test/inc-report-counter :fail) 9 | (println "\nFAIL in" (cemerick.cljs.test/testing-vars-str m#)) 10 | (when (seq cemerick.cljs.test/*testing-contexts*) (println (cemerick.cljs.test/testing-contexts-str))) 11 | (when-let [message# (:message m#)] (println message#)) 12 | (println "expected:" (pr-str (:expected m#))) 13 | (println " actual:" (pr-str (:actual m#)))) 14 | (swap! enfocus.reporting.report-generator/dom-report 15 | #(conj % [(str (cemerick.cljs.test/testing-vars-str m#) 16 | " - " 17 | (cemerick.cljs.test/testing-contexts-str)) 18 | :fail]))) 19 | 20 | (defmethod cemerick.cljs.test/report :pass [m#] 21 | (with-test-out 22 | (cemerick.cljs.test/inc-report-counter :pass)) 23 | (swap! enfocus.reporting.report-generator/dom-report 24 | #(conj % [(str (cemerick.cljs.test/testing-vars-str m#) 25 | " - " 26 | (cemerick.cljs.test/testing-contexts-str)) 27 | :pass]))) 28 | 29 | (defmethod cemerick.cljs.test/report :error [m#] 30 | (with-test-out 31 | (cemerick.cljs.test/inc-report-counter :error) 32 | (println "\nERROR in" (cemerick.cljs.test/testing-vars-str m#)) 33 | (when (seq cemerick.cljs.test/*testing-contexts*) (println (cemerick.cljs.test/testing-contexts-str))) 34 | (when-let [message# (:message m#)] (println message#)) 35 | (println "expected:" (pr-str (:expected m#))) 36 | (print " actual: ") 37 | (let [actual# (:actual m#)] 38 | (if (instance? js/Error actual#) 39 | (println (.-stack actual#)) 40 | (prn actual#)))) 41 | (swap! enfocus.reporting.report-generator/dom-report 42 | #(conj % [(str (cemerick.cljs.test/testing-vars-str m#) 43 | " - " 44 | (cemerick.cljs.test/testing-contexts-str)) 45 | :error]))))) 46 | 47 | -------------------------------------------------------------------------------- /test/tools/server.clj: -------------------------------------------------------------------------------- 1 | (ns server 2 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 3 | [net.cgrand.enlive-html :as enlive] 4 | [compojure.core :refer (GET defroutes)] 5 | [compojure.route :refer (resources)] 6 | [ring.adapter.jetty :as jetty] 7 | [clojure.java.io :as io])) 8 | 9 | (enlive/deftemplate page 10 | (io/resource "public/index.html") 11 | [] 12 | [:body] (enlive/append 13 | (enlive/html [:script (browser-connected-repl-js)]))) 14 | 15 | (defroutes site 16 | (resources "/") 17 | (GET "/*" req (page))) 18 | 19 | (defn run 20 | [] 21 | (defonce server 22 | (jetty/run-jetty #'site {:port 3000 :join? false})) 23 | server) 24 | --------------------------------------------------------------------------------