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

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

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

import com.raquo.airstream.ownership.DynamicOwner
import com.raquo.laminar.DomApi
import com.raquo.laminar.inserters.InserterHooks
import org.scalajs.dom

import scala.scalajs.js

trait ParentNode[+Ref <: dom.Element] extends ReactiveNode[Ref] {

  private[nodes] val dynamicOwner: DynamicOwner = new DynamicOwner(() => {
    val path = DomApi.debugPath(ref).mkString(" > ")
    throw new Exception(s"Attempting to use owner of unmounted element: $path")
  })

}

object ParentNode {

  type Base = ParentNode[dom.Element]

  // @Note End users, you should achieve your DOM manipulation goals using the many <-- and --> methods that Laminar offers.
  //  Those arrow methods are in fact very flexible. Only use the methods below when really needed, and even then, very carefully.
  //  The methods below are safe to use IFF you're not doing crazy stuff.
  //  - For example, don't mess with nodes managed by any other Laminar code such as `child <-- ...` or `children <-- ...`.
  //  - Also, avoid inserting / removing / moving RELEVANT nodes while any other DOM update operation is in process
  //    - You don't want to affect the DOM subtree that is being modified with your own ad hoc modifications
  //    - It should be ok to update UNRELATED nodes though

  /** Note: can also be used to move children, even within the same parent
    *
    * @return Whether child was successfully appended
    */
  def appendChild(
    parent: ParentNode.Base,
    child: ChildNode.Base,
    hooks: js.UndefOr[InserterHooks]
  ): Boolean = {
    val nextParent = Some(parent)
    child.willSetParent(nextParent)

    // 1. Update DOM
    hooks.foreach(_.onWillInsertNode(parent = parent, child = child))
    val appended = DomApi.appendChild(parent = parent.ref, child = child.ref)
    if (appended) {
      // 3. Update child
      child.setParent(nextParent)
    }
    appended
  }

  /** @return Whether child was successfully removed */
  def removeChild(
    parent: ParentNode.Base,
    child: ChildNode.Base
  ): Boolean = {
    var removed = false
    if (child.ref.parentNode == parent.ref) {
      child.willSetParent(None)
      removed = DomApi.removeChild(parent = parent.ref, child = child.ref)
      child.setParent(None)
    }
    removed
  }

  def insertChildBefore(
    parent: ParentNode.Base,
    newChild: ChildNode.Base,
    referenceChild: ChildNode.Base,
    hooks: js.UndefOr[InserterHooks]
  ): Boolean = {
    val nextParent = Some(parent)
    newChild.willSetParent(nextParent)
    hooks.foreach(_.onWillInsertNode(parent = parent, child = newChild))
    val inserted = DomApi.insertBefore(
      parent = parent.ref,
      newChild = newChild.ref,
      referenceChild = referenceChild.ref
    )
    newChild.setParent(nextParent)
    inserted
  }

  def insertChildAfter(
    parent: ParentNode.Base,
    newChild: ChildNode.Base,
    referenceChild: ChildNode.Base,
    hooks: js.UndefOr[InserterHooks]
  ): Boolean = {
    val nextParent = Some(parent)
    newChild.willSetParent(nextParent)
    hooks.foreach(_.onWillInsertNode(parent = parent, child = newChild))
    val inserted = DomApi.insertAfter(
      parent = parent.ref,
      newChild = newChild.ref,
      referenceChild = referenceChild.ref
    )
    newChild.setParent(nextParent)
    inserted
  }

  /** Note: this method can also be used to move children,
    * even within the same parent
    *
    * @return Whether child was successfully inserted
    */
  def insertChildAtIndex(
    parent: ParentNode.Base,
    child: ChildNode.Base,
    index: Int,
    hooks: js.UndefOr[InserterHooks]
  ): Boolean = {
    var inserted = false
    val nextParent = Some(parent)

    child.willSetParent(nextParent)
    hooks.foreach(_.onWillInsertNode(parent = parent, child = child))

    val children = parent.ref.childNodes
    inserted = if (index < children.length) {
      val referenceChild = children(index)
      DomApi.insertBefore(
        parent = parent.ref,
        newChild = child.ref,
        referenceChild = referenceChild
      )
    } else {
      DomApi.appendChild(parent = parent.ref, child = child.ref)
    }

    if (inserted) {
      child.setParent(nextParent)
    }

    inserted
  }

  /** Note: Does nothing if `oldChild` was not found in parent's children, or if `oldChild==newChild`
    *
    * @return Whether child was replaced
    */
  def replaceChild(
    parent: ParentNode.Base,
    oldChild: ChildNode.Base,
    newChild: ChildNode.Base,
    hooks: js.UndefOr[InserterHooks]
  ): Boolean = {
    var replaced = false
    if (oldChild ne newChild) {
      if (oldChild.maybeParent.contains(parent)) {
        val newChildNextParent = Some(parent)

        oldChild.willSetParent(None)
        newChild.willSetParent(newChildNextParent)
        hooks.foreach(_.onWillInsertNode(parent = parent, child = newChild))

        replaced = DomApi.replaceChild(
          parent = parent.ref,
          newChild = newChild.ref,
          oldChild = oldChild.ref
        )

        if (replaced) {
          oldChild.setParent(None)
          newChild.setParent(newChildNextParent)
        }
      }
    }
    replaced
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy