Javascript och DOM Lektion 2

Hej och välkommen tillbaka till "JavaScript och DOM" serien. Förra gången vi täckte några JavaScript-basics och vi berörde olika aspekter av dokumentobjektmodellen, inklusive hur du öppnar noder och korsar genom DOM. Idag kommer vi att täcka hur man manipulerar element inom DOM och vi diskuterar webbläsarhändelsesmodellen.

Manipulerande element

I den sista lektionen omfattade vi stegen involverade i att få tillgång till en samling DOM-noder eller en singel DOM-nod. Den verkliga magiken uppträder när du sedan manipulerar vissa egenskaper som resulterar i vad som är allmänt känt som "beteende".

Varje enskild DOM-nod har en samling egenskaper De flesta av dessa egenskaper ger abstraktioner till viss funktionalitet. Om du till exempel har ett styckeelement med ett ID för "intro" kan du ganska enkelt ändra färg på det här elementet via DOM API:

 document.getElementById ('intro'). style.color = '# FF0000';

För att illustrera objektets / egenskaps naturen för detta API kan det vara lättare att förstå om vi bryter upp det genom att tilldela varje objekt till en variabel:

 var myDocument = dokument; var myIntro = myDocument.getElementById ('intro'); var myIntroStyles = myIntro.style; // Och nu kan vi ställa in färgen: myIntroStyles.color = '# FF0000';

Nu när vi har en hänvisning till "stil" -objektet i stycket kan vi lägga till andra CSS-stilar:

 myIntroStyles.padding = '2px 3px 0 3px'; myIntroStyles.backgroundColor = '#FFF'; myIntroStyles.marginTop = '20px';

Vi använder bara grundläggande CSS-fastighetsnamn här. Den enda skillnaden är att där du normalt skulle hitta ett streck ('-') texten är kamel-cased. Så i stället för "margin-top" använder vi "marginTop". Följande, till exempel, skulle inte arbeta och skulle skapa ett syntaxfel:

 myIntroStyles.padding-top = '10em'; // Producerar ett syntaxfel: // - '' '' tecknet är minusoperatören i JavaScript. // - Dessutom finns det inget sådant egendomsnamn.

Egenskaper kan nås på ett arrayliknande sätt. Så med denna kunskap kunde vi skapa en liten funktion för att ändra vilken stil som helst av ett visst element:

 funktion changeStyle (elem, egenskap, val) elem.style [property] = val; // Observera de fältkonsol som används för att komma åt egenskapen // Du skulle använda ovanstående plugin så här: var myIntro = document.getElementById ('intro'); // Grab Intro stycke changeStyle (myIntro, 'color', 'red');

Detta är bara ett exempel - för att vara ärlig är det förmodligen inte en väldigt användbar funktion, eftersom det är snabbare att använda de konventionella metoder som visats tidigare (t.ex.. elem.style.color = 'red').

Förutom egenskapen "stil" finns det många andra som du kan använda för att manipulera vissa aspekter av en nod / element. Faktum är att om du har Firebug installerat ska du försöka "inspektera ett element", klicka sedan på "DOM" fliken (normalt till höger eller under elementets betraktningspanel) för att se alla dess egenskaper:


DOM Element egenskaper, i Firebug

Alla egenskaper kan nås med användning av den konventionella punktnotationen (t ex Element.tabIndex). Inte alla egenskaper är primitiva datatyper (strängar, tal, boolesker etc.); Egenskapen "stil" till exempel, som vi diskuterade tidigare, är ett objekt som innehåller egna egenskaper. Många av ett elements egenskaper kommer endast att läsas; Vad jag menar med detta är att du inte kan ändra sitt värde. Till exempel kan du inte ändra egenskapen "parentNode" för en nod direkt. Webbläsaren brukar göra ett fel om du försöker ändra en av dessa skrivskyddade egenskaper: t.ex. FEL: "Ange en egenskap som bara har en getter". Det är bara något att vara medveten om ...

Ett vanligt krav är att ändra innehållet inom ett element. Det finns några olika sätt att göra detta. Det allra enklaste sättet är att använda egenskapen "innerHTML", så här:

 var myIntro = document.getElementById ('intro'); // Byta aktuellt innehåll med nytt innehåll: myIntro.innerHTML = 'Nytt innehåll för fantastiskt paragraf!'; // Lägger till aktuellt innehåll: myIntro.innerHTML + = '... lite mer innehåll ...';

Det enda problemet med den här metoden är att det inte anges i någon standard och finns inte i DOM-specifikationen. Om du inte störd om det, fortsätt och använd det. Det är normalt mycket snabbare än konventionella DOM-metoder, vilket vi kommer att täcka nästa.

noder

När du skapar innehåll via DOM API måste du vara medveten om två olika typer av noder, en elementnod och en textnod. Det finns många andra typer av noder men dessa två är de enda viktiga för nu.

För att skapa ett element använder du 'createElement'-metoden och för att skapa en textnod som du använder' createTextNode'-metoden visas de båda nedan:

 var myIntro = document.getElementById ('intro'); // Vi vill lägga till lite innehåll på stycket: var someText = 'Det här är texten jag vill lägga till'; var textNode = document.createTextNode (someText); myIntro.appendChild (textNode);

Här använder vi metoden "appendChild" för att lägga till vår nya textnod till stycket. Att göra det här tar lite längre tid än den icke-standardiserade innerHTML-metoden, men det är fortfarande viktigt att veta båda vägarna så att du kan fatta rätt beslut. Här är ett mer avancerat exempel med DOM-metoder:

 var myIntro = document.getElementById ('intro'); // Vi vill lägga till ett nytt ankare till stycket: // Först skapar vi det nya ankarelementet: var myNewLink = document.createElement ('a'); //  myNewLink.href = 'http://google.com'; // myNewLink.appendChild (document.createTextNode ("Besök Google")); // Besök Google // Nu kan vi lägga till det i stycket: myIntro.appendChild (myNewLink);

Det finns också en "insertBefore" DOM-metod som är ganska självförklarande. Med hjälp av dessa två metoder ('insertBefore' & 'appendChild') kan vi skapa vår egen "insertAfter" -funktion:

 // 'Target' är det element som redan finns i DOM // 'Bullet' är det element du vill infoga funktioninläggAfter (mål, punkt) target.nextSibling? target.parentNode.insertBefore (bullet, target.nextSibling): target.parentNode.appendChild (bullet);  // Vi använder en ternär operatör i ovanstående funktion: // Dess format: CONDITION? EXPRESSION IF TRUE: EXPRESSION IF FALSE;

Ovanstående funktion kontrollerar förekomsten av målets nästa syskon inom DOM. Om det existerar kommer det att sätta in kula-noden innan målets nästa syskon, annars antar man att målet är det sista barnet av ett element och så Det är okej att lägga till kullen som barn av föräldern. DOM API ger oss ingen "insertAfter" -metod eftersom det inte finns något behov - vi kan skapa det själv.

Det finns en hel del mer att lära sig om att manipulera element inom DOM, men ovanstående bör vara en tillräcklig grund för vilken du kan bygga.

evenemang

Webbläsarhändelser är själva kärnan i alla webbapplikationer och de flesta JavaScript-förbättringar. Det är genom dessa händelser som vi definierar när något kommer att hända. Om du har en knapp i ditt dokument och du behöver någon formvalidering så att den äger rum när den klickas så brukar du klicka på händelsen. Nedan följer en översikt över de flesta vanliga webbläsarhändelser:

Obs! Eftersom vi diskuterade förra gången är DOM och JavaScript-språket två separata enheter. Webbläsarhändelser är en del av DOM API, de ingår inte i JavaScript.

Mushändelser

  • 'Mousedown' - Mousedown-händelsen avfyras när pekdonet (vanligtvis en mus) trycks nedåt över ett element.
  • 'Mouseup' - Mouseup-händelsen avfyras när pekdonet (vanligen en mus) släpps över ett element.
  • 'klick' - Klickahändelsen definieras som en mousedown följt av en mouseup på exakt samma position.
  • 'Dblclick' - Den här händelsen avfyras när ett element klickas två gånger i snabb följd i samma position.
  • 'Mouseover' - Mouseover-händelsen avfyras när pekdonet flyttas över ett element.
  • 'MouseOut' - Mouseout-händelsen avfyras när pekdonet flyttas ur ett element. (bort från ett element)
  • 'Mouse' - Mousemove-händelsen avfyras när pekdonet flyttas under svängning över ett element.

Tangentbordshändelser

  • 'knapptryckning' - Denna händelse avfyras när en tangent på tangentbordet trycks in.
  • 'nyckel ner' - Denna händelse bränder närhelst en tangent trycks in, den körs före "tangenttryck" -händelsen.
  • 'KeyUp' - Denna händelse avfyras när en nyckel släpps, efter både "keydown" och "tangenttryck" händelser.

Form händelser

  • 'Välj' - Denna händelse avfyras när text inom ett textfält (inmatning, textarea etc.) väljs.
  • 'Byta' - Denna händelse avfyras när en kontroll förlorar inmatningsfokus och / eller värdet har ändrats sedan fokus har uppnåtts.
  • 'lämna' - Denna händelse avfyras när en blankett skickas in.
  • 'återställa' - Denna händelse avfyras när en blankett återställs.
  • 'fokus' - Denna händelse avfyras när ett element tar emot fokus, vanligtvis från en pekdon.
  • 'fläck' - Denna händelse avfyras när ett element förlorar fokus, vanligtvis från en pekdon.

Andra händelser

  • 'ladda' - Den här händelsen avfyras när användaragenten har laddat allt innehåll i ett dokument, inklusive innehåll, bilder, ramar och objekt. För element, till exempel "IMG", bränder den när innehållet i fråga är klart att ladda.
  • 'Ändra storlek' - Den här händelsen avfyras när dokumentvyn ändras. (dvs när webbläsaren ändras.)
  • 'skrolla' - Den här händelsen avfyras när dokumentet rullas.
  • 'lasta av' - Den här händelsen avfyras när användaragenten tar bort allt innehåll från ett fönster eller en ram, dvs när du lämnar en sida.

Det finns många fler evenemang att välja mellan. De som visas ovan är de viktigaste som du kommer att stöta på ofta i JavaScript-kod. Var medveten om att några av dem har subtila tvärblocksskillnader. Också vara medveten om att många webbläsare implementerar egna händelser, till exempel det finns en hel del Gecko-specifika händelser, som "DOMContentLoaded" eller "DOMMouseScroll" - du kan läsa mer om dessa här: https://developer.mozilla.org / SV / Gecko-Specific_DOM_Events

Händelsehantering

Vi har täckt de aktuella händelserna men vi har ännu inte diskuterat processen att fästa en funktion till en händelse. Det här är där magiken händer. Ovanstående händelser kommer alla att inträffa oavsett om du har skrivit några JavaScript eller inte, för att utnyttja sin kraft måste du registrera "händelsehanterare". Det här är en fin term för att beskriva en funktion som används för att hantera en händelse. Här är ett enkelt exempel med grundläggande evenemangsregistreringsmodell (även känd som "traditionell evenemangsregistrering"):

Grundläggande evenemangsregistrering:

  
 // JavaScript: var myElement = document.getElementById ('my-button'); // Den här funktionen är vår händelsehanterare: funktionsknappKlicka () alert ('Du klickade bara på knappen!');  // Det här är händelsesregistreringsdelen: myElement.onclick = buttonClick;

Vi har en HTML-knapp med ett ID-nummer för "min-knappen" och vi har åtkomst till det med kommandot "document.getElementById". Då skapar vi en ny funktion som senare tilldelas knappen "onclick" DOM ​​på knappen. Det är allt som finns där!

"Basic Event Registration" -modellen är så enkel som den får. Du prefixar den händelse du är ute efter med "på" och få tillgång till den som en egenskap av vilket element du arbetar med. Detta är i grunden den diskreta versionen av att göra något så här (som jag inte rekommenderar):

 

Inline-händelsehantering (med hjälp av HTML-attribut) är mycket påträngande och gör din webbplats mycket svårare att behålla. Det är bättre att använda diskreta JavaScript och ha allt innehållet i respektive ".js" -filer som kan ingå i dokumentet som / när det behövs. Medan vi är föremål för diskreta JavaScript skulle jag vilja korrigera den vanliga missuppfattningen att bibliotek som jQuery gör det "möjligt att koda diskret" - det här är inte sant. När du använder jQuery är det lika lätt att göra saker på fel sätt. Anledningen till att du inte ska använda inlinehantering är exakt samma som anledningen till att du inte ska använda inline CSS-format (med).

Avancerad evenemangsregistrering:

Låt inte detta namn vilseleda dig, bara för att det kallas "avancerat" betyder inte att det är bättre att använda; Faktum är att tekniken som vi diskuterade ovan ("grundläggande evenemangsregistrering") är perfekt passande för det mesta. Användning av grundtekniken har dock en nyckelbegränsning; Du kan inte binda mer än en funktion till en händelse. Det här är inte så dåligt, för du kan bara ringa några andra funktioner inom den enda funktionen, men om du behöver mer kontroll så finns det ett annat sätt att registrera hanterare, skriv in den "avancerade händelsesregistreringsmodellen".

Med den här modellen kan du binda flera hanterare till en enda händelse, vilket innebär att flera funktioner kommer att köras när en händelse inträffar. Dessutom kan den här modellen enkelt avlägsna någon av de bundna händelsehanterarna.

Strängt taget finns det två olika modeller i denna kategori; W3C: s och Microsofts. W3C-modellen stöds av alla moderna webbläsare förutom IE, och Microsofts modell stöds endast av IE. Så här använder du W3C: s modell:

 // FORMAT: target.addEventListener (typ, funktion, useCapture); // Exempel: var myIntro = document.getElementById ('intro'); myIntro.addEventListener ('klicka', introClick, false);

Och här är detsamma, men för IE (Microsofts modell):

 // FORMAT: target.attachEvent ('on' + typ, funktion); // Exempel: var myIntro = document.getElementById ('intro'); myIntro.attachEvent ('onclick', introClick);

Och här är funktionen "introClick":

 funktion introClick () alert ('Du klickade på stycket!'); 

På grund av att ingen modell fungerar i alla webbläsare är det en bra idé att kombinera dem båda i en anpassad funktion. Här är en mycket grundläggande "addEvent" -funktion, som fungerar cross-browser:

 funktion addEvent (elem, typ, fn) if (elem.attachEvent) elem.attachEvent ('on' + typ, fn); lämna tillbaka;  om (elem.addEventListener) elem.addEventListener (typ, fn, false); 

Funktionen kontrollerar egenskaperna 'attachEvent' och 'addEventListener' och använder sedan en av de modeller som är beroende av det testet. Båda modellerna gör det också möjligt att ta bort händelsehanterare, som visas i denna "removeEvent" -funktionen:

 funktion removeEvent (elem, typ, fn) if (elem.detachEvent) elem.detachEvent ('on' + typ, fn); lämna tillbaka;  om (elem.removeEventListener) elem.removeEventListener (typ, fn, false); 

Du skulle använda funktionerna så här:

 var myIntro = document.getElementById ('intro'); addEvent (myIntro, "click", funktion () alert ('DU KLICKADE MIG !!!'););

Observera att vi passerade en namnlös funktion som den tredje parametern. JavaScript tillåter oss att definiera och utföra funktioner utan att namnge dem Funktioner av denna typ kallas "anonyma funktioner" och kan vara mycket användbara, särskilt när du måste skicka en funktion som en parameter till en annan funktion. Vi kunde bara ha lagt in vår "introClick" -funktion (definierad tidigare) som den tredje parametern, men ibland är det bekvämare att göra det med en anonym funktion.

Om du vill att en åtgärd ska inträffa vid en händelse bara första gången den klickade kan du göra något så här:

 // Observera att vi redan har definierat addEvent / removeEvent-funktionerna // (För att kunna använda dem måste de inkluderas) var myIntro = document.getElementById ('intro'); addEvent (myIntro, "click", oneClickOnly); funktion oneClickOnly () alert ('WOW!'); removeEvent (myIntro, "click", oneClickOnly); 

Vi tar bort hanteraren så snart händelsen är avfyrade för första gången. Vi har inte kunnat använda en anonym funktion i ovanstående exempel eftersom vi behövde behålla en referens till funktionen ("oneClickOnly") så att vi senare kunde ta bort den. Det sagt är det faktiskt möjligt att uppnå med en anonym (anonym) funktion:

 addEvent (myIntro, 'click', function () alert ('WOW!'); removeEvent (myIntro, 'click', arguments.callee););

Vi är ganska fräcka här genom att referera till "callee" -egenskapen för "argumentets" objekt. Objektet "Arguments" innehåller alla godkända parametrar för ALLA funktioner och innehåller även en referens till själva funktionen ("callee"). Genom att göra detta eliminerar vi fullständigt behovet av att definiera en namngiven funktion (t ex funktionen "oneClickOnly" tidigare).

Förutom de uppenbara syntaktiska skillnaderna mellan W3C och Microsofts implementering finns det några andra skillnader som är värda att notera. När du binder en funktion till en händelse ska funktionen köras inom ramen för elementet, så det här "nyckelordet" inom funktionen bör referera till elementet. genom att använda antingen den grundläggande händelsesregistreringsmodellen eller W3Cs avancerade modell fungerar det utan fel, men Microsofts implementering misslyckas. Här är ett exempel på vad du skall kunna göra inom händelsehanteringsfunktionerna:

 funktion myEventHandler () this.style.display = 'none';  // Fungerar korrekt, "här" refererar till elementet: myIntro.onclick = myEventHandler; // Fungerar korrekt, 'detta' refererar till elementet: myIntro.addEventListener ('click', myEventHandler, false); // Fungerar inte korrekt, 'detta' refererar till fönstret objektet: myIntro.attachEvent ('onclick', myEventHandler);

Det finns några olika sätt att undvika / åtgärda detta problem. Det enklaste alternativet är det enklaste sättet att använda den grundläggande modellen - det finns nästan inga inkonsekventa tvärbrowser när du använder den här modellen. Om du vill använda den avancerade modellen och du behöver det här "nyckelordet" för att referera till elementet korrekt, bör du titta på några av de mer vidtagna "addEvent" -funktionerna, särskilt John Resig eller Dean Edward (hans gör inte Inte ens använda den avancerade modellen, superb!).

Händelseobjektet

En viktig aspekt av händelsehantering som vi ännu inte har att diskutera är något som kallas "Event object". När du binder en funktion till en händelse, dvs när du skapar en händelsehanterare, skickas funktionen ett objekt. Detta händer nativt, så du behöver inte vidta några åtgärder för att inducera det. Detta händelseobjekt innehåller en mängd information om händelsen som just uppstod. Den innehåller också exekverbara metoder som har olika beteendeeffekter på händelsen. Men, överraskande, valde Microsoft sitt eget sätt att genomföra denna "funktion"; IE-webbläsare överför inte detta händelseobjekt, utan måste komma åt det som en egenskap för det globala fönstret objektet. det här är inte ett problem, det är bara ett problem:

 funktion myEventHandler (e) // Lägg märke till "e" -argumentet ... // När den här funktionen kallas, till följd av händelsen // firing, kommer händelseobjektet att skickas (i W3C-kompatibla agenter) // Låt oss göra ' e 'cross-browser friendly: e = e || window.event; // Nu kan vi säkert hänvisa till 'e' i alla moderna webbläsare.  // Vi skulle binda vår funktion till en händelse här nere ... 

För att kontrollera om e-objektet finns ("Event-objektet") använder vi en OR (logisk) operatör som i princip dikterar följande: om "e" är ett "falskt" värde (null, odefinierad, 0 etc.) sedan tilldela "window.event" till "e"; annars använd bara 'e'. Det här är ett snabbt och enkelt sätt att få det verkliga Event-objektet i en webbläsarmiljö. Om du inte är bekväm med att använda logiska operatörer utanför ett IF-uttalande kan den här konstruktionen passa dig mer:

 om (! e) e = window.event;  // Inget ELSE-uttalande behövs eftersom 'e'vilja // redan definieras i andra webbläsare

Några av de mest användbara kommandon och egenskaper för detta händelseobjekt är tyvärr inkonsekvent implementerade över webbläsare (nämligen IE vs. alla andra). Till exempel kan du avbryta standardåtgärden för en händelse med hjälp av metoden "preventDefault ()" för händelseobjektet, men i IE kan det bara uppnås med objektets "returnValue" -egenskap. Så, igen, vi måste använda båda för att rymma alla webbläsare:

 funktion myEventHandler (e) e = e || window.event; // Förhindra standardaktivitet för en händelse: om (e.preventDefault) e.preventDefault ();  annars e.returnValue = false; 

Standardinställningen för en händelse är vad som normalt händer som en följd av att händelsen skjuter. När du klickar på en ankarlänk är standardåtgärden för att webbläsaren ska navigera till den plats som anges i attributet "href" för den länken. Men ibland vill du avaktivera den här standardåtgärden.

"ReturnValue" / "preventDefault" irritation är inte ensam; många andra egenskaper hos händelseobjektet är inkonsekvent implementerade så det här om / else / eller checkmodell är en obligatorisk uppgift.

Många av dagens JavaScript-biblioteken normaliserar händelseobjektet, vilket betyder att kommandon som "e.preventDefault" kommer att finnas tillgängliga i IE, men du bör notera att bakom kulisserna används "returnValue" -egenskapen fortfarande.

Event bubbling

Händelsebubbling, även känd som "händelseförökning", är när en händelse avfyras och då händelsen "bubblar" upp genom DOM. Det första att notera är att inte alla händelser bubblar, men för dem som gör, så är det hur det fungerar:

Händelsen brinner på målelementet. Evenemanget bränder sedan på varje förfader till det elementet - händelsen bubblar upp genom DOM tills den når det översta elementet:


Event bubbling, illustrerad

Som visat i ovanstående grafik, om ett ankare inom ett stycke är klickat, kommer ankarets klickhändelse att elda först och sedan följer stycken klickhändelsen etc. till dess att kroppselementet nås (kroppen är det högsta DOM-elementet som har ett klick händelse).

Dessa händelser kommer att elda i den ordningen, de sker inte alla samtidigt.

Idén om händelsebubbling kanske inte förstår mycket men i slutändan blir det klart att det är en grundläggande del av vad vi anser vara "normalt beteende". När du binder en hanterare till klickhändelsen i stycket förväntar du dig att den ska avfyra när stycket är klickat, eller hur? Tja, det är precis vad "händelse bubblande" säkerställer - om stycket har flera barn, (s, s, s) så kommer även händelsen att bubbla upp till stycket, även när de är klickade.

Detta bubblande beteende kan stoppas vid någon tid under processen. Så om du bara vill att händelsen ska bubbla upp till stycket men inte längre (inte till kroppsnoden) kan du använda en annan användbar metod som finns i händelseobjektet, "stopPropagation":

 funktion myParagraphEventHandler (e) e = e || window.event; // Stoppa händelsen från att bubbla upp: om (e.stopPropagation) // W3C-kompatibla webbläsare: e.stopPropagation ();  else // IE: e.cancelBubble = true;  // Funktionen skulle vara bunden till klickhändelsen i stycket: // Använda vår anpassade addEvent-funktion: addEvent (document.getElementsByTagName ('p') [0], 'click', myParagraphEventHandler);

Event delegation

Låt oss säga att du till exempel har ett massivt bord med många rader av data. Binda en händelsehanterare till varje enskild kan vara en farlig strävan, främst på grund av den negativa effekten det har på prestanda. Ett vanligt sätt att bekämpa detta problem är att använda "händelsesdelegation". Event delegation beskriver processen att tillämpa en händelsehanterare på ett behållarelement och sedan använda det som grund för alla barnelement. Genom att testa egenskapen "målet" ("srcElement" i IE) i händelseobjektet kan vi bestämma det verkliga klickade elementet.

 var myTable = document.getElementById ('my-table'); myTable.onclick = function () // Hantering av webbläsares inkompatibiliteter: e = e || window.event; var targetNode = e.target || e.srcElement; // Testa om det var en TR som klickades: om (targetNode.nodeName.toLowerCase () === 'tr') alert ('Du klickade på en tabellrad!'); 

Evenemangsdelegation är beroende av händelsebubbling. Ovanstående kod skulle inte fungera om bubblingen stannades innan man nådde bordet.

Det är det för idag!

Vi har täckt hur man manipulerar DOM-element och vi har diskuterat, i ganska mycket djup, webbläsarhändelsesmodellen. Jag hoppas att du har lärt dig någonting idag! Som vanligt, om du har några frågor, tveka inte att fråga.

  • Prenumerera på NETTUTS RSS-flödet för fler dagliga webbutvecklingstoppar och artiklar.