Commit 9e22e76c authored by 杨博 (Yang Bo)'s avatar 杨博 (Yang Bo) Committed by Sam Saccone

Request to add Binding.scala (#1615)

* Squashed 'examples/binding-scala/' content from commit 4f546d0

git-subtree-dir: examples/binding-scala
git-subtree-split: 4f546d0745a5f4b3a72e95ddc1d7f59773f12b28

* Squashed 'examples/binding-scala/' changes from 4f546d0..45178a9

45178a9 Publish to GitHub Pages
d9a0c1a Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
d2ac731 Publish to GitHub Pages
cb59711 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
0324b27 Avoid tab indention
8f4cb6f Update indention
7284ab6 Publish to GitHub Pages
e092b56 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
96c270e Update readme.md

git-subtree-dir: examples/binding-scala
git-subtree-split: 45178a9b9dd50d72eb1f04a4a56e10ca78d3be14

* Ignore jslint for Binding.scala

* Squashed 'examples/binding-scala/' changes from 45178a9..5af88e8

5af88e8 Publish to GitHub Pages
22fd76b Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
15c6fae Update readme.md
fd2c78b Publish to GitHub Pages
62730da Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
c99bf54 Update readme.md
4c8d780 Publish to GitHub Pages
d905883 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
7e3898e Update readme.md
443e2c4 Publish to GitHub Pages
efb05b5 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
56fc875 Update readme.md

git-subtree-dir: examples/binding-scala
git-subtree-split: 5af88e8003ab124c1c0037594454b73bcd2de9e0

* Squashed 'examples/binding-scala/' changes from 5af88e8..5191681

5191681 Publish to GitHub Pages
7ac7f44 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
e8fa8f7 Map source to github
efbf6ec Publish to GitHub Pages
335bcd9 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
95db23e Update build.sbt

git-subtree-dir: examples/binding-scala
git-subtree-split: 51916810746d9928c36a85a3cb6d33a7e313066f

* Squashed 'examples/binding-scala/' changes from 5191681..3385b9d

3385b9d Publish to GitHub Pages
27ad704 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
0a17bac Use upickle for serialization

git-subtree-dir: examples/binding-scala
git-subtree-split: 3385b9da95abb2bee3ddd1536c6d4ded38c9db46

* Squashed 'examples/binding-scala/' changes from 3385b9d..1e71eb4

1e71eb4 Publish to GitHub Pages
d2d2692 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
d22821a Replace Any to Event
f1a73ab Publish to GitHub Pages
0e8d094 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
f4077b5 Seperate event handlers and HTML elements
5c10f67 Publish to GitHub Pages
4a164d0 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
6661116 Seperate event handlers and HTML elements
8cd4a2f Publish to GitHub Pages
b54987f Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
0c51d90 Adjust indention
181600f Adjust indention
4ae6c9b Remove unused imports
8b6b49d Publish to GitHub Pages
6121028 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
1e6e913 Avoid DOM property `htmlFor` in favor of HTML attribute `for`
e130601 Avoid deprecated `each` method, in favor of `bind`
3bd0e84 Publish to GitHub Pages
7583e90 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
cee4dc1 Avoid MountPoint
4978768 Add explicit type signature
7928e68 Publish to GitHub Pages
2d48f1c Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
d593f00 Avoid DRY string
b43c268 Add a comment for Todo class
4adc337 Publish to GitHub Pages
6126301 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
a9e31ae Update readme.md
bdb5573 Publish to GitHub Pages
abd1575 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
4f0f26f Fix compilation error
1343e1a Upgrade Binding.scala to 7.0.0
e564d01 Publish to GitHub Pages
1e7b918 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
17cea8d Use class attributes instead of className
f4a630f Upgrade Binding.scala version
a1b4fff Publish to GitHub Pages
42301b5 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
51742f0 More clear indention
575fa93 Publish to GitHub Pages
4d1e2b6 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
5773f76 Update Main.scala

git-subtree-dir: examples/binding-scala
git-subtree-split: 1e71eb427fe2365b8f014c8bbc8f8978a5efd5c1

* Squashed 'examples/binding-scala/' changes from 1e71eb4..7490122

7490122 Publish to GitHub Pages
6f88ebc Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
0dd27a0 Update readme.md
ff46962 Update readme.md

git-subtree-dir: examples/binding-scala
git-subtree-split: 7490122cd7425b84624e76769d6b5e37429720c5

* Squashed 'examples/binding-scala/' changes from 7490122..ea1b6bb

ea1b6bb Publish to GitHub Pages
21bb704 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
0acc806 Upgrade Scala.js version
eb5c97c Publish to GitHub Pages
57d81eb Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
6886177 Upgrade Binding.scala
6f67bb1 Publish to GitHub Pages
6f1b13c Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
bf0b4d5 Upgrade dependencies
5099d22 Avoid widecard imports so that a reader is able to find out where each class is imported from
79a69a9 Publish to GitHub Pages
cf4d002 Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
79edc64 Add explicit type signature

git-subtree-dir: examples/binding-scala
git-subtree-split: ea1b6bb3ee8b6b0f3fad39cf7d257c7106336cb8

* Squashed 'examples/binding-scala/' changes from ea1b6bb..109f8a7

109f8a7 Publish to GitHub Pages
96bbf7a Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
64fe55c Update line numbers
21bf71a Inline filterListItem
d6b91f8 Adjust spaces
e21d55b Publish to GitHub Pages
e8e8e6f Merge branch 'gh-pages' of github.com:ThoughtWorksInc/todo into gh-pages
e277e4e Update line numbers
5300789 Remove new
0905dbd Extract onclick event in mainSection
55d8f55 Extract onclick event in footer
e61bc2b Extract toggleHandler
68dbdc4 Move done ignoreEvent
27ddd65 Extract standalone blurHandler
92663ea Publish to GitHub Pages
9ac9410 Merge branch 'master' of github.com:ThoughtWorksInc/todo into gh-pages
6d806dd Cache .npm directory

git-subtree-dir: examples/binding-scala
git-subtree-split: 109f8a72f9e9c29407310449819916921fb1c3db

* Add Binding.scala in home page
parent 604a76c5
......@@ -32,6 +32,7 @@
"examples/aurelia/config.js",
"examples/aurelia/dist/*.js",
"examples/aurelia/jspm_packages/**/*.js",
"examples/binding-scala/js/*.js",
"examples/duel/www/**",
"examples/duel/src/main/webapp/js/lib/**",
"examples/ember-cli/todomvc/dist/**/*.js",
......
### Eclipse template
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules/*/package.json
node_modules/*/readme.md
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Scala template
*.class
*.log
# sbt specific
.cache-*
.cache
.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
# Scala-IDE specific
.scala_dependencies
.worksheet
### Eclipse template
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse
# Created by .ignore support plugin (hsz.mobi)
# Files created for deployment
secret/
local.sbt
enablePlugins(SbtJsEngine)
scalaVersion in Global := "2.11.8"
lazy val js = project
val indexHtml = taskKey[File]("Generate an index.html that follows TodoMVC's Application Specification")
indexHtml := {
val linkedJs = (scalaJSLinkedFile in js in Compile).value.asInstanceOf[org.scalajs.core.tools.io.FileVirtualJSFile]
val document = <html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Binding.scala TodoMVC</title>
{
for {
file <- (JsEngineKeys.npmNodeModules in Assets).value
if file.getName.endsWith(".css")
} yield <link rel="stylesheet" href={ file.relativeTo(baseDirectory.value).get.toString }/>
}
</head>
<body>
<script type="text/javascript" src="node_modules/todomvc-common/base.js"></script>
<script type="text/javascript" src={ linkedJs.file.relativeTo(baseDirectory.value).get.toString }></script>
<script type="text/javascript"> com.thoughtworks.todo.Main().main() </script>
</body>
</html>
val outputFile = baseDirectory.value / "index.html"
IO.writeLines(outputFile, Seq("<!DOCTYPE html>", xml.Xhtml.toXhtml(document)))
outputFile
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Binding.scala • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css" /><link rel="stylesheet" href="node_modules/todomvc-common/base.css" />
</head>
<body>
<script type="text/javascript" src="node_modules/todomvc-common/base.js"></script>
<script type="text/javascript" src="js/js-opt.js"></script>
<script type="text/javascript"> com.thoughtworks.todo.Main().main() </script>
</body>
</html>
enablePlugins(ScalaJSPlugin)
libraryDependencies += "com.thoughtworks.binding" %%% "dom" % "7.0.3"
libraryDependencies += "com.lihaoyi" %%% "upickle" % "0.3.9"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
crossPaths := false
crossTarget in fullOptJS := baseDirectory.value
crossTarget in fastOptJS := baseDirectory.value
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
package com.thoughtworks.todo
import com.thoughtworks.binding.{Binding, dom}
import com.thoughtworks.binding.Binding.{BindingSeq, Constants, Var, Vars}
import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom.{document, Event, KeyboardEvent, window}
import org.scalajs.dom.ext.{KeyCode, LocalStorage}
import org.scalajs.dom.raw.{HTMLInputElement, Node}
import upickle.default.{read, write}
@JSExport object Main {
/** @note [[Todo]] is not a case class because we want to distinguish two [[Todo]]s with the same content */
final class Todo(val title: String, val completed: Boolean)
object Todo {
def apply(title: String, completed: Boolean) = new Todo(title, completed)
def unapply(todo: Todo) = Option((todo.title, todo.completed))
}
final case class TodoList(text: String, hash: String, items: BindingSeq[Todo])
object Models {
val LocalStorageName = "todos-binding.scala"
def load() = LocalStorage(LocalStorageName).toSeq.flatMap(read[Seq[Todo]])
def save(todos: Seq[Todo]) = LocalStorage(LocalStorageName) = write(todos)
val allTodos = Vars[Todo](load(): _*)
@dom val autoSave: Binding[Unit] = save(allTodos.bind)
autoSave.watch()
val editingTodo = Var[Option[Todo]](None)
val all = TodoList("All", "#/", allTodos)
val active = TodoList("Active", "#/active", for {todo <- allTodos if !todo.completed} yield todo)
val completed = TodoList("Completed", "#/completed", for {todo <- allTodos if todo.completed} yield todo)
val todoLists = Seq(all, active, completed)
def getCurrentTodoList = todoLists.find(_.hash == window.location.hash).getOrElse(all)
val currentTodoList = Var(getCurrentTodoList)
@dom val hashBinding: Binding[Unit] = window.location.hash = currentTodoList.bind.hash
hashBinding.watch()
window.onhashchange = { _: Event => currentTodoList := getCurrentTodoList }
}
import Models._
@dom def header: Binding[Node] = {
val keyDownHandler = { event: KeyboardEvent =>
(event.currentTarget, event.keyCode) match {
case (input: HTMLInputElement, KeyCode.Enter) =>
input.value.trim match {
case "" =>
case title =>
allTodos.get += Todo(title, completed = false)
input.value = ""
}
case _ =>
}
}
<header class="header">
<h1>todos</h1>
<input class="new-todo" autofocus={true} placeholder="What needs to be done?" onkeydown={keyDownHandler}/>
</header>
}
@dom def todoListItem(todo: Todo): Binding[Node] = {
// onblur is not only triggered by user interaction, but also triggered by programmatic DOM changes.
// In order to suppress this behavior, we have to replace the onblur event listener to a dummy handler before programmatic DOM changes.
val suppressOnBlur = Var(false)
def submit = { event: Event =>
suppressOnBlur := true
editingTodo := None
event.currentTarget.asInstanceOf[HTMLInputElement].value.trim match {
case "" =>
allTodos.get.remove(allTodos.get.indexOf(todo))
case trimmedTitle =>
allTodos.get(allTodos.get.indexOf(todo)) = Todo(trimmedTitle, todo.completed)
}
}
def keyDownHandler = { event: KeyboardEvent =>
event.keyCode match {
case KeyCode.Escape =>
suppressOnBlur := true
editingTodo := None
case KeyCode.Enter =>
submit(event)
case _ =>
}
}
def ignoreEvent = { _: Event => }
@dom def blureHandler: Binding[Event => Any] = if (suppressOnBlur.bind) ignoreEvent else submit
val edit = <input class="edit" value={ todo.title } onblur={ blureHandler.bind } onkeydown={ keyDownHandler } />
def toggleHandler = { event: Event =>
allTodos.get(allTodos.get.indexOf(todo)) = Todo(todo.title, event.currentTarget.asInstanceOf[HTMLInputElement].checked)
}
<li class={s"${if (todo.completed) "completed" else ""} ${if (editingTodo.bind.contains(todo)) "editing" else ""}"}>
<div class="view">
<input class="toggle" type="checkbox" checked={todo.completed} onclick={toggleHandler}/>
<label ondblclick={ _: Event => editingTodo := Some(todo); edit.focus() }>{ todo.title }</label>
<button class="destroy" onclick={ _: Event => allTodos.get.remove(allTodos.get.indexOf(todo)) }></button>
</div>
{ edit }
</li>
}
@dom def mainSection: Binding[Node] = {
def toggleAllClickHandler = { event: Event =>
for ((todo, i) <- allTodos.get.zipWithIndex) {
if (todo.completed != event.currentTarget.asInstanceOf[HTMLInputElement].checked) {
allTodos.get(i) = Todo(todo.title, event.currentTarget.asInstanceOf[HTMLInputElement].checked)
}
}
}
<section class="main" style:display={if (allTodos.length.bind == 0) "none" else ""}>
<input type="checkbox" class="toggle-all" checked={active.items.length.bind == 0} onclick={toggleAllClickHandler}/>
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">{ for { todo <- currentTodoList.bind.items } yield todoListItem(todo).bind }</ul>
</section>
}
@dom def footer: Binding[Node] = {
def clearCompletedClickHandler = { _: Event =>
allTodos.get --= (for { todo <- allTodos.get if todo.completed } yield todo)
}
<footer class="footer" style:display={if (allTodos.length.bind == 0) "none" else ""}>
<span class="todo-count">
<strong>{ active.items.length.bind.toString }</strong> { if (active.items.length.bind == 1) "item" else "items"} left
</span>
<ul class="filters">{
for { todoList <- Constants(todoLists: _*) } yield {
<li>
<a href={ todoList.hash } class={ if (todoList == currentTodoList.bind) "selected" else "" }>{ todoList.text }</a>
</li>
}
}</ul>
<button class="clear-completed" onclick={clearCompletedClickHandler}
style:visibility={if (completed.items.length.bind == 0) "hidden" else "visible"}>
Clear completed
</button>
</footer>
}
@dom def todoapp: Binding[BindingSeq[Node]] = {
<section class="todoapp">{ header.bind }{ mainSection.bind }{ footer.bind }</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="https://github.com/atry">Yang Bo</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
}
@JSExport def main() = dom.render(document.body, todoapp)
}
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
label[for='toggle-all'] {
display: none;
}
.toggle-all {
position: absolute;
top: -55px;
left: -12px;
width: 60px;
height: 34px;
text-align: center;
border: none; /* Mobile Safari */
}
.toggle-all:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: 506px;
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todo-list li label {
word-break: break-all;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
.toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
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;
}
}
/* global _ */
(function () {
'use strict';
/* jshint ignore:start */
// Underscore's Template Module
// Courtesy of underscorejs.org
var _ = (function (_) {
_.defaults = function (object) {
if (!object) {
return object;
}
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
var iterable = arguments[argsIndex];
if (iterable) {
for (var key in iterable) {
if (object[key] == null) {
object[key] = iterable[key];
}
}
}
}
return object;
}
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /(.)^/;
// Certain characters need to be escaped so that they can be put into a
// string literal.
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(text, data, settings) {
var render;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
var matcher = new RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
if (escape) {
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
}
if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
}
if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
index = offset + match.length;
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + "return __p;\n";
try {
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
if (data) return render(data, _);
var template = function(data) {
return render.call(this, data, _);
};
// Provide the compiled function source as a convenience for precompilation.
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
return template;
};
return _;
})({});
if (location.hostname === 'todomvc.com') {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
}
/* jshint ignore:end */
function redirect() {
if (location.hostname === 'tastejs.github.io') {
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
}
}
function findRoot() {
var base = location.href.indexOf('examples/');
return location.href.substr(0, base);
}
function getFile(file, callback) {
if (!location.host) {
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', findRoot() + file, true);
xhr.send();
xhr.onload = function () {
if (xhr.status === 200 && callback) {
callback(xhr.responseText);
}
};
}
function Learn(learnJSON, config) {
if (!(this instanceof Learn)) {
return new Learn(learnJSON, config);
}
var template, framework;
if (typeof learnJSON !== 'object') {
try {
learnJSON = JSON.parse(learnJSON);
} catch (e) {
return;
}
}
if (config) {
template = config.template;
framework = config.framework;
}
if (!template && learnJSON.templates) {
template = learnJSON.templates.todomvc;
}
if (!framework && document.querySelector('[data-framework]')) {
framework = document.querySelector('[data-framework]').dataset.framework;
}
this.template = template;
if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({
backend: true
});
} else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append();
}
this.fetchIssueCount();
}
Learn.prototype.append = function (opts) {
var aside = document.createElement('aside');
aside.innerHTML = _.template(this.template, this.frameworkJSON);
aside.className = 'learn';
if (opts && opts.backend) {
// Remove demo link
var sourceLinks = aside.querySelector('.source-links');
var heading = sourceLinks.firstElementChild;
var sourceLink = sourceLinks.lastElementChild;
// Correct link path
var href = sourceLink.getAttribute('href');
sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
} else {
// Localize demo links
var demoLinks = aside.querySelectorAll('.demo-link');
Array.prototype.forEach.call(demoLinks, function (demoLink) {
if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
}
});
}
document.body.className = (document.body.className + ' learn-bar').trim();
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
};
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect();
getFile('learn.json', Learn);
})();
{
"private": true,
"dependencies": {
"todomvc-app-css": "^2.0.4",
"todomvc-common": "^1.0.1"
}
}
addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
addSbtPlugin("com.thoughtworks.sbt-scala-js-map" % "sbt-scala-js-map" % "2.0.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.11")
# [Binding.scala • TodoMVC](https://github.com/ThoughtWorksInc/todo)
[Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala) is a data-binding framework for [Scala](http://www.scala-lang.org/), running on both JVM and [Scala.js](http://www.scala-js.org/).
Binding.scala can be used as a **[reactive](https://en.wikipedia.org/wiki/Reactive_programming) web framework**.
It enables you use native XML literal syntax to create reactive DOM nodes,
which are able to automatically change whenever the data source changes.
Binding.scala's TodoMVC application has the tiniest code size among all the TodoMVC implementations,
only one source file, 154 lines of code!
## Getting Started
``` scala
/**
* Returns a bindable HTML DOM tree.
*
* The `@dom` annotations enable two magics:
* 1. XHTML literals to create DOM nodes
* 2. `xxx.bind` syntax, which make this DOM tree keep updated whenever `xxx` changes.
*/
@dom def render = {
val value = Var("")
<div>
<input onchange={ event: Event => dom.currentTarget.asInstanceOf[HTMLInputElement].value }/>
Your input value is { value.bind }
</div>
}
/**
* Renders a bindable HTML DOM node into the body of current web page.
*/
@JSExport def main(): Unit = {
dom.render(document.body, render)
}
```
## Instructions to build this application
1. Download and install [sbt](http://www.scala-sbt.org/)
2. Clone this repository `git clone https://github.com/ThoughtWorksInc/todo.git`
3. Execute the shell command `sbt indexHtml` at the base directory of this code base.
4. Open the generated `index.html` in your browser. Enjoy it! 😋
## Links
* [The Binding.scala Project Page](https://github.com/ThoughtWorksInc/Binding.scala)
* [This “Binding.scala • TodoMVC” Project Page](https://github.com/ThoughtWorksInc/todo)
* [This “Binding.scala • TodoMVC” DEMO](https://thoughtworksinc.github.io/todo)
* [API documentation](https://oss.sonatype.org/service/local/repositories/releases/archive/com/thoughtworks/binding/unidoc_2.11/4.0.1/unidoc_2.11-4.0.1-javadoc.jar/!/com/thoughtworks/binding/package.html)
* [Other live DEMOs](https://thoughtworksinc.github.io/Binding.scala/)
* [Chat on Gitter](https://gitter.im/ThoughtWorksInc/Binding.scala)
......@@ -170,6 +170,9 @@
<li class="routing">
<a href="examples/scalajs-react/" data-source="https://github.com/japgolly/scalajs-react" data-content="Facebook's React on Scala.js.">Scala.js + React</a>
</li>
<li class="routing">
<a href="examples/binding-scala/" data-source="https://github.com/ThoughtWorksInc/Binding.scala" data-content="Binding.scala is a reactive web framework. This TodoMVC application has the tiniest code size among all the TodoMVC implementations.">Scala.js + Binding.scala</a>
</li>
<li class="routing">
<a href="examples/js_of_ocaml/" data-source="http://ocsigen.org/js_of_ocaml/" data-content="Js_of_ocaml is a compiler of OCaml bytecode to Javascript.">js_of_ocaml</a>
</li>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment