Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Jun 23 01:05
    raquo locked #98
  • Jun 23 01:05
    raquo unlocked #98
  • Jun 23 01:02
    raquo locked #98
  • Jun 23 01:01
    raquo closed #98
  • Jun 23 01:01
    raquo commented #98
  • Jun 22 15:10
    yatesco opened #98
  • Jun 18 20:37
    raquo commented #97
  • Jun 18 20:37

    raquo on master

    Doc typo Changed variable name (compare)

  • Jun 18 20:37
    raquo closed #97
  • Jun 18 14:39
    Quafadas review_requested #97
  • Jun 18 14:39
    Quafadas opened #97
  • Jun 09 05:14

    raquo on gh-pages

    Deploy website Deploy website … (compare)

  • Jun 03 06:32
    raquo commented #93
  • Jun 02 16:25
    raquo commented #93
  • Jun 02 15:18
    rparree commented #93
  • Jun 02 15:18
    rparree commented #93
  • May 15 23:25

    raquo on gh-pages

    Deploy website Deploy website … (compare)

  • May 15 23:21

    raquo on mdoc-issue

    (compare)

  • May 15 23:21

    raquo on develop

    (compare)

  • May 15 23:20

    raquo on scala3

    (compare)

Nikita Gazarov
@raquo
If you want to avoid re-creating the p elements every time state signal updates, yes, that is correct.
Generally that is the way to go
Though, some times I don't bother when I don't care about performance (e.g. if state updates very rarely) and don't care about maintaining UI state (such as text selection inside that p element)
ylaurent
@ylaurent
yeah got it :ok_hand: thank you very much for the help, i will try all of that tomorrow ! thanks agains for all your effort for providing this awesome lib!! :)
objektwerks
@objektwerks
I can't find the checkValidity method on a laminar or dom input type. Any example code would be great. TIA!
3 replies
felher
@felher
Hey folks. I trying to use scala3 with laminar and I'm stumbling over a small issue. When I have a method which returns a HTMLElement, I can render it into some container when it is in the same file but not when it is in another file. Or maybe I'm just being stupid here. It is small enough to fit on a screenshot:
scala3.png
The same code in two files (the start method), but it doesn't work in the foreign file... The error message is accurate. I get the same with SBT
Same imports

I guess it has something to do with the givens and conversions. If I do this in main:

def mainBase: com.raquo.laminar.nodes.ReactiveElement.Base =
  main

and render mainBase in the other file, everything works fine

Nikita Gazarov
@raquo
@felher Nah our implicits are pretty docile. It's Scala's variable shadowing. When you import * from L, it imports a main tag name among other things. Since you're not explicitly importing your main method, this wildcard import shadows it.
felher
@felher
Oh, that makes sense! Thanks!
Quafadas
@Quafadas

I'm struggling a little with AjaxEventStream... if I provide a valid route, it works!

If my route returns a 400, Bad Request (verified via different tool), then I seem to simply lose the request - I can't figure out how to get any other feedback. I've tried a few variations of the recover block below without success (including a shameless copy paste from the examples)

    println(route)
    val result = new AjaxEventStream(
      route.method.toUpperCase(),
      route.route,
      upickle.default.write(data),
      timeoutMs,
      headers,
      withCredentials,
      responseType,
      isStatusCodeSuccess,
      requestObserver,
      progressObserver,
      readyStateChangeObserver
    ).map(req => {
        val t = req.responseText          
        println(s"Only fires on success : $t")
        route.decode(t)
      }
    ).recover { case err: AjaxEventStream.AjaxStreamError => println("recover"); println(err.getMessage); ??? }     
    result
  }

The recover block never seems to fire...
I've also tried setting up the request and progress observers (again, copy paste example :-/), but I can't get them to emit anything either... any hints?

Dennis
@dennis4b_twitter
You are not catching other errors than AjaxStreamError? (not sure what you get for a 400). You can try .recoverToTry and see what it returns?
8 replies
yurique
@yurique:nowhere.chat
[m]

@Quafadas: when using ajax event stream, there's an additional param:

    isStatusCodeSuccess: Int => Boolean = defaultIsStatusCodeSuccess,

so you can tell it which status codes are considered "okay"

by default it's (status >= 200 && status < 300) || status == 304

1 reply
yup
and
  def defaultIsStatusCodeSuccess(status: Int): Boolean = {
    (status >= 200 && status < 300) || status == 304
  }
you can try setting it to _ => true to collect all the responses and see if that works :)
Quafadas
@Quafadas

you can try setting it to _ => true to collect all the responses and see if that works :)

Gotcha!!!

yurique
@yurique:nowhere.chat
[m]
but you should be getting an error emitted from the stream if it fails
maybe your ajax stream is not getting started?
do you "bind" it somewhere?
3 replies
yurique
@yurique:nowhere.chat
[m]
Hey guys, check this out: https://scribble.laminext.dev/u/yurique/cwdwywmothszmydqerfbstlbigqw/1
  • there are probably bugs to be fixed
  • the back-end might eventually fail and require a restart or something; this hasn't happened – but needs some "battle-testing" to be sure
Nikita Gazarov
@raquo
Whoa, did you make ScalaFiddle 2.0? It has been kinda-broken since forever...
yurique
@yurique:nowhere.chat
[m]
hehe, something like that :)
Nikita Gazarov
@raquo
That would be awesome
I managed to run my own snippet after logging in with github
yurique
@yurique:nowhere.chat
[m]
good! :)
Nikita Gazarov
@raquo
So is this like a LaminarFiddle, or will there be a way to add dependencies eventually? :)
yurique
@yurique:nowhere.chat
[m]
there's Airstream, Laminar, laminext, waypoint and frontroute currently
Nikita Gazarov
@raquo
Neat
yurique
@yurique:nowhere.chat
[m]
I'm thinking to add some dependency management eventually
Nikita Gazarov
@raquo
How does it work on a high level, are you running scala.js compiler in a lambda or something like that, and providing the output bundle URL to the client?
yurique
@yurique:nowhere.chat
[m]
yes, except I didn't go with lambda (I'll probably look into that, but not sure yet) – just a vps
Nikita Gazarov
@raquo
Well this is a very welcome development, I'm stoked
yurique
@yurique:nowhere.chat
[m]
with lambda it's super tricky to deploy, and sbt would be "cold" most of the time, thus long-ish build times (which are not super quick as it is)
yeah, I'm hoping this will help us help each-other :)
Nikita Gazarov
@raquo
Good spirit :+1:
Adrian Filip
@adrianfilip
is there a receiver for the rows of a table? or smth allowing me to write code like this?
table(
  tr(th("Name")),
  tr <- items.toObservable.split(_.id)(renderItemAsTR)
)
Nikita Gazarov
@raquo

Does renderItemAsTR return a tr(...)? In that case, just use children <-- items.toObservable.split(_.id)(renderItemAsTR).

However, because this is a table, you should wrap all of your rows into thead() and/or tbody(). See https://laminar.dev/documentation#tbody

Adrian Filip
@adrianfilip
Yes it returns a tr. I went over your recommendations. Works great! Thanks.
felher
@felher
Now that I figured out how amazingly well .amend works with CSS, especially with BEM Mixes, I will really miss that when working of the react side of my project....
@raquo I'm holding you personally responsible ;P
Nikita Gazarov
@raquo
Heheh you're welcome :p
Quafadas
@Quafadas

As a learning exercise, I've set myself the task of prepending a webserver to the "todo" example. Mostly, I think it's working pretty well, but I'm not sure if I'm doing some things which are, effectively anti-patterns.

button(
      cls("destroy"),
      inContext{
        thisNode => 
        val $click = thisNode.events(onClick).flatMap {_ => deleteStream(itemId)}
          List(
            $click.flatMap{_ => RouteApi.simpleRoute(TodoRoutes.listAllTodos)} --> itemsVar.writer
          )
      }

This "button";

  • creates an AjaxStream which deletes the thing in question serverside.
  • fetches (all) items from the server
  • writes the new list into itemsVar - which is effectively the "application state".
    With laminar being laminar, everything else does it's "observeable = automagic update thing", and this works.

My questions are;

  • The "update" Ajax stream is created, emits once, and is then ( I believe ! ) destroyed. Is this a "reasonable" strategy to create and destroy these streams rather than empty any sort of re-use (is is even possible to force a "single" stream to emit a second time)?
  • itemsVar is has effectively no scoping under this mechanism - it's accessed "directly". This sort of makes me shudder... should it? Would it be preferred to
  • Pass in it's "writer" as an argument to the method that creates the button?
  • Construct some "centralised" updating strategy where the button doesn't update the global state(!)?
  • On updating, it replaces the entire contents of the Var. My understanding is that Laminar's "split" operator doesn't care about that - it diffs per element key. There is therefore no difference between "replace entire list with same thing and one element less" and "remove element"? (Aside from network overhead!)

I couldn't figure out how to "hyrdate" the UI on page load ...

  val initStream: EventStream[Seq[Todos]] = RouteApi.simpleRoute(TodoRoutes.listAllTodos)
  serverStream.foreach(itemsVar.set(_) )(unsafeWindowOwner)

The docs appear to indicate that someone at my skill level :-) probably shouldn't be using "unsafe window owner"... as I don't really understand the implications. But the stream needs both an wonder, and something to "listen" to it, otherwise it never fires. I have the feeling this is... a bit wrong(!)?

Any feedback is welcomed...

Dennis
@dennis4b_twitter

For hydration, just have it lined up with the root element in which the itemsVar is used, something like:

div(
    serverStream --> itemsVar.writer,
    ... // other elemens
)

which is basically an onMount handler.

2 replies
then you don't need to worry about ownerships
Dennis
@dennis4b_twitter
An Ajax event stream is single use, afaik. It's a single request that just has the eventstream semantics to hook it up. What to do with scoping itemsVar and its writer depends on your code really, if it's all local I don't worry about it but you should abstract to what makes you happy with it. You can certainly create helpers that for example take in an ID to remove and return an EventStream with the new State (so combining delete and reload - alternative you can have the deleteItem api point return the new list directly, or you can simply trust that after succesful delete the new list is simply the old list with one less item and update accordingly (without a reload).Your understanding of split sounds right to me. You can either load a new list from the server with one less element, or you can update the list in the browser with a copy of the list with one less element, same thing from Laminars point of view.
The inContext is not needed I think, try something like composeEvents(onClick)(_ => deleteItem(..).flatMap(_ => reloadAllItems)) --> itemsVar.writer
5 replies
Nikita Gazarov
@raquo
Great answers, @dennis4b_twitter :+1:
Per Wiklander
@PerWiklander
Has anyone used Laminar together with Akka.js (https://github.com/akka-js/akka.js) ? I have a lot of server side Akka and would like to reuse some actors and patterns on the client side as well. I would like to compute some data in Akka and then pass it down to Laminar for rendering. And send commands up to the Akka layer from button clicks and input changes.
yurique
@yurique:nowhere.chat
[m]

is it possible that at some point you had something like

deleteItem(..).flatMap(_ => reloadAllItems) --> itemsVar.writer

(without composeEvents) and it got compiled and hot loaded into the browser while you were adding the composeEvents? :)