Browse Source

Add named x-modelables

Caleb Porzio 3 years ago
parent
commit
91ebbc3963

+ 14 - 3
packages/alpinejs/src/directives/x-model.js

@@ -4,7 +4,7 @@ import { mutateDom } from '../mutation'
 import bind from '../utils/bind'
 import on from '../utils/on'
 
-directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
+directive('model', (el, { value, modifiers, expression }, { effect, cleanup }) => {
     let evaluate = evaluateLater(el, expression)
     let assignmentExpression = `${expression} = rightSideOfExpression($event, ${expression})`
     let evaluateAssignment = evaluateLater(el, assignmentExpression)
@@ -25,11 +25,14 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
         }})
     })
 
-    cleanup(() => removeListener())
+    if (! el._x_removeModelListeners) el._x_removeModelListeners = {}
+    el._x_removeModelListeners[value || 'default'] = removeListener
+    
+    cleanup(() => el._x_removeModelListeners[value || 'default']())
 
     // Allow programmatic overiding of x-model.
     let evaluateSetModel = evaluateLater(el, `${expression} = __placeholder`)
-    el._x_model = {
+    let obj = {
         get() { 
             let result
             evaluate(value => result = value)
@@ -40,6 +43,14 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
         },
     }
 
+    // This is a "named" binding (x-model:name).
+    if (value) {
+        if (! el._x_models) el._x_models = {}
+        el._x_models[value] = obj
+    } else {
+        el._x_model = obj
+    }
+
     el._x_forceModelUpdate = () => {
         evaluate(value => {
             // If nested model key is undefined, set the default value to empty string.

+ 8 - 4
packages/alpinejs/src/directives/x-modelable.js

@@ -1,7 +1,7 @@
 import { evaluateLater } from '../evaluator'
 import { directive } from '../directives'
 
-directive('modelable', (el, { expression }, { effect, evaluate, evaluateLater }) => {
+directive('modelable', (el, { value, expression }, { effect, evaluate, evaluateLater }) => {
     let func = evaluateLater(expression)
     let innerGet = () => { let result; func(i => result = i); return result; }
     let evaluateInnerSet = evaluateLater(`${expression} = __placeholder`)
@@ -15,10 +15,14 @@ directive('modelable', (el, { expression }, { effect, evaluate, evaluateLater })
     innerSet(initialValue)
 
     queueMicrotask(() => {
-        if (! el._x_model) return
+        if (! el._x_model && ! el._x_models) return
+
+        // Remove native event listeners as these are now bound with x-modelable.
+        el._x_removeModelListeners[value || 'default']()
     
-        let outerGet = el._x_model.get
-        let outerSet = el._x_model.set
+        console.log(value)
+        let outerGet = value ? el._x_models[value].get : el._x_model.get
+        let outerSet = value ? el._x_models[value].set : el._x_model.set
     
         effect(() => innerSet(outerGet()))
         effect(() => outerSet(innerGet()))

+ 44 - 0
tests/cypress/integration/directives/x-modelable.spec.js

@@ -64,3 +64,47 @@ test('x-modelable works when inside x-bind and x-model is outside',
         get('h2').should(haveText('lob'))
     }
 )
+
+test('x-modelable removes the event listener used by corresponding x-model',
+    html`
+        <div x-data="{ outer: 'foo' }">
+            <div x-data="{ inner: 'bar' }" x-modelable="inner" x-model="outer">
+                <h1 x-text="outer"></h1>
+                <h2 x-text="inner"></h2>
+
+                <button id="1" @click="$dispatch('input', 'baz')"></button>
+            </div>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('foo'))
+        get('h2').should(haveText('foo'))
+        get('#1').click()
+        get('h1').should(haveText('foo'))
+        get('h2').should(haveText('foo'))
+    }
+)
+
+test('can have a named x-modelable',
+    html`
+        <div x-data="{ outer: 'foo' }">
+            <div x-data="{ inner: 'bar' }" x-modelable:custom="inner" x-model:custom="outer">
+                <h1 x-text="outer"></h1>
+                <h2 x-text="inner"></h2>
+
+                <button @click="inner = 'bob'" id="1">change inner</button>
+                <button @click="outer = 'lob'" id="2">change outer</button>
+            </div>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('foo'))
+        get('h2').should(haveText('foo'))
+        get('#1').click()
+        get('h1').should(haveText('bob'))
+        get('h2').should(haveText('bob'))
+        get('#2').click()
+        get('h1').should(haveText('lob'))
+        get('h2').should(haveText('lob'))
+    }
+)