All Downloads are FREE. Search and download functionalities are using the official Maven repository.

teststate.domzipper.DomZipperJsF.scala Maven / Gradle / Ivy

The newest version!
package teststate.domzipper

import org.scalajs.dom
import org.scalajs.dom.html
import scala.reflect.ClassTag
import scala.scalajs.js
import teststate.domzipper.DomZipper.{CssSelResult, DomCollection, Layer}
import teststate.domzipper.ErrorHandler.ErrorHandlerOptionOps

object DomZipperJsF {
  type Dom = dom.Element

  type DomCollection[F[_], C[_], A] = DomZipper.DomCollection[DomZipperJsF, F, C, Dom, A]

  type CssSelEngine = DomZipper.CssSelEngine[Dom, Dom]

  def safeCastDom[F[_], D <: Dom](dom: Dom)(implicit ct: ClassTag[D], F: ErrorHandler[F]): F[D] =
    ct.unapply(dom) orFail s"${dom.nodeName} is not a ${ct.runtimeClass}."

  def liftNode[F[_]](n: dom.Node)(implicit F: ErrorHandler[F]): F[Dom] =
    n match {
      case e: dom.Element => F pass e
      case x              => F fail s"Not an element: $x"
    }

  private val rootDomFn: DomZipperBase.Layers[Dom] => Dom =
    _.latest.dom

  final class Constructors[F[_]](implicit F: ErrorHandler[F]) {

    def root(implicit $: CssSelEngine, scrub: HtmlScrub): DomZipperJsF[F, Dom] =
      apply("window.document.children(0)", dom.window.document.children(0))

    def body(implicit $: CssSelEngine, scrub: HtmlScrub): DomZipperJsF[F, Dom] =
      apply("window.document.body", dom.window.document.body)

    def apply(dom: Dom)(implicit $: CssSelEngine, scrub: HtmlScrub): DomZipperJsF[F, Dom] =
      apply("", dom)

    def apply(name: String, dom: Dom)(implicit $: CssSelEngine, scrub: HtmlScrub): DomZipperJsF[F, Dom] =
      new DomZipperJsF(DomZipperBase.Layers init Layer(name, "", dom), rootDomFn)

    def failBy[G[_]: ErrorHandler]: Constructors[G]   = new Constructors
    def failToOption: Constructors[Option           ] = failBy(ErrorHandler.ReturnOption)
    def failToEither: Constructors[Either[String, *]] = failBy(ErrorHandler.ReturnEither)

    type DomCollection [      C[_], A] = DomZipperJsF.DomCollection[F, C, A]
    type DomCollectionF[G[_], C[_], A] = DomZipperJsF.DomCollection[G, C, A]
  }
}

import DomZipperJsF.{CssSelEngine, Dom, liftNode, rootDomFn, safeCastDom}

final class DomZipperJsF[F[_], A](override protected val layers: DomZipperBase.Layers[Dom],
                                  override protected val peek: DomZipperBase.Layers[Dom] => A
                                )(implicit
                                  override protected val $: CssSelEngine,
                                  override protected[domzipper] val htmlScrub: HtmlScrub,
                                  override protected val F: ErrorHandler[F]
                                ) extends DomZipperBase.WithStore[F, Dom, A, DomZipperJsF] {

  override protected def newStore[B](pos: Pos, peek: Peek[B]): DomZipperJsF[F, B] =
    new DomZipperJsF(pos, peek)

  override def isCapable(c: DomZipper.Capability) = c match {
    case DomZipper.Capability.RadioButtonChecked => true
  }

  override def unmap =
    new DomZipperJsF(layers, rootDomFn)

  override protected def self = this

  protected def copySelf[G[_]](h: HtmlScrub, g: ErrorHandler[G]): DomZipperJsF[G, A] =
    new DomZipperJsF(layers, peek)($, h, g)

  override protected def _parent: F[Dom] =
    liftNode(dom.parentNode)

  override protected def _outerHTML: String =
    dynamicString(_.outerHTML)

  override protected def _innerHTML: String =
    dynamicString(_.innerHTML)

  private def newDomCollection[C[_]](desc: String, result: CssSelResult[Dom], C: DomCollection.Container[F, C]) =
    DomCollection[DomZipperJsF, F, C, Dom, Dom, A](desc, enrichErr, result, C)(addLayer)

  protected def collect[C[_]](sel: String, C: DomCollection.Container[F, C]) =
    newDomCollection(sel, runCssQuery(sel), C)

  private def childIterator: Iterator[Dom] =
    dom.childNodes.iterator.collect {
      case e: org.scalajs.dom.Element => e
    }

  protected def collectChildren[C[_]](desc: String, C: DomCollection.Container[F, C]) =
    newDomCollection(desc, childIterator.toVector, C)

  protected def collectChildren[C[_]](desc: String, sel: String, C: DomCollection.Container[F, C]) = {
    val all = runCssQuery(sel).toSet
    newDomCollection(desc, childIterator.filter(all.contains).toVector, C)
  }

  override def matches(css: String): F[Boolean] =
    F pass dom.matches(css)

  override def getAttribute(name: String): Option[String] =
    Option(dom.attributes.getNamedItem(name)).map(_.value)

  override def tagName: String =
    dom.tagName

  override def innerText: String =
    dom.textContent.trim

  override def checked: F[Boolean] =
    dynamicMethod[Boolean](_.checked) orFail s".checked failed on $dom."

  override def classes: Set[String] =
    dom match {
      case h: html.Element =>
        val c = h.classList
        (0 until c.length).iterator.map(c.apply).toSet
      case _ =>
        Set.empty
    }

  override def value: F[String] =
    dynamicMethod[String](_.value.toString) orFail s".value failed on $dom."

  /** Cast DOM to [[js.Dynamic]] and invoke a method expected to return `A` if successful. */
  def dynamicMethod[B](f: js.Dynamic => Any): Option[B] =
    f(dom.asInstanceOf[js.Dynamic]).asInstanceOf[js.UndefOr[B]].toOption

  /** Cast DOM to [[js.Dynamic]], invoke a method, return the result as a `String`. */
  def dynamicString(f: js.Dynamic => Any): String =
    dynamicMethod[Any](f).fold("undefined")(_.toString)

  def domAs[D <: Dom](implicit ct: ClassTag[D]): F[D] =
    safeCastDom[F, D](dom)

  def domAsHtml: F[html.Element] =
    domAs[html.Element]

  def domAsButton: F[html.Button] =
    domAs[html.Button]

  def domAsInput: F[html.Input] =
    domAs[html.Input]

  def domAsTextArea: F[html.TextArea] =
    domAs[html.TextArea]

  def forceDomAs[D <: Dom]: D =
    dom.asInstanceOf[D]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy