summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-08 19:45:05 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-08 21:54:41 +0800
commit80b83687269ec8bd18fe156ff985bddcd1d5089a (patch)
treec923f61210661a124c3c230f2a2f4fd402d0a0b6
parentd85fd093539bdd7d8d432b058c2e2225eaa1ee2b (diff)
downloadcask-80b83687269ec8bd18fe156ff985bddcd1d5089a.tar.gz
cask-80b83687269ec8bd18fe156ff985bddcd1d5089a.tar.bz2
cask-80b83687269ec8bd18fe156ff985bddcd1d5089a.zip
Full TodoMVC example basically works
-rw-r--r--build.sc4
-rw-r--r--example/todo/resources/todo/app.js99
-rw-r--r--example/todo/resources/todo/base.css141
-rw-r--r--example/todo/src/todo/Server.scala192
4 files changed, 205 insertions, 231 deletions
diff --git a/build.sc b/build.sc
index 6fe21d6..623ae4a 100644
--- a/build.sc
+++ b/build.sc
@@ -30,5 +30,9 @@ object example extends Module{
object todo extends ScalaModule{
def scalaVersion = "2.12.6"
def moduleDeps = Seq(cask)
+ def ivyDeps = Agg(
+ ivy"org.xerial:sqlite-jdbc:3.18.0",
+ ivy"io.getquill::quill-jdbc:2.5.4"
+ )
}
} \ No newline at end of file
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")
)
)