Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom components #129

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 128 additions & 18 deletions dom/src/main/scala/com/thoughtworks/binding/dom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ SOFTWARE.
package com.thoughtworks.binding

import Binding.{BindingSeq, Constants, MultiMountPoint, SingleMountPoint, SingletonBindingSeq}
import dom.Runtime.NodeSeqMountPoint
import dom.Runtime.{NodeMountPoint, NodeSeqMountPoint}
import com.thoughtworks.Extractor._
import com.thoughtworks.binding.XmlExtractor.{PrefixedName, QName, UnprefixedName}
import com.thoughtworks.sde.core.Preprocessor
import macrocompat.bundle
import org.scalajs.dom.raw._

import scala.annotation.{StaticAnnotation, compileTimeOnly, tailrec}
import scala.annotation.{StaticAnnotation, compileTimeOnly, implicitNotFound, tailrec}
import scala.collection.GenSeq
import scala.reflect.macros.whitebox
import scala.language.experimental.macros
Expand Down Expand Up @@ -64,9 +64,93 @@ class dom extends StaticAnnotation {
// @deprecated(message = "Use `@html` instead", since = "11.0.0")
object dom {

private[dom] sealed trait LowPriorityRuntime {
private[dom] sealed trait LowPriorityMountable2 {
@inline
final def notEqual[A, B](left: A, right: B, dummy: Unit = ()) = left != right
implicit final def mountableBindingBindingSeq[Parent, Child](
implicit mountableBindingSeq: Mountable[Parent, BindingSeq[Child]]): Mountable[Parent, Child] =
new Mountable[Parent, Child] {
def mount(parent: Parent, child: Child): Binding[Unit] = {
mountableBindingSeq.mount(parent, Constants(child))
}
}
}
private[dom] sealed trait LowPriorityMountable1 {
@inline
implicit final def mountableBindingBindingSeq[Parent, Child](
implicit mountableBindingSeq: Mountable[Parent, BindingSeq[Child]])
: Mountable[Parent, Binding[BindingSeq[Child]]] =
new Mountable[Parent, Binding[BindingSeq[Child]]] {
def mount(parent: Parent, children: Binding[BindingSeq[Child]]): Binding[Unit] = {
mountableBindingSeq.mount(parent, Constants(children).flatMapBinding(identity))
}
}
}

private[dom] sealed trait LowPriorityMountable0 extends LowPriorityMountable1 {
@inline
implicit final def mountableBinding[Parent, Child](
implicit mountableBindingSeq: Mountable[Parent, BindingSeq[Child]]): Mountable[Parent, Binding[Child]] =
new Mountable[Parent, Binding[Child]] {
def mount(parent: Parent, child: Binding[Child]): Binding[Unit] = {
mountableBindingSeq.mount(parent, SingletonBindingSeq(child))
}
}
}

@implicitNotFound("Don't know how to mount ${Children} into ${Parent}.")
trait Mountable[-Parent, -Children] {
def mount(parent: Parent, children: Children): Binding[Unit]
}

object Mountable extends LowPriorityMountable0 {
@inline
implicit final def mountableBindingSeqNode[Parent <: Node, Child <: Node]: Mountable[Parent, BindingSeq[Child]] =
new Mountable[Parent, BindingSeq[Child]] {
def mount(parent: Parent, children: BindingSeq[Child]): NodeSeqMountPoint = {
new NodeSeqMountPoint(parent, children)
}
}

@inline
implicit final def mountableBindingBindingSeqNode[Parent <: Node, Child <: Node]
: Mountable[Parent, Binding[BindingSeq[Child]]] =
new Mountable[Parent, Binding[BindingSeq[Child]]] {
def mount(parent: Parent, children: Binding[BindingSeq[Child]]): NodeSeqMountPoint = {
new NodeSeqMountPoint(parent, children, ())
}
}

@inline
implicit final def mountableBindingNode[Parent <: Node, Child <: Node]: Mountable[Parent, Binding[Child]] =
new Mountable[Parent, Binding[Child]] {
def mount(parent: Parent, child: Binding[Child]): NodeMountPoint = {
new NodeMountPoint(parent, child)
}
}
}
private[dom] sealed trait LowPriorityRuntime1 {
@inline
final def domBindingSeq[A](x: A) = Constants(x)
}

private[dom] sealed trait LowPriorityRuntime extends LowPriorityRuntime1 {

@inline
final def domBindingSeq[A](x: Binding[A])(implicit dummy: DummyImplicit): SingletonBindingSeq[A] = {
SingletonBindingSeq(x)
}

@inline
final def domBindingSeq[A](x: Option[A])(implicit dummy: DummyImplicit): Constants[A] = {
Constants(x.toSeq: _*)
}

@inline
final def domBindingSeq[A](x: BindingSeq[A])(implicit dummy: DummyImplicit): BindingSeq[A] = x

@inline
final def notEqual[A, B](left: A, right: B, dummy: Unit = ()): Boolean = left != right

}

@inline
Expand All @@ -86,6 +170,28 @@ object dom {
*/
object Runtime extends LowPriorityRuntime {

@inline
def mount(parent: Node, children: Binding[Seq[Node]])(implicit dummy0: DummyImplicit,
dummy1: DummyImplicit,
dummy2: DummyImplicit) = {
new NodeSeqMountPoint(parent, Constants(children).flatMap { s =>
Constants(s.bind: _*)
})
}

@inline
def mount(parent: Node, childOption: Binding[Option[Node]])(implicit dummy0: DummyImplicit,
dummy1: DummyImplicit) = {
new NodeSeqMountPoint(parent, Constants(childOption).flatMap { o =>
Constants(o.bind.toSeq: _*)
})
}

@inline
def mount(parent: Node, text: Binding[String])(implicit dummy: DummyImplicit): NodeMountPoint = {
new NodeMountPoint(parent, Binding { document.createTextNode(text.bind) })
}

@inline
def mount(parent: Node, childrenBinding: BindingSeq[Node]): NodeSeqMountPoint = {
new NodeSeqMountPoint(parent, childrenBinding)
Expand All @@ -101,14 +207,17 @@ object dom {
new NodeMountPoint(parent, childBinding)
}

final class NodeMountPoint private[Runtime] (parent: Node, childBinding: Binding[Node])
@inline
def mount[Parent, Children](parent: Parent, children: Children)(
implicit mountable: Mountable[Parent, Children]): Binding[Unit] = {
mountable.mount(parent, children)
}

final class NodeMountPoint private[dom] (parent: Node, childBinding: Binding[Node])
extends SingleMountPoint[Node](childBinding) {
protected def set(child: Node): Unit = {
removeAll(parent)
if (child.parentNode != null) {
throw new IllegalStateException(raw"""Cannot insert ${child.nodeName} twice!""")
}
parent.appendChild(child)
checkedAppendChild(parent, child)
}
}

Expand All @@ -129,10 +238,7 @@ object dom {
override protected def set(children: Seq[Node]): Unit = {
removeAll(parent)
for (child <- children) {
if (child.parentNode != null) {
throw new IllegalStateException(raw"""Cannot insert ${child.nodeName} twice!""")
}
parent.appendChild(child)
checkedAppendChild(parent, child)
}
}

Expand All @@ -152,10 +258,7 @@ object dom {
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.nodeName} element twice!""")
}
parent.appendChild(newChild)
checkedAppendChild(parent, newChild)
}
} else {
for (newChild <- that) {
Expand Down Expand Up @@ -204,6 +307,13 @@ object dom {
def notEqual[A](left: A, right: A) = left != right
}

private def checkedAppendChild(parent: Node, child: Node): Unit = {
if (child.parentNode != null) {
throw new IllegalStateException(raw"""Cannot insert ${child.nodeName} twice!""")
}
parent.appendChild(child)
}

/**
* This object contains implicit views imported automatically for @dom methods.
*/
Expand Down Expand Up @@ -506,7 +616,7 @@ object dom {
case Seq(child) =>
val (valDefs, transformedChild) = transformXml(child)
valDefs -> atPos(child.pos) {
q"""_root_.com.thoughtworks.binding.dom.Runtime.domBindingSeq($transformedChild)"""
q"""_root_.com.thoughtworks.binding.Binding.apply($transformedChild)"""
}
case _ =>
val transformedPairs = (for {
Expand Down
48 changes: 48 additions & 0 deletions dom/src/test/scala/com/thoughtworks/binding/domMountableSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.thoughtworks.binding
import com.thoughtworks.binding.Binding.{BindingSeq, MultiMountPoint}
import com.thoughtworks.binding.dom.Mountable
import com.thoughtworks.binding.dom.Runtime.TagsAndTags2
import org.scalajs.dom.html.HR
import org.scalatest.{FreeSpec, Inside, Matchers}

import scala.collection.GenSeq
import scala.scalajs.js

/**
* @author 杨博 (Yang Bo)
*/
final class domMountableSpec extends FreeSpec with Matchers with Inside {
"Mountable JavaScript WrappedArray" in {
implicit def mountableJsArray[Element, Child](implicit ev: BindingSeq[Child] <:< BindingSeq[Element])
: Mountable[js.WrappedArray[Element], BindingSeq[Child]] =
new Mountable[js.WrappedArray[Element], BindingSeq[Child]] {
def mount(parent: js.WrappedArray[Element], children: BindingSeq[Child]): Binding[Unit] =
new MultiMountPoint[Element](children) {
protected def set(children: Seq[Element]): Unit = {
parent.clear()
parent ++= children
}
protected def splice(from: Int, that: GenSeq[Element], replaced: Int): Unit = {
parent.splice(from + 1, replaced, that.seq: _*)
}
}
}

implicit final class ArrayTagOps(tagsAndTags2: TagsAndTags2.type) {
object array {
def render = js.WrappedArray.empty[Any]
}
}

@dom
val a = <array><hr/><array/><array><array/><array/></array></array>

a.watch()

inside(a.get) {
case js.WrappedArray(hr: HR, js.WrappedArray(), js.WrappedArray(js.WrappedArray(), js.WrappedArray())) =>
hr.tagName.toLowerCase should be("hr")
}

}
}
Loading