Created
January 19, 2019 19:09
-
-
Save russwyte/f47d8a5ac0fc3d30f3532a2de2b7d2ca to your computer and use it in GitHub Desktop.
react4s Todo with Diode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package todo | |
import com.github.ahnfelt.react4s._ | |
import org.scalajs.dom.window | |
import todo.TodoCircuit._ | |
import scala.scalajs.js.annotation._ | |
@JSExportTopLevel("TodoApp") | |
object TodoApp { | |
@JSExport | |
def run(): Unit = { | |
TodoCircuit(UpdateText("")) | |
} | |
TodoCircuit.subscribe(TodoCircuit.zoomTo(_.model))(m => { | |
ReactBridge.renderToDomById(Component(TodoListComponent, m.value), "main") | |
}) | |
} | |
case class TodoListComponent(model: P[Model]) extends Component[NoEmit] { | |
val filterCompleted = | |
AddEventListener(this, window, "hashchange", _ => window.location.hash) { | |
case ("#/active", _) => Some(false) | |
case ("#/completed", _) => Some(true) | |
case (_, _) => None | |
} | |
def nextId(get: Get) = (0 :: get(model).todos.map(_.id)).max + 1 | |
override def render(get: Get) = { | |
println("rendered...") | |
Fragment( | |
E.section( | |
A.className("todoapp"), | |
E.header( | |
A.className("header"), | |
E.h1(Text("todos")), | |
E.form( | |
E.input( | |
A.className("new-todo"), | |
A.placeholder("What needs to be done?"), | |
A.autoFocus(), | |
A.value(get(model).text), | |
A.onChangeText(t => TodoCircuit(UpdateText(t))) | |
), | |
A.onSubmit { e => | |
e.preventDefault() | |
val item = TodoItem( | |
id = nextId(get), | |
title = get(model).text.trim, | |
completed = false | |
) | |
if (item.title.nonEmpty) { | |
TodoCircuit(UpdateItem(item)) | |
TodoCircuit(UpdateText("")) | |
} | |
} | |
) | |
), | |
E.section( | |
A.className("main"), | |
E.input( | |
A.id("toggle-all"), | |
A.className("toggle-all"), | |
A.`type`("checkbox"), | |
A.onLeftClick { _ => | |
val check = get(model).todos.exists(!_.completed) | |
TodoCircuit(UpdateCompleted(check)) | |
} | |
) | |
.when(get(model).todos.nonEmpty), | |
E.label(A.`for`("toggle-all"), Text("Mark all as complete")) | |
.when(get(model).todos.nonEmpty), | |
E.ul( | |
A.className("todo-list"), | |
Tags(for { | |
(item, i) <- get(model).todos.sortBy(_.id).zipWithIndex | |
if get(filterCompleted).forall(_ == item.completed) | |
} yield { | |
Component(TodoItemComponent, item) | |
.withHandler { | |
case Some(editedItem) => TodoCircuit(UpdateItem(editedItem)) | |
case None => TodoCircuit(RemoveItem(item)) | |
} | |
.withKey("item-" + item.id) | |
}) | |
) | |
.when(get(model).todos.nonEmpty) | |
), | |
E.footer( | |
A.className("footer"), { | |
val incomplete = get(model).todos.count(!_.completed) | |
E.span( | |
A.className("todo-count"), | |
E.strong(Text(incomplete + " ")), | |
Text( | |
if (incomplete == 1) "item left" | |
else "items left" | |
) | |
) | |
}, | |
E.ul( | |
A.className("filters"), | |
E.li( | |
E.a( | |
A.className("selected").when(get(filterCompleted).isEmpty), | |
A.href("#/"), | |
Text("All") | |
) | |
), | |
E.li( | |
E.a( | |
A.className("selected") | |
.when(get(filterCompleted).contains(false)), | |
A.href("#/active"), | |
Text("Active") | |
) | |
), | |
E.li( | |
E.a( | |
A.className("selected") | |
.when(get(filterCompleted).contains(true)), | |
A.href("#/completed"), | |
Text("Completed") | |
) | |
) | |
), | |
E.button( | |
A.className("clear-completed"), | |
Text("Clear completed"), | |
A.onLeftClick { _ => | |
TodoCircuit(ClearCompleted) | |
} | |
) | |
) | |
.when(get(model).todos.nonEmpty) | |
), | |
E.footer( | |
A.className("info"), | |
E.p(Text("Double-click to edit a todo")), | |
E.p( | |
E.a( | |
A.href("http://www.react4s.org"), | |
Text("Written in Scala with React4s") | |
) | |
), | |
E.p( | |
Text("Implements all of "), | |
E.a(A.href("http://todomvc.com/"), Text("TodoMVC")), | |
Text(" in "), | |
E.a( | |
A.href("https://github.com/ahnfelt/react4s-todomvc"), | |
Text("139 lines") | |
) | |
) | |
) | |
) | |
} | |
} | |
case class TodoItemComponent(item: P[TodoItem]) extends Component[Option[TodoItem]] { | |
val editing = State(false) | |
override def render(get: Get) = { | |
println("rendering", get(item).title) | |
E.li( | |
A.className("completed").when(get(item).completed), | |
A.className("editing").when(get(editing)), | |
E.div( | |
A.className("view"), | |
E.input( | |
A.className("toggle"), | |
A.`type`("checkbox"), | |
A.checked().when(get(item).completed), | |
A.onLeftClick( | |
_ => emit(Some(get(item).copy(completed = !get(item).completed))) | |
) | |
), | |
E.label(Text(get(item).title)), | |
E.button(A.className("destroy"), A.onLeftClick(_ => emit(None))), | |
A.on("DoubleClick", _ => editing.set(true)) | |
), | |
E.input( | |
A.className("edit"), | |
A.value(get(item).title), | |
A.onChangeText(text => emit(Some(get(item).copy(title = text)))), | |
A.autoFocus(), | |
A.onBlur(_ => editing.set(false)), | |
A.onKeyDown(e => if (e.key == "Enter") editing.set(false)) | |
) | |
.when(get(editing)) | |
) | |
} | |
} | |
import diode._ | |
object TodoCircuit extends Circuit[Root] { | |
override protected def initialModel: Root = Root(Model("", List.empty)) | |
val modelHandler: ActionHandler[Root, Model] = new ActionHandler(zoomTo(_.model)) { | |
override protected def handle: PartialFunction[Any, ActionResult[Root]] = { | |
case UpdateText(text) => | |
updated(value.copy(text = text)) | |
case UpdateItem(todoItem) => | |
updated { | |
println("Update", todoItem) | |
value.copy(todos = { todoItem :: value.todos.filterNot(_.id == todoItem.id) }) | |
} | |
case UpdateCompleted(b) => | |
updated(value.copy(todos = value.todos.map(_.copy(completed = b)))) | |
case RemoveItem(todoItem) => | |
updated(value.copy(todos = value.todos.filter(_.id != todoItem.id))) | |
case ClearCompleted => | |
updated(value.copy(todos = value.todos.filterNot(_.completed))) | |
} | |
} | |
override protected val actionHandler = foldHandlers(modelHandler) | |
case class UpdateText(text: String) extends Action | |
case class UpdateItem(todoItem: TodoItem) extends Action | |
case class UpdateCompleted(b: Boolean) extends Action | |
case class RemoveItem(todoItem: TodoItem) extends Action | |
case object ClearCompleted extends Action | |
} | |
case class Root(model: Model) | |
case class Model(text: String, todos: List[TodoItem]) | |
case class TodoItem(id: Int, title: String, completed: Boolean) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment