Skip to content

Commit

Permalink
util: CanBind and CanBindN
Browse files Browse the repository at this point in the history
  • Loading branch information
Naftoli Gugenheim committed Oct 3, 2011
1 parent 03b6e3a commit 5befd59
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,7 @@ trait BindHelpers {
new ToCssBindPromoter(Full(str), CssSelectorParser.parse(str))

/**
* promote a String to a ToCssBindPromotor
* promote a CssSelector to a ToCssBindPromotor
*/
implicit def cssSelectorToCssBindPromoter(sel: CssSelector): ToCssBindPromoter =
new ToCssBindPromoter(Empty, Full(sel))
Expand Down
138 changes: 77 additions & 61 deletions core/util/src/main/scala/net/liftweb/util/CssBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,98 +24,112 @@ import java.util.{List => JavaList}
import scala.collection.mutable.ListBuffer



/**
* A type class that creates CssSel instances for a value of some type
* @param apply a function stringSelector => css => x => cssSel. stringSelector and css are the arguments for CssBindImpl's constructor
* A type class that creates a NodeSeq=>Seq[NodeSeq] for a value of some type.
* The apply method delegates to the function passed to the constructor.
* @param f a function T=>NodeSeq=>Seq[NodeSeq] that returns a NodeSeq=>NodeSeq for a T
*/
class CanBind[-T](val apply: Box[String] => Box[CssSelector] => T => CssSel)
class CanBindN[-T](f: T => NodeSeq => Seq[NodeSeq]) extends (T => NodeSeq => Seq[NodeSeq]) {
/**
* Given a value of type T, return a function that can be used in Lift binding.
* The function takes a NodeSeq (the content from the template) and returns
* a Seq[NodeSeq]. The return value is collection-valued because some types repeatedly
* transform the same template content (e.g., "th *" #> List("Column Header 1", "Column Header 2"))
*/
def apply(v: T) = f(v)
}

object CanBind {
/**
* Defines the CanBindN implicits that are available by default
*/
object CanBindN {
/**
* Inserts a String constant according to the CssSelector rules
* Bind a single bindable value.
* Given a type that has an implicit CanBind, delegate to that CanBind,
* wrapping the result in a List is its single element
*/
implicit val string = new CanBind[String](stringSelector => css => str =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] =
List(if (null eq str) NodeSeq.Empty else Text(str))
}
)
implicit def single[T](implicit canBind: CanBind[T]): CanBindN[T] =
new CanBindN[T](v => ns => List(canBind(v)(ns)))

/**
* Inserts a NodeSeq constant according to the CssSelector rules
* Bind zero or more bindable values.
* Given a type that has an implicit CanBind, and a context viewable as an
* Iterable of that type, apply all the CanBinds
*/
implicit val nodeSeq = new CanBind[NodeSeq](stringSelector => css => ns =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = List(ns)
}
)
implicit def iterable[T, I[_]](implicit canBind: CanBind[T], toIter: I[T]=>Iterable[T]): CanBindN[I[T]] =
new CanBindN[I[T]](v => ns => toIter(v).toSeq map (_ apply ns))

/**
* A function that transforms the content according to the CssSelector rules
* Bind a function that, given a NodeSeq, returns zero or more bindable values.
* Given a type that has an implicit CanBindN, and a context viewable as an
* Iterable of that type, apply all the CanBindNs.
*/
implicit val nodeSeqFunc = new CanBind[NodeSeq=>NodeSeq](stringSelector => css => nsFunc =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = List(nsFunc(in))
}
)
implicit def iterableFunc[T, I[_]](implicit canBindN: CanBindN[T], toIter: I[T]=>Iterable[T]): CanBindN[NodeSeq=>I[T]] =
new CanBindN[NodeSeq=>I[T]](v => ns => toIter(v(ns)).toSeq flatMap (_ apply ns))
}

class CanBind[-T](f: T => NodeSeq => NodeSeq) extends (T => NodeSeq => NodeSeq) {
def apply(v: T) = f(v)
}

//TODO obviate StringPromotable
//TODO obviate Bindable

object CanBind {
/**
* Inserts a Bindable constant according to the CssSelector rules.
* Mapper and Record fields implement Bindable.
* Bind a string by replacing content with a Text node (or NodeSeq.empty in the case of null)
*/
implicit val bindable = new CanBind[Bindable](stringSelector => css => bindable =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = List(bindable.asHtml)
}
)
implicit val string = new CanBind[String](str => _ => (if (null eq str) NodeSeq.Empty else Text(str)))

/**
* Inserts a StringPromotable constant according to the CssSelector rules.
* StringPromotable includes Int, Long, Boolean, and Symbol
* Bind a NodeSeq by replacing content with it
*/
implicit def stringPromotable[T<%StringPromotable] = new CanBind[T](stringSelector => css => strPromo =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] =
List(Text(strPromo.toString))
}
)
implicit val nodeSeq: CanBind[NodeSeq] = new CanBind[NodeSeq](ns => _ => ns)

/**
* Applies the N constants according to the CssSelector rules.
* This allows for Seq[String], Seq[NodeSeq], Box[String],
* Box[NodeSeq], Option[String], Option[NodeSeq]
* Bind a Seq[Node] by replacing content with it, via NodeSeq.fromSeq
*/
implicit def iterableConst[T<%IterableConst] = new CanBind[T](stringSelector => css => itrConst =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = itrConst.constList(in)
}
)
implicit val seqNode: CanBind[Seq[Node]] = new CanBind[Seq[Node]](ns => _ => NodeSeq fromSeq ns)

/**
* Apply the function and then apply the results account the the CssSelector
* rules.
* This allows for NodeSeq => Seq[String], NodeSeq =>Seq[NodeSeq],
* NodeSeq => Box[String],
* NodeSeq => Box[NodeSeq], NodeSeq => Option[String],
* NodeSeq =>Option[NodeSeq]
* Bind a function NodeSeq=>U where U has an implicit CanBind, by applying it
* to content and replacing it with the result
*/
implicit def iterableFunc[T<%IterableFunc] = new CanBind[T](stringSelector => css => itrFunc =>
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = itrFunc(in)
}
)
implicit def func[U](implicit canBind: CanBind[U]): CanBind[NodeSeq => U] =
new CanBind[NodeSeq => U](f => in => f(in) apply in)

/**
* Bind an object that extends Bindable, by calling its asHtml method
* and replacing content with its result.
* Mapper and Record fields implement Bindable.
*/
implicit val bindable: CanBind[Bindable] = new CanBind[Bindable](bindable => _ => bindable.asHtml)

/**
* Bind something that has a conversion to StringPromotable, by
* calling the StringPromotable's toString method and replacing content with it wrapped in a Text node.
* StringPromotable includes Int, Long, Boolean, and Symbol
*/
implicit def stringPromotable[T](implicit view: T=>StringPromotable) = new CanBind[T](strPromo => _ => Text(view(strPromo).toString))


}



/**
* An intermediate class used to promote a String or a CssSelector to
* something that can be associated with a value to apply to the selector
* @param stringSelector the unparsed css selector string
* @param css the parsed CssSelector object
*/
final class ToCssBindPromoter(stringSelector: Box[String], css: Box[CssSelector]) {
def #>[T](v: T)(implicit canBind: CanBind[T]): CssSel = canBind.apply(stringSelector)(css)(v)
def replaceWith[T: CanBind](v: T): CssSel = #>[T](v)
def #>[T](v: T)(implicit canBindN: CanBindN[T]): CssSel =
new CssBindImpl(stringSelector, css) {
def calculate(in: NodeSeq): Seq[NodeSeq] = canBindN.apply(v)(in)
}
def replaceWith[T: CanBindN](v: T): CssSel = #>[T](v)
}


Expand Down Expand Up @@ -196,6 +210,7 @@ object IterableConst {

/**
* Converts anything that can be converted into an Box[NodeSeq]
* Bind a value that has a CanBindN implicit available
*/
implicit def boxNodeSeq(it: Box[NodeSeq]): IterableConst =
NodeSeqIterableConst(it.toList)
Expand All @@ -210,6 +225,7 @@ object IterableConst {
* Converts anything that can be converted into an Iterable[NodeSeq]
* into an IterableConst. This includes Seq[NodeSeq], Option[NodeSeq],
* and Box[NodeSeq]
* Bind a value that has a CanBindN implicit available
*/
implicit def itNodeSeq(it: JavaList[NodeSeq]): IterableConst =
new NodeSeqIterableConst(it)
Expand Down Expand Up @@ -393,7 +409,7 @@ sealed trait CssSel extends Function1[NodeSeq, NodeSeq] {
/**
* Inserts a String constant according to the CssSelector rules
*/
def sel(selector: String, str: IterableConst): CssSel = this & (selector #> str)
def sel(selector: String, str: IterableConst): CssSel = this & (selector #> str.constList _)

/**
* Inserts a String constant according to the CssSelector rules
Expand Down Expand Up @@ -1050,7 +1066,7 @@ final class CssJBridge {
/**
* Inserts a String constant according to the CssSelector rules
*/
def sel(selector: String, str: IterableConst): CssSel = (selector #> str)
def sel(selector: String, str: IterableConst): CssSel = (selector #> str.constList _)

/**
* Inserts a String constant according to the CssSelector rules
Expand Down
19 changes: 18 additions & 1 deletion core/util/src/test/scala/net/liftweb/util/BindHelpersSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,18 @@ object CssBindHelpersSpec extends Specification {
(answer \ "a" \ "@href").text must_== "Hi"
(answer \\ "li").length must_== 0
}


"bind something implicity convertable to a String" in {
class X
implicit def X2str(x: X): String = "This is X"
implicitly[X => String] apply new X must_== "This is X"
(implicitly[X => StringPromotable] apply new X toString) must_== "This is X"
val s: String = new X
s must_== "This is X"
val sp: StringPromotable = new X
sp.toString must_== "This is X"
("foo" #> new X apply <foo/> text) must_== "This is X"
}

}
}
Expand All @@ -908,6 +919,12 @@ object CheckTheImplicitConversionsForToCssBindPromoter {

import BindHelpers._

implicitly[CanBind[NodeSeq]]
implicitly[CanBindN[Iterable[NodeSeq]]]
implicitly[CanBindN[NodeSeq => Iterable[NodeSeq]]]
implicitly[CanBindN[NodeSeq => Seq[Node]]]
implicitly[CanBind[NodeSeq => String]]

"foo" #> "baz"

bog #> "Hello"
Expand Down

0 comments on commit 5befd59

Please sign in to comment.