by

Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Jul 20 06:47
    raquo edited #58
  • Jul 20 06:46
    raquo labeled #58
  • Jul 20 06:46
    raquo opened #58
  • Jul 07 05:26

    raquo on v0.9.2

    (compare)

  • Jul 07 05:26

    raquo on master

    New: `observable --> var` alias… New: FormElement type alias New: onMountUnmountCallbackWith… and 2 more (compare)

  • Jun 19 07:59
    ngbinh added as member
  • Jun 08 04:44
    raquo labeled #57
  • Jun 08 04:44
    raquo opened #57
  • May 26 09:38

    raquo on master

    Docs: Add b12 Laminar example (compare)

  • May 25 07:52

    raquo on master

    Docs: Add onClick and children … (compare)

  • May 25 07:49

    raquo on master

    Docs: Explain SVG imports (compare)

  • May 24 06:55

    raquo on master

    Docs: remove links to oldest do… (compare)

  • May 22 09:22
    raquo closed #45
  • May 22 09:22

    raquo on master

    Docs: mention implicit uniquene… (compare)

  • May 22 09:11

    raquo on master

    Docs: Update version to 0.9.1 (compare)

  • May 22 09:09

    raquo on v0.9.1

    (compare)

  • May 22 09:09
    raquo closed #54
  • May 22 09:09

    raquo on master

    Fix: Remove one of the conflict… New: `nbsp`, `emptyMod` aliases Setting version to 0.9.1 and 1 more (compare)

  • May 18 07:46

    raquo on master

    Docs: Dedicated Laminar.cycle &… Docs: update ScalaFiddle link (compare)

  • May 18 07:31
    raquo commented #56
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
Nikita Gazarov
@raquo
So... I didn't know it at the time, but when I said the weekend I meant the next weekend. Haha, sorry. Can't wait myself.
objektwerks
@objektwerks
@raquo Nikita, thanks for all your hard procrastination, I mean, hard work. ;)
S
@SRGOM
I've read the documentation once before but right now I need to do something related to checkboxes and radio buttons and searching for these terms in teh docs yield nothing so can someone give me some quick poitners to how to achieve the following- I have two "divs". One for each radio button, if radio 1 is selected, div 1 shows and div 2 hides. Radio 2 is selected div 2 shows (and div1 hides). I can see that I can change display <-- <some signal>... mapped to none or block but how to generate this signal in a generic way, (where i don't have just 2 inputs and divs but say 5 divs).
Nikita Gazarov
@raquo

@SRGOM If I understand correctly, you want to render a few radio buttons all the time, and depending on which one is selected you want to render a div for it. One easy way to accomplish this is to save the "selected" state in a Var or EventBus, and then read that state from there to decide which div to render. For example, something like:

val $selected = new EventBus[String]

div(
  div(
    input(typ := "radio", onClick.mapTo("companies") --> $selected.writer),
    input(typ := "radio", onClick.mapTo("contacts") --> $selected.writer),
    input(typ := "radio", onClick.mapTo("settings") --> $selected.writer)
  ),
  child <-- $selected.map {
    case "companies" => div("Companies div")
    case "contacts" => div("Contacts div")
    case "settings" => div("Settings div")
  }
)

Note that this will actually add / remove elements instead of showing / hiding them with CSS display property. If you do explicitly want to use CSS, you can do the following instead of child <-- ... above:

div(display <-- $selected.map(showWhen("companies")), "Companies div"),
div(display <-- $selected.map(showWhen("contacts")), "Contacts div"),
div(display <-- $selected.map(showWhen("settings")), "Settings div")

using this helper:

def showWhen(target: String)(selected: String): String = {
  if (target == selected) "block" else "none"
}

If the number of radio buttons / divs is dynamic over time, you can use the split operator, see docs for that

S
@SRGOM
Thank you, you are extremely helpful (you practically wrote the entire code for me!).
Nikita Gazarov
@raquo
No problem, check out the laminar-examples repo for some inspiration as well. Not everything there is good but it all works at least
S
@SRGOM
Thank you. I will check that out too.
Nikita Gazarov
@raquo
@yurique Finally checked out your giter8 laminar template, looks great! I guess jsdom testing env would be a simple addition, just adding JSDOMNodeJSEnv on sbt site, and adding jsdom to package.json. Haven't tried that myself yet. Some day...
One thing, on the generated project sbt backend/reStart appears to kill the server immediately after launching (on macos):
$ sbt backend/reStart
[info] Loading settings for project global-plugins from idea.sbt,build.sbt ...
[info] Loading global plugins from [REDACTED]/.sbt/1.0/plugins
[info] Loading settings for project my-scalajs-project-build from plugins.sbt ...
[info] Loading project definition from [REDACTED]/my-scalajs-project/project
[info] Loading settings for project my-scalajs-project from build.sbt ...
[info] Set current project to my-scalajs-project (in build file:[REDACTED]/my-scalajs-project/)
[info] Application backend not yet started
[info] Starting application backend in the background ...
 Starting starter.boot.Boot.main()
[success] Total time: 0 s, completed Apr. 3, 2020, 10:47:46 p.m.
 ... killing ...
$
sbt backend/run works though.
Also lol'd at .drop(Random.nextInt(TestData.posts.size + 1)) // bug intentional :) I thought I'd fix something haha
Nikita Gazarov
@raquo
Also @yurique, I gotta ask you since diving into webpack plugin code is a bit much for my friday night, what exactly is laminar-specific and/or scalajs- specific in your https://github.com/yurique/purgecss-laminar-webpack-plugin ? Just on a high level, as opposed to the regular purgecss plugin I mean.
Nikita Gazarov
@raquo
🔪🐞 Just released Laminar v0.9.0.
Minor fixes, update to scala-js-dom 1.0.0, plus some domtypes naming changes, see https://github.com/raquo/Laminar/blob/master/CHANGELOG.md
Antoine Doeraene
@sherpal
:tada:

hey @yurique , what is the thing in your webpack config that make it so that your main.css file is able to resolve the @import "tailwindcss/base"? Is the lines

    entry: [
      path.resolve(__dirname, './modules/frontend/src/static/stylesheets/main.scss'),
      path.resolve(__dirname, './modules/frontend/src/static/stylesheets/main.css')
    ],

? In order to make it work, currently (on my projet) I seem to need to do

@import "../../../target/scala-2.13/scalajs-bundler/main/node_modules/tailwindcss/base.css";

which leaves to be desired, to say the least... Thanks!

Iurii Malchenko
@yurique

@raquo

One thing, on the generated project sbt backend/reStart appears to kill the server immediately after launching (on macos):

Yeah, that’s the way sbt revolver works - it starts the process in the background, so sbt thinks the command has finished and exits.
To use reStart you need to run it from within an sbt session:

$ sbt
sbt> reStart
// … the process is in the background
sbt> keep using sbt

Also lol'd at .. // bug intentional :)

=))

what exactly is laminar-specific and/or scalajs- specific in your

specific is the way scalajs-generated JS is parsed and searched for “string”s.
After implementing it, I now think it might have been unnecessary - the default purgecss parser will probably find those strings anyway (it just tokenises the whatever file you feed it). But at least that part was fun to implement, it will result in a much smaller “potential css class names” result set for purgecss to chew and probably less false positives.

In a nutshell - I used the esprima package to tokenize the JS respecting the syntax, extracting “string” tokens out of it and also evaluating the evals and processing them as well (recursively).

@sherpal

what is the thing in your webpack config that make it so that your main.css file is able to resolve the @import "tailwindcss/base"?

It must be the postcss-import plugin. From the tailwindcss docs (https://tailwindcss.com/docs/using-with-preprocessors):

postcss-import is smart enough to look for files in the node_modules folder automatically, so you don't need to provide the entire path — "tailwindcss/base" for example is enough

If that doesn’t work either - it might be the location of the node_modules. I’ve had some issues a while back - don’t remember the specifics exactly, but what I did was moving the node_modules and everything webpack-related into the project root (like ./modules/front-end/webpack.config.js —> ./webpack.config.js) and running npm/yarn from the root as well.

🔪🐞 Just released Laminar v0.9.0.

🎉💣

Antoine Doeraene
@sherpal
thanks @yurique I'll read that thoroughly!
Iurii Malchenko
@yurique

@raquo
so regarding jsdom - I just pulled the Laminar repo, removed the sclajsbundler and settings related to it, instead

  • added Test / jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv() to build.sbt
  • added libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % “1.0.0” to plugins.sbt
  • yarn init
  • yarn add -D jsdom

after this sbt test works and all tests pass

raquo/Laminar#53

S
@SRGOM
Is there a separate room for scala-dom-types?
Nikita Gazarov
@raquo
Nope, if you use it from Laminar you can ask here, or otherwise file an issue or that repo
S
@SRGOM
Solved, thank you.
Iurii Malchenko
@yurique

Has anyone played with module types? (https://www.scala-js.org/doc/project/module.html)

I’m getting these results right now (using the starter project):

scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }
  time to full opt:    6 sec 
  frontend-fastopt.js: 2.3M
  frontend-opt.js:     433K
  main.js:             454K 
  main.js.gz:          129K 

scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }
  time to full opt:    3 sec 
  frontend-fastopt.js: 2.3M 
  frontend-opt.js:     2.2M 
  main.js:             1.2M
  main.js.gz:          195K

It looks like there’s no full optimization for es modules fullOptJS, it happens much quicker, and doesn’t seem to load the cpu as much.

I find this interesting on its own, but I got into this while trying to make a sample test in the starter template.
And it turns out that scala-js-env-jsdom-nodejs doesn’t run the tests when module kind is set to anything but ModuleKind.NoModule, it’s explicitly hardcoded there:

private def validateInput(input: Seq[Input]): List[Path] =
  { input.map { 
    case Input.Script(script) => script 
    case _ => throw new UnsupportedInputException(input)
  }.toList
}

// the options here are these:
object Input {
  final case class Script(script: Path) extends Input 
  final case class ESModule(module: Path) extends Input 
  final case class CommonJSModule(module: Path) extends Input
}

so I tried to set

Test / scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) },

and now it accepts it, but no @imports are allowed :)

Nikita Gazarov
@raquo
Huh, interesting. I haven't tried ES modules in scala.js myself
Iurii Malchenko
@yurique
Oh, sorry, that was a stupid mis-copy-paste from me :(

for tests to work I had to do

Test / scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.NoModule) },

which indeed disables imports

Nikita Gazarov
@raquo
That's a strange limitation, not supporting js imports... So that means that we can't have such a scalajs-bundler-less setup for any projects that have JS dependencies? i don't quite understand why, your setup seems to have everything needed to create a bundle
Nikita Gazarov
@raquo

Hrm. So... jsdom env emulates the browser environment, so it only accepts scripts because browsers don't understand require(x) or import x. For the same reason the NoModule ModuleKind doesn't support imports – browsers don't understand them.

So, to test projects with npm dependencies we'd need to compile the bundle and provide the resulting script to jsdom. In fact that is what scalajs-bundler appears to be doing here: https://github.com/scalacenter/scalajs-bundler/blob/b22548b2d7d924fb4ebad038fd6a834e560d3252/sbt-scalajs-bundler/src/main/scala-sjs-1.x/scalajsbundler/sbtplugin/Settings.scala#L139-L185

So if we ever wanted that setup to support testing with JS dependencies, that's how it would need to be done, approximately. Don't need that for Laminar itself though as Laminar does not have JS dependencies.

S
@SRGOM
:pointup: [January 5, 2019 4:12 AM](https://gitter.im/Laminar/Lobby?at=5c2fe13d12db9607e7397581) Tad too late to quote maybe but if I wonder if @raquo would accept a suggestion to create an official building-blocks repo which only has a README.md that points to repos (with actual building blocks, like by @yurique )
Nikita Gazarov
@raquo
I already list Laminar-related projects right on the home page in "Community & Support" and "Laminar Addons" section. If anyone wants to put up some building blocks / examples / whatever, I'll happily link to them https://github.com/raquo/Laminar/
S
@SRGOM
cool! I will when I build something. I'm currently thinking of modelling a flow where all input changes automatically get sent to a specified endpoint (perhaps specified using data- attribute) using ajax and at the same time there is an associated div which changes states between spinner, green tick or red cross (for ajax failure). I'm thinking how I would do this.
Nikita Gazarov
@raquo
easiest is to make a Var of your result, and write to that var when initiating the request, and when receiving a result or an error, then render your spinner / tick / cross based on this Var's .signal
S
@SRGOM
Thank you, I'll go in that direction.
Kit Langton
@kitlangton
Laminar is incredibly cool!
:clap:
I'm curious if there are any examples of multi-page apps. The patterns in the TodoMVP are very helpful—a mini redux-like loop with an observer and a state Var—but I'm curious if those patterns continue to serve you well in larger apps.
Nikita Gazarov
@raquo
Technically https://github.com/yurique/scala-js-laminar-starter.g8 is multi-page but well it's a very small example, probably not what you're looking for. I don't have any big open source Laminar apps, but in my own project state management is mostly a breeze. The docs have a few suggestions on how to compose observables and observers for common patterns. If you're familiar with redux vs react-component-state tradeoffs, those don't go away, just take on different shapes. Sometimes local state makes more sense, other times global makes more sense. The benefit of Observables is that they provide a unified API for both cases, it's just a matter of declaring a local Var vs making it globally accessible, to put it simply. It's all in your haaands :)
Iurii Malchenko
@yurique
@kitlangton I am working on a project that is rather large. Not that I can share it, but I can tell that it has a quite lot in it: a fair amount of different kinds of content, navigation, a number of (somewhat) complex forms (with validation), listings, articles, videos, a “forum", file uploads, payment/subscription management, etc — and Laminar/AirStream(+Scala) helps sooo much at managing all the complexity. To say the least - I wouldn’t want to implement it with, say, react+ts :). And unlike the latter - with Laminar there’s never a case when I’m like “this is repetitive, boilerplate, whatever, and I need to abstract it, but can’t, because the building blocks are not composable.” (yes, I’m talking to you, react hooks =) )
Kit Langton
@kitlangton
Thanks @raquo & @yurique! Sounds like the best way is to just dive in :)
Kit Langton
@kitlangton
This is going to be so good :D I figured out a way to derive forms from case classes using Magnolia.
Now to pitch my team on ScalaJS :smile:
Kit Langton
@kitlangton
Has reflex-frp (https://reflex-frp.org) been an inspiration at all?
I greatly prefer the Laminar syntax—not being pure at all costs has its usability benefits—but there were some neat combinators in Reflex that are probably worth stealing :D
If I end up finding some useful helper methods, are you open to PRs?
Lorenzo Gabriele
@lolgab

This is going to be so good :D I figured out a way to derive forms from case classes using Magnolia.

Wow, nice!!! :clap:

Nikita Gazarov
@raquo

@kitlangton Laminar itself was inspired mostly by Outwatch, Cycle.js and Knockout.js. Airstream was influenced by xstream.js and scala.rx (and more indirectly, some academic reading like "Deprecating the observer pattern"). Relevant blog post: https://dev.to/raquo/my-four-year-quest-for-perfect-scala-js-ui-development-b9a

Reflex rings a bell, but given that I still can't read this I probably didn't look much into it at the time.

PRs are welcome, but I do have opinions about what should and shouldn't be in Laminar, so let's just chat in this group about anything you'd like to add before you spend the effort coding. Although I guess a lot of these helpers will probably be quite simple.

Nikita Gazarov
@raquo
Airstream's selection of operators is quite austere. It has been plenty enough in my personal experience, but other libraries do have richer APIs. Half of the reason is I just didn't get to it and nobody whined about it loudly enough, another half is that complex operators and features need a more considered design due to Airstream's peculiarities (ownership, directed-acyclic-graph approach to eliminationg FRP glitches, integration of streams and signals, etc.)
Kit Langton
@kitlangton

Reflex rings a bell, but given that I still can't read this I probably didn't look much into it at the time

Hahaha. That is some wonderfully repellent code to read :)

Kit Langton
@kitlangton
I think there's some low hanging API fruit around zip combinators for EventStream. For example:
def zipN(es1: EventStream[A], es2: EventStream[B])(f: (A,B) => C) : EventStream[C])
def zipN(es1: EventStream[A], es2: EventStream[B],es3: EventStream[C])(f: (A,B,C) => D) : EventStream[D])
etcetera
Nothing too complicated :P
ZIO's ZStream API has some lovely methods that you may find ripe for poaching.
Not sure how easy methods like take, takeWhile, takeUntil would be to implement in Airstream, or if you'd find them useful.
https://github.com/zio/zio/blob/master/streams/shared/src/main/scala/zio/stream/ZStream.scala#L2386