]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(vModel): handle true-value and false-value for checkbox (#449)
authorCédric Exbrayat <cexbrayat@users.noreply.github.com>
Tue, 12 Nov 2019 21:24:39 +0000 (22:24 +0100)
committerEvan You <yyx990803@gmail.com>
Tue, 12 Nov 2019 21:24:39 +0000 (16:24 -0500)
packages/runtime-dom/__tests__/directives/vModel.spec.ts
packages/runtime-dom/src/directives/vModel.ts
packages/runtime-dom/src/patchProp.ts

index 357e827beff7beecefbc57065c0d798788e49167..bb263a3a1057815158c65642a1ab4ee766ff6711 100644 (file)
@@ -189,6 +189,101 @@ describe('vModel', () => {
     data.value = false
     await nextTick()
     expect(input.checked).toEqual(false)
+
+    data.value = true
+    await nextTick()
+    expect(input.checked).toEqual(true)
+
+    input.checked = false
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual(false)
+  })
+
+  it('should work with checkbox and true-value/false-value', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              type: 'checkbox',
+              'true-value': 'yes',
+              'false-value': 'no',
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('input')
+    const data = root._vnode.component.data
+
+    input.checked = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual('yes')
+
+    data.value = 'no'
+    await nextTick()
+    expect(input.checked).toEqual(false)
+
+    data.value = 'yes'
+    await nextTick()
+    expect(input.checked).toEqual(true)
+
+    input.checked = false
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual('no')
+  })
+
+  it('should work with checkbox and true-value/false-value with object values', async () => {
+    const component = createComponent({
+      data() {
+        return { value: null }
+      },
+      render() {
+        return [
+          withVModel(
+            h('input', {
+              type: 'checkbox',
+              'true-value': { yes: 'yes' },
+              'false-value': { no: 'no' },
+              'onUpdate:modelValue': setValue.bind(this)
+            }),
+            this.value
+          )
+        ]
+      }
+    })
+    app.mount(component, root)
+
+    const input = root.querySelector('input')
+    const data = root._vnode.component.data
+
+    input.checked = true
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual({ yes: 'yes' })
+
+    data.value = { no: 'no' }
+    await nextTick()
+    expect(input.checked).toEqual(false)
+
+    data.value = { yes: 'yes' }
+    await nextTick()
+    expect(input.checked).toEqual(true)
+
+    input.checked = false
+    triggerEvent('change', input)
+    await nextTick()
+    expect(data.value).toEqual({ no: 'no' })
   })
 
   it(`should support array as a checkbox model`, async () => {
index 6c30927707074534eaad65b6932a33861640091b..8cc9ea7b8b3acce762b361b8a83717b724962a7d 100644 (file)
@@ -101,7 +101,7 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
           assign(filtered)
         }
       } else {
-        assign(checked)
+        assign(getCheckboxValue(el, checked))
       }
     })
   },
@@ -119,7 +119,7 @@ function setChecked(
   if (isArray(value)) {
     el.checked = looseIndexOf(value, vnode.props!.value) > -1
   } else if (value !== oldValue) {
-    el.checked = !!value
+    el.checked = looseEqual(value, getCheckboxValue(el, true))
   }
 }
 
@@ -228,6 +228,15 @@ function getValue(el: HTMLOptionElement | HTMLInputElement) {
   return '_value' in el ? (el as any)._value : el.value
 }
 
+// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
+function getCheckboxValue(
+  el: HTMLInputElement & { _trueValue?: any; _falseValue?: any },
+  checked: boolean
+) {
+  const key = checked ? '_trueValue' : '_falseValue'
+  return key in el ? el[key] : checked
+}
+
 export const vModelDynamic: ObjectDirective<
   HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
 > = {
index a74faa88e2fbf1877605f855e1c9697d53284921..1f8ff8133fdad10db3fe27e9ffc6da63ebb43276 100644 (file)
@@ -5,9 +5,9 @@ import { patchDOMProp } from './modules/props'
 import { patchEvent } from './modules/events'
 import { isOn } from '@vue/shared'
 import {
-  VNode,
   ComponentInternalInstance,
-  SuspenseBoundary
+  SuspenseBoundary,
+  VNode
 } from '@vue/runtime-core'
 
 export function patchProp(
@@ -53,6 +53,15 @@ export function patchProp(
           unmountChildren
         )
       } else {
+        // special case for <input v-model type="checkbox"> with
+        // :true-value & :false-value
+        // store value as dom properties since non-string values will be
+        // stringified.
+        if (key === 'true-value') {
+          ;(el as any)._trueValue = nextValue
+        } else if (key === 'false-value') {
+          ;(el as any)._falseValue = nextValue
+        }
         patchAttr(el, key, nextValue)
       }
       break