app.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // +build js
  2. package views
  3. import (
  4. "fmt"
  5. "net/rpc"
  6. "github.com/cryptix/exp/humbleRpcTodo/types"
  7. "github.com/soroushjp/humble"
  8. "github.com/soroushjp/humble/view"
  9. "honnef.co/go/js/dom"
  10. )
  11. type App struct {
  12. humble.Identifier
  13. Children []*Todo
  14. Footer *Footer
  15. CurrentFilter TodoFilter
  16. Client *rpc.Client
  17. }
  18. const (
  19. EnterKey = 13
  20. EscapeKey = 27
  21. todoListSelector = "#todo-list"
  22. newTodoSelector = "input#new-todo"
  23. toggleBtnSelector = "input#toggle-all"
  24. )
  25. var (
  26. doc = dom.GetWindow().Document()
  27. elements = struct {
  28. body dom.Element
  29. todoList dom.Element
  30. newTodo dom.Element
  31. toggleBtn dom.Element
  32. }{}
  33. )
  34. type TodoFilter int
  35. const (
  36. FilterAll TodoFilter = iota
  37. FilterActive
  38. FilterCompleted
  39. )
  40. func (a *App) RenderHTML() string {
  41. return fmt.Sprintf(`
  42. <section id="todoapp">
  43. <header id="header">
  44. <h1>todos</h1>
  45. <input id="new-todo" placeholder="What needs to be done?" autofocus>
  46. </header>
  47. <section id="main">
  48. <input id="toggle-all" type="checkbox">
  49. <label for="toggle-all">Mark all as complete</label>
  50. <ul id="todo-list"></ul>
  51. </section>
  52. <footer id="footer">
  53. </footer>
  54. </section>
  55. <footer id="info">
  56. <p>Double-click to edit a todo</p>
  57. <p>Part of <a href="http://todomvc.com">TodoMVC</a>
  58. </p>
  59. </footer>`)
  60. }
  61. func (v *App) OuterTag() string {
  62. return "div"
  63. }
  64. func (v *App) InitChildren() {
  65. // Get existing todos
  66. args := &types.TodoListArgs{"All"}
  67. var todos []types.Todo
  68. if err := v.Client.Call("TodoService.List", args, &todos); err != nil {
  69. panic(err)
  70. }
  71. //Create individual todo views
  72. v.Children = []*Todo{}
  73. if v.Footer != nil {
  74. v.Footer.TodoViews = &v.Children
  75. }
  76. for _, todo := range todos {
  77. todoView := &Todo{
  78. Model: &todo,
  79. Parent: v,
  80. }
  81. v.addChild(todoView)
  82. }
  83. }
  84. func (v *App) OnLoad() error {
  85. var err error
  86. elements.todoList, err = view.QuerySelector(v, todoListSelector)
  87. if err != nil {
  88. return err
  89. }
  90. elements.newTodo, err = view.QuerySelector(v, newTodoSelector)
  91. if err != nil {
  92. return err
  93. }
  94. elements.toggleBtn, err = view.QuerySelector(v, toggleBtnSelector)
  95. if err != nil {
  96. return err
  97. }
  98. // Add each child view to the DOM
  99. for _, childView := range v.Children {
  100. view.AppendToParentHTML(childView, todoListSelector)
  101. }
  102. // Set up the footer view
  103. v.Footer = &Footer{}
  104. v.Footer.TodoViews = &v.Children
  105. view.ReplaceParentHTML(v.Footer, "#footer")
  106. if len(v.Children) > 0 {
  107. showTodosContainer()
  108. showTodosFooter()
  109. }
  110. if err := view.AddListener(v, newTodoSelector, "keyup", v.newTodoKeyUp); err != nil {
  111. return err
  112. }
  113. if err := view.AddListener(v, toggleBtnSelector, "click", v.toggleBtnClicked); err != nil {
  114. return err
  115. }
  116. return nil
  117. }
  118. func (v *App) ApplyFilter(filter TodoFilter) {
  119. v.CurrentFilter = filter
  120. for _, todoView := range v.Children {
  121. if todoView.GetId() == "" {
  122. continue
  123. }
  124. switch filter {
  125. case FilterAll:
  126. // For FilterAll we want to show all todos, regardless of whether they are complete
  127. if err := view.Show(todoView); err != nil {
  128. panic(err)
  129. }
  130. case FilterActive:
  131. // For the FilterActive, we want to hide views that are completed
  132. switch todoView.Model.IsCompleted {
  133. case true:
  134. if err := view.Hide(todoView); err != nil {
  135. panic(err)
  136. }
  137. case false:
  138. if err := view.Show(todoView); err != nil {
  139. panic(err)
  140. }
  141. }
  142. case FilterCompleted:
  143. // For the FilterCompleted, we want to hide views that are completed
  144. switch todoView.Model.IsCompleted {
  145. case true:
  146. if err := view.Show(todoView); err != nil {
  147. panic(err)
  148. }
  149. case false:
  150. if err := view.Hide(todoView); err != nil {
  151. panic(err)
  152. }
  153. }
  154. }
  155. }
  156. }
  157. func (v *App) removeChild(todoView *Todo) {
  158. for i, child := range v.Children {
  159. if child.Id == todoView.Id {
  160. v.Children = append(v.Children[:i], v.Children[i+1:]...)
  161. }
  162. }
  163. // Update the footer text
  164. if err := view.Update(v.Footer); err != nil {
  165. panic(err)
  166. }
  167. }
  168. func (v *App) addChild(todoView *Todo) {
  169. v.Children = append(v.Children, todoView)
  170. }
  171. // addTodoListener responds to DOM element input#new-todo being submitted by user to add a new todo to list and model
  172. func (v *App) newTodoKeyUp(event dom.Event) {
  173. //If not Enter key, ignore event
  174. if event.(*dom.KeyboardEvent).KeyCode != EnterKey {
  175. return
  176. }
  177. //If newTodo input is empty, ignore event
  178. title := elements.newTodo.Underlying().Get("value").String()
  179. if title == "" {
  180. return
  181. }
  182. //This ensures the todo list container is visible. Does nothing if already visible, but costs no more than a check.
  183. showTodosContainer()
  184. showTodosFooter()
  185. //Create a model, send to server and append view
  186. var t types.Todo
  187. t.Title = title
  188. if err := v.Client.Call("TodoService.Save", t, &t); err != nil {
  189. panic(err)
  190. }
  191. todoView := &Todo{
  192. Model: &t,
  193. Parent: v,
  194. }
  195. if err := view.AppendToParentHTML(todoView, todoListSelector); err != nil {
  196. panic(err)
  197. }
  198. v.addChild(todoView)
  199. //Clear newTodo text input
  200. elements.newTodo.Underlying().Set("value", "")
  201. // Update the footer text
  202. if err := view.Update(v.Footer); err != nil {
  203. panic(err)
  204. }
  205. }
  206. // toggleBtnListener responds to DOM element input#toggle-all being clicked to toggle all todo
  207. // items between the completed and active states.
  208. func (v *App) toggleBtnClicked(event dom.Event) {
  209. isChecked := event.Target().(*dom.HTMLInputElement).Checked
  210. for _, todo := range v.Children {
  211. todo.setComplete(isChecked)
  212. }
  213. if v.CurrentFilter == FilterActive {
  214. switch isChecked {
  215. case true:
  216. // If we are only showing active todos and we just completed all of them,
  217. // hide all the views
  218. for _, todoView := range v.Children {
  219. if err := view.Hide(todoView); err != nil {
  220. panic(err)
  221. }
  222. }
  223. case false:
  224. // If we are only showing active todos and we just uncompleted all of them,
  225. // show all the views
  226. for _, todoView := range v.Children {
  227. if err := view.Show(todoView); err != nil {
  228. panic(err)
  229. }
  230. }
  231. }
  232. } else if v.CurrentFilter == FilterCompleted {
  233. switch isChecked {
  234. case true:
  235. // If we are only showing completed todos and we just completed all of them,
  236. // show all the views
  237. for _, todoView := range v.Children {
  238. if err := view.Show(todoView); err != nil {
  239. panic(err)
  240. }
  241. }
  242. case false:
  243. // If we are only showing completed todos and we just uncompleted all of them,
  244. // hide all the views
  245. for _, todoView := range v.Children {
  246. if err := view.Hide(todoView); err != nil {
  247. panic(err)
  248. }
  249. }
  250. }
  251. }
  252. // Update the footer text
  253. if err := view.Update(v.Footer); err != nil {
  254. panic(err)
  255. }
  256. }
  257. // showTodosContainer sets the outer container of todos to visible when our first todo is added
  258. func showTodosContainer() {
  259. doc.QuerySelector("#main").SetAttribute("style", "display: block;")
  260. }
  261. // showTodosContainer sets the outer todos footer (which contains links and the number of items left)
  262. // to visible when our first todo is added
  263. func showTodosFooter() {
  264. doc.QuerySelector("#footer").SetAttribute("style", "display: block;")
  265. }