Browse Source

Merge pull request #558 from HugoDF/feat-x-model-checkbox-number

Feature: x-model checkbox array `.number` modifier
Caleb Porzio 5 năm trước cách đây
mục cha
commit
351f8041dc
2 tập tin đã thay đổi với 46 bổ sung6 xóa
  1. 11 6
      src/directives/model.js
  2. 35 0
      test/model.spec.js

+ 11 - 6
src/directives/model.js

@@ -1,4 +1,5 @@
 import { registerListener } from './on'
+import { isNumeric } from '../utils'
 
 export function registerModelListener(component, el, modifiers, expression, extraVars) {
     // If the element we are binding to is a select, a radio, or checkbox
@@ -31,9 +32,10 @@ function generateModelAssignmentFunction(el, modifiers, expression) {
         if (event instanceof CustomEvent && event.detail) {
             return event.detail
         } else if (el.type === 'checkbox') {
-            // If the data we are binding to is an array, toggle it's value inside the array.
+            // If the data we are binding to is an array, toggle its value inside the array.
             if (Array.isArray(currentValue)) {
-                return event.target.checked ? currentValue.concat([event.target.value]) : currentValue.filter(i => i !== event.target.value)
+                const newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value
+                return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(i => i !== newValue)
             } else {
                 return event.target.checked
             }
@@ -41,18 +43,21 @@ function generateModelAssignmentFunction(el, modifiers, expression) {
             return modifiers.includes('number')
                 ? Array.from(event.target.selectedOptions).map(option => {
                     const rawValue = option.value || option.text
-                    const number = rawValue ? parseFloat(rawValue) : null
-                    return isNaN(number) ? rawValue : number
+                    return safeParseNumber(rawValue)
                 })
                 : Array.from(event.target.selectedOptions).map(option => {
                     return option.value || option.text
                 })
         } else {
             const rawValue = event.target.value
-            const number = rawValue ? parseFloat(rawValue) : null
             return modifiers.includes('number')
-                ? (isNaN(number) ? rawValue : number)
+                ? safeParseNumber(rawValue)
                 : (modifiers.includes('trim') ? rawValue.trim() : rawValue)
         }
     }
 }
+
+function safeParseNumber(rawValue) {
+    const number = rawValue ? parseFloat(rawValue) : null
+    return isNumeric(number) ? number : rawValue
+}

+ 35 - 0
test/model.spec.js

@@ -170,6 +170,41 @@ test('x-model binds checkbox value to array', async () => {
     })
 })
 
+test('x-model checkbox array binding supports .number modifier', async () => {
+    document.body.innerHTML = `
+        <div
+            x-data="{
+                selected: [2]
+            }"
+        >
+            <input type="checkbox" value="1" x-model.number="selected" />
+            <input type="checkbox" value="2" x-model.number="selected" />
+            <input type="checkbox" value="3" x-model.number="selected" />
+
+            <span x-bind:bar="JSON.stringify(selected)"></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelectorAll('input[type=checkbox]')[0].checked).toEqual(false)
+    expect(document.querySelectorAll('input[type=checkbox]')[1].checked).toEqual(true)
+    expect(document.querySelectorAll('input[type=checkbox]')[2].checked).toEqual(false)
+    expect(document.querySelector('span').getAttribute('bar')).toEqual("[2]")
+
+    fireEvent.change(document.querySelectorAll('input[type=checkbox]')[2], { target: { checked: true }})
+
+    await wait(() => { expect(document.querySelector('span').getAttribute('bar')).toEqual("[2,3]") })
+
+    fireEvent.change(document.querySelectorAll('input[type=checkbox]')[0], { target: { checked: true }})
+
+    await wait(() => { expect(document.querySelector('span').getAttribute('bar')).toEqual("[2,3,1]") })
+
+    fireEvent.change(document.querySelectorAll('input[type=checkbox]')[0], { target: { checked: false }})
+    fireEvent.change(document.querySelectorAll('input[type=checkbox]')[1], { target: { checked: false }})
+    await wait(() => { expect(document.querySelector('span').getAttribute('bar')).toEqual("[3]") })
+})
+
 test('x-model binds radio value', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">