Browse Source

Add .once modifer

Caleb Porzio 5 years ago
parent
commit
d270383841
8 changed files with 79 additions and 15 deletions
  1. 6 1
      README.md
  2. 2 2
      dist/mix-manifest.json
  3. 18 5
      dist/project-x.js
  4. 0 0
      dist/project-x.min.js
  5. 10 1
      index.html
  6. 1 1
      package.json
  7. 17 5
      src/component.js
  8. 25 0
      test/on.spec.js

+ 6 - 1
README.md

@@ -139,7 +139,7 @@ In this example, the "hidden" class will only be applied when the value of the `
 For example:
 `<button x-bind:disabled="myVar">Click me</button>`
 
-This will add or remove the `disabled` attribute when `myVar` is true or false respectively. 
+This will add or remove the `disabled` attribute when `myVar` is true or false respectively.
 
 Most common boolean attributes are supported, like `readonly`, `required`, etc.
 
@@ -178,6 +178,11 @@ Adding `.stop` to an event listener will call `stopPropagation` on the triggered
 
 Adding `.window` to an event listener will install the listener on the global window object instead of the DOM node on which it is declared. This is useful for when you want to modify component state when something changes with the window, like the resize event. In this example, when the window grows larger than 768 pixels wide, we will close the modal/dropdown, otherwise maintain the same state.
 
+**`.once` modifier**
+**Example:** `<button x-on:mouseenter.once="fetchSomething()"></button>`
+
+Adding the `.once` modifer to an event listener will ensure that the listener will only be handled once. This is useful for things you only want to do once, like fetching HTML partials and such.
+
 ---
 
 ### `x-model`

+ 2 - 2
dist/mix-manifest.json

@@ -1,4 +1,4 @@
 {
-    "/project-x.js": "/project-x.js?id=6a940bba5ac7fd0b68e1",
-    "/project-x.min.js": "/project-x.min.js?id=39ed1c431c27b0184930"
+    "/project-x.js": "/project-x.js?id=ec509918933bb1813fd2",
+    "/project-x.min.js": "/project-x.min.js?id=8bba743308e09dcd7d20"
 }

+ 18 - 5
dist/project-x.js

@@ -1094,8 +1094,7 @@ function () {
       var _this2 = this;
 
       if (modifiers.includes('away')) {
-        // Listen for this event at the root level.
-        document.addEventListener(event, function (e) {
+        var handler = function handler(e) {
           // Don't do anything if the click came form the element or within it.
           if (el.contains(e.target)) return; // Don't do anything if this element isn't currently visible.
 
@@ -1103,15 +1102,29 @@ function () {
           // is from outside it, let's run the expression.
 
           _this2.runListenerHandler(expression, e);
-        });
+
+          if (modifiers.includes('once')) {
+            document.removeEventListener(event, handler);
+          }
+        }; // Listen for this event at the root level.
+
+
+        document.addEventListener(event, handler);
       } else {
         var node = modifiers.includes('window') ? window : el;
-        node.addEventListener(event, function (e) {
+
+        var _handler = function _handler(e) {
           if (modifiers.includes('prevent')) e.preventDefault();
           if (modifiers.includes('stop')) e.stopPropagation();
 
           _this2.runListenerHandler(expression, e);
-        });
+
+          if (modifiers.includes('once')) {
+            node.removeEventListener(event, _handler);
+          }
+        };
+
+        node.addEventListener(event, _handler);
       }
     }
   }, {

File diff suppressed because it is too large
+ 0 - 0
dist/project-x.min.js


+ 10 - 1
index.html

@@ -5,7 +5,7 @@
             [x-cloak] { display: none; }
         </style>
 
-        <script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.4.3/dist/project-x.min.js" defer></script>
+        <script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.4.4/dist/project-x.min.js" defer></script>
     </head>
     <body>
         <table>
@@ -138,6 +138,15 @@
                     </td>
                 </tr>
 
+                <tr>
+                    <td>x-on:click.once</td>
+                    <td>
+                        <div x-data="{ count: 0 }">
+                            <button x-on:click.once="count++">I've been clicked: <span x-text="count"></span></button>
+                        </div>
+                    </td>
+                </tr>
+
                 <tr>
                     <td>Append DOM</td>
                     <td>

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "main": "dist/project-x.js",
   "name": "project-x",
-  "version": "0.4.3",
+  "version": "0.4.4",
   "repository": {
     "type": "git",
     "url": "git://github.com/calebporzio/project-x.git"

+ 17 - 5
src/component.js

@@ -177,8 +177,7 @@ export default class Component {
 
     registerListener(el, event, modifiers, expression) {
         if (modifiers.includes('away')) {
-            // Listen for this event at the root level.
-            document.addEventListener(event, e => {
+            const handler = e => {
                 // Don't do anything if the click came form the element or within it.
                 if (el.contains(e.target)) return
 
@@ -188,16 +187,29 @@ export default class Component {
                 // Now that we are sure the element is visible, AND the click
                 // is from outside it, let's run the expression.
                 this.runListenerHandler(expression, e)
-            })
+
+                if (modifiers.includes('once')) {
+                    document.removeEventListener(event, handler)
+                }
+            }
+
+            // Listen for this event at the root level.
+            document.addEventListener(event, handler)
         } else {
             const node = modifiers.includes('window') ? window : el
 
-            node.addEventListener(event, e => {
+            const handler = e => {
                 if (modifiers.includes('prevent')) e.preventDefault()
                 if (modifiers.includes('stop')) e.stopPropagation()
 
                 this.runListenerHandler(expression, e)
-            })
+
+                if (modifiers.includes('once')) {
+                    node.removeEventListener(event, handler)
+                }
+            }
+
+            node.addEventListener(event, handler)
         }
     }
 

+ 25 - 0
test/on.spec.js

@@ -1,5 +1,6 @@
 import projectX from 'project-x'
 import { wait } from '@testing-library/dom'
+const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
 
 global.MutationObserver = class {
     observe() {}
@@ -96,6 +97,30 @@ test('.window modifier', async () => {
     await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
 })
 
+test('.once modifier', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ count: 0 }">
+            <button x-on:click.once="count = count+1"></button>
+
+            <span x-bind:foo="count"
+        </div>
+    `
+
+    projectX.start()
+
+    expect(document.querySelector('span').getAttribute('foo')).toEqual('0')
+
+    document.querySelector('button').click()
+
+    await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('1') })
+
+    document.querySelector('button').click()
+
+    await timeout(25)
+
+    expect(document.querySelector('span').getAttribute('foo')).toEqual('1')
+})
+
 test('click away', async () => {
     // Because jsDom doesn't support .offsetHeight and offsetWidth, we have to
     // make our own implementation using a specific class added to the class. Ugh.

Some files were not shown because too many files changed in this diff