Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.lrng.binding.html.scala Maven / Gradle / Ivy
package org.lrng
package binding
import com.thoughtworks.binding.Binding._
import com.thoughtworks.binding._
import com.thoughtworks.binding.bindable.{Bindable, BindableSeq}
import org.lrng.binding.dynamicanyref.{AnyRefApplyDynamic, AnyRefSelectDynamic}
import org.scalajs.dom._
import org.scalajs.dom.raw._
import scala.annotation._
import scala.language.experimental.macros
import scala.language.{dynamics, implicitConversions}
import scala.reflect.macros.whitebox
import scala.scalajs.js
object html {
def mount(parent: Node, children: BindingSeq[Node]): Binding[Unit] = {
new NodeBindingSeq.NodeSeqMountPoint(parent, children)
}
def render[Children](parent: Node, children: Children)(implicit
bindableSeq: BindableSeq.Lt[Children, Node]
) = {
mount(parent, bindableSeq.toBindingSeq(children)).watch()
}
/** The factory class to create [[ElementBuilder]] */
trait ElementFactory[E <: Element] extends Curried {
protected def tagName: String
@inline def applyBegin = new NodeBinding.Constant.ElementBuilder(
document.createElement(tagName).asInstanceOf[E]
)
}
object AttributeFactory {
private object Typed {
final class AttributeMountPointBuilder[E <: Element, A <: Typed](implicit
attributeSetter: NodeBinding.Constant.AttributeSetter[E, A]
) extends NodeBinding.Interpolated.MountPointBuilder[E, A, String] {
def mountProperty(e: E, binding: Binding[String]): Binding[Unit] = {
Binding.BindingInstances.map(binding)(
attributeSetter.setAttribute(e, _)
)
}
}
final class OptionAttributeMountPointBuilder[E <: Element, A <: Typed](
implicit
attributeSetter: NodeBinding.Constant.AttributeSetter[E, A],
attributeRemover: NodeBinding.Constant.AttributeRemover[E, A]
) extends NodeBinding.Interpolated.MountPointBuilder[
E,
A,
Option[String]
] {
def mountProperty(
e: E,
binding: Binding[Option[String]]
): Binding[Unit] = {
Binding.BindingInstances.map(binding)(optValue =>
optValue match {
case Some(value) => attributeSetter.setAttribute(e, value)
case _ => attributeRemover.removeAttribute(e)
}
)
}
}
}
trait Typed extends Curried {
@inline
implicit def attributeMountPointBuilder[E <: Element](implicit
attributeSetter: NodeBinding.Constant.AttributeSetter[E, this.type]
): NodeBinding.Interpolated.MountPointBuilder[E, this.type, String] =
new Typed.AttributeMountPointBuilder[E, this.type]
@inline
implicit def optionalAttributeMountPointBuilder[E <: Element](implicit
attributeSetter: NodeBinding.Constant.AttributeSetter[E, this.type],
attributeRemover: NodeBinding.Constant.AttributeRemover[E, this.type]
): NodeBinding.Interpolated.MountPointBuilder[E, this.type, Option[
String
]] =
new Typed.OptionAttributeMountPointBuilder[E, this.type]
@inline
def applyBegin =
new NodeBinding.Constant.MultipleAttributeBuilder.Typed[this.type](this)
@inline
def apply() =
new NodeBinding.Constant.AttributeBuilder.Typed[this.type]("")
@inline
def apply(textBuilder: NodeBinding.Constant.TextBuilder) =
new NodeBinding.Constant.AttributeBuilder.Typed[this.type](
textBuilder.data
)
@inline def apply[A <: Binding[Any]](value: A) =
new NodeBinding.Interpolated.PropertyBuilder[this.type, A](value)
}
final class Untyped(private val attributeName: String)
extends AnyVal
with Curried {
@inline
protected def setAttribute(element: Element, value: String) = {
element.setAttribute(attributeName, value)
}
@inline
def applyBegin =
new NodeBinding.Constant.MultipleAttributeBuilder.Untyped(this)
@inline
def apply() =
new NodeBinding.Constant.AttributeBuilder.Untyped(setAttribute(_, ""))
@inline
def apply(textBuilder: NodeBinding.Constant.TextBuilder) =
new NodeBinding.Constant.AttributeBuilder.Untyped(
setAttribute(_, textBuilder.data)
)
protected type Self = Untyped
def apply(binding: Binding[String]) =
new NodeBinding.Interpolated.AttributeBuilder({ element =>
Binding.BindingInstances.map(binding)(
element.setAttribute(attributeName, _)
)
})
def apply(
optionalBinding: Binding[Option[String]]
)(implicit dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit) =
new NodeBinding.Interpolated.AttributeBuilder({ element =>
Binding.BindingInstances.map(optionalBinding) {
case Some(value) => element.setAttribute(attributeName, value)
case _ => element.removeAttribute(attributeName)
}
})
// def apply(optionalBinding: Binding[Option[String]])(implicit dummyImplicit: DummyImplicit =
// DummyImplicit.dummyImplicit): Unit = new NodeBinding.Interpolated.AttributeBuilder({ element =>
// Binding.BindingInstances.map(optionalBinding)(optValue => optValue match {
// case Some(value) => attributeSetter.setAttribute(e, value)
// case _ => attributeRemover.removeAttribute(e)element.setAttribute(attributeName, _))
// })
}
}
type NodeBindingSeq[+A] = BindingSeq[A] {
def value: collection.Seq[A]
}
object NodeBindingSeq {
@inline
@tailrec
private def removeAll(parent: Node): Unit = {
val firstChild = parent.firstChild
if (firstChild != null) {
parent.removeChild(firstChild)
removeAll(parent)
}
}
private[lrng] final class NodeSeqMountPoint(
parent: Node,
childrenBinding: BindingSeq[Node]
) extends MultiMountPoint[Node](childrenBinding) {
protected def set(children: Seq[Node]): Unit = {
set(children: Iterable[Node])
}
protected def set(children: Iterable[Node]): Unit = {
removeAll(parent)
for (child <- children) {
if (child.parentNode != null) {
throw new IllegalStateException(
raw"""Cannot insert ${child.toString} twice!"""
)
}
parent.appendChild(child)
}
}
protected def splice(
from: Int,
that: Iterable[Node],
replaced: Int
): Unit = {
spliceGenIterable(from, that, replaced)
}
protected def splice(
from: Int,
that: collection.GenSeq[Node],
replaced: Int
): Unit = {
spliceGenIterable(from, that, replaced)
}
private def spliceGenIterable(
from: Int,
that: collection.GenIterable[Node],
replaced: Int
): Unit = {
@inline
@tailrec
def removeChildren(child: Node, n: Int): Node = {
if (n == 0) {
child
} else {
val nextSibling = child.nextSibling
parent.removeChild(child)
removeChildren(nextSibling, n - 1)
}
}
val child = removeChildren(parent.childNodes(from), replaced)
if (child == null) {
for (newChild <- that) {
if (newChild.parentNode != null) {
throw new IllegalStateException(
raw"""Cannot insert a ${newChild.toString} element twice!"""
)
}
parent.appendChild(newChild)
}
} else {
for (newChild <- that) {
if (newChild.parentNode != null) {
throw new IllegalStateException(
raw"""Cannot insert a ${newChild.toString} element twice!"""
)
}
parent.insertBefore(newChild, child)
}
}
}
}
final class Builder(
val childNodes: js.Array[Node] = new js.Array(0),
val mountPoints: js.Array[Binding[Unit]] = new js.Array(0)
) extends ChildBuilder {
def applyEnd: NodeBindingSeq[Node] = {
if (childNodes.isEmpty) {
Constants(scalajs.runtime.toScalaVarArgs(childNodes): _*)
} else {
new NodeBindingSeq.Interpolated(childNodes, mountPoints)
}
}
}
type Constants[+A] = Binding.Constants[A]
final class Interpolated private[lrng] (
private[lrng] val nodes: js.Array[Node] = new js.Array(0),
private[lrng] val mountPoints: js.Array[Binding[Unit]] = new js.Array(0)
) extends BindingSeq[Node] {
type All[+A] = collection.Seq[A]
def value: collection.Seq[Node] = nodes
private var referenceCount = 0
protected def addPatchedListener(
listener: PatchedListener[Node]
): Unit = {
if (referenceCount == 0) {
mountPoints.foreach(_.watch())
}
referenceCount += 1
}
protected def removePatchedListener(
listener: PatchedListener[Node]
): Unit = {
referenceCount -= 1
if (referenceCount == 0) {
mountPoints.foreach(_.unwatch())
}
}
}
}
type NodeBinding[+A] = Binding[A] {
val value: A
}
object NodeBinding {
object Constant {
@implicitNotFound("${AttributeObject} is not an attribute of ${E}")
final class AttributeSetter[-E <: Element, AttributeObject](
val setAttribute: (E, String) => Unit
) extends AnyVal
@implicitNotFound("${AttributeObject} is not an attribute of ${E}")
final class AttributeRemover[-E <: Element, AttributeObject](
val removeAttribute: (E) => Unit
) extends AnyVal
final class ElementBuilder[+E <: Element] private[lrng] (
private[lrng] val element: E
) extends AnyVal {
@inline
def applyNext[AttributeObject](child: AttributeBuilder.Untyped) = {
child.set(element)
this
}
@inline
def applyNext[AttributeObject](
child: AttributeBuilder.Typed[AttributeObject]
)(implicit attributeSetter: AttributeSetter[E, AttributeObject]) = {
attributeSetter.setAttribute(element, child.value)
this
}
def applyNext(child: Binding[String]) = {
toInterpolated.applyNext(child)
}
def applyNext[Child](
child: Binding[Child]
)(implicit bindableSeq: BindableSeq.Lt[Child, Node]) = {
toInterpolated.applyNext(child)
}
@inline
def applyNext(child: NodeBinding.Interpolated.AttributeBuilder)(implicit
dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit
) = {
toInterpolated.applyNext(child)
}
@inline
def applyNext[Property, Expected](
child: NodeBinding.Interpolated.PropertyBuilder[Property, Binding[
Expected
]]
)(implicit
mountPointBuilder: Interpolated.MountPointBuilder[
E,
Property,
Expected
]
) = {
toInterpolated.applyNext(child)
}
@inline
def applyNext(child: NodeBinding.Constant.ElementBuilder[Element]) = {
toInterpolated.applyNext(child)
}
def applyNext(
child: NodeBinding.Interpolated.ElementBuilder[Element]
) = {
toInterpolated.applyNext(child)
}
@inline
def applyNext(child: NodeBinding.Constant.NodeBuilder[Node]) = {
toInterpolated.applyNext(child)
}
@inline
def applyNext(child: NodeBinding.Constant.TextBuilder) = {
toInterpolated.applyNext(child)
}
@inline
def applyEnd = this
private def toInterpolated =
new NodeBinding.Interpolated.ElementBuilder[E](element)
}
final class NodeBuilder[+Node] private[lrng] (
private[lrng] val node: Node
) extends AnyVal
final class TextBuilder private[lrng] (private[lrng] val data: String)
extends AnyVal
object AttributeBuilder {
final class Untyped(private[lrng] val set: Element => Unit)
extends AnyVal
final class Typed[A](private[lrng] val value: String) extends AnyVal
}
object MultipleAttributeBuilder {
final class Typed[A <: AttributeFactory.Typed](
private[html] val attributeFactory: A,
stringBuilder: StringBuilder = new StringBuilder
) {
def applyNext(textBuilder: TextBuilder) = {
stringBuilder ++= textBuilder.data
this
}
def applyEnd = {
new AttributeBuilder.Typed[A](stringBuilder.toString())
}
}
final class Untyped(
attributeFactory: AttributeFactory.Untyped,
stringBuilder: StringBuilder = new StringBuilder
) {
def applyNext(textBuilder: TextBuilder) = {
stringBuilder ++= textBuilder.data
this
}
def applyEnd: AttributeBuilder.Untyped = {
attributeFactory(new TextBuilder(stringBuilder.toString()))
}
}
}
}
type Constant[+A] = Binding.Constant[A]
final class Interpolated[+A] private[lrng] (
val value: A,
private[lrng] val mountPoints: js.Array[Binding[Unit]]
) extends Binding[A] {
private var referenceCount = 0
protected def addChangedListener(listener: ChangedListener[A]): Unit = {
if (referenceCount == 0) {
mountPoints.foreach(_.watch())
}
referenceCount += 1
}
protected def removeChangedListener(
listener: ChangedListener[A]
): Unit = {
referenceCount -= 1
if (referenceCount == 0) {
mountPoints.foreach(_.unwatch())
}
}
}
object Interpolated {
final class AttributeBuilder(val mountPoint: Element => Binding[Unit])
extends AnyVal
final class PropertyBuilder[PropertyType, Value](val value: Value)
extends AnyVal
@implicitNotFound(
"${AttributeObject} of type ${V} is not a property of ${E}"
)
trait MountPointBuilder[-E <: Element, AttributeObject, -V] {
def mountProperty(e: E, binding: Binding[V]): Binding[Unit]
}
object MountPointBuilder {
implicit def convertedMountPointBuilder[E <: Element, A, V0, V1](
implicit
mountPointBuilder: MountPointBuilder[E, A, V1],
bindable: Bindable.Lt[Binding[V0], V1]
): MountPointBuilder[E, A, V0] = new MountPointBuilder[E, A, V0] {
def mountProperty(e: E, binding: Binding[V0]): Binding[Unit] = {
mountPointBuilder.mountProperty(e, bindable.toBinding(binding))
}
}
}
final class ElementBuilder[+E <: Element](
val element: E,
val bindingSeqs: js.Array[Binding[BindingSeq[Node]]] =
new js.Array(0),
var childNodes: js.Array[Node] = new js.Array(0),
val mountPoints: js.Array[Binding[Unit]] = new js.Array(0)
) extends ChildBuilder {
private[lrng] def mergeTrailingNodes(): Unit = {
if (childNodes.nonEmpty) {
bindingSeqs += Binding.Constant(
Binding.Constants(scalajs.runtime.toScalaVarArgs(childNodes): _*)
)
childNodes = new js.Array(0)
}
}
private[lrng] def flush(): Unit = {
mergeTrailingNodes()
if (bindingSeqs.nonEmpty) {
mountPoints += mount(
element,
Constants(scalajs.runtime.toScalaVarArgs(bindingSeqs): _*)
.flatMapBinding(identity)
)
}
}
@inline
def applyEnd = this
def applyNext(child: Binding[String]) = {
val textNode = document.createTextNode("")
childNodes += textNode
mountPoints += Binding.BindingInstances.map(child)(textNode.data_=)
this
}
def applyNext[Child](
child: Binding[Child]
)(implicit bindableSeq: BindableSeq.Lt[Child, Node]) = {
mergeTrailingNodes()
bindingSeqs += Binding.BindingInstances.map(child)(
bindableSeq.toBindingSeq(_)
)
this
}
@inline
def applyNext[AttributeObject](
attributeBuilder: NodeBinding.Constant.AttributeBuilder.Untyped
) = {
attributeBuilder.set(element)
this
}
@inline
def applyNext[AttributeObject](
attributeBuilder: NodeBinding.Constant.AttributeBuilder.Typed[
AttributeObject
]
)(implicit
attributeSetter: Constant.AttributeSetter[E, AttributeObject]
) = {
attributeSetter.setAttribute(element, attributeBuilder.value)
this
}
@inline
def applyNext(
attributeBuilder: NodeBinding.Interpolated.AttributeBuilder
)(implicit
dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit
) = {
mountPoints += attributeBuilder.mountPoint(element)
this
}
@inline
def applyNext[Property, Expected](
attributeBuilder: NodeBinding.Interpolated.PropertyBuilder[
Property,
Binding[Expected]
]
)(implicit
mountPointBuilder: MountPointBuilder[E, Property, Expected]
) = {
mountPoints += mountPointBuilder.mountProperty(
element,
attributeBuilder.value
)
this
}
}
}
}
private[lrng] trait ChildBuilder {
def childNodes: js.Array[Node]
def mountPoints: js.Array[Binding[Unit]]
@inline
def applyNext(
elementBuilder: NodeBinding.Constant.ElementBuilder[Element]
): this.type = {
childNodes += elementBuilder.element
this
}
def applyNext(
elementBuilder: NodeBinding.Interpolated.ElementBuilder[Element]
): this.type = {
if (
elementBuilder.bindingSeqs.isEmpty && elementBuilder.mountPoints.isEmpty
) {
val childElement = elementBuilder.element
elementBuilder.childNodes.foreach(childElement.appendChild)
childNodes += childElement
} else {
elementBuilder.flush()
childNodes += elementBuilder.element
mountPoints.push(
scalajs.runtime.toScalaVarArgs(elementBuilder.mountPoints): _*
)
}
this
}
@inline
def applyNext(
nodeBuilder: NodeBinding.Constant.NodeBuilder[Node]
): this.type = {
childNodes += nodeBuilder.node
this
}
@inline
def applyNext(textBuilder: NodeBinding.Constant.TextBuilder): this.type = {
childNodes += document.createTextNode(textBuilder.data)
this
}
}
private[html] trait LowPriorityAutoImports {
// TODO: move to https://github.com/ThoughtWorksInc/bindable.scala
implicit def bindableBindableSeq[From, BindableValue, Value0](implicit
bindableSeq: BindableSeq.Aux[From, BindableValue],
bindable: Bindable.Aux[BindableValue, Value0]
): BindableSeq.Aux[From, Value0] =
new BindableSeq[From] {
type Value = Value0
def toBindingSeq(from: From): BindingSeq[Value] = {
bindableSeq.toBindingSeq(from).mapBinding(bindable.toBinding(_))
}
}
}
private[lrng] object elementTypes {
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLTimeElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLDataElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLPictureElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLOutputElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLMeterElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLDetailsElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLDialogElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLTemplateElement <: HTMLElement
/** @todo
* Remove this type alias once scala-js-dom added the definition for this
* type
*/
type HTMLSlotElement <: HTMLElement
}
object autoImports extends LowPriorityAutoImports {
object data {
@inline def entities = EntityBuilders
@inline def interpolation = Binding
object values
extends AnyRefSelectDynamic[NodeBinding.Constant.TextBuilder] {
@inline def selectDynamic(data: String) =
new NodeBinding.Constant.TextBuilder(data)
@inline def selectDynamic: NodeBinding.Constant.TextBuilder =
selectDynamic("selectDynamic")
}
object attributes extends AnyRefApplyDynamic {
object applyDynamic {
@inline def apply(attributeName: String): AttributeFactory.Untyped =
new AttributeFactory.Untyped(attributeName)
@inline implicit def asUntyped(
a: => applyDynamic.type
): AttributeFactory.Untyped =
new AttributeFactory.Untyped("applyDynamic")
}
}
}
// TODO: move to https://github.com/ThoughtWorksInc/bindable.scala
implicit def jsFunctionBindable[A, R]
: Bindable.Aux[Binding[A => R], js.Function1[A, R]] =
new Bindable[Binding[A => R]] {
type Value = js.Function1[A, R]
def toBinding(from: Binding[A => R]): Binding[js.Function1[A, R]] = {
Binding.BindingInstances.map(from)(f => f: js.Function1[A, R])
}
}
// TODO: move to https://github.com/ThoughtWorksInc/bindable.scala
implicit def bindingBindingSeqBindableSeq[BindableValue, Value0](implicit
bindable: Bindable.Aux[BindableValue, Value0]
): BindableSeq.Aux[BindingSeq[BindableValue], Value0] =
new BindableSeq[BindingSeq[BindableValue]] {
type Value = Value0
def toBindingSeq(from: BindingSeq[BindableValue]): BindingSeq[Value] = {
from.mapBinding(bindable.toBinding(_))
}
}
object xml {
@inline def noPrefix[Uri](uri: Uri) = uri
object prefixes extends Dynamic {
@inline def getClass[Uri](uri: Uri) = uri
@inline def !=[Uri](uri: Uri) = uri
@inline def ##[Uri](uri: Uri) = uri
@inline def +[Uri](uri: Uri) = uri
@inline def ->[Uri](uri: Uri) = uri
@inline def ==[Uri](uri: Uri) = uri
@inline def asInstanceOf[Uri](uri: Uri) = uri
@inline def ensuring[Uri](uri: Uri) = uri
@inline def eq[Uri](uri: Uri) = uri
@inline def equals[Uri](uri: Uri) = uri
@inline def formatted[Uri](uri: Uri) = uri
@inline def hashCode[Uri](uri: Uri) = uri
@inline def isInstanceOf[Uri](uri: Uri) = uri
@inline def ne[Uri](uri: Uri) = uri
@inline def notify[Uri](uri: Uri) = uri
@inline def notifyAll[Uri](uri: Uri) = uri
@inline def synchronized[Uri](uri: Uri)(implicit
dummyImplicit: DummyImplicit = DummyImplicit.dummyImplicit
) =
uri
@inline def toString[Uri](uri: Uri) = uri
@inline def wait[Uri](uri: Uri) = uri
@inline def →[Uri](uri: Uri) = uri
object applyDynamic {
@inline def applyDynamic[Uri](uri: Uri) = uri
}
case class applyDynamic(prefix: String) extends AnyVal {
@inline def apply[Uri](uri: Uri) = uri
}
}
object uris {
@inline
def `http://www.w3.org/1999/xhtml` = xml
}
@inline def elements = ElementFactories
@inline def attributes = AttributeFactories
@inline def entities = EntityBuilders
@inline def cdata(data: String) =
new NodeBinding.Constant.NodeBuilder(document.createCDATASection(data))
object values
extends AnyRefSelectDynamic[NodeBinding.Constant.TextBuilder] {
@inline def selectDynamic: NodeBinding.Constant.TextBuilder =
selectDynamic("selectDynamic")
@inline def selectDynamic(data: String) =
new NodeBinding.Constant.TextBuilder(data)
}
/** @note
* All methods in [[AnyRef]] are overloaded, in case of name clash
* {{{
* import html.autoImports.xml.texts
* import html.NodeBinding.Constant.TextBuilder
* texts.selectDynamic should be(a[TextBuilder])
* texts.getClass should be(a[TextBuilder])
* texts.!= should be(a[TextBuilder])
* texts.## should be(a[TextBuilder])
* texts.+ should be(a[TextBuilder])
* texts.-> should be(a[TextBuilder])
* texts.== should be(a[TextBuilder])
* texts.asInstanceOf should be(a[TextBuilder])
* texts.ensuring should be(a[TextBuilder])
* texts.eq should be(a[TextBuilder])
* texts.equals should be(a[TextBuilder])
* texts.formatted should be(a[TextBuilder])
* texts.hashCode should be(a[TextBuilder])
* texts.isInstanceOf should be(a[TextBuilder])
* texts.ne should be(a[TextBuilder])
* texts.notify should be(a[TextBuilder])
* texts.notifyAll should be(a[TextBuilder])
* texts.synchronized should be(a[TextBuilder])
* texts.toString should be(a[TextBuilder])
* texts.wait should be(a[TextBuilder])
* texts.→ should be(a[TextBuilder])
* }}}
*/
object texts
extends AnyRefSelectDynamic[NodeBinding.Constant.TextBuilder] {
@inline def selectDynamic: NodeBinding.Constant.TextBuilder =
selectDynamic("selectDynamic")
@inline def selectDynamic(data: String) =
new NodeBinding.Constant.TextBuilder(data)
}
@inline def comment(data: String) =
new NodeBinding.Constant.NodeBuilder(document.createComment(data))
object processInstructions extends Dynamic {
@inline def getClass(data: String) = applyDynamic("getClass")(data)
@inline def !=(data: String) = applyDynamic("!=")(data)
@inline def ##(data: String) = applyDynamic("##")(data)
@inline def +(data: String) = applyDynamic("+")(data)
@inline def ->(data: String) = applyDynamic("->")(data)
@inline def ==(data: String) = applyDynamic("==")(data)
@inline def asInstanceOf(data: String) =
applyDynamic("asInstanceOf")(data)
@inline def ensuring(data: String) = applyDynamic("ensuring")(data)
@inline def eq(data: String) = applyDynamic("eq")(data)
@inline def equals(data: String) = applyDynamic("equals")(data)
@inline def formatted(data: String) = applyDynamic("formatted")(data)
@inline def hashCode(data: String) = applyDynamic("hashCode")(data)
@inline def isInstanceOf(data: String) =
applyDynamic("isInstanceOf")(data)
@inline def ne(data: String) = applyDynamic("ne")(data)
@inline def notify(data: String) = applyDynamic("notify")(data)
@inline def notifyAll(data: String) = applyDynamic("notifyAll")(data)
@inline def synchronized(data: String) =
applyDynamic("synchronized")(data)
@inline def toString(data: String) = applyDynamic("toString")(data)
@inline def wait(data: String) = applyDynamic("wait")(data)
@inline def →(data: String) = applyDynamic("→")(data)
@inline def applyDynamic(target: String)(data: String) =
new NodeBinding.Constant.NodeBuilder(
document.createProcessingInstruction(target, data)
)
}
@inline def interpolation = Binding
object literal extends Curried {
@inline def apply[Node](
nodeBuilder: NodeBinding.Constant.NodeBuilder[Node]
) = Constant(nodeBuilder.node)
@inline def apply[E <: Element](
elementBuilder: NodeBinding.Constant.ElementBuilder[E]
) =
Constant(elementBuilder.element)
def apply[E <: Element](
elementBuilder: NodeBinding.Interpolated.ElementBuilder[E]
): NodeBinding[E] = {
if (
elementBuilder.mountPoints.isEmpty && elementBuilder.bindingSeqs.isEmpty
) {
val element = elementBuilder.element
elementBuilder.childNodes.foreach(element.appendChild)
Constant(element)
} else {
elementBuilder.flush()
new NodeBinding.Interpolated(
elementBuilder.element,
elementBuilder.mountPoints
)
}
}
@inline def applyBegin = new NodeBindingSeq.Builder
}
}
}
private[lrng] final class WhiteBoxMacros(context: whitebox.Context)
extends nameBasedXml.Macros(context) {
import c.universe._
override protected def transformBody(tree: Tree): Tree = q"""
import _root_.org.lrng.binding.html.autoImports.{
!= => _,
## => _,
== => _,
eq => _,
equals => _,
getClass => _,
hashCode => _,
ne => _,
notify => _,
notifyAll => _,
synchronized => _,
toString => _,
wait => _,
_
}
${new NameBasedXmlTransformer().transform(tree)}
"""
}
}
/** An annotation to convert XHTML literals to data-bindable DOM nodes.
* @author
* 杨博 (Yang Bo) <[email protected] >
*
* @example
* XHTML literals with text attributes
* {{{
* @html
* val mySpan =
* mySpan.value.nodeName should be("SPAN")
* mySpan.value.className should be("my-class")
* mySpan.value.tabIndex should be(42)
* }}}
*
* Nested XHTML literals with interpolation:
*
* {{{
* @html
* val mySpan2 = text {mySpan.bind}
* mySpan2.watch()
* mySpan2.value.outerHTML should be("""text html """)
* }}}
*
* @example
* Special character in attribute names
* {{{
* @html
* val myMeta =
* myMeta.watch()
* myMeta.value.outerHTML should be(""" """)
* }}}
*
* @example
* Opaque type aliases of HTMLInputElement
* {{{
* @html
* val myMeta =
* myMeta.watch()
* myMeta.value.outerHTML should be(""" """)
* }}}
*
* @example
* Element list of XHTML literals
*
* {{{
* @html
* val mySpans =
* mySpans.watch()
* import org.scalajs.dom.html.Span
* inside(mySpans.value) {
* case collection.Seq(span1: Span, span2: Span, span3: Span) =>
* span1.nodeName should be("SPAN")
* span1.hasAttribute("class") should be(false)
* span1.className should be("")
* span2.title should be("my title")
* span2.hasAttribute("class") should be(true)
* span2.className should be("")
* span3.className should be("my-class")
* }
* }}}
*
* @example
* Text interpolation in an element
* {{{
* @html val monadicSpan = {"text"}
* monadicSpan.watch()
* assert(monadicSpan.value.outerHTML == "text ")
* }}}
*
* @example
* Changing text
* {{{
* import com.thoughtworks.binding.Binding.Var
* val v0 = Var("original text")
* @html val monadicSpan = {v0.bind}
* monadicSpan.watch()
* assert(monadicSpan.value.outerHTML == " original text ")
* v0.value = "changed"
* assert(monadicSpan.value.outerHTML == " changed ")
* }}}
*
* @example
* `for` / `yield` expressions in XHTML interpolation
* {{{
* import com.thoughtworks.binding.Binding.Vars
* val v0 = Vars("original text 0","original text 1")
* @html val monadicSpan = { for (s <- v0) yield {s} }
* monadicSpan.watch()
* val span = monadicSpan.value
* assert(monadicSpan.value.outerHTML == " original text 0 original text 1 ")
* v0.value.prepend("prepended")
* assert(span eq monadicSpan.value)
* assert(monadicSpan.value.outerHTML == " prepended original text 0 original text 1 ")
* v0.value.remove(1)
* assert(span eq monadicSpan.value)
* assert(monadicSpan.value.outerHTML == " prepended original text 1 ")
* }}}
*
* @example
* `for` / `yield` / `if` expressions in XHTML interpolation
* {{{
* import com.thoughtworks.binding.Binding
* import com.thoughtworks.binding.Binding.Var
* import com.thoughtworks.binding.Binding.Vars
* final case class User(firstName: Var[String], lastName: Var[String], age: Var[Int])
* val filterPattern = Var("")
* val users = Vars(
* User(Var("Steve"), Var("Jobs"), Var(10)),
* User(Var("Tim"), Var("Cook"), Var(12)),
* User(Var("Jeff"), Var("Lauren"), Var(13))
* )
* def shouldShow(user: User): Binding[Boolean] = Binding {
* val pattern = filterPattern.bind
* if (pattern == "") {
* true
* } else if (user.firstName.bind.toLowerCase.contains(pattern)) {
* true
* } else if (user.lastName.bind.toLowerCase.contains(pattern)) {
* true
* } else {
* false
* }
* }
*
* @html
* def tbodyBinding = {
* {
* for {
* user <- users
* if shouldShow(user).bind
* } yield {user.firstName.bind} {user.lastName.bind} {user.age.bind.toString}
* }
* }
*
* @html
* val tableBinding = {
* First Name Second Name Age {tbodyBinding.bind}
* }
* tableBinding.watch()
* assert(tableBinding.value.outerHTML == """First Name Second Name Age Steve Jobs 10 Tim Cook 12 Jeff Lauren 13
""")
* filterPattern.value = "o"
* assert(tableBinding.value.outerHTML == """First Name Second Name Age Steve Jobs 10 Tim Cook 12
""")
* }}}
*
* @example
* Dynamic attributes
* {{{
* @html
* val myBr =
* myBr.watch()
* myBr.value.outerHTML should be(""" """)
* }}}
*
* @example
* Changing attribute values
* {{{
* import com.thoughtworks.binding.Binding.Var
* val id = Var("oldId")
* @html val myInput = id.value = "newId" }/>
* myInput.watch()
* assert(myInput.value.outerHTML == """ """)
* myInput.value.onclick(null)
* assert(myInput.value.outerHTML == """ """)
* }}}
*
* @example
* A child node must not be inserted more than once
* {{{
* an[IllegalStateException] should be thrownBy {
* @html
* val child =
* @html
* val parent = {child.bind} {child.bind}
* parent.watch()
* }
* }}}
*
* @example
* Seq in DOM
* {{{
* import org.scalajs.dom.document
* @html def mySelect = {
* {Seq(data1 , data2 )}
* }
* val span = document.createElement("span")
* html.render(span, mySelect)
* span.firstChild.childNodes.length should be(2)
* }}}
*
* @example
* XHTML comments
* {{{
* import org.scalajs.dom.document
* @html def comment =
* val span = document.createElement("span")
* html.render(span, comment)
* assert(span.innerHTML == " ")
* }}}
*
* @example
* Escape
* {{{
* import org.scalajs.dom.document
* @html def escaped = $minus
* val span = document.createElement("span")
* html.render(span, escaped)
* assert(span.innerHTML == " $minus ")
* }}}
* @example
* Entity references
* {{{
* import org.scalajs.dom.document
* @html def entity = my text < > © λ
* val span = document.createElement("span")
* html.render(span, entity)
* assert(span.innerHTML == """my text < > © λ """)
* }}}
* @example
* Process instructions
* {{{
* @html val myXmlStylesheet =
* myXmlStylesheet.watch()
* myXmlStylesheet.value.target should be("my-instruction")
* myXmlStylesheet.value.data should be("my data")
* }}}
* @example
* CDATA sections are not supported in HTML documents
* {{{
* import scala.scalajs.js
* a[js.JavaScriptException] should be thrownBy {
* @html val myCData =
* myCData.watch()
* }
* }}}
*
* @example
* XML namespaces
* {{{
* import scala.language.dynamics
* import org.scalajs.dom._
* import org.scalajs.dom.raw._
* import com.thoughtworks.binding.Binding
* import com.thoughtworks.binding.Binding.BindingInstances.monadSyntax._
* object svg {
* object texts extends Dynamic {
* @inline def selectDynamic(data: String) = new _root_.org.lrng.binding.html.NodeBinding.Constant.TextBuilder(data)
* }
* object elements {
* object svg extends Curried {
* @inline def applyBegin = new _root_.org.lrng.binding.html.NodeBinding.Constant.ElementBuilder(document.createElementNS("http://www.w3.org/2000/svg", "svg").asInstanceOf[SVGSVGElement])
* }
* object text extends Curried {
* @inline def applyBegin = new _root_.org.lrng.binding.html.NodeBinding.Constant.ElementBuilder(document.createElementNS("http://www.w3.org/2000/svg", "text").asInstanceOf[SVGTextElement])
* }
* }
* @inline def interpolation = Binding
* object values {
* sealed trait FontStyle
* case object normal extends FontStyle
* case object italic extends FontStyle
* case object oblique extends FontStyle
* }
* object attributes {
* def font$minusstyle(value: values.FontStyle) = {
* new _root_.org.lrng.binding.html.NodeBinding.Constant.AttributeBuilder.Untyped(_.setAttribute("font-style", value.toString))
* }
* def font$minusstyle(binding: Binding[values.FontStyle]) = {
* new _root_.org.lrng.binding.html.NodeBinding.Interpolated.AttributeBuilder( element =>
* binding.map { value =>
* element.setAttribute("font-style", value.toString)
* }
* )
* }
* }
* }
* implicit final class SvgUriOps(uriFactory: _root_.org.lrng.binding.html.autoImports.xml.uris.type) {
* @inline def http$colon$div$divwww$u002Ew3$u002Eorg$div2000$divsvg = svg
* }
* @html
* val mySvg1 = my text
* mySvg1.watch()
* mySvg1.value.outerHTML should be("""my text """)
*
* import svg.values.normal
* @html
* val mySvg2 = my text
* mySvg2.watch()
* mySvg2.value.outerHTML should be("""my text """)
* }}}
*/
@compileTimeOnly("enable macro paradise to expand macro annotations")
class html extends StaticAnnotation {
def macroTransform(annottees: Any*): Any =
macro _root_.org.lrng.binding.html.WhiteBoxMacros.macroTransform
}