Generating css directly with Clojure is great; before cssgen arrived, for example, I had to have all style sets (e.g. desktop/mobile) ready at once and communicate with the client indirectly (i.e. through html attributes). Now, I can just let my server make the decisions, generating and composing html/css at will.
Why not do the same with client-side scripting? Generating client-side scripts, hiccup style:
[:button {:onclick (cljs (js/alert "hi!"))} "click me!"]
cueball leverages the ClojureScript compiler so that you can inline scripts from server code. While it's always best to keep large chunks of client-side logic properly seperated, this can give you a bit more flexibility in terms of communicating with the client (i.e., rather than using html attributes). Eventually, it could also be used in situations where libraries (e.g. for MongoDB) accept javascript for scripting.
The cljs
syntax also supports unquoting, so you can splice in calls to server-side functions in order to generate and modify code on the fly:
(cljs (* (+ 5 3) 5))
;=> "((5 + 3) * 5);\n"
(cljs (* ~(+ 5 3) 5))
;=> "(8 * 5);\n"
(cljs (map inc (list -1 ~@(range 9) 9 10)))
;=> "cljs.core.map.call(
; null,
; cljs.core.inc,
; cljs.core.list.call(null,-1,0,1,2,3,4,5,6,7,8,9,10));\n"
;; Pass a settings map to the client code
(defn settings-map [] {:mobile-site true})
(cljs (set! client/settings-map ~(settings-map)))
;=> "client.settings_map =
; cljs.core.ObjMap.fromObject(
; [\"\\uFDD0'mobile-site\"],
; {\"\\uFDD0'mobile-site\":true});\n"
If you want to :use
other libraries, you can create a namespace. It's best to use the script
macro to put this into the <head>
of all of your pages.
(script (ns my-ns))
;=> <script type="text/javascript">
; goog.provide('my-ns');
; goog.require('cljs.core');
; </script>
(script
(def x 5)
(defn f [n] (* n 2)))
;=> <script type="text/javascript">
; my-ns.x = 5;
; my-ns.f = (function f(n){
; return (n * 2);
; });
; </script>
An interesting use for cueball might be to create libraries of self-contained widgets which don't require the user to touch clojurescript at all:
;; An input field which will display a default message until it is used.
(defn field-reset [id s]
(cljs (#(let [f (js/$ ~(str "#" id))
v (.prop f "value")]
(.prop f "value" ~s)))))
(defpartial field [id msg]
(text-field {:value msg
:onfocus (field-reset id "")
:onblur (field-reset id msg)}
id))
;; Usage
(html [:body (field "name" "enter your name")])
For now, just copy cueball.clj
over to your source folder. It's best to use it with something like noir-cljs, as you need to make sure that ClojureScript is bootstrapped on the client side (unless you avoid all core
functions). lein run
this project to launch a noir server with some examples on.
This is partly an experiment in making the join between Clojure and -Script more seamless; it would be great to have a framework with capabilities like opa's one day, and this is a (small) step in that direction.