瀏覽代碼

Handle special binding case for 'checked' and 'selected' (#3535)

* Handle special binding case for 'checked' and 'selected'

* Fix typo in "bindAttributeAndProperty"

Co-authored-by: Eric Kwoka <43540491+ekwoka@users.noreply.github.com>

---------

Co-authored-by: Eric Kwoka <43540491+ekwoka@users.noreply.github.com>
Chris Morrell 2 年之前
父節點
當前提交
36156c2f31
共有 3 個文件被更改,包括 41 次插入1 次删除
  1. 19 0
      packages/alpinejs/src/utils/bind.js
  2. 20 1
      tests/cypress/integration/directives/x-bind.spec.js
  3. 2 0
      tests/cypress/utils.js

+ 19 - 0
packages/alpinejs/src/utils/bind.js

@@ -22,6 +22,14 @@ export default function bind(el, name, value, modifiers = []) {
         case 'class':
             bindClasses(el, value)
             break;
+        
+        // 'selected' and 'checked' are special attributes that aren't necessarily
+        // synced with their corresponding properties when updated, so both the 
+        // attribute and property need to be updated when bound.
+        case 'selected':
+        case 'checked':
+            bindAttributeAndProperty(el, name, value)
+            break;
 
         default:
             bindAttribute(el, name, value)
@@ -78,6 +86,11 @@ function bindStyles(el, value) {
     el._x_undoAddedStyles = setStyles(el, value)
 }
 
+function bindAttributeAndProperty(el, name, value) {
+    bindAttribute(el, name, value)
+    setPropertyIfChanged(el, name, value)
+}
+
 function bindAttribute(el, name, value) {
     if ([null, undefined, false].includes(value) && attributeShouldntBePreservedIfFalsy(name)) {
         el.removeAttribute(name)
@@ -94,6 +107,12 @@ function setIfChanged(el, attrName, value) {
     }
 }
 
+function setPropertyIfChanged(el, propName, value) {
+    if (el[propName] !== value) {
+        el[propName] = value
+    }
+}
+
 function updateSelect(el, value) {
     const arrayWrappedValue = [].concat(value).map(value => { return value + '' })
 

+ 20 - 1
tests/cypress/integration/directives/x-bind.spec.js

@@ -1,4 +1,4 @@
-import { beHidden, beVisible, haveText, beChecked, haveAttribute, haveClasses, haveValue, notBeChecked, notHaveAttribute, notHaveClasses, test, html } from '../../utils'
+import { beHidden, beVisible, haveText, beChecked, haveAttribute, haveClasses, haveProperty, haveValue, notBeChecked, notHaveAttribute, notHaveClasses, test, html } from '../../utils';
 
 test('sets attribute bindings on initialize',
     html`
@@ -452,3 +452,22 @@ test('Can retrieve Alpine bound data with global bound method',
         get('#6').should(haveText('bar'))
     }
 )
+
+test('x-bind updates checked attribute and property after user interaction',
+    html`
+        <div x-data="{ checked: true }">
+            <button @click="checked = !checked">toggle</button>
+            <input type="checkbox" x-bind:checked="checked" @change="checked = $event.target.checked" />
+        </div>
+    `,
+    ({ get }) => {
+        get('input').should(haveAttribute('checked', 'checked'))
+        get('input').should(haveProperty('checked', true))
+        get('input').click()
+        get('input').should(notHaveAttribute('checked'))
+        get('input').should(haveProperty('checked', false))
+        get('button').click()
+        get('input').should(haveAttribute('checked', 'checked'))
+        get('input').should(haveProperty('checked', true))
+    }
+)

+ 2 - 0
tests/cypress/utils.js

@@ -97,6 +97,8 @@ export let haveAttribute = (name, value) => el => expect(el).to.have.attr(name,
 
 export let notHaveAttribute = (name, value) => el => expect(el).not.to.have.attr(name, value)
 
+export let haveProperty = (name, value) => el => expect(el).to.have.prop(name, value)
+
 export let haveText = text => el => expect(el).to.have.text(text)
 
 export let notHaveText = text => el => expect(el).not.to.have.text(text)