Skip to content

Commit

Permalink
v-model binding with array. (fix #3958,#3979) (#3988)
Browse files Browse the repository at this point in the history
* fix v-model with array binding

* add mutli selects test case

* add test case. v-bind with array

* add comments

* code refactor
  • Loading branch information
defcc authored and yyx990803 committed Oct 22, 2016
1 parent 5f8ae40 commit 3105661
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 7 deletions.
24 changes: 20 additions & 4 deletions src/platforms/web/compiler/directives/model.js
Expand Up @@ -2,6 +2,7 @@

import { isIE } from 'core/util/env'
import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
import parseModel from 'web/util/model'

let warn

Expand Down Expand Up @@ -79,7 +80,7 @@ function genRadioModel (el: ASTElement, value: string) {
}
const valueBinding = getBindingAttr(el, 'value') || 'null'
addProp(el, 'checked', `_q(${value},${valueBinding})`)
addHandler(el, 'change', `${value}=${valueBinding}`, null, true)
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
}

function genDefaultModel (
Expand Down Expand Up @@ -114,8 +115,8 @@ function genDefaultModel (
? `$event.target.value${trim ? '.trim()' : ''}`
: `$event`
let code = number || type === 'number'
? `${value}=_n(${valueExpression})`
: `${value}=${valueExpression}`
? genAssignmentCode(value, `_n(${valueExpression})`)
: genAssignmentCode(value, valueExpression)
if (isNative && needCompositionGuard) {
code = `if($event.target.composing)return;${code}`
}
Expand All @@ -136,10 +137,13 @@ function genSelect (el: ASTElement, value: string) {
if (process.env.NODE_ENV !== 'production') {
el.children.some(checkOptionWarning)
}
const code = `${value}=Array.prototype.filter` +

const assignment = `Array.prototype.filter` +
`.call($event.target.options,function(o){return o.selected})` +
`.map(function(o){return "_value" in o ? o._value : o.value})` +
(el.attrsMap.multiple == null ? '[0]' : '')

const code = genAssignmentCode(value, assignment)
addHandler(el, 'change', code, null, true)
}

Expand All @@ -156,3 +160,15 @@ function checkOptionWarning (option: any): boolean {
}
return false
}

function genAssignmentCode (value: string, assignment: string): string {
const modelRs = parseModel(value)
if (modelRs.idx === null) {
return `${value}=${assignment}`
} else {
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
`if (!Array.isArray($$exp)){` +
`${value}=${assignment}}` +
`else{$$exp.splice($$idx, 1, ${assignment})}`
}
}
84 changes: 84 additions & 0 deletions src/platforms/web/util/model.js
@@ -0,0 +1,84 @@
/* @flow */

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

/**
* parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
*
* for loop possible cases:
*
* - test
* - test[idx]
* - test[test1[idx]]
* - test["a"][idx]
* - xxx.test[a[a].test1[idx]]
* - test.xxx.a["asa"][test1[idx]]
*
*/

export default function parseModel (val: string): Object {
str = val
len = str.length
index = expressionPos = expressionEndPos = 0

if (val.indexOf('[') < 0) {
return {
exp: val,
idx: null
}
}

while (!eof()) {
chr = next()
if (isStringStart(chr)) {
parseString(chr)
} else if (chr === 0x5B) {
parseBracket(chr)
}
}

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

function next (): number {
return str.charCodeAt(++index)
}

function eof (): boolean {
return index >= len
}

function isStringStart (chr: number): boolean {
return chr === 0x22 || chr === 0x27
}

function parseBracket (chr: number): void {
let inBracket = 1
expressionPos = index
while (!eof()) {
chr = next()
if (isStringStart(chr)) {
parseString(chr)
continue
}
if (chr === 0x5B) inBracket++
if (chr === 0x5D) inBracket--
if (inBracket === 0) {
expressionEndPos = index
break
}
}
}

function parseString (chr: number): void {
const stringQuote = chr
while (!eof()) {
chr = next()
if (chr === stringQuote) {
break
}
}
}
11 changes: 8 additions & 3 deletions test/unit/features/directives/model-component.spec.js
Expand Up @@ -2,14 +2,18 @@ import Vue from 'vue'

describe('Directive v-model component', () => {
it('should work', done => {
const spy = jasmine.createSpy()
const vm = new Vue({
data: {
msg: 'hello'
msg: ['hello']
},
watch: {
msg: spy
},
template: `
<div>
<p>{{ msg }}</p>
<validate v-model="msg">
<validate v-model="msg[0]">
<input type="text">
</validate>
</div>
Expand Down Expand Up @@ -40,7 +44,8 @@ describe('Directive v-model component', () => {
input.value = 'world'
triggerEvent(input, 'input')
}).then(() => {
expect(vm.msg).toBe('world')
expect(vm.msg).toEqual(['world'])
expect(spy).toHaveBeenCalled()
}).then(() => {
document.body.removeChild(vm.$el)
vm.$destroy()
Expand Down
40 changes: 40 additions & 0 deletions test/unit/features/directives/model-radio.spec.js
Expand Up @@ -85,6 +85,46 @@ describe('Directive v-model radio', () => {
}).then(done)
})

it('multiple radios ', (done) => {
const spy = jasmine.createSpy()
const vm = new Vue({
data: {
selections: ['a', '1'],
radioList: [
{
name: 'questionA',
data: ['a', 'b', 'c']
},
{
name: 'questionB',
data: ['1', '2']
}
]
},
watch: {
selections: spy
},
template:
'<div>' +
'<div v-for="(radioGroup, idx) in radioList">' +
'<div>' +
'<span v-for="(item, index) in radioGroup.data">' +
'<input :name="radioGroup.name" type="radio" :value="item" v-model="selections[idx]" :id="idx"/>' +
'<label>{{item}}</label>' +
'</span>' +
'</div>' +
'</div>' +
'</div>'
}).$mount()
document.body.appendChild(vm.$el)
var inputs = vm.$el.getElementsByTagName('input')
inputs[1].click()
waitForUpdate(() => {
expect(vm.selections).toEqual(['b', '1'])
expect(spy).toHaveBeenCalled()
}).then(done)
})

it('warn inline checked', () => {
const vm = new Vue({
template: `<input v-model="test" type="radio" value="1" checked>`,
Expand Down
38 changes: 38 additions & 0 deletions test/unit/features/directives/model-select.spec.js
Expand Up @@ -263,6 +263,44 @@ describe('Directive v-model select', () => {
}).then(done)
})

it('multiple selects', (done) => {
const spy = jasmine.createSpy()
const vm = new Vue({
data: {
selections: ['', ''],
selectBoxes: [
[
{ value: 'foo', text: 'foo' },
{ value: 'bar', text: 'bar' }
],
[
{ value: 'day', text: 'day' },
{ value: 'night', text: 'night' }
]
]
},
watch: {
selections: spy
},
template:
'<div>' +
'<select v-for="(item, index) in selectBoxes" v-model="selections[index]">' +
'<option v-for="element in item" v-bind:value="element.value" v-text="element.text"></option>' +
'</select>' +
'<span ref="rs">{{selections}}</span>' +
'</div>'
}).$mount()
document.body.appendChild(vm.$el)
var selects = vm.$el.getElementsByTagName('select')
var select0 = selects[0]
select0.options[0].selected = true
triggerEvent(select0, 'change')
waitForUpdate(() => {
expect(spy).toHaveBeenCalled()
expect(vm.selections).toEqual(['foo', ''])
}).then(done)
})

it('should warn inline selected', () => {
const vm = new Vue({
data: {
Expand Down
41 changes: 41 additions & 0 deletions test/unit/features/directives/model-text.spec.js
Expand Up @@ -64,6 +64,47 @@ describe('Directive v-model text', () => {
expect(vm.test).toBe('what')
})

it('multiple inputs', (done) => {
const spy = jasmine.createSpy()
const vm = new Vue({
data: {
selections: [[1, 2, 3], [4, 5]],
inputList: [
{
name: 'questionA',
data: ['a', 'b', 'c']
},
{
name: 'questionB',
data: ['1', '2']
}
]
},
watch: {
selections: spy
},
template:
'<div>' +
'<div v-for="(inputGroup, idx) in inputList">' +
'<div>' +
'<span v-for="(item, index) in inputGroup.data">' +
'<input v-bind:name="item" type="text" v-model.number="selections[idx][index]" v-bind:id="idx+\'-\'+index"/>' +
'<label>{{item}}</label>' +
'</span>' +
'</div>' +
'</div>' +
'<span ref="rs">{{selections}}</span>' +
'</div>'
}).$mount()
var inputs = vm.$el.getElementsByTagName('input')
inputs[1].value = 'test'
triggerEvent(inputs[1], 'input')
waitForUpdate(() => {
expect(spy).toHaveBeenCalled()
expect(vm.selections).toEqual([[1, 'test', 3], [4, 5]])
}).then(done)
})

if (isIE9) {
it('IE9 selectionchange', done => {
const vm = new Vue({
Expand Down

0 comments on commit 3105661

Please sign in to comment.