mirror of https://github.com/vuejs/vue.git
v-component & v-with refactor
This commit is contained in:
parent
2059be5a63
commit
665520f59b
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ BindingProto.update = function (value) {
|
|||
execute: function () {
|
||||
if (!self.unbound) {
|
||||
self._update()
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ module.exports = {
|
|||
style : require('./style'),
|
||||
partial : require('./partial'),
|
||||
view : require('./view'),
|
||||
component : require('./component'),
|
||||
|
||||
attr: {
|
||||
bind: function () {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}}'
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue