Commit 8c4053d8 authored by TasteBot's avatar TasteBot

update the build files for gh-pages [ci skip]

parent 8ee834c7
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)
This diff is collapsed.
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