we have been using scala and the lift framework for our project for over a year now. as we are starting on a new subcomponent of it, i thought i’d take a crack at using lift’s new CSS selector transforms.
CSS transforms provide an alternative to the traditional lift templating mechanism:
[sourcecode language=”xml”] <lift:FooBar> <foobar:dosomething /> <lift:FooBar> [/sourcecode]
which requires the following scala code:
[sourcecode language=”scala”] class FooBar { def render(in: NodeSeq): NodeSeq = bind("foobar", in, "dosomething" -> <span>dog</span>) } [/sourcecode]
and would result in
[sourcecode language=”xml”] <span>dog</span> [/sourcecode]
with the new CSS transforms we get rid of the <lift:FooBar>
tags and instead use plain HTML:
[sourcecode language=”xml”] <div class="lift:Foobar"> <span id="animal">XXX</span> </div> [/sourcecode]
and then change the binding to:
[sourcecode language=”scala”] class FooBar { def render = "#animal *" "dog" } [/sourcecode]
and end up, again, with
[sourcecode language=”xml”] <span id="animal">dog</span> [/sourcecode]
— just with much less code! and once you start looking into expanding lists, it really becomes obvious that CSS transforms are a huge step forward. add to that that you can now design your web apps using normal HTML design tools (which in my case is emacs, nxhtml mode, and moz-repl) and life has just become a lot easier and more productive…
…except for one thing :-) you see, we are using comets and lift’s rather cool partialUpdate(SetHtml("dog", Text(wuff! wuff!)))
construct to dynamically update the “dog” span. for that we were — in the pre-CSS transform age — using calls to chooseTemplate
to fetch XML snippets and re-render them. how to do this with CSS transforms?
let’s assume our HTML snippet is a bit more complex and includes an image:
[sourcecode language=”xml”] <div class="lift:Foobar"> <div><span id="animal"><img src="XXX" /></span></div> </div> [/sourcecode]
the CSS selector transforms page lists the following construct:
[sourcecode language=”scala”] "animal ^^" #> "ignore" [/sourcecode]
this will select the element with the id “animal”. so far, so good. what’s not so good is that the intuitively next idea
[sourcecode language=”scala”] "animal ^^" #> "ignore" & "img [src]" #> "dog.png" [/sourcecode]
doesn’t work (CSS transforms are actually functions, so we can invoke them on NodeSeq
in sbt’s console):
scala> val in = <div><span id="animal"><img src="XXX" /></span></div>
in: scala.xml.Elem = <div><span id="animal"><img src="XXX"></img></span></div>
scala> ("#animal ^^" #> "ignore")(in)
res0: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="XXX"></img></span>)
scala> ("#animal ^^" #> "ignore" & "img [src]" #> "dog.png")(in)
res1: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="XXX"></img></span>)
what does work is the following code:
[sourcecode language=”scala”] val template = ("#animal ^^" #> "ignore")(in) ("img [src]" #> "dog.png")(template) [/sourcecode]
if we try this in console we get:
scala> val template = ("#animal ^^" #> "ignore")(in)
template: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="XXX"></img></span>)
scala> ("img [src]" #> "dog.png")(template)
res2: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="dog.png"></img></span>)
which is what we expect. just a bit cumbersome to write and the contraction
[sourcecode language=”scala”] ("img [src]" #> "dog.png")(("#animal ^^" #> "ignore")(in)) [/sourcecode]
while producing the same result, is a bit hard to understand.
ideally, we could write it as:
[sourcecode language=”scala”] "#animal ^^" #> "ignore" ~> "img [src]" #> "dog.png" [/sourcecode]
as in: first select the template, then apply the following transform. well, we are in scala land, where (almost) everything is possible! so, why not create that operator? how hard can it be?
“not very” is the answer. first step is to figure out what it is that we want: CssBindFunc
can be viewed as (NodeSeq) => NodeSeq
functions. so, we really want to concatenate one (NodeSeq) => NodeSeq
with another one. writing this as a function that translates into:
[sourcecode language=”scala”] def andThenInto(first: (NodeSeq) => NodeSeq, second: (NodeSeq) => NodeSeq): (NodeSeq) => NodeSeq = (ns: NodeSeq) => second(first(ns)) [/sourcecode]
throwing that into the sbt console yields:
scala> def andThenInto(first: (NodeSeq) => NodeSeq, second: (NodeSeq) => NodeSeq): (NodeSeq) => NodeSeq =
(ns: NodeSeq) => second(first(ns))
andThenInto: (first: (scala.xml.NodeSeq) => scala.xml.NodeSeq,second: (scala.xml.NodeSeq) => scala.xml.NodeSeq)(scala.xml.NodeSeq) => scala.xml.NodeSeq
scala> andThenInto("#animal ^^" #> "ignore", "img [src]" #> "dog.png")
res4: (scala.xml.NodeSeq) => scala.xml.NodeSeq = <function1>
scala> res4(in)
res4(in)
res5: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="dog.png"></img></span>)
scala>
which is exactly what we are after — well, almost. andThenInto
is a bit long, we wanted to use ~>
. so, let’s try that:
def ~>(first: (NodeSeq) => NodeSeq, second: (NodeSeq) => NodeSeq): (NodeSeq) => NodeSeq =
(ns: NodeSeq) => second(first(ns))
$tilde$greater: (first: (scala.xml.NodeSeq) => scala.xml.NodeSeq,second: (scala.xml.NodeSeq) => scala.xml.NodeSeq)(scala.xml.NodeSeq) => scala.xml.NodeSeq
doesn’t look too bad. let’s give it a spin:
~>("#animal ^^" #> "ignore", "img [src]" #> "dog.png")
res6: (scala.xml.NodeSeq) => scala.xml.NodeSeq = <function1>
scala> res6(in)
res6(in)
res7: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="dog.png"></img></span>)
looking good. next, let’s pimp CssBindFunc
with that:
[sourcecode language=”scala”] implicit def pimpCssBindFuncWithAndThenInto(first: (NodeSeq) => NodeSeq) = new { def ~>(second: (NodeSeq) => NodeSeq): (NodeSeq) => NodeSeq = (ns: NodeSeq) => second(first(ns)) } [/sourcecode]
and give it a go:
scala> "#animal ^^" #> "ignore" ~> "img [src]" #> "dog.png"
<console>:14: error: type mismatch;
found : java.lang.String("img [src]")
required: (scala.xml.NodeSeq) => scala.xml.NodeSeq
"#animal ^^" #> "ignore" ~> "img [src]" #> "dog.png"
ooops. what happened? the compiler seems to be doing the grouping in a different way then we expected. “operator precedence” was jumping up and down in my mind in the back row desparate to get called on: looking up that topic in the “scala bible” turned up the explanation: the ~
character is in the highest precedence category together with #
. to avoid having to use parenthesis we changed our “andThenInto” operator to &~>
:
[sourcecode language=”scala”] implicit def pimpCssBindFuncWithAndThenInto(first: (NodeSeq) => NodeSeq) = new { def &~>(second: (NodeSeq) => NodeSeq): (NodeSeq) => NodeSeq = (ns: NodeSeq) => second(first(ns)) } [/sourcecode]
which then yields the desired result:
"#animal ^^" #> "ignore" &~> "img [src]" #> "dog.png"
res10: (scala.xml.NodeSeq) => scala.xml.NodeSeq = <function1>
scala> res10(in)
res10(in)
res11: scala.xml.NodeSeq = NodeSeq(<span id="animal"><img src="dog.png"></img></span>)
scala>
:-)