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

com.raquo.laminar.setters.ChildrenCommandSetter.scala Maven / Gradle / Ivy

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

import com.raquo.airstream.eventstream.EventStream
import com.raquo.domtypes.generic.Modifier
import com.raquo.laminar.DomApi
import com.raquo.laminar.collection.CollectionCommand
import com.raquo.laminar.nodes.{ReactiveChildNode, ReactiveComment, ReactiveElement}
import com.raquo.laminar.setters.ChildrenSetter.Child
import org.scalajs.dom

import scala.scalajs.js

class ChildrenCommandSetter(
  commandStream: EventStream[ChildrenCommandSetter.ChildrenCommand]
) extends Modifier[ReactiveElement[dom.Element]] {

  import ChildrenCommandSetter.updateList

  override def apply(parentNode: ReactiveElement[dom.Element]): Unit = {
    var nodeCount = 0

    val sentinelNode = new ReactiveComment("")
    parentNode.appendChild(sentinelNode)(DomApi.treeApi)

    parentNode.subscribe(commandStream) { command =>
      val nodeCountDiff = updateList(
        command,
        parentNode = parentNode,
        sentinelNode = sentinelNode,
        nodeCount
      )
      nodeCount += nodeCountDiff
    }
  }
}

object ChildrenCommandSetter {

  type ChildrenCommand = CollectionCommand[Child]

  def updateList(
    command: ChildrenCommandSetter.ChildrenCommand,
    parentNode: ReactiveElement[dom.Element],
    sentinelNode: ReactiveChildNode[dom.Comment],
    nodeCount: Int
  ): Int = {
    var nodeCountDiff = 0
    command match {
      case CollectionCommand.Append(node) =>
        val sentinelIndex = parentNode.indexOfChild(sentinelNode)
        if (parentNode.insertChild(node, atIndex = sentinelIndex + nodeCount + 1)(DomApi.treeApi)) {
          nodeCountDiff = 1
        }
        nodeCountDiff = 1
      case CollectionCommand.Prepend(node) =>
        val sentinelIndex = parentNode.indexOfChild(sentinelNode)
        if (parentNode.insertChild(node, atIndex = sentinelIndex + 1)(DomApi.treeApi)) {
          nodeCountDiff = 1
        }
      case CollectionCommand.Insert(node, atIndex) =>
        val sentinelIndex = parentNode.indexOfChild(sentinelNode)
        if (parentNode.insertChild(node, atIndex = sentinelIndex + atIndex + 1)(DomApi.treeApi)) {
          nodeCountDiff = 1
        }
      case CollectionCommand.Remove(node) =>
        if (parentNode.removeChild(node)(DomApi.treeApi)) {
          nodeCountDiff = -1
        }
      case CollectionCommand.Move(node, toIndex) =>
        // @TODO same as Insert.
        // @TODO Should we also add a MoveToEnd method?
        val sentinelIndex = parentNode.indexOfChild(sentinelNode)
        if (parentNode.insertChild(node, atIndex = sentinelIndex + toIndex + 1)(DomApi.treeApi)) {
          nodeCountDiff = 1
        }
      case CollectionCommand.Replace(node, withNode) =>
        parentNode.replaceChild(oldChild = node, newChild = withNode)(DomApi.treeApi)
      case CollectionCommand.ReplaceAll(newNodes) =>
        val sentinelIndex = parentNode.indexOfChild(sentinelNode)
        if (nodeCount == 0) {
          var numInsertedNodes = 0
          newNodes.foreach { newChild =>
            if (parentNode.insertChild(newChild, atIndex = sentinelIndex + 1 + numInsertedNodes)(DomApi.treeApi)) {
              numInsertedNodes += 1
            }
          }
          nodeCountDiff = numInsertedNodes
        } else {
          val oldNodeCount = nodeCount
          val replaced = parentNode.replaceChildren(
            fromIndex = sentinelIndex + 1,
            toIndex = sentinelIndex + nodeCount,
            js.Array(newNodes.toSeq: _*) // @TODO[Performance]
          )(DomApi.treeApi)
          if (replaced) {
            nodeCountDiff = newNodes.size - oldNodeCount
          }
        }
    }
    nodeCountDiff
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy