Skip to content

Commit 0cd1489

Browse files
defccyyx990803
authored andcommitted
.number modifier should work with select, radio, checkbox (fix #4018) (#4022)
* support number modifier in select, radio, checkbox * add test case * add ASTModifier type to specify modifiers type * fix typo * keep code consistent
1 parent 4a3b4c4 commit 0cd1489

File tree

7 files changed

+104
-19
lines changed

7 files changed

+104
-19
lines changed

flow/compiler.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ declare type ModuleOptions = {
4141
staticKeys?: Array<string>; // AST properties to be considered static
4242
}
4343

44+
declare type ASTModifiers = { [key: string]: boolean }
45+
4446
declare type ASTElementHandler = {
4547
value: string;
46-
modifiers: ?{ [key: string]: true };
48+
modifiers: ?ASTModifiers;
4749
}
4850

4951
declare type ASTElementHandlers = {
@@ -55,7 +57,7 @@ declare type ASTDirective = {
5557
rawName: string;
5658
value: string;
5759
arg: ?string;
58-
modifiers: ?{ [key: string]: true };
60+
modifiers: ?ASTModifiers;
5961
}
6062

6163
declare type ASTNode = ASTElement | ASTText | ASTExpression

flow/vnode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ declare type VNodeDirective = {
6060
value?: any;
6161
oldValue?: any;
6262
arg?: string;
63-
modifiers?: { [key: string]: boolean };
63+
modifiers?: ASTModifiers;
6464
def?: Object;
6565
}

src/compiler/helpers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function addDirective (
2727
rawName: string,
2828
value: string,
2929
arg: ?string,
30-
modifiers: ?{ [key: string]: true }
30+
modifiers: ?ASTModifiers
3131
) {
3232
(el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
3333
}
@@ -36,7 +36,7 @@ export function addHandler (
3636
el: ASTElement,
3737
name: string,
3838
value: string,
39-
modifiers: ?{ [key: string]: true },
39+
modifiers: ?ASTModifiers,
4040
important: ?boolean
4141
) {
4242
// check capture modifier

src/platforms/web/compiler/directives/model.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,23 @@ export default function model (
2626
}
2727
}
2828
if (tag === 'select') {
29-
genSelect(el, value)
29+
genSelect(el, value, modifiers)
3030
} else if (tag === 'input' && type === 'checkbox') {
31-
genCheckboxModel(el, value)
31+
genCheckboxModel(el, value, modifiers)
3232
} else if (tag === 'input' && type === 'radio') {
33-
genRadioModel(el, value)
33+
genRadioModel(el, value, modifiers)
3434
} else {
3535
genDefaultModel(el, value, modifiers)
3636
}
3737
// ensure runtime directive metadata
3838
return true
3939
}
4040

41-
function genCheckboxModel (el: ASTElement, value: string) {
41+
function genCheckboxModel (
42+
el: ASTElement,
43+
value: string,
44+
modifiers: ?ASTModifiers
45+
) {
4246
if (process.env.NODE_ENV !== 'production' &&
4347
el.attrsMap.checked != null) {
4448
warn(
@@ -47,6 +51,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
4751
'Declare initial values in the component\'s data option instead.'
4852
)
4953
}
54+
const number = modifiers && modifiers.number
5055
const valueBinding = getBindingAttr(el, 'value') || 'null'
5156
const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
5257
const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
@@ -60,7 +65,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
6065
'$$el=$event.target,' +
6166
`$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
6267
'if(Array.isArray($$a)){' +
63-
`var $$v=${valueBinding},` +
68+
`var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
6469
'$$i=_i($$a,$$v);' +
6570
`if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +
6671
`else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
@@ -69,7 +74,11 @@ function genCheckboxModel (el: ASTElement, value: string) {
6974
)
7075
}
7176

72-
function genRadioModel (el: ASTElement, value: string) {
77+
function genRadioModel (
78+
el: ASTElement,
79+
value: string,
80+
modifiers: ?ASTModifiers
81+
) {
7382
if (process.env.NODE_ENV !== 'production' &&
7483
el.attrsMap.checked != null) {
7584
warn(
@@ -78,15 +87,17 @@ function genRadioModel (el: ASTElement, value: string) {
7887
'Declare initial values in the component\'s data option instead.'
7988
)
8089
}
81-
const valueBinding = getBindingAttr(el, 'value') || 'null'
90+
const number = modifiers && modifiers.number
91+
let valueBinding = getBindingAttr(el, 'value') || 'null'
92+
valueBinding = number ? `_n(${valueBinding})` : valueBinding
8293
addProp(el, 'checked', `_q(${value},${valueBinding})`)
8394
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
8495
}
8596

8697
function genDefaultModel (
8798
el: ASTElement,
8899
value: string,
89-
modifiers: ?Object
100+
modifiers: ?ASTModifiers
90101
): ?boolean {
91102
if (process.env.NODE_ENV !== 'production') {
92103
if (el.tag === 'input' && el.attrsMap.value) {
@@ -111,12 +122,13 @@ function genDefaultModel (
111122
const needCompositionGuard = !lazy && type !== 'range'
112123
const isNative = el.tag === 'input' || el.tag === 'textarea'
113124

114-
const valueExpression = isNative
125+
let valueExpression = isNative
115126
? `$event.target.value${trim ? '.trim()' : ''}`
116127
: `$event`
117-
let code = number || type === 'number'
118-
? genAssignmentCode(value, `_n(${valueExpression})`)
119-
: genAssignmentCode(value, valueExpression)
128+
valueExpression = number || type === 'number'
129+
? `_n(${valueExpression})`
130+
: valueExpression
131+
let code = genAssignmentCode(value, valueExpression)
120132
if (isNative && needCompositionGuard) {
121133
code = `if($event.target.composing)return;${code}`
122134
}
@@ -133,14 +145,20 @@ function genDefaultModel (
133145
addHandler(el, event, code, null, true)
134146
}
135147

136-
function genSelect (el: ASTElement, value: string) {
148+
function genSelect (
149+
el: ASTElement,
150+
value: string,
151+
modifiers: ?ASTModifiers
152+
) {
137153
if (process.env.NODE_ENV !== 'production') {
138154
el.children.some(checkOptionWarning)
139155
}
140156

157+
const number = modifiers && modifiers.number
141158
const assignment = `Array.prototype.filter` +
142159
`.call($event.target.options,function(o){return o.selected})` +
143-
`.map(function(o){return "_value" in o ? o._value : o.value})` +
160+
`.map(function(o){var val = "_value" in o ? o._value : o.value;` +
161+
`return ${number ? '_n(val)' : 'val'}})` +
144162
(el.attrsMap.multiple == null ? '[0]' : '')
145163

146164
const code = genAssignmentCode(value, assignment)

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,32 @@ describe('Directive v-model checkbox', () => {
133133
}).then(done)
134134
})
135135

136+
it('.number modifier', () => {
137+
const vm = new Vue({
138+
data: {
139+
test: [],
140+
check: true
141+
},
142+
template: `
143+
<div>
144+
<input type="checkbox" v-model.number="test" value="1">
145+
<input type="checkbox" v-model="test" value="2">
146+
<input type="checkbox" v-model.number="check">
147+
</div>
148+
`
149+
}).$mount()
150+
document.body.appendChild(vm.$el)
151+
var checkboxInputs = vm.$el.getElementsByTagName('input')
152+
expect(checkboxInputs[0].checked).toBe(false)
153+
expect(checkboxInputs[1].checked).toBe(false)
154+
expect(checkboxInputs[2].checked).toBe(true)
155+
checkboxInputs[0].click()
156+
checkboxInputs[1].click()
157+
checkboxInputs[2].click()
158+
expect(vm.test).toEqual([1, '2'])
159+
expect(vm.check).toEqual(false)
160+
})
161+
136162
it('warn inline checked', () => {
137163
const vm = new Vue({
138164
template: `<input type="checkbox" v-model="test" checked>`,

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,27 @@ describe('Directive v-model radio', () => {
125125
}).then(done)
126126
})
127127

128+
it('.number modifier', () => {
129+
const vm = new Vue({
130+
data: {
131+
test: 1
132+
},
133+
template: `
134+
<div>
135+
<input type="radio" value="1" v-model="test" name="test">
136+
<input type="radio" value="2" v-model.number="test" name="test">
137+
</div>
138+
`
139+
}).$mount()
140+
document.body.appendChild(vm.$el)
141+
expect(vm.$el.children[0].checked).toBe(true)
142+
expect(vm.$el.children[1].checked).toBe(false)
143+
vm.$el.children[1].click()
144+
expect(vm.$el.children[0].checked).toBe(false)
145+
expect(vm.$el.children[1].checked).toBe(true)
146+
expect(vm.test).toBe(2)
147+
})
148+
128149
it('warn inline checked', () => {
129150
const vm = new Vue({
130151
template: `<input v-model="test" type="radio" value="1" checked>`,

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,24 @@ describe('Directive v-model select', () => {
301301
}).then(done)
302302
})
303303

304+
it('.number modifier', () => {
305+
const vm = new Vue({
306+
data: {
307+
test: 2
308+
},
309+
template:
310+
'<select v-model.number="test">' +
311+
'<option value="1">a</option>' +
312+
'<option :value="2">b</option>' +
313+
' <option :value="3">c</option>' +
314+
'</select>'
315+
}).$mount()
316+
document.body.appendChild(vm.$el)
317+
updateSelect(vm.$el, '1')
318+
triggerEvent(vm.$el, 'change')
319+
expect(vm.test).toBe(1)
320+
})
321+
304322
it('should warn inline selected', () => {
305323
const vm = new Vue({
306324
data: {

0 commit comments

Comments
 (0)