mirror of https://github.com/vuejs/vue.git
rename internal, optimize memory usage for GC
This commit is contained in:
parent
aff965f353
commit
d78df31017
|
|
@ -6,8 +6,8 @@
|
|||
"src/main.js",
|
||||
"src/config.js",
|
||||
"src/utils.js",
|
||||
"src/seed.js",
|
||||
"src/scope.js",
|
||||
"src/compiler.js",
|
||||
"src/viewmodel.js",
|
||||
"src/binding.js",
|
||||
"src/directive-parser.js",
|
||||
"src/text-parser.js",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Seed.controller('todos', {
|
|||
|
||||
// initializer, reserved
|
||||
init: function () {
|
||||
window.app = this
|
||||
// listen for hashtag change
|
||||
this.updateFilter()
|
||||
this.$on('filterchange', this.updateFilter.bind(this))
|
||||
|
|
@ -29,9 +30,9 @@ Seed.controller('todos', {
|
|||
return this.total - this.remaining
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target scope
|
||||
// dynamic context computed property using info from target viewmodel
|
||||
todoFiltered: {get: function (ctx) {
|
||||
return filters[this.filter](ctx.scope)
|
||||
return filters[this.filter]({ completed: ctx.vm.completed })
|
||||
}},
|
||||
|
||||
// dynamic context computed property using info from target element
|
||||
|
|
@ -45,10 +46,11 @@ Seed.controller('todos', {
|
|||
return this.remaining === 0
|
||||
},
|
||||
set: function (value) {
|
||||
this.remaining = value ? 0 : this.total
|
||||
this.todos.forEach(function (todo) {
|
||||
todo.completed = value
|
||||
})
|
||||
this.remaining = value ? 0 : this.total
|
||||
todoStorage.save(this.todos)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -64,32 +66,32 @@ Seed.controller('todos', {
|
|||
},
|
||||
|
||||
removeTodo: function (e) {
|
||||
this.todos.remove(e.scope)
|
||||
this.remaining -= e.scope.completed ? 0 : 1
|
||||
this.todos.remove(e.vm)
|
||||
this.remaining -= e.vm.completed ? 0 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
toggleTodo: function (e) {
|
||||
this.remaining += e.scope.completed ? -1 : 1
|
||||
this.remaining += e.vm.completed ? -1 : 1
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
editTodo: function (e) {
|
||||
this.beforeEditCache = e.scope.title
|
||||
e.scope.editing = true
|
||||
this.beforeEditCache = e.vm.title
|
||||
e.vm.editing = true
|
||||
},
|
||||
|
||||
doneEdit: function (e) {
|
||||
if (!e.scope.editing) return
|
||||
e.scope.editing = false
|
||||
e.scope.title = e.scope.title.trim()
|
||||
if (!e.scope.title) this.removeTodo(e)
|
||||
if (!e.vm.editing) return
|
||||
e.vm.editing = false
|
||||
e.vm.title = e.vm.title.trim()
|
||||
if (!e.vm.title) this.removeTodo(e)
|
||||
todoStorage.save(this.todos)
|
||||
},
|
||||
|
||||
cancelEdit: function (e) {
|
||||
e.scope.editing = false
|
||||
e.scope.title = this.beforeEditCache
|
||||
e.vm.editing = false
|
||||
e.vm.title = this.beforeEditCache
|
||||
},
|
||||
|
||||
removeCompleted: function () {
|
||||
|
|
|
|||
112
src/binding.js
112
src/binding.js
|
|
@ -5,17 +5,17 @@ var utils = require('./utils'),
|
|||
/*
|
||||
* Binding class.
|
||||
*
|
||||
* each property on the scope has one corresponding Binding object
|
||||
* each property on the viewmodel has one corresponding Binding object
|
||||
* which has multiple directive instances on the DOM
|
||||
* and multiple computed property dependents
|
||||
*/
|
||||
function Binding (seed, key) {
|
||||
this.seed = seed
|
||||
this.scope = seed.scope
|
||||
function Binding (compiler, key) {
|
||||
this.compiler = compiler
|
||||
this.vm = compiler.vm
|
||||
this.key = key
|
||||
var path = key.split('.')
|
||||
this.inspect(utils.getNestedValue(seed.scope, path))
|
||||
this.def(seed.scope, path)
|
||||
this.inspect(utils.getNestedValue(compiler.vm, path))
|
||||
this.def(compiler.vm, path)
|
||||
this.instances = []
|
||||
this.subs = []
|
||||
this.deps = []
|
||||
|
|
@ -27,85 +27,82 @@ var BindingProto = Binding.prototype
|
|||
* Pre-process a passed in value based on its type
|
||||
*/
|
||||
BindingProto.inspect = function (value) {
|
||||
var type = utils.typeOf(value),
|
||||
self = this
|
||||
var type = utils.typeOf(value)
|
||||
// preprocess the value depending on its type
|
||||
if (type === 'Object') {
|
||||
if (value.get) {
|
||||
var l = Object.keys(value).length
|
||||
if (l === 1 || (l === 2 && value.set)) {
|
||||
self.isComputed = true // computed property
|
||||
value.get = value.get.bind(self.scope)
|
||||
if (value.set) value.set = value.set.bind(self.scope)
|
||||
this.isComputed = true // computed property
|
||||
this.rawGet = value.get
|
||||
value.get = value.get.bind(this.vm)
|
||||
if (value.set) value.set = value.set.bind(this.vm)
|
||||
}
|
||||
}
|
||||
} else if (type === 'Array') {
|
||||
value = utils.dump(value)
|
||||
utils.watchArray(value)
|
||||
value.on('mutate', function () {
|
||||
self.pub()
|
||||
})
|
||||
value.on('mutate', this.pub.bind(this))
|
||||
}
|
||||
self.value = value
|
||||
this.value = value
|
||||
}
|
||||
|
||||
/*
|
||||
* Define getter/setter for this binding on scope
|
||||
* Define getter/setter for this binding on viewmodel
|
||||
* recursive for nested objects
|
||||
*/
|
||||
BindingProto.def = function (scope, path) {
|
||||
var self = this,
|
||||
seed = self.seed,
|
||||
key = path[0]
|
||||
BindingProto.def = function (viewmodel, path) {
|
||||
var key = path[0]
|
||||
if (path.length === 1) {
|
||||
// here we are! at the end of the path!
|
||||
// define the real value accessors.
|
||||
def(scope, key, {
|
||||
get: function () {
|
||||
def(viewmodel, key, {
|
||||
get: (function () {
|
||||
if (observer.isObserving) {
|
||||
observer.emit('get', self)
|
||||
observer.emit('get', this)
|
||||
}
|
||||
return self.isComputed
|
||||
? self.value.get({
|
||||
el: seed.el,
|
||||
scope: seed.scope
|
||||
return this.isComputed
|
||||
? this.value.get({
|
||||
el: this.compiler.el,
|
||||
vm: this.compiler.vm
|
||||
})
|
||||
: self.value
|
||||
},
|
||||
set: function (value) {
|
||||
if (self.isComputed) {
|
||||
: this.value
|
||||
}).bind(this),
|
||||
set: (function (value) {
|
||||
if (this.isComputed) {
|
||||
// computed properties cannot be redefined
|
||||
// no need to call binding.update() here,
|
||||
// as dependency extraction has taken care of that
|
||||
if (self.value.set) {
|
||||
self.value.set(value)
|
||||
}
|
||||
} else if (value !== self.value) {
|
||||
self.update(value)
|
||||
if (this.value.set) {
|
||||
this.value.set(value)
|
||||
}
|
||||
} else if (value !== this.value) {
|
||||
this.update(value)
|
||||
}
|
||||
}).bind(this)
|
||||
})
|
||||
} else {
|
||||
// we are not there yet!!!
|
||||
// create an intermediate subscope
|
||||
// create an intermediate object
|
||||
// which also has its own getter/setters
|
||||
var subScope = scope[key]
|
||||
if (!subScope) {
|
||||
subScope = {}
|
||||
def(scope, key, {
|
||||
get: function () {
|
||||
return subScope
|
||||
},
|
||||
set: function (value) {
|
||||
// when the subScope is given a new value,
|
||||
var nestedObject = viewmodel[key]
|
||||
if (!nestedObject) {
|
||||
nestedObject = {}
|
||||
def(viewmodel, key, {
|
||||
get: (function () {
|
||||
return this
|
||||
}).bind(nestedObject),
|
||||
set: (function (value) {
|
||||
// when the nestedObject is given a new value,
|
||||
// copy everything over to trigger the setters
|
||||
for (var prop in value) {
|
||||
subScope[prop] = value[prop]
|
||||
}
|
||||
this[prop] = value[prop]
|
||||
}
|
||||
}).bind(nestedObject)
|
||||
})
|
||||
}
|
||||
// recurse
|
||||
this.def(subScope, path.slice(1))
|
||||
this.def(nestedObject, path.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +113,7 @@ BindingProto.update = function (value) {
|
|||
this.inspect(value)
|
||||
var i = this.instances.length
|
||||
while (i--) {
|
||||
this.instances[i].update(value)
|
||||
this.instances[i].update(this.value)
|
||||
}
|
||||
this.pub()
|
||||
}
|
||||
|
|
@ -132,6 +129,21 @@ BindingProto.refresh = function () {
|
|||
}
|
||||
}
|
||||
|
||||
BindingProto.unbind = function () {
|
||||
var i = this.instances.length
|
||||
while (i--) {
|
||||
this.instances[i].unbind()
|
||||
}
|
||||
i = this.deps.length
|
||||
var subs
|
||||
while (i--) {
|
||||
subs = this.deps[i].subs
|
||||
subs.splice(subs.indexOf(this), 1)
|
||||
}
|
||||
if (Array.isArray(this.value)) this.value.off('mutate')
|
||||
this.vm = this.compiler = this.pubs = this.subs = this.instances = null
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify computed properties that depend on this binding
|
||||
* to update themselves
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
var config = require('./config'),
|
||||
Scope = require('./scope'),
|
||||
ViewModel = require('./viewmodel'),
|
||||
Binding = require('./binding'),
|
||||
DirectiveParser = require('./directive-parser'),
|
||||
TextParser = require('./text-parser'),
|
||||
|
|
@ -11,26 +11,26 @@ var slice = Array.prototype.slice,
|
|||
eachAttr = config.prefix + '-each'
|
||||
|
||||
/*
|
||||
* The main ViewModel class
|
||||
* scans a node and parse it to populate data bindings
|
||||
* The DOM compiler
|
||||
* scans a DOM node and compile bindings for a ViewModel
|
||||
*/
|
||||
function Seed (el, options) {
|
||||
|
||||
config.log('\ncreated new Seed instance.\n')
|
||||
function Compiler (el, options) {
|
||||
|
||||
config.log('\ncreated new Compiler instance.\n')
|
||||
if (typeof el === 'string') {
|
||||
el = document.querySelector(el)
|
||||
}
|
||||
|
||||
this.el = el
|
||||
el.seed = this
|
||||
this._bindings = {}
|
||||
this._watchers = {}
|
||||
this._listeners = []
|
||||
el.compiler = this
|
||||
this.bindings = {}
|
||||
this.directives = []
|
||||
this.watchers = {}
|
||||
this.listeners = []
|
||||
// list of computed properties that need to parse dependencies for
|
||||
this._computed = []
|
||||
this.computed = []
|
||||
// list of bindings that has dynamic context dependencies
|
||||
this._contextBindings = []
|
||||
this.contextBindings = []
|
||||
|
||||
// copy options
|
||||
options = options || {}
|
||||
|
|
@ -48,71 +48,71 @@ function Seed (el, options) {
|
|||
data = data || {}
|
||||
el.removeAttribute(dataAttr)
|
||||
|
||||
// if the passed in data is the scope of a Seed instance,
|
||||
// if the passed in data is the viewmodel of a Compiler instance,
|
||||
// make a copy from it
|
||||
if (data.$seed instanceof Seed) {
|
||||
if (data instanceof ViewModel) {
|
||||
data = data.$dump()
|
||||
}
|
||||
|
||||
// check if there is a controller associated with this seed
|
||||
// check if there is a controller associated with this compiler
|
||||
var ctrlID = el.getAttribute(ctrlAttr), controller
|
||||
if (ctrlID) {
|
||||
el.removeAttribute(ctrlAttr)
|
||||
controller = config.controllers[ctrlID]
|
||||
if (controller) {
|
||||
this._controller = controller
|
||||
this.controller = controller
|
||||
} else {
|
||||
config.warn('controller "' + ctrlID + '" is not defined.')
|
||||
}
|
||||
}
|
||||
|
||||
// create the scope object
|
||||
// if the controller has an extended scope contructor, use it;
|
||||
// otherwise, use the original scope constructor.
|
||||
var ScopeConstructor = (controller && controller.ExtendedScope) || Scope,
|
||||
scope = this.scope = new ScopeConstructor(this, options)
|
||||
// create the viewmodel object
|
||||
// if the controller has an extended viewmodel contructor, use it;
|
||||
// otherwise, use the original viewmodel constructor.
|
||||
var VMCtor = (controller && controller.ExtendedVM) || ViewModel,
|
||||
viewmodel = this.vm = new VMCtor(this, options)
|
||||
|
||||
// copy data
|
||||
for (var key in data) {
|
||||
scope[key] = data[key]
|
||||
viewmodel[key] = data[key]
|
||||
}
|
||||
|
||||
// apply controller initialize function
|
||||
if (controller && controller.init) {
|
||||
controller.init.call(scope)
|
||||
controller.init.call(viewmodel)
|
||||
}
|
||||
|
||||
// now parse the DOM
|
||||
this._compileNode(el, true)
|
||||
this.compileNode(el, true)
|
||||
|
||||
// for anything in scope but not binded in DOM, create bindings for them
|
||||
for (key in scope) {
|
||||
if (key.charAt(0) !== '$' && !this._bindings[key]) {
|
||||
this._createBinding(key)
|
||||
// for anything in viewmodel but not binded in DOM, create bindings for them
|
||||
for (key in viewmodel) {
|
||||
if (key.charAt(0) !== '$' && !this.bindings[key]) {
|
||||
this.createBinding(key)
|
||||
}
|
||||
}
|
||||
|
||||
// extract dependencies for computed properties
|
||||
if (this._computed.length) depsParser.parse(this._computed)
|
||||
delete this._computed
|
||||
if (this.computed.length) depsParser.parse(this.computed)
|
||||
this.computed = null
|
||||
|
||||
// extract dependencies for computed properties with dynamic context
|
||||
if (this._contextBindings.length) this._bindContexts(this._contextBindings)
|
||||
delete this._contextBindings
|
||||
if (this.contextBindings.length) this.bindContexts(this.contextBindings)
|
||||
this.contextBindings = null
|
||||
}
|
||||
|
||||
// for better compression
|
||||
var SeedProto = Seed.prototype
|
||||
var CompilerProto = Compiler.prototype
|
||||
|
||||
/*
|
||||
* Compile a DOM node (recursive)
|
||||
*/
|
||||
SeedProto._compileNode = function (node, root) {
|
||||
var seed = this
|
||||
CompilerProto.compileNode = function (node, root) {
|
||||
var compiler = this
|
||||
|
||||
if (node.nodeType === 3) { // text node
|
||||
|
||||
seed._compileTextNode(node)
|
||||
compiler.compileTextNode(node)
|
||||
|
||||
} else if (node.nodeType === 1) {
|
||||
|
||||
|
|
@ -125,14 +125,14 @@ SeedProto._compileNode = function (node, root) {
|
|||
directive = DirectiveParser.parse(eachAttr, eachExp)
|
||||
if (directive) {
|
||||
directive.el = node
|
||||
seed._bind(directive)
|
||||
compiler.bindDirective(directive)
|
||||
}
|
||||
|
||||
} else if (ctrlExp && !root) { // nested controllers
|
||||
|
||||
new Seed(node, {
|
||||
new Compiler(node, {
|
||||
child: true,
|
||||
parentSeed: seed
|
||||
parentCompiler: compiler
|
||||
})
|
||||
|
||||
} else { // normal node
|
||||
|
|
@ -153,7 +153,7 @@ SeedProto._compileNode = function (node, root) {
|
|||
if (directive) {
|
||||
valid = true
|
||||
directive.el = node
|
||||
seed._bind(directive)
|
||||
compiler.bindDirective(directive)
|
||||
}
|
||||
}
|
||||
if (valid) node.removeAttribute(attr.name)
|
||||
|
|
@ -162,7 +162,7 @@ SeedProto._compileNode = function (node, root) {
|
|||
|
||||
// recursively compile childNodes
|
||||
if (node.childNodes.length) {
|
||||
slice.call(node.childNodes).forEach(seed._compileNode, seed)
|
||||
slice.call(node.childNodes).forEach(compiler.compileNode, compiler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,10 +171,10 @@ SeedProto._compileNode = function (node, root) {
|
|||
/*
|
||||
* Compile a text node
|
||||
*/
|
||||
SeedProto._compileTextNode = function (node) {
|
||||
CompilerProto.compileTextNode = function (node) {
|
||||
var tokens = TextParser.parse(node)
|
||||
if (!tokens) return
|
||||
var seed = this,
|
||||
var compiler = this,
|
||||
dirname = config.prefix + '-text',
|
||||
el, token, directive
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
|
|
@ -184,7 +184,7 @@ SeedProto._compileTextNode = function (node) {
|
|||
directive = DirectiveParser.parse(dirname, token.key)
|
||||
if (directive) {
|
||||
directive.el = el
|
||||
seed._bind(directive)
|
||||
compiler.bindDirective(directive)
|
||||
}
|
||||
} else {
|
||||
el.nodeValue = token
|
||||
|
|
@ -195,29 +195,56 @@ SeedProto._compileTextNode = function (node) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Add a directive instance to the correct binding & scope
|
||||
* Create binding and attach getter/setter for a key to the viewmodel object
|
||||
*/
|
||||
SeedProto._bind = function (directive) {
|
||||
CompilerProto.createBinding = function (key) {
|
||||
config.log(' created binding: ' + key)
|
||||
var binding = new Binding(this, key)
|
||||
this.bindings[key] = binding
|
||||
if (binding.isComputed) this.computed.push(binding)
|
||||
return binding
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a directive instance to the correct binding & viewmodel
|
||||
*/
|
||||
CompilerProto.bindDirective = function (directive) {
|
||||
|
||||
this.directives.push(directive)
|
||||
directive.compiler = this
|
||||
directive.vm = this.vm
|
||||
|
||||
var key = directive.key,
|
||||
seed = directive.seed = this
|
||||
compiler = this
|
||||
|
||||
// deal with each block
|
||||
if (this.each) {
|
||||
if (key.indexOf(this.eachPrefix) === 0) {
|
||||
key = directive.key = key.replace(this.eachPrefix, '')
|
||||
} else {
|
||||
seed = this.parentSeed
|
||||
compiler = this.parentCompiler
|
||||
}
|
||||
}
|
||||
|
||||
// deal with nesting
|
||||
seed = traceOwnerSeed(directive, seed)
|
||||
var binding = seed._bindings[key] || seed._createBinding(key)
|
||||
compiler = traceOwnerCompiler(directive, compiler)
|
||||
var binding = compiler.bindings[key] || compiler.createBinding(key)
|
||||
|
||||
binding.instances.push(directive)
|
||||
directive.binding = binding
|
||||
|
||||
// for newly inserted sub-VMs (each items), need to bind deps
|
||||
// because they didn't get processed when the parent compiler
|
||||
// was binding dependencies.
|
||||
var i, dep
|
||||
if (binding.contextDeps) {
|
||||
i = binding.contextDeps.length
|
||||
while (i--) {
|
||||
dep = this.bindings[binding.contextDeps[i]]
|
||||
dep.subs.push(directive)
|
||||
}
|
||||
}
|
||||
|
||||
// invoke bind hook if exists
|
||||
if (directive.bind) {
|
||||
directive.bind(binding.value)
|
||||
|
|
@ -230,81 +257,74 @@ SeedProto._bind = function (directive) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create binding and attach getter/setter for a key to the scope object
|
||||
*/
|
||||
SeedProto._createBinding = function (key) {
|
||||
config.log(' created binding: ' + key)
|
||||
var binding = new Binding(this, key)
|
||||
this._bindings[key] = binding
|
||||
if (binding.isComputed) this._computed.push(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
|
||||
CompilerProto.bindContexts = function (bindings) {
|
||||
var i = bindings.length, j, k, binding, depKey, dep, ins
|
||||
while (i--) {
|
||||
binding = bindings[i]
|
||||
j = binding.contextDeps.length
|
||||
while (j--) {
|
||||
depKey = binding.contextDeps[j]
|
||||
dep = this._bindings[depKey]
|
||||
dep.subs.push(binding)
|
||||
k = binding.instances.length
|
||||
while (k--) {
|
||||
ins = binding.instances[k]
|
||||
dep = ins.compiler.bindings[depKey]
|
||||
dep.subs.push(ins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Call unbind() of all directive instances
|
||||
* to remove event listeners, destroy child seeds, etc.
|
||||
*/
|
||||
SeedProto._unbind = function () {
|
||||
var i, ins, key, listener
|
||||
// unbind all bindings
|
||||
for (key in this._bindings) {
|
||||
ins = this._bindings[key].instances
|
||||
i = ins.length
|
||||
while (i--) {
|
||||
if (ins[i].unbind) ins[i].unbind()
|
||||
}
|
||||
}
|
||||
// remove all listeners on eventbus
|
||||
i = this._listeners.length
|
||||
while (i--) {
|
||||
listener = this._listeners[i]
|
||||
eventbus.off(listener.event, listener.handler)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unbind and remove element
|
||||
*/
|
||||
SeedProto._destroy = function () {
|
||||
this._unbind()
|
||||
CompilerProto.destroy = function () {
|
||||
var i, key, dir, listener, inss
|
||||
// remove all directives that are instances of external bindings
|
||||
i = this.directives.length
|
||||
while (i--) {
|
||||
dir = this.directives[i]
|
||||
if (dir.binding.compiler !== this) {
|
||||
inss = dir.binding.instances
|
||||
inss.splice(inss.indexOf(dir), 1)
|
||||
}
|
||||
dir.unbind()
|
||||
}
|
||||
// remove all listeners on eventbus
|
||||
i = this.listeners.length
|
||||
while (i--) {
|
||||
listener = this.listeners[i]
|
||||
eventbus.off(listener.event, listener.handler)
|
||||
}
|
||||
// unbind all bindings
|
||||
for (key in this.bindings) {
|
||||
this.bindings[key].unbind()
|
||||
}
|
||||
// remove el
|
||||
this.el.compiler = null
|
||||
this.el.parentNode.removeChild(this.el)
|
||||
}
|
||||
|
||||
// Helpers --------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* determine which scope a key belongs to based on nesting symbols
|
||||
* determine which viewmodel a key belongs to based on nesting symbols
|
||||
*/
|
||||
function traceOwnerSeed (key, seed) {
|
||||
function traceOwnerCompiler (key, compiler) {
|
||||
if (key.nesting) {
|
||||
var levels = key.nesting
|
||||
while (seed.parentSeed && levels--) {
|
||||
seed = seed.parentSeed
|
||||
while (compiler.parentCompiler && levels--) {
|
||||
compiler = compiler.parentCompiler
|
||||
}
|
||||
} else if (key.root) {
|
||||
while (seed.parentSeed) {
|
||||
seed = seed.parentSeed
|
||||
while (compiler.parentCompiler) {
|
||||
compiler = compiler.parentCompiler
|
||||
}
|
||||
}
|
||||
return seed
|
||||
return compiler
|
||||
}
|
||||
|
||||
module.exports = Seed
|
||||
module.exports = Compiler
|
||||
|
|
@ -4,7 +4,7 @@ var Emitter = require('emitter'),
|
|||
|
||||
var dummyEl = document.createElement('div'),
|
||||
ARGS_RE = /^function\s*?\((.+?)\)/,
|
||||
SCOPE_RE_STR = '\\.scope\\.[\\.A-Za-z0-9_][\\.A-Za-z0-9_$]*',
|
||||
SCOPE_RE_STR = '\\.vm\\.[\\.A-Za-z0-9_][\\.A-Za-z0-9_$]*',
|
||||
noop = function () {}
|
||||
|
||||
/*
|
||||
|
|
@ -21,7 +21,7 @@ function catchDeps (binding) {
|
|||
})
|
||||
parseContextDependency(binding)
|
||||
binding.value.get({
|
||||
scope: createDummyScope(binding),
|
||||
vm: createDummyVM(binding),
|
||||
el: dummyEl
|
||||
})
|
||||
observer.off('get')
|
||||
|
|
@ -37,31 +37,37 @@ function filterDeps (binding) {
|
|||
while (i--) {
|
||||
dep = binding.deps[i]
|
||||
if (!dep.deps.length) {
|
||||
config.log(' └─' + dep.key)
|
||||
config.log(' └─ ' + dep.key)
|
||||
dep.subs.push(binding)
|
||||
} else {
|
||||
binding.deps.splice(i, 1)
|
||||
}
|
||||
}
|
||||
var ctdeps = binding.contextDeps
|
||||
if (!ctdeps || !config.debug) return
|
||||
i = ctdeps.length
|
||||
while (i--) {
|
||||
config.log(' └─ ctx:' + ctdeps[i])
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to invoke each binding's getter for dependency parsing,
|
||||
* but we don't know what sub-scope properties the user might try
|
||||
* but we don't know what sub-viewmodel properties the user might try
|
||||
* to access in that getter. To avoid thowing an error or forcing
|
||||
* the user to guard against an undefined argument, we staticly
|
||||
* analyze the function to extract any possible nested properties
|
||||
* the user expects the target scope to possess. They are all assigned
|
||||
* the user expects the target viewmodel to possess. They are all assigned
|
||||
* a noop function so they can be invoked with no real harm.
|
||||
*/
|
||||
function createDummyScope (binding) {
|
||||
var scope = {},
|
||||
function createDummyVM (binding) {
|
||||
var viewmodel = {},
|
||||
deps = binding.contextDeps
|
||||
if (!deps) return scope
|
||||
if (!deps) return viewmodel
|
||||
var i = binding.contextDeps.length,
|
||||
j, level, key, path
|
||||
while (i--) {
|
||||
level = scope
|
||||
level = viewmodel
|
||||
path = deps[i].split('.')
|
||||
j = 0
|
||||
while (j < path.length) {
|
||||
|
|
@ -71,20 +77,20 @@ function createDummyScope (binding) {
|
|||
j++
|
||||
}
|
||||
}
|
||||
return scope
|
||||
return viewmodel
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract context dependency paths
|
||||
*/
|
||||
function parseContextDependency (binding) {
|
||||
var fn = binding.value.get,
|
||||
var fn = binding.rawGet,
|
||||
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
|
||||
var depsRE = new RegExp(args[1] + SCOPE_RE_STR, 'g'),
|
||||
matches = str.match(depsRE),
|
||||
base = args[1].length + 4
|
||||
if (!matches) return null
|
||||
var i = matches.length,
|
||||
deps = [], dep
|
||||
|
|
@ -95,7 +101,7 @@ function parseContextDependency (binding) {
|
|||
}
|
||||
}
|
||||
binding.contextDeps = deps
|
||||
binding.seed._contextBindings.push(binding)
|
||||
binding.compiler.contextBindings.push(binding)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,14 @@ function Directive (directiveName, expression, oneway) {
|
|||
this._update = definition.update
|
||||
for (prop in definition) {
|
||||
if (prop !== 'update') {
|
||||
if (prop === 'unbind') {
|
||||
this._unbind = definition[prop]
|
||||
} else {
|
||||
this[prop] = definition[prop]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.oneway = !!oneway
|
||||
this.directiveName = directiveName
|
||||
|
|
@ -62,11 +66,11 @@ DirProto.update = function (value) {
|
|||
* called when a dependency has changed
|
||||
*/
|
||||
DirProto.refresh = function () {
|
||||
// pass element and scope info to the getter
|
||||
// pass element and viewmodel info to the getter
|
||||
// enables powerful context-aware bindings
|
||||
var value = this.value.get({
|
||||
el: this.el,
|
||||
scope: this.seed.scope
|
||||
vm: this.vm
|
||||
})
|
||||
if (value === this.computedValue) return
|
||||
this.computedValue = value
|
||||
|
|
@ -135,6 +139,15 @@ DirProto.parseKey = function (rawKey) {
|
|||
this.key = key
|
||||
}
|
||||
|
||||
/*
|
||||
* unbind noop, to be overwritten by definitions
|
||||
*/
|
||||
DirProto.unbind = function (update) {
|
||||
if (!this.el) return
|
||||
if (this._unbind) this._unbind(update)
|
||||
if (!update) this.vm = this.el = this.compiler = this.binding = null
|
||||
}
|
||||
|
||||
/*
|
||||
* parse a filter expression
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
var config = require('../config')
|
||||
var config = require('../config'),
|
||||
utils = require('../utils')
|
||||
|
||||
/*
|
||||
* Mathods that perform precise DOM manipulation
|
||||
|
|
@ -59,16 +60,16 @@ var mutationHandlers = {
|
|||
},
|
||||
|
||||
sort: function () {
|
||||
var i, l = this.collection.length, scope
|
||||
var i, l = this.collection.length, viewmodel
|
||||
for (i = 0; i < l; i++) {
|
||||
scope = this.collection[i]
|
||||
scope.$index = i
|
||||
this.container.insertBefore(scope.$el, this.ref)
|
||||
viewmodel = this.collection[i]
|
||||
viewmodel.$index = i
|
||||
this.container.insertBefore(viewmodel.$el, this.ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutationHandlers.reverse = mutationHandlers.sort
|
||||
//mutationHandlers.reverse = mutationHandlers.sort
|
||||
|
||||
module.exports = {
|
||||
|
||||
|
|
@ -84,7 +85,6 @@ module.exports = {
|
|||
update: function (collection) {
|
||||
|
||||
this.unbind(true)
|
||||
if (!Array.isArray(collection)) return
|
||||
this.collection = collection
|
||||
|
||||
// attach an object to container to hold handlers
|
||||
|
|
@ -92,10 +92,9 @@ module.exports = {
|
|||
|
||||
// listen for collection mutation events
|
||||
// the collection has been augmented during Binding.set()
|
||||
var self = this
|
||||
collection.on('mutate', function (mutation) {
|
||||
mutationHandlers[mutation.method].call(self, mutation)
|
||||
})
|
||||
collection.on('mutate', (function (mutation) {
|
||||
mutationHandlers[mutation.method].call(this, mutation)
|
||||
}).bind(this))
|
||||
|
||||
// create child-seeds and append to DOM
|
||||
for (var i = 0, l = collection.length; i < l; i++) {
|
||||
|
|
@ -106,16 +105,16 @@ module.exports = {
|
|||
buildItem: function (ref, data, index) {
|
||||
var node = this.el.cloneNode(true)
|
||||
this.container.insertBefore(node, ref)
|
||||
var Seed = require('../seed'),
|
||||
spore = new Seed(node, {
|
||||
var Compiler = require('../compiler'),
|
||||
spore = new Compiler(node, {
|
||||
each: true,
|
||||
eachPrefix: this.arg + '.',
|
||||
parentSeed: this.seed,
|
||||
parentCompiler: this.compiler,
|
||||
index: index,
|
||||
data: data,
|
||||
delegator: this.container
|
||||
})
|
||||
this.collection[index] = spore.scope
|
||||
this.collection[index] = spore.vm
|
||||
},
|
||||
|
||||
updateIndexes: function () {
|
||||
|
|
@ -125,20 +124,19 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
unbind: function (reset) {
|
||||
if (this.collection && this.collection.length) {
|
||||
var i = this.collection.length,
|
||||
fn = reset ? '_destroy' : '_unbind'
|
||||
unbind: function () {
|
||||
if (this.collection) {
|
||||
this.collection.off('mutate')
|
||||
var i = this.collection.length
|
||||
while (i--) {
|
||||
this.collection[i].$seed[fn]()
|
||||
this.collection[i].$destroy()
|
||||
}
|
||||
this.collection = null
|
||||
}
|
||||
var ctn = this.container,
|
||||
handlers = ctn.sd_dHandlers
|
||||
for (var key in handlers) {
|
||||
ctn.removeEventListener(handlers[key].event, handlers[key])
|
||||
}
|
||||
delete ctn.sd_dHandlers
|
||||
ctn.sd_dHandlers = null
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ module.exports = {
|
|||
if (this.oneway) return
|
||||
var el = this.el, self = this
|
||||
this.change = function () {
|
||||
self.seed.scope[self.key] = el.value
|
||||
self.compiler.vm[self.key] = el.value
|
||||
}
|
||||
el.addEventListener('keyup', this.change)
|
||||
},
|
||||
|
|
@ -69,7 +69,7 @@ module.exports = {
|
|||
if (this.oneway) return
|
||||
var el = this.el, self = this
|
||||
this.change = function () {
|
||||
self.seed.scope[self.key] = el.checked
|
||||
self.compiler.vm[self.key] = el.checked
|
||||
}
|
||||
el.addEventListener('change', this.change)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,29 +13,29 @@ module.exports = {
|
|||
expectFunction : true,
|
||||
|
||||
bind: function () {
|
||||
if (this.seed.each) {
|
||||
if (this.compiler.each) {
|
||||
// attach an identifier to the el
|
||||
// so it can be matched during event delegation
|
||||
this.el[this.expression] = true
|
||||
// attach the owner scope of this directive
|
||||
this.el.sd_scope = this.seed.scope
|
||||
// attach the owner viewmodel of this directive
|
||||
this.el.sd_viewmodel = this.vm
|
||||
}
|
||||
},
|
||||
|
||||
update: function (handler) {
|
||||
|
||||
this.unbind()
|
||||
this.unbind(true)
|
||||
if (!handler) return
|
||||
|
||||
var seed = this.seed,
|
||||
var compiler = this.compiler,
|
||||
event = this.arg,
|
||||
ownerScope = this.binding.seed.scope
|
||||
ownerVM = this.binding.vm
|
||||
|
||||
if (seed.each && event !== 'blur' && event !== 'blur') {
|
||||
if (compiler.each && event !== 'blur' && event !== 'blur') {
|
||||
|
||||
// for each blocks, delegate for better performance
|
||||
// focus and blur events dont bubble so exclude them
|
||||
var delegator = seed.delegator,
|
||||
var delegator = compiler.delegator,
|
||||
identifier = this.expression,
|
||||
dHandler = delegator.sd_dHandlers[identifier]
|
||||
|
||||
|
|
@ -46,8 +46,8 @@ module.exports = {
|
|||
var target = delegateCheck(e.target, delegator, identifier)
|
||||
if (target) {
|
||||
e.el = target
|
||||
e.scope = target.sd_scope
|
||||
handler.call(ownerScope, e)
|
||||
e.vm = target.sd_viewmodel
|
||||
handler.call(ownerVM, e)
|
||||
}
|
||||
}
|
||||
dHandler.event = event
|
||||
|
|
@ -58,15 +58,17 @@ module.exports = {
|
|||
// a normal, single element handler
|
||||
this.handler = function (e) {
|
||||
e.el = e.currentTarget
|
||||
e.scope = seed.scope
|
||||
handler.call(seed.scope, e)
|
||||
e.vm = compiler.vm
|
||||
handler.call(compiler.vm, e)
|
||||
}
|
||||
this.el.addEventListener(event, this.handler)
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
unbind: function () {
|
||||
unbind: function (update) {
|
||||
this.el.removeEventListener(this.arg, this.handler)
|
||||
this.handler = null
|
||||
if (!update) this.el.sd_viewmodel = null
|
||||
}
|
||||
}
|
||||
22
src/main.js
22
src/main.js
|
|
@ -1,6 +1,6 @@
|
|||
var config = require('./config'),
|
||||
Seed = require('./seed'),
|
||||
Scope = require('./scope'),
|
||||
Compiler = require('./compiler'),
|
||||
ViewModel = require('./viewmodel'),
|
||||
directives = require('./directives'),
|
||||
filters = require('./filters'),
|
||||
textParser = require('./text-parser'),
|
||||
|
|
@ -40,12 +40,12 @@ api.data = function (id, data) {
|
|||
*/
|
||||
api.controller = function (id, properties) {
|
||||
if (!properties) return controllers[id]
|
||||
// create a subclass of Scope that has the extension methods mixed-in
|
||||
var ExtendedScope = function () {
|
||||
Scope.apply(this, arguments)
|
||||
// create a subclass of ViewModel that has the extension methods mixed-in
|
||||
var ExtendedVM = function () {
|
||||
ViewModel.apply(this, arguments)
|
||||
}
|
||||
var p = ExtendedScope.prototype = Object.create(Scope.prototype)
|
||||
p.constructor = ExtendedScope
|
||||
var p = ExtendedVM.prototype = Object.create(ViewModel.prototype)
|
||||
p.constructor = ExtendedVM
|
||||
for (var prop in properties) {
|
||||
if (prop !== 'init') {
|
||||
p[prop] = properties[prop]
|
||||
|
|
@ -53,7 +53,7 @@ api.controller = function (id, properties) {
|
|||
}
|
||||
controllers[id] = {
|
||||
init: properties.init,
|
||||
ExtendedScope: ExtendedScope
|
||||
ExtendedVM: ExtendedVM
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,12 +91,12 @@ api.config = function (opts) {
|
|||
* Compile a single element
|
||||
*/
|
||||
api.compile = function (el) {
|
||||
return new Seed(el).scope
|
||||
return new Compiler(el).vm
|
||||
}
|
||||
|
||||
/*
|
||||
* Bootstrap the whole thing
|
||||
* by creating a Seed instance for top level nodes
|
||||
* by creating a Compiler instance for top level nodes
|
||||
* that has either sd-controller or sd-data
|
||||
*/
|
||||
api.bootstrap = function (opts) {
|
||||
|
|
@ -107,7 +107,7 @@ api.bootstrap = function (opts) {
|
|||
dataSlt = '[' + config.prefix + '-data]'
|
||||
/* jshint boss: true */
|
||||
while (el = document.querySelector(ctrlSlt) || document.querySelector(dataSlt)) {
|
||||
new Seed(el)
|
||||
new Compiler(el)
|
||||
}
|
||||
booted = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function dump (val) {
|
|||
} else if (type === 'Object') {
|
||||
if (val.get) { // computed property
|
||||
return val.get()
|
||||
} else { // object / child scope
|
||||
} else { // object / child viewmodel
|
||||
var ret = {}, prop
|
||||
for (var key in val) {
|
||||
prop = val[key]
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
var utils = require('./utils')
|
||||
|
||||
/*
|
||||
* Scope is the ViewModel/whatever exposed to the user
|
||||
* that holds data, computed properties, event handlers
|
||||
* ViewModel exposed to the user that holds data,
|
||||
* computed properties, event handlers
|
||||
* and a few reserved methods
|
||||
*/
|
||||
function Scope (seed, options) {
|
||||
this.$seed = seed
|
||||
this.$el = seed.el
|
||||
function ViewModel (compiler, options) {
|
||||
this.$compiler = compiler
|
||||
this.$el = compiler.el
|
||||
this.$index = options.index
|
||||
this.$parent = options.parentSeed && options.parentSeed.scope
|
||||
this.$seed._watchers = {}
|
||||
this.$parent = options.parentCompiler && options.parentCompiler.vm
|
||||
}
|
||||
|
||||
var ScopeProto = Scope.prototype
|
||||
var VMProto = ViewModel.prototype
|
||||
|
||||
/*
|
||||
* register a listener that will be broadcasted from the global event bus
|
||||
*/
|
||||
ScopeProto.$on = function (event, handler) {
|
||||
VMProto.$on = function (event, handler) {
|
||||
utils.eventbus.on(event, handler)
|
||||
this.$seed._listeners.push({
|
||||
this.$compiler.listeners.push({
|
||||
event: event,
|
||||
handler: handler
|
||||
})
|
||||
|
|
@ -29,9 +28,9 @@ ScopeProto.$on = function (event, handler) {
|
|||
/*
|
||||
* remove the registered listener
|
||||
*/
|
||||
ScopeProto.$off = function (event, handler) {
|
||||
VMProto.$off = function (event, handler) {
|
||||
utils.eventbus.off(event, handler)
|
||||
var listeners = this.$seed._listeners,
|
||||
var listeners = this.$compiler.listeners,
|
||||
i = listeners.length, listener
|
||||
while (i--) {
|
||||
listener = listeners[i]
|
||||
|
|
@ -43,19 +42,19 @@ ScopeProto.$off = function (event, handler) {
|
|||
}
|
||||
|
||||
/*
|
||||
* watch a key on the scope for changes
|
||||
* watch a key on the viewmodel for changes
|
||||
* fire callback with new value
|
||||
*/
|
||||
ScopeProto.$watch = function (key, callback) {
|
||||
VMProto.$watch = function (key, callback) {
|
||||
var self = this
|
||||
// yield and wait for seed to finish compiling
|
||||
// yield and wait for compiler to finish compiling
|
||||
setTimeout(function () {
|
||||
var scope = self.$seed.scope,
|
||||
binding = self.$seed._bindings[key],
|
||||
var viewmodel = self.$compiler.vm,
|
||||
binding = self.$compiler.bindings[key],
|
||||
i = binding.deps.length,
|
||||
watcher = self.$seed._watchers[key] = {
|
||||
watcher = self.$compiler.watchers[key] = {
|
||||
refresh: function () {
|
||||
callback(scope[key])
|
||||
callback(viewmodel[key])
|
||||
},
|
||||
deps: binding.deps
|
||||
}
|
||||
|
|
@ -68,50 +67,51 @@ ScopeProto.$watch = function (key, callback) {
|
|||
/*
|
||||
* remove watcher
|
||||
*/
|
||||
ScopeProto.$unwatch = function (key) {
|
||||
VMProto.$unwatch = function (key) {
|
||||
var self = this
|
||||
setTimeout(function () {
|
||||
var watcher = self.$seed._watchers[key]
|
||||
var watcher = self.$compiler.watchers[key]
|
||||
if (!watcher) return
|
||||
var i = watcher.deps.length, subs
|
||||
while (i--) {
|
||||
subs = watcher.deps[i].subs
|
||||
subs.splice(subs.indexOf(watcher))
|
||||
}
|
||||
delete self.$seed._watchers[key]
|
||||
self.$compiler.watchers[key] = null
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/*
|
||||
* load data into scope
|
||||
* load data into viewmodel
|
||||
*/
|
||||
ScopeProto.$load = function (data) {
|
||||
VMProto.$load = function (data) {
|
||||
for (var key in data) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump a copy of current scope data, excluding seed-exposed properties.
|
||||
* Dump a copy of current viewmodel data, excluding compiler-exposed properties.
|
||||
* @param key (optional): key for the value to dump
|
||||
*/
|
||||
ScopeProto.$dump = function (key) {
|
||||
var bindings = this.$seed._bindings
|
||||
VMProto.$dump = function (key) {
|
||||
var bindings = this.$compiler.bindings
|
||||
return utils.dump(key ? bindings[key].value : this)
|
||||
}
|
||||
|
||||
/*
|
||||
* stringify the result from $dump
|
||||
*/
|
||||
ScopeProto.$serialize = function (key) {
|
||||
VMProto.$serialize = function (key) {
|
||||
return JSON.stringify(this.$dump(key))
|
||||
}
|
||||
|
||||
/*
|
||||
* unbind everything, remove everything
|
||||
*/
|
||||
ScopeProto.$destroy = function () {
|
||||
this.$seed._destroy()
|
||||
VMProto.$destroy = function () {
|
||||
this.$compiler.destroy()
|
||||
this.$compiler = null
|
||||
}
|
||||
|
||||
module.exports = Scope
|
||||
module.exports = ViewModel
|
||||
Loading…
Reference in New Issue