Bläddra i källkod

Merge pull request #1175 from cthulahoops/duplicate_event_handlers

Remove stale event handlers when nodes are readded to the document.
Caleb Porzio 4 år sedan
förälder
incheckning
b0a482f63c
5 ändrade filer med 57 tillägg och 4 borttagningar
  1. 8 0
      dist/alpine-ie11.js
  2. 7 0
      dist/alpine.js
  3. 2 0
      src/component.js
  4. 7 0
      src/directives/on.js
  5. 33 4
      test/on.spec.js

+ 8 - 0
dist/alpine-ie11.js

@@ -6929,6 +6929,8 @@
       event = camelCase(event);
     }
 
+    var node_add_count = el.__x_node_add_count;
+
     var _handler2, listenerTarget;
 
     if (modifiers.includes('away')) {
@@ -6966,6 +6968,11 @@
           }
         }
 
+        if (el.__x_node_add_count !== node_add_count) {
+          listenerTarget.removeEventListener(event, _handler2, options);
+          return;
+        }
+
         if (isKeyEvent(event)) {
           if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
             return;
@@ -7806,6 +7813,7 @@
                   return;
                 }
 
+                node.__x_node_add_count = (node.__x_node_add_count || 0) + 1;
                 this.initializeElements(node);
               }.bind(this));
             }

+ 7 - 0
dist/alpine.js

@@ -863,6 +863,7 @@
       event = camelCase(event);
     }
 
+    const node_add_count = el.__x_node_add_count;
     let handler, listenerTarget;
 
     if (modifiers.includes('away')) {
@@ -894,6 +895,11 @@
           }
         }
 
+        if (el.__x_node_add_count !== node_add_count) {
+          listenerTarget.removeEventListener(event, handler, options);
+          return;
+        }
+
         if (isKeyEvent(event)) {
           if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
             return;
@@ -1805,6 +1811,7 @@
                 return;
               }
 
+              node.__x_node_add_count = (node.__x_node_add_count || 0) + 1;
               this.initializeElements(node);
             });
           }

+ 2 - 0
src/component.js

@@ -412,6 +412,8 @@ export default class Component {
                             return
                         }
 
+                        node.__x_node_add_count = (node.__x_node_add_count || 0) + 1
+
                         this.initializeElements(node)
                     })
                 }

+ 7 - 0
src/directives/on.js

@@ -9,6 +9,8 @@ export function registerListener(component, el, event, modifiers, expression, ex
         event = camelCase(event);
     }
 
+    const node_add_count = el.__x_node_add_count
+
     let handler, listenerTarget
 
     if (modifiers.includes('away')) {
@@ -43,6 +45,11 @@ export function registerListener(component, el, event, modifiers, expression, ex
                 }
             }
 
+            if (el.__x_node_add_count !== node_add_count) {
+                listenerTarget.removeEventListener(event, handler, options);
+                return
+            }
+
             if (isKeyEvent(event)) {
                 if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
                     return

+ 33 - 4
test/on.spec.js

@@ -2,10 +2,6 @@ import Alpine from 'alpinejs'
 import { wait, fireEvent } from '@testing-library/dom'
 const timeout = ms => new Promise(resolve => setTimeout(resolve, ms))
 
-global.MutationObserver = class {
-    observe() {}
-}
-
 test('data modified in event listener updates affected attribute bindings', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">
@@ -560,3 +556,36 @@ test('.camel modifier correctly binds event listener with namespace', async () =
         expect(document.querySelector('p').textContent).toEqual('bob');
     });
 })
+
+test('event handlers only fire once when nodes have been readded to the document.', async () => {
+    document.body.innerHTML = `
+        <div id="container" x-data="{'x': 0}">
+          <span x-text="x">0</span>
+          <button id="a" x-on:click="x += 1; $el.appendChild(document.getElementById('a'))">A</button>
+          <button id="b" x-on:click="x += 1; $el.appendChild(document.getElementById('b'))">B</button>
+          <button id="c" x-on:click="x += 1; $el.appendChild(document.getElementById('c'))">C</button>
+        </div>
+    `
+    Alpine.start()
+    const span = document.querySelector('span')
+
+    expect(span.textContent).toEqual('0')
+
+    document.querySelector('#a').click()
+    await wait(() => {
+        expect(span.textContent).toEqual('1')
+    })
+    expect(document.querySelector('#container').lastChild).toEqual(document.querySelector('#a'))
+
+    document.querySelector('#a').click()
+    await wait(() => {
+        expect(span.textContent).toEqual('2')
+    })
+    expect(document.querySelector('#container').lastChild).toEqual(document.querySelector('#a'))
+
+    document.querySelector('#a').click()
+    await wait(() => {
+        expect(span.textContent).toEqual('3')
+    })
+    expect(document.querySelector('#container').lastChild).toEqual(document.querySelector('#a'))
+})