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

com.raquo.laminar.nodes.ReactiveHtmlElement.scala Maven / Gradle / Ivy

The newest version!
package com.raquo.laminar.nodes

import com.raquo.airstream.ownership.DynamicSubscription
import com.raquo.ew.JsArray
import com.raquo.laminar.DomApi
import com.raquo.laminar.inputs.InputController
import com.raquo.laminar.keys.{HtmlProp, Key}
import com.raquo.laminar.tags.{CustomHtmlTag, HtmlTag}
import org.scalajs.dom

import scala.scalajs.js

class ReactiveHtmlElement[+Ref <: dom.html.Element](
  override val tag: HtmlTag[Ref],
  final override val ref: Ref
) extends ReactiveElement[Ref] {

  /** List of value controllers installed on this element */
  private[this] var controllers: js.UndefOr[JsArray[InputController[_, _, _]]] = js.undefined

  /**
    * List of binders for props that are controllable.
    * Note: this includes both controlled and uncontrolled binders
    */
  private[this] var controllablePropBinders: js.UndefOr[JsArray[String]] = js.undefined

  private[this] def appendValueController(controller: InputController[_, _, _]): Unit = {
    controllers.fold {
      controllers = js.defined(JsArray(controller))
    }(_.push(controller))
  }

  private[this] def appendControllablePropBinder(propDomName: String): Unit = {
    controllablePropBinders.fold {
      controllablePropBinders = js.defined(JsArray(propDomName))
    }(_.push(propDomName))
  }

  private[laminar] def hasBinderForControllableProp(domPropName: String): Boolean = {
    controllablePropBinders.exists(_.includes(domPropName))
  }

  private[laminar] def hasOtherControllerForSameProp(thisController: InputController[_, _, _]): Boolean = {
    controllers.exists(_.asScalaJs.exists { otherController =>
      otherController.propDomName == thisController.propDomName && otherController != thisController
    })
  }

  private[laminar] def bindController(controller: InputController[_, _, _]): DynamicSubscription = {
    val dynSub = controller.bind()
    appendValueController(controller)
    dynSub
  }

  // --

  private[laminar] def controllableProps: js.UndefOr[JsArray[String]] = {
    if (DomApi.isCustomElement(ref)) {
      tag match {
        case t: CustomHtmlTag[_] => t.allowableControlProps
        case _ => js.undefined
      }
    } else {
      InputController.htmlControllableProps
    }
  }

  private[laminar] def isControllableProp(propDomName: String): Boolean = {
    controllableProps.exists(_.includes(propDomName))
  }

  private def hasController(propDomName: String): Boolean = {
    controllers.exists(_.asScalaJs.exists(_.propDomName == propDomName))
  }

  override private[laminar] def onBoundKeyUpdater(key: Key): Unit = {
    key match {
      case p: HtmlProp[_, _] =>
        if (isControllableProp(p.name)) {
          if (hasController(p.name)) {
            throw new Exception(s"Can not add uncontrolled `${p.name} <-- ???` to element `${DomApi.debugNodeDescription(ref)}` that already has an input controller for `${p.name}` property.")
          } else {
            appendControllablePropBinder(p.name)
          }
        }
      case _ => ()
    }
  }

  override def toString: String = {
    // `ref` is not available inside ReactiveElement's constructor due to initialization order, so fall back to `tag`.
    s"ReactiveHtmlElement(${if (ref != null) ref.outerHTML else s"tag=${tag.name}"})"
  }
}

object ReactiveHtmlElement {

  type Base = ReactiveHtmlElement[dom.html.Element]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy