1
1
package com .thoughtworks .todo
2
2
3
- import org .lrng .binding .html
4
- import com .thoughtworks .binding .{Binding , Route }
5
- import com .thoughtworks .binding .Binding .{BindingSeq , Var , Vars }
6
- import com .thoughtworks .binding .Binding .BindingInstances .monadSyntax ._
3
+ import com .yang_bo .html .*
4
+ import com .thoughtworks .binding .{Binding , LatestEvent }
5
+ import com .thoughtworks .binding .Binding .{BindingSeq , Var , Vars , Constants }
7
6
7
+ import scala .scalajs .js
8
8
import scala .scalajs .js .annotation .{JSExport , JSExportTopLevel }
9
- import org .scalajs .dom .{Event , KeyboardEvent , window }
10
- import org .scalajs .dom .ext .{KeyCode , LocalStorage }
11
- import org .scalajs .dom .raw .{HTMLInputElement , Node }
12
- import upickle .default ._
9
+ import org .scalajs .dom .*
10
+ import upickle .default .*
13
11
14
12
@ JSExportTopLevel (" Main" ) object Main {
15
13
16
- /** @note [[Todo ]] is not a case class because we want to distinguish two [[Todo ]]s with the same content */
17
- final class Todo (val title : String , val completed : Boolean )
14
+ /** @note [[equals ]] is overridden to distinguish two [[Todo ]]s with the same content */
15
+ final case class Todo (title : String , completed : Boolean ) {
16
+ override def equals (x : Any ): Boolean = super .equals(x)
17
+ }
18
18
object Todo {
19
19
implicit val rw : ReadWriter [Todo ] = macroRW
20
- def apply (title : String , completed : Boolean ) = new Todo (title, completed)
21
- def unapply (todo : Todo ) = Option ((todo.title, todo.completed))
22
20
}
23
21
24
22
final case class TodoList (text : String , hash : String , items : BindingSeq [Todo ])
25
23
26
24
object Models {
27
25
val LocalStorageName = " todos-binding.scala"
28
- def load () = LocalStorage ( LocalStorageName ).toSeq.flatMap(read[Seq [Todo ]](_))
29
- def save (todos : collection.Seq [Todo ]) = LocalStorage (LocalStorageName ) = write(todos)
26
+ def load () = Option (window.localStorage.getItem( LocalStorageName ) ).toSeq.flatMap(read[Seq [Todo ]](_))
27
+ def save (todos : collection.Seq [Todo ]) = window.localStorage.setItem (LocalStorageName , write(todos) )
30
28
31
29
val allTodos = Vars [Todo ](load(): _* )
32
30
@@ -39,16 +37,15 @@ import upickle.default._
39
37
val active = TodoList (" Active" , " #/active" , for (todo <- allTodos if ! todo.completed) yield todo)
40
38
val completed = TodoList (" Completed" , " #/completed" , for (todo <- allTodos if todo.completed) yield todo)
41
39
val todoLists = Vector (all, active, completed)
42
- val route = Route .Hash (all)(new Route .Format [TodoList ] {
43
- override def unapply (hashText : String ) = todoLists.find(_.hash == window.location.hash)
44
- override def apply (state : TodoList ): String = state.hash
45
- })
46
- route.watch()
40
+ val route = Binding {
41
+ LatestEvent .hashchange(window).bind
42
+ todoLists.find(_.hash == window.location.hash).getOrElse(all)
43
+ }
47
44
}
48
45
import Models ._
49
46
50
- @ html def header : Binding [Node ] = {
51
- val keyDownHandler = { event : KeyboardEvent =>
47
+ def header : Binding [Node ] = {
48
+ def keyDownHandler ( event : KeyboardEvent ) = {
52
49
(event.currentTarget, event.keyCode) match {
53
50
case (input : HTMLInputElement , KeyCode .Enter ) =>
54
51
input.value.trim match {
@@ -60,17 +57,17 @@ import upickle.default._
60
57
case _ =>
61
58
}
62
59
}
63
- <header class =" header" >
60
+ html """ <header class="header">
64
61
<h1>todos</h1>
65
- <input class =" new-todo" autofocus ={ true } placeholder =" What needs to be done?" onkeydown ={keyDownHandler}/>
66
- </header >
62
+ <input class="new-todo" autofocus placeholder="What needs to be done?" onkeydown= $ {keyDownHandler}/>
63
+ </header> """
67
64
}
68
65
69
- @ html def todoListItem (todo : Todo ): Binding [Node ] = {
66
+ def todoListItem (todo : Todo ): Binding [Node ] = {
70
67
// onblur is not only triggered by user interaction, but also triggered by programmatic DOM changes.
71
68
// In order to suppress this behavior, we have to replace the onblur event listener to a dummy handler before programmatic DOM changes.
72
69
val suppressOnBlur = Var (false )
73
- def submit = { event : Event =>
70
+ def submit ( event : Event ) = {
74
71
suppressOnBlur.value = true
75
72
editingTodo.value = None
76
73
event.currentTarget.asInstanceOf [HTMLInputElement ].value.trim match {
@@ -80,7 +77,7 @@ import upickle.default._
80
77
allTodos.value(allTodos.value.indexOf(todo)) = Todo (trimmedTitle, todo.completed)
81
78
}
82
79
}
83
- def keyDownHandler = { event : KeyboardEvent =>
80
+ def keyDownHandler ( event : KeyboardEvent ) = {
84
81
event.keyCode match {
85
82
case KeyCode .Escape =>
86
83
suppressOnBlur.value = true
@@ -91,66 +88,66 @@ import upickle.default._
91
88
}
92
89
}
93
90
def blurHandler = Binding [Event => Any ] { if (suppressOnBlur.bind) Function .const(()) else submit }
94
- def toggleHandler = { event : Event =>
91
+ def toggleHandler ( event : Event ) = {
95
92
allTodos.value(allTodos.value.indexOf(todo)) = Todo (todo.title, event.currentTarget.asInstanceOf [HTMLInputElement ].checked)
96
93
}
97
- val editInput = <input id = " editInput " class =" edit" value ={ todo.title } onblur ={ blurHandler.bind } onkeydown ={ keyDownHandler } />;
98
- <li class ={s " ${if (todo.completed) " completed" else " " } ${if (editingTodo.bind.contains(todo)) " editing" else " " }" }>
94
+ val editInput = html """ <input class="edit" value= $ { todo.title } onblur= $ { blurHandler.bind } onkeydown= $ { keyDownHandler } /> """
95
+ html """ <li class= $ {s " ${if (todo.completed) " completed" else " " } ${if (editingTodo.bind.contains(todo)) " editing" else " " }" }>
99
96
<div class="view">
100
- <input class =" toggle" type =" checkbox" checked ={todo.completed} onclick ={toggleHandler}/>
101
- <label ondblclick ={ _ : Event => editingTodo.value = Some (todo); editInput.value.focus() }>{ todo.title }</label >
102
- <button class =" destroy" onclick ={ _ : Event => allTodos.value.remove(allTodos.value.indexOf(todo)) }></button >
97
+ <input class="toggle" type="checkbox" checked= $ {todo.completed} onclick= $ {toggleHandler}/>
98
+ <label ondblclick= ${ ( _ : Event ) => editingTodo.value = Some (todo); editInput.value.focus() }> $ { todo.title }</label>
99
+ <button class="destroy" onclick= ${ ( _ : Event ) => allTodos.value.remove(allTodos.value.indexOf(todo)) }></button>
103
100
</div>
104
- {editInput}
105
- </li >
101
+ $ {editInput}
102
+ </li> """
106
103
}
107
104
108
- @ html def mainSection : Binding [Node ] = {
109
- def toggleAllClickHandler = { event : Event =>
105
+ def mainSection : Binding [Node ] = {
106
+ def toggleAllClickHandler ( event : Event ) = {
110
107
for ((todo, i) <- allTodos.value.zipWithIndex) {
111
108
if (todo.completed != event.currentTarget.asInstanceOf [HTMLInputElement ].checked) {
112
109
allTodos.value(i) = Todo (todo.title, event.currentTarget.asInstanceOf [HTMLInputElement ].checked)
113
110
}
114
111
}
115
112
}
116
- <section class =" main" style ={ if (allTodos.length.bind == 0 ) " display:none" else " " }>
117
- <input type =" checkbox" id =" toggle-all" class =" toggle-all" checked ={active.items.length.bind == 0 } onclick ={toggleAllClickHandler}/>
113
+ html """ <section class="main" style= $ { if (allTodos.length.bind == 0 ) " display:none" else " " }>
114
+ <input type="checkbox" id="toggle-all" class="toggle-all" checked= $ {active.items.length.bind == 0 } onclick= $ {toggleAllClickHandler}/>
118
115
<label for="toggle-all">Mark all as complete</label>
119
- <ul class =" todo-list" >{ for (todo <- route.state .bind.items) yield todoListItem(todo).bind }</ul >
120
- </section >
116
+ <ul class="todo-list"> $ { for (todo <- route.bind.items) yield todoListItem(todo).bind }</ul>
117
+ </section> """
121
118
}
122
119
123
- @ html def footer : Binding [Node ] = {
124
- def clearCompletedClickHandler = { _ : Event =>
120
+ def footer : Binding [Node ] = {
121
+ def clearCompletedClickHandler ( event : MouseEvent ) = {
125
122
allTodos.value --= (for (todo <- allTodos.value if todo.completed) yield todo)
126
123
}
127
- <footer class =" footer" style ={ if (allTodos.length.bind == 0 ) " display:none" else " " }>
124
+ html """ <footer class="footer" style= $ { if (allTodos.length.bind == 0 ) " display:none" else " " }>
128
125
<span class="todo-count">
129
- <strong >{ active.items.length.bind.toString }</strong > { if (active.items.length.bind == 1 ) " item" else " items" } left
126
+ <strong> $ { active.items.length.bind.toString }</strong> $ { if (active.items.length.bind == 1 ) " item" else " items" } left
130
127
</span>
131
- <ul class =" filters" >{
128
+ <ul class="filters"> $ {
132
129
for (todoList <- todoLists) yield {
133
- <li >
134
- <a href ={ todoList.hash } class ={ if (todoList == route.state. bind) " selected" else " " }>{ todoList.text }</a >
135
- </li >
130
+ html """ <li>
131
+ <a href= $ { todoList.hash } class= $ { if (todoList eq route.bind) " selected" else " " }> $ { todoList.text }</a>
132
+ </li> """
136
133
}
137
134
}</ul>
138
- < button class = " clear-completed" onclick= { clearCompletedClickHandler}
139
- style= {if (completed.items.length.bind == 0 ) " visibility:hidden" else " visibility:visible" }>
135
+ <button class="clear-completed" onclick= $ clearCompletedClickHandler
136
+ style= $ {if (completed.items.length.bind == 0 ) " visibility:hidden" else " visibility:visible" }>
140
137
Clear completed
141
138
</button>
142
- </footer >
139
+ </footer> """
143
140
}
144
141
145
- @ html def todoapp : BindingSeq [Node ] = {
146
- <section class =" todoapp" >{ header.bind }{ mainSection.bind }{ footer.bind }</section >
142
+ def todoapp : BindingSeq [Node ] = html """
143
+ <section class="todoapp"> ${ header.bind}${ mainSection.bind}${ footer.bind}</section>
147
144
<footer class="info">
148
145
<p>Double-click to edit a todo</p>
149
146
<p>Written by <a href="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/atry">Yang Bo</a></p>
150
147
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
151
148
</footer>
152
- }
149
+ """
153
150
154
- @ JSExport def main (container : Node ) = html. render(container, todoapp)
151
+ @ JSExport def main (container : Element ) = render(container, todoapp)
155
152
156
153
}
0 commit comments