From 0a805ea975820b56d9d212deea95ed6ebf211055 Mon Sep 17 00:00:00 2001 From: orangemug Date: Thu, 29 Sep 2016 22:59:37 +0100 Subject: [PATCH 1/2] Prevent bubbling of events on disabled form elements (issue #73) --- lib/delegate.js | 40 +++++++++++++++++++++++++++----------- test/tests/delegateTest.js | 23 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/lib/delegate.js b/lib/delegate.js index df37477..af37635 100644 --- a/lib/delegate.js +++ b/lib/delegate.js @@ -294,6 +294,8 @@ Delegate.prototype.handle = function(event) { break; } + var toFire = []; + // Need to continuously check // that the specific list is // still populated in case one @@ -312,23 +314,22 @@ Delegate.prototype.handle = function(event) { break; } + if( + target.tagName && + ["button", "input", "select", "textarea"].indexOf(target.tagName.toLowerCase()) > -1 && + target.hasAttribute("disabled") + ) { + // Remove things that have previously fired + toFire = []; + } // Check for match and fire // the event if there's one // // TODO:MCG:20120117: Need a way // to check if event#stopImmediatePropagation // was called. If so, break both loops. - if (listener.matcher.call(target, listener.matcherParam, target)) { - returned = this.fire(event, target, listener); - } - - // Stop propagation to subsequent - // callbacks if the callback returned - // false - if (returned === false) { - event[EVENTIGNORE] = true; - event.preventDefault(); - return; + else if (listener.matcher.call(target, listener.matcherParam, target)) { + toFire.push([event, target, listener]); } } @@ -344,6 +345,23 @@ Delegate.prototype.handle = function(event) { l = listenerList.length; target = target.parentElement; } + + for(i=0; i' + '' + '' + + '' + + '' ); }; @@ -604,6 +606,27 @@ buster.testCase('Delegate', { assert.calledOnce(bubbleSpy); }, + 'Disabled buttons don\'t trigger click' : function() { + var delegate = new Delegate(document); + var spy = this.spy(); + + delegate.on('click', '#btn-disabled', spy); + var element = document.getElementById("btn-disabled"); + + setupHelper.fireMouseEvent(element, "click"); + refute.called(spy); + }, + 'Disabled buttons with inner element don\'t trigger click' : function() { + var delegate = new Delegate(document); + var spy = this.spy(); + + delegate.on('click', '#btn-disabled-alt-label', spy); + var element = document.getElementById("btn-disabled-alt-label"); + + setupHelper.fireMouseEvent(element, "click"); + refute.called(spy); + }, + 'tearDown': function() { setupHelper.tearDown(); } From 74700f9e64d28be9cbf3367bdda21e323d05698c Mon Sep 17 00:00:00 2001 From: orangemug Date: Tue, 4 Oct 2016 09:19:07 +0100 Subject: [PATCH 2/2] Perf improvement of new event handlig code. --- lib/delegate.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/delegate.js b/lib/delegate.js index af37635..f50d8ba 100644 --- a/lib/delegate.js +++ b/lib/delegate.js @@ -29,6 +29,9 @@ function Delegate(root) { /** @type function() */ this.handle = Delegate.prototype.handle.bind(this); + + // Cache of event listeners removed during an event cycle + this._removedListeners = []; } /** @@ -239,6 +242,7 @@ Delegate.prototype.off = function(eventType, selector, handler, useCapture) { listener = listenerList[i]; if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) { + this._removedListeners.push(listener); listenerList.splice(i, 1); } } @@ -346,9 +350,11 @@ Delegate.prototype.handle = function(event) { target = target.parentElement; } + var ret; + for(i=0; i -1) { continue; } returned = this.fire.apply(this, toFire[i]); @@ -359,9 +365,12 @@ Delegate.prototype.handle = function(event) { if (returned === false) { toFire[i][0][EVENTIGNORE] = true; toFire[i][0].preventDefault(); - return false; + ret = false; + break; } } + + return ret; }; /**