diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-08-08 19:45:05 +0800 |
---|---|---|
committer | Li Haoyi <haoyi.sg@gmail.com> | 2018-08-08 21:54:41 +0800 |
commit | 80b83687269ec8bd18fe156ff985bddcd1d5089a (patch) | |
tree | c923f61210661a124c3c230f2a2f4fd402d0a0b6 /example/todo | |
parent | d85fd093539bdd7d8d432b058c2e2225eaa1ee2b (diff) | |
download | cask-80b83687269ec8bd18fe156ff985bddcd1d5089a.tar.gz cask-80b83687269ec8bd18fe156ff985bddcd1d5089a.tar.bz2 cask-80b83687269ec8bd18fe156ff985bddcd1d5089a.zip |
Full TodoMVC example basically works
Diffstat (limited to 'example/todo')
-rw-r--r-- | example/todo/resources/todo/app.js | 99 | ||||
-rw-r--r-- | example/todo/resources/todo/base.css | 141 | ||||
-rw-r--r-- | example/todo/src/todo/Server.scala | 192 |
3 files changed, 201 insertions, 231 deletions
diff --git a/example/todo/resources/todo/app.js b/example/todo/resources/todo/app.js index df8dc7a..b7b8437 100644 --- a/example/todo/resources/todo/app.js +++ b/example/todo/resources/todo/app.js @@ -1,35 +1,70 @@ var state = "all"; -var newTodoInput = document.getElementsByClassName("new-todo")[0]; -var todoList = document.getElementsByClassName("todo-list")[0]; -newTodoInput.addEventListener( - "keydown", - function(evt){ - if (evt.keyCode === 13) { - fetch("/add/" + state, { - method: "POST", - body: newTodoInput.value - }) - .then(function(response){ return response.text()}) - .then(function (text) { - newTodoInput.value = ""; - todoList.innerHTML = text; - }) + +var todoApp = document.getElementsByClassName("todoapp")[0]; +function postFetchUpdate(url){ + fetch(url, { + method: "POST", + }) + .then(function(response){ return response.text()}) + .then(function (text) { + todoApp.innerHTML = text; + initListeners() + }) +} + +function bindEvent(cls, url, endState){ + + document.getElementsByClassName(cls)[0].addEventListener( + "mousedown", + function(evt){ + postFetchUpdate(url) + if (endState) state = endState } - } -); -newTodoInput.addEventListener( - "mousedown", - function(evt){ - if (evt.keyCode === 13) { - fetch("/add/" + state, { - method: "POST", - body: newTodoInput.value - }) - .then(function(response){ return response.text()}) - .then(function (text) { - newTodoInput.value = ""; - todoList.innerHTML = text; - }) + ); +} + +function bindIndexedEvent(cls, func){ + Array.from(document.getElementsByClassName(cls)).forEach( function(elem) { + elem.addEventListener( + "mousedown", + function(evt){ + postFetchUpdate(func(elem.getAttribute("data-todo-index"))) + } + ) + }); +} + +function initListeners(){ + bindIndexedEvent( + "destroy", + function(index){return "/delete/" + state + "/" + index} + ); + bindIndexedEvent( + "toggle", + function(index){return "/toggle/" + state + "/" + index} + ); + bindEvent("toggle-all", "/toggle-all/" + state); + bindEvent("todo-all", "/list/all", "all"); + bindEvent("todo-active", "/list/active", "active"); + bindEvent("todo-completed", "/list/completed", "completed"); + bindEvent("clear-completed", "/clear-completed/" + state); + var newTodoInput = document.getElementsByClassName("new-todo")[0]; + newTodoInput.addEventListener( + "keydown", + function(evt){ + if (evt.keyCode === 13) { + fetch("/add/" + state, { + method: "POST", + body: newTodoInput.value + }) + .then(function(response){ return response.text()}) + .then(function (text) { + newTodoInput.value = ""; + todoApp.innerHTML = text; + initListeners() + }) + } } - } -);
\ No newline at end of file + ); +} +initListeners()
\ No newline at end of file diff --git a/example/todo/resources/todo/base.css b/example/todo/resources/todo/base.css deleted file mode 100644 index deec144..0000000 --- a/example/todo/resources/todo/base.css +++ /dev/null @@ -1,141 +0,0 @@ -hr { - margin: 20px 0; - border: 0; - border-top: 1px dashed #c5c5c5; - border-bottom: 1px dashed #f7f7f7; -} - -.learn a { - font-weight: normal; - text-decoration: none; - color: #b83f45; -} - -.learn a:hover { - text-decoration: underline; - color: #787e7e; -} - -.learn h3, -.learn h4, -.learn h5 { - margin: 10px 0; - font-weight: 500; - line-height: 1.2; - color: #000; -} - -.learn h3 { - font-size: 24px; -} - -.learn h4 { - font-size: 18px; -} - -.learn h5 { - margin-bottom: 0; - font-size: 14px; -} - -.learn ul { - padding: 0; - margin: 0 0 30px 25px; -} - -.learn li { - line-height: 20px; -} - -.learn p { - font-size: 15px; - font-weight: 300; - line-height: 1.3; - margin-top: 0; - margin-bottom: 0; -} - -#issue-count { - display: none; -} - -.quote { - border: none; - margin: 20px 0 60px 0; -} - -.quote p { - font-style: italic; -} - -.quote p:before { - content: '“'; - font-size: 50px; - opacity: .15; - position: absolute; - top: -20px; - left: 3px; -} - -.quote p:after { - content: '”'; - font-size: 50px; - opacity: .15; - position: absolute; - bottom: -42px; - right: 3px; -} - -.quote footer { - position: absolute; - bottom: -40px; - right: 0; -} - -.quote footer img { - border-radius: 3px; -} - -.quote footer a { - margin-left: 5px; - vertical-align: middle; -} - -.speech-bubble { - position: relative; - padding: 10px; - background: rgba(0, 0, 0, .04); - border-radius: 5px; -} - -.speech-bubble:after { - content: ''; - position: absolute; - top: 100%; - right: 30px; - border: 13px solid transparent; - border-top-color: rgba(0, 0, 0, .04); -} - -.learn-bar > .learn { - position: absolute; - width: 272px; - top: 8px; - left: -300px; - padding: 10px; - border-radius: 5px; - background-color: rgba(255, 255, 255, .6); - transition-property: left; - transition-duration: 500ms; -} - -@media (min-width: 899px) { - .learn-bar { - width: auto; - padding-left: 300px; - } - - .learn-bar > .learn { - left: 8px; - } -}
\ No newline at end of file diff --git a/example/todo/src/todo/Server.scala b/example/todo/src/todo/Server.scala index 7dd64ae..5b4b1f8 100644 --- a/example/todo/src/todo/Server.scala +++ b/example/todo/src/todo/Server.scala @@ -1,41 +1,152 @@ package todo +import cask.internal.Router +import com.typesafe.config.ConfigFactory +import io.getquill.{SnakeCase, SqliteJdbcContext} import scalatags.Text.all._ import scalatags.Text.tags2 -import scalatags.Text.tags2.section -case class Todo(checked: Boolean, text: String) object Server extends cask.MainRoutes{ - var todos = Seq( - Todo(true, "Get started with Cask"), - Todo(false, "Profit!") + val tmpDb = java.nio.file.Files.createTempDirectory("todo-cask-sqlite") + + object ctx extends SqliteJdbcContext( + SnakeCase, + ConfigFactory.parseString( + s"""{"driverClassName":"org.sqlite.JDBC","jdbcUrl":"jdbc:sqlite:$tmpDb/file.db"}""" + ) + ) + + class transactional extends cask.Decorator{ + class TransactionFailed(val value: Router.Result.Error) extends Exception + def wrapFunction(pctx: cask.ParamContext, delegate: Delegate): Returned = { + try ctx.transaction( + delegate(Map()) match{ + case Router.Result.Success(t) => Router.Result.Success(t) + case e: Router.Result.Error => throw new TransactionFailed(e) + } + ) + catch{case e: TransactionFailed => e.value} + + } + } + + case class Todo(id: Int, checked: Boolean, text: String) + + ctx.executeAction( + """CREATE TABLE todo ( + | id INTEGER PRIMARY KEY AUTOINCREMENT, + | checked BOOLEAN, + | text TEXT + |); + |""".stripMargin + ) + ctx.executeAction( + """INSERT INTO todo (checked, text) VALUES + |(1, 'Get started with Cask'), + |(0, 'Profit!'); + |""".stripMargin ) - @cask.get("/list/:state") - def list(state: String) = list0(state).render + import ctx._ + + @transactional + @cask.post("/list/:state") + def list(state: String) = renderBody(state).render + @transactional @cask.post("/add/:state") def add(state: String, request: cask.Request) = { - todos = Seq(Todo(false, new String(request.data.readAllBytes()))) ++ todos - list0(state).render + val body = new String(request.data.readAllBytes()) + run(query[Todo].insert(_.checked -> lift(false), _.text -> lift(body)).returning(_.id)) + renderBody(state).render + } + + @transactional + @cask.post("/delete/:state/:index") + def delete(state: String, index: Int) = { + run(query[Todo].filter(_.id == lift(index)).delete) + renderBody(state).render + } + + @transactional + @cask.post("/toggle/:state/:index") + def toggle(state: String, index: Int) = { + run(query[Todo].filter(_.id == lift(index)).update(p => p.checked -> !p.checked)) + renderBody(state).render + } + + @transactional + @cask.post("/clear-completed/:state") + def clearCompleted(state: String) = { + run(query[Todo].filter(_.checked).delete) + renderBody(state).render } - def list0(state: String) = { + @transactional + @cask.post("/toggle-all/:state") + def toggleAll(state: String) = { + + val next = run(query[Todo].filter(_.checked).size) != 0 + run(query[Todo].update(_.checked -> !lift(next))) + renderBody(state).render + } + + def renderBody(state: String) = { val filteredTodos = state match{ - case "all" => todos - case "active" => todos.filter(!_.checked) - case "completed" => todos.filter(_.checked) + case "all" => run(query[Todo]).sortBy(-_.id) + case "active" => run(query[Todo].filter(!_.checked)).sortBy(-_.id) + case "completed" => run(query[Todo].filter(_.checked)).sortBy(-_.id) } frag( - for((todo, i) <- filteredTodos.zipWithIndex) yield li(if (todo.checked) cls := "completed" else (), - div(cls := "view", - input(cls := "toggle", `type` := "checkbox", if (todo.checked) checked else ()), - label(todo.text), - button(cls := "destroy", data("todo-index") := i) + header(cls := "header", + h1("todos"), + input(cls := "new-todo", placeholder := "What needs to be done?", autofocus := "") + ), + tags2.section(cls := "main", + input( + id := "toggle-all", + cls := "toggle-all", + `type` := "checkbox", + if (run(query[Todo].filter(_.checked).size != 0)) checked else () ), - input(cls := "edit", value := todo.text) + label(`for` := "toggle-all","Mark all as complete"), + ul(cls := "todo-list", + for(todo <- filteredTodos) yield li( + if (todo.checked) cls := "completed" else (), + div(cls := "view", + input( + cls := "toggle", + `type` := "checkbox", + if (todo.checked) checked else (), + data("todo-index") := todo.id + ), + label(todo.text), + button(cls := "destroy", data("todo-index") := todo.id) + ), + input(cls := "edit", value := todo.text) + ) + ) + ), + footer(cls := "footer", + span(cls := "todo-count", + strong(run(query[Todo].filter(!_.checked).size).toInt), + " items left" + ), + ul(cls := "filters", + li(cls := "todo-all", + a(if (state == "all") cls := "selected" else (), "All") + ), + li(cls := "todo-active", + a(if (state == "active") cls := "selected" else (), "Active") + ), + li(cls := "todo-completed", + a(if (state == "completed") cls := "selected" else (), "Completed") + ) + ), + button(cls := "clear-completed","Clear completed") ) ) } + @transactional @cask.get("/") def index() = { cask.Response( @@ -44,54 +155,19 @@ object Server extends cask.MainRoutes{ meta(charset := "utf-8"), meta(name := "viewport", content := "width=device-width, initial-scale=1"), tags2.title("Template • TodoMVC"), - link(rel := "stylesheet", href := "/static/base.css"), link(rel := "stylesheet", href := "/static/index.css") ), body( - section(cls := "todoapp", - header(cls := "header", - h1("todos"), - input(cls := "new-todo", placeholder := "What needs to be done?", autofocus := "") - ), - section(cls := "main", - input(id := "toggle-all", cls := "toggle-all", `type` := "checkbox"), - label(`for` := "toggle-all","Mark all as complete"), - ul(cls := "todo-list", - list0("all") - ) - ), - footer(cls := "footer", - span(cls := "todo-count", - strong("0"), - "item left" - ), - ul(cls := "filters", - li( - a(cls := "selected", href := "#/","All") - ), - li( - a(href := "#/active","Active") - ), - li( - a(href := "#/completed","Completed") - ) - ), - button(cls := "clear-completed","Clear completed") - ) - ), + tags2.section(cls := "todoapp", renderBody("all")), footer(cls := "info", p("Double-click to edit a todo"), - p("Template by", - a(href := "http://sindresorhus.com","Sindre Sorhus") - ), - p("Created by", - a(href := "http://todomvc.com","you") + p("Created by ", + a(href := "http://todomvc.com","Li Haoyi") ), - p("Part of", + p("Part of ", a(href := "http://todomvc.com","TodoMVC") ) ), - script(src := "node_modules/todomvc-common/base.js"), script(src := "/static/app.js") ) ) |