Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Mar 01 01:30
    raquo labeled #90
  • Mar 01 01:29
    raquo opened #90
  • Feb 28 10:56
    raquo labeled #89
  • Feb 28 10:55
    raquo opened #89
  • Feb 28 03:46

    raquo on master

    Docs: Fix typo in link (compare)

  • Feb 27 09:17

    raquo on gh-pages

    Deploy website Deploy website … (compare)

  • Feb 27 09:07

    raquo on gh-pages

    Deploy website Deploy website … (compare)

  • Feb 27 08:53

    raquo on v0.12.1

    (compare)

  • Feb 27 08:53

    raquo on master

    Docs: Add Ajax live example Docs: Inputs and form state exa… Fix: Remove debug logging from … and 2 more (compare)

  • Feb 27 04:38
    ngochai94 edited #88
  • Feb 26 22:35
    raquo closed #17
  • Feb 26 22:35
    raquo commented #17
  • Feb 26 22:33
    raquo labeled #88
  • Feb 26 22:33
    raquo labeled #88
  • Feb 26 22:33
    raquo edited #88
  • Feb 26 22:33
    raquo commented #88
  • Feb 26 13:07
    yurique commented #88
  • Feb 26 13:05
    yurique commented #88
  • Feb 26 13:00
    yurique commented #88
  • Feb 26 12:25
    ngochai94 opened #88
megri
@megri
So no default value needed, in my mind..
Iurii Malchenko
@yurique
maybe trait AttrDefaultValue[T] { }/*something.maybe <— */ (implicit default: AttrDefaultValue[T]) ?
megri
@megri
Note that this would remove .maybe from props like idAttr, which in my mind makes sense
Maybe I'm missing something, why would there need to be a default value?
if I have cls.maybe := None, what's the "default" ?
Iurii Malchenko
@yurique

well, classes are completely different.. but in this case it would be ”” I suppose :)

I was thinking more about this

Right... the problem is, we don't know the default value of each prop.

megri
@megri
I'm probably missing something important, but for instance the default of styleAttr is to not have the attribute :P
Iurii Malchenko
@yurique
Yes, and that would be reflected in its AttrDefaultValue instance
:)
but I’m just thinking aloud
megri
@megri
What would that be then?
Oh you mean as a general mechanism for props that are allowed to be removed from elements?
Iurii Malchenko
@yurique
AttrDefaultValue can contain something lik def modify(el: Element) not just a value, and that would remove the style property
yeah
megri
@megri
Well that's one way to think of it but if you think of it from the other way around there's no need for an implicit at all.
Iurii Malchenko
@yurique
that can totally be the case
I haven’t looked into it as deeply as Nikita has, so my judgement here should not be trusted =)
megri
@megri
Something like this:
abstract class ComplexProp[T](name: String) {
  var values: List[T]
  def separator: String // " " for cls, ";" for styleAttr
  def stringified: List[String] // List[T] => List[String]
  def render(parentElem: HtmlElement): Unit = {
    parentElem.setAttribute(name) = stringified.mkString(separator)
  }
}
1 reply
I don't know, it's probably overly simplistic..
But I mean, instead of having everything take an implicit DefaultAttrValue that may or may not completely remove the attribute, all modifier instances (cls, idAttr, styleAttr, name etc.) would be based on either SimpleProp or ComplexProp.
Otherwise the end result might be things like .maybe that are attached to things where it doesn't make sense, but cannot be used because there is no implicit instance in scope
Quafadas
@Quafadas

Thanks ever so much to the people who have helped me get this far... this was my POC... which I'm super happy with.

  1. The to do app highlights new entries
  2. The pie chart at the bottom reacts to the proportions of open / closed to do's
  3. Clicking the word cloud highlights issues with that word in.

Alt Text

Number 3 for me was super exciting... as it means that I can wheel in Vega, which is very good at data viz, and access it's events back in laminar, which appears to be very good at everything else!

baby steps... but I think it's cool :-)!

1 reply
Jim Balhoff
@balhoff
Hi, I’m new to Laminar, and I’m trying to use Waypoint for routing in my app. I’ve previously been using an old version of Outwatch and all my URL routing is after the hash (e.g. https://example.org/#/person/1234. Does Waypoint support this kind of route? I can’t figure it out. Alternatively, are there some docs for how to set up SBT to serve my app while developing so that the routes work like they would on a server? (with the hash style I can just open the JS file in a browser). I guess this may be just a general Scala.js question, but if anyone can point me in the right direction I would appreciate it.
megri
@megri
@balhoff This is a long shot, but try using this instead of root at the start of your path..
import urldsl.errors._
import urldsl.language._
import urldsl.vocabulary._

object WaypointExtras {
  val frag = PathSegment.simplePathSegment(
    s => if (s.content == "#") Right(()) else Left(DummyError.dummyErrorIsPathMatchingError.wrongValue("#", s.content)),
    _ => Segment("#")
  )
}
Not actually sure if fragments are considered part of the path. I know they are never sent to the backend.
Jim Balhoff
@balhoff
@megri thanks for the idea, I will give it a try.
Nikita Gazarov
@raquo

@balhoff The last version of URL-DSL can match URL fragments. You need Waypoint 0.3.0 for this.

Although, I don't actually know how if it will work to match fragment URLs on a file:// URL. You'll need to try. You'll probably need to encode your route as root / "path" / "to" / "your" / "file.html" / endOfSegments / fragment[String] or something like that

Looks like you'll need to parse the fragment all by yourself though. Eh, better to get a local server running. There are a few project examples on https://laminar.dev/resources to get you started, e.g. https://github.com/keynmol/http4s-laminar-stack Sbt can be annoying to deal with but once you have it working it's not too bad
Jim Balhoff
@balhoff
@raquo thanks, I think I will try the local server route. Previously I was treating the part after the hash as a fake path + query parameters, so yes I would need to parse all that. But the URLs would look nicer without the hash business. I’ll look at the example projects.
And by the way, looking forward to getting into Laminar! So far I am appreciating the well done documentation.
Nikita Gazarov
@raquo
Nice, thank you!
megri
@megri
Are there any guidelines on how to test observables?
Nikita Gazarov
@raquo
@megri Just the observables, or whole Laminar components? See Airstream and Laminar tests for that respectively. For testing Laminar I use https://github.com/raquo/scala-dom-testutils/ plus some sugar to test that the DOM nodes are as expected. It is very bare bones though
megri
@megri
I was thinking it would make sense to give examples on laminar.dev. It could be very educational to frame some examples in terms of unit/component tests
Nikita Gazarov
@raquo
Would be nice... but not gonna lie, it'll be a while till I get to that.
jatcwang
@jatcwang:matrix.org
[m]

Let's say I got a Var[NonEmptyString] and I'd like to create a Var[String] from that (which does nothing if you try to set an empty String), what's the quickest (most readabl) way to go about it instead of creating two more variables?

Basically I'm looking for zoom with the signature def zoom[A, B](in: A => B)(out: B => Option[A]).
Actual use case is form text input that should not be empty (or any other validation, really). Feel like it'd be a common pattern in the FE

jatcwang
@jatcwang:matrix.org
[m]
(I guess this is more of an airstream question). I wrote a def contramapOpt[B](f: B => Option[A]): Observer[B] extension on Observer[A], but I guess having this on a Var would be nice too..
Nikita Gazarov
@raquo
@jatcwang Hm you could implement such a zoom (zoomCollect?) as an extension method that would call zoom under the hood. To convert B => Option[A] into B => A, you want to emit the parent var's current value when no update is desired. I'm not sure if this implementation would be 100% robust in the face of data races, but that's the best you can do if you want a DerivedVar like this (and not a pair of derived Signal and Observer). This could be implemented robustly in Airstream core but we'd need to change DerivedVar slightly.
jatcwang
@jatcwang:matrix.org
[m]
implicit class ObserverExt[A](val ob: Observer[A]) extends AnyVal {
    def contramapOpt[B](f: B => Option[A]): Observer[B] = {
      new Observer[B] {
        override def onNext(nextValue: B): Unit = {
          f(nextValue) match {
            case Some(a) => ob.onNext(a)
            case None    => () // do nothing
          }
        }

        override def onError(err: Throwable): Unit = ob.onError(err)

        override def onTry(nextValue: Try[B]): Unit = nextValue match {
          case Success(value) =>
            f(value) match {
              case Some(s) => ob.onTry(Success(s))
              case None    => () // do nothing
            }
          case Failure(e) => ob.onTry(Failure(e))

        }
      }
    }
  }
This is what I did (just on Observer instead of Var). I don't think it's possible to implement it in terms of zoom (since in the case of None we won't emit any events, but the interface of zoom does not allow you to express this)
Nikita Gazarov
@raquo
Actually for observers you can implement contramapOpt in terms of contracollect
And, if you are extending Observer manually, you should add error handling code similar to Observer.fromTry (see docs for observer error handling) for it to be well behaved

since in the case of None we won't emit any events, but the interface of zoom does not allow you to express this

Right that's why when you want to emit None, you should read the parent Var's value and emit that instead, when implementing in terms of zoom. Signals perform a == check before emitting so the Var will not actually emit anything in this case because its value didn't change.

jatcwang
@jatcwang:matrix.org
[m]
Can use contracollect if you have a PartialFunc, but normally you'd have a function like toIntOption so it's actually less efficient that way since the conversion function is run twice (once for the isDefinedAt and then once again to get the value)
Nikita Gazarov
@raquo
Yep, we could add a native implementation of contramapOpt eventually, same as zoomOpt / zoomCollect
To clarify my comment about error handling in a custom observer: since you're calling into ob a lot of it is handled already, but not if f throws. You could implement onTry method as ob.onTry(nextValue.map(f)) and call into it from onNext and onError to solve that
jatcwang
@jatcwang:matrix.org
[m]
@raquo: I can raise a PR for Observer one. Implementation will be based on Observer.withRecover for exception safety (I think this should work)
zoomOpt is a bit more complicated since we probably want to cache the result as well like zoom
Nikita Gazarov
@raquo
Cool, but you can implement it without copying the error handling code from withRecover, I think you can just call into fromTry or withRecover like we do in contramap / contramapTry implementations
jatcwang
@jatcwang:matrix.org
[m]
oh yeah that's what I meant sorry
jatcwang
@jatcwang:matrix.org
[m]
PR is up.
jatcwang
@jatcwang:matrix.org
[m]
Another Q: Do we need a split on Var[List[A]] as well into something like Var[List[Var[A]]? Use case is a form with array of subforms