Ok, so I have managed to create it!!! And it works as expected. I can add and delete new tasks for now.
But, I think I broke the FRP law in one place.
I have created a new UI component that deals with Cell[List[Task]]
. The code can be found below:
class Listing[T](text: Cell[List[T]], f: T => scalatags.JsDom.Modifier) {
val initialValue: List[T] = List()
val element = span(for(elem <- initialValue.toSeq) yield f(elem))
var domElement = element.render
var listener = text
.changes()
.listen((newVal: List[T]) => {
val newLast =
span(for(elem <- newVal.toSeq) yield f(elem)).render
domElement.parentElement.replaceChild(newLast, domElement)
domElement = newLast
})
}
I basically map inside the component the list to a function that is provided as parameter. (that way I can wrap around the values in my list in HTML tags)
The main part of the program is here.
var stream: StreamSink[Message[Task]] = new StreamSink()
var loopState = new StreamLoop[List[Task]]
var cState = loopState.hold(List());
loopState.loop(
stream.snapshot(
cState,
(event, _state: List[Task]) => {
event.action match {
case "add" => {
println("add")
println(event.value)
event.value :: _state
}
case "remove" => {
println("remove")
println(event.value)
_state.filter(p => p.id != event.value.id)
}
}
}
)
)
//textfield for new task
val textField = new TextField("")
//button for adding a new task
val addButton = new Button("+", "")
addButton.eventClicked.snapshot(textField.text, (click, text: String) => {
// as I mentioned before, I think I break FRP law here because I'm explicitly sending events inside this stream
stream.send(new Message[Task]("add", new Task(text, false)))
""
})
//this is the new UI component I created. the second argument is a function that is
//applied to each element on my list and returns a fragment of HTML code
//i also create a button for each list, and I'm also using send here, which I believe it's a bad idea.
val listing = new Listing[Task](cState, elem => {
val delete = new Button("-", "")
delete.eventClicked.listen(u =>
stream.send(new Message[Task]("remove", elem))
)
div(cls := "d-flex align-items-center")(
div(cls := "d-flex align-items-center border p-2")(
span(cls := "mr-2", elem.description),
if (elem.isDone)
span(cls := "badge badge-pill badge-warning", "Ongoing")
else span(cls := "badge badge-pill badge-success", "Done")
),
delete.domElement(cls := "btn btn-light ml-2")
)
})
dom.document.body.innerHTML = ""
dom.document.body.appendChild(
div(cls := "mt-3")(
h1("Todo Application"),
br,
div(cls := "d-flex align-items-center")(
addButton.domElement,
textField.domElement
),
br,
br,
listing.domElement
).render
)
Here's my Message and Task classes if you're wondering how they look like.
class Task(var description: String, var isDone: Boolean) {
val id: String = ju.UUID.randomUUID.toString
}
class Message[T](var action: String, var value: T)
So, to conclude, my questions are how do I map the list inside a cell and render it properly? Or is my Listing component a good way of doing this? Also, How can I avoid explicitly emitting events (see comments in 2nd snippet of code).