Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • May 12 21:03
    raquo commented #106
  • May 12 17:48
    i10416 closed #106
  • May 09 01:06
    raquo edited #117
  • May 09 01:05
    raquo edited #117
  • May 09 01:05
    raquo edited #117
  • May 09 01:05
    raquo edited #117
  • May 09 01:04
    raquo edited #117
  • May 09 01:03
    raquo edited #117
  • May 09 01:01
    raquo labeled #117
  • May 09 01:01
    raquo opened #117
  • May 08 07:23

    raquo on next-0.15

    WIP: Docs etc. (compare)

  • Apr 02 07:30
    Dennis4b opened #116
  • Feb 24 23:23
    raquo commented #115
  • Feb 24 23:23

    raquo on next-0.15

    Build: Mdoc 2.3.1 (#115) (compare)

  • Feb 24 23:23
    raquo closed #115
  • Feb 24 23:02
    raquo edited #115
  • Feb 24 13:21
    keynmol review_requested #115
  • Feb 24 13:21
    keynmol opened #115
  • Feb 02 03:57

    raquo on next-0.15

    Docs: Initial draft of v15.0.0 … (compare)

  • Feb 01 09:21

    raquo on next-0.15

    Naming: ReactiveElement.bindSub… Build: Bump versions (compare)

Sébastien Doeraene
@sjrd
val valueUpdater: Observer[Double] = ...

      input(
        typ := "text",
        controlled(
          value <-- item.map(_.value.toString),
          onInput.mapToValue.map(_.toDoubleOption).collect {
            case Some(newValue) => newValue
          } --> valueUpdater,
        ),
      )
but that does not let the user type in transiently invalid data.
For example, they cannot enter "5" then "." then "6" to input "5.6", because the transient state "5." doesn't parse as a Double, and therefore the partial input is blocked.
How should I go about this?
Ironically, previously I had onInput.mapToValue.map(_.toDouble) --> valueUpdater, which had the desired user behavior, but threw transient errors as exceptions in the console. It doesn't seem like I want that either.
Dennis
@dennis4b_twitter
Do you want or to basically have Observer[Either[String,Double]] or flip from Some(5.0) to None to Some(5.6) during typing?
(or leave the last valid value and only update whenever it's a valid Double)
Sébastien Doeraene
@sjrd
I would prefer to leave the last valid value.
Because that observer feeds into my model, basically. I don't want to undermine the "quality" of my model by turning values into Either[String, Double] instead of Doubles.
Nikita Gazarov
@raquo
@sjrd Hm, this does seem more annoying than I remember, perhaps due to the need for such an input to be controlled. The code below works but is ugly and kinda fragile – e.g. I assume that your item is a Signal, not sure if it would work with a stream. I need to add some helper for this in the library itself...
      val inputVar = Var("")
      input(
        typ := "text",
        controlled(
          value <-- inputVar.signal,
          onInput.mapToValue.filter(_.toDoubleOption.nonEmpty) --> inputVar,
        ),
        item.map(_.value.toString) --> inputVar,
        inputVar.signal --> { valueStr =>
          valueStr.toDoubleOption.foreach(valueUpdater.onNext)
        }
      )
Sébastien Doeraene
@sjrd
Thanks. That seems to make sense. I'll give it a try later today when I'm in front of a computer.
Nikita Gazarov
@raquo
(FWIW, I remember this being a mild annoyance in React as well)
Sébastien Doeraene
@sjrd
It worked as I wanted once I removed
.filter(_.toDoubleOption.nonEmpty)
3 replies
:)
I further elaborated the idea so that the string is left unchanged even when the double.toString doesn't return exactly the same string:
  def inputForDouble(valueSignal: Signal[Double], valueUpdater: Observer[Double]): Input = {
    val strValue = Var[String]("")
    input(
      typ := "text",
      controlled(
        value <-- strValue.signal,
        onInput.mapToValue --> strValue,
      ),
      valueSignal --> strValue.updater[Double] { (prevStr, newValue) =>
        if prevStr.toDoubleOption == Some(newValue) then prevStr
        else newValue.toString
      },
      strValue.signal --> { valueStr =>
        valueStr.toDoubleOption.foreach(valueUpdater.onNext)
      },
    )
  }
1 reply
Sébastien Doeraene
@sjrd
If you have a few minutes to spare, would you mind going over https://github.com/sjrd/scalajs-sbt-vite-laminar-chartjs-example/blob/main/src/main/scala/testvite/Main.scala and tell me if I did something horribly wrong with Laminar there? I intend to use this content in a video to get started with Scala.js, Vite and Laminar.
10 replies
ghstrider
@ghstrider
I am following the Waypoint example in readme and getting exhaustive error. Version 0.4.2 . Am I missing something. [error] It would fail on the following input: (x: Int forSome x not in 0) [error] implicit val UserPageRW: ReadWriter[UserPage] = macroRW
Nikita Gazarov
@raquo
@ghstrider Weird. Can you please show the exact code and line you have in your IDE that triggers this error? Which Scala version are you using? Also, are you using upickle 1.4.2 which is included with Waypoint, or are you including a different version of upickle?
1 reply
Simão Martins
@Lasering
Hey! Couldn't splitOne key always be _ => ()? Or will split remember the specific instances of each Input?
Nikita Gazarov
@raquo
Heyo. If you make the key return the same value all the time, the render callback that you provide to splitOne will only be called once, on the very first event, and never again, because the key is the same. Is that what you want?
That way, all the updates would be channeled into the signal parameter of the render callback
Simão Martins
@Lasering
I guess so because my render callback is { case (_, _, signal) => render(signal) }
Nikita Gazarov
@raquo
well... in that case you could just put render(sourceSignal) into the dom tree, skipping splitOne?
Simão Martins
@Lasering
since signal is always initialized with the initialInput I never need the second param, and since I set the key to always be the same the first param is also useless
right, I knew I was doing something stupid
Nikita Gazarov
@raquo
heh
Simão Martins
@Lasering
thanks for the help
Nikita Gazarov
@raquo
np!
felher
@felher

Hey all. Could it be that Laminar (or better, the dom library), is missing the title tag in svg, i.e. https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title

If so, how would I create a custom svg element on the fly. If not, I apologize for being blind, but where is it? :)

Okay, in the first case, svg.svgTag("title", false)("your title here") seems to work \o/
Dennis
@dennis4b_twitter
The following should work for html, so I guess also for svg:
import com.raquo.domtypes.generic.codecs.StringAsIsCodec

val svgTitleAttr = customHtmlAttr("title", StringAsIsCodec)

svg(
    svgTitleAttr("the title")
    ...
felher
@felher
In svg, it is actually a title tag. I don't think title attributes are valid in svg.
Simão Martins
@Lasering
Yes, it is missing raquo/scala-dom-types#80
you can create one with
import com.raquo.laminar.api.L._
svg.customSvgTag("title")("the text you want to show")
felher
@felher
Ah, great. Thanks 🙏
Simão Martins
@Lasering
Hey. Does anyone have recommendations for i18n libs?
Nikita Gazarov
@raquo
My two cents:
1) Don't bother with live-reloading the chosen language. Bunch of complexity and overhead for nothing. Change the language via a full page reload if needed.
2) Pick some plain JS library (or a translation service which offers a JS SDK) that can load translated strings from a JSON file or something like that, so that you don't need to generate N webpack bundles, one for each language.
3) Pick some library / service that lets you use template vars in translation strings, and ideally provide comments to translators for each string you need to translate
4) You will probably need i18n on the backend as well, but you don't want to expose backend strings to the frontend
Now for actual libraries / services, I don't know, it's been a while since I needed that.
Simão Martins
@Lasering
thanks for the explanation
reading that I assumed the translations always come from the backend via a json file, so I dont understand point 4
Nikita Gazarov
@raquo
I mean it's not only your frontend app that will need translation, right? You probably want to translate strings that you use on the backend as well, such as the content of emails that your backend sends. I'm just saying, you want to set things up in such a way that the translation strings that you ONLY use on the backend are not polluting the JSON file that you send to the frontend
Simão Martins
@Lasering
ahh got it, thanks again
ex0ns
@ex0ns

I have some issue to combine onClick with a Signal, I currently have the following code:

            button(
              typ := "submit",
              cls.toggle("bell-fill") <-- isSubscribed,
              cls.toggle("bell") <-- isSubscribed.map(!_),
              cls := "w-6 bg-center pointer",
              onClick.mapTo(isSubscribed) --> (clicked =>
                if (clicked) unsubscribe(domainName.now, _ => requestTrigger.emit(()))
                else subscribe(domainName.now, _ => requestTrigger.emit(()))
              ),
              ""
            )

This obviously does not work because isSubscribed is a Signal[Boolean]
A solution I had was to turn this 'inside out' and map a signal to an element, like that:

domainName.combineWith(isSubscribed).map { case (name, subscribed) =>
      button(
        typ := "submit",
        cls.toggle("bell-fill") <-- isSubscribed,
        cls.toggle("bell") <-- isSubscribed.map(!_),
        cls := "w-6 bg-center pointer",
        onClick.mapTo(subscribed) --> (clicked =>
          if (clicked) unsubscribe(name, _ => requestTrigger.emit(()))
          else subscribe(name, _ => requestTrigger.emit(()))
        ),
        ""
      )
    }

But this feels wrong

What would be the proper approach here ?

Nikita Gazarov
@raquo

You're right, that's wrong because you will be re-creating the button element every time your signal emits. You don't want that.

Instead, you want composeEvents so that you can use all stream operators, something like:

composeEvents(onClick)(_.sample(isSubscribed)) --> (clicked => ...)

Alternatively:

inContext { _.events(onClick).sample(isSubscribed) --> (clicked => ...) }

Note that the sample operator will trigger the observer only when the click happens, not when isSubscribed signal is updated. combineWithFn is another option.

33 replies
Mohammad Yousuf Minhaj Zia
@yzia2000
Hi looking for advice. I wanted to create a video chat application in Laminar for production use case. Has anyone done that in Laminar before and what was your experience? Anything in general I need to watch out for before making a decision like this? :)
1 reply
felher
@felher

What is the order mounting happens in? I would assume parents before siblings and siblings at the top/left before siblings at the bottom/right. I.e.

val child1 = div()
val child2 = div()
val parent = div(child1, child2)

will mount in order parent, child1, child2 when/if parent is eventually mounted.

Is that correct?

felher
@felher
Oh, an a onMountCallback will be executed as if they were a child at that position? So that div(onMountCallback(a), div(onMountCallback(b)), onMountCallback(c)) will actually do a, b and then c and not a, c and then b.
Nikita Gazarov
@raquo

When you've built that val parent, you now have a parent div element with two children div-s in it. However, nothing is mounted yet, and no subscriptions have been activated.

Once you render() that parent div (or add it as a child to an already mounted div), that parent div will become mounted too, and all of its subscriptions (-->, <--, onMount*, etc.) will become activated.

Subscriptions created by modifiers (child elements are also modifiers) are activated in the same order as said modifiers appear in the Laminar element tree, depth-first. So yes, your last message is the correct and expected behavior.

felher
@felher
Great, thank you!