mirror of https://github.com/vuejs/vue.git
auto parse dependency for computed properties!!!!!
This commit is contained in:
parent
67ff344810
commit
7a0172d60b
6
TODO.md
6
TODO.md
|
|
@ -1,11 +1,7 @@
|
|||
- use descriptor for computed properties
|
||||
- getter setter should emit events
|
||||
- auto dependency extraction for computed properties (evaluate, record triggered getters)
|
||||
|
||||
- parse textNodes?
|
||||
- more directives / filters
|
||||
- sd-if
|
||||
- sd-with
|
||||
- sd-visible
|
||||
- sd-style
|
||||
- sd-style="transform:transform < x y z rotate"
|
||||
- nested properties in scope (kinda hard, maybe later)
|
||||
|
|
@ -23,7 +23,7 @@ Seed.controller('Todos', function (scope) {
|
|||
}}
|
||||
|
||||
scope.completed = {get: function () {
|
||||
return scope.total() - scope.remaining
|
||||
return scope.total - scope.remaining
|
||||
}}
|
||||
|
||||
scope.itemLabel = {get: function () {
|
||||
|
|
@ -66,7 +66,7 @@ Seed.controller('Todos', function (scope) {
|
|||
scope.todos.forEach(function (todo) {
|
||||
todo.done = e.el.checked
|
||||
})
|
||||
scope.remaining = e.el.checked ? 0 : scope.total()
|
||||
scope.remaining = e.el.checked ? 0 : scope.total
|
||||
}
|
||||
|
||||
scope.removeCompleted = function () {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
>
|
||||
</header>
|
||||
|
||||
<section id="main" sd-show="total < todos">
|
||||
<section id="main" sd-show="total">
|
||||
<input
|
||||
id="toggle-all"
|
||||
type="checkbox"
|
||||
|
|
@ -58,10 +58,10 @@
|
|||
</section>
|
||||
|
||||
<!-- footer controls -->
|
||||
<footer id="footer" sd-show="total < todos">
|
||||
<footer id="footer" sd-show="total">
|
||||
<span id="todo-count">
|
||||
<strong sd-text="remaining"></strong>
|
||||
<span sd-text="itemLabel < remaining"></span>
|
||||
<span sd-text="itemLabel"></span>
|
||||
left
|
||||
</span>
|
||||
<ul id="filters">
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
<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>)
|
||||
Remove Completed (<span sd-text="completed"></span>)
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -91,17 +91,13 @@ function Directive (directiveName, expression) {
|
|||
this.filters = filterExps
|
||||
? filterExps.map(parseFilter)
|
||||
: null
|
||||
|
||||
var depExp = expression.match(DEPS_RE)
|
||||
this.deps = depExp
|
||||
? depExp[0].slice(1).trim().split(/\s+/).map(parseKey)
|
||||
: null
|
||||
}
|
||||
|
||||
// called when a dependency has changed
|
||||
Directive.prototype.refresh = function () {
|
||||
if (this.value) {
|
||||
var value = this.value.call(this.seed.scope)
|
||||
var getter = this.value
|
||||
if (getter && typeof getter === 'function') {
|
||||
var value = getter.call(this.seed.scope)
|
||||
if (this.inverse) value = !value
|
||||
this._update(
|
||||
this.filters
|
||||
|
|
@ -109,9 +105,7 @@ Directive.prototype.refresh = function () {
|
|||
: value
|
||||
)
|
||||
}
|
||||
if (this.binding.refreshDependents) {
|
||||
this.binding.refreshDependents()
|
||||
}
|
||||
this.binding.emitChange()
|
||||
}
|
||||
|
||||
// called when a new value is set
|
||||
|
|
@ -128,7 +122,9 @@ Directive.prototype.update = function (value) {
|
|||
? this.applyFilters(value)
|
||||
: value
|
||||
)
|
||||
if (this.deps) this.refresh()
|
||||
if (this.binding.isComputed) {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
Directive.prototype.applyFilters = function (value) {
|
||||
|
|
|
|||
138
src/seed.js
138
src/seed.js
|
|
@ -7,6 +7,13 @@ var slice = Array.prototype.slice,
|
|||
ctrlAttr = config.prefix + '-controller',
|
||||
eachAttr = config.prefix + '-each'
|
||||
|
||||
var depsObserver = new Emitter(),
|
||||
parsingDeps = false
|
||||
|
||||
/*
|
||||
* The main ViewModel class
|
||||
* scans a node and parse it to populate data bindings
|
||||
*/
|
||||
function Seed (el, options) {
|
||||
|
||||
if (typeof el === 'string') {
|
||||
|
|
@ -16,6 +23,7 @@ function Seed (el, options) {
|
|||
this.el = el
|
||||
el.seed = this
|
||||
this._bindings = {}
|
||||
this._computed = []
|
||||
|
||||
// copy options
|
||||
options = options || {}
|
||||
|
|
@ -37,6 +45,7 @@ function Seed (el, options) {
|
|||
scope = this.scope = scope.$dump()
|
||||
}
|
||||
|
||||
// expose some useful stuff on the scope
|
||||
scope.$seed = this
|
||||
scope.$destroy = this._destroy.bind(this)
|
||||
scope.$dump = this._dump.bind(this)
|
||||
|
|
@ -44,13 +53,14 @@ function Seed (el, options) {
|
|||
scope.$parent = options.parentSeed && options.parentSeed.scope
|
||||
scope.$refresh = this._refreshBinding.bind(this)
|
||||
|
||||
// update bindings when a property is set
|
||||
// add event listener to update corresponding binding
|
||||
// when a property is set
|
||||
this.on('set', this._updateBinding.bind(this))
|
||||
|
||||
// revursively compile nodes for directives
|
||||
// now parse the DOM
|
||||
this._compileNode(el, true)
|
||||
|
||||
// if has controller, apply it
|
||||
// if has controller function, apply it
|
||||
var ctrlID = el.getAttribute(ctrlAttr)
|
||||
if (ctrlID) {
|
||||
el.removeAttribute(ctrlAttr)
|
||||
|
|
@ -61,8 +71,17 @@ function Seed (el, options) {
|
|||
console.warn('controller ' + ctrlID + ' is not defined.')
|
||||
}
|
||||
}
|
||||
|
||||
// extract dependencies for computed properties
|
||||
parsingDeps = true
|
||||
this._computed.forEach(this._parseDeps.bind(this))
|
||||
delete this._computed
|
||||
parsingDeps = false
|
||||
}
|
||||
|
||||
/*
|
||||
* Compile a node (recursive)
|
||||
*/
|
||||
Seed.prototype._compileNode = function (node, root) {
|
||||
var seed = this
|
||||
|
||||
|
|
@ -77,9 +96,10 @@ Seed.prototype._compileNode = function (node, root) {
|
|||
|
||||
if (eachExp) { // each block
|
||||
|
||||
var binding = DirectiveParser.parse(eachAttr, eachExp)
|
||||
if (binding) {
|
||||
seed._bind(node, binding)
|
||||
var directive = DirectiveParser.parse(eachAttr, eachExp)
|
||||
if (directive) {
|
||||
directive.el = node
|
||||
seed._bind(directive)
|
||||
}
|
||||
|
||||
} else if (ctrlExp && !root) { // nested controllers
|
||||
|
|
@ -103,7 +123,8 @@ Seed.prototype._compileNode = function (node, root) {
|
|||
var directive = DirectiveParser.parse(attr.name, exp)
|
||||
if (directive) {
|
||||
valid = true
|
||||
seed._bind(node, directive)
|
||||
directive.el = node
|
||||
seed._bind(directive)
|
||||
}
|
||||
})
|
||||
if (valid) node.removeAttribute(attr.name)
|
||||
|
|
@ -120,13 +141,18 @@ Seed.prototype._compileNode = function (node, root) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compile a text node
|
||||
*/
|
||||
Seed.prototype._compileTextNode = function (node) {
|
||||
return TextNodeParser.parse(node)
|
||||
}
|
||||
|
||||
Seed.prototype._bind = function (node, directive) {
|
||||
/*
|
||||
* Add a directive instance to the correct binding & scope
|
||||
*/
|
||||
Seed.prototype._bind = function (directive) {
|
||||
|
||||
directive.el = node
|
||||
directive.seed = this
|
||||
|
||||
var key = directive.key,
|
||||
|
|
@ -159,43 +185,24 @@ Seed.prototype._bind = function (node, directive) {
|
|||
// set initial value
|
||||
directive.update(binding.value)
|
||||
|
||||
// computed properties
|
||||
if (directive.deps) {
|
||||
directive.deps.forEach(function (dep) {
|
||||
var depScope = determinScope(dep, scope),
|
||||
depBinding =
|
||||
depScope._bindings[dep.key] ||
|
||||
depScope._createBinding(dep.key)
|
||||
if (!depBinding.dependents) {
|
||||
depBinding.dependents = []
|
||||
depBinding.refreshDependents = function () {
|
||||
depBinding.dependents.forEach(function (dept) {
|
||||
dept.refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
depBinding.dependents.push(directive)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Seed.prototype._createBinding = function (key) {
|
||||
|
||||
var binding = {
|
||||
value: this.scope[key],
|
||||
changed: false,
|
||||
instances: []
|
||||
}
|
||||
|
||||
var binding = new Binding(this.scope[key])
|
||||
this._bindings[key] = binding
|
||||
|
||||
// bind accessor triggers to scope
|
||||
var seed = this
|
||||
Object.defineProperty(this.scope, key, {
|
||||
get: function () {
|
||||
if (parsingDeps) {
|
||||
depsObserver.emit('get', binding)
|
||||
}
|
||||
seed.emit('get', key)
|
||||
return binding.value
|
||||
return binding.isComputed
|
||||
? binding.value()
|
||||
: binding.value
|
||||
},
|
||||
set: function (value) {
|
||||
if (value === binding.value) return
|
||||
|
|
@ -209,11 +216,13 @@ Seed.prototype._createBinding = function (key) {
|
|||
Seed.prototype._updateBinding = function (key, value) {
|
||||
|
||||
var binding = this._bindings[key],
|
||||
type = typeOf(value)
|
||||
type = binding.type = typeOf(value)
|
||||
|
||||
// preprocess the value depending on its type
|
||||
if (type === 'Object') {
|
||||
if (value.get) { // computed property
|
||||
type = 'Computed'
|
||||
this._computed.push(binding)
|
||||
binding.isComputed = true
|
||||
value = value.get
|
||||
} else { // normal object
|
||||
// TODO watchObject
|
||||
|
|
@ -221,15 +230,11 @@ Seed.prototype._updateBinding = function (key, value) {
|
|||
} else if (type === 'Array') {
|
||||
watchArray(value)
|
||||
value.on('mutate', function () {
|
||||
if (binding.dependents) {
|
||||
binding.refreshDependents()
|
||||
}
|
||||
binding.emitChange()
|
||||
})
|
||||
}
|
||||
|
||||
binding.type = type
|
||||
binding.value = value
|
||||
binding.changed = true
|
||||
|
||||
// update all instances
|
||||
binding.instances.forEach(function (instance) {
|
||||
|
|
@ -237,10 +242,7 @@ Seed.prototype._updateBinding = function (key, value) {
|
|||
})
|
||||
|
||||
// notify dependents to refresh themselves
|
||||
if (binding.dependents) {
|
||||
binding.refreshDependents()
|
||||
}
|
||||
|
||||
binding.emitChange()
|
||||
}
|
||||
|
||||
Seed.prototype._refreshBinding = function (key) {
|
||||
|
|
@ -250,6 +252,17 @@ Seed.prototype._refreshBinding = function (key) {
|
|||
})
|
||||
}
|
||||
|
||||
Seed.prototype._parseDeps = function (binding) {
|
||||
depsObserver.on('get', function (dep) {
|
||||
if (!dep.dependents) {
|
||||
dep.dependents = []
|
||||
}
|
||||
dep.dependents.push.apply(dep.dependents, binding.instances)
|
||||
})
|
||||
binding.value()
|
||||
depsObserver.off('get')
|
||||
}
|
||||
|
||||
Seed.prototype._unbind = function () {
|
||||
var unbind = function (instance) {
|
||||
if (instance.unbind) {
|
||||
|
|
@ -281,7 +294,7 @@ Seed.prototype._dump = function () {
|
|||
if (!val) continue
|
||||
if (Array.isArray(val)) {
|
||||
dump[key] = val.map(subDump)
|
||||
} else {
|
||||
} else if (typeof val !== 'function') {
|
||||
dump[key] = this._bindings[key].value
|
||||
}
|
||||
}
|
||||
|
|
@ -289,10 +302,27 @@ Seed.prototype._dump = function () {
|
|||
return dump
|
||||
}
|
||||
|
||||
/*
|
||||
* Binding class
|
||||
*/
|
||||
function Binding (value) {
|
||||
this.value = value
|
||||
this.instances = []
|
||||
this.dependents = []
|
||||
}
|
||||
|
||||
Binding.prototype.emitChange = function () {
|
||||
this.dependents.forEach(function (dept) {
|
||||
dept.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
// Helpers --------------------------------------------------------------------
|
||||
|
||||
// determine which scope a key belongs to
|
||||
// based on nesting symbols
|
||||
/*
|
||||
* determinScope()
|
||||
* determine which scope a key belongs to based on nesting symbols
|
||||
*/
|
||||
function determinScope (key, scope) {
|
||||
if (key.nesting) {
|
||||
var levels = key.nesting
|
||||
|
|
@ -307,13 +337,19 @@ function determinScope (key, scope) {
|
|||
return scope
|
||||
}
|
||||
|
||||
// get accurate type of an object
|
||||
/*
|
||||
* typeOf()
|
||||
* get accurate type of an object
|
||||
*/
|
||||
var OtoString = Object.prototype.toString
|
||||
function typeOf (obj) {
|
||||
return OtoString.call(obj).slice(8, -1)
|
||||
}
|
||||
|
||||
// augment an Array so that it emit events when mutated
|
||||
/*
|
||||
* watchArray()
|
||||
* augment an Array so that it emit events when mutated
|
||||
*/
|
||||
var arrayMutators = ['push','pop','shift','unshift','splice','sort','reverse']
|
||||
var arrayAugmentations = {
|
||||
remove: function (scope) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue