Skip to content

Instantly share code, notes, and snippets.

@Fristi
Last active November 14, 2018 16:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Fristi/34f06be89e6747fd3f57 to your computer and use it in GitHub Desktop.
Save Fristi/34f06be89e6747fd3f57 to your computer and use it in GitHub Desktop.
A simple examle of Scala.js ala Elm
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
}
@ches
Copy link

ches commented Nov 5, 2016

In case you're landing here first, there's exposition of this example on this article here.

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