Bubble.js een 1.6K-oplossing voor een veelvoorkomend probleem

Een van de meest voorkomende taken bij webontwikkeling is evenementenbeheer. Onze JavaScript-code luistert meestal naar gebeurtenissen die door de DOM-elementen worden verzonden. 

Dit is hoe we informatie krijgen van de gebruiker: dat wil zeggen, hij of zij klikt, typt, communiceert met onze pagina en we moeten weten zodra dit gebeurt. Het toevoegen van gebeurtenislisteners ziet er triviaal uit maar kan een moeilijk proces zijn.

In dit artikel zullen we een echt probleem met de zaak en zijn 1.6K-oplossing zien.

Het probleem

Een vriend van mij werkt als junior-ontwikkelaar. Als zodanig heeft hij niet veel ervaring met vanilla JavaScript; hij moest echter frameworks zoals AngularJS en Ember gebruiken zonder de fundamentele kennis van de DOM-naar-JavaScript-relatie te kennen. Tijdens zijn tijd als junior-ontwikkelaar kreeg hij de leiding over een klein project: websites met één pagina en bijna geen JavaScript. Hij stond voor een klein maar zeer interessant probleem dat me uiteindelijk leidde tot het schrijven van Bubble.js.

Stel je voor dat we een popup hebben. Een mooie stijl

element:

...

Hier is de code die we gebruiken om een ​​bericht te tonen:

var popup = document.querySelector ('. popup'); var showMessage = function (msg) popup.style.display = 'block'; popup.innerHTML = msg;  ... showMessage ('Bezig met laden. Even geduld');

We hebben een andere functie hideMessage dat verandert de tonen eigendom aan geen en verbergt de pop-up. De aanpak kan in het meest generieke geval werken, maar heeft nog steeds enkele problemen. 

Stel bijvoorbeeld dat we aanvullende logica moeten implementeren als de problemen één voor één worden opgelost. Laten we zeggen dat we twee knoppen moeten toevoegen aan de inhoud van de pop-up - Ja en Nee.

var content = 'Weet je het zeker?
'; inhoud + = 'Ja'; inhoud + = 'Nee'; showMessage (inhoud);

Dus, hoe zullen we weten dat de gebruiker erop klikt? We moeten event listeners toevoegen aan elk van de links. 

Bijvoorbeeld:

var addListeners = function (yesCB, noCB) popup.querySelector ('. popup - yes'). addEventListener ('klik', yesCB); popup.querySelector ('. popup - no'). addEventListener ('klik', noCB);  showMessage (inhoud); addListeners (function () console.log ('Yes button clicked');, function () console.log ('Geen knop aangeklikt'););

De bovenstaande code werkt tijdens de eerste run. Wat als we een nieuwe knop nodig hebben, of erger nog, wat als we een ander soort knop nodig hebben? Dat wil zeggen, wat als we zouden blijven gebruiken  elementen maar met verschillende klassenamen? We kunnen hetzelfde niet gebruiken addListeners functie, en het is vervelend om een ​​nieuwe methode te maken voor elke variant van de pop-up.

Hier zijn de problemen zichtbaar:

  • We moeten de luisteraars steeds weer toevoegen. In feite moeten we dit telkens doen wanneer de HTML in de pop-up's staat
    is gewijzigd.
  • We kunnen gebeurtenislisteners alleen koppelen als de inhoud van de pop-up is bijgewerkt. Alleen na de toon bericht roeping. We moeten daar voortdurend aan denken en de twee processen synchroniseren.
  • De code die de luisteraars toevoegt, heeft één harde afhankelijkheid - de pop-up variabel. We moeten het noemen querySelector functie in plaats van document.querySelector. Anders kunnen we een verkeerd element selecteren.
  • Zodra we de logica in het bericht hebben gewijzigd, moeten we de selectors en waarschijnlijk de addEventListener noemt. Het is helemaal niet droog.

Er moet een betere manier zijn om dit te doen.

Ja, er is een betere aanpak. En nee, de oplossing is niet om een ​​kader te gebruiken.

Laten we, voordat we het antwoord onthullen, een beetje praten over de gebeurtenissen in de DOM-boom.

Eventafhandeling begrijpen

Evenementen zijn een essentieel onderdeel van webontwikkeling. Ze voegen interactiviteit toe aan onze applicaties en fungeren als een brug tussen de bedrijfslogica en de gebruiker. Elk DOM-element kan gebeurtenissen verzenden. Het enige dat we moeten doen, is abonneren op deze evenementen en het ontvangen evenement-object verwerken.

Er is een term evenement propagatie dat staat achter event bubbling en het vastleggen van gebeurtenissen beide zijn twee manieren om gebeurtenissen af ​​te handelen in DOM. Laten we de volgende markup gebruiken en het verschil tussen deze markeringen zien.

Klik hier

We zullen hechten Klik event handlers voor beide elementen. Omdat er echter genest zijn in elkaar, zullen ze allebei de Klik evenement.

document.querySelector ('. wrapper'). addEventListener ('klik', functie (e) console.log ('. wrapper geklikt');); document.querySelector ('a'). addEventListener ('klik', functie (e) console.log ('een aangeklikte'););

Zodra we op de link hebben gedrukt, zien we de volgende uitvoer in de console:

een aangeklikte .wrapper heeft geklikt

Dus beide elementen ontvangen inderdaad de Klik evenement. Eerst de link en dan de

. Dit is het bubbelende effect. Van het diepste mogelijke element tot zijn ouders. Er is een manier om het borrelen te stoppen. Elke handler ontvangt een gebeurtenisobject dat dat wel heeft stopPropagation methode:

document.querySelector ('a'). addEventListener ('klik', functie (e) e.stopPropagation (); console.log ('een geklikte'););

Door het gebruiken van stopPropagation functie, geven we aan dat de gebeurtenis niet naar de ouders moet worden verzonden.

Soms moeten we de volgorde misschien omkeren en de gebeurtenis laten vastlopen door het buitenelement. Om dit te bereiken, moeten we een derde parameter gebruiken in addEventListener. Als we passeren waar als een waarde die we zullen doen event capturing. Bijvoorbeeld:

document.querySelector ('. wrapper'). addEventListener ('klik', functie (e) console.log ('. wrapper clicked');, true); document.querySelector ('a'). addEventListener ('klik', functie (e) console.log ('een aangeklikte');, true);

Dat is hoe onze browser de gebeurtenissen verwerkt wanneer we de pagina gebruiken.

De oplossing

Oké, dus waarom brachten we een deel van het artikel door met praten over borrelen en vastleggen. We noemden ze omdat borrelen het antwoord is op onze problemen met de pop-up. We moeten de gebeurtenislisteners niet instellen op de links, maar op de

direct.

var content = 'Weet je het zeker?
'; inhoud + = 'Ja'; inhoud + = 'Nee'; var addListeners = function () popup.addEventListener ('klik', functie (e) var link = e.target;); showMessage (inhoud); addListeners ();

Door deze aanpak te volgen, elimineren we de problemen die in het begin worden genoemd.

  • Er is slechts één gebeurtenislistener en we voegen deze één keer toe. Het maakt niet uit wat we in de pop-up plaatsen, het vangen van de gebeurtenissen gebeurt bij de ouder.
  • We zijn niet gebonden aan de aanvullende inhoud. Met andere woorden, het interesseert ons niet wanneer het toon bericht wordt genoemd. Zolang de pop-up variabele leeft, we vangen de gebeurtenissen op.
  • Omdat we bellen addListeners eenmaal gebruiken we de pop-up variabele ook één keer. We hoeven het niet te houden of door te geven tussen de methoden.
  • Onze code werd flexibel omdat we ervoor kozen om niet om de HTML te geven die werd doorgegeven toon bericht. We hebben daar toegang tot het geklikte anker e.target wijst naar het ingedrukte element.

De bovenstaande code is beter dan de code waarmee we zijn begonnen. Werkt echter nog steeds niet op dezelfde manier. Zoals we al zeiden, e.target wijst naar de aangeklikte label. Dus, we zullen dat gebruiken om het te onderscheiden Ja en Nee toetsen.

var addListeners = function (callbacks) popup.addEventListener ('klik', functie (e) var link = e.target; var buttonType = link.getAttribute ('class'); if (callbacks [buttonType]) callbacks [ buttonType] (e););  ... addListeners ('popup - yes': function () console.log ('Yes');, 'popup - no': function () console.log ('No');) ;

We haalden de waarde van de klasse attribuut en gebruik het als een sleutel. De verschillende klassen wijzen naar verschillende callbacks.

Het is echter geen goed idee om de klasse attribuut. Het is gereserveerd voor het toepassen van visuele stijlen op het element en de waarde ervan kan op elk moment veranderen. Als JavaScript-ontwikkelaars zouden we moeten gebruiken gegevens attributen.

var content = 'Weet je het zeker?
'; inhoud + = 'Ja'; inhoud + = 'Nee';

Onze code wordt ook een beetje beter. We kunnen de aanhalingstekens verwijderen die in addListeners functie:

addListeners (yes: function () console.log ('Yes');, no: function () console.log ('No'););

Het resultaat was te zien in deze JSBin.

Bubble.js

Ik heb de oplossing hierboven in verschillende projecten toegepast, dus het was logisch om er een bibliotheek van te maken. Het heet Bubble.js en het is beschikbaar in GitHub. Het is een 1.6K-bestand dat precies doet wat we hierboven hebben gedaan.

Laten we ons pop-upvoorbeeld transformeren om te gebruiken Bubble.js. Het eerste dat we moeten veranderen is de gebruikte markup:

var content = 'Weet je het zeker?
'; inhoud + = 'Ja'; inhoud + = 'Nee';

In plaats van data-actie we zouden moeten gebruiken data-bubble-actie.

Zodra we opnemen bubble.min.js op onze pagina hebben we een wereldwijde bubbel functie beschikbaar. Het accepteert een DOM-elementselector en retourneert de API van de bibliotheek. De op methode is degene die de luisteraars toevoegt:

bubble ('. popup') .on ('yes', function () console.log ('Yes');) .on ('nee', functie () console.log ('Nee'); );

Er is ook een alternatieve syntaxis:

bubble ('. popup'). on (yes: function () console.log ('Yes');, no: function () console.log ('No'););

Standaard, Bubble.js luistert naar Klik evenementen, maar er is een optie om dat te veranderen. Laten we een invoerveld toevoegen en luisteren naar zijn keyup evenement:

De JavaScript-handler ontvangt nog steeds het Event-object. In dit geval kunnen we de tekst van het veld weergeven:

bubble ('. popup'). on (... input: function (e) console.log ('Nieuwe waarde:' + e.target.value););

Soms moeten we niet één maar veel gebeurtenissen vangen die door hetzelfde element worden verzonden. data-bubble-actie accepteert meerdere waarden, gescheiden door een komma:

Vind hier de laatste variant in een JSBin.

fallbacks

De oplossing in dit artikel is volledig afhankelijk van het borrelen van het evenement. In sommige gevallen e.target mag niet verwijzen naar het element dat we nodig hebben. 

Bijvoorbeeld:

alsjeblieft, Kiezen me!

Als we onze muis boven "kiezen" plaatsen en een klik uitvoeren, is het element dat de gebeurtenis verzendt niet de tag maar de span element.

Samenvatting

Toegegeven, communicatie met de DOM is een essentieel onderdeel van onze applicatie-ontwikkeling, maar het is een gebruikelijke praktijk dat we kaders gebruiken om die communicatie te omzeilen.. 

We houden er niet van om steeds weer luisteraars toe te voegen. We vinden het niet leuk om rare bugs met dubbele gebeurtenissen te debuggen. De waarheid is dat als we weten hoe de browser werkt, we deze problemen kunnen oplossen.

Bubble.js is slechts één resultaat van een paar uur lezen en een uur coderen - het is onze 1.6K-oplossing voor een van de meest voorkomende problemen.