summaryrefslogtreecommitdiff
path: root/readme.md
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-08 22:21:23 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-08 22:21:23 +0800
commitb1969928a179bfa833cab528d544a1f77cf24987 (patch)
tree72db5f6056d61c5e3047cd26b3dd470641b2f63d /readme.md
parent80b83687269ec8bd18fe156ff985bddcd1d5089a (diff)
downloadcask-b1969928a179bfa833cab528d544a1f77cf24987.tar.gz
cask-b1969928a179bfa833cab528d544a1f77cf24987.tar.bz2
cask-b1969928a179bfa833cab528d544a1f77cf24987.zip
add full stack todomvc example to readme
Diffstat (limited to 'readme.md')
-rw-r--r--readme.md199
1 files changed, 198 insertions, 1 deletions
diff --git a/readme.md b/readme.md
index f20e4c4..4ed7322 100644
--- a/readme.md
+++ b/readme.md
@@ -540,4 +540,201 @@ While this example is specific to Quill, you can easily modify the
you happen to be using. For libraries which need an implicit transaction, it can
be passed into each endpoint function as an additional parameter list as
described in
-[Extending Endpoints with Decorators](#extending-endpoints-with-decorators). \ No newline at end of file
+[Extending Endpoints with Decorators](#extending-endpoints-with-decorators).
+
+TodoMVC Full Stack Web
+----------------------
+
+The following code snippet is the complete code for a full-stack TodoMVC
+implementation: including HTML generation for the web UI via
+[Scalatags](https://github.com/lihaoyi/scalatags), Javascript for the
+interactivity, static file serving, and database integration via
+[Quill](https://github.com/getquill/quill). While slightly long, this example
+should give you a tour of all the things you need to know to use Cask.
+
+Note that this is a "boring" server-side-rendered webapp with Ajax interactions,
+without any complex front-end frameworks or libraries: it's purpose is to
+demonstrate a simple working web application of using Cask end-to-end, which you
+can build upon to create your own Cask web application architected however you
+would like.
+
+```scala
+import cask.internal.Router
+import com.typesafe.config.ConfigFactory
+import io.getquill.{SnakeCase, SqliteJdbcContext}
+import scalatags.Text.all._
+import scalatags.Text.tags2
+object Server extends cask.MainRoutes{
+ 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
+ )
+
+ 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) = {
+ 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
+ }
+
+ @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" => run(query[Todo]).sortBy(-_.id)
+ case "active" => run(query[Todo].filter(!_.checked)).sortBy(-_.id)
+ case "completed" => run(query[Todo].filter(_.checked)).sortBy(-_.id)
+ }
+ frag(
+ 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 ()
+ ),
+ 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(
+ "<!doctype html>" + html(lang := "en",
+ head(
+ meta(charset := "utf-8"),
+ meta(name := "viewport", content := "width=device-width, initial-scale=1"),
+ tags2.title("Template • TodoMVC"),
+ link(rel := "stylesheet", href := "/static/index.css")
+ ),
+ body(
+ tags2.section(cls := "todoapp", renderBody("all")),
+ footer(cls := "info",
+ p("Double-click to edit a todo"),
+ p("Created by ",
+ a(href := "http://todomvc.com","Li Haoyi")
+ ),
+ p("Part of ",
+ a(href := "http://todomvc.com","TodoMVC")
+ )
+ ),
+ script(src := "/static/app.js")
+ )
+ )
+ )
+ }
+
+ @cask.static("/static")
+ def static() = "example/todo/resources/todo"
+
+ initialize()
+}
+```