Skip to content

Commit

Permalink
feat(v-model): craete non-existent properties as reactive
Browse files Browse the repository at this point in the history
close #5932
  • Loading branch information
yyx990803 committed Oct 4, 2017
1 parent 2431d3d commit e1da0d5
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
53 changes: 34 additions & 19 deletions src/compiler/directives/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,42 +37,57 @@ export function genAssignmentCode (
value: string,
assignment: string
): string {
const modelRs = parseModel(value)
if (modelRs.idx === null) {
const res = parseModel(value)
if (res.key === null) {
return `${value}=${assignment}`
} else {
return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
return `$set(${res.exp}, ${res.key}, ${assignment})`
}
}

/**
* parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
* Parse a v-model expression into a base path and a final key segment.
* Handles both dot-path and possible square brackets.
*
* for loop possible cases:
* Possible cases:
*
* - test
* - test[idx]
* - test[test1[idx]]
* - test["a"][idx]
* - xxx.test[a[a].test1[idx]]
* - test.xxx.a["asa"][test1[idx]]
* - test[key]
* - test[test1[key]]
* - test["a"][key]
* - xxx.test[a[a].test1[key]]
* - test.xxx.a["asa"][test1[key]]
*
*/

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

export function parseModel (val: string): Object {
str = val
len = str.length
index = expressionPos = expressionEndPos = 0
type ModelParseResult = {
exp: string,
key: string | null
}

export function parseModel (val: string): ModelParseResult {
len = val.length

if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) {
return {
exp: val,
idx: null
index = val.lastIndexOf('.')
if (index > -1) {
return {
exp: val.slice(0, index),
key: '"' + val.slice(index + 1) + '"'
}
} else {
return {
exp: val,
key: null
}
}
}

str = val
index = expressionPos = expressionEndPos = 0

while (!eof()) {
chr = next()
/* istanbul ignore if */
Expand All @@ -84,8 +99,8 @@ export function parseModel (val: string): Object {
}

return {
exp: val.substring(0, expressionPos),
idx: val.substring(expressionPos + 1, expressionEndPos)
exp: val.slice(0, expressionPos),
key: val.slice(expressionPos + 1, expressionEndPos)
}
}

Expand Down
22 changes: 14 additions & 8 deletions test/unit/features/directives/model-parse.spec.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
import { parseModel } from 'compiler/directives/model'

describe('model expression parser', () => {
it('parse single path', () => {
const res = parseModel('foo')
expect(res.exp).toBe('foo')
expect(res.key).toBe(null)
})

it('parse object dot notation', () => {
const res = parseModel('a.b.c')
expect(res.exp).toBe('a.b.c')
expect(res.idx).toBe(null)
expect(res.exp).toBe('a.b')
expect(res.key).toBe('"c"')
})

it('parse string in brackets', () => {
const res = parseModel('a["b"][c]')
expect(res.exp).toBe('a["b"]')
expect(res.idx).toBe('c')
expect(res.key).toBe('c')
})

it('parse brackets with object dot notation', () => {
const res = parseModel('a["b"][c].xxx')
expect(res.exp).toBe('a["b"][c].xxx')
expect(res.idx).toBe(null)
expect(res.exp).toBe('a["b"][c]')
expect(res.key).toBe('"xxx"')
})

it('parse nested brackets', () => {
const res = parseModel('a[i[c]]')
expect(res.exp).toBe('a')
expect(res.idx).toBe('i[c]')
expect(res.key).toBe('i[c]')
})

it('combined', () => {
const res = parseModel('test.xxx.a["asa"][test1[idx]]')
const res = parseModel('test.xxx.a["asa"][test1[key]]')
expect(res.exp).toBe('test.xxx.a["asa"]')
expect(res.idx).toBe('test1[idx]')
expect(res.key).toBe('test1[key]')
})
})
21 changes: 21 additions & 0 deletions test/unit/features/directives/model-text.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,5 +354,26 @@ describe('Directive v-model text', () => {
done()
}, 16)
})

it('should create and make reactive non-existent properties', done => {
const vm = new Vue({
data: {
foo: {}
},
template: '<input v-model="foo.bar">'
}).$mount()
expect(vm.$el.value).toBe('')

vm.$el.value = 'a'
triggerEvent(vm.$el, 'input')
expect(vm.foo.bar).toBe('a')
vm.foo.bar = 'b'
waitForUpdate(() => {
expect(vm.$el.value).toBe('b')
vm.foo = {}
}).then(() => {
expect(vm.$el.value).toBe('')
}).then(done)
})
}
})

0 comments on commit e1da0d5

Please sign in to comment.