{"id":17743961,"url":"https://github.com/narigo/scalajs-fun","last_synced_at":"2025-04-01T00:41:59.146Z","repository":{"id":149036935,"uuid":"62587800","full_name":"Narigo/scalajs-fun","owner":"Narigo","description":"Trying out ScalaJS by porting CycleJS to ScalaJS","archived":false,"fork":false,"pushed_at":"2017-05-29T06:41:55.000Z","size":364,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-29T14:06:34.017Z","etag":null,"topics":["cyclejs","scalajs"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Narigo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-04T21:02:27.000Z","updated_at":"2020-06-13T08:41:50.000Z","dependencies_parsed_at":"2023-09-02T16:00:26.135Z","dependency_job_id":null,"html_url":"https://github.com/Narigo/scalajs-fun","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Narigo%2Fscalajs-fun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Narigo%2Fscalajs-fun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Narigo%2Fscalajs-fun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Narigo%2Fscalajs-fun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Narigo","download_url":"https://codeload.github.com/Narigo/scalajs-fun/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246563384,"owners_count":20797446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cyclejs","scalajs"],"created_at":"2024-10-26T06:22:24.798Z","updated_at":"2025-04-01T00:41:59.122Z","avatar_url":"https://github.com/Narigo.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# scalajs-fun\n\nTrying out ScalaJS.\n\nI'll try to port CycleJS to ScalaJS in order to learn both of it.\n\nThe egghead course looks like a good way to start 🤔\nhttps://egghead.io/courses/cycle-js-fundamentals\n\n## Used libraries\n\nIf you want to use a library with Scala.js, you need to use one that is exported to\n`\u003cgroup\u003e.\u003cidentifier\u003e_sjs\u003cversion\u003e` (just like Scala `_2.10` or `_2.11`). RxScala is currently not supporting this,\naccording to [their issue #161](https://github.com/ReactiveX/RxScala/issues/161).\n\nTherefore, I went with [scala.rx](https://github.com/lihaoyi/scala.rx). It is not really the same as RxScala, but you\ncan have reactive variables with it. It has a different API, but may be working in a similar way.\n\n**Update:** After fiddling around a lot with scala.rx, I've switched to another library. This results in an \nimplementation that looks a lot more similar to the real CycleJS implementation shown in the videos.\n\n## Video 01 - The Cycle.js principle: separating logic from effects\n\nAs you cannot map with scala.rx but have `Var` and `Rx{}`, the code looks a bit different.\n\n## Video 02 - Main function and effects function\n\nTo separate logic and effects, the code is split into two functions. As this was done, the next step is to use the sink\n(the `Rx[String]` in our case) and hand it over to multiple effects: Writing into DOM and writing into console.\n\nThe main function is called `logic()` as `main()` is needed by `js.App` as main entry point.\n\n## Video 03 - Customizing effects from the main function\n\nIn this case, the main function (called `logic` in our case) is split into separate sinks. This is done through a map in\nthe video. It's not possible to return `Obs` directly and use it as the value of the observed `Rx` is not passed into\nthe callback.\n\nWeird behavior: When using `i() = i() * 2` instead of `i() = i() + 2`, the `consoleLogEffect` does not fire anymore.\nAfter fiddling around with this a bit more, I realized that `i` was set to `Var(0)` initially. Every update `* 2` made\nit be `0` again, so it does not update internally and therefore does not fire again. To prevent that, one should use the\n`.propagate()` method on the `Rx` / `Obs` that listens on `i`.\n\nWhen using [scala.rx](https://github.com/lihaoyi/scala.rx) version `0.3.0+`, it added Ownership context. This is used to\nprevent creating leaky `Rx` when nesting them. The documentation doesn't really say how to create a safe context, so in\nthe current code, the line `implicit private val ctx = Ctx.Owner.safe()` is added to the App object as it doesn't seem\nto be possible to create this in the `main()` method itself.\n\n## Video 04 - Introducing run() and driver functions\n\nThis is more or less a matter of renaming things: `effects` are called `drivers` now and there is a map to show the\nrespective drivers that are at work right now. If one is not used / needed, one can comment it out without having to\ntouch multiple lines now.\n\n## Video 05 - Read effects from the DOM: click events\n\nIn this video, the drivers start returning sources. The proxy logic is different to JavaScript as we are usually not\npassing around mutable state. It needs to be done in a more scala-like way.\n\nThe drivers have to use input and output parameters in order to use the same Variable as the `proxySource`. Using the\n`.getOrElseUpdate()` method on the `proxySource` `Map`, we can initialize the sources in the `main` method. This keeps\nthe whole logic and variable initialization inside the main method.\n\n## Video 06 - Generalizing run() function for more types of sources\n\nUsing the `trigger` method, we can actually get rid of the output parameter in the drivers method. This way, we have two\nseparate variables, but this seems to be the way that Cycle does it itself.\n\nTo truly generalize the `run` method now, it's been pulled out into the `Scycle` object. Using type aliases makes it a\nbit easier to understand which types are used for what. It also shows that the logic method does a bit more right now\nthan just taking the drivers output and giving some output to feed into the respective driver.\n\n### Note about implementation of the timer\n\nA small thing which is different to the implementation of the JavaScript is that the interval timer does not get reset\nevery time the document is clicked. If we were starting a new interval timer on each click, we would need to introduce a\nvariable that can be used to clear the old timer and remembers the new one. So it's possible to do but in order to keep\nthe implementation of the core Cycle principle in focus, we will continue with the single timer.\n\n## Video 07 - Making our toy DOM Driver more flexible\n\nThe latest refactorings are done to abstract the driver and let it do a lot more for us. It receives a specific input \n(in case of the `DomDriver` it wants a `dom.Element`) and returns functions that can be used to receive events in the \nmain function. Called `selectEvents`, it takes the currently available DOM elements and adds event listeners on them. In\nthe current implementation, it cannot add listeners to the added elements yet, as the selectEvents call is done before \nthe elements are added to the DOM.\n\n## Video 08 - Fine-grained control over the DOM Source\n\nThe new implementation of the DomDriver takes all events that occur on the document and then filters out the ones that\nwe created listeners for. This ensures to catch events of newly created DOM elements as well.\n\n## Video 09 - Hyperscript as our alternative to template languages\n\nIn this video, we see a small improvement on creating and handling the DOM elements. It uses Hyperscript, which is \nbasically functions wrapping around the creation of DOM elements. Something similar is implemented with the \n`Hyperscript` trait and the `HyperScriptElement` classes. The `Text` case is somewhat special, as we need to wrap a text\nnode into a span to let it count as an element. With an implicit conversion of `String` to `Text` (`stringToTextNode`)\nwe can get rid of extra calls to `Text()`.\n\n## Video 10 - From toy DOM Driver to real DOM Driver\n\nWhen creating the DOM Driver, we now pass a selector to it, to select the container element. The container element is \nstill the one we were using before (`#app`), so the result does not change. The `makeXXXDriver` functions now return a\nfunction expecting a `LogicOutput` and return a `Driver`. This way, we do not need to pass an explicit `input` and can\njust use / cast from the `LogicOutput` (that is what the `input` variable has been before anyways).\n\nThe drivers were moved into a separate package. During this refactoring, the `ConsoleDriver` received the `input` \nparameter just like the `DomDriver`.\n\n\u003e Latest refactor: Move `Hyperscript` elements into `dom` package. Remove `ConsoleDriver` to focus on single `dom` \ndriver and start with next lesson.\n\n## Video 11 - Hello World in Cycle.js\n\nFirst, we create a few more helpers for the `Hyperscript`. A `label`, `input` and `hr` element helps to build the GUI.\nDoing a complete replacement of `container.innerHTML` with the `outerHTML` of `input()`, the GUI updates with a quirk: \nThe input field looses focus whenever the view updates.\n\nWhen using a virtual dom implementation that would not replace the whole `input` DOM element, we could mitigate this\nproblem. But that means we need to implement a virtual dom before continuing.  \n\n---\n\n### Starting a virtual dom implementation\n\nFinally something we can test! Let's start by adding a scalatest dependency. A small test to check that the test setup \nworks is a nice trick to be sure that you're not wasting time debugging your tests when in reality the build setup is \nnot correct.\n\nTo be sure that the code can be tested correctly, we need to test within a browser instead of testing in Rhino \nenvironment. This can be done by either installing PhantomJS (`npm i -g phantomjs`) and disabling Rhino in sbt (by\n`set scalaJSUseRhino in Global := false` in the sbt REPL) or testing with a browser directly through the new HTML Test \nRunner feature in ScalaJS (use `testHtmlFastOpt` and open the path to the file presented in the output in the browser).\n\nAfter building a simple virtual dom diffing algorithm, it turns out that adding \"simple\" optimizations may take some \ntime to get them right. There may still be some bugs lurking in the current algorithm but we'll try to refocus on the\nreal implementation of CycleJS again.\n\n---\n\nBeing done with the virtual dom implementation for now, we can update our DOM driver to use it. It made sense to let the\nmain function return a virtual dom representation as well instead of mapping from and to real HTML elements. This way, \nthe driver only needs the virtual dom representation and the user code (our `ScycleApp` `logic` function) does not need\nto deal with the real DOM itself.\n\n## Video 12 - An interactive counter in Cycle.js\n\nBefore beginning the implementation of the next video, we are going to need a few more HTML elements: `button` and `p`. \nWe are going to implement them just like all other of our `Hyperscript` elements for now.\n\nAfter all the work with virtual dom was done, setting up the GUI is pretty straight forward. The two buttons, a \nparagraph and text label. In the video, we see how to merge and scan `Rx.Observable` streams. With the `ScalaRx` \nimplementation we can use the `triggerLater` methods to update the `result`, which basically is our counter state.  \n\nTo make it more in sync with the JavaScript implementation, we took the `Rx` context out of the Map. This way it will \nre-evaluate the `Map` with the calculations outside of the values we return for the drivers.\n\n## Video 13 - Using the HTTP Driver\n\nWe start by making a new GUI for the desired result. The second step is to hook up the `Rx` variables of an `HttpDriver`\nto the app.\n\n\u003e After struggling a lot trying to get the value of an `Option`, it looks like ScalaJS or rx.Scala do not like using \n\u003e `Option.map` or `Option.get`. The underlying problem seems to be that `Rx[Something]` does not like having null values\n\u003e assigned to it. We need to investigate this behavior further to see what we can do. Maybe having a `Non-Request` class\n\u003e would help.\n\u003e \n\u003e Turns out that `NonRequest` doesn't really help. It's also not very clear to me why using `response()` in `ScycleApp`\n\u003e does not fire. A lot of debugging code exists in this commit now, but I don't think I'll come around to fix this. As \n\u003e it doesn't really look like the `scala.rx` library gets much love, I will push this on a branch and restart this \n\u003e project with another library: https://github.com/LukaJCB/rxscala-js\n\u003e\n\u003e Using the rxscala-js library, the code will be much closer to the videos anyways. Let's see how far we get using that.\n\n## Restarting with rxscala-js\n\nAfter trying out rxscala for the first few videos, we're going to restart implementing [Cycle.js](http://cycle.js.org/) \nwith [rxscala-js](https://github.com/LukaJCB/rxscala-js). As it is based on Observables, we can implement it more easily\nfollowing the videos.\n\nTo begin, we have to rewrite the main logic around observables. We can keep the `VirtualDom` implementation for now and \nsee how far we get with the new library. First, we need to depend on the new library by changing the dependencies in our\n`build.sbt` file using `\"com.github.lukajcb\" %%% \"rxscala-js\" % \"0.4.0\"`.\n\nAnother thing we need to do is add the `js-deps` script to our `index-dev.html`, as `rxscala-js` has a \"real\" JavaScript\ndependency. If we don't do that, we get errors in the browser later, even though everything compiles.\n\n### Implementing the main function again\n\nAs a first step in the rewrite, we implement the core functions again, namely `Scycle.run`. The first working iteration\nis a simple `Observable[_] =\u003e Observable[_]`. This looks similar to what we had before, but to get there, we need to use\nsub functions to let the compiler be able inferring the correct types. The functions `wireProxyToSink` and `createProxy`\ndo that.\n\nThis is not really the ideal scenario, especially since we still need to cast in the main (`logic`) function in our \n`ScycleApp` to get the correct `Observable`.\n\nThe first step to do less type casting is to make the `apply` method of the Driver cast itself to the correct types \ninstead of having the user cast it.\n\n## Video 14 - Body Mass Index calculator built in Cycle.js\n\nDuring this video, we can see how to build a small application in Cycle.js that calculates a BMI. With a few range \nsliders (input fields of type range), we should get a calculated value. The first thing we do is to build structure of \nthe resulting DOM tree.\n\n# Break!\n\nSo after fiddling around a lot with the new `RxScala` library, I've switched to just try to write tests and port over \nthe CycleJS code to Scala by looking at the sources. I'm still not sure if that's a good idea or try to re-implement the\ncore idea in ScalaJS. Especially as it uses quite a few `any` types and I'm unsure if that helps us at all here. On the \nother hand, I guess I'd need to reinvent an API that resonates more with Scala developers, but will not really resemble\nthe current CycleJS implementation (through JavaScript Objects - or in our case Scala Maps).\n\nMost probably I'll go on porting and then see if I can come up with a more Scala-ish API and rewrite it once I've \nlearned enough on the way.\n\n## Porting the code\n\nWhile porting the code, there was the need to add a small missing piece to the RxScala library. It did not fully support\nthe dispose mechanism as CycleJS uses it. After fixing this, the library updated to the newest ScalaJS version and this\nport updated it as well. After updating, we need to install the `jsdom` NPM module through `npm i jsdom`, which seems to\nbe new in order to get the tests for the still embedded virtual dom implementation working again.\n\nAfter putting a lot of work into getting at least the adapter tests pass, it was time for a refactor. Refactoring the \ncurrent code would mean to be able to cut a lot of unnecessary checks that CycleJS has due to JavaScript. While doing \nthat, there are some type issues that are pretty tricky to fix, due to type erasure. I'm actually unsure if it is \npossible to fix that at all right now...\n\nThinking more about this, it might be a good idea to try and find a more scala-ish way. Using Maps feels a bit strange \nand, regarding the usage syntax in the tests, is not very user friendly. Maybe having a new type for `Sinks` and \n`Sources` could help. Got to investigate this idea further.\n\nAfter fiddling around with lots of type issues, the main test for doing a complete cycle seems to work. Now the next \nstep is to fix the `DomDriver` to emit the correct events. Right now this does not seem to be as trivial as expected \neven though the cycling test works.\n\n## Got the cycle working!\n\nOkay, so in the last few commits a small application could do the cycle with the `DomDriver` and a very simple \n`HttpDriver`. As the Scycle code is now a lot like the real CycleJS implementation and we can try to simplify the \nimplementation a bit.\n\nThe next improvements were meant to get rid of some type issues and things that made the code overly complex. One thing \nto note is the `DriverFunction[_, _]`. It was used to make a driver always return an observable. If we look closely into\nthe Cycle code, this concept does not even exist. By now, the code got rid of this structure and lets all drivers \ninclude some function to return an `Observable[_]`. This is more like the real implementation and works quite well now.\n\n## Current status\n\nIt seems like the current implementation is at least somewhat okay for now and works for easy apps (see `ScycleApp`).\nMaybe now is the time to test this against the last few videos and see if everything works as expected. We can also make\nsome improvements to the creation to the drivers still: Cycle uses `makeXXXDriver` functions to construct a driver. The \ncurrent code just relies on `new XXXDriver` instead.\n\nWith the newest commit, the `makeXXXDriver` was implemented. This factory method will create the correct driver as Cycle\nitself does it.\n\n# Unbreak!\n\nWe continue with the videos again. After getting everything to run, we can finally do the last few videos again and see\nhow using the API works now.\n\n## Video 14 - Body Mass Index calculator built in Cycle.js\n\nAs mentioned before, we start by creating a small template with sliders. While doing that, we can see an issue with the\ncurrent virtual dom implementation. Input tags did not allow `min` and `max` attributes yet and therefore had to be \nchanged a bit in order to allow any kind of key/value pair. After fixing the tests, we can continue implementing \neverything.\n\nOne of the more hard to do things now was getting the DOM driver to be able to catch all events each time the DOM itself\nchanges. In the current version, we simply unsubscribe and reattach all event listeners that were selected. So for the\nsimple demo apps this may work, but at some point in the future, this could be optimized by having the selection of \nevents and the virtual dom implementation work together. It's also not possible to unsubscribe an event right now, which\nshould definitely be possible in a real application.\n\n## Video 15 - Model-View-Intent pattern for separation of concerns \n\nIn this video, the main components were split up into three parts. The read effects, the state calculation and the write\neffects. There is not much to be done in the Scycle code, just the example application had to change according to the \nvideo: In essence, it's just refactoring to get the main logic function smaller and split out the various calculations \nand selectors into smaller functions. \n\n---\n\n# Interlude\n\nThere is a small thing that should be done before continuing. This thing here is developed in the open and at some point\nit should be available for others to consume. Right now you can only fork and build it yourself through sbt. It should \nbe possible for anybody to just readily use it. Thus, we're going to release it to public now.\n\nWhat does that mean and what do we have to do to achieve that?\n\n## Preparing sbt for packaging\n\nFirst of all, we separate the examples from the core Scycle framework. Putting it into two directories helps a lot - \nusing different `src/main|examples|whatever` folders does not seem to work for me. It's either all sources in one or \ntrouble awaits.\n\nSo after splitting it into two sub-directories (`scycle` and `scycle-examples`), we have separate `src/main/scala` \nfolders as well as `src/test/scala`. In each of our projects we will be able to have tests, dependence on other projects\n(examples depend on the Scycle core in our case here).\n\n## Uploading Scycle-core to Maven Central or similar\n\nTODO\n\n---\n\n## Video 16 - Our first component: a labeled slider\n\nThe video shows how to refactor a single slider into a component. It does it by putting a new kind of \"driver\" into the \nsources object, which just returns the properties of the slider.\n\n## Video 17 - Using the component in the main() function\n\nA simple change is done in this video. The main function gets renamed to `LabeledSlider` and then a new `main` is \nwritten that uses this renamed function. In our case, the `logic` function is used for this as `main` is reserved for \nthe ScalaJS export.\n\n## Video 18 - Multiple independent instances of a component\n\nWe moved `LabeledSlider` into its own class here, so the example `ScycleApp` uses it for a weight and height slider with\ndifferent properties, using a simple `SliderPropsDriver` which provides a configuration for the sliders. To be in sync\nwith the current implementation of the `DomDriver`, we had to change the `select` event. It is now returning another \n`DomDriver` instance which uses the same mutable Map for `selectedEvents` in order to synchronize all captured events.\n\nThe example app is now able to pass a preselected `DomDriver` to the two `LabeledSlider` instances.\n\n## Video 19 - Isolating component instances\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarigo%2Fscalajs-fun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnarigo%2Fscalajs-fun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarigo%2Fscalajs-fun/lists"}