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:
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(); }); });