Skip to content

Integration with ScalaCSS #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
pishen opened this issue May 31, 2018 · 5 comments
Closed

Integration with ScalaCSS #20

pishen opened this issue May 31, 2018 · 5 comments

Comments

@pishen
Copy link
Contributor

pishen commented May 31, 2018

Hello,

I'm trying to figure out if I can rebuild my project, which currently using ScalaTags + ScalaCSS, with Laminar.
Would it be possible to integrate ScalaCSS into the DOM builder that Laminar is using?
(Like how it did in ScalaTags https://japgolly.github.io/scalacss/book/ext/scalatags.html)

I have tried to read the document and source code of scala-dom-builder and scala-dom-types. But I get confused by the traits and type parameters design.

Any hint would be appreciated and thanks for this cool project!

@raquo
Copy link
Owner

raquo commented Jun 1, 2018

Hey,

Definitely possible, and, dare I say, looks like it's actually fairly easy!

Check out the code for https://github.com/japgolly/scalacss/tree/master/ext-scalatags, it's very small and quite straightforward. In a couple files it defines a few classes and methods that translate from ScalaCSS concepts to ScalaTags concepts.

You'd need to basically copy that code but replace all ScalaTags imports / concepts with equivalent Laminar / Scala DOM Builder / Scala DOM Types imports / concepts.

Take a moment to understand what each function in that code does – one of them creates a ScalaTags element representing a <style> DOM element with the CSS stylesheet contents provided by ScalaCSS. Now you can put that element into a ScalaTags tree, and ScalaTags will put it into the real DOM. You want a similar chain of responsibility with your Laminar integration.

For example, in Laminar the equivalent to Scalatags' Modifier is another trait named Modifier in Scala DOM Types. Its primary method is called apply though, not applyTo, and its type param is different – it should be something like Modifier[ReactiveElement[X]] instead of ScalaTags' Modifier[X].

Regarding ScalaTags TypedTag, the alternative to that in Scala DOM Builder is Element I guess, but if you want to integrate ScalaCSS with Laminar, you'll need ReactiveElement instead, because that's what Laminar needs to render. So your implementation will be slightly Laminar-specific (unless you add even more genericism to your scalacss integration code, but I suggest going that way only after you get a non-generic version working).

And well now that we know that we need to use ReactiveElement, other values and type params become more obvious (e.g. the Modifier, see above).


I assume here that you want to use ScalaCSS with Laminar. But if you want to instead use it with Scala DOM Builder, the idea is all the same as above except you will be getting your styleTag (replacement for ScalaTags' tags2.style) from Scala DOM Builder or from your own custom bundle instead of from Laminar.

@pishen
Copy link
Contributor Author

pishen commented Jun 2, 2018

When using ScalaCSS with ScalaTags, I may attach multiple classes onto a div like this:

div(
  CSS.btn,
  CSS.control,
  CSS.btnBlack,
  ...
)

ScalaTags' text.Builder has an appendAttr() function for us to append a new class into current classNames
(https://github.com/japgolly/scalacss/blob/master/ext-scalatags/shared/src/main/scala/scalacss/ScalatagsText.scala#L12)

But seems that ReactiveElement doesn't provide any similar function?
I've checked

com.raquo.laminar.nodes.ReactiveHtmlElement
com.raquo.laminar.nodes.ReactiveElement
com.raquo.laminar.nodes.ReactiveNode
com.raquo.laminar.nodes.ReactiveChildNode
com.raquo.dombuilder.generic.nodes.Element
com.raquo.dombuilder.generic.nodes.Node
com.raquo.domtypes.generic.nodes.Element
com.raquo.domtypes.generic.nodes.Node

with no luck. Maybe I missed something.

BTW, I often get lost when I have to jump back and forth between all these trait files. Not sure if it will help but maybe we can make a ScalaDoc for easier browsing?

Thanks for the fast and detailed reply!

@pishen
Copy link
Contributor Author

pishen commented Jun 2, 2018

I think I can also use it like this if I don't insist on the exactly same pattern as ScalaCSS in ScalaTags:

div(
  classNames := Seq(
    CSS.btn,
    CSS.control,
    CSS.btnBlack
  ).map(_.htmlClass)
)

It's acceptable :P

@raquo
Copy link
Owner

raquo commented Jun 2, 2018

Hmm so you wrote the part that adds CSS class names to Laminar elements, looks good. But what actually loads the CSS stylesheets that define these CSS class names? I briefly looked at the ScalaTags integration and was under the impression that ScalaCSS gives you objects representing inline stylesheets that you need to convert to a <style> tag and mount into the DOM. Is that not the case, or how are you achieving that?


Re: ScalaDoc, good point, for now I've created an issue about that.


If you want to add a className to a ReactiveElement you can potentially create a meta modifier that will read the current value of the className prop and append a new className to it, using inContext.

There isn't a built-in facility for this in Scala DOM Builder because it's supposed to be unopinionated, such features are expected to be provided by libraries built on top of it. You could trivially copy Laminar's inContext into your project if you use Scala DOM Builder.

As for Laminar, it doesn't have a special facility to append class names because Laminar prefers a more declarative style that uses reactive observables to achieve changes in all props.

@pishen
Copy link
Contributor Author

pishen commented Jun 3, 2018

Yes, I can mount the <style> tag into DOM using

val sty = styleTag(CSS.render[String])
dom.document.querySelector("head").appendChild(sty.ref)

Here's a working version:
https://github.com/pishen/laminar-css/blob/master/src/main/scala/core/Main.scala

I think this can fit my needs for now, will close this issue, any further suggestions are welcomed.

Thanks for all the help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants