v-component & v-with refactor

This commit is contained in:
Evan You 2014-03-12 17:02:21 -04:00
parent 2059be5a63
commit 665520f59b
12 changed files with 204 additions and 122 deletions

View File

@ -23,6 +23,7 @@
"src/transition.js",
"src/batcher.js",
"src/directives/index.js",
"src/directives/component.js",
"src/directives/if.js",
"src/directives/repeat.js",
"src/directives/on.js",

View File

@ -29,9 +29,8 @@ BatcherProto.flush = function () {
// as we execute existing jobs
for (var i = 0; i < this.queue.length; i++) {
var job = this.queue[i]
if (job.cancelled) continue
if (job.execute() !== false) {
this.has[job.id] = false
if (!job.cancelled) {
job.execute()
}
}
this.reset()

View File

@ -39,8 +39,6 @@ BindingProto.update = function (value) {
execute: function () {
if (!self.unbound) {
self._update()
} else {
return false
}
}
})

View File

@ -325,6 +325,8 @@ CompilerProto.observeData = function (data) {
*/
CompilerProto.compile = function (node, root) {
/* jshint boss: true */
var compiler = this,
nodeType = node.nodeType,
tagName = node.tagName
@ -335,22 +337,15 @@ CompilerProto.compile = function (node, root) {
if (utils.attr(node, 'pre') !== null) return
// special attributes to check
var repeatExp,
viewExp,
withExp,
directive,
// resolve a standalone child component with no inherited data
hasComponent = this.resolveComponent(node, undefined, true)
var directive, repeatExp, viewExp, Component
// It is important that we access these attributes
// procedurally because the order matters.
//
// `utils.attr` removes the attribute once it gets the
// value, so we should not access them all at once.
// v-repeat has the highest priority
// and we need to preserve all other attributes for it.
/* jshint boss: true */
if (repeatExp = utils.attr(node, 'repeat')) {
// repeat block cannot have v-id at the same time.
@ -370,18 +365,26 @@ CompilerProto.compile = function (node, root) {
}
// Child component has 2nd highest priority
} else if (root !== true && ((withExp = utils.attr(node, 'with')) || hasComponent)) {
} else if (root !== true && (Component = this.resolveComponent(node, undefined, true))) {
withExp = Directive.split(withExp || '')
withExp.forEach(function (exp, i) {
var directive = Directive.parse('with', exp, compiler, node)
if (directive) {
// notify the directive that this is the
// last expression in the group
directive.last = i === withExp.length - 1
compiler.deferred.push(directive)
}
})
directive = Directive.parse('component', '', compiler, node)
if (directive) {
directive.Ctor = Component
compiler.deferred.push(directive)
}
// should build component
// withExp = Directive.split(withExp || '')
// withExp.forEach(function (exp, i) {
// var directive = Directive.parse('with', exp, compiler, node)
// if (directive) {
// // notify the directive that this is the
// // last expression in the group
// directive.last = i === withExp.length - 1
// compiler.deferred.push(directive)
// }
// })
} else {
@ -432,7 +435,13 @@ CompilerProto.compileNode = function (node) {
exp = exps[j]
dirname = attr.name.slice(prefix.length)
directive = Directive.parse(dirname, exp, this, node)
this.bindDirective(directive)
if (dirname === 'with') {
this.bindDirective(directive, this.parent)
} else {
this.bindDirective(directive)
}
}
} else if (config.interpolate) {
// non directive attribute, check interpolation tags
@ -811,7 +820,10 @@ CompilerProto.eval = function (exp, data) {
*/
CompilerProto.resolveComponent = function (node, data, test) {
var exp = utils.attr(node, 'component', test),
// late require to avoid circular deps
ViewModel = ViewModel || require('./viewmodel')
var exp = utils.attr(node, 'component'),
tagName = node.tagName,
id = this.eval(exp, data),
tagId = (tagName.indexOf('-') > 0 && tagName.toLowerCase()),
@ -822,8 +834,10 @@ CompilerProto.resolveComponent = function (node, data, test) {
}
return test
? Ctor
: Ctor || (ViewModel || (ViewModel = require('./viewmodel')))
? exp === ''
? ViewModel
: Ctor
: Ctor || ViewModel
}
/**

View File

@ -0,0 +1,20 @@
module.exports = {
isLiteral: true,
bind: function () {
if (!this.el.vue_vm) {
this.component = new this.Ctor({
el: this.el,
parent: this.vm
})
}
},
unbind: function () {
if (this.component) {
this.component.$destroy()
}
}
}

View File

@ -13,6 +13,7 @@ module.exports = {
style : require('./style'),
partial : require('./partial'),
view : require('./view'),
component : require('./component'),
attr: {
bind: function () {

View File

@ -3,88 +3,136 @@ var utils = require('../utils')
module.exports = {
bind: function () {
if (this.el.vue_vm) {
this.subVM = this.el.vue_vm
var compiler = this.subVM.$compiler
if (this.arg && !compiler.bindings[this.arg]) {
compiler.createBinding(this.arg)
var self = this,
childKey = self.arg,
parentKey = self.key,
compiler = self.compiler,
owner = self.binding.compiler
if (compiler === owner) {
this.alone = true
return
}
if (childKey) {
if (!compiler.bindings[childKey]) {
compiler.createBinding(childKey)
}
} else if (this.isEmpty) {
this.build()
// sync changes on child back to parent
compiler.observer.on('change:' + childKey, function (val) {
if (compiler.init) return
if (!self.lock) {
self.lock = true
utils.nextTick(function () {
self.lock = false
})
}
owner.vm.$set(parentKey, val)
})
}
},
update: function (value, init) {
var vm = this.subVM,
key = this.arg || '$data'
if (!vm) {
this.build(value)
} else if (!this.lock && vm[key] !== value) {
vm[key] = value
}
if (init) {
// watch after first set
this.watch()
// The v-with directive can have multiple expressions,
// and we want to make sure when the ready hook is called
// on the subVM, all these clauses have been properly set up.
// So this is a hack that sniffs whether we have reached
// the last expression. We hold off the subVM's ready hook
// until we are actually ready.
if (this.last) {
this.subVM.$compiler.execHook('ready')
update: function (value) {
// sync from parent
if (!this.alone && !this.lock) {
if (this.arg) {
this.vm.$set(this.arg, value)
} else {
this.vm.$data = value
}
}
},
build: function (value) {
var data = value
if (this.arg) {
data = {}
data[this.arg] = value
}
var Ctor = this.compiler.resolveComponent(this.el, data)
this.subVM = new Ctor({
el : this.el,
data : data,
parent : this.vm,
compilerOptions: {
// it is important to delay the ready hook
// so that when it's called, all `v-with` wathcers
// would have been set up.
delayReady: !this.last
}
})
// mark that this VM is created by v-with
utils.defProtected(this.subVM, '$with', true)
},
/**
* For inhertied keys, need to watch
* and sync back to the parent
*/
watch: function () {
if (!this.arg) return
var self = this,
key = self.key,
ownerVM = self.binding.compiler.vm
this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
if (!self.lock) {
self.lock = true
utils.nextTick(function () {
self.lock = false
})
}
ownerVM.$set(key, val)
})
},
unbind: function () {
// all watchers are turned off during destroy
// so no need to worry about it
if (this.subVM.$with) {
this.subVM.$destroy()
}
}
}
}
// var utils = require('../utils')
// module.exports = {
// bind: function () {
// if (this.el.vue_vm) {
// this.subVM = this.el.vue_vm
// var compiler = this.subVM.$compiler
// if (this.arg && !compiler.bindings[this.arg]) {
// compiler.createBinding(this.arg)
// }
// } else if (this.isEmpty) {
// this.build()
// }
// },
// update: function (value, init) {
// var vm = this.subVM,
// key = this.arg || '$data'
// if (!vm) {
// this.build(value)
// } else if (!this.lock && vm[key] !== value) {
// vm[key] = value
// }
// if (init) {
// // watch after first set
// this.watch()
// // The v-with directive can have multiple expressions,
// // and we want to make sure when the ready hook is called
// // on the subVM, all these clauses have been properly set up.
// // So this is a hack that sniffs whether we have reached
// // the last expression. We hold off the subVM's ready hook
// // until we are actually ready.
// if (this.last) {
// this.subVM.$compiler.execHook('ready')
// }
// }
// },
// build: function (value) {
// var data = value
// if (this.arg) {
// data = {}
// data[this.arg] = value
// }
// var Ctor = this.compiler.resolveComponent(this.el, data)
// this.subVM = new Ctor({
// el : this.el,
// data : data,
// parent : this.vm,
// compilerOptions: {
// // it is important to delay the ready hook
// // so that when it's called, all `v-with` wathcers
// // would have been set up.
// delayReady: !this.last
// }
// })
// // mark that this VM is created by v-with
// utils.defProtected(this.subVM, '$with', true)
// },
// /**
// * For inhertied keys, need to watch
// * and sync back to the parent
// */
// watch: function () {
// if (!this.arg) return
// var self = this,
// key = self.key,
// ownerVM = self.binding.compiler.vm
// this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
// if (!self.lock) {
// self.lock = true
// utils.nextTick(function () {
// self.lock = false
// })
// }
// ownerVM.$set(key, val)
// })
// },
// unbind: function () {
// // all watchers are turned off during destroy
// // so no need to worry about it
// if (this.subVM.$with) {
// this.subVM.$destroy()
// }
// }
// }

View File

@ -63,10 +63,10 @@ var utils = module.exports = {
/**
* get an attribute and remove it.
*/
attr: function (el, type, preserve) {
attr: function (el, type) {
var attr = config.prefix + '-' + type,
val = el.getAttribute(attr)
if (!preserve && val !== null) {
if (val !== null) {
el.removeAttribute(attr)
}
return val

View File

@ -5,8 +5,8 @@
<!-- custom element + v-with -->
<my-avatar id="element-and-with" v-with="user"></my-avatar>
<!-- v-with alone -->
<div id="with" v-with="user">{{hi}} {{name}}</div>
<!-- v-with with default Ctor -->
<div id="with" v-component v-with="user">{{hi}} {{name}}</div>
<!-- v-component alone -->
<div id="component" v-component="my-element"></div>
@ -15,15 +15,16 @@
<my-element id="element"></my-element>
<!-- v-with + binding sync -->
<div id="with-sync" v-with="childHi:hi, childName:user.name">
<div id="with-sync" v-component v-with="childHi:hi, childName:user.name">
{{childHi}} {{childName}}
</div>
<div id="component-with-sync" v-component="sync" v-with="childHi:hi, childName:user.name"></div>
<!-- conditional component -->
<div id="conditional" v-component="{{ok ? 'my-element' : 'nope'}}"></div>
<!-- this one will change everything for everyone else... -->
<div id="component-with-sync" v-component="sync" v-with="childHi:hi, childName:user.name"></div>
<!-- conditional component with v-repeat! -->
<div class="repeat-conditional {{type}}" v-repeat="items" v-component="{{type}}"></div>
</div>
@ -31,9 +32,9 @@
<script src="../../../dist/vue.js"></script>
<script>
Vue.config({
debug: true
})
// Vue.config({
// debug: true
// })
Vue.component('my-avatar', {
template: '{{hi}} {{name}}'

View File

@ -12,7 +12,7 @@
<script src="../../../dist/vue.js"></script>
<script>
Vue.config({debug:true})
// Vue.config({debug:true})
var log = document.getElementById('log')
var T = Vue.extend({
created: function () {
@ -40,8 +40,8 @@
})
T.partial('partial-test', '{{partialMsg}}')
T.component('vm-w-model', {
data : {
selfMsg: 'component with model '
ready: function () {
this.selfMsg = 'component + with '
}
})
var C = T.extend({

View File

@ -7,7 +7,7 @@ casper.test.begin('Encapsulation & Inheritance', 8, function (test) {
test.assertSelectorHasText('.filter', 'filter works')
test.assertSelectorHasText('.partial', 'partial works')
test.assertSelectorHasText('.vm', 'component works')
test.assertSelectorHasText('.vm-w-model', 'component with model works')
test.assertSelectorHasText('.vm-w-model', 'component + with works')
test.assertSelectorHasText('#log', 'T created T ready T created C created T ready C ready', 'hook inheritance works')
test.assertSelectorHasText('.cvm', 'component works', 'Child should have access to Parent options')
})

View File

@ -609,7 +609,7 @@ describe('Directives', function () {
it('should create a child viewmodel with given data', function () {
var testId = 'with-test'
mock(testId, '<span v-with="test">{{msg}}</span>')
mock(testId, '<span v-component v-with="test">{{msg}}</span>')
var t = new Vue({
el: '#' + testId,
data: {
@ -625,7 +625,7 @@ describe('Directives', function () {
var t = new Vue({
template:
'<span>{{test.msg}} {{n}}</span>'
+ '<p v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
+ '<p v-component v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
data: {
n: 1,
test: {
@ -707,7 +707,7 @@ describe('Directives', function () {
it('should work with interpolation', function () {
t = new Vue({
template: '<div v-with="obj" v-ref="{{ok ? \'a\' : \'b\'}}"></div>',
template: '<div v-component v-with="obj" v-ref="{{ok ? \'a\' : \'b\'}}"></div>',
data: { obj: { a: 123 } }
})
assert.equal(t.$.b.a, 123)