Skip to content

Commit e1da0d5

Browse files
committed
feat(v-model): craete non-existent properties as reactive
close #5932
1 parent 2431d3d commit e1da0d5

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

src/compiler/directives/model.js

+34-19
Original file line numberDiff line numberDiff line change
@@ -37,42 +37,57 @@ export function genAssignmentCode (
3737
value: string,
3838
assignment: string
3939
): string {
40-
const modelRs = parseModel(value)
41-
if (modelRs.idx === null) {
40+
const res = parseModel(value)
41+
if (res.key === null) {
4242
return `${value}=${assignment}`
4343
} else {
44-
return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
44+
return `$set(${res.exp}, ${res.key}, ${assignment})`
4545
}
4646
}
4747

4848
/**
49-
* parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
49+
* Parse a v-model expression into a base path and a final key segment.
50+
* Handles both dot-path and possible square brackets.
5051
*
51-
* for loop possible cases:
52+
* Possible cases:
5253
*
5354
* - test
54-
* - test[idx]
55-
* - test[test1[idx]]
56-
* - test["a"][idx]
57-
* - xxx.test[a[a].test1[idx]]
58-
* - test.xxx.a["asa"][test1[idx]]
55+
* - test[key]
56+
* - test[test1[key]]
57+
* - test["a"][key]
58+
* - xxx.test[a[a].test1[key]]
59+
* - test.xxx.a["asa"][test1[key]]
5960
*
6061
*/
6162

6263
let len, str, chr, index, expressionPos, expressionEndPos
6364

64-
export function parseModel (val: string): Object {
65-
str = val
66-
len = str.length
67-
index = expressionPos = expressionEndPos = 0
65+
type ModelParseResult = {
66+
exp: string,
67+
key: string | null
68+
}
69+
70+
export function parseModel (val: string): ModelParseResult {
71+
len = val.length
6872

6973
if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
70-
return {
71-
exp: val,
72-
idx: null
74+
index = val.lastIndexOf('.')
75+
if (index > -1) {
76+
return {
77+
exp: val.slice(0, index),
78+
key: '"' + val.slice(index + 1) + '"'
79+
}
80+
} else {
81+
return {
82+
exp: val,
83+
key: null
84+
}
7385
}
7486
}
7587

88+
str = val
89+
index = expressionPos = expressionEndPos = 0
90+
7691
while (!eof()) {
7792
chr = next()
7893
/* istanbul ignore if */
@@ -84,8 +99,8 @@ export function parseModel (val: string): Object {
8499
}
85100

86101
return {
87-
exp: val.substring(0, expressionPos),
88-
idx: val.substring(expressionPos + 1, expressionEndPos)
102+
exp: val.slice(0, expressionPos),
103+
key: val.slice(expressionPos + 1, expressionEndPos)
89104
}
90105
}
91106

Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
import { parseModel } from 'compiler/directives/model'
22

33
describe('model expression parser', () => {
4+
it('parse single path', () => {
5+
const res = parseModel('foo')
6+
expect(res.exp).toBe('foo')
7+
expect(res.key).toBe(null)
8+
})
9+
410
it('parse object dot notation', () => {
511
const res = parseModel('a.b.c')
6-
expect(res.exp).toBe('a.b.c')
7-
expect(res.idx).toBe(null)
12+
expect(res.exp).toBe('a.b')
13+
expect(res.key).toBe('"c"')
814
})
915

1016
it('parse string in brackets', () => {
1117
const res = parseModel('a["b"][c]')
1218
expect(res.exp).toBe('a["b"]')
13-
expect(res.idx).toBe('c')
19+
expect(res.key).toBe('c')
1420
})
1521

1622
it('parse brackets with object dot notation', () => {
1723
const res = parseModel('a["b"][c].xxx')
18-
expect(res.exp).toBe('a["b"][c].xxx')
19-
expect(res.idx).toBe(null)
24+
expect(res.exp).toBe('a["b"][c]')
25+
expect(res.key).toBe('"xxx"')
2026
})
2127

2228
it('parse nested brackets', () => {
2329
const res = parseModel('a[i[c]]')
2430
expect(res.exp).toBe('a')
25-
expect(res.idx).toBe('i[c]')
31+
expect(res.key).toBe('i[c]')
2632
})
2733

2834
it('combined', () => {
29-
const res = parseModel('test.xxx.a["asa"][test1[idx]]')
35+
const res = parseModel('test.xxx.a["asa"][test1[key]]')
3036
expect(res.exp).toBe('test.xxx.a["asa"]')
31-
expect(res.idx).toBe('test1[idx]')
37+
expect(res.key).toBe('test1[key]')
3238
})
3339
})

test/unit/features/directives/model-text.spec.js

+21
Original file line numberDiff line numberDiff line change
@@ -354,5 +354,26 @@ describe('Directive v-model text', () => {
354354
done()
355355
}, 16)
356356
})
357+
358+
it('should create and make reactive non-existent properties', done => {
359+
const vm = new Vue({
360+
data: {
361+
foo: {}
362+
},
363+
template: '<input v-model="foo.bar">'
364+
}).$mount()
365+
expect(vm.$el.value).toBe('')
366+
367+
vm.$el.value = 'a'
368+
triggerEvent(vm.$el, 'input')
369+
expect(vm.foo.bar).toBe('a')
370+
vm.foo.bar = 'b'
371+
waitForUpdate(() => {
372+
expect(vm.$el.value).toBe('b')
373+
vm.foo = {}
374+
}).then(() => {
375+
expect(vm.$el.value).toBe('')
376+
}).then(done)
377+
})
357378
}
358379
})

0 commit comments

Comments
 (0)