Skip to content

Instantly share code, notes, and snippets.

@flashingpumpkin
Forked from Fristi/ScalaJsElmExample.scala
Created November 14, 2018 16:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flashingpumpkin/e3288a4992dd56e73410680468152171 to your computer and use it in GitHub Desktop.
Save flashingpumpkin/e3288a4992dd56e73410680468152171 to your computer and use it in GitHub Desktop.
Monifu / Monix / Elm example
import japgolly.scalajs.react.vdom.all._
import japgolly.scalajs.react.{ReactElement, SyntheticEvent}
import monifu.reactive.Observable
import monifu.reactive.subjects.BehaviorSubject
object StateExample {
//---------------------------------
// (VIEW)-MODEL
//---------------------------------
case class Person(id: Int, name: String, age: Int)
val persons = Seq(
Person(1, "Karel", 22),
Person(2, "Dirk", 33),
Person(3, "Frits", 23)
)
//---------------------------------
// UPDATE
//---------------------------------
/**
* Our command ADT
**/
sealed trait Command
case object Init extends Command
case class IncreaseAge(personId: Int) extends Command
case class DecreaseAge(personId: Int) extends Command
/**
* This is our channel which receives commands,
* the start command is Init, so we just render our page
*/
val commands = BehaviorSubject[Command](Init)
/**
* Updates one person in a sequence of person by id
*
* @param model The sequence of persons
* @param id The id of the person to modify
* @param update The actual update
* @return A modified sequence of persons
*/
def updateAge(model: Seq[Person], id: Int, update: Person => Person): Seq[Person] = {
val idx = model.indexWhere(_.id == id)
if(idx > -1) {
model.patch(idx, Seq(update(model(idx))), 1)
} else {
model
}
}
/**
* This our state machine, it will process a sequence of Commands.
* Our initial seed is the list of persons (see above (view model)).
* The update function uses updateAge to return a new view model.
* Scan can be compared to Elm's foldp. Note there is also a Observable.flatScan available.
* This allows you to return a Observable[R],
* so you could work with Future's for example or other Observable stuff in the update function.
*/
val processor = commands.scan(persons) { case (model, cmd) =>
cmd match {
case Init => model
case IncreaseAge(personId) =>
updateAge(model, personId, s => s.copy(age = s.age + 1))
case DecreaseAge(personId) =>
updateAge(model, personId, s => s.copy(age = s.age - 1))
}
}
//---------------------------------
// VIEW
//---------------------------------
/**
* The view (pure function)
* @param persons A List of persons
* @return A ReactElement
*/
def view(persons: Seq[Person]) = table(
tr(th("Id"), th("Name"), th("Age"), th(), persons.map(renderPerson))
)
/**
* Function to render a Person
* @param person A person
* @return A ReactElement
*/
def renderPerson(person: Person) = tr(
td(person.id),
td(person.name),
td(person.age),
td(btn("Increase age", IncreaseAge(person.id)), btn("Decrease age", DecreaseAge(person.id)))
)
/**
* Helper function to render a button
* @param text The text of the button
* @param cmd The command which will be send to our commands channel (see above)
* @return A ReactElement
*/
def btn(text: String, cmd: Command) =
button(onClick ==> ((_: SyntheticEvent[org.scalajs.dom.Node]) => commands.onNext(cmd)))(text)
//---------------------------------
// MAIN LOOP
//---------------------------------
/**
* This fuses our processor and view together and will return a Observable[ReactElement]
* @return
*/
def page: Observable[ReactElement] = processor map view
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment