{"id":17267487,"url":"https://github.com/technius/scalajs-mithril","last_synced_at":"2025-04-14T08:02:29.690Z","repository":{"id":57716319,"uuid":"41342486","full_name":"Technius/scalajs-mithril","owner":"Technius","description":"Scala.js facades for Mithril.js","archived":false,"fork":false,"pushed_at":"2017-07-05T00:01:17.000Z","size":1140,"stargazers_count":7,"open_issues_count":5,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-06T10:22:39.296Z","etag":null,"topics":["mithril","scala","scalajs","scalatags-support"],"latest_commit_sha":null,"homepage":null,"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/Technius.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}},"created_at":"2015-08-25T04:12:43.000Z","updated_at":"2018-11-23T02:02:35.000Z","dependencies_parsed_at":"2022-08-26T12:33:17.434Z","dependency_job_id":null,"html_url":"https://github.com/Technius/scalajs-mithril","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Fscalajs-mithril","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Fscalajs-mithril/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Fscalajs-mithril/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Fscalajs-mithril/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Technius","download_url":"https://codeload.github.com/Technius/scalajs-mithril/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240177055,"owners_count":19760308,"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":["mithril","scala","scalajs","scalatags-support"],"created_at":"2024-10-15T08:10:47.126Z","updated_at":"2025-02-24T23:31:03.210Z","avatar_url":"https://github.com/Technius.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Scala.js facades for Mithril.js\n\n[![Build Status](https://travis-ci.org/Technius/scalajs-mithril.svg?branch=master)](https://travis-ci.org/Technius/scalajs-mithril)\n\nThis is an experimental library that provides facades\nfor [Mithril](https://lhorie.github.io/mithril/index.html).\n\nAt the moment, scalajs-mithril is being rewritten to support mithril `1.1.1`.\nThe `0.1.0` version of scalajs-mithril for mithril `0.2.5` can be\nfound [here](/tree/v0.1.0).\n\nMithril 1.x.y is significantly different from 0.2.0, which is why this rewrite\nis required.\n\n## Table of Contents\n\n* [Setup](#setup)\n* [Example](#example)\n* [The Basics](#the-basics)\n  * [Using the Helpers](#using-the-helpers)\n  * [Subclassing Component](#subclassing-component)\n* [Routing](#routing)\n* [Making Web Requests](#making-web-requests)\n* [Scalatags Support](#scalatags-support)\n* [Compiling](#compiling)\n* [License](#license)\n\n## Setup\n\nAdd `scalajs-bundler` to `project/plugins.sbt`:\n```scala\naddSbtPlugin(\"ch.epfl.scala\" % \"sbt-scalajs-bundler\" % \"0.7.0\")\n```\n\nThen, add the following lines to `build.sbt`:\n```scala\nresolvers += Resolver.sonatypeRepo(\"snapshots\")\nlibraryDependencies += \"co.technius\" %%% \"scalajs-mithril\" % \"0.2.0-SNAPSHOT\"\nenablePlugins(ScalaJSBundlerPlugin)\n\n// Change mithril version to any version supported by this library\nnpmDependencies in Compile += \"mithril\" -\u003e \"1.1.1\"\n```\n\nBuild your project with `fastOptJS::webpack`.\n\n## Example\n\n```scala\nimport co.technius.scalajs.mithril._\nimport scala.scalajs.js\nimport org.scalajs.dom\n\nobject MyModule {\n  val component = Component.stateful[State, js.Object](_ =\u003e new State) { vnode =\u003e\n    import vnode.state\n    m(\"div\", js.Array[VNode](\n      m(\"span\", s\"Hi, ${state.name()}!\"),\n      m(\"input[type=text]\", js.Dynamic.literal(\n        oninput = m.withAttr(\"value\", state.name),\n        value = state.name()\n      ))\n    ))\n  }\n\n  class State {\n    val name = MithrilStream(\"Name\")\n  }\n}\n\nobject MyApp extends js.JSApp {\n  def main(): Unit = {\n    m.mount(dom.document.getElementById(\"app\"), MyModule.component)\n  }\n}\n```\n\n```html\n\u003c!DOCTYPE HTML\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003escalajs-mithril Example\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n    \u003cscript src=\"example-fastopt-bundle.js\"\u003e\u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nSee the [examples folder](/examples/src/main/scala)\nfor complete examples.\n\n## The Basics\n\nThis section assumes you are familiar with mithril. If you aren't, don't worry;\nmithril can be picked up very quickly.\n\nA component is parametized on `State` (for `vnode.state`) and `Attrs` (for\n`vnode.attrs`). If `State` and `Attrs` are not neccessary for the component, use\n`js.Object` and `js.Dictionary[js.Any]` should be used instead, respectively.\n\nThere are two ways to define a component using this library:\n\n1. Use one of the component helpers, such as `Component.stateful` or\n   `Component.viewOnly`.\n2. Subclass `Component`.\n\nWhile the helpers provide limited control over components, they are sufficiently\npowerful for most situations. If more control over a component is desired (e.g.\noverriding lifecycle methods), then subclass `Component` instead.\n\nVirtual DOM nodes (vnodes) are defined as `GenericVNode[State, Attr]`. For\nconvenience, a type alias `VNode` is defined as `GenericVNode[js.Object,\njs.Dictionary[js.Any]]` to reduce the hassle of adding type signatures for\nvnodes.\n\n### Using the Helpers\n\nDefining a stateless component is very simple using the `Component.viewOnly` function:\n\n```scala\nimport co.technius.scalajs.mithril._\nimport scala.scalajs.js\n\nobject HelloApp extends js.JSApp {\n  // viewOnly has the view function as an argument\n  val component = Component.viewOnly[js.Object] { vnode =\u003e\n    m(\"div\", \"Hello world!\")\n  }\n  \n  def main(): Unit = {\n    m.mount(dom.document.getElementById(\"app\"), component)\n  }\n}\n```\n\n`viewOnly` is parameterized on `Attr`, so it's possible to handle arguments:\n\n```scala\n// First, create a class to represent the attriute object\ncase class Attr(name: String)\n\n// Then, supply it as a type parameter to viewOnly\nval component = Component.viewOnly[Attr] { vnode =\u003e\n  m(\"div\", \"Hello \" + vnode.attr.name)\n}\n```\n\nIt's more common to see stateful components, though. They can be defined using\n`Component.stateful`.\n\n```scala\nimport co.technius.scalajs.mithril._\nimport scala.scalajs.js\n\nobject NameApp extends js.JSApp {\n\n  // Just like for attributes, define a state class to hold the state\n  protected class State {\n    var name = \"Name\"\n  }\n\n  // stateful has two arguments:\n  // - A function to create the state from a vnode\n  // - The view function\n  // It's also parameterized on state and attributes\n  val component = Component.stateful[State, js.Object](_ =\u003e new State) { vnode =\u003e\n    import vnode.state\n    m(\"div\", js.Array[VNode](\n      m(\"span\", s\"Hi, ${state.name}!\"),\n      m(\"input[type=text]\", js.Dynamic.literal(\n        oninput = m.withAttr(\"value\", newName =\u003e state.name = newName),\n        value = state.name\n      ))\n    ))\n  }\n  \n  def main(): Unit = {\n    m.mount(dom.document.getElementById(\"app\"), component)\n  }\n}\n```\n\n### Subclassing Component\n\nSubclassing component requires more boilerplate, but it gives more fine-grained\ncontrol over the component's lifecycle.\n\nFirst, you'll need to define your component, which is parametized on `State`\n(for `vnode.state`) and `Attrs` (for `vnode.attrs`). If `State` and `Attrs` are\nnot neccessary for the component, use `js.Object`.\n\n```scala\nobject MyComponent extends Component[js.Object, js.Object] {\n  // RootNode is defined as an alias\n  override val view = (vnode: RootNode) =\u003e {\n    m(\"div\", js.Array(\n      m(\"p\", \"Hello world!\")\n      m(\"p\", \"How fantastic!\")\n    ))\n  }\n}\n```\n\n`Component` is a subtrait of `Lifecycle`, which defines the lifecycle methods.\nThus, it's possible to override the lifecycle methods in a `Component`. Here's\nan example of a stateful component that overrides `oninit` to set the state:\n\n```scala\nobject MyComponent extends Component[MyComponentState, js.Object] {\n  override val oninit = js.defined { (vnode: RootNode) =\u003e\n    vnode.state = new MyComponentState\n  }\n\n  override val view = { vnode: RootNode =\u003e\n    import vnode.state\n    m(\"div\", js.Array(\n      m(\"span\", s\"Hey there, ${state.name()}!\"),\n      m(\"input[type=text]\", js.Dynamic.literal(\n        oninput = m.withAttr(\"value\", state.name),\n        value = ctrl.name()\n      ))\n    ))\n  }\n}\n\nclass MyComponentState {\n  val name = MithrilStream(\"Name\")\n}\n```\n\nDue to the way mithril handles the fields in the component, runtime errors\noccur if methods or functions are defined directly in the component from\nScala.js. One possible workaround is to define the functions in an inner object:\n\n```scala\nobject MyComponent extends Component[MyComponentState, js.Object] {\n  override val oninit = js.defined { (vnode: RootNode) =\u003e\n    vnode.state = new MyComponentState\n  }\n\n  override val view = { (vnode: RootNode) =\u003e\n    import helpers._\n    myFunction(vnode.state)\n    /* other code omitted */\n  }\n  \n  object helpers {\n    def myFunction(state: MyComponentState): Unit = {\n      // do stuff\n    }\n  }\n}\n\nclass MyComponentState { /* contents omitted */ }\n```\n\nLastly, call `m.mount` with your controller:\n\n```scala\nimport co.technius.scalajs.mithril._\nimport org.scalajs.dom\nimport scala.scalajs.js\n\nobject MyApp extends js.JSApp {\n  def main(): Unit = {\n    m.mount(dom.document.getElementById(\"app\"), MyComponent)\n  }\n}\n```\n\nTo use `Attrs` in a component, define a class for `Attrs` and change the\nparameter on `Component`. The component should then be created by calling\n`m(component, attrs)` (see the TreeComponent example).\n\n```scala\nimport co.technius.scalajs.mithril._\n\ncase class MyAttrs(name: String)\n\nobject MyComponent extends Component[js.Object, MyAttrs] {\n  override val view =  { (vnode: RootNode) =\u003e\n    m(\"span\", vnode.attrs.name)\n  }\n}\n```\n\n## Routing\n\nTo use Mithril's routing functionality, use `m.route` as it is defined in mithril:\n\n```scala\nimport co.technius.scalajs.mithril._\nimport org.scalajs.dom\nimport scala.scalajs.js\n\nobject MyApp extends js.JSApp {\n  val homeComponent = Component.viewOnly[js.Object] { vnode =\u003e\n    m(\"div\", \"This is the home page\")\n  }\n\n  val pageComponent = Component.viewOnly[js.Object] { vnode =\u003e\n    m(\"div\", \"This is another page\")\n  }\n\n  def main(): Unit = {\n    val routes = js.Dictionary[MithrilRoute.Route](\n      \"/\" -\u003e homeComponent,\n      \"/page\" -\u003e pageComponent\n    )\n    m.route(dom.document.getElementById(\"app\"), \"/\", routes)\n  }\n}\n```\n\nFor convenience, there is an alias for `m.route` that accepts a vararg list of\nroutes instead of a `js.Dictionary`:\n\n```scala\nm.route(dom.document.getElementById(\"app\"), \"/\", routes)(\n  \"/\" -\u003e homeComponent,\n  \"/page\" -\u003e pageComponent\n)\n```\n\nA [`RouteResolver`](https://mithril.js.org/route.html#routeresolver) may be used\nfor more complicated routing situations. There are two ways to construct a\n`RouteResolver`: using a helper method or subclassing `RouteResolver`.\n\n`RouteResolver.render` creates a `RouteResolver` with the given `render`\nfunction.\n\n```scala\nm.route(dom.document.getElementById(\"app\"), \"/\", routes)(\n  \"/\" -\u003e RouteResolver.render { vnode =\u003e\n    m(\"div\", js.Array[VNode](\n      m(\"h1\", \"Home component\"),\n      homeComponent\n    ))\n  },\n  \"/page\" -\u003e pageComponent\n)\n```\n\nSimilarly, `RouteResolver.onmatch` creates a `RouteResolver` with the given\n`onmatch` function.\n\n```scala\nval accessDeniedComponent = Component.viewOnly[js.Object] { vnode =\u003e\n  m(\"div\", \"Incorrect or missing password!\")\n}\n\nval secretComponent = Component.viewOnly[js.Object] { vnode =\u003e\n  m(\"div\", \"Welcome to the secret page!\")\n}\n\nm.route(dom.document.getElementById(\"app\"), \"/\", routes)(\n  \"/secret\" -\u003e RouteResolver.onmatch { (params, requestedPath) =\u003e\n    // check if password is correct\n    if (params.get(\"password\").fold(false)(_ == \"12345\")) {\n      secretComponent\n    } else {\n      accessDeniedComponent\n    }\n  }\n)\n```\n\nIf it is required to define both `render` and `onmatch`, subclass\n`RouteResolver`. Note that this library always ensures that `render` is defined,\nbut allows `onmatch` to be undefined.\n\n```scala\nval helloComponent = Component.viewOnly[js.Object](_ =\u003e m(\"div\", \"Hello world!\"))\n\nval myRouteResolver = new RouteResolver {\n  override def onmatch = js.defined { (params, requestedPath) =\u003e\n    helloComponent\n  }\n\n  override def render = { vnode =\u003e\n    vnode\n  }\n}\n```\n\n## Making Web Requests\n\nFirst, create an `XHROptions[T]`, where `T` is the data to be returned:\n\n```scala\nval opts = new XHROptions[js.Object](method = \"GET\", url = \"/path/to/request\")\n```\n\nIt's possible to use most of the optional arguments:\n```scala\nval opts =\n  new XHROptions[js.Object](\n    method = \"POST\",\n    url = \"/path/to/request\",\n    data = js.Dynamic.literal(\"foo\" -\u003e 1, \"bar\" -\u003e 2),\n    background = true)\n```\n\nThen, pass the options to `m.request`, which will return a `js.Promise[T]`:\n```scala\nval reqPromise = m.request(opts)\n\n// convert Promise[T] to Future[T]\n// use of Future requires implicit ExecutionContext\nimport scala.concurrent.ExecutionContext.Implicits.global\nreqPromise.toFuture.foreach { data =\u003e\n  println(data)\n}\n```\n\nBy default, the response data will be returned as a `js.Object`. It may be\nconvenient to define a facade to hold the response data:\n\n```scala\n// Based on examples/src/main/resources/sample-data.json\nimport scala.concurrent.ExecutionContext.Implicits.global\n@js.native\ntrait MyData extends js.Object {\n  val key: String\n  val some_number: Int\n}\n\nval opts = new XHROptions[MyData](method = \"GET\", url = \"/path/to/request\")\n\nm.request(opts).toFuture foreach { data =\u003e\n  println(data.key)\n  println(data.some_number)\n}\n```\n\n## Scalatags Support\n\nThere is also support for [Scalatags](http://lihaoyi.com/scalatags), which can\nmake it easier to create views. Add the following line to `build.sbt`:\n\n```scala\nlibraryDependencies += \"co.technius\" %%% \"scalajs-mithril-scalatags\" % \"0.2.0-SNAPSHOT\"\n```\n\nThen, import `co.technius.scalajs.mithril.VNodeScalatags.all._`. If you already\nimported the `mithril` package as a wildcard, simply import\n`VNodeScalatags.all._`. You can then use tags in your components:\n\n```scala\nval component = Component.viewOnly[js.Object] { vnode =\u003e\n  div(id := \"my-div\")(\n    p(\"Hello world!\")\n  ).render\n}\n```\n\nIt's also possible to use `VNode`s with scalatags:\n\n```scala\n// Components are also VNodes\nval embeddedComponent = Component.viewOnly[js.Object] { vnode =\u003e\n  p(\"My embedded component\").render\n}\n\nval component = Component.viewOnly[js.Object] { vnode =\u003e\n  div(\n    m(\"p\", \"My root component\"),\n    embeddedComponent\n  ).render\n}\n```\n\nThe lifecycle methods, as well as `key`, are usable as attributes in scalatags:\n\n```scala\nclass Attrs(items: scala.collection.mutable.Map[String, String])\n\nval component = Component.viewOnly[Attrs] { vnode =\u003e\n  ul(oninit := { () =\u003e println(\"Initialized!\") })(\n    vnode.attrs.items.map {\n      case (id, name) =\u003e li(key := id, name)\n    }\n  ).render\n}\n```\n\nSee the [scalatags demo](/examples/src/main/scala/ScalatagsDemo.scala) for an\nexample.\n\n## Compiling\n\n* Compile the core project with `core/compile`.\n* Examples can be built locally by running `examples/fastOptJS::webpack` and\nthen navigating to `examples/src/main/resources/index.html`.\n* The benchmarks are built in the same manner as the examples. `benchmarks/fastOptJS::webpack`\n* To run tests, use `tests/test`.\n\n## License\nThis library is licensed under the MIT License. See LICENSE for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnius%2Fscalajs-mithril","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechnius%2Fscalajs-mithril","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnius%2Fscalajs-mithril/lists"}