dynamic context computed properties

This commit is contained in:
Evan You 2013-08-11 11:30:53 -04:00
parent e4d06a10ea
commit de1c1a1112
6 changed files with 91 additions and 53 deletions

View File

@ -1,3 +1,5 @@
- tests
- docs
- sd-with
- standarized way to reuse components (sd-component?)
- plugins: seed-touch, seed-storage, seed-router

View File

@ -7,10 +7,8 @@
</head>
<body>
<section id="todoapp" sd-controller="Todos">
<header id="header">
<h1>todos</h1>
<!-- main input box -->
<input
id="new-todo"
autofocus
@ -20,16 +18,13 @@
sd-on="keyup:addTodo | key enter"
>
</header>
<section id="main" sd-show="total">
<!-- toggle all checkbox-->
<input
id="toggle-all"
type="checkbox"
sd-checked="allDone"
>
<ul id="todo-list">
<!-- a single todo item -->
<li
class="todo"
sd-each="todo:todos"
@ -56,8 +51,6 @@
</li>
</ul>
</section>
<!-- footer controls -->
<footer id="footer" sd-show="total">
<span id="todo-count">
<strong sd-text="remaining"></strong> {{itemLabel}} left
@ -71,17 +64,12 @@
Remove Completed ({{completed}})
</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="js/app.js"></script>
</body>

View File

@ -1,16 +1,27 @@
var storageKey = 'todos-seedjs',
todos = JSON.parse(localStorage.getItem(storageKey)) || [],
filters = {
Seed.controller('Todos', function (scope) {
// data persistence -------------------------------------------------------
var STORAGE_KEY = 'todos-seedjs'
function sync () {
localStorage.setItem(STORAGE_KEY, scope.$serialize('todos'))
}
// filters ----------------------------------------------------------------
var filters = {
all: function () { return true },
active: function (v) { return !v },
completed: function (v) { return v }
}
Seed.controller('Todos', function (scope) {
updateFilter()
window.addEventListener('hashchange', updateFilter)
function updateFilter () {
if (!location.hash) return
scope.filter = location.hash.slice(2) || 'all'
}
// regular properties -----------------------------------------------------
scope.todos = todos
scope.remaining = todos.reduce(function (n, todo) {
scope.todos = JSON.parse(localStorage.getItem(STORAGE_KEY)) || []
scope.remaining = scope.todos.reduce(function (n, todo) {
return n + (todo.completed ? 0 : 1)
}, 0)
@ -27,12 +38,12 @@ Seed.controller('Todos', function (scope) {
return scope.remaining > 1 ? 'items' : 'item'
}}
// computed property using info from target scope
// dynamic context computed property using info from target scope
scope.filterTodo = {get: function (e) {
return filters[scope.filter](e.scope.completed)
}}
// computed property using info from target element
// dynamic context computed property using info from target element
scope.checkFilter = {get: function (e) {
return scope.filter === e.el.textContent.toLowerCase()
}}
@ -52,8 +63,9 @@ Seed.controller('Todos', function (scope) {
// event handlers ---------------------------------------------------------
scope.addTodo = function () {
if (scope.newTodo.trim()) {
scope.todos.unshift({ title: scope.newTodo.trim(), completed: false })
var value = scope.newTodo.trim()
if (value) {
scope.todos.unshift({ title: value, completed: false })
scope.newTodo = ''
scope.remaining++
sync()
@ -99,19 +111,8 @@ Seed.controller('Todos', function (scope) {
sync()
}
// filters
updateFilter()
window.addEventListener('hashchange', updateFilter)
function updateFilter () {
if (!location.hash) return
scope.filter = location.hash.slice(2) || 'all'
}
// data persistence using localStorage
function sync () {
localStorage.setItem(storageKey, scope.$serialize('todos'))
}
})
Seed.bootstrap({ debug: true })
var s = Date.now()
Seed.bootstrap({ debug: false })
console.log(Date.now() - s)

View File

@ -115,6 +115,13 @@ BindingProto.update = function (value) {
this.pub()
}
BindingProto.refresh = function () {
var i = this.instances.length
while (i--) {
this.instances[i].refresh()
}
}
/*
* Notify computed properties that depend on this binding
* to update themselves

View File

@ -19,8 +19,9 @@ function catchDeps (binding) {
observer.on('get', function (dep) {
binding.deps.push(dep)
})
parseContextDependency(binding)
binding.value.get({
scope: createDummyScope(binding.value.get),
scope: createDummyScope(binding),
el: dummyEl
})
observer.off('get')
@ -37,7 +38,7 @@ function filterDeps (binding) {
dep = binding.deps[i]
if (!dep.deps.length) {
config.log(' └─' + dep.key)
dep.subs.push.apply(dep.subs, binding.instances)
dep.subs.push(binding)
} else {
binding.deps.splice(i, 1)
}
@ -53,18 +54,15 @@ function filterDeps (binding) {
* the user expects the target scope to possess. They are all assigned
* a noop function so they can be invoked with no real harm.
*/
function createDummyScope (fn) {
function createDummyScope (binding) {
var scope = {},
str = fn.toString()
var args = str.match(ARGS_RE)
if (!args) return scope
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
matches = str.match(argRE)
if (!matches) return scope
var i = matches.length, j, path, key, level
deps = binding.contextDeps
if (!deps) return scope
var i = binding.contextDeps.length,
j, level, key, path
while (i--) {
level = scope
path = matches[i].slice(args[1].length + 7).split('.')
path = deps[i].split('.')
j = 0
while (j < path.length) {
key = path[j]
@ -76,6 +74,22 @@ function createDummyScope (fn) {
return scope
}
/*
* Extract context dependency paths
*/
function parseContextDependency (binding) {
var fn = binding.value.get,
str = fn.toString(),
args = str.match(ARGS_RE)
if (!args) return null
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
matches = str.match(argRE),
base = args[1].length + 7
if (!matches) return null
binding.contextDeps = matches.map(function (key) { return key.slice(base) })
binding.seed._contextBindings.push(binding)
}
module.exports = {
/*

View File

@ -21,10 +21,13 @@ function Seed (el, options) {
el = document.querySelector(el)
}
this.el = el
el.seed = this
this._bindings = {}
this._computed = []
this.el = el
el.seed = this
this._bindings = {}
// list of computed properties that need to parse dependencies for
this._computed = []
// list of bindings that has dynamic context dependencies
this._contextBindings = []
// copy options
options = options || {}
@ -82,6 +85,9 @@ function Seed (el, options) {
// extract dependencies for computed properties
if (this._computed.length) depsParser.parse(this._computed)
delete this._computed
if (this._contextBindings.length) this._bindContexts(this._contextBindings)
delete this._contextBindings
}
// for better compression
@ -193,7 +199,10 @@ SeedProto._bind = function (directive) {
seed = traceOwnerSeed(directive, seed)
var binding = seed._bindings[key] || seed._createBinding(key)
// add directive to this binding
if (binding.contextDeps) {
console.log(1)
}
binding.instances.push(directive)
directive.binding = binding
@ -220,6 +229,23 @@ SeedProto._createBinding = function (key) {
return binding
}
/*
* Process subscriptions for computed properties that has
* dynamic context dependencies
*/
SeedProto._bindContexts = function (bindings) {
var i = bindings.length, j, binding, depKey, dep
while (i--) {
binding = bindings[i]
j = binding.contextDeps.length
while (j--) {
depKey = binding.contextDeps[j]
dep = this._bindings[depKey]
dep.subs.push(binding)
}
}
}
/*
* Call unbind() of all directive instances
* to remove event listeners, destroy child seeds, etc.