From 767b40ede32564e61bcdae0ef78f13f0188bba6e Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Fri, 9 Feb 2024 23:40:17 +0100 Subject: [PATCH] fix singular throttle event dispatched two times Relates to: #3097 So, in #3097 I changed the throttle behavior to also dispatch an event after the throttle time has passed to make sure that the last event is dispatched. However, this caused the event to be dispatched twice in the case that there was only one event in the first place. This fixes that, although the throttle code is still a bit of a mess and should probably be refactored completely at some point. --- assets/js/phoenix_live_view/dom.js | 19 ++++++++++++------- assets/test/debounce_test.js | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/assets/js/phoenix_live_view/dom.js b/assets/js/phoenix_live_view/dom.js index 814724c578..907d731fa5 100644 --- a/assets/js/phoenix_live_view/dom.js +++ b/assets/js/phoenix_live_view/dom.js @@ -222,21 +222,25 @@ let DOM = { default: let timeout = parseInt(value) let trigger = (blur) => { + const throttled = this.private(el, THROTTLED) if(blur){ // if the input is blurred, we need to cancel the next throttle timeout // therefore we store the timer id in the THROTTLED private attribute - if(throttle && this.private(el, THROTTLED)){ - clearTimeout(this.private(el, THROTTLED)) + if(throttle && throttled !== null){ + clearTimeout(throttled.timeout) this.deletePrivate(el, THROTTLED) } // on debounce we just trigger the callback return callback() } // no blur, remove the throttle attribute if we are in throttle mode - if(throttle) this.deletePrivate(el, THROTTLED) - // always call the callback to ensure that the latest event is processed, - // even when throttle is active - callback() + if(throttle){ + this.deletePrivate(el, THROTTLED) + // run callback if there was a throttled event + if(throttled && throttled.updated) callback() + } else { + callback() + } } let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger) if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) } @@ -249,6 +253,7 @@ let DOM = { } if(!newKeyDown && this.private(el, THROTTLED)){ + this.updatePrivate(el, THROTTLED, {}, (existing) => ({...existing, updated: true})) return false } else { callback() @@ -259,7 +264,7 @@ let DOM = { const t = setTimeout(() => { if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER) } }, timeout) - this.putPrivate(el, THROTTLED, t) + this.putPrivate(el, THROTTLED, {timer: t}) } } else { setTimeout(() => { diff --git a/assets/test/debounce_test.js b/assets/test/debounce_test.js index d2758c251c..9b05970dd5 100644 --- a/assets/test/debounce_test.js +++ b/assets/test/debounce_test.js @@ -240,6 +240,25 @@ describe("throttle", function (){ done() }) }) + + test("triggers only once when there is only one event", done => { + let calls = 0 + let el = container().querySelector("#throttle-200") + + el.addEventListener("click", e => { + DOM.debounce(el, e, "phx-debounce", 100, "phx-throttle", 200, () => true, () => { + calls++ + el.innerText = `now:${calls}` + }) + }) + DOM.dispatchEvent(el, "click") + expect(calls).toBe(1) + expect(el.innerText).toBe("now:1") + after(250, () => { + expect(calls).toBe(1) + done() + }) + }) })