Stand-alone jQuery cannot handle the event properly

Update: Maybe jQuery () trigger does some extra work in tests, I opened issue in github.

=====

I follow learnQuery to create my simple jQuery. Now, working on the DOM event, we implement the on() and off() functions. They provided some evidence, I can’t pass on some of them.

Here is my code: ( And you can clone this 06.event_listeners/runner.html , run 06.event_listeners/runner.html to start testing )

 "use strict"; function isEmpty(str) { return (!str || 0 === str.length); } // listener use to bind to DOM element, call corresponding functions when event firing. function geneEventListener(event) { console.log('gene'); let type = Object.keys(this.handlers).find(type=>type===event.type); if (!type) return; let functions = this.handlers[type]; functions.forEach(f=>f.apply(this,event)); } // cache elements which bound event listener let Cache = function () { this.elements = []; this.uid = 1; }; Cache.prototype = { constructor:Cache, init:function (element) { if(!element.uid) element.uid = this.uid++; if(!element.handlers) element.handlers = {}; if(!element.lqListener) element.lqListener = geneEventListener.bind(element); this.elements.push(element); }, removeElement:function (uid) { this.elements.splice(this.elements.findIndex(e=>e.uid===uid),1); }, removeType:function (uid,type) { if(this.get(uid)) delete this.get(uid).handlers[type]; }, removeCallback:function (uid, type, callback) { if(this.get(uid) && this.get(uid).handlers[type]) { let functions = this.get(uid).handlers[type]; functions.splice(functions.findIndex(callback),1) } }, // return element or undefined get:function (uid) { return this.elements.find(e=>e.uid===uid); }, }; /* * One type could have many event listeners, One element could have many event types of listeners * So use element.handlers = {'click':[listener1, listener2, ...], 'hover':[...], ...} * */ let eventListener = (function() { let cache = new Cache(); function add (element, type, callback){ cache.init(element); element.addEventListener(type,element.lqListener); if(!element.handlers[type]){ element.handlers[type] = []; } (element.handlers[type]).push(callback); } // remove a type of event listeners, should remove the callback array and remove DOM event listener function removeType (element, type) { element.removeEventListener(type,element.lqListener); cache.removeType(element.uid,type); } // remove a event listener, just remove it from the callback array function removeCallback(element, type, callback) { cache.removeCallback(element.uid,type,callback); } // bind a callback. function on(element,type,callback) { if(!(element||type||callback)) throw new Error('Invalid arguments'); add(element,type,callback); } function off(element,type,callback) { if(!(element instanceof HTMLElement)) throw new Error('Invaild element, need a instance of HMTLElement'); let handlers = cache.get(element.uid).handlers; if(isEmpty(type)&&!callback){ for(let type in handlers){ removeType(element,type); } } console.log('off') if(!isEmpty(type)&&!callback) removeType(element,type); if(!isEmpty(type) && (typeof callback === 'function')) removeCallback(element,type,callback); } return { on, off } })(); 

I am using a chrome debugger to follow the value of element.handlers seems to work just fine when adding and removing callbacks.

And the testing has several console.log() in the event callback function, oddly enough, these console.log() not registered in the console, and I'm trying to set a breakpoint in the callback, but it also does not work.

I have little experience with javascript, If anyone can tell me how to debug and where is the error, thank you very much! And why console.log() cannot work in the callback. It should work, as they wrote it during testing, I think.

Here is the testing code:

 /*global affix*/ /*global eventListener*/ describe('EventListeners', function() { 'use strict'; var $selectedElement, selectedElement, methods; beforeEach(function() { affix('.learn-query-testing #toddler .hidden.toy+h1[class="title"]+span[class="subtitle"]+span[class="subtitle"]+input[name="toyName"][value="cuddle bunny"]+input[class="creature"][value="unicorn"]+.hidden+.infinum[value="awesome cool"]'); methods = { showLove: function(e) { console.log('<3 JavaScript <3'); }, giveLove: function(e) { console.log('==> JavaScript ==>'); return '==> JavaScript ==>'; } }; spyOn(methods, 'showLove'); spyOn(methods, 'giveLove'); $selectedElement = $('#toddler'); selectedElement = $selectedElement[0]; }); it('should be able to add a click event to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove).toHaveBeenCalled(); }); it('should be able to add the same event+callback two times to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(2); }); it('should be able to add the same callback for two different events to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'hover', methods.showLove); console.log('3') $selectedElement.trigger('click'); $selectedElement.trigger('hover'); expect(methods.showLove.calls.count()).toEqual(2); }); it('should be able to add two different callbacks for same event to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); $selectedElement.trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); expect(methods.giveLove.calls.count()).toEqual(1); }); it('should be able to remove one event handler of an HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.off(selectedElement, 'click', methods.showLove); console.log('5') $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(0); expect(methods.giveLove.calls.count()).toEqual(1); }); it('should be able to remove all click events of a HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.on(selectedElement, 'hover', methods.showLove); eventListener.off(selectedElement, 'click'); console.log('6') $selectedElement.trigger('hover'); $selectedElement.trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); expect(methods.giveLove).not.toHaveBeenCalled(); }); it('should be able to remove all events of a HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.on(selectedElement, 'hover', methods.showLove); eventListener.off(selectedElement); var eventHover = new Event('hover'); var eventClick = new Event('click'); selectedElement.dispatchEvent(eventClick); selectedElement.dispatchEvent(eventHover); expect(methods.showLove).not.toHaveBeenCalled(); expect(methods.giveLove).not.toHaveBeenCalled(); }); it('should trigger a click event on a HTML element', function() { $selectedElement.off(); $selectedElement.on('click', methods.showLove); eventListener.trigger(selectedElement, 'click'); expect(methods.showLove.calls.count()).toBe(1); }); it('should delegate an event to elements with a given css class name', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); $('.title').trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should not delegate an event to elements without a given css class name', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); $('.subtitle').trigger('click'); $('.title').trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should delegate an event to elements that are added to the DOM to after delegate call', function() { eventListener.delegate(selectedElement, 'new-element-class', 'click', methods.showLove); var newElement = document.createElement('div'); newElement.className = 'new-element-class'; $selectedElement.append(newElement); $(newElement).trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should trigger delegated event handler when clicked on an element inside a targeted element', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); var newElement = document.createElement('div'); newElement.className = 'new-element-class'; $selectedElement.append(newElement); $('.title').append(newElement); $(newElement).trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should not trigger delegated event handler if clicked on container of delegator', function() { var $targetElement = $('<p class="target"></p>'); $selectedElement.append($targetElement); eventListener.delegate(selectedElement, 'target', 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(0); }); it('should trigger delegated event handler multiple times if event happens on multiple elements', function() { eventListener.delegate(selectedElement, 'subtitle', 'click', methods.showLove); $('.subtitle').trigger('click'); expect(methods.showLove.calls.count()).toEqual(2); }); it('should not trigger method registered on element A when event id triggered on element B', function() { var elementA = document.createElement('div'); var elementB = document.createElement('div'); $selectedElement.append(elementA); $selectedElement.append(elementB); eventListener.on(elementA, 'click', methods.showLove); eventListener.on(elementB, 'click', methods.giveLove); $(elementA).trigger('click'); expect(methods.showLove).toHaveBeenCalled(); expect(methods.giveLove).not.toHaveBeenCalled(); }); }); 
+7
javascript jquery
source share
3 answers

I am the owner of the question.

After I created the issue , they fixed the error during testing. When testing, we cannot use our homemade on() and off() to add event listeners, and then use jQuery trigger() for testing, as jQuery will do some extra work. Therefore, they replaced it with dispatchEvent() .

In addition, there are some errors in my code. As mentioned in @ guest271314, I used apply() incorrectly, should use call() and should use _Cache to replace Cache . Besides, in the removeCallback function, I used functions.findIndex(callback) incorrectly, it should be functions.findIndex(f=>f===callback)

The correct code is on this thread , all the on and off tags passed.

Thank you everybody!

0
source share

The problem is that there is no event called hover .

Just a combination of mouseenter and mouseleave .

You can view all types of events listed here .

When calling element.addEventListener(type, element.lqListener) with the value type hover , it just doesn't work.

You can see more information on this. Can jQuery.on and hover be used? .

+1
source share

You are very close to your own demand. The only problem that can be found in the code is that Function.prototype.apply() expects Array with the second parameter

Syntax

 func.apply(thisArg, [argsArray]) 

Replace

 // pass `event` as element to array literal as second parameter to `.apply()` functions.forEach(f => f.apply(this, [event])); 

for

 functions.forEach(f => f.apply(this, event)); 

Also, the name of the replaced _Cache function for Cache , because Cache is a globally defined function

The Cache interface provides a storage mechanism for Request / Response pairs of objects that are cached, for example, as part of the ServiceWorker life cycle.

 "use strict"; function isEmpty(str) { return (!str || 0 === str.length); } // listener use to bind to DOM element, call corresponding functions when event firing. function geneEventListener(event) { console.log('gene'); let type = Object.keys(this.handlers).find(type => type === event.type); if (!type) return; let functions = this.handlers[type]; functions.forEach(f => f.apply(this, [event])); } // cache elements which bound event listener let _Cache = function() { this.elements = []; this.uid = 1; }; _Cache.prototype = { constructor: _Cache, init: function(element) { if (!element.uid) element.uid = this.uid++; if (!element.handlers) element.handlers = {}; if (!element.lqListener) element.lqListener = geneEventListener.bind(element); this.elements.push(element); }, removeElement: function(uid) { this.elements.splice(this.elements.findIndex(e => e.uid === uid), 1); }, removeType: function(uid, type) { if (this.get(uid)) delete this.get(uid).handlers[type]; }, removeCallback: function(uid, type, callback) { if (this.get(uid) && this.get(uid).handlers[type]) { let functions = this.get(uid).handlers[type]; functions.splice(functions.findIndex(callback), 1) } }, // return element or undefined get: function(uid) { return this.elements.find(e => e.uid === uid); }, }; /* * One type could have many event listeners, One element could have many event types of listeners * So use element.handlers = {'click':[listener1, listener2, ...], 'hover':[...], ...} * */ let eventListener = (function() { let cache = new _Cache(); function add(element, type, callback) { cache.init(element); element.addEventListener(type, element.lqListener); if (!element.handlers[type]) { element.handlers[type] = []; } (element.handlers[type]).push(callback); } // remove a type of event listeners, should remove the callback array and remove DOM event listener function removeType(element, type) { element.removeEventListener(type, element.lqListener); cache.removeType(element.uid, type); } // remove a event listener, just remove it from the callback array function removeCallback(element, type, callback) { cache.removeCallback(element.uid, type, callback); } // bind a callback. function on(element, type, callback) { if (!(element || type || callback)) throw new Error('Invalid arguments'); add(element, type, callback); } function off(element, type, callback) { if (!(element instanceof HTMLElement)) throw new Error('Invaild element, need a instance of HMTLElement'); let handlers = cache.get(element.uid).handlers; if (isEmpty(type) && !callback) { for (let type in handlers) { removeType(element, type); } } console.log('off') if (!isEmpty(type) && !callback) removeType(element, type); if (!isEmpty(type) && (typeof callback === 'function')) removeCallback(element, type, callback); } return { on, off } })(); onload = () => { eventListener.on(document.querySelector("div"), "click", function(event) { console.log(event.type); eventListener.off(event.target, "click"); }); } 
 <div>click</div> 
+1
source share

All Articles