Skip to content

Commit

Permalink
New: modSeqWith and seqWith. Fixes #146
Browse files Browse the repository at this point in the history
  • Loading branch information
raquo committed Jan 16, 2025
1 parent e62a7df commit aaa1a95
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 1 deletion.
46 changes: 45 additions & 1 deletion src/main/scala/com/raquo/laminar/api/Laminar.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.raquo.laminar.api

import com.raquo.airstream.web.DomEventStream
import com.raquo.laminar.{nodes, DomApi}
import com.raquo.laminar.{DomApi, nodes}
import com.raquo.laminar.defs.attrs.{AriaAttrs, HtmlAttrs, SvgAttrs}
import com.raquo.laminar.defs.complex.{ComplexHtmlKeys, ComplexSvgKeys}
import com.raquo.laminar.defs.eventProps.{DocumentEventProps, GlobalEventProps, WindowEventProps}
Expand Down Expand Up @@ -132,6 +132,49 @@ with Implicits {
new DetachedRoot(rootNode, activateNow)
}

/**
* This method returns a Seq of modifiers, each of which is created from the same `input`.
*
* This is convenient when you e.g. want to bind multiple listeners to
* the same observable, keeping related together, without repeating yourself:
*
* {{{
* div(
* modSeqWith(coordinatesSignal.map(process).distinct)(
* left.px <-- _.map(_.x),
* top.px <-- _.map(_.y),
* _.map(_.z) --> zObserver
* )
* )
* }}}
*
* You can use the output of such a modSeqWith call directly in Laminar because
* Laminar implicitly converts `Seq[Modifier]` to `Modifier`.
*
* The above snippet is equivalent to:
*
* {{{
* lazy val coords = coordinatesSignal.map(process).distinct
* div(
* left.px <-- coords.map(_.x),
* top.px <-- coords.map(_.y),
* coords.map(_.z) --> zObserver
* )
* }}}
*
* See also [[seqWith]] for a more generalized version.
*/
def modSeqWith[El <: Element, In](input: In)(outputs: (In => Modifier[El])*): Seq[Modifier[El]] = {
outputs.map(_(input))
}

/** Like [[modSeqWith]], but works for arbitrary output types.
* Downside is that type inference that requires Modifier implicit conversions may not work.
*/
def seqWith[In, Out](input: In)(outputs: (In => Out)*): Seq[Out] = {
outputs.map(_(input))
}

/**
* Get a Seq of modifiers. You could just use Seq(...), but this helper
* has better type inference in some cases.
Expand Down Expand Up @@ -241,4 +284,5 @@ with Implicits {
): Binder[ReactiveHtmlElement[Ref]] = {
InputController.controlled(listener, updater)
}

}
36 changes: 36 additions & 0 deletions src/test/scala/com/raquo/laminar/tests/basic/ModSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.raquo.laminar.tests.basic
import com.raquo.laminar.api.L._
import com.raquo.laminar.utils.UnitSpec

import scala.scalajs.js

class ModSpec extends UnitSpec {

it("when keyword") {
Expand Down Expand Up @@ -77,4 +79,38 @@ class ModSpec extends UnitSpec {
)
}

case class Coordinates(x: Int, y: Int, z: Int)

it("modSeqWith") {
var lastZ: js.UndefOr[Int] = js.undefined
val zObserver = Observer[Int](lastZ = _)

val el = div(
modSeqWith(Val(Coordinates(1, 2, 3)))(
_ => "hello", // testing type inference that depends on implicits
left.px <-- _.map(_.x),
top.px <-- _.map(_.y),
_.map(_.z) --> zObserver,
child.text <-- _.map(_.toString)
)
)

assertEquals(lastZ, js.undefined)

// --

mount(el)

expectNode(
div.of(
"hello",
left is "1px",
top is "2px",
"Coordinates(1,2,3)"
)
)

assertEquals(lastZ, 3)
}

}

0 comments on commit aaa1a95

Please sign in to comment.