Skip to content

Commit

Permalink
Replace asFrag with implicit conversions. Add map2 for Seqs
Browse files Browse the repository at this point in the history
  • Loading branch information
sake92 committed Dec 11, 2020
1 parent 4756bea commit c372b33
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 72 deletions.
1 change: 1 addition & 0 deletions core/src/main/scala/ba/sake/rxtags/Directives.scala
Expand Up @@ -18,6 +18,7 @@ trait Directives {

case class focus(range: (Int, Int)) extends Directive {
private val (start, end) = range

def execute(elem: dom.Element): Unit = {
val inputElem = elem.asInstanceOf[dom.html.Input]
inputElem.focus()
Expand Down
26 changes: 12 additions & 14 deletions core/src/main/scala/ba/sake/rxtags/RxFrags.scala
@@ -1,30 +1,28 @@
package ba.sake.rxtags

import java.util.UUID

import scala.language.implicitConversions
import org.scalajs.dom
import scalatags.JsDom.all._

private[rxtags] trait RxFrags {

implicit class StatefulFragOps[T <: Frag](rxFrag: Stateful[T]) {
def asFrag: Frag = new RxFrag(rxFrag)
}
implicit def statefulRxFrag[T <: Frag](rxFrag: Stateful[T]): Frag =
new RxFrag(rxFrag)

implicit class StatefulStringOps[T](rxString: Stateful[String]) {
private val rxFrag = rxString.map(StringFrag)
def asFrag: Frag = new RxFrag(rxFrag)
implicit def statefulRxString(rxString: Stateful[String]): Frag = {
val rxFrag = rxString.map(StringFrag)
new RxFrag(rxFrag)
}

implicit class StatefulNumericOps[T: Numeric](rxNum: Stateful[T]) {
private val rxFrag = rxNum.map(n => StringFrag(n.toString))
def asFrag: Frag = new RxFrag(rxFrag)
implicit def statefulRxNumeric[T: Numeric](rxNum: Stateful[T]): Frag = {
val rxFrag = rxNum.map(n => StringFrag(n.toString))
new RxFrag(rxFrag)
}

implicit class StatefulSeqFragOps[CC <: Seq[Frag]](rxSeq: Stateful[CC]) {
implicit def statefulRxSeq[CC <: Seq[Frag]](rxSeq: Stateful[CC]): Frag = {
implicit val ev: Frag => Frag = identity
private val rxFrag = rxSeq.map(s => SeqFrag(s))
def asFrag: Frag = new RxFrag(rxFrag)
val rxFrag = rxSeq.map(s => SeqFrag(s))
new RxFrag(rxFrag)
}
}

Expand Down
8 changes: 7 additions & 1 deletion core/src/main/scala/ba/sake/rxtags/package.scala
@@ -1,3 +1,9 @@
package ba.sake

package object rxtags extends RxFrags with RxAttrValues with RxStyleValues with ScalatagsAddons with Directives
package object rxtags extends RxFrags with RxAttrValues with RxStyleValues with ScalatagsAddons with Directives {

implicit class StatefulSeqOps[T](rx: Stateful[Seq[T]]) {
def map2[R](f: T => R): Stateful[Seq[R]] = rx.map(cc => cc.map(f))
}

}
4 changes: 2 additions & 2 deletions core/src/main/scala/ba/sake/rxtags/vars.scala
@@ -1,13 +1,13 @@
package ba.sake.rxtags

trait Reactive[T] {
trait Reactive[+T] {

def attach(f: T => Unit): Unit

def on(f: => Unit): Unit = attach(_ => f)
}

trait Stateful[T] extends Reactive[T] {
trait Stateful[+T] extends Reactive[T] {

def now: T

Expand Down
21 changes: 10 additions & 11 deletions docs/src/main/scala/site/View.scala
Expand Up @@ -23,7 +23,7 @@ object View extends templates.RxTagsBlogPage {
Here is an example of a ticker, it counts the number of seconds passed:
""".md,
chl.scala.withLineHighlight("1,4,8")(
chl.scala.withLineHighlight("1,4,10")(
"""
val ticker$ = Var(0)
Expand All @@ -32,9 +32,10 @@ object View extends templates.RxTagsBlogPage {
1000
)
def content = ticker$.map { c =>
s"Ticker: $c"
}.asFrag
def content = div(
"Ticker: ",
ticker$
)
"""
),
b("Result:"),
Expand All @@ -44,10 +45,9 @@ object View extends templates.RxTagsBlogPage {
Next, at line <mark>4</mark> we increment it, every second.
Finally, at line <mark>8</mark> we `map` it to HTML.
When we `map` a `Var[T]` to a `Var[Frag]`,
ScalaTags doesn't know how to render it to DOM.
That's why we need to call `asFrag` on it.
Finally, at line <mark>10</mark> we render it to HTML.
Any `Var[Frag]` can be put just as ordinary ScalaTags! :)
There are also handy implicit conversions for `Var[String]`, `Var[Int]` (in example above) and similar.
---
Here is another example, using an event handler:
Expand All @@ -60,9 +60,8 @@ object View extends templates.RxTagsBlogPage {
"Please enter your name: ",
input(onkeyup := updateName),
br,
name$.map { name =>
s"Your name: $name"
}.asFrag
"Your name: ",
name$
)
def updateName: (dom.KeyboardEvent => Unit) =
Expand Down
5 changes: 2 additions & 3 deletions examples/src/main/scala/ba/sake/rxtags/example/Ex3.scala
Expand Up @@ -13,9 +13,8 @@ object Ex3 extends Example {
"Please enter your name: ",
input(onkeyup := updateName),
br,
name$.map { name =>
s"Your name: $name"
}.asFrag
"Your name: ",
name$
)

def updateName: (dom.KeyboardEvent => Unit) =
Expand Down
2 changes: 1 addition & 1 deletion examples/src/main/scala/ba/sake/rxtags/example/Ex4.scala
Expand Up @@ -9,7 +9,7 @@ object Ex4 extends Example {
val counter$ = Var(0)

def content = div(
counter$.map(c => h4(s"Reactive counter: $c")).asFrag,
counter$.map(c => h4(s"Reactive counter: $c")),
button(onclick := add(-1))("-"),
button(onclick := add(1))("+")
)
Expand Down
2 changes: 1 addition & 1 deletion examples/src/main/scala/ba/sake/rxtags/example/Ex5.scala
Expand Up @@ -16,7 +16,7 @@ object Ex5 extends Example {

def content = div(
"Ticker: ",
ticker$.asFrag
ticker$
)

}
18 changes: 8 additions & 10 deletions examples/src/main/scala/ba/sake/rxtags/example/Ex6.scala
Expand Up @@ -14,20 +14,18 @@ object Ex6 extends Example {
CartItem(10, "eggs"),
CartItem(1, "milk"),
CartItem(3, "bananas"),
CartItem(2, "icecreams")
CartItem(2, "ice creams")
)
)

def content = ul(
cartItems$.map { items =>
items.map { item =>
li(
b(item.count),
s" ${item.name} ",
button(onclick := delete(item))("Delete")
)
}
}.asFrag
cartItems$.map2 { item =>
li(
b(item.count),
s" ${item.name} ",
button(onclick := delete(item))("Delete")
)
}
)

def delete(item: CartItem): (dom.Event => Unit) =
Expand Down
19 changes: 8 additions & 11 deletions todo/src/main/scala/ba/sake/rxtags/todo/Main.scala
Expand Up @@ -10,18 +10,15 @@ object Main {
val todoService = new TodoService
val mainComponent = new MainComponent(todoService, router)

val routes: Router.Routes = {
case "/active" =>
mainComponent.todoFilter$.set(TodoFilter.Active)
mainComponent
case "/completed" =>
mainComponent.todoFilter$.set(TodoFilter.Completed)
mainComponent
case _ =>
mainComponent.todoFilter$.set(TodoFilter.All)
mainComponent
// always return MainComponent
val routes: Router.Routes = _ => mainComponent

val listener: Router.Listener = {
case "/active" => todoService.filter$.set(TodoFilter.Active)
case "/completed" => todoService.filter$.set(TodoFilter.Completed)
case _ => todoService.filter$.set(TodoFilter.All)
}

router.withRoutesData("main", routes).init()
router.withRoutesData("main", routes).withListener(listener).init()
}
}
17 changes: 6 additions & 11 deletions todo/src/main/scala/ba/sake/rxtags/todo/MainComponent.scala
Expand Up @@ -4,18 +4,15 @@ import org.scalajs.dom
import org.scalajs.dom.ext.KeyValue
import org.scalajs.dom.html
import scalatags.JsDom.all._
import scalatags.JsDom.tags2.section
import ba.sake.scalajs_router._
import ba.sake.rxtags._

class MainComponent(todoService: TodoService, router: Router) extends Component {

val todoFilter$ = Var(TodoFilter.All)

private val todos$ = todoService.todos$

private val todosFiltered$ = todos$.map {
todos => todos.filter(todoFilter$.now.isValid)
todos => todos.filter(todoService.filter$.now.isValid)
}

private val mainDisplay$ = todos$.map(todos => if (todos.isEmpty) "none" else "block")
Expand Down Expand Up @@ -46,11 +43,9 @@ class MainComponent(todoService: TodoService, router: Router) extends Component
),
label(`for` := "toggle-all", "Mark all as complete"),
ul(cls := "todo-list")(
todosFiltered$.map { tf =>
tf.map { t =>
TodoComponent(todoService, t).render
}
}.asFrag
todosFiltered$.map2 { t =>
TodoComponent(todoService, t).render
}
)
),
footer(cls := "footer", css("display") := mainDisplay$)(
Expand All @@ -59,7 +54,7 @@ class MainComponent(todoService: TodoService, router: Router) extends Component
val count = todos.count(!_.completed)
val itemsLabel = if (count == 1) "item" else "items"
frag(strong(count), s" $itemsLabel left")
}.asFrag
}
),
ul(cls := "filters")(
li(
Expand Down Expand Up @@ -107,7 +102,7 @@ class MainComponent(todoService: TodoService, router: Router) extends Component
}

private def selectedCls(filter: TodoFilter) =
todoFilter$.map { tf =>
todoService.filter$.map { tf =>
Option.when(tf == filter)("selected")
}
}
4 changes: 1 addition & 3 deletions todo/src/main/scala/ba/sake/rxtags/todo/Todo.scala
Expand Up @@ -2,9 +2,7 @@ package ba.sake.rxtags.todo

import java.util.UUID

case class Todo(name: String, completed: Boolean = false, id: UUID = UUID.randomUUID()) {
def toggled: Todo = copy(completed = !completed)
}
case class Todo(name: String, completed: Boolean = false, id: UUID = UUID.randomUUID())

object Todo {
import upickle.default.{ReadWriter => RW, macroRW}
Expand Down
2 changes: 1 addition & 1 deletion todo/src/main/scala/ba/sake/rxtags/todo/TodoFilter.scala
Expand Up @@ -5,7 +5,7 @@ trait TodoFilter {
}

object TodoFilter {
val All: TodoFilter = todo => true
val All: TodoFilter = _ => true
val Completed: TodoFilter = todo => todo.completed
val Active: TodoFilter = todo => !todo.completed
}
5 changes: 2 additions & 3 deletions todo/src/main/scala/ba/sake/rxtags/todo/TodoService.scala
Expand Up @@ -6,12 +6,11 @@ import ba.sake.rxtags._

class TodoService {

val todos$ = initTodos()
val filter$ = Var(TodoFilter.All)
val toggleAllState$ = Var(false)

val editId$ = Var(Option.empty[UUID])

val todos$ = initTodos()

todos$.attachAndFire { todos =>
if (todos.length == 1) // synchronize with last element..
toggleAllState$.set(todos.head.completed)
Expand Down

0 comments on commit c372b33

Please sign in to comment.