scala lift环境搭建(8)

Binding via CSS Selectors

Lift 2.2-M1 introduced a new mechanism for transforming XHTML: CSS Selector Transforms (CssBindFunc).
The new mechanism provides a subset of CSS selectors that can be used to transform NodeSeq => NodeSeq.
CSS Selector Transforms offer an alternative to Lift’s traditional binding (See Helpers.bind()).
Most recent documentation: Simply Lift @ 7.10
Examples of this feature include:

"#name" #> userName – replace the element with the id name with the variable userName

使用变量userName替换id为name的元素

"#chat_lines *" #> listOfChats – replace the content of chat_lines with each element of listOfChats

用listOfChats中的每一个元素替换chat_lines的内容

".pretty *" #> <b>Unicorn</b> – each element with CSS class pretty, replace content with <b>Unicorn</b>

使用<b>Unicorn</b>替换class是pretty的每一个元素的内容

"dog=cat [href]" #> "http://dogscape.com" – set the href attribute of all elements with the dog attribute set to cat


"#name" #> userName&"#age" #> userAge – set name to userName and age to userAge

设置name为userName,同时设置age为userAge

"#name ^^" #> "str" – select the element with the id name, “str” can be anything and will not be used

选择id是name的元素

"#name ^*" #> "str" – select child of the element with the id name, “str” can be anything and will not be used

选择id是name的元素的子元素


CSS Selector Transforms extends NodeSeq => NodeSeq … they are quite literally functions and can be passed as a parameter to anything expecting NodeSeq =>NodeSeq or returned as a result for any method that returns NodeSeq => NodeSeq.

Let’s look at each of the pieces to see how they work.
Imports

First, you must add these imports
import net.liftweb.util._
import Helpers._


These packages include the classes and the implicit conversions that make the CSS Selector transforms work.

The transform is defined by: selector #> transform value
Selector and replacement rules

The selector is a String constant which implements the following subset of CSS Selectors:

#id – selects the element with the specified id
.class – selects all elements have a class attribute where one of the space-separated values equals class
attr_name=attr_value – selects all elements where the given attribute equals the given value
* – selects the current element. Useful for binding multiple values to an element (e.g. attributes and content)

You can put replacement rules after the selector:

none e.g. #id replaces all matching elements with the values, merging attributes of input and output elements.


"#name" #> "David" transforms 
<span><span id="name"/></span> to

<span>David</span>


"#name" #> <span>David</span> transforms
<span class="foo" id="name"/> to

<span id="name" class="foo">David</span>


* e.g. #id * replaces the content children of the matching elements with the values


"#name *" #> "David" transforms
<span><span id="name"/></span> to

<span><span id="name">David</span></span>


[attr] e.g. #id [href] replaces the matching attribute’s value with the values.


"#link [href]" #> "http://dogscape.com" transforms
<a href="#" id="link">Dogscape</a> to

<a href="http://dogscape.com" id="link">Dogscape</a>


[attr+] e.g. #id [class+] appends the matching attribute’s value with the values.

#id [class=]可以追加值到匹配的属性值上

"#link [class+]" #> "bar" transforms
<a class="foo" href="http://dogscape.com" id="link">Dogscape</a> to

<a class="foo bar" href="http://dogscape.com" id="link">Dogscape</a>


Transform values

The right hand side of the CSS Selector Transform can be one of the following:


String – a String constant, for example:

"#name *" #> getUserNameAsString or
"#name *" #> "David" transforms
<span id="name"/> to

<span id="name">David</span>


NodeSeq – a NodeSeq constant, for example:
"#name *" #> getUserNameAsHtml or
"#name *" #> <i>David</i> transforms
<span id="name"/> to

<span id="name"><i>David</i></span>


NodeSeq =>NodeSeq — a function that transforms the node (yes, it can be a CssBindFunc):
"#name" #> ((n:NodeSeq) => ("* [class]" #> "dog")(n)) transforms
<span id="name"/> to

<span id="name" class="dog"/>


Useful for debugging, to see which node was selected:

"#name" #> ((n: NodeSeq) =>{println("node found: " + n); "David" })


Bindable – something that implements theBindable trait (e.g.,MappedField andRecord.Field )
StringPromotable — A constant that can be promoted to a String (Int, Symbol, Long orBoolean ). There is an automatic (implicit) conversion from Int, Symbol, Long or Boolean to StringPromotable.
"#id_like_cats" #> true
"#number_of_cats" #> 2
IterableConst – A Box, Seq, or Option of String, NodeSeq, or Bindable.

Implicit conversions automatically promote the likes of Box[String], List[String], List[NodeSeq], etc. to IterableConst.


"#id" #> (Empty: Box[String]) transforms <span><span id="id">Hi</span></span> to <span/>


"#id" #> List("a", "b", "c") transforms <span><span id="id"/></span> to <span>abc</span>


"#id [href]" #> (None: Option[String]) transforms <a id="id" href="dog"/> to <a id="id"/>


IterableFunc — ABox ,Seq , orOption of functions that transform NodeSeq => String, NodeSeq, Seq[String], Seq[NodeSeq], Box[String], Box[NodeSeq], Option[String] orOption[NodeSeq].
Implicit conversions automatically promote the functions with the appropriate signature to an IterableFunc .
Binding to children

Note that if you bind to the children of a selected element, multiple copies of the element result from bind to an IterableConst or IterableFunc (if the element has an id attribute, the id attribute will be stripped after the first element):


"#line *" #> List("a", "b", "c") transforms
<li id="line>sample</li> to

<li id="line">a</li><li>b</li><li>c</li>


Using the fact that the transform value can be a NodeSeq =>NodeSeq (in this case a CssBindFunc):
".row *" #> (".cell *" #> "Fred" )
transforms
<table>
<tr class="row"><td class="cell">Name</td></tr>
</table>
to
<table>
<tr class="row"><td class="cell">Fred</td></tr>

</table>


Using the fact that the transform value can be a IterableFunc (in this case a Seq of CssBindFunc’s):
".row *" #> List((".cell *" #> "Fred" ),(".cell *" #> "Bob" ))
or equivalently:
val names = List("Fred","Bob")
".row *" #> names.map(x => ".cell *" #> x)
transforms
<table>
<tr class="row"><td class="cell">Name</td></tr>
</table>
to
<table>
<tr class="row"><td class="cell">Fred</td></tr>
<tr class="row"><td class="cell">Bob</td></tr>

</table>


Another common use case is to transform a single placeholder tag into multiple tags of same type, each with an attribute or content (or both) set to the subsequent values, taken from a List[String]. A useful scenario can be to produce a series of <img> tags like so:

val names = List("dog", "cat", "cow")

".container" #> { ".repeater" #> (names.map(name =>
/* set .repeater's src attribute to name.jpg */
"* [src]" #> (name+".jpg") &
/* append name to .repeater's alt attribute */
"* [alt+]" #> name &
/* NOTE: this last transform is only for demonstration as proper
* HTML <img> tag should normally have no text content.
* replace .repeater's content with name */
"* *" #> name
))}

transforms

<div class="container">
<img src="blank.gif" alt="picture of a " class="repeater"/>
</div>
to

<div class="container">
<img class="repeater" alt="picture of a dog" src="dog.jpg">dog</img>
<img class="repeater" alt="picture of a cat" src="cat.jpg">cat</img>
<img class="repeater" alt="picture of a cow" src="cow.jpg">cow</img>

</div>


If the Iterable is empty-ish (Nil, Seq.empty, Empty, None, etc.), the result is a single, empty child element:
"#age *" #> (None: Option[NodeSeq])
transforms
<span><span id="age">Dunno</span></span>
to
<span><span id="age"></span></span>

The above use cases may seem a little strange (they are not quite orthogonal), but they address common use cases in Lift.
Binding several values to the same element

You can bind both attributes and children as follows:
<tr>
<td class="values">123</td>
</tr>

".values" #> things.map {
case x if x.last => "* *" #> x.toString & "* [class+]" #> "last"
case x => "* *" #> x.toString
}
Chaining

You can chain CSS Selector Transforms with the & method:

"#id" #> "33" & "#name" #> "David" & "#chat_line" #> List("a", "b", "c") & ClearClearable
Transformation application order

If you chain several transformation in one method, you have to be careful:
if an Elem is being replaced, the CSS Selector Transforms are not applied to its children (either before or after the replacement) ;
if child Nodes of an Elem are being replaced, the CSS Selector Transforms are not applied to the children either before or after the transform.

So, if you to transform the following HTML:
val xml = <div id="foo"><span id="bar">BAR</span></div>

Into:
<div id="foo"><div><span id="bar">REPLACED</span></div></div>

You have to either use andThen in place of & chaining method, or be explicit about where transformation are applied.

Using and then:
(
"#foo *" #> { (n:NodeSeq) => <div>{n}</div> } andThen
"#bar *" #> "REPLACED"
)(xml)

Being explicit about where transformation are applied:
("#foo *" #> ((n: NodeSeq) => <div>{("#bar *" #> "REPLACED")(n)}</div>) )(xml)
Example use cases
Grab some data from db and build a html representation
Template:
<ul class="lift:domain.listAll allDomains" >
<li class="domain"><a class="link" href=""></a></li>
</ul>
Snippet:
def listAll =
{
val items = model.Domain.findAll;
".domain *" #> items.map(d =>
".link *" #> d.Name &
".link [href]" #> "/domain/%s".format(d.Slug))
};
Testing using the Scala console

CSS Selector Transforms can easily be tested at the Scala console (REPL).

Example:
scala> import net.liftweb.util._
scala> import Helpers._
scala> import scala.xml._

scala> val xml = <html><head></head><body><div class="test">abcde<span>fghij</span></div></body></html>
scala> ("span *" #> "This is replaced...")(xml)
res3: scala.xml.NodeSeq = NodeSeq(<html><head></head><body><div class="test">abcde<span>This is replaced...</span></div></body></html>)
Removing attributes
val blank: Option[String] = None
"#thing [class]" #> blank
Iteration

In this example, we make it explicit that we have a single function
rather than a collection of functions that are applied to the children
of .people. Then we map across the collection which will apply the
transformation for each of the elements of the collection to the nodes
and flatten the result out into a NodeSeq

case class Person(name: String, age: Int)

val people: List[Person] = List(Person("Maria", 24), Person("Peter", 31))

".people *" #> ((ns: NodeSeq) =>
(people.flatMap(p => (".name *" #> p.name & ".age *" #> p.age)(ns))))


transforms:

<ul class="people">
<li>
<span class="name">Name</span>: <span class="age">24</span>
</li>
</ul>


into:

<ul class="people">
<li>
<span class="name">Maria</span>: <span class="age">24</age>
</li>
<li>
<span class="name">Peter</span>: <span class="age">31</age>
</li>
</ul>

Replace tags, keep content

Transfor this:
<people version="1" otherProp="prop">
<name>foo</name>
<friends><name>bar</name></name><name>baz</name></friends>
</people>

into this:
<user version="2" otherProp="prop">
<login>foo</login>
<knows><name>bar</name></name><name>baz</name></knows>
</user>

Use this css rule:
"people" #> ((ns: NodeSeq) => <user version="2">{
val kids = ns.asInstanceOf[Elem].child
("name" #> ((ns: NodeSeq) => <login>{ns.asInstanceOf[Elem].child}</login>)).apply(kids)
}</user>)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值