ソースを参照

.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
chengchao 9 年 前
コミット
0cd1489e68

+ 4 - 2
flow/compiler.js

@@ -41,9 +41,11 @@ declare type ModuleOptions = {
   staticKeys?: Array<string>; // AST properties to be considered static
 }
 
+declare type ASTModifiers = { [key: string]: boolean }
+
 declare type ASTElementHandler = {
   value: string;
-  modifiers: ?{ [key: string]: true };
+  modifiers: ?ASTModifiers;
 }
 
 declare type ASTElementHandlers = {
@@ -55,7 +57,7 @@ declare type ASTDirective = {
   rawName: string;
   value: string;
   arg: ?string;
-  modifiers: ?{ [key: string]: true };
+  modifiers: ?ASTModifiers;
 }
 
 declare type ASTNode = ASTElement | ASTText | ASTExpression

+ 1 - 1
flow/vnode.js

@@ -60,6 +60,6 @@ declare type VNodeDirective = {
   value?: any;
   oldValue?: any;
   arg?: string;
-  modifiers?: { [key: string]: boolean };
+  modifiers?: ASTModifiers;
   def?: Object;
 }

+ 2 - 2
src/compiler/helpers.js

@@ -27,7 +27,7 @@ export function addDirective (
   rawName: string,
   value: string,
   arg: ?string,
-  modifiers: ?{ [key: string]: true }
+  modifiers: ?ASTModifiers
 ) {
   (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
 }
@@ -36,7 +36,7 @@ export function addHandler (
   el: ASTElement,
   name: string,
   value: string,
-  modifiers: ?{ [key: string]: true },
+  modifiers: ?ASTModifiers,
   important: ?boolean
 ) {
   // check capture modifier

+ 32 - 14
src/platforms/web/compiler/directives/model.js

@@ -26,11 +26,11 @@ export default function model (
     }
   }
   if (tag === 'select') {
-    genSelect(el, value)
+    genSelect(el, value, modifiers)
   } else if (tag === 'input' && type === 'checkbox') {
-    genCheckboxModel(el, value)
+    genCheckboxModel(el, value, modifiers)
   } else if (tag === 'input' && type === 'radio') {
-    genRadioModel(el, value)
+    genRadioModel(el, value, modifiers)
   } else {
     genDefaultModel(el, value, modifiers)
   }
@@ -38,7 +38,11 @@ export default function model (
   return true
 }
 
-function genCheckboxModel (el: ASTElement, value: string) {
+function genCheckboxModel (
+  el: ASTElement,
+  value: string,
+  modifiers: ?ASTModifiers
+) {
   if (process.env.NODE_ENV !== 'production' &&
     el.attrsMap.checked != null) {
     warn(
@@ -47,6 +51,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
       'Declare initial values in the component\'s data option instead.'
     )
   }
+  const number = modifiers && modifiers.number
   const valueBinding = getBindingAttr(el, 'value') || 'null'
   const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
   const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
@@ -60,7 +65,7 @@ function genCheckboxModel (el: ASTElement, value: string) {
         '$$el=$event.target,' +
         `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
     'if(Array.isArray($$a)){' +
-      `var $$v=${valueBinding},` +
+      `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
           '$$i=_i($$a,$$v);' +
       `if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +
       `else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
@@ -69,7 +74,11 @@ function genCheckboxModel (el: ASTElement, value: string) {
   )
 }
 
-function genRadioModel (el: ASTElement, value: string) {
+function genRadioModel (
+    el: ASTElement,
+    value: string,
+    modifiers: ?ASTModifiers
+) {
   if (process.env.NODE_ENV !== 'production' &&
     el.attrsMap.checked != null) {
     warn(
@@ -78,7 +87,9 @@ function genRadioModel (el: ASTElement, value: string) {
       'Declare initial values in the component\'s data option instead.'
     )
   }
-  const valueBinding = getBindingAttr(el, 'value') || 'null'
+  const number = modifiers && modifiers.number
+  let valueBinding = getBindingAttr(el, 'value') || 'null'
+  valueBinding = number ? `_n(${valueBinding})` : valueBinding
   addProp(el, 'checked', `_q(${value},${valueBinding})`)
   addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
 }
@@ -86,7 +97,7 @@ function genRadioModel (el: ASTElement, value: string) {
 function genDefaultModel (
   el: ASTElement,
   value: string,
-  modifiers: ?Object
+  modifiers: ?ASTModifiers
 ): ?boolean {
   if (process.env.NODE_ENV !== 'production') {
     if (el.tag === 'input' && el.attrsMap.value) {
@@ -111,12 +122,13 @@ function genDefaultModel (
   const needCompositionGuard = !lazy && type !== 'range'
   const isNative = el.tag === 'input' || el.tag === 'textarea'
 
-  const valueExpression = isNative
+  let valueExpression = isNative
     ? `$event.target.value${trim ? '.trim()' : ''}`
     : `$event`
-  let code = number || type === 'number'
-    ? genAssignmentCode(value, `_n(${valueExpression})`)
-    : genAssignmentCode(value, valueExpression)
+  valueExpression = number || type === 'number'
+    ? `_n(${valueExpression})`
+    : valueExpression
+  let code = genAssignmentCode(value, valueExpression)
   if (isNative && needCompositionGuard) {
     code = `if($event.target.composing)return;${code}`
   }
@@ -133,14 +145,20 @@ function genDefaultModel (
   addHandler(el, event, code, null, true)
 }
 
-function genSelect (el: ASTElement, value: string) {
+function genSelect (
+    el: ASTElement,
+    value: string,
+    modifiers: ?ASTModifiers
+) {
   if (process.env.NODE_ENV !== 'production') {
     el.children.some(checkOptionWarning)
   }
 
+  const number = modifiers && modifiers.number
   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})` +
+    `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
+    `return ${number ? '_n(val)' : 'val'}})` +
     (el.attrsMap.multiple == null ? '[0]' : '')
 
   const code = genAssignmentCode(value, assignment)

+ 26 - 0
test/unit/features/directives/model-checkbox.spec.js

@@ -133,6 +133,32 @@ describe('Directive v-model checkbox', () => {
     }).then(done)
   })
 
+  it('.number modifier', () => {
+    const vm = new Vue({
+      data: {
+        test: [],
+        check: true
+      },
+      template: `
+        <div>
+          <input type="checkbox" v-model.number="test" value="1">
+          <input type="checkbox" v-model="test" value="2">
+          <input type="checkbox" v-model.number="check">
+        </div>
+      `
+    }).$mount()
+    document.body.appendChild(vm.$el)
+    var checkboxInputs = vm.$el.getElementsByTagName('input')
+    expect(checkboxInputs[0].checked).toBe(false)
+    expect(checkboxInputs[1].checked).toBe(false)
+    expect(checkboxInputs[2].checked).toBe(true)
+    checkboxInputs[0].click()
+    checkboxInputs[1].click()
+    checkboxInputs[2].click()
+    expect(vm.test).toEqual([1, '2'])
+    expect(vm.check).toEqual(false)
+  })
+
   it('warn inline checked', () => {
     const vm = new Vue({
       template: `<input type="checkbox" v-model="test" checked>`,

+ 21 - 0
test/unit/features/directives/model-radio.spec.js

@@ -125,6 +125,27 @@ describe('Directive v-model radio', () => {
     }).then(done)
   })
 
+  it('.number modifier', () => {
+    const vm = new Vue({
+      data: {
+        test: 1
+      },
+      template: `
+        <div>
+          <input type="radio" value="1" v-model="test" name="test">
+          <input type="radio" value="2" v-model.number="test" name="test">
+        </div>
+      `
+    }).$mount()
+    document.body.appendChild(vm.$el)
+    expect(vm.$el.children[0].checked).toBe(true)
+    expect(vm.$el.children[1].checked).toBe(false)
+    vm.$el.children[1].click()
+    expect(vm.$el.children[0].checked).toBe(false)
+    expect(vm.$el.children[1].checked).toBe(true)
+    expect(vm.test).toBe(2)
+  })
+
   it('warn inline checked', () => {
     const vm = new Vue({
       template: `<input v-model="test" type="radio" value="1" checked>`,

+ 18 - 0
test/unit/features/directives/model-select.spec.js

@@ -301,6 +301,24 @@ describe('Directive v-model select', () => {
     }).then(done)
   })
 
+  it('.number modifier', () => {
+    const vm = new Vue({
+      data: {
+        test: 2
+      },
+      template:
+        '<select v-model.number="test">' +
+          '<option value="1">a</option>' +
+          '<option :value="2">b</option>' +
+        ' <option :value="3">c</option>' +
+        '</select>'
+    }).$mount()
+    document.body.appendChild(vm.$el)
+    updateSelect(vm.$el, '1')
+    triggerEvent(vm.$el, 'change')
+    expect(vm.test).toBe(1)
+  })
+
   it('should warn inline selected', () => {
     const vm = new Vue({
       data: {