Browse Source

complete todo example

Evan You 13 years ago
parent
commit
7d126127e6

+ 2 - 0
README.md

@@ -11,6 +11,8 @@ WIP, playing with data binding
 
 ### Data Binding
 
+### Event Handling
+
 ### Filters
 
 ### Computed Properties

+ 4 - 1
TODO.md

@@ -1,3 +1,6 @@
-- parse textNodes
+- parse textNodes?
+- method invoke with arguments
 - more directives / filters
+    - sd-if
+    - sd-route
 - nested properties in scope (kinda hard, maybe later)

+ 0 - 3
component.json

@@ -1,9 +1,6 @@
 {
   "name": "seed",
   "version": "0.0.1",
-  "dependencies": {
-    "component/emitter": "*"
-  },
   "main": "src/main.js",
   "scripts": [
     "src/main.js",

+ 0 - 119
examples/todos.html

@@ -1,119 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>Todo</title>
-        <meta charset="utf-8">
-        <script src="../dist/seed.js"></script>
-        <style type="text/css">
-            .red {
-                color: red;
-            }
-            .done {
-                text-decoration: line-through;
-            }
-            #app.all .all {
-                font-weight: bold;
-            }
-            #app.remaining .todo.done {
-                display: none;
-            }
-            #app.remaining .remaining {
-                font-weight: bold;
-            }
-            #app.completed .todo:not(.done) {
-                display: none;
-            }
-            #app.completed .completed {
-                font-weight: bold;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="app" sd-controller="Todos" sd-class="filter">
-            <div>
-                <input placeholder="What needs to be done?" sd-on="change:addTodo">
-            </div>
-            <ul sd-show="todos">
-                <li class="todo" sd-each="todo:todos" sd-class="done:todo.done">
-                    <input type="checkbox" sd-checked="todo.done" sd-on="change:toggleTodo">
-                    <span sd-text="todo.text"></span>
-                    <a sd-on="click:removeTodo">X</a>
-                </li>
-            </ul>
-            <div id="footer">
-                Total: <span sd-text="total < todos"></span> |
-                Remaining: <span sd-text="remaining < completed"></span> |
-                Completed: <span sd-text="completed"></span>
-                <br>
-                <a class="all" sd-on="click:setFilter">Show All</a> |
-                <a class="remaining" sd-on="click:setFilter">Show Remaining</a> |
-                <a class="completed" sd-on="click:setFilter">Show Completed</a>
-                <br>
-                <a sd-on="click:removeCompleted">Remove Completed</a>
-            </div>
-
-        </div>
-        <script>
-
-            var Seed = require('seed')
-
-            var todos = [
-                { text: 'make nesting controllers work', done: true },
-                { text: 'complete ArrayWatcher', done: false },
-                { text: 'computed properties', done: false },
-                { text: 'parse textnodes', done: false }
-            ]
-
-            Seed.controller('Todos', function (scope) {
-
-                // regular properties
-                scope.todos = todos
-                scope.filter = 'all'
-                scope.completed = todos.reduce(function (count, todo) {
-                    return count + (todo.done ? 1 : 0)
-                }, 0)
-
-                // computed properties
-                scope.total = function () {
-                    return scope.todos.length
-                }
-
-                scope.remaining = function () {
-                    return scope.todos.length - scope.completed
-                }
-
-                // event handlers
-                scope.addTodo = function (e) {
-                    var val = e.el.value
-                    if (val) {
-                        e.el.value = ''
-                        scope.todos.unshift({ text: val, done: false })
-                    }
-                }
-
-                scope.removeTodo = function (e) {
-                    scope.todos.remove(e.scope)
-                    scope.completed -= e.scope.done ? 1 : 0
-                }
-
-                scope.toggleTodo = function (e) {
-                    scope.completed += e.scope.done ? 1 : -1
-                }
-
-                scope.setFilter = function (e) {
-                    scope.filter = e.el.className
-                }
-
-                scope.removeCompleted = function () {
-                    scope.todos = scope.todos.filter(function (todo) {
-                        return !todo.done
-                    })
-                }
-
-            })
-
-            var app = Seed.bootstrap()
-
-        </script>
-    </body>
-</html>

+ 80 - 0
examples/todos/app.js

@@ -0,0 +1,80 @@
+var Seed = require('seed')
+
+var todos = [
+    { text: 'make nesting controllers work', done: true },
+    { text: 'complete ArrayWatcher', done: false },
+    { text: 'computed properties', done: false },
+    { text: 'parse textnodes', done: false }
+]
+
+Seed.controller('Todos', function (scope) {
+
+    // regular properties -----------------------------------------------------
+    scope.todos = todos
+    scope.filter = 'all'
+    scope.allDone = false
+    scope.remaining = todos.reduce(function (count, todo) {
+        return count + (todo.done ? 0 : 1)
+    }, 0)
+
+    // computed properties ----------------------------------------------------
+    scope.total = function () {
+        return scope.todos.length
+    }
+
+    scope.completed = function () {
+        return scope.total() - scope.remaining
+    }
+
+    scope.itemLabel = function () {
+        return scope.remaining > 1 ? 'items' : 'item'
+    }
+
+    // event handlers ---------------------------------------------------------
+    scope.addTodo = function (e) {
+        var val = e.el.value
+        if (val) {
+            e.el.value = ''
+            scope.todos.unshift({ text: val, done: false })
+        }
+        scope.remaining++
+    }
+
+    scope.removeTodo = function (e) {
+        scope.todos.remove(e.scope)
+        scope.remaining -= e.scope.done ? 0 : 1
+    }
+
+    scope.updateCount = function (e) {
+        scope.remaining += e.scope.done ? -1 : 1
+        scope.allDone = scope.remaining === 0
+    }
+
+    scope.edit = function (e) {
+        e.scope.editing = true
+    }
+
+    scope.stopEdit = function (e) {
+        e.scope.editing = false
+    }
+
+    scope.setFilter = function (e) {
+        scope.filter = e.el.dataset.filter
+    }
+
+    scope.toggleAll = function (e) {
+        scope.todos.forEach(function (todo) {
+            todo.done = e.el.checked
+        })
+        scope.remaining = e.el.checked ? 0 : scope.total()
+    }
+
+    scope.removeCompleted = function () {
+        scope.todos = scope.todos.filter(function (todo) {
+            return !todo.done
+        })
+    }
+
+})
+
+Seed.bootstrap()

BIN
examples/todos/bg.png


+ 13 - 0
examples/todos/custom.css

@@ -0,0 +1,13 @@
+#todoapp.all [data-filter="all"],
+#todoapp.active [data-filter="active"],
+#todoapp.completed [data-filter="completed"] {
+    font-weight: bold;
+}
+
+#todoapp.active #todo-list li.completed {
+    display: none;
+}
+
+#todoapp.completed #todo-list li:not(.completed) {
+    display: none;
+}

+ 82 - 0
examples/todos/index.html

@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Todo</title>
+        <meta charset="utf-8">
+        <link rel="stylesheet" type="text/css" href="todomvc.css">
+        <link rel="stylesheet" type="text/css" href="custom.css">
+    </head>
+    <body>
+        <section id="todoapp" sd-controller="Todos" sd-class="filter">
+
+            <header id="header">
+                <h1>todos</h1>
+                <!-- main input box -->
+                <input
+                    id="new-todo"
+                    autofocus
+                    sd-on="keyup:addTodo | key enter"
+                    placeholder="What needs to be done?"
+                >
+            </header>
+            
+            <section id="main" sd-show="total < todos">
+                <input id="toggle-all" type="checkbox" sd-checked="allDone" sd-on="change:toggleAll">
+                <ul id="todo-list">
+                    <!-- a single todo item -->
+                    <li sd-each="todo:todos" sd-class="completed:todo.done, editing:todo.editing">
+                        <div class="view">
+                            <input
+                                class="toggle"
+                                type="checkbox"
+                                sd-checked="todo.done"
+                                sd-on="change:updateCount"
+                            >
+                            <label
+                                sd-text="todo.text"
+                                sd-on="dblclick:edit"
+                            ></label>
+                            <button class="destroy" sd-on="click:removeTodo"></button>
+                        </div>
+                        <input
+                            class="edit"
+                            type="text"
+                            sd-focus="todo.editing"
+                            sd-on="blur:stopEdit, keyup:stopEdit | key enter"
+                            sd-value="todo.text"
+                        >
+                    </li>
+                </ul>
+            </section>
+
+            <!-- footer controls -->
+            <footer id="footer" sd-show="total < todos">
+                <span id="todo-count">
+                    <strong sd-text="remaining"></strong>
+                    <span sd-text="itemLabel < remaining"></span>
+                    left
+                </span>
+                <ul id="filters">
+                    <li><a href="#/all" data-filter="all" sd-on="click:setFilter">All</a></li>
+                    <li><a href="#/active" data-filter="active" sd-on="click:setFilter">Active</a></li>
+                    <li><a href="#/completed" data-filter="completed" sd-on="click:setFilter">Completed</a></li>
+                </ul>
+                <button id="clear-completed" sd-on="click:removeCompleted">
+                    Remove Completed (<span sd-text="completed < total remaining"></span>)
+                </button>
+            </footer>
+
+        </section>
+
+        <!-- info -->
+        <footer id="info">
+            <p>Double-click to edit a todo</p>
+            <p>Powered by <a href="https://github.com/yyx990803/seed">Seed.js</a></p>
+            <p>Created by <a href="http://evanyou.me">Evan You</a></p>
+        </footer>
+
+        <!-- js -->
+        <script src="../../dist/seed.js"></script>
+        <script src="app.js"></script>
+    </body>
+</html>

+ 414 - 0
examples/todos/todomvc.css

@@ -0,0 +1,414 @@
+html,
+body {
+	margin: 0;
+	padding: 0;
+}
+
+button {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	background: none;
+	font-size: 100%;
+	vertical-align: baseline;
+	font-family: inherit;
+	color: inherit;
+	-webkit-appearance: none;
+	/*-moz-appearance: none;*/
+	-ms-appearance: none;
+	-o-appearance: none;
+	appearance: none;
+}
+
+body {
+	font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+	line-height: 1.4em;
+	background: #eaeaea url('bg.png');
+	color: #4d4d4d;
+	width: 550px;
+	margin: 0 auto;
+	-webkit-font-smoothing: antialiased;
+	-moz-font-smoothing: antialiased;
+	-ms-font-smoothing: antialiased;
+	-o-font-smoothing: antialiased;
+	font-smoothing: antialiased;
+}
+
+#todoapp {
+	background: #fff;
+	background: rgba(255, 255, 255, 0.9);
+	margin: 130px 0 40px 0;
+	border: 1px solid #ccc;
+	position: relative;
+	border-top-left-radius: 2px;
+	border-top-right-radius: 2px;
+	box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
+				0 25px 50px 0 rgba(0, 0, 0, 0.15);
+}
+
+#todoapp:before {
+	content: '';
+	border-left: 1px solid #f5d6d6;
+	border-right: 1px solid #f5d6d6;
+	width: 2px;
+	position: absolute;
+	top: 0;
+	left: 40px;
+	height: 100%;
+}
+
+#todoapp input::-webkit-input-placeholder {
+	font-style: italic;
+}
+
+#todoapp input::-moz-placeholder {
+	font-style: italic;
+	color: #a9a9a9;
+}
+
+#todoapp h1 {
+	position: absolute;
+	top: -120px;
+	width: 100%;
+	font-size: 70px;
+	font-weight: bold;
+	text-align: center;
+	color: #b3b3b3;
+	color: rgba(255, 255, 255, 0.3);
+	text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
+	-webkit-text-rendering: optimizeLegibility;
+	-moz-text-rendering: optimizeLegibility;
+	-ms-text-rendering: optimizeLegibility;
+	-o-text-rendering: optimizeLegibility;
+	text-rendering: optimizeLegibility;
+}
+
+#header {
+	padding-top: 15px;
+	border-radius: inherit;
+}
+
+#header:before {
+	content: '';
+	position: absolute;
+	top: 0;
+	right: 0;
+	left: 0;
+	height: 15px;
+	z-index: 2;
+	border-bottom: 1px solid #6c615c;
+	background: #8d7d77;
+	background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
+	background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+	background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+	background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+	background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+	background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
+	filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
+	border-top-left-radius: 1px;
+	border-top-right-radius: 1px;
+}
+
+#new-todo,
+.edit {
+	position: relative;
+	margin: 0;
+	width: 100%;
+	font-size: 24px;
+	font-family: inherit;
+	line-height: 1.4em;
+	border: 0;
+	outline: none;
+	color: inherit;
+	padding: 6px;
+	border: 1px solid #999;
+	box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	-ms-box-sizing: border-box;
+	-o-box-sizing: border-box;
+	box-sizing: border-box;
+	-webkit-font-smoothing: antialiased;
+	-moz-font-smoothing: antialiased;
+	-ms-font-smoothing: antialiased;
+	-o-font-smoothing: antialiased;
+	font-smoothing: antialiased;
+}
+
+#new-todo {
+	padding: 16px 16px 16px 60px;
+	border: none;
+	background: rgba(0, 0, 0, 0.02);
+	z-index: 2;
+	box-shadow: none;
+}
+
+#main {
+	position: relative;
+	z-index: 2;
+	border-top: 1px dotted #adadad;
+}
+
+label[for='toggle-all'] {
+	display: none;
+}
+
+#toggle-all {
+	position: absolute;
+	top: -42px;
+	left: -4px;
+	width: 40px;
+	text-align: center;
+	border: none; /* Mobile Safari */
+}
+
+#toggle-all:before {
+	content: '»';
+	font-size: 28px;
+	color: #d9d9d9;
+	padding: 0 25px 7px;
+}
+
+#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 dotted #ccc;
+}
+
+#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: 13px 17px 12px 17px;
+	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;
+	/*-moz-appearance: none;*/
+	-ms-appearance: none;
+	-o-appearance: none;
+	appearance: none;
+}
+
+#todo-list li .toggle:after {
+	content: '✔';
+	line-height: 43px; /* 40 + a couple of pixels visual adjustment */
+	font-size: 20px;
+	color: #d9d9d9;
+	text-shadow: 0 -1px 0 #bfbfbf;
+}
+
+#todo-list li .toggle:checked:after {
+	color: #85ada7;
+	text-shadow: 0 1px 0 #669991;
+	bottom: 1px;
+	position: relative;
+}
+
+#todo-list li label {
+	word-break: break-word;
+	padding: 15px;
+	margin-left: 45px;
+	display: block;
+	line-height: 1.2;
+	-webkit-transition: color 0.4s;
+	-moz-transition: color 0.4s;
+	-ms-transition: color 0.4s;
+	-o-transition: color 0.4s;
+	transition: color 0.4s;
+}
+
+#todo-list li.completed label {
+	color: #a9a9a9;
+	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: 22px;
+	color: #a88a8a;
+	-webkit-transition: all 0.2s;
+	-moz-transition: all 0.2s;
+	-ms-transition: all 0.2s;
+	-o-transition: all 0.2s;
+	transition: all 0.2s;
+}
+
+#todo-list li .destroy:hover {
+	text-shadow: 0 0 1px #000,
+				 0 0 10px rgba(199, 107, 107, 0.8);
+	-webkit-transform: scale(1.3);
+	-moz-transform: scale(1.3);
+	-ms-transform: scale(1.3);
+	-o-transform: scale(1.3);
+	transform: scale(1.3);
+}
+
+#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: 0 15px;
+	position: absolute;
+	right: 0;
+	bottom: -31px;
+	left: 0;
+	height: 20px;
+	z-index: 1;
+	text-align: center;
+}
+
+#footer:before {
+	content: '';
+	position: absolute;
+	right: 0;
+	bottom: 31px;
+	left: 0;
+	height: 50px;
+	z-index: -1;
+	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
+				0 6px 0 -3px rgba(255, 255, 255, 0.8),
+				0 7px 1px -3px rgba(0, 0, 0, 0.3),
+				0 43px 0 -6px rgba(255, 255, 255, 0.8),
+				0 44px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+#todo-count {
+	float: left;
+	text-align: left;
+}
+
+#filters {
+	margin: 0;
+	padding: 0;
+	list-style: none;
+	position: absolute;
+	right: 0;
+	left: 0;
+}
+
+#filters li {
+	display: inline;
+}
+
+#filters li a {
+	color: #83756f;
+	margin: 2px;
+	text-decoration: none;
+}
+
+#filters li a.selected {
+	font-weight: bold;
+}
+
+#clear-completed {
+	float: right;
+	position: relative;
+	line-height: 20px;
+	text-decoration: none;
+	background: rgba(0, 0, 0, 0.1);
+	font-size: 11px;
+	padding: 0 10px;
+	border-radius: 3px;
+	box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
+}
+
+#clear-completed:hover {
+	background: rgba(0, 0, 0, 0.15);
+	box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
+}
+
+#info {
+	margin: 65px auto 0;
+	color: #a6a6a6;
+	font-size: 12px;
+	text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
+	text-align: center;
+}
+
+#info a {
+	color: inherit;
+}
+
+/*
+	Hack to remove background from Mobile Safari.
+	Can't use it globally since it destroys checkboxes in Firefox and Opera
+*/
+@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 {
+		top: -56px;
+		left: -15px;
+		width: 65px;
+		height: 41px;
+		-webkit-transform: rotate(90deg);
+		transform: rotate(90deg);
+		-webkit-appearance: none;
+		appearance: none;
+	}
+}
+
+.hidden{
+	display:none;
+}

+ 12 - 6
src/directive-parser.js

@@ -95,7 +95,12 @@ function Directive (directiveName, expression) {
 // called when a dependency has changed
 Directive.prototype.refresh = function () {
     if (this.value) {
-        this._update(this.value.call(this.seed.scope))
+        var value = this.value.call(this.seed.scope)
+        this._update(
+            this.filters
+            ? this.applyFilters(value)
+            : value
+        )
     }
     if (this.binding.refreshDependents) {
         this.binding.refreshDependents()
@@ -104,16 +109,17 @@ Directive.prototype.refresh = function () {
 
 // called when a new value is set
 Directive.prototype.update = function (value) {
+    if (value && (value === this.value)) return
     this.value = value
     // computed property
     if (typeof value === 'function' && !this.fn) {
         value = value()
     }
-    // apply filters
-    if (this.filters) {
-        value = this.applyFilters(value)
-    }
-    this._update(value)
+    this._update(
+        this.filters
+        ? this.applyFilters(value)
+        : value
+    )
     if (this.deps) this.refresh()
 }
 

+ 5 - 3
src/directives/each.js

@@ -26,8 +26,11 @@ var mutationHandlers = {
     unshift: function (m) {
         var self = this
         m.args.forEach(function (data, i) {
-            var seed = self.buildItem(data, i)
-            self.container.insertBefore(seed.el, self.collection[m.args.length].$seed.el)
+            var seed = self.buildItem(data, i),
+                ref  = self.collection.length > m.args.length
+                     ? self.collection[m.args.length].$seed.el
+                     : self.marker
+            self.container.insertBefore(seed.el, ref)
         })
         self.reorder()
     },
@@ -132,7 +135,6 @@ module.exports = {
     },
 
     reorder: function () {
-        console.log('reorder')
         this.collection.forEach(function (scope, i) {
             scope.$index = i
         })

+ 29 - 5
src/directives/index.js

@@ -4,14 +4,23 @@ module.exports = {
     each : require('./each'),
 
     text: function (value) {
-        this.el.textContent = value === null ?
-            '' : value.toString()
+        this.el.textContent =
+            (value !== null && value !== undefined)
+            ? value.toString() : ''
     },
 
     show: function (value) {
         this.el.style.display = value ? '' : 'none'
     },
 
+    hide: function (value) {
+        this.el.style.display = value ? 'none' : ''  
+    },
+
+    focus: function (value) {
+        this.el[value ? 'focus' : 'blur']()
+    },
+
     class: function (value) {
         if (this.arg) {
             this.el.classList[value ? 'add' : 'remove'](this.arg)
@@ -22,17 +31,32 @@ module.exports = {
         }
     },
 
+    value: {
+        bind: function () {
+            var el = this.el, self = this
+            this.change = function () {
+                self.seed.scope[self.key] = el.value
+            }
+            el.addEventListener('change', this.change)
+        },
+        update: function (value) {
+            this.el.value = value
+        },
+        unbind: function () {
+            this.el.removeEventListener('change', this.change)
+        }
+    },
+
     checked: {
         bind: function () {
-            var el = this.el,
-                self = this
+            var el = this.el, self = this
             this.change = function () {
                 self.seed.scope[self.key] = el.checked
             }
             el.addEventListener('change', this.change)
         },
         update: function (value) {
-            this.el.checked = value
+            this.el.checked = !!value
         },
         unbind: function () {
             this.el.removeEventListener('change', this.change)

+ 31 - 0
src/filters.js

@@ -1,3 +1,13 @@
+var keyCodes = {
+    enter: 13,
+    tab: 9,
+    'delete': 46,
+    up: 38,
+    left: 37,
+    right: 39,
+    down: 40
+}
+
 module.exports = {
 
     capitalize: function (value) {
@@ -7,6 +17,27 @@ module.exports = {
 
     uppercase: function (value) {
         return value.toString().toUpperCase()
+    },
+
+    currency: function (value, args) {
+        if (!value) return value
+        var sign = (args && args[0]) || '$',
+            i = value % 3,
+            f = '.' + value.toFixed(2).slice(-2),
+            s = Math.floor(value).toString()
+        return sign + s.slice(0, i) + s.slice(i).replace(/(\d{3})(?=\d)/g, '$1,') + f
+    },
+
+    key: function (handler, args) {
+        var code = keyCodes[args[0]]
+        if (!code) {
+            code = parseInt(args[0], 10)
+        }
+        return function (e) {
+            if (e.originalEvent.keyCode === code) {
+                handler(e)
+            }
+        }
     }
 
 }

+ 24 - 23
src/seed.js

@@ -1,5 +1,4 @@
-var Emitter         = require('emitter'),
-    config          = require('./config'),
+var config          = require('./config'),
     DirectiveParser = require('./directive-parser'),
     TextNodeParser  = require('./textnode-parser')
 
@@ -7,20 +6,6 @@ var slice           = Array.prototype.slice,
     ctrlAttr        = config.prefix + '-controller',
     eachAttr        = config.prefix + '-each'
 
-function determinScope (key, scope) {
-    if (key.nesting) {
-        var levels = key.nesting
-        while (scope.parentSeed && levels--) {
-            scope = scope.parentSeed
-        }
-    } else if (key.root) {
-        while (scope.parentSeed) {
-            scope = scope.parentSeed
-        }
-    }
-    return scope
-}
-
 function Seed (el, options) {
 
     if (typeof el === 'string') {
@@ -45,11 +30,15 @@ function Seed (el, options) {
             || {}
     el.removeAttribute(dataPrefix)
 
+    // if the passed in data is already consumed by
+    // a Seed instance, make a copy from it
+    if (scope.$seed) {
+        scope = this.scope = scope.$dump()
+    }
+
     scope.$seed     = this
     scope.$destroy  = this._destroy.bind(this)
     scope.$dump     = this._dump.bind(this)
-    scope.$on       = this.on.bind(this)
-    scope.$emit     = this.emit.bind(this)
     scope.$index    = options.index
     scope.$parent   = options.parentSeed && options.parentSeed.scope
 
@@ -76,7 +65,7 @@ Seed.prototype._compileNode = function (node, root) {
 
         self._compileTextNode(node)
 
-    } else {
+    } else if (node.nodeType !== 8) { // exclude comment nodes
 
         var eachExp = node.getAttribute(eachAttr),
             ctrlExp = node.getAttribute(ctrlAttr)
@@ -164,9 +153,7 @@ Seed.prototype._bind = function (node, directive) {
     }
 
     // set initial value
-    if (binding.value) {
-        directive.update(binding.value)
-    }
+    directive.update(binding.value)
 
     // computed properties
     if (directive.deps) {
@@ -259,6 +246,20 @@ Seed.prototype._dump = function () {
     return dump
 }
 
-Emitter(Seed.prototype)
+// Helpers --------------------------------------------------------------------
+
+function determinScope (key, scope) {
+    if (key.nesting) {
+        var levels = key.nesting
+        while (scope.parentSeed && levels--) {
+            scope = scope.parentSeed
+        }
+    } else if (key.root) {
+        while (scope.parentSeed) {
+            scope = scope.parentSeed
+        }
+    }
+    return scope
+}
 
 module.exports = Seed