Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Mar 28 00:40
    raquo closed #52
  • Mar 28 00:40

    raquo on master

    API: `ReactiveElement.amend()` … (compare)

  • Mar 22 05:55

    raquo on master

    Fix: Incorrect SVG composite at… (compare)

  • Mar 21 14:02
    yurique commented #52
  • Mar 21 05:17
    raquo labeled #52
  • Mar 21 05:17
    raquo opened #52
  • Mar 20 22:31

    raquo on master

    Docs: Link to blog post, and to… (compare)

  • Mar 20 06:49
    raquo unlabeled #48
  • Mar 16 19:24

    raquo on v0.8.0

    (compare)

  • Mar 16 05:04
    ngbinh commented #39
  • Mar 15 22:56

    raquo on master

    Docs: Mention Waypoint API: Throw exception earlier if… Docs: Fix badge URL (compare)

  • Mar 15 22:43
    raquo closed #39
  • Mar 15 22:43
    raquo commented #39
  • Mar 14 18:12

    raquo on master

    Setting version to 0.8.0 Setting version to 0.8.1-SNAPSH… Docs: Add sherpal/full-scala-sc… (compare)

  • Mar 14 10:40
    raquo closed #51
  • Mar 14 10:40
    raquo commented #51
  • Mar 14 10:16
    raquo unlabeled #39
  • Mar 14 10:16
    raquo closed #18
  • Mar 14 10:16
    raquo commented #18
  • Mar 14 10:14
    raquo closed #46
Iurii Malchenko
@yurique
for now I ended up having a class SvgCompositeAttr[Attr <: Key](val key: Attr, separator: Char) { which is a copy of CompositeAttr but with SvgElement instead of HtmlElement everywhere, and a custom
lazy val svgCls: SvgCompositeAttr[SvgAttr[String]] = new SvgCompositeAttr(
    new SvgAttr("class", StringAsIsCodec, namespace = None),
    separator = ' '
  )
Nikita Gazarov
@raquo
@yurique Good find, thanks, my types were wrong. I just fixed in master, your fix should also work.
mushtaq
@mushtaq
@raquo I am curious if it is feasible to use material-components-web-components with Laminar? In effect, can someone use <mwc-button> instead of <button> assuming both support similar JavaScript API?
Nikita Gazarov
@raquo
Should be possible, but will require some integration. When you call button() in Laminar, it calls document.createElement("button") under the hood to create a button element in Javascript. Presumably there's a similar API to instantiate web components, so you'd need to extend ReactiveElement similarly to how ReactiveHtmlElement extends it, and provide the js.native code that instantiates the underlying web component. I'm not familiar with the API of web components so I don't know how exactly to do that or what else they might need.
mushtaq
@mushtaq
@raquo thanks. good to know that it is possible. will explore it someday if it becomes a compelling mwc becomes compelling option.
Wojtek Pituła
@Krever
@mushtaq Is web components a thing finally? I heard about few years ago but seemed to never get the traction
mushtaq
@mushtaq
@Krever not sure myself. I was thinking if we use Laminar, we need a components lib to integrate with. material-components-web looks like a good candidate. And then while browsing that I saw the wrapper project for web-components I linked above. Would like to hear from others what they think.
Wojtek Pituła
@Krever
:thumbsup:
Antoine Doeraene
@sherpal
indeed this material-components-web library seems to be a good candidate for integrating into Laminar. Where should we start looking in the code if we want to implement it?
Nikita Gazarov
@raquo
Look into creating something like ReactiveHtmlElement – the key is to implement a class that extends ReactiveElement and provides an implementation of Ref. Once you have that, you can think how you'd like to instantiate such classes. You might create one subclass per web component for example, or a single subclass for all web components, but some helper functions to instantiate different web components
Iurii Malchenko
@yurique

A small update regarding my experiments with tailwindcss: I love it!
The downside is that now it hurts me more than ever to look at bootstrap (or other “frameworks”), even for simple prototyping.

I was somewhat concerned about tailwind's css size (I’d argue it’s not huge when gzipped, but ~2MB unminimised css is not something to be ignored; and it will grow with added adding, etc).

So I managed to get purgecss working with laminar (or rather with a js generated by scala-js, and probably with any js), and it works perfectly fine (except one small caveat*). At least with my setup (separate entries for css and js, css is extracted). But I guess that’s more or less what everyone is doing with Laminar. Also, still struggling to shove my brain back into the head after this… too much webpack internals for a night.

If anyone is interested I can share. Maybe even put it to npm?

  • the small caveat: one can’t use string interpolation (or any string building) when assigning classes to tags - but I can live with that :) (and there’s an option to whitelist a set of classes so they never get purged).
Iurii Malchenko
@yurique

also I’ve come up with something I think I like when building tailwind-based things in laminar

looks like this:

button.btn.lg.indigo(
  cls := “be extra classy”,
  “Submit”,
  onClick --> …
)

// and it’s possible to make this a compilation error (if, say, `lg` was only “defined” fo “buttons” and not for “cards"):

div.card.lg( … )

can share this as well (though there’s nothing extraordinary)

Nikita Gazarov
@raquo
Ha! So .btn and .lg are simple implicit extension methods that call amend (or I guess your modify version of it) under the hood to apply a certain class name? This is an awesome pattern actually
Even if you don't publish a library, a blog post or even just a gist with this pattern would be great!
I'm also compiling CSS into a separate bundle. Do you use scalajs-bundler? I do, and I've been wanting to jump off of that for a while now
Iurii Malchenko
@yurique

are simple implicit extension methods

Yup, exactly. Though that was the first version. Now I actually “amend” the tags themselves (so I don’t have to carry mods: Modifier[]*) everywhere:

  implicit class HtmlTagWithTailwind[T <: dom.html.Element](val tag: HtmlTag[T]) extends AnyVal {

    @inline def btn: AmendedHtmlTag[T, AmButton] =
      tag.amend[AmButton](
        cls := "inline-flex items-center border border-transparent rounded-md focus:outline-none transition ease-in-out duration-150",
      )

  }

  implicit class AmendedHtmlTagWithButton[T <: dom.html.Element](val tag: AmendedHtmlTag[T, AmButton]) extends AnyVal {

    @inline def sm: AmendedHtmlTag[T, AmButton] =
      tag.amend(
        cls := "px-3 py-2 text-sm leading-4 font-medium",
      )

    @inline def indigo: AmendedHtmlTag[T, AmButton] =
      tag.amend(
        cls := "text-white bg-indigo-600 hover:bg-indigo-500 focus:border-indigo-700 …",
      )


  }

  implicit class HtmlTagWithAmend[T <: dom.html.Element](val tag: HtmlTag[T]) extends AnyVal {

    @inline def amend[AmType <: AmAny](mods: Modifier[ReactiveHtmlElement[T]]*): AmendedHtmlTag[T, AmType] =
      new AmendedHtmlTag[T, AmType](tag.name, tag.void, mods)

  }

trait AmAny
  trait AmButton extends AmAny

  class AmendedHtmlTag[+R <: dom.html.Element, AmType <: AmAny](
    name: String,
    void: Boolean = false,
    amendments: Seq[Mod[ReactiveHtmlElement[R]]]
  ) extends HtmlTag[R](name, void) {

    override def apply(modifiers: Mod[ReactiveHtmlElement[R]]*): ReactiveHtmlElement[R] =
      super.apply(modifiers ++ amendments: _*)

    def amend(mods: Mod[ReactiveHtmlElement[R]]*): AmendedHtmlTag[R, AmType] =
      new AmendedHtmlTag[R, AmType](name, void, amendments ++ mods)

  }

This is kind of WIP still. Also, maybe too many @inlines, and most likely it can be optimised quite a bit. But it works :)

Iurii Malchenko
@yurique

Do you use scalajs-bundler? I

I used to a while ago, then got tired of not being able to control it as much as I needed.
So now I just use npm + webpack directly, with packages.json and webpack.config.js in the root of the project, referencing js and css from the frontend module:

entry: [
    path.resolve(__dirname, './modules/frontend/src/static/stylesheets/main-tw.css’),
// …
    path.resolve(__dirname, './modules/frontend/.js/target/scala-2.13/frontend-fastopt.js’),
]

(I actually started with the webpack.config.js that scalajs-bundler was generating back in the day)

also

  • PostCSS,
  • ExtractCssChunks, HtmlWebpackPlugin, CopyWebpackPlugin plugins
  • webpack-dev-server

plas a few more ad-hok things.

a blog post or even just a gist with this pattern would be great!

Yeah, I should probably do that. And I probably will as soon as I get some time to spare :)

Nikita Gazarov
@raquo
Yes I see how your amending works but do you really need AmendedHtmlTag? You could just add an implicit extension apply method to ReactiveElement and have the same syntax without the new class.
Ah, right, except you'll need to do button().lg instead of button.lg.
Iurii Malchenko
@yurique
yuo
Nikita Gazarov
@raquo
looks good, yo
Iurii Malchenko
@yurique
laminar’s API made it almost too easy :)
Nikita Gazarov
@raquo
Re: webpack, I guess i'm switching off scalajs-bundler next time I touch webpack config for any reason, looks simple enough. I guess I'll just need to configure jsdom for tests myself and that's it
Iurii Malchenko
@yurique
I always forget about jsdom. Haven’t tried configuring it. I’ll share my webpack setup (wanted to make a g8 template for it for a long time now). But that will have to wait a bit, got to get some sleep :)
Binh Nguyen
@ngbinh
looks great @yurique
I really like the fact that Laminar allows me to stay on Scala land while writing performant and idiomatic frontend code. The thing I don't know yet is how to incorporate existing vanilla JS projects with Laminar? Scala.js react, the one that I am using right now, has a pretty good story on how to integrate with other react components.
Nikita Gazarov
@raquo
If you are motivated enough, you can certainly make a helper that lets you render either ScalaJS-React elements or native React elements from Laminar (or the other way). It's just a matter of translating between the props and streams API, and wiring up the matching mount / unmount hooks. If using plain React you would also need basic typings for a small part of its API, but I think ScalablyTyped has something for that.
Nikita Gazarov
@raquo
Or actually you can probably just use the raw React types straight from ScalaJS-React, without using ScalaJS-React components
mushtaq
@mushtaq

I always forget about jsdom. Haven’t tried configuring it. I’ll share my webpack setup (wanted to make a g8 template for it for a long time now).

A template/sample without scalajs-bundler will be very helpful! I read about this pattern, but a sample will enable many to understand/adopt it. Thanks.

Nikita Gazarov
@raquo
I concur. I know it's doable, but ugh. One of those things for which a template would be great
Iurii Malchenko
@yurique

I did it. Took me far longer than I expected :)
https://github.com/yurique/scala-js-laminar-starter.g8

all feedback and PRs is more than welcome (esp for the webpack side; as, despite I’ve been dealing a lot with it lately, I’m far from being a webpack expert)

I’ll also add the purgecss stuff I mentioned earlier when I get that webpack plugin polished a little bit and published

@raquo I used the todomvc example there, hope you don’t mind :) (I’ll add a reference to it, forgot)
Nikita Gazarov
@raquo
Totes cool! I'll check this out tonight :D
Nikita Gazarov
@raquo
tonight on the weekend ._.
mushtaq
@mushtaq
@yurique :thumbsup: thanks a lot!
Philip Stutz
@pstutz
@yurique Thanks a lot for the starter template! I love Tailwind CSS as well and ran into limitations configuring Webpack with the scalajs-bundler plugin.
Philip Stutz
@pstutz

Does anyone know why the intuitive ways of defining classes on SVG tags do not work? Do I misunderstand the API or is this an oversight?
Custom extensions are also a bit ugly due to protected, so I'm curious about the canonical way to do this.

import com.raquo.laminar.api.L._
import com.raquo.laminar.api.L

L.svg.svg(L.svg.cls := "foo")
L.svg.svg(cls := "foo")

Error:(22, 23) type mismatch;
found : com.raquo.laminar.modifiers.Setter[com.raquo.laminar.api.Laminar.HtmlElement]
(which expands to) com.raquo.laminar.modifiers.Setter[com.raquo.laminar.nodes.ReactiveHtmlElement[org.scalajs.dom.raw.HTMLElement]]
required: com.raquo.laminar.api.Laminar.Modifier[com.raquo.laminar.nodes.ReactiveSvgElement[org.scalajs.dom.svg.SVG]]
(which expands to) com.raquo.domtypes.generic.Modifier[com.raquo.laminar.nodes.ReactiveSvgElement[org.scalajs.dom.raw.SVGSVGElement]]
L.svg.svg(L.svg.cls := "foo")

Nikita Gazarov
@raquo
Hey @pstutz L.svg.svg(L.svg.cls := "foo") should work but doesn't due to a bug, I fixed it in master recently, will publish a version soon. Yuri posted a workaroud if you scroll up a few days back, or you can checkout master and sbt publishLocal it for now. On the other hand L.svg.svg(cls := "foo") should not work, that cls attribute from L is for html elements. It's a bit annoying but in practice I import com.raquo.laminar.api._; import com.raquo.laminar.api.L.svg._ when I need to compile lots of svg.
Philip Stutz
@pstutz
Thanks a lot! I really like Laminar so far, very kind of you to share it.
Nikita Gazarov
@raquo
nice you're welcome
Philip Stutz
@pstutz
dt/dd tags seem odd as well with regard to modifier typing. Oddly dl seems fine.
Nikita Gazarov
@raquo
@pstutz shouldn't be any issue, those are standard HTML elements. Are you perhaps using scala-js-dom 1.0.0? It's not compatible with scala-js-dom 0.9.8 that we use right now, and it removed the specialized types for dt and dd
Nikita Gazarov
@raquo
I'll release 0.9.0 this weekend with all the fixes we talked about
Philip Stutz
@pstutz
Yes, I'm using scala-js-dom 1.0.0. Thanks a lot, looking forward to the release.
Antoine Doeraene
@sherpal

hello, probably a terrifying question: is it possible to run a sideEffect within a stream? Or do the following in a nicer way: I've got a stream that submit my forms that basically look like

submitBus.events.mapTo([some data case class]).flatMap(EventStream.fromFuture(...))

I want to feed a isSubmitting: EventBus[Boolean] with true at the beginning and feed it with false afterwards.
I'm currently doing this:

  val $submitEvents: EventStream[SubmitReturn] = submitBus.events
    .mapTo(isSubmittingBus.writer.onNext(true))
    .mapTo($formDataView.now)
    .map(submitProgram)
    .flatMap(EventStream.fromZIOEffect)
    .map(traverse => {
      isSubmittingBus.writer.onNext(false)
      traverse
    })

thanks!

Iurii Malchenko
@yurique

I do it like this:

val $isSubmitting = 
  EventStream.merge(
    $submitEvents.mapToValue(true),
    $responseEvents.mapToValue(false)
  ).toSignal(false)

Or sometimes like this:

val $isSubmitting = Var(false)

something(
…
      $submitEvents.mapToValue(true) --> $isSubmitting.writer,
      $responseEvents.mapToValue(false) --> $isSubmitting.writer,
...
)
Antoine Doeraene
@sherpal
Thanks, I like your first suggestion very much, actually :D
Iurii Malchenko
@yurique

So, the purgecss-laminar-webpack-plugin is done (if anyone ever again tells me that sbt is hard, I’ll ask them to write and publish a webpack plugin =) )

Here it is: https://www.npmjs.com/package/purgecss-laminar-webpack-plugin
Also updated the template: https://github.com/yurique/scala-js-laminar-starter.g8

image.png