Skip to content

Commit 1a05dea

Browse files
committed
v-with allows linking keys between child and parent VMs
1 parent 918797c commit 1a05dea

File tree

5 files changed

+146
-26
lines changed

5 files changed

+146
-26
lines changed

src/compiler.js

+19-10
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function Compiler (vm, options) {
4949
log('\nnew VM instance:', el.tagName, '\n')
5050

5151
// set compiler properties
52-
compiler.vm = vm
52+
compiler.vm = el.vue_vm = vm
5353
compiler.bindings = makeHash()
5454
compiler.dirs = []
5555
compiler.deferred = []
@@ -132,7 +132,9 @@ function Compiler (vm, options) {
132132
compiler.init = false
133133

134134
// post compile / ready hook
135-
compiler.execHook('ready')
135+
if (!compiler.delayReady) {
136+
compiler.execHook('ready')
137+
}
136138
}
137139

138140
var CompilerProto = Compiler.prototype
@@ -304,7 +306,7 @@ CompilerProto.compile = function (node, root) {
304306

305307
// special attributes to check
306308
var repeatExp,
307-
withKey,
309+
withExp,
308310
partialId,
309311
directive,
310312
componentId = utils.attr(node, 'component') || tagName.toLowerCase(),
@@ -332,13 +334,19 @@ CompilerProto.compile = function (node, root) {
332334
}
333335

334336
// v-with has 2nd highest priority
335-
} else if (root !== true && ((withKey = utils.attr(node, 'with')) || componentCtor)) {
336-
337-
directive = Directive.parse('with', withKey || '', compiler, node)
338-
if (directive) {
339-
directive.Ctor = componentCtor
340-
compiler.deferred.push(directive)
341-
}
337+
} else if (root !== true && ((withExp = utils.attr(node, 'with')) || componentCtor)) {
338+
339+
withExp = Directive.split(withExp || '')
340+
withExp.forEach(function (exp, i) {
341+
var directive = Directive.parse('with', exp, compiler, node)
342+
if (directive) {
343+
directive.Ctor = componentCtor
344+
// notify the directive that this is the
345+
// last expression in the group
346+
directive.last = i === withExp.length - 1
347+
compiler.deferred.push(directive)
348+
}
349+
})
342350

343351
} else {
344352

@@ -801,6 +809,7 @@ CompilerProto.destroy = function () {
801809
} else {
802810
vm.$remove()
803811
}
812+
el.vue_vm = null
804813

805814
this.destroyed = true
806815
// emit destroy hook

src/directives/with.js

+63-10
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,88 @@
1-
var ViewModel
1+
var ViewModel,
2+
nextTick = require('../utils').nextTick
23

34
module.exports = {
45

56
bind: function () {
6-
if (this.isEmpty) {
7+
if (this.el.vue_vm) {
8+
this.subVM = this.el.vue_vm
9+
var compiler = this.subVM.$compiler
10+
if (!compiler.bindings[this.arg]) {
11+
compiler.createBinding(this.arg)
12+
}
13+
} else if (this.isEmpty) {
714
this.build()
815
}
916
},
1017

11-
update: function (value) {
12-
if (!this.component) {
18+
update: function (value, init) {
19+
var vm = this.subVM,
20+
key = this.arg || '$data'
21+
if (!vm) {
1322
this.build(value)
14-
} else {
15-
this.component.$data = value
23+
} else if (!this.lock && vm[key] !== value) {
24+
vm[key] = value
25+
}
26+
if (init) {
27+
// watch after first set
28+
this.watch()
29+
// The v-with directive can have multiple expressions,
30+
// and we want to make sure when the ready hook is called
31+
// on the subVM, all these clauses have been properly set up.
32+
// So this is a hack that sniffs whether we have reached
33+
// the last expression. We hold off the subVM's ready hook
34+
// until we are actually ready.
35+
if (this.last) {
36+
this.subVM.$compiler.execHook('ready')
37+
}
1638
}
1739
},
1840

1941
build: function (value) {
2042
ViewModel = ViewModel || require('../viewmodel')
21-
var Ctor = this.Ctor || ViewModel
22-
this.component = new Ctor({
43+
var Ctor = this.Ctor || ViewModel,
44+
data = value
45+
if (this.arg) {
46+
data = {}
47+
data[this.arg] = value
48+
}
49+
this.subVM = new Ctor({
2350
el: this.el,
24-
data: value,
51+
data: data,
2552
compilerOptions: {
53+
// it is important to delay the ready hook
54+
// so that when it's called, all `v-with` wathcers
55+
// would have been set up.
56+
delayReady: !this.last,
2657
parentCompiler: this.compiler
2758
}
2859
})
2960
},
3061

62+
/**
63+
* For inhertied keys, need to watch
64+
* and sync back to the parent
65+
*/
66+
watch: function () {
67+
if (!this.arg) return
68+
var self = this,
69+
key = self.key,
70+
ownerVM = self.binding.compiler.vm
71+
this.subVM.$compiler.observer.on('change:' + this.arg, function (val) {
72+
if (!self.lock) {
73+
self.lock = true
74+
nextTick(function () {
75+
self.lock = false
76+
})
77+
}
78+
ownerVM.$set(key, val)
79+
})
80+
},
81+
3182
unbind: function () {
32-
this.component.$destroy()
83+
// all watchers are turned off during destroy
84+
// so no need to worry about it
85+
this.subVM.$destroy()
3386
}
3487

3588
}

test/functional/fixtures/component.html

+17-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313

1414
<!-- custom element alone -->
1515
<simple id="element"></simple>
16+
17+
<!-- v-with + binding sync -->
18+
<div id="with-sync" v-with="childHi:hi, childName:user.name">
19+
{{childHi}} {{childName}}
20+
</div>
21+
22+
<div id="component-with-sync" v-component="sync" v-with="childHi:hi, childName:user.name">
1623
</div>
1724

1825
<script src="../../../dist/vue.js"></script>
@@ -21,16 +28,22 @@
2128
Vue.config({debug: true})
2229

2330
Vue.component('avatar', {
24-
template: '{{hi}} {{name}}',
25-
ready: function () {
26-
console.log(JSON.stringify(this))
27-
}
31+
template: '{{hi}} {{name}}'
2832
})
2933

3034
Vue.component('simple', {
3135
template: '{{hi}} {{user.name}}'
3236
})
3337

38+
Vue.component('sync', {
39+
template: '{{childHi}} {{childName}}',
40+
ready: function () {
41+
// should sync back to parent
42+
this.childHi = 'hello'
43+
this.childName = 'Vue'
44+
}
45+
})
46+
3447
var app = new Vue({
3548
el: '#test',
3649
data: {

test/functional/specs/component.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
casper.test.begin('Components', 5, function (test) {
1+
casper.test.begin('Components', 7, function (test) {
22

33
casper
44
.start('./fixtures/component.html')
55
.then(function () {
6-
var expected = '123 Jack'
6+
var expected = 'hello Vue'
77
test.assertSelectorHasText('#component-and-with', expected)
88
test.assertSelectorHasText('#element-and-with', expected)
99
test.assertSelectorHasText('#component', expected)
1010
test.assertSelectorHasText('#with', expected)
1111
test.assertSelectorHasText('#element', expected)
12+
test.assertSelectorHasText('#with-sync', expected)
13+
test.assertSelectorHasText('#component-with-sync', expected)
1214
})
1315
.run(function () {
1416
test.done()

test/unit/specs/directives.js

+43
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,49 @@ describe('UNIT: Directives', function () {
624624
assert.strictEqual(t.$el.querySelector('span').textContent, testId)
625625
})
626626

627+
it('should accept args and sync parent and child', function (done) {
628+
var t = new Vue({
629+
template:
630+
'<span>{{test.msg}} {{n}}</span>'
631+
+ '<p v-with="childMsg:test.msg, n:n" v-ref="child">{{childMsg}} {{n}}</p>',
632+
data: {
633+
n: 1,
634+
test: {
635+
msg: 'haha!'
636+
}
637+
}
638+
})
639+
640+
nextTick(function () {
641+
assert.strictEqual(t.$el.querySelector('span').textContent, 'haha! 1')
642+
assert.strictEqual(t.$el.querySelector('p').textContent, 'haha! 1')
643+
testParentToChild()
644+
})
645+
646+
function testParentToChild () {
647+
// test sync from parent to child
648+
t.test = { msg: 'hehe!' }
649+
nextTick(function () {
650+
assert.strictEqual(t.$el.querySelector('p').textContent, 'hehe! 1')
651+
testChildToParent()
652+
})
653+
}
654+
655+
function testChildToParent () {
656+
// test sync back
657+
t.$.child.childMsg = 'hoho!'
658+
t.$.child.n = 2
659+
assert.strictEqual(t.test.msg, 'hoho!')
660+
assert.strictEqual(t.n, 2)
661+
nextTick(function () {
662+
assert.strictEqual(t.$el.querySelector('span').textContent, 'hoho! 2')
663+
assert.strictEqual(t.$el.querySelector('p').textContent, 'hoho! 2')
664+
done()
665+
})
666+
}
667+
668+
})
669+
627670
})
628671

629672
describe('ref', function () {

0 commit comments

Comments
 (0)