mirror of https://github.com/vuejs/vue.git
dynamic context computed properties
This commit is contained in:
parent
e4d06a10ea
commit
de1c1a1112
2
TODO.md
2
TODO.md
|
|
@ -1,3 +1,5 @@
|
||||||
|
- tests
|
||||||
|
- docs
|
||||||
- sd-with
|
- sd-with
|
||||||
- standarized way to reuse components (sd-component?)
|
- standarized way to reuse components (sd-component?)
|
||||||
- plugins: seed-touch, seed-storage, seed-router
|
- plugins: seed-touch, seed-storage, seed-router
|
||||||
|
|
@ -7,10 +7,8 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section id="todoapp" sd-controller="Todos">
|
<section id="todoapp" sd-controller="Todos">
|
||||||
|
|
||||||
<header id="header">
|
<header id="header">
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<!-- main input box -->
|
|
||||||
<input
|
<input
|
||||||
id="new-todo"
|
id="new-todo"
|
||||||
autofocus
|
autofocus
|
||||||
|
|
@ -20,16 +18,13 @@
|
||||||
sd-on="keyup:addTodo | key enter"
|
sd-on="keyup:addTodo | key enter"
|
||||||
>
|
>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section id="main" sd-show="total">
|
<section id="main" sd-show="total">
|
||||||
<!-- toggle all checkbox-->
|
|
||||||
<input
|
<input
|
||||||
id="toggle-all"
|
id="toggle-all"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
sd-checked="allDone"
|
sd-checked="allDone"
|
||||||
>
|
>
|
||||||
<ul id="todo-list">
|
<ul id="todo-list">
|
||||||
<!-- a single todo item -->
|
|
||||||
<li
|
<li
|
||||||
class="todo"
|
class="todo"
|
||||||
sd-each="todo:todos"
|
sd-each="todo:todos"
|
||||||
|
|
@ -56,8 +51,6 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- footer controls -->
|
|
||||||
<footer id="footer" sd-show="total">
|
<footer id="footer" sd-show="total">
|
||||||
<span id="todo-count">
|
<span id="todo-count">
|
||||||
<strong sd-text="remaining"></strong> {{itemLabel}} left
|
<strong sd-text="remaining"></strong> {{itemLabel}} left
|
||||||
|
|
@ -71,17 +64,12 @@
|
||||||
Remove Completed ({{completed}})
|
Remove Completed ({{completed}})
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- info -->
|
|
||||||
<footer id="info">
|
<footer id="info">
|
||||||
<p>Double-click to edit a todo</p>
|
<p>Double-click to edit a todo</p>
|
||||||
<p>Powered by <a href="https://github.com/yyx990803/seed">Seed.js</a></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>
|
<p>Created by <a href="http://evanyou.me">Evan You</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- js -->
|
|
||||||
<script src="../../dist/seed.js"></script>
|
<script src="../../dist/seed.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,27 @@
|
||||||
var storageKey = 'todos-seedjs',
|
Seed.controller('Todos', function (scope) {
|
||||||
todos = JSON.parse(localStorage.getItem(storageKey)) || [],
|
|
||||||
filters = {
|
// data persistence -------------------------------------------------------
|
||||||
|
var STORAGE_KEY = 'todos-seedjs'
|
||||||
|
function sync () {
|
||||||
|
localStorage.setItem(STORAGE_KEY, scope.$serialize('todos'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// filters ----------------------------------------------------------------
|
||||||
|
var filters = {
|
||||||
all: function () { return true },
|
all: function () { return true },
|
||||||
active: function (v) { return !v },
|
active: function (v) { return !v },
|
||||||
completed: function (v) { return v }
|
completed: function (v) { return v }
|
||||||
}
|
}
|
||||||
|
updateFilter()
|
||||||
Seed.controller('Todos', function (scope) {
|
window.addEventListener('hashchange', updateFilter)
|
||||||
|
function updateFilter () {
|
||||||
|
if (!location.hash) return
|
||||||
|
scope.filter = location.hash.slice(2) || 'all'
|
||||||
|
}
|
||||||
|
|
||||||
// regular properties -----------------------------------------------------
|
// regular properties -----------------------------------------------------
|
||||||
scope.todos = todos
|
scope.todos = JSON.parse(localStorage.getItem(STORAGE_KEY)) || []
|
||||||
scope.remaining = todos.reduce(function (n, todo) {
|
scope.remaining = scope.todos.reduce(function (n, todo) {
|
||||||
return n + (todo.completed ? 0 : 1)
|
return n + (todo.completed ? 0 : 1)
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
|
|
@ -27,12 +38,12 @@ Seed.controller('Todos', function (scope) {
|
||||||
return scope.remaining > 1 ? 'items' : 'item'
|
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) {
|
scope.filterTodo = {get: function (e) {
|
||||||
return filters[scope.filter](e.scope.completed)
|
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) {
|
scope.checkFilter = {get: function (e) {
|
||||||
return scope.filter === e.el.textContent.toLowerCase()
|
return scope.filter === e.el.textContent.toLowerCase()
|
||||||
}}
|
}}
|
||||||
|
|
@ -52,8 +63,9 @@ Seed.controller('Todos', function (scope) {
|
||||||
|
|
||||||
// event handlers ---------------------------------------------------------
|
// event handlers ---------------------------------------------------------
|
||||||
scope.addTodo = function () {
|
scope.addTodo = function () {
|
||||||
if (scope.newTodo.trim()) {
|
var value = scope.newTodo.trim()
|
||||||
scope.todos.unshift({ title: scope.newTodo.trim(), completed: false })
|
if (value) {
|
||||||
|
scope.todos.unshift({ title: value, completed: false })
|
||||||
scope.newTodo = ''
|
scope.newTodo = ''
|
||||||
scope.remaining++
|
scope.remaining++
|
||||||
sync()
|
sync()
|
||||||
|
|
@ -99,19 +111,8 @@ Seed.controller('Todos', function (scope) {
|
||||||
sync()
|
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)
|
||||||
|
|
@ -115,6 +115,13 @@ BindingProto.update = function (value) {
|
||||||
this.pub()
|
this.pub()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BindingProto.refresh = function () {
|
||||||
|
var i = this.instances.length
|
||||||
|
while (i--) {
|
||||||
|
this.instances[i].refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Notify computed properties that depend on this binding
|
* Notify computed properties that depend on this binding
|
||||||
* to update themselves
|
* to update themselves
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ function catchDeps (binding) {
|
||||||
observer.on('get', function (dep) {
|
observer.on('get', function (dep) {
|
||||||
binding.deps.push(dep)
|
binding.deps.push(dep)
|
||||||
})
|
})
|
||||||
|
parseContextDependency(binding)
|
||||||
binding.value.get({
|
binding.value.get({
|
||||||
scope: createDummyScope(binding.value.get),
|
scope: createDummyScope(binding),
|
||||||
el: dummyEl
|
el: dummyEl
|
||||||
})
|
})
|
||||||
observer.off('get')
|
observer.off('get')
|
||||||
|
|
@ -37,7 +38,7 @@ function filterDeps (binding) {
|
||||||
dep = binding.deps[i]
|
dep = binding.deps[i]
|
||||||
if (!dep.deps.length) {
|
if (!dep.deps.length) {
|
||||||
config.log(' └─' + dep.key)
|
config.log(' └─' + dep.key)
|
||||||
dep.subs.push.apply(dep.subs, binding.instances)
|
dep.subs.push(binding)
|
||||||
} else {
|
} else {
|
||||||
binding.deps.splice(i, 1)
|
binding.deps.splice(i, 1)
|
||||||
}
|
}
|
||||||
|
|
@ -53,18 +54,15 @@ function filterDeps (binding) {
|
||||||
* the user expects the target scope to possess. They are all assigned
|
* the user expects the target scope to possess. They are all assigned
|
||||||
* a noop function so they can be invoked with no real harm.
|
* a noop function so they can be invoked with no real harm.
|
||||||
*/
|
*/
|
||||||
function createDummyScope (fn) {
|
function createDummyScope (binding) {
|
||||||
var scope = {},
|
var scope = {},
|
||||||
str = fn.toString()
|
deps = binding.contextDeps
|
||||||
var args = str.match(ARGS_RE)
|
if (!deps) return scope
|
||||||
if (!args) return scope
|
var i = binding.contextDeps.length,
|
||||||
var argRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
|
j, level, key, path
|
||||||
matches = str.match(argRE)
|
|
||||||
if (!matches) return scope
|
|
||||||
var i = matches.length, j, path, key, level
|
|
||||||
while (i--) {
|
while (i--) {
|
||||||
level = scope
|
level = scope
|
||||||
path = matches[i].slice(args[1].length + 7).split('.')
|
path = deps[i].split('.')
|
||||||
j = 0
|
j = 0
|
||||||
while (j < path.length) {
|
while (j < path.length) {
|
||||||
key = path[j]
|
key = path[j]
|
||||||
|
|
@ -76,6 +74,22 @@ function createDummyScope (fn) {
|
||||||
return scope
|
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 = {
|
module.exports = {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
28
src/seed.js
28
src/seed.js
|
|
@ -24,7 +24,10 @@ function Seed (el, options) {
|
||||||
this.el = el
|
this.el = el
|
||||||
el.seed = this
|
el.seed = this
|
||||||
this._bindings = {}
|
this._bindings = {}
|
||||||
|
// list of computed properties that need to parse dependencies for
|
||||||
this._computed = []
|
this._computed = []
|
||||||
|
// list of bindings that has dynamic context dependencies
|
||||||
|
this._contextBindings = []
|
||||||
|
|
||||||
// copy options
|
// copy options
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
@ -82,6 +85,9 @@ function Seed (el, options) {
|
||||||
// extract dependencies for computed properties
|
// extract dependencies for computed properties
|
||||||
if (this._computed.length) depsParser.parse(this._computed)
|
if (this._computed.length) depsParser.parse(this._computed)
|
||||||
delete this._computed
|
delete this._computed
|
||||||
|
|
||||||
|
if (this._contextBindings.length) this._bindContexts(this._contextBindings)
|
||||||
|
delete this._contextBindings
|
||||||
}
|
}
|
||||||
|
|
||||||
// for better compression
|
// for better compression
|
||||||
|
|
@ -193,7 +199,10 @@ SeedProto._bind = function (directive) {
|
||||||
seed = traceOwnerSeed(directive, seed)
|
seed = traceOwnerSeed(directive, seed)
|
||||||
var binding = seed._bindings[key] || seed._createBinding(key)
|
var binding = seed._bindings[key] || seed._createBinding(key)
|
||||||
|
|
||||||
// add directive to this binding
|
if (binding.contextDeps) {
|
||||||
|
console.log(1)
|
||||||
|
}
|
||||||
|
|
||||||
binding.instances.push(directive)
|
binding.instances.push(directive)
|
||||||
directive.binding = binding
|
directive.binding = binding
|
||||||
|
|
||||||
|
|
@ -220,6 +229,23 @@ SeedProto._createBinding = function (key) {
|
||||||
return binding
|
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
|
* Call unbind() of all directive instances
|
||||||
* to remove event listeners, destroy child seeds, etc.
|
* to remove event listeners, destroy child seeds, etc.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue