Pārlūkot izejas kodu

Allow x-model to be accessed programmatically (#2303)

Caleb Porzio 3 gadi atpakaļ
vecāks
revīzija
98805c323d

+ 1 - 3
packages/alpinejs/src/directives/x-data.js

@@ -21,9 +21,7 @@ directive('data', skipDuringClone((el, { expression }, { cleanup }) => {
 
     let data = evaluate(el, expression, { scope: dataProviderContext })
 
-    if( data === undefined ) {
-        data = {}
-    }
+    if (data === undefined) data = {}
 
     injectMagics(data, el)
 

+ 13 - 0
packages/alpinejs/src/directives/x-model.js

@@ -27,6 +27,19 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
 
     cleanup(() => removeListener())
 
+    // Allow programmatic overiding of x-model.
+    let evaluateSetModel = evaluateLater(el, `${expression} = __placeholder`)
+    el._x_model = {
+        get() { 
+            let result
+            evaluate(value => result = value)
+            return result
+        },
+        set(value) {
+            evaluateSetModel(() => {}, { scope: { '__placeholder': value }})
+        },
+    }
+
     el._x_forceModelUpdate = () => {
         evaluate(value => {
             // If nested model key is undefined, set the default value to empty string.

+ 1 - 1
packages/docs/src/en/directives/bind.md

@@ -3,7 +3,7 @@ order: 4
 title: bind
 ---
 
-# `x-bind`
+# x-bind
 
 `x-bind` allows you to set HTML attributes on elements based on the result of JavaScript expressions.
 

+ 1 - 1
packages/docs/src/en/directives/cloak.md

@@ -3,7 +3,7 @@ order: 12
 title: cloak
 ---
 
-# `x-cloak`
+# x-cloak
 
 Sometimes, when you're using AlpineJS for a part of your template, there is a "blip" where you might see your uninitialized template after the page loads, but before Alpine loads.
 

+ 1 - 1
packages/docs/src/en/directives/data.md

@@ -3,7 +3,7 @@ order: 1
 title: data
 ---
 
-# `x-data`
+# x-data
 
 Everything in Alpine starts with the `x-data` directive.
 

+ 1 - 1
packages/docs/src/en/directives/effect.md

@@ -3,7 +3,7 @@ order: 11
 title: effect
 ---
 
-# `x-effect`
+# x-effect
 
 `x-effect` is a useful directive for re-evaluating an expression when one of its dependencies change. You can think of it as a watcher where you don't have to specify what property to watch, it will watch all properties used within it.
 

+ 1 - 1
packages/docs/src/en/directives/for.md

@@ -3,7 +3,7 @@ order: 8
 title: for
 ---
 
-# `x-for`
+# x-for
 
 Alpine's `x-for` directive allows you to create DOM elements by iterating through a list. Here's a simple example of using it to create a list of colors based on an array.
 

+ 1 - 1
packages/docs/src/en/directives/html.md

@@ -3,7 +3,7 @@ order: 7
 title: html
 ---
 
-# `x-html`
+# x-html
 
 `x-html` sets the "innerHTML" property of an element to the result of a given expression.
 

+ 1 - 1
packages/docs/src/en/directives/if.md

@@ -3,7 +3,7 @@ order: 16
 title: if
 ---
 
-# `x-if`
+# x-if
 
 `x-if` is used for toggling elements on the page, similarly to `x-show`, however it completely adds and removes the element it's applied to rather than just changing its CSS display property to "none".
 

+ 1 - 1
packages/docs/src/en/directives/ignore.md

@@ -3,7 +3,7 @@ order: 11
 title: ignore
 ---
 
-# `x-ignore`
+# x-ignore
 
 By default, Alpine will crawl and initialize the entire DOM tree of an element containing `x-init` or `x-data`.
 

+ 1 - 1
packages/docs/src/en/directives/init.md

@@ -3,7 +3,7 @@ order: 2
 title: init
 ---
 
-# `x-init`
+# x-init
 
 The `x-init` directive allows you to hook into the initialization phase of any element in Alpine.
 

+ 37 - 1
packages/docs/src/en/directives/model.md

@@ -3,7 +3,7 @@ order: 7
 title: model
 ---
 
-# `x-model`
+# x-model
 
 `x-model` allows you to bind the value of an input element to Alpine data.
 
@@ -336,3 +336,39 @@ The default throttle interval is 250 milliseconds, you can easily customize this
 ```alpine
 <input type="text" x-model.throttle.500ms="search">
 ```
+
+<a name="programmatic access"></a>
+## Programmatic access
+
+Alpine exposes under-the-hood utilities for getting and setting properties bound with `x-model`. This is useful for complex Alpine utilities that may want to override the default x-model behavior, or instances where you want to allow `x-model` on a non-input element.
+
+You can access these utilities through a property called `_x_model` on the `x-model`ed element. `_x_model` has two methods to get and set the bound property:
+
+* `el._x_model.get()` (returns the value of the bound property)
+* `el._x_model.set()` (sets the value of the bound property)
+
+```alpine
+<div x-data="{ username: 'calebporzio' }">
+    <div x-ref="div" x-model="username"></div>
+
+    <button @click="$refs.div._x_model.set('phantomatrix')">
+        Change username to: 'phantomatrix'
+    </button>
+
+    <span x-text="$refs.div._x_model.get()"></span>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ username: 'calebporzio' }">
+        <div x-ref="div" x-model="username"></div>
+
+        <button @click="$refs.div._x_model.set('phantomatrix')">
+            Change username to: 'phantomatrix'
+        </button>
+
+        <span x-text="$refs.div._x_model.get()"></span>
+    </div>
+</div>
+<!-- END_VERBATIM -->

+ 1 - 1
packages/docs/src/en/directives/on.md

@@ -3,7 +3,7 @@ order: 5
 title: on
 ---
 
-# `x-on`
+# x-on
 
 `x-on` allows you to easily run code on dispatched DOM events.
 

+ 1 - 1
packages/docs/src/en/directives/ref.md

@@ -3,7 +3,7 @@ order: 11
 title: ref
 ---
 
-# `x-ref`
+# x-ref
 
 `x-ref` in combination with `$refs` is a useful utility for easily accessing DOM elements directly. It's most useful as a replacement for APIs like `getElementById` and `querySelector`.
 

+ 1 - 1
packages/docs/src/en/directives/show.md

@@ -3,7 +3,7 @@ order: 3
 title: show
 ---
 
-# `x-show`
+# x-show
 
 `x-show` is one of the most useful and powerful directives in Alpine. It provides an expressive way to show and hide DOM elements.
 

+ 1 - 1
packages/docs/src/en/directives/text.md

@@ -3,7 +3,7 @@ order: 6
 title: text
 ---
 
-# `x-text`
+# x-text
 
 `x-text` sets the text content of an element to the result of a given expression.
 

+ 1 - 1
packages/docs/src/en/directives/transition.md

@@ -3,7 +3,7 @@ order: 10
 title: transition
 ---
 
-# `x-transition`
+# x-transition
 
 Alpine provides a robust transitions utility out of the box. With a few `x-transition` directives, you can create smooth transitions between when an element is shown or hidden.
 

+ 18 - 0
tests/cypress/integration/directives/x-model.spec.js

@@ -92,3 +92,21 @@ test('x-model trims value if trim modifier is present',
         get('div').should(haveData('foo', 'bar'))
     }
 )
+
+test('x-model can be accessed programmatically',
+    html`
+    <div x-data="{ foo: 'bar' }" x-model="foo">
+        <input x-model="foo">
+
+        <span x-text="$root._x_model.get()"></span>
+        <button @click="$root._x_model.set('bob')">Set foo to bob</button>
+    </div>
+    `,
+    ({ get }) => {
+        get('span').should(haveText('bar'))
+        get('input').type('baz')
+        get('span').should(haveText('barbaz'))
+        get('button').click()
+        get('span').should(haveText('bob'))
+    }
+)