Välkommen till HTMLHunden! En pragmatisk höghastighetsguide till webbutveckling. En guide som ger dig tillräckligt mycket för att ta dig ifrån den absoluta grunden till att bygga moderna/dynamiska/responsiva/snabba/pragmatiska webbsidor. Vi berättar inte bara hur du gör. Vi ger dig inte bara en massa kodexempel. Vi förklarar varför och ger dig kodexempel. Vår filosofi är inte att lära dig allt — utan att lära dig tillräckligt mycket för att du ska kunna lära dig själv! Det tror vi är det snabbaste sättet. Så på med rakethatten så kör vi!
Den här guiden är till för dig. Just ja. Precis just dig. Inte någon hypotetisk student någonstans. Den är inte heller mer till för dina kompisar än för dig. Eller mer till för de som du tycker kan så himla mycket mer än vad du kan. Den här guiden är till för vemsomhelst som vill lära sig att skapa webbsidor. Så oavsett om du vill lära dig ifrån grunden, eller vill förbättra dina redan existerande kunskaper, så är den här guiden till för just dig.
Vi förväntar oss egentligen inte att du har några förkunskaper som helst förutom en viss datorvana, och att du någon gång har sett ett programmeringsspråk. Vi försöker verkligen att täcka allt men någonstans måste det ta stopp. Men vi har satt gränsen för vad som krävs av dig på en plats som vi känner oss mycket bekväma med. Det viktiga är att du själv använder ditt intellekt och dina nätsökskunskaper för att hela tiden aktivt försöka förstå vad som sägs.
HTMLHunden är ett levande dokument. Vi skriver om, skriver nytt, och skriver om konstant. Såldes kan många delar fotfarande kännas inkompletta. Men vi lovar, vi skriver för fulla muggar och det är på väg.
Om du har något du vill berätta, så tryck gärna på feedback-knappen (till höger). Customer is king! Du är vår kund, och din åsikt är viktigast! Om du tycker att någonting är konstigt, bra, dåligt, kunde bli bättre, fel, eller rent utsagt helt uppåt väggarna så vill vi gärna ha in din åsikt.
Vi är medvetna om att denna guide är högst opinionerad, och således partisk. Om du står för andra åsikter än dem som presenteras här så skulle vi fantastiskt gärna vilja höra dina åsikter. Vi välkomnar olikheter. Allt för att bli bättre.
Vi jobbar även på att skapa videos för stora delar av det här materialet. Det finns en del video nu, vilket du hittar inbäddat i löptexten. Om du är intresserad av att se alla videoklippen i ordning utan att leta omkring här så hänvisar vi dig vänligast till vår playlist på YouTube.
Den här guiden är framtagen av Christopher Okhravi (@chrokh) (Universitetsadjunkt, Uppsala Universitet) och Madelen Hermelin (Universitetsadjunkt, f.d. Forskningsassistent, Uppsala Universitet). Detta är ett dokument som delvis används vid undervisning under Institutionen för Informatik och Media vid Uppsala Universitet, men bör ses som ett uttryck för författarnas personliga åsikter och inte universitetets.
Alldeles snart ska vi djupdyka in och skapa vår första webbsida. Men först behöver vi snabbt orientera oss. Vad är egentligen en webbsida? Vad är egentligen en webbläsare? Och vad är det som gör att en webbläsare kan läsa en webbsida? Låt oss bena ut några begrepp.
Vi kommer sannolikt möta fler definitioner under denna resas gång. Men låt oss, för enkelhetens skull, definiera en webbsida som följande: Ett interaktivt dokument med hyperlänkar till andra interaktiva dokument.
Men vad skiljer då dessa "interaktiva" dokument ifrån helt vanliga textdokument. Låt oss se till några exempel. Webbsidor kan...
Alla ovan punkter har förstås inte alltid varit sanna. I webbens tidiga dagar hade vi tur om vi träffade på en animerad gif av en hamster som dansade. Idag har vi världsförändrande webbsidor såsom Google Maps. Gränsen för vad som kan täckas in av ordet webbsida börjar tänjas längre och längre, och gränsen mellan en traditionell applikation och en webbsida är nästan redan utsuddad. Men låt oss återkomma till den här diskussionen i ett senare kapitel.
Du har sannolikt redan en god uppfattning om vad en webbläsare är. Men det är viktigt att vi funderar över det lite närmare. Låt oss försöka bryta ned det.
Låt oss fundera lite på vad en webbläsare är genom att fundera på vad ett datorprogram är. Ett datorprogram är (oftast) en uppsättning funktioner som transformerar input till output.
I vissa fall innebär denna transformation att vi går ifrån ett filformat till ett annat. Säg t.ex. när vi konverterar en videofil ifrån ett format till ett annat. Kanske för att det ska gå att spela upp på en mobil enhet.
I andra fall innebär denna transformation att vi går ifrån ett format som endast en dator kan förstå till ett format som en människa kan tolka genom något av sina sinnen. En rå videofil är t.ex. allt för komplex för att vi som människor ska kunna läsa dess "binära" data och förstå vad filmen handlar om. Istället måste datorn successivt "koda om" detta format till en ström av bilder som visas upp på vår skärm.
Ett videouppspelningsprogram såsom VLC, Media Player Classic, Quick Time, eller Windows Media Player fungerar alltså detta fall som en slags "tolk" av information. Återigen, är programvarans uppgift alltså att transformera någonting ifrån ett format till annat.
Men vad har detta med webbläsare att göra? Fundera på vad vi nyss ha sagt, och fundera sedan på att alla webbläsare också är program. Webbläsare transformerar input till output. Nothing more, nothing less.
Webbläsarens input är (t.ex.) ett HTML-dokument, och webbläsarens output är (bl.a.) en, på skärmen, visuell representation av strukturen och innehållet av det dokumentet. HTML är med andra ord maskinens sätt att läsa ett dokument, och webbläsarens visuella representation av samma dokument är den mänskligt läsbara motsvarigheten.
Innan vi går vidare behöver vi fundera lite på vad en fil är för någonting. Alltså helt vanliga filer som du hittar på din egen dator. Bildfilder, dokument, musik etc. Det två viktiga realisationer vi behöver göra.
Egentligen kan vi se allting som text. (Sanningen är djupare än så. Egentligen är även mänsklig text för en dator bara hittepå och konvention. Men det är att gräva onödigt djupt för nu). Om vi accepterar att alla filer, oavsett om de är bilder, filmer, textdokument eller webbsidor egentligen bara består av text så börjar vi nå intressanta frågor. Hur i hela friden kan en biofilm representeras i text? Låt oss formalisera frågorna.
Svaret på den första frågan är egentligen tätt bundet till svaret på den andra. Ponera t.ex. talserien (0,255,255)
. Ponera att vi "mappar" dessa siffror emot konventionen RGB (Red, Green, Blue) (som används för att representera färg). Ponera att varje värde ska separeras med ett komma-tecken. Ponera även att 0
betyder "färgkanal på" och 255
betyder "färgkanal av". Denna tankelek göra att vi genom konvention och helt vanlig plain-text specificerat färgen röd. Poängen är alltså att om vi bara ser till texten i sig är den helt meningslös. Men när vi applicerar idéen om konventionen på texten fyller den plötsligt ett syfte.
En fil är bara en mängd arbiträr men strukturerad data. Program följer olika konventioner, och när man läser en fil med hjälp av en viss konvention kan man extrahera den information som faktiskt finns inbäddad i filen. D.v.s visa bilden, spela upp ljudet, representera dokumentet.
Så hur vet operativsystemet vilken konvention den ska applicera? Tänk tillbaka på tidigare diskussion om att ett programs enda uppgift är att transformera input till output. Om ett program är specialiserat på att transformera en viss typ av input till en viss typ av output — då är det en utmärkt kandidat för att vara en explicit konvention. Innehållet i en fil är alltså input (texten) och ett given program är alltså transformationsprocessen (konventionen).
Låt oss formulera det i andra ord. En fil innehåller egentligen bara strukturerad råtext (egentligen: data). När denna fil med strukturerad råtext sedan öppnas med det program den var ämnad att öppnas i, kommer denna till synes mumbo jumbo av text helt plötsligt spela sin roll.
Prova själv genom att öppna t.ex. en .jpeg-fil i en vanlig texteditor (såsom Notepad eller TextEdit).
Egentligen kan vi slänga på vilken filändelse (alltså t.ex. .tex, .doc, .pdf, .jpg, etc.) på vilken fil som helst. Den spelar ingen roll för själva innehållet i filen. Så länge vi öppnar filen med det avsedda programmet (och så länge det avsedda programmet tillåter oss att forcera det att öppna filer med "fel" filändelse) så kommer allt fungera precis som vanligt.
Faktum är att UNIX-baserade operativsystem i grund och botten inte bryr sig om filändelser. Istället för att filändelsen specificerar vilket program som bör användas för att öppna filen, så specificerar filen själv vilket program som bör användas.
Medan det konkreta beteendet skiljer sig ifrån operativssytem till operativssystem, så är dagens läxa alltså att alla program i teorin egentligen kan läsa alla filer. Det går bara mer eller mindre bra. Det gäller alltså bara att det att programmet applicerar rätt konvention för hur filen ska läsas. Texteditorer (t.ex.) kan öppna ljudfiler, de kan visa upp det råa innehållet i textuell representation. Men de har ingen aning om hur man faktiskt får ljudet att spelas upp.
Låt oss analysera ett scenario du själv eller någon du känner kanske redan stött på. Anta att vi har en fil: bild.jpg
. Anta sedan att vi döper om den till bild.png
. Har filen nu konverterats ifrån formatet JPEG till PNG? Fundera... Självklart inte! Filändelsen är ju bara en del av filnamnet. Inte en del av innehållet. Innehållet är fortfarande detsamma. JPEG är en konvention, PNG en annan. Om filens innehåll följde konventionen JPEG tidigare, och vi endast förändrade filändelsen (alltså filens namn) så innehåller den ju fortfarande data som följer JPEG-konventionen. Alltså har filen inte konverterats. Däremot kommer vi sannolikt ha det svårare att öppna filen i vissa program och/eller operativsystem. Programmet kan komma att anta att filen följer konventionen PNG (i.o.m. att filen har en filändelse som är .png) men när filen sedan faktiskt följer konventionen JPEG (eftersom innehållet i filen följer JPEG-konventionen) så blir programmet superförvirrat och ger upp. En fil med innehåll som följer JPEG-konventionen är inte en fil som följer PNG-konventionen — oavsett vilken filändelse vi slänger på. En JPEG-fil är inte en PNG-fil — oavsett vilken filändelse vi slänger på.
Låt oss nu prata om HTML. Webben drivs i stor del av ett märkesspråk (markup language) vid namn HTML. Innan vi kan börja förstå vad HTML är behöver vi förstå vad ett märkesspråk är.
Ett märkesspråk (oavsett vilket) kan ses som ett textfilsformat vi använder för att "markera upp" olika delar av en text. T.ex. kan det användas för att tilldela ett dokuments olika delar olika semantisk betydelse. Men vad menar vi då med att "markera upp"?
Reflektera över denna sida. Över denna text. Du ser att vissa delar är paragrafer och att vissa delar är rubriker. Du ser att denna paragraf inte är samma som den föregående. Hur? T.ex. är du ju en människa som vet att du ska tolka upprepade radbrytningar som paragrafskiften. Men hur visste datorn det? Eller mer specifikt. Hur vet din webbläsare (eller det program du använder för att läsa texten) vad som är vad? Hur vet webbläsaren vad som är en rubrik, vad som är en paragraf, vad som är en länk och vart länkarna pekar? Som du antagligen gissat är svaret märkesspråk — markup languages.
Vi använder alltså märkesspråk för att delimitera olika sektioner i en flödestext. Om du tänker tillbaka på vad vi tidigare diskuterat om att allting är rå text, så kan denna text i teorin representera vad som helst.Text, video, bild etc. I vilket fall så använder vi märkesspråk för att delimitera olika delar av dokumentet i olika sektioner. Låt oss se till ett snabbt exempel på hur detta fungerar i märkesspråket HTML.
Endast ett ord i denna text, nämnligen <b>detta</b> kommer att visas i fetstil.
Om vi kör ovan genom en webbläsare så kommer webbläsaren att rendera följande resultat:
Notera alltså hur användandet av notationen <b>...</b>
används för att delimitera en del av texten som är av extra semantisk vikt och bör visas upp i fetstil. Detta är vad som i HTML kallas för ett element
. Ett element som byggs upp genom kombinationen av en start-tag (<b>
) och en slut-tag (</b>
).
Användandet av bokstaven b
i ovan exempel är något sånär arbiträrt. Bokstaven delimiterar vilket elment det är vi vill skapa. I HTML finns ett antal fördefinerade element, varvid b
är just ett sådant. Användandet av tecknena mindre än (<
), större än (>
), och slash (/
) är dock inte arbiträra. De tillhör märkesspråket HTML's syntax.
Syntax är ett ord som används för att beskriva hur vi uttrycker någonting i ett visst språk. Varje programmeringsspråk och märkesspråk har en egen syntax. Olika märkesspråk har olika syntax, men idéen är densamma. Ett märkesspråk använder någon form av syntax för att "markera ut" vilka delar av en text som är av vilken typ. T.ex. rubriker, paragrafer, länkar, emfas, citat o.s.v.
Vi kommer att gräva mycket mer i hur HTML fungerar senare. Men för nu behöver du förstå att HTML är ett märkesspråk, och att märkesspråk används för att "markera upp" olika delar av en text så att en maskin kan "förstå" dokumentet.
Den huvudsakliga frågan vi ska försöka besvara i detta kapitel är — hur fungerar en webbsida? Alltså, hur kan det komma sig att när vi knattrar in www.google.com i en webbläsare och trycker enter, så kommer en interaktiv webbsida tillbaka.
För att kunna svara på den frågan behöver vi bena ut ett par olika begrepp och lära oss lite mer om den arkitktur som vårt kära internet bygger på. Vi behöver bl.a. prata om klienter, servrar, nätverk, requests och responses.
Förnim dig att vi redan har diskuterat att alla filformat bygger på konventioner. Och att alla program "läser" filer genom att de känner till rätt konventioner. Webbläsaren är egentligen ett helt vanligt program som bl.a. bygger på konventioner kring formatet HTML.
Webbläsaren är alltså ett program som "förstår HTML". Om du öppnar en webbläsare och säger "File > Open"
så förväntar den sig att du ska ge den en fil i (t.ex.) HTML-format.
När vi ger webbläsaren en fil i HTML-format så tolkar webbläsaren filen enligt de konventioner som specificeras av HTML-standarden. Tyvärr har olika webbläsare valt att implementera olika delar av standarden. Samt tolkat olika delar av standarden olika. Vi kommer prata mer om denna situation senare. Hur det kan komma sig att samma kod kan betee sig olika i olika webbläsare. Vad det har för konsekvenser, och hur vi kan hantera det.
Men det vi behöver fokusera på nu, är alltså att vi inte behöver se webbläsaren som ett program som "browsar" internet. Vi kan likväl se webbläsaren som ett program som kan rendera HTML-filer.
Så, vad menar vi då med att webbläsaren "renderar" en HTML-fil? De webbläsare vi oftast kommer i kontakt med (Google Chrome, Firefox, Internet Explorer etc.) är webbläsare vars uppgift (bl.a.) är att "läsa" HTML-filer och rendera deras visuella representation på skärmen. Vi, i egenskap av människor, vill inte se själva markup-koden utan en rimlig visuell representation av den markup-koden. Om ett antal ord t.ex. är omslutna av bold-taggen (ex: <b>Hej</b>
) så vill vi inte (när webbläsaren har renderat dokumentet) se själva taggen. Istället vill vi se texten som omslöts av elementet i visuell fetstil (alltså: Hej). En webbläsares uppgift är alltså (bl.a.) att konvertera ett HTML dokument ifrån ett machine-readable format format till ett human-readable format, som visuellt representeras på skärmen.
När vi talar om språk för webbutveckling talar vi oftast antingen om server-side- eller client-side-språk. Vi pratar också om statiska och dynamiska webbsidor. Förenklat uttryckt så kan vi med hjälp av server-side-språk uppnå dynamiska språk. Utan server-side språk kan vi endast skapa statiska webbsidor. Detta är en halvsanning (p.g.a. JavaScript), men det kommer vi tala om mer senare. För att skapa dynamiska webbsidor behöver vi introducera någon form av programmerings/scripting-språk. Med märkesspråk endast, kan vi alltså endast skapa statiska webbsidor.
Notera att ovan paragraf inte använder facktermer utan bör istället betraktas som en tolkning.
Innan vi kan gå över till att diskutera statiska, dynamiska och skillnaden emellan de — låt oss återigen påminna oss själva om hur språket HTML ser ut.
Ett ord i <b>fetstil</b> och ett <u>understruket</u>.
HTML är alltså det märkesspråk vi använder för att "märka upp" ett stycke media. Vi använder termen media eftersom HTML-dokument inte endast behöver bestå av text. De kan även innehålla bilder, ljud och video.
Med ordet statiskt menar vi (krasst) att om vi skulle öppna samma HTML-dokument två gånger i samma webbläsare så kommer vi att se ett precis likadant dokument andra gången som första. Låt oss omformulera detta. Ett icke-dynamiskt dokument är alltså ett icke-förändrande dokument. Ett statiskt dokument är ett dokument som inte förändras oavsett när vi visar upp det.
Låt oss uttrycka oss tydligare. När vi har en statisk webbsida så är det alltid samma markup (HTML) och resurser (bilder, css, javascript etc.) som levereras till webbläsaren. När vi bygger statiska sidor så har våran webbapplikation inte heller någon möjlighet att kommunicera med servern. Om sidan inte kan kommunicera med en server så kan den inte heller hålla någon form av "state". Med state menar vi minne. Om vi t.ex. vill bygga en highscore-lista för ett spel så behöver vi ett persistant state för att alla ska
Ett exempel på en statisk webbsida är denna. Alltså htmlhunden.se. De dynamiska delarna (såsom t.ex. innehållsförteckningen) av denna bok har redan genererats i förväg. När din webbläsare ber om att få den sida du är på just nu — så kommer servern alltid att servera samma HTML. Nämligen den du just nu ser på.
Om statiska webbsidor innebär webbsidor där den server som serverar webbsidan alltid serverar samma filer vid sina URL:er — vad är då en dynamisk webbsida? Som du kanske redan gissat, kan servrar som serverar dynamiska webbsidor servera olika HTML olika gånger trots att vi begär samma URL. Det finns alltså inga garantier för att vår webbläsare kommer att motta samma resurser när vi laddar om sidan.
Vi behöver skapa dynamiska webbapplikationer när vi behöver...
Det finns (som alltid) ett par undantag i relation till ovan lista. Men de undantagen är av för hög överkurs för att vi ska gå in på dem. I stora drag är ovan lista sann.
Kommer snart...
Innan vi kan gå djupare in på varför vi inte kan utföra komplex logik i html så behöver vi lära oss lite om ansvarsområden för klienter och servrar. Vi har tidigare i detta kapitel uttryckt oss i bemärkelsen att en "server serverar en fil". Om vi är ännu mer explicita skulle vi kunna säga att en server severar en fil som en klient konsumerar.
En server servererar resurser som konsumeras av klienter.
Vad i hela friden menar vi med det? Vem är servern? Vem är klienten? Vad är en resurs? Detta är förstås generella termer som kan anta många skepnader. Men i de flesta av fall är din webbläsare klienten. Klienten är den som konsumerar. Klienten är den som blir serverad en HTML-sida. Klienten ansvarar själv för att rendera sidan. Såsom vi tidigare diskuterat. Webbläsaren (klienten) är ett program som är expert HTML-konventionen. Så när webbläsaren tar emot ett dokument som följer konventionen HTML så kan den utan problem rendera den visuella representationen av det dokumentet. Oavsett om du använder din webbläsare för att öppna www.google.com eller en HTML-fil på din egen dator så är webbläsaren en klient.
Servern däremot kan variera. När du öppnar www.google.com i en webbläsare så finns servern någon helt annanstans. Din klient (webbläsaren) skickar ett request till någon av (i detta fall) Google's servrar som svarar med ett response innehållandes HTML. Eftersom din klient (webbläsaren) är expert på att läsa HTML så läser den svaret och renderar den visuella representationen i din webbläsare.
När du öppnar en HTML-fil på din egen dator så är det din egen dator som är servern. När du väljer File > Open
, väljer en fil och sedan klickar OK så pratar din webbläsare med filsystemet på din dator. Löst uttryckt, ber din webbläsare helt enkelt ditt filsystem om den fil som finns på den plats där den fil du valde finns. Du kommer senare upptäcka att vad en traditionell webbserver gör, faktiskt inte ens är särskilt annorlunda än detta triviala scenario.
För att slutligen svara på vad vi menar med ordet "resurs" i ovan citat så kan det egentligen vara nästan vad som helst. Vi ska strax prata om request-response-modellen. Allt som går att skicka som ett response är en resurs som skulle kunna serveras av en server och konsumeras av en klient. HTML-dokument, bilder, javascript-filer, musik, Word-dokument etc.
När vi som webbutvecklare utvecklar sidor är det vanligt att vi kör en lokal webbserver på vår egen dator. Detta bl.a. för att närmare efterlikna (mimik:a) den miljö som vår webbsida faktiskt kommer att leva i när den ligger "live" på Internet.
Många tänker på mörka dundrande hallar fyllda med korridorer av monstermaskiner när de tänker på servrar. Nu vet du att verkligheten absolut ibland ser ut just så. Men du vet också att det inte alls behöver vara fallet. En server är egentligen också bara ett program som kan köras på en dator. Denna dator kan lika gärna vara din egen laptop. Och programmet kan lika gärna vara väldigt litet.
Internet bygger på en modell som vi kan kalla för request-response-modellen. Ett "request" är en förfrågan, och ett "response" är ett svar. Vi vet ju nu att en servers uppgift är att servera klient med resurser. Men mer specifikt så serverar en server ett (passande) response till en klient, när denne klient skickar ett request. Vi säger passande eftersom reponse:et förstås beror på vilket request vi har skickat.
En server serverar ett response till den klient som skickar ett request.
Det här låter kanske komplext men är egentligen väldigt enkelt. Låt oss se till det lite närmare. Aktörerna i denna klient-server-historia är alltså följande:
En konversation mellan de två parterna skulle alltså kunna låta så här:
client request "Hörru servern, jag skulle vilja titta på filen index.html." server response "Okidokes, här kommer den!" Klienten läser filen och upptäcker att filen refererade till en bild som den också behöver client request "Du din gamle server, du berättade inte att jag behövde logo.png också, langar du över den är du snäll!" server response "Sorry, eftersom jag bygger på den gamla request-response-modellen kunde jag inte berätta det för dig på en gång, här har du!"
Ovan figur är alltså en visualisering av hur ett request-response-scenario skulle kunna spela ut sig, när en klient ber om en webbsida. Och i essens är det ungefär det här som händer varje gång vi öppnar vår webbläsare och skriver in en adress såsom exempelvis www.google.com.
Låt oss nu istället prata om server-side språk. Ett server-side-språk är ett lös term som refererar till ett programmeringsspråk som kan användas för att leverera ett response när ett request kommer in. Tänk så här. Vi skulle kunna ha en klient-server-arkitektur helt utan ett server-side-språk. Hur? Jo, klienten ber om en sida genom ett request som vandrar över internet och når rätt server som rakt av svarar med ett response som är en html-sida.
Men nu är det ju så att det moderna internet består av mycket mer komplexitet än statiska sidor. Den enda skillnaden vi introducerar i ovanstående process handlar då om att vi introducerar ett språk som ansvarar för att konstruera html-sidor beroende på response.
Vi omformulerar ovan paragraf. Ett server-side-språks huvudsakliga uppgift är alltså att "hitta på" ett html-dokument. Ett statiskt response skulle vara att bara svara med en existerande html-fil. Men ett dynamiskt response skulle innebära att server-side-språket först utför en del logik, och sen "on the fly" skapar den html-fil som servern svarar med.
Exempel på server-side-språk är t.ex. PHP, Ruby, Python, ASP.NET etc.
Så om server-side-språket "genererar" HTML-filer — varför måste vi då lära oss att skriva det själva? Enkelt svarat — eftersom det är vi som skriver server-side-koden, och därmed även vi som definierar hur HTML-sidorna ska genereras. Det finns alltså inte någon magisk HTML-generator utan någonstans måste vi definera exakt hur HTML-sidorna ska genereras beroende på de request vi får in. Vi återkommer alltså till den gamla tanken mdash; eftersom datorer är korkade, så måste vi berätta för dem exakt vad vi vill ha.
Navet i det stora webbhjulet måste vi förstås hävda är just HTML. Detta markup-language har hängt med länge och är just det språk vi använder för att strukturera upp information på ett sätt som en webbläsare förstår. Detta kapitel kommer lära dig hur HTML fungerar och hur vi skriver det.
Innan vi verkligen ger oss på hur man använder HTML, CSS och JavaScript så kan det vara skönt att få en snabb introduktion till hur allt hänger samman. Kika igenom filmen på 25 minuter nedan.
HyperText Markup Language (HTML) är det språk vi använder för att märka upp content vi vill kunna visa i en webbläsare. I detta kapitel diskuterar vi vad HTML är och hur vi skriver det.
Innan vi ser till ett exempel behöver vi kort diskutera vad HTML är för någonting. Låt oss utföra en tankelek. Ponera att du, på datorn, skrivit ett dokument i Microsoft Word, Pages, Open Office eller dylik ordbehandlare. Fundera kort över vad detta dokument består av. Vad innehåller det? Text? Jo, onekligen. Men mer exakt än så då? De flesta dokument innehåller någon kombination av rubriker, underrubriker och paragrafer. Men gräver vi djupare än så så hittar vi saker som citat, listor, understrykningar, fetstilsmarkeringar, kursivitet o.s.v.
För att förstå hur HTML fungerar behöver vi egentligen bara förstå att vår ordbehandlare omöjligen kan komma ihåg vilka delar vi anser vara rubriker eller fetstilsmarkeringar om den inte någonstans sparar den informationen när vi först berättar det. Låt oss uttrycka oss på generellare form. Nästan alla dokument — ovavsett typ — består inte bara av text. De består av text som är tätt bunden till semantik. Varje del av texten spelar någon roll i helheten som inte nödvändigtvis behöver vara samma roll som någon annan del av samma text. En term som ofta används för att referera till detta förhållande mellan text och meta-information är semantik. Vi kommer framöver på många sätt prata om begreppet semantisk signifikans för att diskutera vad saker har för signifikans i en kontext.
Dokument består inte bara av rå text, utan rå text tätt med olika semantik.
En paragraf är inte en paragraf om inte den som läser paragrafen kan urskilja och förstå att det är en paragraf den läser. Det är detta vi talar om när vi talar om semantisk signifikans. Utöver att vi som människor läser texten som paragrafen består av så "läser vi in" faktumet att det är ett avgränsatt område text — i.e. en paragraf.
Om information saknade semantisk signifikans hade vi likväl kunnat representera dokument i ett enda långt flöde. All text på en enda lång rad. Men vi människor verkar gilla semantisk indelning för att t.ex. underlätta inlärning. Således har vi lärt oss att (t.ex.) extra vertikalt mellanrum (whitespace) emellan rader denoterar en paragrafindelning och således ett avslut och en påbörjan på en ny tanke/poäng. En paragrafindelning denoterar alltså slutet på en idé och antagligen början på nästa.
Men maskiner (e.g. webbläsare) har idag inte den förståelse som ovan beskrivs. En webbläsare kan inte förstå vad den ska representera som en egen paragraf genom att processa texten. Maskinen läser inte texten och börjar tänka att "hmm.. här ska vi nog ha en radbrytning". Därför behöver vi berätta för maskinen vad som är vad.
Maskiner behöver inte bara veta vad som är vad för att vi ska kunna närma oss artificiell intelligens. Utan i fallet av webben behöver webbläsaren helt enkelt veta vad som är vad för att kunna presentera informationen för oss på ett rimligt sätt. Kom ihåg. Annars är vi tillbaka i den svårhantereliga värld där paragrafindelningar inte existerar och all text kommer i ett enda långt stycke ifrån början till slut rakt av. Vilken pers det hade varit.
Självklart handlar alltså inte detta om endast paragrafer. Den abstraktare poängen är alltså att vi måste denotera olika delar för att en maskin ska kunna resonera kring dem. Om vi inte berättar för maskinen vad som är rubriker, vad som är paragrafer, vad som är listor o.s.v. o.s.v.
HTML-dokument bygger på en metafor om att alla dokument går att beskriva som hierarkiska trädstrukturer. Fundera på det. I stort sett alla dokument går att beskriva som en hierarkisk trädstruktur. Ta en bok till exempel. Högst upp i abstraktionskedjan hittar vi av ett antal delar. Boken börjar med ett förord, sen kommer första delen, sedan andra delen, och avslutningsvis efterordet. Böcker ser ju förstås olika ut men detta är ett exempel på hur en bok skulle kunna se ut. Men indelningen tar ju inte slut när vi har delat upp boken i olika delar. Under varje del hittar vi kapitel. Nu kan vi se boken som att den består av ett antal delar under vilka det finns ett antal kapitel. Letar vi vidare så upptäcker vi att varje kapitel innehåller paragrafer. Och letar vi ännu vidare så upptäcker vi att vissa paragrafer innehåller bilder. Om vi skulle försöka representera ovan bok som en linjär trädstruktur skulle den rimligen se ut så här: Del > Kapitel > Paragraf > Bilder
.
Vad vi nu har beskrivit är (i en trädstruktur) relationen mellan föräldrar och barn. Ett förälder kan innehålla barn, och barn kan höra till föräldrar. Ett kapitel kan innehålla paragrafer och en paragraf kan tillhöra ett kapitel. Därav användandet av notationen med större-än-tecknet (>
). Det till vänster om större-än-tecknet är alltså föräldern. Men om trädstruktur bara kunde ha barn och föräldrar så skulle de vara helt linjära. Istället behöver vi inse att barn kan ha syskon. En förälder kan alltså ha flera barn. Eller uttryckt i termer av ett träd. En gren kan ha flera förgreningar. Vi kommer att prata mer om detta när vi ser till ett par dokumenstruktursexempel.
HTML har många likheter med ett annat märkesspråk vid namn XML. XML är också ett märkesspråk som bygger på idéen om att information kan strutkrureras hierarkiskt. Den huvudsakliga skillnaden mellan HTML och XML är att HTML specifikt är skapat för att beskriva webbsidor. XML däremot är ett "general purpose" märkesspråk som kan användas för att beskriva vilken semi-strukturerad information som helst.
Men när vi nu börjar diskutera element så är det alltså viktigt att du kommer ihåg vårt mål när vi skapar HTML-dokument. Målet är att modellera den information vi önskar att beskriva i ett hierarkiskt format.
Vi har nu alltså klargjort att HTML bygger på en metfor om att alla dokument går att modellera som hierarkiska trädstrukturer. Men hur gör vi då detta rent konkret? Svaret i HTML är något som benämns element. Element skapar vi genom att använda oss av <
, och >
-tecken. Följande är ett exempel på ett element som denoterar en paragraf.
<p>Paragraftexten här...</p>
Ovan illustrerar alltså användandet av p
-elementet, eller paragraf-elementet. Låt oss bena ut vad syntaxen är. Kom ihåg att ordet syntax handlar om hur vi uttrycker någonting i ett visst språk. I fallet av HTML så är de tre första tecknena i ovan exempel, samt de fyra sista del av syntaxen i HTML. Övrigt är rå text.
HTML-element byggs upp av taggar.
HTML-element består helt enkelt av någonting som vi referar till som taggar (tags). Element är alltså en komposition av taggar. Ett element kan komma i två former. Element kan alltså bestå av antingen...
Paragraf-elementet vi såg tidigare är ett ypperligt exempel på den första formen där vi har ett tag-par som tillsammans bildar ett element. Vi öppnar en paragraf, skriver en text, och stänger sedan paragrafen. Notera alltså att vi använde slash-tecknet (/
) och det återupprepade tagg-namnet (p
) för att denotera stängningstaggen (</p>
). Men låt oss se till ett exempel på ett element som inte kräver en stängningstagg.
Plötsligt...
<hr>
...dök en horisontell linje upp
Elementet <hr>
denoterar en horisontell linje (avdelare) som ritas ut rakt över sidan. En s.k. "horizontal ruler". Om vi funderar lite på det en stund så inser vi snabbt varför ensamma element existerar. En avdelare är en avdelare och det finns ingenting intelligent vi kan denotera innuti en avdelare. Andra element av denna typ är t.ex. bilder. Här ser vi också en tydlig anledning. En bild är ju alltid en bild. Det är ologiskt att anta att vi skulle vilja denotera existensen av någonting i bilden. Bilden själv beskriver vad som finns i bilden.
Om detta låter lite "lurvigt", oroa dig inte. Vi kommer att diskutera detta närmare och förhoppningsvis blir det klarare när du får se några exempel. Men för nu — kom ihåg att det finns två typer av element. De som inte behöver stängas (eftersom vi kan placera element eller content i dem), och de som behöver stängas.
För att kunna bygga hierarkiska trädstrukturer så behöver vi förstå att HTML kan innehålla element i element. Ett element kan alltså vara barn till ett annat element. Med andra ord kan vi sluta oss till att ett element som öppnas och stängs kan (i sin kropp) innehålla antingen...
När vi säger text så menar vi förstås även avsaknaden av text. Den tomma strängen. Så är det även tillåtet att ett element, som förväntar sig ett barn eller text, inte innehåller någonting. Med andra ord skulle elementet öppnas (starttagg) och sedan stängas på en gång (sluttagg).
Ett element kan antingen innehålla ett annat element eller text.
Det är när vi börjar förstå att element kan innehålla andra element som vi verkligen börjar närma oss idéen om hierarkisk informationsrepresentation. Vi kan nu alltså börja uttrycka saker såsom:
<section>
<p>En första paragraf.</p>
<p>Följd av en annan.</p>
</section>
En viktig poäng om relationen mellan text och element är att webbläsaren tolkar allt som inte denoterats som någonting annat som text. Oavsett hur många radbrytningar eller mellanslag vi slänger in i ett stycke text så kommer webbläsaren ändå att trunkera all text. Med andra ord hamnar all text på samma rad. Och upprepade mellanslag ersättsm ed ett enda.
Man skulle alltså kunna säga att HTML i någon bemärkelse är addativt. Vi har en mängd text, och omsluter sedan olika delar av texten med element för att skapa en semantisk indelning. Utan element, har vi ingenting annat än en enda lång sträng av text.
Anta t.ex. att vi vill lägga in ett antal radbrytningar i en paragraf. Anta att vi försöker göra det genom att helt enkelt skapa radbrytningar med hjälp av ENTER-tangenten vid de platser vi vill. Beakta nedan kod:
<p>
Oavsett
hur många mellanslag, eller
radbrytningar vi lägger in,
så har det ingen effekt.
</p>
Trots alla radbrytningar och mellanslag blir ändå resultatet följande...
Såsom exemplets text förtäljer har traditionella radbrytningar ingen effekt i ett HTML-dokument. Detta är ett gott exempel för att illustrera att webbläsaren läser dokumentet som en enda lång sträng av text. Så länge vi inte använder HTML-element för att denotera åtskilda delar av dokumentet, så kommer webbläsaren att hantera dokumentet som en enda lång massa av text.
Ett elements öppnande tag kan även innehålla attribut med värden. Attribut är i simpla termer egenskaper för ett givet element. Om vi t.ex. har en hyperlänk (<a>
) kan vi använda oss av attributet href
(hyper reference) för att denotera vart hyperlänken ska peka någonstans.
Attribut är egenskaper för en instans av ett element.
För att t.ex. skapa länkar använder vi elementet <a>
. För att sedan denotera vart länken ska peka ger vi attributet href
ett värde. Detta värde tar formen av en URL. Beakta nedan exempel och fundera över användandet av attributet href
.
<a href="http://uu.se">Klicka på mig</a>
Attribut kommer i två former där den vanligaste är nyckel-värde-par (key-value-pairs). Vi specificerar en nyckel och tilldelar den ett värde. Enligt syntaxen nyckel="värde"
. Där ordet "nyckel" alltså ersätts med en nyckel som är tillåten för ett givet element. Och ordet "värde" ersätts med ett värde som är tillåtet för den givna nyckeln.
Alla attribut (nycklar) är inte tillåtna på alla element. Alla värden är inte heller tillåtna på alla nycklar. Anledningen till detta är helt enkelt att attribut denoterar saker som ofta är specifika för just en given typ av element. I exemplet ovan använder vi t.ex. attributet href
— "hyper reference". En hyperreferens är logisk vid användandet av en länk eftersom en länk måste ha en målplats. En länk är inte en länk om den inte har någonstans att länka. Om vi däremot diskuterar en paragraf (<p>
) så blir användandet av en hyperreferens helt meningslös. En paragraf är en paragraf av text, inte en länk. En paragraf ska inte hyperreferera någonstans. Det är inte logiskt.
Olika element tillåter olika attribut.
Nu kanske du tänker att en paragraf ju måste gå att göra klickbar. Och det är helt sant. Men inte genom att klistra på ett hyperreferens-attribut på paragraf-taggen. Istället kan vi omsluta en del av paragrafens text i ett <a>
-element. Kom ihåg — vi kan nästla element i element!
Det finns dock några attribut som vi kan slänga på på precis vilket element som helst. Dessa är id
och class
. Detta är attribut som kommer att visa sig mycket användbara. Vi kommer att prata mer om dessa när vi börjar diskutera CSS och JavaScript.
Vi har nu pratat om både element och attribut. Låt oss sammanfatta. Element är alltså de "grenar" vi använder för att bygga upp vårt "dokumentträd". Attribut är egenskaper vi kan applicera på våra grenar.
I de flesta märkes- och programmeringsspråk så finns faciliteter för vad som kallas för kommentarer. Text vi kan skriva i våra källkodsdokument som inte har någon effekt på det renderade resultatet.
I HTML ser en kommentar ut som följande.
<!-- Åh, en sån kommentar! -->
Kommentarer kan vi skriva i HTML-dokument av olika anledningar men t.ex. skulle vi kunna använda de till att skriva förklarande kommentarer, för att logiskt gruppera olika delar av HTML-dokumentet (för utvecklaren), eller kanske för att skriva en TODO-notis om någonting som måste bättras på senare.
Kommentarer är alltså en facilitet som existerar för att underlätta vårt arbete som programmerare. Kommentarer i HTML har ingen effekt på sidan mer än att de syns i källkoden. Kom ihåg att vem som helst kan visa källkoden för en HTML-sida genom att öppna den i en textredigerare istället för en webbläsare. Alltså behöver vi inse att vem som helst kommer kunna se våra HTML-kommentarer om de vill. Alltså är det inte en smart idé att skriva känslig information (som vi inte vill läcker ut) i våra kommentarer.
Beakta nedan exempel som understryker faktumet att kommentarer inte syns när en webbsida renderas.
Följande HTML...
<p>Detta syns</p>
<!-- Detta syns inte -->
...renderar följande resultat...
Men kommentaren i ovan exempel kommer alltså fortfarande att synas i källkoden. Så om användaren skulle högerklicka och välja "View Source" ("Visa källkod", eller dyl.), alternativt inspektera texten med webbläsarens Webbutvecklarverktyg så kommer denne att kunna se kommentaren. Prova själv!
Vi har nu lärt oss att HTML-dokument modellerar trädstrukturer. Men hur ska då en trädstruktur för ett HTML-dokument se ut? Ett HTML-dokument behöver vara valit ("valid"), enligt standarden, för att kunna renderas korrekt av en webbläsare. Eftersom webbläsare har lite varierande implementationer av HTML-standarden så betyder det att dokumen egentligen inte måste följa standarden till hundra procent. Men det är bra att sträva efter det.
Men för att återgå till ämnet. Det finns några saker ett HTML-dokument alltid måste innehålla. En dokumenttypsdeklaration (doctype), ett html-rot-element, ett huvud med en titel, och en kropp. Det minsta html-dokumentet vi kan konstruera som fortfarande uppfyller standarden (html5) är följande:
<!DOCTYPE html>
<html>
<head>
<title> Stora hundhemsidan </title>
</head>
<body>
</body>
</html>
Låt oss försöka visualisera samma dokument som ovan i en hierarkisk struktur av "lådor i lådor". En låda "i" en låda representerar alltså en barn-förälderrelation, medan en låda "bredvid" en annan låde representerar en syskonrelation.
Jämför ovan bilder av "boxar i boxar" med den faktiska HTML-koden i bilden högre upp. Försök förstå varför vi har ritat bilden på det sätt vi har ritat den. Notera att DOCTYPE
-deklarationen inte är med i ovan exempel.
När vi ändå är i farten med att försöka visualisera dokumenthierarkier. Låt oss även visualisera ovan som en indenterad lista.
DOCTYPE html head title [text] body [empty]
Detta med indentering leder oss även in på en meningsfull vana html-utvecklare respekterar.
Om en tag är ett barn till tag:en ovan, indentera ett steg.
Notera alltså hur title
är indenterad i relation till head
, men hur body
inte är indenterad i relation till head.
Återigen. Vi har inte bara slängt ihop ovan text lite hursomhelst. Utan det indenterade dokumentet är en representativ omskrivning av det tidigare diskuterade HTML-dokumentet. Återigen. Jämför denna indenterade version med den faktiska HTML-koden. Föräldra-barnrelationer defineras alltså nu genom indentering in. Syskonrelationer kan vi identifiera genom att hitta element som befinner sig på samma horisontella nivå under en och samma förälder.
Detta leder oss in på en viktig poäng som du kanske redan förstått. När vi öppnar ett element måste vi stänga det innan vi stänger dess förälder.
Alla barnelement behöver stängas innan vi stänger föräldern.
<article>
<p>
Hello world...
</article>
</p>
<article>
<p>
Hello world...
</p>
</article>
förhoppningsvis har du under läsningens gång märkt att vi i våra kodexempel indenterar koden. Det vill säga "drar in" vissa linjer lite till höger. Placerar några mellanslag före vissa rader. Detta gör vi för att öka läsbarheten av koden.
Indentering är kotym bland programmerare och möjligheten till indentering finns i nästan alla moderna språk. I vissa är det till och med obligatoriskt. Indentering kan i början kännas onödigt och krångligt. Men håll uppe glöden. Du kommer att tjäna på det i längden. De som läser din kod kommer tjäna på det. När du ber någon om hjälp kommer du tjäna på det. Indentering är en av de viktigaste kotymerna vi programmerare har.
Ok, så hur indenterar man då? Låt oss börja med några exempel.
<p>
<span>Detta är ok!</span>
</p>
<p>
<span>
Också ok!
</span>
</p>
<p><span>Också ok men svårläsligt!</span></p>
<p>
<span>Strunta _inte_ i indenteringen!</span>
</p>
<p>
Indentera barn...
</p>
<p>
...men inte syskon!
</p>
Ett enkelt sätt att veta när man ska indentera — alltså flytta en rad inåt, är följande minnesregel. Om vi öppnar ett element, ska allt som efterföljer indenteras, ända tills vi stängt elementet.
Alla barn till ett element ska indenteras ett "steg".
Öppningstaggar och stängningstaggar ska alltså vara indenterade in till samma nivå. Om vi indenterar korrekt kommer det vara busenkelt att snabbt identifiera vilka element som är barn till vilka element. Vilka element som är syskon. Vart ett element stängs. Och så vidare, och så vidare.
Att ta med sig ifrån det här stycket är alltså — indentera! Du kommer snabbt märka att de flesta programmerare är allergiska emot dålig indentering. Så skippa bara indenteringen om du vill skapa dig fiender :)
Ett validerande HTML-dokument måste alltså innehålla ett par saker. En dokumenttypsdeklaration, ett huvud, en titel och en kropp. Nyssnämnt direktäversättningar av de korrekta termerna DOCTYPE
, HEAD
, BODY
och TITLE
. Låt oss återgå till tidigare nämnt kod-exempel (se nedan), och fundera över hur dessa element ska nästlas i varandra. Med andra ord, låt oss diskutera vilka element som är barn/föräldrar till vilka.
<!DOCTYPE html>
<html>
<head>
<title>Page about kittenz!</title>
</head>
<body>
</body>
</html>
Notera även att det absolut yttersta elementet är <html>
. Ett HTML-dokument måste innehålla ett och endast ett <html>
-element. I detta element måste det finnas ett och endast ett <head>
- och respektive <body>
-element. I huvudet måste vi även ange en sidtitel med hjälp av <title>
. Detta är det minsta dokumentet vi kan skapa som validerar, och det är även så här alla html-dokument är strukturerade i botten.
Så vad lägger vi då inanför <head>
-taggarna? Det korta svaret är: meta-data.
Det längre svaret är att vi även laddar in externa referenser i huvudet. Följande kodruta är ett exempel på hur ett set av <head>
-taggar skulle kunna se ut.
...
<head>
<title> Hover cat </title>
<link rel="stylesheet" href="stylesheets/main.css">
<script src="javascripts/main.js">
<meta charset="utf-8">
<meta name="keywords" content="Kittens,Hovercrafts">
</head>
...
Låt oss diskutera ovan kod rad för rad.
Rad | Förklaring |
---|---|
3 | Den titel som visas högst upp i en sidans "tab" i en webbläsare. |
4 | Säger åt webbläsaren att ladda in en Stylesheet-fil, som finns på platsen definerad av href="sökväg-till-filen-här" . |
5 | Säger åt webbläsaren att ladda in en JavaScript-fil, som finns på platsen definerad av src="sökväg-till-filen-här" . |
6 | Berättar för webbläsaren vilken "character encoding" sidan är skriven i, så att tecken som åäö kan visas korrekt. |
7 | Definerar ett par keywords för sidan. Denna information används av bl.a. sökmotorer för att "förstå" sidans innehåll. |
Om <head>
beskrivs som sidans meta-content — alltså content om content. Då skulle vi kunna säga att <body>
är sidans faktiska content.
...
<body>
<h2> Sidans titel </h2>
<p> Det här är en paragraf med text. <p>
<p>
Och det här är en till, som innehåller en
<a href="http://uu.se">länk</a> till UU.
<p>
</body>
...
Låt oss nu bekanta oss med de vanligast förekommande elementen och relevanta attribut.
Låt oss börja genom att diskutera de två kanske vanligaste elementen. Paragrafer och rubriker. Paragrafer skapar vi genom att använda oss av elementet <p>
och element genom att använda någon av rubrikelementen.
Ett exempel på användande av paragrafer följer nedan.
<p>En paragraf denoterar alltså ett stycke text.</p>
<p>Varje ny paragraf börjar, om inte annat anges, på en ny rad.</p>
En paragraf denoterar alltså ett stycke text.
Varje ny paragraf börjar, om inte annat anges, på en ny rad.
De flesta dokument består ju inte bara av paragrafer utan även av rubriker. Vi skapar rubriker i HTML genom att använda oss av <hX>
, där X ersätts med en siffra ifrån 1-6
. Alltså:
<h1>En rubrik</h1>
<p>Följd av en paragraf.</p>
<h2>En underrubrik</h2>
<p>Ytterligare en paragraf.</p>
Och så kan vi fortsätta hela vägen ned till h6
. Rubriken h1
är alltså den viktigaste rubriken (den högsta nivån av rubriker) och h6
den minst viktiga. Resten följer förstås i inbördes ordning däremellan.
Om vi skulle exemplifera användandet av rubriker på den här sidan, löper vi en stor risk att skapa förvirring kring vad som faktiskt är rubriker och vad som är exempel på rubriker. Så för att se ett exempel på hur rubriker fungerar så råder vi dig att ta en snabb titt på ett exempel ifrån w3schools.
Låt oss nu diskutera de vanligaste textformatteringselementen. Fetstil och kursivitet. För att uppnå fetstilt text, kan vi välja att använda någon av elementen <b>
(bold) eller <strong>
.
All text <b>inom ett b-element</b>
eller ett <strong>strong-element</strong>
renderas i fetstil.
Om vi istället skulle vilja ha kursiv text, även kallad italics, så kan vi välja att använda något utav elementen <i>
(italics) eller <em>
(emphasis).
All text <i>inom ett i-element</i>
eller ett <em>em-element</em>
renderas som kursiv text.
Men varför finns det två sätt att denotera fetstil text och två sätt att denotera kursiv text? Du kommer lättare förstå skillnaden mellan de olika elementen när vi börjar diskutera semantisk signifikans. Men som kort svar så denoterar alltså t.ex. i
mer presentation snarare än semantik, och em
mer semantik snarare än presentation. Föreställ dig en blind person. Den visuella effekten av kursiv text är inte av signifikans för den blinde. Men idéen extra emfas är signifikant. Att det sedan råkar sig så att visualiseringen av extra emfas sker genom samma visuella effekt som kursivitet är alltså i någon bemärkelse ett sammanträffande.
För att skapa punktlistor i HTML behöver vi kombinera två olika element. Ett element som denoterar vilken typ av lista vi vill skapa. Innuti detta element behöver vi upprepat använda ett annat element — en gång per punkt i vår punktlista.
Vi nämnde alltså att det första elementet denoterar vilken typ av lista vi vill skapa. Det finns, i HTML, alltså två typer av listor — numrerade listor och onumrerade listor. Numrerade listor denoteras genom elementet <ol>
(ordered lists) och onumrerade listor genom elementet <ul>
(unordered lists).
Som nämnt behöver vi sedan denotera varje element (punkt) i listan för sig. Detta gör vi helt enkelt genom att använda elementet <li<>< code=""> (list item). Samma element används alltså oavsett om det skall användas i en numrerad eller onumrerad lista.><>
Låt oss se till ett exempel på en onumrerad lista.
<ul>
<li>Katt</li>
<li>Hund</li>
<li>Sköldpadda</li>
</ul>
Lagom intuitivt så är alltså skillnaden mellan en numrerad och en onumrerad lista att den numrerade listan använder nummer istället för symboler framför varje element i listan.
<ol>
<li>Katt </li>
<li>Hund</li>
<li>Sköldpadda</li>
</ol>
Självklart är vi inte bundna till att använda just dessa typer av symboler och/eller nummer framför varje listelement. Istället för att använda vanliga siffror skulle vi t.ex. kunna använda romerska siffror. Alltså I, II, III, IV
o.s.v. Detta kommer vi att exemplifiera när vi börjar tala om css.
I de tidigare dagarna av HTML använde kreativa webbutvecklare ofta tabeller för att strukturera upp hela webbsidor. Tabeller i HTML är alltså helt vanliga tabeller. Kolumner, rader, rubriker och inget mer. Men eftersom tabeller betedde sig på ett mycket förutsägbart sätt så upptäckte man att det var tacksamt att strukturera sina sidor med hjälp av tabeller.
Idag används tabeller nästan uteslutande för att representera tabulär data. Såsom elementet rimligen var tänkt att användas ifrån början.
När vi kommer in på diskussionen om semantisk signifikans så kommer du förhoppningsvis förstå varför det är både viktigt och naturligt att inte använda tabeller till annat än representation av tabulär data. Men återigen handlar det om distiktionen mellan hur saker ser ut och vad de faktiskt innebär. Användandet av tabeller implicierar att ett stycke data kan behandlas tabulärt. Så om vi använder tabeller för att visuellt strukturera vår sida — så implicerar vi att vår sida är en enda stor tabell av strukturerad data. Vilket oftast inte är sant.
När vi diskuterar tabeller så finns det egentligen fyra element som vi behöver lära oss. För att skapa en tabell börjar vi alltid med elementet <table>
. Detta element enkapsulerar hela tabellen. Alla dess rader, kolumner och data.
Innanför elementet table
kan vi sedan placera ett valfritt antal element av typen <tr>
(table row). Detta element skapar nya tabellrader. När vi skapar tabeller i HTML behöver vi alltså specificera kolumnerna i raderna och inte tvärtom. Syntaxen hade ju förstås likaväl kunnat fungera tvärtom men nu är fallet inte så.
I tabeller specificerar vi först raderna — sedan kolumnerna. Aldrig tvärtom.
Innanför elementet tr
kan vi sedan placera ett valfritt antal element av typen <td>
(table data. För att underlätta den mentala modellen kan du alltså tänka att elementet td
skapar kolumner. Om vi använder tr
för att skapa rader i tabellen så använder vi td
för att skapa kolumner i en rad.
Det sista elementet vi kan använda när vi arbetar med tabeller är >th<
(table header). Detta element kan ersätta vilket <td>
som helst. Vi använder alltså elementet för att denotera att en viss cell inte innehåller vanlig celldata. Utan snarare bör behandlas som en rubrik.
Låt oss se till ett komplett exempel.
<table>
<tr>
<th>Djur</th>
<th>Storlek</th>
</tr>
<tr>
<td>Golden Retriever</td>
<td>Stor</td>
</tr>
<tr>
<td>Norsk Skogskatt</td>
<td>Liten</td>
</tr>
</table>
Djur | Storlek |
---|---|
Golden Retriever | L |
Norks Skogskatt | S |
Var inte rädd för att använda tabeller! Men kom alltså ihåg att tabeller endast ska användas för data som är rimlig att presentera i tabeller.
Kommer snart...
Dags att bli visuella och diskutera hur vi får in bilder i våra HTML-dokument. Som vanligt när det kommer till HTML är det egentligen ganska enkelt. Genom att använda <img>
-taggen tillsammans med attributet src
kan vi infoga bilder i våra dokument. Låt oss se till ett exempel.
<img src="http://placekitten.com/g/60/60">
Prova gärna att klistra in adressen som bilden ovan pekar mot i webbläsaren och kolla vad som finns under adressen.
Notera alltså att att adressen ovan (som antytt) pekar mot en URL som renderar en bild. (Tjänsten placekitten erbjuder helt enkelt bilder i olika storlekar under alla sina URL:er.) Således kan vi alltså ersätta den adressen med en bild som finns lokalt på vår dator, på vår egen server eller någon annanstans på internet. Attributet src
förväntar sig helt enkelt en adress till en bild.
Men vad händer om en bild inte kan renderas? Och vad händer när en screen reader upptäcker en bild. In kommer alt
-attributet och räddar dagen! Låt oss se till ett exempel innan vi går vidare.
<img src="http://placekitten.com/g/60/60" alt="En mästerkatt utan stövlar">
Om vi nu försöker nå ovan bild genom någon form av läsare som inte kan rendera bilder så kommer vi istället få texten En mästerkatt utan stövlar. I annat fall kommer bilden att visas som vanligt och texten inte synas. Tänk på att attributet alt
krävs för att en <img>
-tagg ska vara valid.
Attributenalt
ochsrc
krävs båda för att en<img>
-tagg ska vara valid.
Om vi vill lägga till en bildtext till vår bild kommer de nya html5-elementen <figure>
och <figcaption>
väl till pass.
Dessa är semantiska attribut snarare än visuella. Med andra ord. Visst har <p>
-taggen en semantisk innebörd — en paragraf representerar ju semantiskt ett stycke text, och således rimligen en tanke. Men i HTML så resulterar ju även en paragraf i någonting visuellt skillt ifrån plain-text och därmed
Elementen figure
och figcaption
är båda semantiska element snarare än direkt visuella. Med andra ord, om vi skulle välja att bara skriva ut vår bild följd av ett helt vanligt paragraf-element som innehåller vår bildtext, så skulle det visuella resultatet bli ungefär likadant som om vi använde figure och figcaption. Poängen med att dock istället använda figure och figcaption är att vi berikar dokumentet med semantik. Plus att vi har ett enhetligt sätt att angripa bildtexter ifrån våra stilmallar (css).
<figure>
och<figcaption>
är båda element med semantisk mening.
<figure>
<img src="http://placekitten.com/g/130/130">
<figcaption>
En mästerkatt utan stövlar.
</figcaption>
</figure>
När vi pratar om semantisk signifikans så pratar vi om vad någonting (t.ex.) ett stycke text har för kontextuell betydelse. Lite som att läsa emellan raderna. Vi har i tidigare kapitel flera gånger nämnt termerna semantik och semantisk signifikans. Nu är det dags att faktiskt bena ut vad det handlar om.
Intågandet av den "levande standarden" HTML5 — kan anses som en stark drivkraft i diskussionen om semantik och webben. Med HTML5 deprekerade man ett antal element som bl.a. var allt för fokuserade på presentation. Istället introducerade man ett par intressanta och nyttiga element som fokuserade på semantik.
Men vad pratar vi egentligen om när vi pratar om semantik? Och vad menar vi egentligen när vi säger att de var för fokuserade på presentation? Är inte det just presentation som är poängen med HTML? Att presentera information för en användare.
Idéen om att separera presentation ifrån content handlar i essens att det går att skilja på presentation (representation) av content ifrån faktiskt contentet. Med andra ord — att representationen av information inte är detsamma som informationen i sig. Låt oss omformulera oss — att uttrycka en tanke är inte samma sak som essansen av tanken. Den talade tanken är inte samma sak som tanken. And down the rabbit hole it goes...
Vi sniffar nu lite i gränslandet till filosofi. Vad är kunskap? Är det skillnad på kunskap och kommunicerad kunskap? Om vi kan lagra kunskap som är separerad ifrån representation borde vi inte då kunna skapa maskiner som är lika smarta som vi? Vi närmar oss kunskapsrepresentation. Vi närmar oss artificiell intelligens.
Som du märker finns det mycket att gräva i. Och det är därför vi uttrycker oss i termer av "down the rabbit hole". Men utan att gräva ned oss allt för djupt åt något håll så finns det intressanta potentiella förmåner vi kan dra nytta av genom att inse att presentation och content inte är samma sak. Och sedan agera därefter.
Varje gång vi i HTML använder ett element så säger vi någonting om det content som vi väljer att placera i just det elementet. Ta t.ex. <em>
-elementet. Ett element som används för att denotera emfas. Emfas är inte bara någonting som är relaterat till presentation. Fundera över följande meningar.
Förhoppningsvis känner du, både ifrån verkliga livet och ifrån skolan, igen att emfas kan göra att samma mening betyder helt olika saker. Ovan tre meningar får tre helt olika innebörder beroende på hur vi väljer att intonera. Alltså vart vi väljer att lägga emfas.
Föreställ dig en blind person. Hur läser en blind person din webbsida? Rimligen med någon form av screen reader. En screen reader (t.ex.) är en maskin som läser källkoden för din webbsida och genom text-till-tal-syntes sedan läser upp relevant text på webbsidan.
När en screen-reader ska "läsa" våra webbsidor så skapar den egentligen en ny presentation. I termer av content och presentation. Screen-readern bryr sig egentligen bara om vårt content. Den vill identifiera vårt content, och sedan intelligent återrepresentera detta för användaren i ett format som är anpassat för denne.
Men vad är det screen readern behöver veta? Rimligen behöver den t.ex. veta vad som är meny-länkar så att den kan ge användaren en möjlighet att navigera ifrån den sidan den är på. Rimligen behöver den veta vilken del av sidan som är "huvud-content" (t.ex. en artikel) så att den inte börjar läsa upp annonserna för användaren. Rimligen behöver den veta vad artikeln har för rubrik så att den kan läsa upp den först, och sedan pausa i någon sekund så att användaren, lyssnaren, förstår att det är en rubrik. Rimligen behöver den veta vilka ord som ska läsas med extra emfas så att vi inte råkar ut för missförstånd såsom i det tidigare exemplet med punktlistan.
Detta är alltså varför det är viktigt att vi har element såsom t.ex. <em>
som gör att vi kan denotera emfas. Eller <nav>
för att kunna denotera meny-navigation. Detta är förstås inte ens en bråkdel av alla element med semantisk signifikans som existerar i HTML. Men vi återkommer till dessa strax.
Låt oss bara understryka att den som tror att idéen om att separera content ifrån presentation endast är en tillgänglighetsfråga (accessibility) — är naiv. Vi diskuterar den blinde användaren för att ha ett exempel att utgå ifrån men både problemet och fördelarna gömmer sig mycket djupare än så. Vi är på väg mot en webb där information är fri ifrån presentation. Där en multitud av maskiner kan läsa informationen på olika sätt. Allt ifrån TV-apparater till glasögon.
Kommer snart...
Kommer snart...
Kommer snart...
Poängen med detta kapitel är egentligen enkel. Vi vill plantera ett frö som förhoppningsvis gör att du börjar tänka på varför du väljer ett element över ett annat. Vi vill att du börjar tänka på att det finns en poäng med att hålla sin markup så "ren", minimalistisk och semantisk som möjligt. Vi vill att du börjar vara medveten om att alltid separera content ifrån presentation.
Det, för HTML, kanske mest representativa elementet måste ju vara länken. En klickbar yta på skärmen som navigerar oss ifrån en webbsida till en annan. Den tagg vi använder för att denotera en länk är <a>
. Ett komplett exempel på användandet av en länk skulle kunna se ut som följande.
<a href="http://www.example.com">Klicka på mig</a>
Vilket skulle rendera följande resultat:
Notera alltså användandet av attributet href
i ovan exempel. Attributets namn är en förkortning av "hyper reference". Det kan vara bra att fundera över vad de olika förkortningarna du kommer i kontakt med faktiskt éxpanderar till. Då blir det lättare att komma ihåg/på vad attributen faktiskt gör.
Attributet href
specificerar alltså en sökväg till en annan webbsida. Men vad kan ge attributet för olika typer av värden? När vi pratar om sökvägar är det viktigt att förstå att det finns två sätt att ange sökvägar.
Dessa begrepp är generella och stämmer precis lika bra överens med sökvägar i operativsystemet på din dator. För att förstå skillnaden mellan de två, låt oss använda operativsystemet som exempel.
Under Microsoft Windows så skulle ett exempel på en absolut sökväg t.ex. vara C:/Users/Jon Snow/Pictures/me.jpg
. Under samma operativsystem skulle ett exempel på en relativ sökväg kunna vara Pictures/me.jpg
. Den huvudsakliga skillanden emellan de två exemplena är alltså att den första börjar med C:/
. Förhoppningsvis är du familjär med att varje hårddisk (fysisk eller virtuell) under Windows blir tilldelad en enhetsbokstav, såsom C:, D:, E: o.s.v. Den absoluta sökvägen i ovan exempel utgick alltså ifrån en av dessa enheter. Den relativa däremot utgår inte ifrån någonting explicit. Istället utgår den ifrån den mapp "du är i just nu".
Det lättaste sättet att förstå sökvägar är att reflektera över hur vi själva navigerar igenom filsystemet i våra operativsystem. Tänk på det. När vi letar efter en fil på hårddisken så öppnar vi först någon mapp. Sedan finns det två saker vi kan göra:
Det är även dessa tre verktyg vi har att arbeta med när vi specificerar sökvägar. Varje ord motsvarar en mapp eller en fil. Varje slash-tecken (/
) motsvarar idéen om att klicka sig in i en ny mapp. Där ordet efter slash-tecknet denoterar namnet på mappen. Notationen punkt-punkt (..
) motsvarar idéen om att gå upp/bakåt en mapp i hierarkin.
Vi pratade tidigare lite om absoluta och relative sökvägar i relation till Microsoft Windows. Låt oss även prata om absoluta och relativa sökvägar i *nix-baserade system. I nästan alla operativsystem fungerar idéen om sökvägar på samma sätt. Punkt refererar relativt till den nuvarande mappen. Punkt-punkt till en mapp uppåt. Ett ord till en fil eller mapp i nuvarande mapp. Och slash används för att koppla ihop dessa. Varje slash denoterar alltså i någon bemärkelse "nästa steg".
I t.ex. Mac OS och Linux så fungerar alltså sökvägar på samma sätt som ovan nämnt, med en skillnad. För att specificera absoluta sökvägar i *nix-baserade system så anger vi inte namnet på en enhet (ex: C:
) såsom i Windows. Isället börjar vi helt enkelt med tecknet slash (/
). Följande är alltså en absolut sökväg:
/Users/jon-snow/pictures/me.jpg
Om vi i ett *nix-baserat system vill nå en annan enhet, på samma sätt som vi i Windows kan nå ex. D: så fungerar det ofta som så:
/Volumes/MyOtherDisk
För att sammanfatta absoluta och relativa sökvägar så vill vi understryka att det egentligen alltså är mycket enkelt. En relativ sökväg utgår ifrån den mapp där den som använder sökvägen befinner sig. En absolut sökväg utgår ifrån "roten" av den nuvarande enheten.
En relativ sökväg utgår ifrån nuvarande mapp. En absolut sökväg utgår ifrån någontings rot.
Förhoppningsvis blir du lite förvirrad av att ovan uttrycker sig i termer av "någontings rot". Det finns olika typer av absoluta sökvägar när vi börjar tala om nätverk, och det är dessa vi ska se närmare på strax.
För att kunna diskutera sökvägar på internet behöver vi skapa oss en förståelse för två saker: protokoll och URL:er. Låt oss diskutera de en för en.
Som du nu vet baseras webbsidor på standarden HTML. När vi med andra ord ber webbläsaren hämta en webbsida så anropar den en server som ger oss ett response som innehåller ett HTML-dokument. Vi har kort diskuterat request-response-modellen. Men vi har inte diskuterat de protokoll som möjliggör det. TCP, IP och HTTP bl.a. För att hålla oss fokuserade kommer vi inte att gräva i dessa. Men för att kunna intelligent angripa idéen om URL:er måste vi skapa oss en viss förståelse för protokollet HTTP.
Men först, vad är ett protokoll? Kom ihåg att vi sa att en klient skickar ett request och en server svarar med ett response. Ett protokoll är helt enkelt en överenskommelse kring hur klienten och servern ska tala med varandra. När vi skickar saker över nätet så skickar vi de i fragmenterade paket och för att routrar, ISP:er och servrar ska veta vart våra paket är på väg, behöver vi protokoll. Här kommer TCP/IP in i bilden. För att servern ska kunna förstå vårt request och för att webbläsaren ska kunna förstå serverns response behöver vi protokollet HTTP (HyperText Transfer Protocol). Protokoll kan alltså liknas vid ett överenskommet språk emellan två parter.
För att två parter ska kunna kommunicera krävs ett överenskommet språk — ett protokoll.
Du har säkert kommit i kontakt med både protokollet http
och den säkrare varianten https
. Varje gång du skriver in en adress i webbläsaren så anger vi http://
. Om vi inte gör det själva är webbläsarna idag tillräckligt smarta för att slänga in det protokollet åt oss.
Nu när vi vet vad ett protokoll är — vad är då en URL (Uniform Resource Locator)? En URL är helt enkelt en webbadress. Den pekar på en plats på internet där en webbresurs bör finnas.
En URL är en webbadress till en resurs på internet.
När vi skriver in en webbadress i vår webbläsare så skriver vi alltså in en URL. Som tidigare nämnt så hjälper de flesta moderna webbläsare oss att skriva korrekta URL:er. En URL måste nämligen bl.a. innehålla ett protokoll. Var sig det är HTTP, HTTPS, FTP, SFTP o.s.v.
Låt oss nu prata om sökvägar/webbadresser i HTML. Som tidigare nämnt så hanterar webbläsaren endast adresser i URL-format. Oavsett om det är en adress som pekar internt inom samma sida eller externt ut till en annan sida så behöver de vara i URL-format.
En webbläsare behöver alltså ha en absolut sökväg i URL-formatet. Men när vi specificerar hyperlänkar i vår HTML. T.ex. genom att använda <a>
-taggen, så behöver vi faktiskt inte alltid ange kompletta URL:er enligt URL-formatet. De fall då vi inte behöver göra det är alltså när vi refererar till resurser inom vår egen sida (domän). Om vi däremot vill referera till en resurs utanför vår domän så behöver vi ange en komplett adress i URL-formatet.
I HTML behöver vi inte ange protokoll när vi refererar till en URL inom samma domän.
När vi refererar till en resurs inom vår egen domän så använder vi ett format som närmast liknar det *nix-system använder. Förnim dig det vi tidigare diskuterat! Alltså slash för att denotera nästa mapp, punkt-punkt för att vandra en mapp upp i hierarkin, och en initial slash för att denotera roten.
Detta blir antagligen enklare genom att diskutera ett par exempel. Anta att vi befinner oss på följande sida: http://example.com/pages/links.html
. Nedan följer ett par exempel på hur webbläsaren kommer att översätta våra adresser, om vi specificerar de inom (t.ex.) en <a>
-tagg.
Hyperlänk | Typ | Webbläsarens tolkning... |
---|---|---|
images.html | Relativ | http://example.com/pages/images.html |
../images.html | Relativ | http://example.com/images.html |
/images.html | Absolut | http://example.com/images.html |
/images/album.html | Absolut | http://example.com/images/album.html |
Ovan gäller alltså om vi antar att användaren befinner sig på sidan http://example.com/pages/links.html |
Om vi inte anger ett protokoll kommer webbläsaren att tolka vår URL som intern! Även om vi börjar URL:en med www.
Notera alltså att om vi inte anger ett protokoll så kommer webbläsaren att tolka våra URL:er som interna till vår domän. Ett vanligt misstag är således att glömma att ange protokollet när vi försöker ange en extern adress. Anta att vibefinner oss på http://example.com
och skriver följande:
<a href="www.google.com">Klicka här</a>
Vi antar att länken ska ta användaren till google.com. Fallet är dock inte så. Eftersom vi inte angett protokoll tolkar webbläsaren adressen som intern. Webbläsaren översätter således adressen till följande:
http://example.com/www.google.com
Inte riktigt vad vi menade förstås. Det korrekta sättet att skapa ovan URL är alltså genom att även ange protokollet.
<a href="http://www.google.com">Klicka här</a>
En typ av länkar vi ännu inte pratat om är ankare. Ankare är ett sätt att länka till en specifik del av en sida. Föreställ dig en lång sida. Alltså en sida med mycket content där du kan scrolla långt. Ankare hjälper dig då att skapa länkar inom samma sida.
Ett ankare kan appliceras i slutet av vilken URL som helst.
Ett ankare börjar med fyrkants-tecknet (hashtag) (#
) och sedan vilken sträng som helst.
#my_anchor
Vi applicerar alltså ett ankare i slutet av en vanlig URL.
http://example.com/index.html#my_anchor
Vi kan alltså använda oss av ankare för att ge användaren en möjlighet att navigera inom samma sida. När vi klickar på en länk med ett ankare så kommer alltså webbläsaren inte bara att ladda den sida vi angett — utan även scrolla ned till ankarets målposition. Vi kan med andra ord se ankare som en form av "bokmärken" för långa sidor.
Ankaren har även fler, mer avancerade tillämpningsområdet men det kommer vi in på mycket senare.
Eftersom ankare är en del av URL:er så är det så att vi inte bara kan använda ankare när vi vill ge användaren en möjlighet att navigera inom samma sida. Vi kan även använda oss av dem när vi vill skicka användaren till ett visst ankare på en annan sida.
Låt oss exemplifiera för att göra det tydligare. Ponera att vi har en HTML-sida med följande länk i sig.
<a href="#images">Bildgalleriet</a>
Ovan länk kommer alltså inte att byta sida. Ovan länk är en relativ URL som i browsern kommer att översättas till samma URL som vi är vid, fast med ankaret "#images" pålagt i slutet.
Ponera om vi istället hade skapat en länk som pekade på en full URL med ett ankare i slutet, såsom nedan...
<a href="http://example.com/index.html#images">Bildgalleriet</a>
Om vi hade specificerat en URL såsom ovan, hade vi skickat användaren till index.html under domänen example.com. Oavsett vilken sida vi råkade vara på vid tillfället. Det viktiga att förstå är dock att ankaret kommer att fungera i vilket fall. När webbläsaren har nått den sida vi skickat användaren till så kommer den automatiskt att scrolla ned till den plats där ankaret är specificerat.
Det finns en sak vi ännu inte pratat om vad gäller ankare. Nu vet vi hur man skapar en länk till ett ankare. Men vi har inte pratat om hur man skapar ett ankare som man kan skickas till på en sida. Tidigare så använde vi exemplet #my_anchor
, men vart hamnar användaren när den klickar på ankaret? Vart är "målet" för detta ankare specificerat?
Egentligen är det ganska enkelt. En länk till ett ankare specificeras där vi kan ange en URL, och ett mål för ett ankare specificeras i vilket element som helst, under attributet ID.
Ankare pekar på egenskapen id
i element.
Låt oss se till ett exempel.
<!-- Om vi har någonting med ett ID -->
<h1 id="images">Bildgalleriet</h1>
<!-- Så kan vi sedan länka till det som ett ankare -->
<a href="#images">Gå till bildgalleriet</a>
Med andra ord så kan vi skapa "bokmärken" på våra sidor genom att ge olika element ID:n. När vi sedan vill att en användare snabbt ska kunna navigera till en viss del av sidan (ett "bokmärke") så skapar vi en länk som pekar på just det ID:t, som ett ankare.
Detta är bara en av många funktioner som ID:n fyller, men det återkommer vi till senare.
För att användare ska kunna interagera med våra webbsidor har vi flera tillgänglia faciliteter. Vi har pratat om länkar som ger en användare möjligheten att navigera emellan sidor. Vi har pratat om ankare som ger användare möjligheten att navigera inom en och samma sida. Men om användaren vill ge oss data? Om vi t.ex. vill fråga användaren om dennes namn? In kommer formulär och räddar dagen.
Låt oss, innan vi går vidare se till ett exempel för hur ett formulär skulle kunna se ut.
Vanliga scenarion där vi använder formulär är t.ex. användarregistrering, inloggning, kontaktformulär, undersökningar, chat, forum, kommentarsfält, sökfält, etc. Listan är lång. Tänk på sidor som du vanligen brukar besöka. Varje gång du skriver in någon form av fritext på sidan, kryssar i en checkbox, radioknapp eller dyl. så interagerar du nästan alltid med ett formulär. Tänk
Viktigt att förstå är att vi med endast HTML inte kan göra särskilt mycket med formulär. Vi kan presentera de för användaren, men vi kan inte på några sätt "processa" den data som användaren matar in. För att bearbeta den data som användarna matar in i formulär behöver vi ett script- eller programmeringspråk. Med andra ord t.ex. JavaScript
, PHP
, Ruby
, ASP.NET
, eller dyl. För att persistent spara data som kan delas emellan flera datorer behöver vi någon form av databas. Om vi vill spara data på klientens dator och det inte spelar någon roll ifall andra kan komma åt datan eller ej — så räcker det med JavaScript och HTML5 (genom persistant storage). Men det viktiga är alltså att förstå att vi med hjälp av endast HTML inte kan spara eller processa datan användare matar in i våra formulär. Men vi kommer prata mer om att processa/spara data ifrån formulär när vi pratar om script- och programmeringsspråk.
Låt oss skapa formulär. Vi tar det del för del och steg för steg.
Vi börjar helt enkelt med att använda <form>
-element. På samma sätt som table
enkapsulerar allt innehåll av en tabell — så enkapsulerar form
allt innehåll av ett formulär. Vi skapar alltså "skalet" för ett formulär som så...
<form method="POST" action="process-data.php">
...
</form>
Ovan kod resulterar rent visuellt inte i någonting alls. På den renderade sidan syns inget formulär. Däremot kommer sidan förstås onekligen innehålla markupen för formuläret. Men eftersom vi ännu inte placerat några formulärkomponenter i vårt formulär så syns ju ingenting.
Låt oss börja med att kika på hur vi ger användaren möjlighet att mata in fritext. Vi har två alternativ att välja emellan.
<textarea>
<input type="text">
Den förstnämnda använder vi när vi vill ge användaren att skriva en längre text. Såsom t.ex. en kommentar eller ett blogginlägg. När vi däremot bara söker kortare information ifrån användaren, såsom t.ex. ett namn eller en adress så passar det andra alternativet ypperligt.
Låt oss börja med att kika på ett exempel på användande av input
. Elementet används för att denotera en mängd olika formkontroller. Allt ifrån checkboxar och radioknappar till fritext och datum. För att webbläsaren ska veta vilken typ av inputkontroll vi vill skapa behöver vi ge ett värde för type
-attributet. Det enklaste alternativet är förstås fritext, vilket vi alltså denoterar genom att sätta type="text"
.
Så om vi med andra ord skriver...
<input type="text" placeholder="Vänligen skriv förnamn här...">
Så renderar webbläsaren följande resultat...
Som du kanske märkte är alltså placeholder
ett annat attribut vi kan använda på input
-element. Detta attribut anger vi för att helt enkelt specificiera en platshållare för kontrollen. Undersök hur detta fungerar genom att skriva någonting i exempelfältet ovan. En platshållartext är helt enkelt en text som visas när kontrollen är "tom". Det vill säga både innan användaren har skrivit någonting i textfältet, men även så fort som användaren rensar nuvarande text i fältet. Notera att detta attribut inte stöds av alla äldre webbläsare.
input
-elementet endast består av en tagg. Vi behöver alltså inte (och bör inte) ange en stängningstagg. I Tidigare versioner av standarden för HTML var det möjligt att ange en sluttagg. Således kommer detta inte att orsaka något fel i de flesta webbläsare. Men om vi följer standarden HTML5 så finns det ingen anledning att ange en stängningstagg.Om vi behöver ge användaren en möjlighet att skriva mer än endast en rad text så passar elementet textarea
utmärkt. Detta element består, till skillnad ifrån input
, utav ett taggpar. Med andra ord en öppningstagg och en stängningstagg. Allt däremellan är text som kommer att visas i textfältet. Låt oss se till ett exempel.
<textarea placeholder="Textarea stödjer placeholders">
Denna text renderas i textarean
</textarea>
Platshållare (i.e. placeholders) kan onekligen användas för att ge användaren ett hum om vad som ska skrivas i vilket fält. Men i många fall behöver vi mer än bara platshållare.
Till exempel så stödjer inte alla webbläsare placeholders. Vilket skulle rendera fälten helt tomma. Och användaren skulle inte ha en aning om vad som ska skrivas vad. Ett annat problem är förstås att om det redan finns någonting skrivet i fältet (t.ex. eftersom användaren har skrivit det men sen glömt, eller för att webbläsaren har sparat texten). Detta skulle ju alltså göra att platshållartexten inte visas. Eftersom platshållartexten ju endast visas när det faktiskt inte finns en text i formkontrollen.
Istället för att bara använda placeholders så kan vi alltså även använda elementet label
.
Det som huvudsak skiljer labels ifrån helt vanlig text är att vi kan associera en label med en formkontroll. Detta betyder att webbläsaren t.ex. kan göra så att när användaren klickar på etiketten (label) så hamnar respektive formkontroll i "fokus". Detta betyder även förstås att robotar (t.ex. sökspindlar) lättare kan läsa av vad en viss formkontroll är till för, eftersom de kan läsa etiketten.
Så låt oss associera en etikett med den fritextkontroll vi såg i tidigare exempel. Kom ihåg att prova klicka på etiketten.
<label for="firstname">Förnamn</label>
<input type="text" placeholder="Vänligen skriv förnamn här..." id="firstname">
Radioknappar ger oss möjligheten att låta användaren välja ett och endast ett alternativ, givet flera. Låt oss se till ett exempel. Kom ihåg hur vi (som tidigare diskuterat) använder attributet for
för att associera en ettikett med en formkontroll.
<label for="alt-yes">Ja</label>
<input type="radio" id="alt-yes" name="yes-or-no" value="yes">
<label for="alt-no">Nej</label>
<input type="radio" id="alt-no" name="yes-or-no" value="no">
Prova knapparna! Både genom att klicka på själva radioknapparna, men även genom att klicka på dess etiketter.
Fundera på vad de olika attributen i ovan exempel faktiskt gör. Vi har inte pratat om attributet name
tidigare. Attributet går att använda på alla formkontroller och är ett sätt att tilldela ett namn på en viss kontroll. Detta namn är av relevans när det kommer till att processa data i formuläret. Vi kommer som sagt inte riktigt att prata om detta än men du bör ändock veta om att det är vad attributet är till för.
De två radioknapparna i ovan exempel har ju alltså samma värde för attributet name
. Detta innebär att de tillhör samma grupp. Vi sa ju tidigare att radioknappar gör att användaren kan välja ett och endast ett val. Men om vi har flera set av radioknappar på en och samma sida — hur ska då webbläsaren veta vilka radioknappar som inte får vara valda samtidigt. Jo, genom grupper. Så vi kan alltså omformulera oss som så: En och endast en radioknapp får vara vald per grupp. Och en grupp definierar vi ju alltså genom att ge flera radioknappar samma värde för attributet name
.
Låt oss se till ett exempel.
<label>Ja eller nej?</label>
<label for="4307904643-alt-yes">Ja</label>
<input type="radio" name="yes-or-no" id="4307904643-alt-yes">
<label for="4307904643-alt-no">Nej</label>
<input type="radio" name="yes-or-no" id="4307904643-alt-no">
<label>Vilken frukt?</label>
<label for="4307904643-alt-apple">Äpple</label>
<input type="radio" name="fruit" id="4307904643-alt-apple">
<label for="4307904643-alt-banana">Banan</label>
<input type="radio" name="fruit" id="4307904643-alt-banana">
Checkboxes använder vi till skillnad ifrån radio buttons när vi vill ge användaren möjligheten att välja ett, eller flera alternativ av många. Mycket användbart när det kommer till frukt! I övrigt fungerar de i stort sett som radioknappar.
<input type="checkbox" name="fruit" value="apple" id="alt-apple">
<label for="alt-apple">Apple</label>
<input type="checkbox" name="fruit" value="banana" id="alt-banana">
<label for="alt-banana">Banan</label>
<input type="checkbox" name="fruit" value="grapes" id="alt-grapes">
<label for="alt-grapes">Vindruvor</label>
Attributet value
specificerar alltså vilket värde som kommer att associeras med respektive nyckel (alltså checkboxgruppen "fruit"). Vi återkommer till detta när vi pratar om vad som händer när man skickar ett formulär.
Ibland har vi så många alternativ vi vill erbjuda en användare, att det skulle bli absurt att försöka presentera alla som t.ex. radioknappar. Ett vanligt use-case är t.ex. att välja land. Det finns så många länder i världen att sidan skulle bli jättelång om vi skulle presentera alla. In kommer <select>
-listor och räddar dagen.
<select name="fruit">
<option value="banana">Banan</option>
<option value="apple">Äpple</option>
<option value="grapes">Vindruvor</option>
<option value="orange">Apelsin</option>
</select>
Återigen så använder vi alltså attributet value
för att denotera vad som kommer att skickas om användaren har valt just det valet (option
).
I vanliga select
-listor kan användaren bara välja ett alternativ i listan. Men om vi istället sätter egenskapen multiple
så tillåter listan användaren att välja flera alternativ. Beroende på vilken webbläsare användaren befinner sig i så fungerar detta på lite olika sätt. Men oftast fungerar det att (1) klicka och dra, (2) hålla in ctrl
(Windows) eller cmd
(Mac) och välja en i taget, eller (3) hålla in shift för att välja två stycken och alla däremellan. Du känner rimligen igen dessa konventioner ifrån när vi markerar filer i operativsystemet. Nästan oavsett operativsystem.
Att implementationen av denna select
-kontroll skiljer sig ifrån webbläsare till webbläsare understryker en viktig poäng. Webbläsare väljer själva hur de ska implementera standarden. Detta betyder att vissa element (du kommer t.ex. att märka detta när vi pratar om elementet <video>
) renderas på helt olika sätt i olika webbläsare. Detta av naturliga skäl. En webbläsare för mobiltelefoner måste rimligen hantera en (t.ex.) select
-lista annorlunda. Det senare eftersom mobilanvändare ju interagerar med (t.ex.) touch, och inte mus + tangentbord.
<select name="fruit" multiple>
...
</select>
Så när vi har komponterat ihop det formulär vi vill ha så måste vi ju ge användaren en möjlighet att skicka iväg formuläret. För det behöver vi en submit-knapp. Vi använder då åter elementet <input>
, men sätter attributet type
till submit
.
Easy as pancakes! Låt oss se till ett exempel.
<input type="submit" value="Skicka!">
Men vad händer egentligen när användaren skickar ett formulär? Vart skickas datan? Hur kan vi hantera den? Försök förnimma dig om att vi tidigare i detta kapitel talade om att HTML i sig inte är tillräckligt för att processa formulär. Det är fortfarande sant.
När användaren klickar på en submit-knapp i ett formulär så skickas det ifyllda datat med i nästa request. Vad betyder det? Ett request är ju alltså när vår webbläsare ber om en ny sida. Vår webbläsare skickar ett HTTP request som tas emot av en server, som i sin tur svarar med ett response. Ett response som slutligen renderas av vår webbläsareKnappen leder oss till en ny sida som defineras genom attributet action
.
Så, när användaren klickar på en submit-knapp i ett formulär så skickas det ifyllda datat med i nästa request. Det betyder att när requestet kommer till servern som ska hantera request:et och svara med ett response, så har servern tillgång till den data användaren fyllde i formuläret.
Men vart skickas datat mer specifikt? Jo, det request som kommer göras är alltså det som finns specificerat i action
-attributet i formulärets form
-tagg. Med andra ord är submit-knappar egentligen som en helt vanlig länk. När vi klickar på knappen så skickas vi dit där attributet action
pekar. När vi klickar på en vanlig länk så skickas vi dit där attributet href
pekar. Men den huvudsakliga skillnaden mellan en vanlig länk och en submit-knapp för ett formulär är alltså att datat i formuläret kodas och skickas med i request:et.
Action-attributet används alltså som så:
<form action="put/target/url/here"> </form>
När användaren klickar på en submit-knapp i ett formulär så skickas alltså datat med i ett request som görs emot den url som specificeras i action-attributet. Ok, men hur skickas datat med? För att förstå det måste vi förstå att det finns två olika metoder att skicka formulärdata över HTTP.
Anta att vi skapar ett formulär på sidan http://example.com/login.php
. Anta att formulärets action pekar på http://example.com/process_login.php
. Om vi använder oss av HTTP-metoden GET
så skulle det request som konstrueras se ut något sånt här:
http://example.com/process_login.php?username=snow&password=supersecret&remember=1
Formulärdatat skickas alltså direkt i URL:en. Allting efter frågetecknet (?
) är en urlkodad sträng av formulärdatat. Datat är kodat enligt principen nyckel/värde. Om vi analyserar strängen lite närmare upptäcker vi att den följer följande konvention.
key=value
Där key
ersätts med det värde vi gett name
-attribuet för respektive formkontroll. Ordet value
ersätts i sin tur av det faktiska värdet för den formkontrollen.
Sedan märker vi även att varje nyckel-värde-par avdelas med hjälp av ett och-tecken (&
). Som så...
key1=value1&key2=value2&key3=value3...
GET
. Du kommer att få förklaringar till varför när vi pratar om den andra metoden — POST
.Både nycklarna såväl som värdena kodas med en teknik som kallas urlencoding. Detta innebär icke-numeriska och icke-alfabetiska tecken ersätts med någon form av kod. Varför? Jo, för att undvika parsing-problem. Eftersom värdena skickas konkatenerade i en enda lång sträng, så betyder det att den som ska använda värdena måste parse:a dem. Att parse:a ett givet stycke text betyder innebär i stort sett att "tolka" det givna stycket enligt ett givet set "grammatiska" regler.
För att göra behovet av urlencoding mer uppenbart. Föreställ dig ett formulär med ett fritextfält. Föreställ dig sedan att användaren fyller i någon text med ett och-tecken (&
) i mitten. Eftersom och-tecknet används som avdelare emellan set av nyckel-värde-par, så kommer parser:n (alltså det program/maskin som parse:ar strängen) bli tokförvirrad. Så fort parser:n stöter på ett och-tecken kommer den att avsluta nuvarande nyckel-värde-par och anta att nästa börjar.
Urlencoding är alltså anledning till att mellanslag ersätts med %20
i url:er. Detta vilket du kanske redan stött på.
Vi nämnde tidigare att det finns två sätt att skicka formulärdata över HTTP. POST
och GET
. Vi har även kort nämnt att de olika metoderna är olika passande för olika situationer. Detta reflekteras även av deras namn. Tänk på det. Post och get. Förstnämnda är designad för att posta data. Sistnämnda är designad för att hämta data. Distinktionen mellan dessa är förstås varken solkar eller svartvit. Men om vi ser det som en tumregel kan det bli lättare att välja vilken teknik (POST eller GET) vi ska använda.
POST
bör vi alltså huvudsakligen använda när vi på något sätt vill skicka data till servern. Föreställ dig till exempel ett registreringsformulär. Vi vill skicka data till servern. Vi vill säga "det här är mina uppgifter, vänligen skapa ett konto åt mig". Eller föreställ dig ett kommentarsfält. Vad säger vi till servern? "Här har du min kommentar! Vänligen posta den."
Den mer formella tumregeln är att requests som riskerar att ha sidoeffekter bör utföras över POST. Vi kommer att prata mer om vad sidoeffekter när vi pratar om programmering. Men med requests som riskerar sidoeffekter, så menar vi här requests som riskerar att förändra state på servern. T.ex. förändra något i en databas. Därav t.ex. registrering.
GET
å andra sidan bör vi alltså huvudsakligen använda när vi på något sätt vill hämta data. Exempel på detta kan t.ex. vara paginering. Med paginering menar vi alltså när en stor mängd content delas upp på flera sidor. Det är då vanligt förekommande att man använder sig av GET
-tekniken för i request:et skicka med vilket sidnummer vi vill se.
Ett annat lämpligt scenario för GET är t.ex. sökningar och filtrering. Föreställ dig en webbshop med en sökruta. När vi söker efter en produkt så förändrar vi ju självklart inte några produkter på webbshop:ens server. Vi använder då GET för att berätta för servern vilken/vilka term/-er vi har sökt — så att servern kan svara med rätt response.
Den kanske mest uppenbara skillnaden emellan GET
och POST
är förstås hur formulärdatan skickas. Med förstnämnda tekniken skickas formulärdatat i URL:en själv. Således är formulärdatat uppenbart synligt för användaren. Genom att däremot använda sistnämnda tekniken (POST) så skickas formulärdatan som en HTTP-header. Således är datan inte synlig i URL och inte uppenbart synlig för användaren.
Notera att vi uttrycker oss i termer av uppenbart synlig. Detta är medvetet. Även HTTP headers går att analysera. Så bara för att vi skickar data genom POST betyder det inte att vi är helt säkra. Även POST-data går att analysera. Dock krävs det då en lite mer tekniskt händig användare.
Metod | Skickas.. | Användningsområde |
---|---|---|
GET | ..i slutet av URL:en | När vi vill använda formulärdatat för att hämta någonting. |
POST | ..som en HTTP-header | När vi vill använda formulärdatat för att förändra någonting på servern. |
Låt oss, innan vi snurrar ihop detta kapitels säck, kolla in ett komplett formulärexempel.
<form action="#" method="GET">
<label for="field-name">Ditt namn</label>
<input type="text" id="field-name" name="name" placeholder="Ditt namn" required>
<label>Vad vill du äta till frukost?</label>
<label for="field-pancakes">Pannkakor</label>
<input type="radio" id="field-pancakes" name="breakfast" value="pancakes">
<label for="field-scrambled">Äggröra</label>
<input type="radio" id="field-scrambled" name="breakfast" value="scrambled">
<label for="field-toast">Övrigt</label>
<input type="radio" id="field-toast" name="breakfast" value="toast">
<input type="submit" value="Skicka!">
</form>
Prova att skicka formuläret och notera dels vad attributet required
orsakar, samt vad som händer i webbläsarens adressfält.
HTML har funnits i fler än ett par år, och med de åren på nacken kommer en mer än brokig historia. Det har länge funnits fler än en enda browser. Och eftersom de flesta browsers (självklarligen) vill äga majoriteten av marknaden har gett upphov till uttrycket The Browser Wars. Webbläsare slåss om att vinna den stora majoriteten av användare.
Detta har (bland andra anledningar) gett upphov till att webbläsare i olika skeden implementerat olika delar av HTML-, CSS- och JavaScript-specifikationerna. Med andra ord, att olika "dialekter" av samma språk varit tillåtet i olika webbläsare. Med andra ord att vi kan skriva vissa saker i vissa webbläsare och andra i andra, vilket gör att webbläsare A kanske inte förstår uttryck B, som webbläsare C förstår.
För att försöka vara i framkant har webbläsare implementerat experimentell funktionalitet. Sedan har webbutvecklare börjat använda dessa funktioner, och börjat förlita sig på existensen av dessa funktioner. Vilket har lett till problem när dessa webbsidor sedan läses i webbläsare som inte implementerat dessa funktioner. Kort sagt, så är det en soppa.
Om du ska ta med dig en poäng ifrån det här kapitlet så bör det vara denna. Anta aldrig att alla webbläsare stödjer den funktionalitet du vill använda. För att ta reda på vilka webbläsare som stödjer en viss funktionalitet du vill använda brukar en enkel webbsökning på funktionaliteten plus termen "browser support" göra jobbet. Om du vill undersöka någonting som är relaterat till HTML5- eller CSS3-standarden så är även caniuse.com en mycket bra resurs.
Webbläsarkrigen har (bland andra anledningar) gjort att det är viktigt att deklarera vilken HTML-standard våra dokument följer. Detta så att webbläsaren, "intelligent" (utan att gissa) kan parse:a (tolka) vår fil enligt rätt specifikation.
Om du har svårt att förstå varför vi behöver dessa standarder, stanna upp och fundera över faktumet att html-dokument egentligen bara är text. Ingenting annat. Precis som med naturligt språk, så måste mottagaren av meddelandet förstå hur den ska avkoda informationen. Vi kan alltså se doctype-deklarationen lite som att vi berättar för webbläsaren som ska läsa filen vilket dialekt av HTML vi pratar. Eller kanske snarare vilken grammatik vi använder i vårt dokument. Tänk tillbaka till filtypskapitlet.
Eftersom det finns olika HTML-standarder så finns det alltså olika sätt att uttrycka HTML. Således betyder samma sak olika saker beroende på vilken standard vi använder när vi läser dokumentet.
FÖr att berätta för webbläsaren vilken standard dokumentet bör läsas med behöver vi på den absolut första raden i dokumentet ange en doctype. Vi definerar doctypes med syntaxen <!DOCTYPE x>
. Där x
ersätts med den faktiska doctype:en. Nedan följer ett par exempel för hur man deklarerar doctypes.
För att deklarera att ett dokument följer standarden HTML5 skriver vi helt enkelt följande...
<!DOCTYPE html>
Och i kontexten av ett HTML-dokument skulle det se ut som följande...
<!DOCTYPE html>
<html>
<head>
<title>Min sida</title>
...
</head>
<body>
...
</body>
</html>
Om du inte har en medveten anledning till att använda någonting annat än HTML5, så hävdar vi att det inte finns någon anledning att göra det. Med andra ord, så skulle vi uppmana dig till att antingen hålla dig till HTML5 eller läsa på mer om doctypes om du vill använda någon annan.
Håll dig till HTML5, alltså <DOCTYPE html>
, så länge du inte har en annan medveten anledning.
Men det finns ju onekligen ett antal äldre doctypes som motsvarar äldre standarder. Låt oss snabbt diskutera igenom några stycken för att skapa oss en uppfattning om varför de har funnits och vad de har gjort.
HTML 4.01 Strict
tillåter alla HTML 4.01-element och -attribut, men tillåter inte de som deprekerats ("deprecated") i HTML 4.01-standarden. Närmare bestämt, element och attribut som rör visuell presentation snarare än struktur och content, såsom elementet <font>
eller attributet bgcolor="#000000"
.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
Även HTML 4.01 Transitional
tillåter alla element och attribut som är tillåtna i HTML 4.01, inklusive de som rör presentation (se förklaring i ovan paragraf).
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
Det finns som sagt markant fler doctypes än dessa. Men vi rekommenderar alltså att du helt enkelt håller dig till HTML5
. Genom att således deklarera dokumenttypen <!DOCTYPE html>
. Vill du läsa mer om doctypes kan du t.ex. göra det hos W3 Schools eller på Wikipedia.
Kanske har du redan märkt att när man skapar html-dokument som innehåller tecken såsom å
, ä
, ö
kan det hända att de ersätts med en uppsjö a mystiska tecken. Lösningen på detta är character sets
.
För att göra en lång historia kort så var det alltså så att man i datorernas tidiga dagar representerade tecken som ASCII-koder. T.ex. så representerades A
genom 65
och a
genom 97
.
ASCII-tabellen använde sig av 7 bitar, vilket resulterade i 128 olika tecken (eftersom 2^7=128
), varvid vissa var "unprintable" kontroll-tecken. Som du säkert kan tänka dig upptäckte man snabbt att detta var fullt otillräckligt för att lagra all världens olika tecken. Vi har ju förstås inte bara åäö att arbeta med, utan även kinesiska, grekiska, arabiska o.s.v.
There is no such thing as plain text
Ovan citat understryker faktumet att datorer egentligen är väldigt korkade och bara gör det vi säger åt dem. Utan att berätta för en dokumentläsare på vilket sätt vi sparat ett visst tecken så har den ingen chans att veta vad det är för tecken, hur det ska visas, eller ens vart tecknet slutar.
In kommer utf-8 och räddar dagen! Detta är kort sagt är ett sätt att representera Unicode-tecken. Och kort sagt möjliggör användning av världens alla tecken. Och som kort sagt blivit den vanligaste teckenkodningen för webbsidor.
Ett dokuments teckenkodning sätter vi genom att skapa en <meta>
-tag med attributet charset
satt till valfritt charset (såsom just utf-8). Eftersom detta tillhör meta-information om dokumentet skall vi placera taggen under <head>
. Vidare bör vi även specificera vårt charset
så tidigt som möjligt i <head>
eftersom webbläsaren behöver veta vilken teckenkodning vi använt för att ordentligt kunna läsa det dokument den redan läser.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>The utf-8, I speak!</title>
</head>
</html>
Om HTML beskriver en webbsidas innehåll så beskriver CSS hur detta innehåll ser ut. Om webben var en teaterpjäs skulle HTML vara manuset och CSS regissören. "Du ska stå där, du där och du där! Vänta nu, du behöver ha en grön hatt, och du behöver ha en mask och en cape!"
CSS — (Cascading StyleSheets) är stilmallar i praktiken används till att formge dokument. Formge färg, teckensnitt, positionering, justering, backgrunder, scroll, o.s.v. En enda CSS-mall kan styra tusentals dokument och det är då enkelt att ändra formateringen genom att det bara i CSS-mallen.
CSS har tagit HTML ett steg längre och möjliggjort formateringar och effekter som inte fanns i HTML standarden. En av fördelarna med CSS är att flera mallar kan användas och de har då företräde inbördes så att en "huvudmall" med de övergripande formateringarna kan ersättas på en lägre nivå av en "lokal mall" som då gäller före huvudmallen. Det är detta som åsyftas när man säger att css är cascading
.
Med css kan vi separera innehåll och presentation.
CSS är ett initiativ till att separera innehåll och presentation. Att definiera allt relaterat till presentation i en extern mall har många fördelar. Bland annat att:
En målsättning och effekt av att presentation separeras ifrån innehåll -- är adaptivitet. Om innehållet är helt "befriat ifrån" presentation så skulle man i teorin kunna visa innehållet med vilken typ av presentation som som helst.
För att göra det mer uppenbart, låt oss fundera över hur situationen såg ut tidigare. Om vi beblandar presentation (css) med vårt innehåll (html) så kommer det vara svårt för en maskin att avgöra vad som hör till presentationen och vad som hör till innehållet. Tänk t.ex. på radbrytningar. Används en radbrytning för att understryka att två paragrafer är skilda ifrån varandra, eller används den för att skapa ett bekvämt visuellt avstånd? Både fallen kan vara sanna. Detta är svårt för en maskin att avgöra.
I praktiken har detta t.ex. varit ett problem för syn- och hörselskadade. Tänk på screen readers t.ex. som genom text-to-speech försöker läsa upp en sidas innehåll för en användare. Hur skulle det vara om den började läsa saker som "blå bakgrund".
Ett av målen för W3C (standardsorganisationen för bl.a. css) är att underlätta för konumptionen av webbaserat innehåll på plattformar än just en dator. Det kan handla om allt ifrån Smartphones till enheter för talsyntes och punktskrift (Braille). Genom att separera innehåll ifrån presentation är det alltså markant lättare, eftersom maskinen inte behöver bry sig om att filtrera ut presentationen ifrån innehållet.
Detta betyder förstås att vi sedan lång tid tillbaka behövt lägga gamla troll som Frames, Iframes, Imagemaps, java applets, javascript, bilder utan ALT-text, GIF-animationer, Flash, Shockwave, PDF-dokument osv. Många funktioner som används idag går då alltså bort helt.
För att enklare förstå hur CSS hanteras, föreställ dig att webbläsaren består av två ninjateams. Det första ninjateamet läser HTML-filen och skriver ut en massa text, bilder, listor, tabeller och länkar på skärmen. När första teamet hittar en referens till en CSS-fil, skickar de det till det andra ninjateamet. Det andra ninjateamet går då lös med färgkritor och klipper och klistrar tills allt ser snyggt ut.
Även om ovanstående exempel är orimligt oseriöst är poängen att rendering av en webbsida (metaforiskt) sker i olika "pass". Där det första är att få ut resultatet av HTML:en på skärmen, och det andra att visuellt ändra på resultatet av HTML:en enligt det som definierats i CSS-filen. Du kommer upptäcka att det finns fler "pass" (och att ordningen kan variera) när vi kommer till JavaScript, men det lämnar vi för nu.
Varför är det då viktigt att förstå att CSS kommer i det "andra passet"? Jo, eftersom det är viktigt att förstå att CSS appliceras på ett befintligt dokument. Med andra ord, ett CSS-dokument är i sig helt meningslöst. Eftersom ett CSS-dokument då appliceras på ett HTML-dokument så måste varje CSS-regel veta vad den ska appliceras på. Och det är här selectors
kommer in i bilden. Låt oss se till ett exempel.
En css-selector
definierar vilka html-element
som ska påverkas av en viss effekt.
index.html
<!DOCTYPE html>
<html>
<head>
<title>The morning</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<p>As Gregor Samsa awoke one morning from uneasy dreams [..]</p>
</body>
</html>
main.css
body{
background-color: lightblue;
}
p{
color: #ffffff;
}
Resultat
As Gregor Samsa awoke one morning from uneasy dreams [..]
Hur ska vi nu tänka kring det här? De viktigaste sakerna att poängtera är:
main.css
.selector
vars target
är <body>
-elementet. Det betyder att allt mellan följande måsvingar ({...}
) kommer att appliceras på alla <body>
-element i html-dokumentet. Nu bör det ju förstås bara finnas ett body-element men förhoppningsvis har du redan förstått att vi hade kunnat välja vilket annat html-element som helst.deklarationer
. Det är de som således bestämmer vilken visuell effekt som ska appliceras på just den selector vi definierat.En css-selector
kan vara vilket html-element
som helst.
Låt oss se det rent generellt. Syntaxen är alltså som följande.
css-selector{
declaration-property: declaration-value;
}
Så, låt oss uttrycka syntaxen för deklarationer
i ord: En css-deklaration
består av en egenskap
(även kallat: property, nyckel, key), följt av ett kolon (:
) som fungerar som en avgränsare mellan nyckeln och värdet. Vidare följt av det faktiska värdet
(som kan ges i en mängd olika format, såsom exempelvis left
, -32px
, 233%
eller light
, beroende på vilken egenskap vi sätter värdet för). Slutligen anger vi ett semikolon (;
) för att terminera raden. Det sistnämnda gör det möjligt att skriva flera deklarationer på samma rad (vilket dock oftast gör filen väldigt svårläslig).
En css-deklaration
består av enproperty
(även kallat: nyckel, key, egenskap), ett kolon (:
), ettvärde
och slutligen ett semikolon (;
).
Utöver att skriva css-selectors som träffar html-element så kan vi även skriva selectors för ID
:n och klasser
.
Vi använder ID:n för att defineira "unika" element. Vad menar vi med unika? Jo att om någonting har id:et container
så får det endast finnas ett enda element på sidan som har just det ID:et. Vi kan självklart skriva flera css-regler som använder just den selectorn. Men i html-dokumentet får ID:t alltså endast förekomma en enda gång.
Behöver vi kunna referera till flera element så använder vi oss av klasser. Klasser fungerar på exakt samma sätt som ID:n förutom just det att det är tillåtet att flera element använder samma klass.
Enklast är nog att som vanligt se till ett exempel över hur detta fungerar.
index.html
...
<p class="redish">As Gregor Samsa awoke one morning</p>
<p id="blueish">from uneasy dreams</p>
<p>he found himself transformed in his bed</p>
<p class="redish">into a monstrous vermin.</p>
...
main.css
p{
color: orange;
}
.redish{
color: red;
}
#blueish{
color: blue;
}
Resultat
As Gregor Samsa awoke one morning
from uneasy dreams
he found himself transformed in his bed
into a monstrous vermin.
Man kan ju tycka att det borde räcka med att kunna hänvisa till element bara via deras typ, klass eller typer. Detta är dock inte sant, suck! Det går att använda att komma åt element på andra sätt och detta gör det lättare man slipper sätta klass eller id på allt. Nedanför finner ni olika selektorer och exempel. Observera att det finns flera....Never ending story!
<h2>En rubrik, yay!</h2>
<p>En paragraf, yay!</p>
*{
font-size: 24px;
background:#ff6600;
}
E står i detta fall att man använder elementets typ som selektor.
<p>En paragraf, yay!</p>
p{
font-size: 24px;
background:#ff6600;
}
<a href="http://htmlhunden.se">Värsta grymma grejen!</a>
a:link{
color:pink;
}
<a href="http://htmlhunden.se">Värsta grymma grejen!</p>
a:visited{
color:blue;
}
<a href="http://htmlhunden.se">Värsta grymma grejen!</p>
a:hover{
color:green;
}
<a href="http://htmlhunden.se">Värsta grymma grejen!</p>
a:active{
color:purple;
}
Alla element F som kommer efter E. Alltså i detta fall alla fyra första paragrafer.
<div class="yttre">
<p>En första paragraf</p>
<p>En andra paragraf</p>
<p>En tredje paragraf</p>
<div class="inre">
<p>En fjärde paragraf</p>
</div>
</div>
.yttre p{
color:pink;
}
alla element som direkt föregås av E. Alltså i detta fall kommer andra och tredje paragrafen att påverkas.
<div class="yttre">
<p>En första paragraf</p>
<p>En andra paragraf</p>
<p>En tredje paragraf</p>
<div class="inre">
<p>En rubrik, yay!</p>
</div>
</div>
p + p{
color:pink;
}
Anta att vi har följande html...
<div class="yttre">
<p>En första paragraf</p>
<p>En andra paragraf</p>
<div class="inre">
<p>En inre paragraf!</p>
</div>
</div>
Och sedan skriver denna regel..
.yttre > p{
color:pink;
}
Så kommer vi således alltså endast "träffa" de två första <p>
-elementen eftersom endast de är direkta barn till elementet med klassen .yttre
.
En första paragraf
En andra paragraf
En inre paragraf
När du använder CSS för att formatera en sida kan du infoga CSS-formateringen på tre sätt:
Det här är den vanligaste användningen av CSS där ett externt dokument som innehåller formateringen kopplas till alla de sidor som ska tillämpa formatet. Namnet på CSS-mallen måste ha filtilläget .css och namnet på mallen i exemplet nedan är "mall.css". Den här metoden är mest effektiv, om formateringen ska ändras behöver du bara göra det i ett enda mall-dokument. Här uppfylls målet med att separera innehåll och struktur i dokumenten. Så här kan koden för sidorna som kopplas till mallen se ut:
<!DOCTYPE html>
<html>
<head>
<title> Sidans namn </title>
<link href="mall.css" rel="stylesheet">
</head>
<body>
<h2>En rubrik, yay!</h2>
</body>
</html>
Du kan koppla flera externa CSS-mallar till samma dokument. Om samma selektorer förekommer i båda mallarna men med olika formatering gäller den mall som angivits senast i radvis ordning. I exemplet nedan gäller alltså "mall2.css" före "mall.css":
<!DOCTYPE html>
<html>
<head>
<title> Sidans namn </title>
<link href="mall.css" rel="stylesheet">
<link href="en_till_mall.css" rel="stylesheet">
</head>
<body>
<h2>En rubrik, yay!</h2>
</body>
</html>
CSS-formatering angiven direkt i dokumentet kan användas när vissa sidor ska avvika från huvudmallens formatering. Den här metoden är inte lika effektiv som att använda en extern CSS-mall. Om formateringen ska ändras måste det utföras i varje dokument som använder formateringen. Här formateras rubriken direkt i dokumentet:
<!DOCTYPE html>
<html>
<head>
<title> Sidans namn </title>
<style type="text/css">
h2 { font-size: 24px; }
</style>
</head>
<body>
<h2>En rubrik, yay!</h2>
</body>
</html>
Den här metoden är minst effektiv och här uppfylls inte målet med att separera innehåll och struktur i dokumenten. CSS-formateringen anges i anslutning till de elementsom ska formateras. När formatet ska användas i ett nytt elememnt måste CSS-koden anges på nytt och det innebär att sidorna innehåller mycket kod och tar längre tid att laddas i webbläsaren. Så här kan koden se ut när rubriken formateras direkt i elementet:
<!DOCTYPE html>
<html>
<head>
<title> Sidans namn </title>
</head>
<body>
<h2 style="font-size:24px;">En rubrik, yay!</h2>
</body>
</html>
Prioriteringsordningen av formateringen är följande:
Detta innebär att du kan börja formateringen i en extern CSS-mall som du kopplar till dina dokument. Vill du sedan ange avvikande format lokalt i ett dokument formaterar du CSS direkt i dokumentet (gäller då före den externa CSS-mallen). Om någon del av dokumentet ska avvika från övrig CSS-formatering anger du detta direkt i avsnittet/objektet (gäller då före både CSS i dokumentets HEAD och CSS i en extern CSS-mall)
Tänk på namnet — Cascading Stylesheets — stilmallar som "kaskadar". Vad menas egentligen med att de "kaskadar" och hur kan vi använda det till vår fördel?
Just ordet "kaskad" åsyftar idén om att någonting "faller" ned ifrån en nivå till en annan och så vidare i etapper. I relation till just CSS så handlar detta om att (de flesta) CSS-regler som appliceras på en förälder även gäller för alla förälderns barn.
Låt oss se till exempel för att bättre förstå vad vi pratar om.
<body>
<p>First paragraph</p>
<div>
<p>Second paragraph</p>
<div>
</body>
body {
color: green;
}
First paragraph
Second paragraph
Mer specifika regler skriver över mindre specifika regler oavsett i vilken ordning de dyker upp i css-filerna.
Att mer specifika regler skriver över mindre specifika regler innebär att vi kan utnyttja kaskadet till vår fördel. Genom att således definiera generella regler på en "hög" nivå och sedan skriva över med de specika ändringar som vi vill göra.
<body>
<p>First paragraph</p>
<div>
<p>Second paragraph</p>
<div>
</body>
body { color: green; }
div p { color: blue; }
First paragraph
Second paragraph
Låt oss se till ett till exempel där vi använder ett elements ID för att kunna skriva över stilarna specifikt. Notera hur den andra paragrafen fortsätter att vara fetstilad eftersom den "ärver" den regeln av sin förälder. Alltså, egenskapen kaskadar ned ifrån föräldern till barnet.
<p>First paragraph</p>
<p id="selected">Second paragraph</p>
<p>Third paragraph</p>
p {
color: red;
font-weight: bold;
}
#selected {
color: orange;
}
First paragraph
Second paragraph
Third paragraph
Varje element som finns på en sida är egentligen en box. Därav så brukar man ofta prata om Boxmodellen för att se vad som
händer runt varje element -------------------------------------------------------------------------
| Margin |
| --------------------------------------------------------------------- |
| | Border | |
| | ----------------------------------------------------------------- | |
| | | Padding | | |
| | | ------------------------------------------------------------- | | |
| | | | Content | | | |
| | | | | | | |
| | | ------------------------------------------------------------- | | |
| | | | | |
| | ----------------------------------------------------------------- | |
| | | |
| --------------------------------------------------------------------- |
| |
--------------------------------------------------------------------------|
Förklaring av de olika delarna:
Om man tar en sväng på internet och funderar lite på det vi hittills lärt oss om HTML och CSS så inser man snabbt att saker inte alls bara ligger rakt upp och ned i dokumentet. Texter ligger i mitten, vi har kolumner och rader, marginaler och boxar som verkar vara "sticky".
I det här kapitlet går vi igenom de olika värden vi kan ge css-attributet position
, närmare bestämt absolute
, relative
och static
.
Om vi inte anger någonting annat så är alla element statiskt placerade. Det är värt att notera att även om namnet på attributet position
verkar antyda det så är det verkligen inte det enda sättet webbutvecklare positionerar saker genom. Men med rätt förståelse för attributet finns det knappt någon positionering vi inte kommer kunna åstadkomma.
Om du inte anger någonting annat, kommer element att positioneras statiskt. Alltså följa sin naturliga plats i dokumentet.
Det är viktigt att uppmärksamma att ett statiskt element på de (tänk dig en sida som ett koordinatsystem) koordinaterna {0,0}
, omöjliggör att ett statiskt placerat syskon också placeras på {0,0}
. Med andra ord tar statiskt placerade element upp plats och således kan syskon inte ligga på varandra utan placeras istället under (om de är block-level element) eller bredvid (om de är inline-level element) varandra.
Statiska element tar upp plats
Att ett statiskt element inte kan placeras på ett annat element gäller förstås bara element som är syskon. Ett elements barn placeras förstås naturligt "innuti" förälderelementet.
Med andra ord. Om ett förälderelement har de (hypotetiska) koordinaterna {0,0}
så kommer även första barnet till det elementet ha koordinaterna {0,0}
.
Låt oss se till några exempel.
<div>
:ar efter varandra.<div>
i en <div>
Fixerade element tar inte upp plats
Med fixerad positionering säger vi åt ett element att ignorera sin "normala" plats i dokumentflödet och istället placera sig på en position i relation till webbläsarfönstret.
Med andra ord så är alltså (den tänkta koordinaten) {0,0}
högst upp till vänster i webbläsaren. Detta förutsatt att vi sätter föregående värden för egenskaperna left
och top
vilket då i ett kordinatsystem skulle motsvaras av x
och y
. Således kan vi sluta oss till att origo är högst upp till vänster i webbläsaren.
Vad som gör positionering i css intressant är att vi även kan vända steken och istället sätta värden för egenskaperna right
och bottom
. Vi hanterar då fortfarande x
och y
i bemärkelsen horisontellt och vertikalt men vi har nu flyttat origo ner till högra hörnet. Plus att vårt koordinatsystem nu fungerar "baklänges". Ett högre värde för right innebär att vi flyttar vårt element längre åt vänster. Sätt detta i relation till att ett högre värde för left flyttar vårt element längre åt höger.
Tänk på positioneringsteknikernafixed
,absolute
ochrelative
som positionering genom i ett koordinatsystem.
Medan ovan kommentarer gäller för alla position
-värden utom static
, gäller följande endast för position
-värdet relative
.
Relative ignorerar hur användaren scrollar i ett dokument. En tänkt koordinat, säg {100,120}
, står i relation till browserns storlek och endast browserns storlek och det "fönster" där browsern renderar sidan. Med andra ord, kommer elementet alltid att befinna sig 120px ifrån fönstrets topp, inte ifrån dokumentets topp (vilket är hur absolute
positionering beteer sig).
#blue {
position: fixed;
top: 20px;
left: 30px;
}
Även med absolut positionering så säger vi åt ett element att ignorera sitt "normala" dokumentsflöde och istället placera sig på precis de koordinater vi specificierar.
Frågan är då bara — precis på de koordinaterna i relation till vad? I det normala fallet så betyder det i relation till fönstret. Men om någonting absolut positionerat befinner sig i någonting annat som är absolut eller relativt positionerat så räknar vi då i relation till den föräldern.
Ovan blir nog enklare att förstå om vi ser till ett par exempel.
#red {
position: absolute;
top: 0;
right: 0;
}
#blue {
position: absolute;
bottom: 0px;
left: 0px;
}
<div>
:ar<div>
i en absolut positionerad <div>
När vi positionerar ett element relativt så är det som om vi kombinerar metoderna statisk och absolut. Vi positionerar ett element relativt till dess statiska position.
Med andra ord, elementet antar först den position den bör få i det statiska flödet, och vi ser nu den platsen som "nollpunkten" (origo). Sedan tas elementet ut ur dokumentflödet och vi placerar det absolut (enligt de koordinater vi angett) i relation till sin statiska placering.
#red {
position: relative;
top: 0;
left: 0;
}
#blue {
position: relative;
top: -10px;
left: 10px;
}
<div>
:ar<div>
i en relativt positionerad <div>
Om du finner ovan lite konfunderande finns nedan en film som applicerar några av dessa metoder i praktiken.
I videon är målet att centrera en <div>
horisontellt och vertikalt på sidan. Detta vilket bl.a. leder oss in på en teknik som använder sig av positionering genom absolute
.
Den största limitationen med HTML och CSS skulle kunna beskrivas med ett att de är ett ord — statiska. De båda språken klarar av en viss nivå av interaktivitet, men i huvudsak behöver vi någonting mycket mer dynamiskt för att kunna uppnå seriös interaktivitet. Ridån faller.. och in kommer JavaScript!
Om du börjar jobba med HTML och CSS kommer du snabbt upptäcka att de båda teknikerna har sina limitationer. Limitationen skulle enkelt kunna beskrivas som avsaknaden av ett ord — interaktivitet!
Visst, vi kan använda HTML för att strukturera content, och visst, vi kan använda CSS för att positionera, och style:a innehållet så att det inte bara är snyggt utan även kognitivt lättillgängligt för en människa. Men hur är det egentligen med interaktiveten?
Förvisso kan vi använda pseudo-selektorn :hover
i css för att skapa effekter när användaren låter musen rulla över länkar (såsom t.ex. understrykning). Och visst, vi kan ju genom HTML skapa olika sidor som vi sedan sammanlänkar genom hyperlänkar (<a>
-taggen), vilket alltså betyder att användaren interaktivt kan navigera sig emellan dessa sidor.
Onekligen är ovan två nämnda exempel just det, exempel på interaktiviet vi kan uppnå genom HTML och CSS. Men finns det interaktivitet vi skulle vilja skapa som vi inte kan uppnå med bara HTML och CSS? Föreställ dig en delete-knapp. Föreställ dig att vi har en applikation, vilken som helst, och det finns en knapp som säger "Ta bort mitt konto". Vore det inte då rimligt att be användaren att konfirmera att denne verkligen vill ta bort sitt konto när den trycker på knappen? Självklart.
Tänk på ovan exempel en stund. Hur skulle vi lösa det genom HTML? Om vi ignorerar galna lösningar (som jag inte alls skulle rekommendera) med <iframe>
's så har vi egentligen bara ett val. Säg att knappen ligger på en sida som heter delete.html
. När man trycker på den knappen behöver vi skicka användaren till en annan sida, vi kallar den för confirm_delete.html
där användaren presenteras med två möjligheter till. Alltså två nya länkar till två andra sidor. Kanske är dessa länkar samma sidor som innan, kanske inte. Om inte, så har vi skapat två sidor till, nämligen: yes_delete.html
och no_delete.html
. Rimligen ser du vilken soppa av sidor det här snabbt blir. Och rimligen har du nu kommit på att det är här JavaScript kommer in i bilden.
Vi diskuterar nu förstås lite halvsanningar eftersom lösningen skulle kunna bli enklare med ett server-side-språk. Men lita på oss när vi säger att JavaScript kommer göra det ännu enklare.
Låt oss se till hur vi skulle kunna lösa samma sak genom JavaScript.
När du provat ovan exempel, finns det en till viktig sak att inse. Interaktiveten i ovan exempel sker "isolerat" i den lilla exempelrutan. Resten av sidan påverkas inte. Slutsatsen vi drar är alltså att vi kan använda JavaScript till att uppnå en nivå av interaktivet vi omöjligen kan uppnå med endast HTML och CSS utan att använda flera sidor.
Med JavaScript kan vi låta användaren interaktivt interagera med sidan utan att den behöver "laddas om".
Innan vi lärt oss ett dugg om JavaScript ska vi köra en djupdyk med näsan först, och skriva vårt första skript. Samtidigt kommer vi att diskutera var vi väljer att placera vår kod och hur man bör arbeta med JavaScript.
Om det enkla skriptet i nästföljande figur körs på en webbsida så tar den helt sonika bort allt dokumentet innehåller och ersätter det med texten "Evil rabbits... osv". När vi använder det här skriptet som ett exempel i detta dokument så får du låtsas som om varje exempel-ruta är en egen webbsida. För om vis kulle köra document.write("");
på hela denna sida skulle vi ju rensa hela sidan vilket skulle göra det väldigt svårt för dig att fortsätta läsa denna text.
document.write("Evil rabbits are taking over our servers! Must find carrots!");
Hursomhelst, låt oss nu istället diskutera lite olika platser vi skulle kunna lägga in detta skript på.
Ett ställe vi kan placera vår JavaScript på är rakt upp och ner i <HEAD>
-taggen. Så länge som vi snurrar in vår JavaScript emellan starttaggen <script>
och sluttaggen </script>
. Nedan följer ett exempel i ett minimalistiskt HTML-dokument. När du läser exemplet, tänk framförallt på vart <script>
-taggarna och ovan nämn javascript-rad är placerad/-e.
<HEAD>
-taggen<!DOCTYPE html>
<html>
<head>
<title>Exempel</title>
<script>
document.write("Evil rabbits...");
</script>
</head>
<body>
<p>Denna text kommer inte att synas eftersom JavaScriptet skriver över den.</p>
</body>
</html>
För att illustrera att detta är någonting som verkligen skapas av JavaScript, så tar vi och kommenterar ut rad 6 såsom nedan. Att kommentera ut en rad gör att den ignoreras av webbläsaren och således inte exekveras.
// document.write("Evil rabbits...");
...så får vi istället nedan resultat.
Ett annat ställe vi kan placera vår JavaScript-kod på är i onClick-attributet. Som namnet onClick
antyder kommer då koden att exekveras just då — "on click". Alltså när användaren klickar på elementet i fråga.
Låt oss se till ett exempel, som använder sig av samma kod.
<!DOCTYPE html>
<button>
<head>
<title>Exempel</title>
</head>
<body>
<a onClick="document.write('Evil rabbits...');">Engage descrution!</a>
</body>
</html>
I ovan exempel finns det tre viktiga skillnader att notera i relation till det första exemplet. Först och främst. Vår JavaScript ligger nu inte längre inom <HEAD>
-taggen utan inom <BODY>
. Det betyder alltså att vi har specificerat vårt skript bland vårt content och inte vårt meta-content. Eftersom skript inte är content så borde det här ringa en varningssignal om att vi sysslar med en dålig "practice", men det återkommer vi till senare.
Den andra viktiga skillnaden vi bör inse är att vi inte längre kör vårt JavaScript när sidan laddas utan istället när användaren klickar på en knapp. Vi har alltså inte bara lagt vårt skript rakt av, utan vi har lagt det som en anonym funktion som kommer att köras när event lyssnaren onClick
avfyras. Vi kommer att prata mer om event listeners senare men för nu kan du helt enkelt tänka dig det så här. Egenskapen onClick
är ett nyckel-värde-par där värdet är en sträng. Denna sträng kommer inte att tolkas som vilken sträng som helst, utan kommer att exekveras som JavaScript. Vi hade således kunnat skriva vilken arbiträr mängd JavaScript som helst mellan de två citationstecknena som delimiterade värdet. Låt oss se till ett exempel till för att verkligen förstå hur det fungerar.
<a onClick="alert('Hello...'); alert('...you!');">Welcome me!</a>
Glöm nu allt du lärt dig om att skriva inline-javascript ionClick
och glöm nästan allt du lärt dig om att skriva JavaScript direkt i<head>
— det finns bättre sätt!
Som vanligt är detta bättre sätt baserat på idéen om "separation of concerns" och innebär att vi flyttar vår JavaScript till en separat fil. Och det är detta vi kommer göra i nästa stycke.
Om du inte har en medveten anledning till varför du inte ska göra det så är det bästa sättet att hantera JavsScript — i en separat fil. På precis samma sätt som vi arbetar med CSS så skapar vi en ny fil som vi döper till ett-schysst-filnamn.js
. Notera alltså ändelsen .js
. Sedan gör vi på precis (nästan) samma sätt som när vi har en extern CSS-fil. Alltså, vi lägger in ett element i <head>
som pekar på vår JavaScript-fil. Som så:
<!DOCTYPE html>
<html>
<head>
<title>Exempel</title>
<script src="main.js"></script>
</head>
<body>
</body>
</html>
document.write("Oh noes evil rabbits...");
Oh noes evil rabbits...
Notera alltså rad 5 i ovan exempel. Det är just rad 5 som berättar för webbläsaren vart JavaScript-filen finns. Vi anger .js-filens plats med en relativ sökväg genom att bara skriva filnamnet.js
rakt upp och ned. Så webbläsaren kommer alltså leta efter en fil vid namn filnamnet.js
i samma mapp som index.html ligger.
Kanske har du märkt att många webbutvecklare laddar in sina JavaScript-filer i <body>
och inte i <head>
. Detta har med performance och göra, och är egentligen en superb idé! Låt oss prata om varför.
När en webbläsare renderar en webbsida så går den uppifrån och ned. Och när den stöter på ett request till en extern resurs, såsom en bild, en css-fil, eller en javascript-fil etc. så behöver den stanna, vänta, och ladda in filen. Tänk på det en stund. När vi lägger in en bild i ett html-dokument så lägger vi ju faktiskt inte in bilden utan endast en referens till den plats url
där bilden befinner sig. Detta innebär att webbläsaren alltså måste hämta ("ladda ner") denna bild för att faktiskt kunna visa den.
Eftersom webbläsare endast kan hantera ett limiterat antal requests parallelt så betyder det att sidan lätt fastnar i en flaskhals. Standarden HTTP/1.1 specificerar att en browser max bör hålla två öppna connections per server, detta vilket uppenbart orsakar en flaskhals ifall vi hämtar 10 bilder, 2 css-filer och 1 javascript-fil ifrån vår egen server.
Moderna browsers har dock valt att ignorera dessa maxtal och hanterar faktiskt fler parallella uppkopplingar men trots detta har det blivit praxis att låta JavaScript-filerna laddas in allra sist i <body>
. Anledningen till detta är alltså att om webbläsaren får en chans att parse:a hela <body>
:n innan den krockar med det element som pekar på en javascript-fil, så betyder det att webbläsaren kommer ha en chans att visuellt printa ut sidan för användaren och SEN börja hämta denna javascript-fil.
Faktum är att det t.o.m. är tillåtet enligt specifikation att placera <script>
-taggar i <body>
. Låt oss se till ett exempel över hur detta skulle kunna se ut.
<body>
<!DOCTYPE html>
<html>
<head>
<title>Exempel</title>
</head>
<body>
<p>Först lägger vi allt vårt content</p>
<p>Och sen sist, laddar vi in js:</p>
<script src="main.js"></script>
</body>
</html>
Således används alltså denna teknik för att ge användaren en känsla av att sidan har laddat klart snabbare än den egentligen gör. Om vi lägger våra javascript-filer i <head>
kommer webbläsaren alltså att "blocka" sidan och inte printa ut någonting visuellt förrän hela javascript-filen har laddats ned. Men om vi lägger referensen till vår javascript fil i slutet av <body>
kommer webbläsaren att rendera hela <body>
:n innan den börjar blocka och ladda in javascript-filen.
Såsom mycket annat i världen så är detta inte svart eller vitt, utan det finns många fall där vi faktiskt bör ladda in JavaScript i <head>
. Du kan läsa mer om detta bl.a. här.
Eftersom JavaScript, som namnet antyder, är ett skriptspråk har vi tillgång till mäktiga koncept såsom variabler och funktioner. En variabel kan metaforiskt ses som en box vi lägger någonting i, och det är just denna box vi kommer att fokusera på i detta kapitel.
Variabler kan ses som en arbiträr box, av arbiträr storlek, där vi kan placera ett stycke, och endast ett stycke, arbiträr data. Tänk på det en stund. Låt oss formulera om samma sak. En variabel är en pekare mot en arbiträr plats i minnet, av arbiträr storlek, som innehåller arbiträr data. Det är lite närmare sanningen men fortfarande en metafor.
Variabler kan ses som en arbiträr box, av arbiträr storlek, där vi kan placera ett och endast ett stycke arbiträr data.
Så hur deklarerar vi då en variabel? Låt oss se till ett exempel.
var name = "Dr. Zaius";
I ovan exempel deklarerar ("skapar") vi alltså en variabel och tilldelar den värdet av texten "Dr. Zaius". Således finns det alltså två saker vi gör här. Vi (1) deklarerar, och vi (2) tilldelar. Låt oss se till ett nytt exempel där vi gör dessa steg för steg.
// Deklaration
var name; // name => undefined
var age; // age => undefined
// Tilldelning
name = "Dr. Zaius"; // name => "Dr. Zaius"
age = 42; // age => 42
// Deklaration och tilldelning samtidigt
var species = "Orangutang";
Notera alltså att ovan exempel illustrerar att det är fullt möjligt att först deklarera en variabel och sedan tilldela den ett värde, i två steg. Detta kommer sig av den enkla anledningen att tilldelning och deklaration är två olika saker.
var foo;
)foo = "bar";
)I många språk deklarerar vi variabler som olika typer beroende på vad för typ av innehåll vi vill kunna lagra i variabeln (text, heltalsnummer, decimaltal etc.) men i JavaScript räcker det med att vi deklarerar att variabeln finns. Vilken typ den sedan är av, avgörs av vad vi sedan väljer att lagra i den. JavaScript är alltså vad vi brukar kalla för ett Dynamiskt typat språk.
Nedan följer en kort videointroduktion till variabler i JavaScript.
Vad kan vi lagra? Kort sagt: vad som helst som faller under JavaScripts tre datatypskategorier: primära datatyper, komposit-datatyper, eller speciella datatyper. Dessa är som följer:
Datatyp | Förklaring | Exempel (separerade med ;) |
---|---|---|
Primary datatypes | ||
number | Siffror | 0; 12; -432; 11.4; -32.4; |
string | Text | "Ekonomikum 1A"; |
boolean | Sant eller falskt | true; false; |
Composite datatypes | ||
array | Listor innehållandes andra datatyper | [1, 3, 2]; ["Hello", 123]; |
object | Nycklar som pekar på värden | { title:"Dr", name:"Snuggles" } |
Special datatypes | ||
null | Värdet för ingenting | null; |
undefined | Värdet för avsaknaden av ett värde | undefined; |
Någonting som ofta är förvirrande när det kommer till programmering är att likhetstecknet (=
) i programmering skiljer sig signifikant ifrån likhetstecknet i matematik. Likhetstecknet i matematik implicerar ekvivalens medan likhetstecknet i programmering implicerar tilldelning. Vad är då skillnaden?
Om två uttryck är ekvivalenta menar vi att vi kan ersätta det första uttrycket med det andra och det betyder samma sak. Låt oss se till ett exempel.
(1 + 1) = (2)De två uttrycken, isolerade av paranteser, är ekvivalenta och vänstra ledet kan således ersättas med det högra. Detta gäller inte i programmering.
Likhetstecknet i matematik implicerar ekvivalens medan likhetstecknet i programmering implicerar tilldelning.
Men om ekvivalens inte gäller i programmering, vad gäller då istället? Jo, tilldelning. Med matematisk ekvivalens menar vi att evalueringen (resultatet av att beräkna) av det vänstra ledet är exakt samma sak som evalueringen av det högra ledet. Med tilldelning däremot, menar vi att evalueringen (resultatet av beräkningen) av det högra ledet representeras av det vänstra ledet.
I matematik tänker vi ofta i termer av sanning, men för att lättare förstå hur imperativ programmering fungerar kan det vara fördelaktigt att istället försöka tänka i termer av att vi "räknar ut värden och lägger de i lådor (variabler)". Boxarna har ingen aning om vart värdena kom ifrån, och värdena har ingen aning om i vilka boxar de kommer placeras.
Kanske hade det varit lättare att förstå tilldelning om syntaxen (notationen) istället hade varit a <= 23
. Alltså: lagra värdet 23 i variabeln a.
var a = 1; // a => 1
var b = a + 3; // b => 4
var c = a + b; // c => 5
var d = a + b + c; // d => 10
Läs ovan, rad för rad, och fundera över varför det resultat som sparas i variabeln blir det som visas i kommentaren till höger.När vi pratar om programmering är det lättare att tänka att vi "räknar ut värden och lägger resultaten i boxar".
Nu börjar vi närma oss de signifikant mer intressanta delarna av programmering. Funktioner! Funktioner i programmering kan med fördel jämföras med funktioner i matematik. En funktion är som en maskin. En maskin där du kan stoppa in ett ting och få ut ett annat ting. Alltså en funktion (F
) som tar emot ett ting (x
) och returnerar ett annat ting (y
), där detta andra ting, i matematik, kan uttryckas som en funktion applicerad på det första (F(x)
).
PS. Vänligen anmäl oss inte till Högeskoleverket om det skulle vara så att våra matematiska metaforer är inkorrekta. Vi försöker bara använda de här för att skapa en förståelse.
Innan vi fortsätter prata om hur funktioner fungerar, låt oss se till ett exempel.
var addition = function(x, y){
return x + y;
}
Ovan definerar vi en funktion, som vi namnger addition
, och som returnerar resultatet av en addition av dess två parametrar. Motsvarande funktion skulle matematiskt kunna uttryckas:
F(x, y) = x + y
Vi har nu sett hur man definierar en funktion. Men hur använder vi den då? Låt oss återigen se till några exempel.
// Först definierar vi en funktion
var addition = function(x, y){
return x + y;
}
// Sen anropar vi funktionen
addition(1, 1); // => 2
addition(30, 5); // => 35
addition(addition(1,2), 4) // => 7
addition(addition(1,addition(1,1)), 4) // => 7
Låt oss uttrycka samma sak i matematik för att skapa ytterligare förståelse för vad vi gör:
Först definierar vi funktionen...
F(x, y) = x + y
Sen använder vi den...
F(1, 1) = 2
F(30, 5) = 35
F(F(1,2), 4) = 7
F(F(1,F(1,1), 4) = 7
Notera alltså att vi kan skicka resultatet av en funktion som parameter (input) till en annan funktion. Precis som i matematik så måste den innersta beräkningen utföras först innan vi kan utföra den yttre.
Precis som i matematik behöver det innersta uttrycket räknas ut först innan vi kan fortsätta "utåt".
Eftersom JavaScript på gott och ont är väldigt flexibelt finns det olika sätt att deklarera funktioner.
Detta är det klassiska sättet att deklarera funktioner i JavaScript.
function myFunc(){ //work };
Denna metod gör att funktionsnamnet är tillgängligt i hela sitt scope. Även innan den är deklarerad.
console.log(myFunc()); // => Hello
function myFunc(){ return "Hello" };
Detta sätt att deklarera kan ses som att vi tilldelar en anonym funktion till en variabel.
var myFunc = function(){ //work };
När vi deklarerar en funktion så här så är den inte tillgänglig före deklarationen, endast efter.
console.log(myFunc()); // => TypeError
var myFunc = function(){ return "Hello" };
Vi kan även kombinera ovan två metoder som så:
var myFunc = function myFunc(){ // work };
Så, istället för att gräva ned oss i vilket sätt man bör använda så föreslår vi att du följer Douglas Crockford's rekommendation om att använda function expressions. Alltså:
var myFunc = function(){ //work };
Välj function expressions över function statements, tills den dag kommer du då intresserar dig av att lära dig varför.
Document Object Model(DOM) är ett API för HTML och XML dokument. Den definierar den logiska strukturen och hur man kommer åt ett dokument och manipulerar det.
Med denna modell kan vi nu med hjälp av Javascript kunna modifiera trädet och skapa dynamiska hemsidor. Det handlar alltså om att vi kan ta bort, komma åt, modifiera och lägga till element.
Document är ett objekt som "Äger" alla andra objekt, med det menas att utifall du vill komma åt andra objekt i DOM:en görs detta genom document.method()
.
Metod | Förklaring | |
---|---|---|
Hitta element | ||
document.getElementById() | Hämtar ett element med hjälp av dess ID | |
document.getElementsByTagName() | Hämtar alla element med hjälp av dess tagnamn | |
document.getElementsByClassName() | Hämtar ett element med hjälp av dess klassnamn |
När vi hämtar ett element ur DOM:en genom någon av de metoder som diskuterades ovan, så kan vi sedan läsa och skriva information till det elementet. Ett element är alltså bara ännu ett objekt med ett antal egenskaper och metoder. Med andra ord kan vi inte bara läsa ifrån DOM:en, utan vi kan även förändra den. Nedan ser du några exempel på vad vi kan göra med ett element
Antag att vi har ett element i variabeln elem | ||
elem.innerHTML | Ändrar den inre HTML:en av ett element | |
elem.attribute | Ändrar ett elements attribut. | |
elem.style | Ett objekt som vi kan läsa/skriva css från/till |
Låt oss se till ett par exempel på hur vi kan manipulera DOM:en.
<p id="secret"> LYSSNA PÅ MIG! </p>
var p = document.getElementById("secret");
p.style.display = "none";
Vad vi gör ovan är alltså helt enkelt att vi först hämtar elementet med hjälp av dess ID. Sedan hämtar vi style
-objektet på elementet. På style
-elementet sätter vi egenskapen display
till värdet none
. JavaScriptkoden skulle kunna komprimeras till att skrivas på en enda rad, vet du hur?
<p> A </p>
<p> B </p>
<p> C </p>
<p> D </p>
var as = menu.getElementsByTagName('p');
as[0].innerHTML = "Ett";
as[1].innerHTML = "Två";
as[3].innerHTML = as[1].innerHTML;
Ett
Två
C
Två
Ok, nu kanske det krävs en liten förklaring. Om vi som i första exemplet hämtar med id då vet vi att vi endast får ett objekt tillbaka. I andra exemplet ovan ber vi om objekten med hjälp av tagnamnet och därför returneras dessa i en array. Med andra ord: när vi hämtar ett element genom ett ID kan vi alltid vara hundra på att det bara finns ett (eftersom ett ID endast får förekomma en gång i ett HTML-dokument). Men när vi söker element via tagnamn så kan vi omöjligen veta hur många instanser det finns av just den taggen. Således har man valt att låta getElementsByTagName
returnera en array. Således är det även därför vi ovan använder bracket-notationen ([x]
) för att arbeta med resultatet.
Se till att du förstår varför outputen i exemplet ovan blir som den blir innan du går vidare
När vi skriver JavaScript som på något sätt interagerar med DOM:en, är det viktigt att vi är säkra på att DOM:en är redo (inladdad) innan vi börjar försöka nå den. Detta gör vi enkelt genom att använda oss av metoden onload
.
window.onload = function(){
// do all fancy work here
}
Varför gör vi alltså detta? Jo, om vi inte skulle göra det så finns alltså risken för att de element vi försöker komma åt inom funktionskroppen i ovan exempel ännu inte finns.
Ovan kan förstås lika gärna göras i två steg, genom att registrera en redan deklarerad funktion.
// Declare the init function
var init = function(){
}
// Register it as a listener to the onload event
window.onload = init;
Vad vi har gjort nu kallas mer generellt för att registrera en funktion (en event handler
) till ett event
. Mer om detta i avsnittet om events.
Nästa ämne vi ska prata om är eventlyssnare. Något som finns i många programmeringsspråk och som ofta beteer sig liknande, om än med olika syntax. För att förstå event listeners, fundera över följande: hur vet vi att en användare har klickat på en knapp?
Mer generellt uttryckt: Event listeners hjälper oss att exekvera specifik kod vid en specifik händelse. Det kan tyckas trivialt. Men tänk på när vi skriver ett program. Om vi inte hade events skulle hela programmet endast kunna köras i ett svep (rufft uttryckt). Programmet börjar exekveras, och oavsett hur mycket klasser och funktioner vi använder så kommer programmet att köras ifrån början till slut. Med events så kan vi däremot registrera vad som kallas för lyssnare
. Programmets exekvering "fryser" inte. Programmet stannar alltså inte när vi registrerar en event listener utan fortsätter exekvera som vanligt. När den händelse som eventlyssnaren lyssnar efter (exempelvis en knapptryckning) händer så exekveras koden lyssnaren pekar på. Detta kallas ofta för event-driven programming.
Vi ska i det här kapitlet prata om metoden addEventListener()
, men för att enklare förstå hur de fungerar behöver vi först prata om vad det betyder att JavaScript har en Asynkron Event Model
. Beakta följande kodexempel:
console.log("a");
// setTimeout är en funktion som kör en annan
// funktion efter en given väntetid i millisekunder
setTimeout(function(){
console.log("b");
}, 1000)
console.log("c");
a
c
b
Hur kan det komma sig att vi fick outputen i ovan ordning? Detta har alltså att göra med den asynkrona event modellen i JavaScript. Den funktion som körs av setTimeout
körs inte förrän väntetiden på 1000 ms
har passerat. Men, eftersom JavaScript har asynkrona event, så registreras denna funktion som ett event. Således kommer programmet fortsätta att exekvera. När tiden sedan gått ut, och event-loopen är "ledig" så exekveras den givna funktionen.
Om setTimeout
hade varit en synkron metod hade exekveringen avstannat ("fryst") vid anropet till setTimeout
, och inte fortsatt förrän väntetiden passerat.
Även om vi hade satt timeout-tiden ovan till 0ms så hade vi fått samma output. Varför? Jo, för att även om väntetiden för setTimeout
är 0 så registrerar den funktionen till event-loopen utan att exekvera den på en gång. Således kan den givna funktionen inte exekveras förrän nästa lediga "tick" i event-loopen.
Varför är detta då viktigt? Jo, detta gäller alltså även för när vi registrerar event-lyssnare.
Istället för att gräva ned oss i mer teori, låt oss kolla på ett exempel kring hur vi kan använda addEventListener.
<p id="eventlistener-example-1">
hello
</p>
var tag = document.getElementById('eventlistener-example-1');
tag.addEventListener("click", function(){
if(tag.innerHTML == "hello")
tag.innerHTML = "world";
else
tag.innerHTML = "hello";
});
hello
Nu är en bra tid att prata om webbläsarstöd. Äldre webbläsare stödjer inte metoden addEventListener, så om ovan exempel inte fungerar för dig så betyder det att din webbläsare är för gammal. Internet Explorer, tidigare och upp till, 8 har en alternativ implementation där metoden heter attachEvent
. Så för att ovan exempel även ska fungera i Internet Explorer 8 behöver vi välja vilken metod vi använder berodende på vilken som finns. Läs mer om det i denna tråd på Stack Overflow.
Den funktion som körs när ett event avfyras, kallas för en event handler
. Självklart hade vi ju kunnat skriva denna event handler som en function declaration eller function expression istället för att bara skriva den inline. Detta är bra eftersom vi då kan återanvända beteende. Såsom nedan:
<p id="eventlistener-example-2">
hello
</p>
var onClick = function(element){
if(element.target.innerHTML == "hello")
element.target.innerHTML = "world";
else
element.target.innerHTML = "hello";
}
document.getElementById('eventlistener-example-2-1').addEventListener('click', onClick);
document.getElementById('eventlistener-example-2-2').addEventListener('click', onClick);
hello
world
Styrkan i ovan exempel är ju alltså att vi nu kan återanvända vår eventhandler och attacha den till flera olika events. En annan lärdom vi kan dra av ovan exempel är att eventhandlers anropas med argument. Detta kan vi använda för att upptäcka vilket element som faktiskt har avfyrat eventet.
JavaScript är inte ett objektorienterat språk i den klassiska bemärkelsen utan istället ett prototypbaserat språk. Vad detta innebär kommer vi inte fokusera på i denna sektion, och därmed kommer vi inte syssla med instansiering. Istället kommer vi använda objekt som ett sätt att organisera vår kod.
Om du programmerat i ett tidigare språk så kan du jämföra JavaScript-objekt med map
's eller dictionary
's. Om du inte har programmerat tidgare så kan du jämföra objekt med uppslagsverk. Va? Jo, precis så! Helt vanliga gamla bokuppslagsverk. Vänta nu? Va? Jo men tänk dig. Hur fungerar ett uppslagsverk. Om jag undrar vad ordet "katt" betyder så tar jag mitt fysiska uppslagsverk (objektet) och börjar leta efter uppslagsordet "katt" (nyckeln), och när jag väl hittat det så läser jag beskrivningen av vad en katt är (värdet).
Låt oss analysera de tre nyckelorden vi identifierat i ovan stycke.
Innan vi snurrar vidare är det nog bäst att vi börjar undersöka syntaxen vi använder för att skapa objekt och således diskutera ett exempel.
var haddock = {
name : 'Kapten Haddock',
beard: 'black as the night',
rank: 1
}
Nu har vi skapat ett objekt och lagrat det i variabeln haddock
. Låt oss nu se hur vi kan interagera med objektet.
haddock.name; // => "Haddock"
haddock.rank; // => 1
haddock.beard; // => "black as the night"
haddock.weapon; // => undefined
Vi kan nu alltså använda punkt-notation för att nå de värden som gömmer sig bakom ett objekts nycklar. Men faktum är att vi även kan nå värdena genom att använda samma notation vi använder för att nå värdena i en array. Enda skillnaden är att vi istället för att be om en arrays numeriska index ber vi om värdet bakom en viss nyckel. Låt oss se till ett exempel, och tänk på att det är exakt samma objekt som vi arbetar med i båda dessa två exempel.
haddock['name']; // => 'Haddock'
haddock['rank']; // => 1
haddock['beard']; // => "black as the night"
haddock['vegetables']; // => undefined
Om vi jämför de två olika sätten att nå ett objekts värden märker vi snabbt att vi i det första exemplet skriver nyckelns namn rakt av, medan vi i det andra exemplet skriver nyckeln i formen av en sträng. Det betyder att vi skulle kunna byta ut denna sträng emot ett uttryck. Alltså en variabel, eller t.o.m. en funktion! Låt oss prova:
var prop = 'name';
haddock[prop]; // => 'Haddock'
prop = 'rank'
haddock[prop]; // => 1
prop = 'beard'
haddock[prop]; // => "black as the night"
prop = 'fruit'
haddock[prop]; // => undefined
Beroende på vilket värde vi lägger i variabeln hämtar vi värdena vid olika nycklar. Men om det är så att vi inom klammrarna kan ha ett uttryck vilket som helst så skulle vi ju förstås kunna göra ännu galnare grejer.
haddock['na' + 'me']; // => 'Haddock'
prop = 'na';
haddock[prop + 'na']; // => 'Haddock'
// eller vad sägs om funktioner..
function getKey(){
return 'name'
}
haddock[getKey()]; // => 'Haddock'
Mycket av världens mjukvara drivs av open source. Alltså mjukvara vars källkod är öppen för allmänheten. Men vad är egentligen open source? Hur kan vi använda det? Hur kan vi bidra?
Open-source, eller öppen källkod på svenska, är idéen om att lämna ut sin källkod till allmänheten så att vemsomhelst kan använda, modifiera eller bygga vidare på den. Idag är det nästan omöjligt att surfa omkring på internet utan att använda sådant som del- eller helvis står på axlarna av open-source.
Ett par exempel på lösningar som är drivna med öppen källkod är bloggmotorn WordPress, operativsystemet Linux, webbservern Apache och databashanteraren MySQL.
Om man ska diskutera de projekt som storskaligt verkligen haft influens på hur vi webbutvecklar är det nästan hädelse att inte nämna jQuery. JavaScript-biblioteket med stort B, som hjälper oss att göra allt ifrån animation, iteration, ajax och att fånga tangentbordstryckningar.
Att koda JavaScript är en fröjd i många avseenden, men framförallt för att språket genomsyras av en fantastisk open-source-kultur där en multitud av problem redan är lösta. I den här sektionen pratar vi lite om open-source och js-bibliotek generellt. Men framförallt ska vi lära oss att skaka fram webbläsareffekter som en annan jQuery-Houdini. slideIn(); fadeOut();
jQuery är verkligen JavaScript-biblioteket med stort B. Biblioteket hjälper oss att göra allt ifrån animation, iteration, till att bygga one-pagers som använder ajax för att ladda in data, eller att bygga tangentbordsvänliga applikationer genom att fånga tangentbordstryckningar. I detta kapitel ska vi dyka rakt in i och börja använda jQuery, utan att fundera särskilt mycket över varför saker fungerar som de fungerar.
jQuery är ju alltså ett JavaScript-bibliotek. Ett bibliotek kan, oavsett språk, ses som någonting som utökar det språk vi skriver i för att underlätta vissa aktiviteter.
Säg att vi t.ex. ofta utför aktivitet A
, B
och C
i följd. Någonting ett bibliotek ofta gör är då att t.ex. ge oss ett nytt namn — säg Z
, vilket vi kan använda för att utföra alla tre aktiviteter samtidigt.
Ett bibliotek består då alltså av kod, i detta fall kod skriven i JavaScript. Så, för att kunna använda jQuery behöver vi "koppla in" jQuery i våra egna dokument. Låt oss uttrycka oss mer specifikt. Hela jQuery-biblioteket behöver laddas in på varje .html-sida som ska använda jQuery, innan vi försöker använda det.
Att ladda in jQuery är enkelt. Hela biblioteket får plats i fil. Vi har två alternativ. Antingen kan vi...
För enkelhetens skull kommer vi att arbeta med det senare alternativet. Detta gör vi helt enkelt genom att placera följande script-element i vår .html-fil:
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
Så, nu har vi jQuery tillgängligt i vårt dokument. Men efter detta behöver vi ladda in en till JavaScript-fil där vi faktiskt använder jQuery. Låt oss alltså skapa en till .js-fil och ladda in den direkt efter som så:
<script src="main.js"></script>
Låt oss nu faktiskt använda jQuery till att göra någonting. Vi fortsätter alltså skriva kod i filen main.js
. Följande kod kommer att vänta tills DOM:en (alltså HTML-trädet) har laddats in av webbläsaren, och sedan "poppa" up en alert-ruta. Prova att klistra in koden i main.js
och ladda om din html-sida.
$(function(){
alert("Hello from jQuery");
});
Tada!! Vi har nu använt oss av biblioteket jQuery. Låt oss prova någonting annat. Följande kod identifierar alla länkar (<a>
-taggar) på din html-sida. Inaktiverar deras vanliga funktionalitet, och gör istället så att de klistrar in bilder på en massa fantastiska katter varje gång du klickar. Tänk på att du alltså behöver lägga in länkar i din html-sida för att nedan kod ska ha någon effekt. Annars har vi ju inga länkar att klicka på.
$(function(){
var randomNum = function(){
return Math.floor(Math.random()*101);
}
$('a').click(function(e){
e.preventDefault();
var size = 75 + randomNum(),
x = randomNum() + '%',
y = randomNum() + '%',
$img = $('<img/>');
$img.attr('src', 'http://placekitten.com/'+size+'/'+size);
$img.css({
'position': 'fixed',
'left' : x,
'top' : y
});
$('body').append($img);
});
});
Om du inte själv har valt att implementera den fantastiska jQuery-koden ovan så kan du få en sneak-peak på vilken underbar värld som väntar genom att prova exemplet nedan!
Svårare än så är det inte! Så, när du använder jQuery behöver du helt enkelt komma ihåg tre saker.
$(document).ready(function(){ /* din kod här */ });
.Sedan är det bara att tuta och köra!
Ett kortare sätt att skriva..
$(document).ready(function(){
/* din kod här */
});
...är att skriva så här...
$(function(){
// din kod här...
});
Båda sätten ger samma resultat.
I vår första djupdykning in i jQuery pratade vi kort om att det är viktigt att vänta tills hela DOM:en är inladdad innan vi börjar exekvera vår jQuery-beroende kod. I det här kapitlet kollar vi närmare på hur just .ready()
-funktionen fungerar. Detta kapitel hjälper dig att skapa en djupare förståelse för både jQuery såväl som JavaScript.
Denna mystiska ovan nämnda metod vid namn .ready()
är en metod vi använder för att kunna vänta med att exekvera jQuery-kod tills webbläsaren har hunnit ladda in ("ned") hela sidan. Låt oss först se till ett exempel och sen diskutera det:
$(document).ready(function(){
alert("Hello world from jQuery");
});
Ovan exempel använder jQuery för att avvakta tills webbläsaren har laddat in hela sidan. Sedan använder vi helt vanlig JavaScript för att poppa up en alert-ruta med meddelandet "Hello world...". Låt oss dissekera ovan kod, rad för rad och del för del, för att faktiskt förstå vad det är som händer.
Först ut är detta mystiska dollartecken. Förklaringen till vad detta är, är egentligen ganska enkel. Förklaringen till varför vi använder tecknet inte lika. Men låt oss först diskutera vad det är. $
-tecknet är egentligen ingenting annat än syntaktiskt socker. Alltså ett annat sätt att skriva någonting. Detta någonting är jQuery's huvudmetod med samma namn, alltså jQuery
. Så, när vi med andra ord säger $(document)
är det exakt samma sak som när vi säger jQuery(document)
. Det förstnämnda är helt enkelt bara ett kortare sätt att uttrycka det på för att vi som programmerare ska slippa skriva så mycket.
document
är helt enkelt en variabel. Men notera här alltså att vi säger att document är en variabel, vi säger inte att det är ett keyword. Varför är det viktigt att tänka på? Jo, var
är ett keyword, en language construct som alltid finns i JavaScript oavsett vart vi kör språket. Variabeln window är däremot ett koncept som är uppfunnit sonika för browsers. Om vi kör en JavaScript-interpretator i en terminal (alltså inte en browser) så existerar inte variabeln document
. I browsern däremot, för att överhuvudtaget kunna manipulera en webbsida, behöver vi något sätt att genom javascript komma åt noderna (elementen) i vårt HTML-dokument. Således är document
, löst uttryckt, vår "entry-point" in till noderna i html-dokumentet. I variabeln document
hittar vi elementet <html>
och som barn till det elementet hittar vi förstås resten av noderna. För att sammanfatta så är alltså document
den variabel som innehåller hela vårt html-dokument och således den variabel vi behöver interagera med för att manipulera vårt dokument.
Nu vet vi alltså att $()
är en korthandssyntax, och således samma sak som att anropa jQuery()
. Men vad gör då denna metod? Jo, metoden returnerar ett jQuery-objekt som innehåller det vi har skickat in till den. Vi kan alltså se det som att jQuery dekorerar det vi har skickat in med alla dessa fantastiska jQuery-metoder. Låt oss exemplifiera.
// Dekorerar alla länkar med jquery
// Sparar referensen i variabeln link
var link = $('a');
Vi använder alltså denna "jquery-dekorerade version" av ett html-element för att anropa de fantastiska metoder jquery utökar våra element med. Låt oss exemplifiera.
// Fade:a ut alla länkar
$('a').fadeOut();
// Samma sak i två steg
var link = $('a');
link.fadeOut();
Det kan snabbt bli virrigt gällande vilka variabler som är jquery-dekorerade och vilka som inte är det. Konventionen brukar således vara döpa sina variabler med ett initial $
-tecken. Som så:
// Konventionen är att döpa jquery variabler med ett intialt $-tecken
var $link = $('a');
För att återgå till det här med $(document)
. Vad betyder det då alltså att skicka in dokumentet till jquerys dekorationsmetod? Jo att vi nu kan anropa alla de fantastiska metoderna jquery erbjuder på vår rotnod. Det vill säga hela dokumentet. Att dekorera dokumentet med jquery har egentligen exakt samma effekt som att dekorera ett enskilt element (t.ex. en länk).
Precis som rubriken säger så använder vi metoden för att helt enkelt veta när dokumentet (DOM:en) är färdigladdat. Tänk efter. Eftersom vi använder jQuery-metoden för att dekorera element i vårt dokument. Så är det viktigt att alla element är "konstruerade" innan vi försöker komma åt dem. Hur ska vi kunna hitta alla länkar om vi inte är säker på att webbläsaren har hunnit läsa in alla länkar?
Vi kan använda .ready()-metoden, men vi kan också använda .load()-metoden. Den första nöjer sig med att DOM:en (vilket kan ses som dokumentets struktur) har laddat. Den senare väntar på att alla resurser såsom bilder o.s.v. har laddat.
Så vad ska vi skicka som argument till denna funktion? Jo, en till funktion! Va? Vääänta nu. En funktion som tar en funktion som argument? Det låter ju helknäppt. Men det är faktiskt inte så knäppt. Faktum är att mycket kod vi skriver i JavaScript går ut på att just skicka funktioner till funktioner. För att kunna skapa "callbacks". Den funktion vi skickar till .ready()
-funktionen kan ses som ett callback som körs när dokumentet har laddat.
Tänk dig.. vi anropar jquery ready-metoden, och säger "Hej, nu vill jag att du säger till mig när sidans DOM har laddat klart". Sedan skickar vi in en funktion till ready-metoden och då är det som att vi säger "..och när sidans DOM har laddat klart, då vill jag att du utför allt som står i den här andra funktionen". Inga konstigheter!
Så, låt oss repetera den kod vi pratar om i helhet.
$(document).ready(function(){
alert("Hello world from jQuery");
});
Låt oss beskriva koden i ord, rad för rad.
jQuery
-objekt av innehållet i variabeln document
. Anropa sedan funktionen ready()
på det jquery-dekorerade dokumentet. Skicka en anonym funktion som argument.alert()
, med strängen "Hello world.." som argument, och kommer således att visa göra så att alert-ruta "poppar upp" med texten "Hello world..".}
. Stäng sedan funktionsanropet med )
. Terminera raden med ;
.I JavaScript-kapitlet använde vi rå JavaScript för att hämta och manipulera objekt i DOM:en. Vi har alltså direkt interagerat med DOM:ens API. Eftersom det lätt blir frustrerande att t.ex. skriva document.getElementsByTagName
gång på gång så finns det ramverk som kan hjälpa oss. Därför ska vi nu istället göra detta med jQuery, och därför behöver vi prata om det mest grundläggande objektet i jQuery, själva jQuery-objektet. Således även reflektera över skillnaden mellan att arbeta med jQuery-objekt och HTMLElement
-objekt.
När vi hämtar element ifrån DOM:en m.h.a JavaScript får vi alltså tillbaka just DOM-element. Men när vi arbetar emot "råa" DOM-element blir många saker, som tidigare nämnt, ofta "tjatiga" och onödigt komplicerade. Beakta nedan exempel:
<body>
document.getElementsByTagName('body');
$('body')[0];
Uppenbart kräver jQuery-exempelet ovan att vi skriver markant mindre kod. Onödigt, kanske du tänker nu. Visst, vinsten i ovan exempel är inte massiv. Men låt oss istället se till ett mer avancerat exempel. Anta att vi skulle vilja ta bort alla <p>
-element ur ett dokument. Nedan ser du hur vi först skulle kunna lösa det med JavaScript och sedan med jQuery.
<p>
-elementvar all = document.getElementsByTagName('p');
for(i=all.length-1; i>=0; i--){
all[i].parentNode.removeChild(all[i]);
}
$('p').remove();
Förhoppningsvis ser du nu styrkan! jQuery försökt att ta hand om den del vanligt återkommande problem och således försökt erbjuda oss utvecklare lite mindre huvudvärk. Nu kanske du redan fått huvudvärk flera gånger och känner den komma igen av att du behöver lära dig någonting nytt — men lugn! När du väl fått kläm på syntaxen kommer jQuery hjälpa dig ofantligt, och förhoppningsvis kommer du vara arg på att vi försökte lära dig JavaScript först.
Förutom att jQuery gör det lättare för oss att utföra omständiga DOM-operationer gör den också att vi får mer webbläsarkompatibel kod, eftersom jQuery bygger på mycket "best practices".
jQuery-objektet fungerar helt enkelt så att vi "wrappar" (omsluter/dekorerar) ett helt vanligt HTMLElement
med jQuery. När vi har gjort det har vi helt plötsligt ett objekt som dels innehåller en referens till detta helt vanliga HTMLElement
men även en massa smidiga hjälpmetoder som hjälper oss att interagera med detta HTMLElement
.
jQuery-objekt kan göra mycket fler saker än att hämta och manipulera element men nu ska vi fokusera på att det kan göra just det sistnämnda — alltså hämta element, och sedan manipulera dem. Låt oss se till det tidigare diskuterade exempelet som tog bort alla <p>
-element ur ett dokument.
$('p').remove();
Det viktigaste vi måste förstå med ovan exempel, är att jQuery opererar på kollektioner av element och inte på enstaka element. Nu är det ju förstås så att det är fullt möjligt att vår kollektion endast innehåller ett element, men det är ändock en kollektion. För att dra en parallell så kan du tänka på hur JavaScript-metoden document.getElementsByTagName()
fungerar. Namnet på metoden är pluraliserad eftersom även den returnerar en kollektion av HTMLElement
. Detta alltså till skillnad ifrån document.getElementById
som i alla fall returnerar max ett HTMLElement
.
jQuery opererar på kollektioner av element.
Men vad betyder då detta i praktiken? Jo, det betyder alltså att jQuery inte bryr sig om huruvida vi hittade ett eller flera element, när vi i ovan exempel anropar metoden .remove()
så tar den alltså bort alla element i hela den kollektion den hade hittat. Låt oss se till ytterligare ett exempel för att illustrera detta:
<p> hello </p>
<p> world </p>
$('p')[0].innerHTML; // => "hello"
$('p')[1].innerHTML; // => "world"
$('p').text(); // => "hello world"
Vi kan hantera jQuery-objektet som en array. På rad 1 och 2 i ovan exempel gör vi just det. När vi använder klammerparantesnotationen ([i]
) och ger ett index, så hämtar vi alltså det HTMLElement
som gömmer sig under det indexet. När vi sedan anropar innerHTML
får vi ut den text som finns i just det HTMLElementet
. När vi däremot på rad 3 anropar text()
-metoden (som alltså är en jQuery-specifik metod) så opererar vi alltså på kollektionen av alla träffade element. Anledningen till att vi inte använder text()
-metoden på rad 1 och 2 är alltså för att den metoden är specifik för jQuery. Eftersom vi på rad 1 och 2 redan plockat ut ett HTMLElement
ur jQuery-objektet så har vi alltså inte längre tillgång till jQuery's metoder. Vice versa gäller alltså på rad 3. Eftersom vi inte plockat ut något HTMLElement
ur jQuery-kollektionen kan vi inte använda "vanliga" HTMLElement
-metoder (eftersom det är ett jQuery object
) utan måste istället använda jQuery-specifika metoder. Vill vi använda de vanliga HTMLElement
-metoderna behöver vi plocka ut ett specifikt element ur kollektionen.
Låt oss nu prata om vi hämtar element-kollektioner m.h.a. jQuery. Att hämta element med jQuery är egentligen mycket enkelt. Vi skriver helt enkelt $(x)
där x
ersätts med vilken css-selektor som helst. Med andra ord kan vi återanvända alla våra css-kunskaper nu när vi dyker in i jQuery.
Utan att snöa ner oss i svårare selektorer så kommer du förhoppningsvis ihåg de enklaste. De vanligaste css-selektorerna är:
x
#x
.x
Om du behöver läsa på om css-selektorer, läs mer i css-kapitlet!
Ok, men nu var det ett väldigt generellt prat här. Låt oss istället se till några exempel där vi faktiskt använder dessa selektorer.
<div> Anta </div>
<div> att vi har </div>
<div> ett par div:ar </div>
// Då väljer vi alla så här:
var allaDivar = $('div');
Ovan hämtade vi alltså element baserat på deras tagName
. Låt oss nu istället hämta alla element, oavsett typ, som har en viss class
.
<div class="dog"> Woof! </div>
<div class="cat"> Mjau! </div>
<div class="dog"> Bark! </div>
// Använd klass-selektorn för att hämta alla element med en viss klass
var dogs = $(".dog");
// För att illustrara att det verkligen fungerar
dogs.text(); // => "Woof! Bark!"
Busenkelt! Du börjar se mönstret? Låt oss nu istället prova en sista gång genom att hämta alla element med ett visst ID
.
<p id="super-woman">Kryptonite!</p>
<p id="cat-woman">In your house eating your cat food!</p>
<p id="modesty-blaise">Secret agent</p>
// Vi använder css-selektorn för ID
var hero = $('#super-woman');
// Och har nu valt rätt element
hero.text(); // => "Kryptonite!"
När vi väl lärt oss att välja element m.h.a. jQuery är det busenkelt att börja modifiera dessa. jQuery-objektet exponerar oss en mängd behändiga funktioner och egenskaper vi kan använda för att modifiera kollektionen av element som gömmer sig under objektet. I detta kapitel kommer vi att prata om några av de vanligaste.
Metoden html()
kan ses som jQuery's motsvarighet till JavaScript's innerHTML
. Lagom straight forward så hämtar den eller sätter helt enkelt elements innerHTML
.
Om vi har följande html..
<p>
<span>En katt</span>
</p>
<p>satt i en hatt</p>
Kan vi läsa första paragrafen genom att säga...
$('p').html(); // => "<span>En katt</span>"
Men vi kan även modifiera den genom att säga...
$('p').html('En hund') // => Ändrar ALLA p-elements inre html till "En hund"
Vilket förändrar vår html så att vi nu har...
<p>En hund</p>
<p>En hund</p>
Notera alltså att vi "blev av" med <span>
-elementet. Samt att båda <p>
-elementens inre html rensades.
Men vänta nu, vad hände nu? Om du var uppmärksam så märkte du att när vi använde metoden html()
för att läsa ifrån en element-kollektion så fick vi endast värdet ifrån det första elementet i kollektionen. Men när vi däremot använda html()
-funktionen för att skriva så opererade vi på alla element i objekt-kollektionen. Vad händer nu egentligen?
Alla metoder på jQuery-objektet arbetar inte alltid på alla element i kollektionen.
Oavsett vilket jQuery-metod vi använder är det alltså alltid viktigt att vara medveten om huruvida den arbetar på hela kollektionen eller ett specifikt element. En tumregel vi kan använda är att många av metoderna läser ifrån det första elementet i kollektionen och skriver till alla i kollektionen. Anropar vi alltså html()
för att läsa kommer vi bara att få all inre html för det första elementet, medan om vi använder metoden till att skriva html('something')
kommer vi förändra innerHTML
för alla element i kollektionen.
En bra tumregel är att många jQuery-metoder läser ifrån första elementet i en objekt-kollektion, och skriver till alla.
Metoden text()
påminner mycket om metoden html()
förutom att den fantastiskt nog ger oss innehållet i det första elementet i en element-kollektion utan html. Vi får alltså allt som webbläsaren faktiskt uppfattat som text, utan all störande html. Detta är en mycket användbar metod! Vi kan förstås, precis som med metoden html()
använda den för att även skriva text till alla element i en kollektion. Låt oss se till några exempel:
Om vi har följande html..
<p>
En liten men
<span>mästerlig</span>
katt, satt en
<span>solig dag </span>
och beundrade en hatt.
</p>
Och sedan hämtar <p>
-elementets text så här...
$('p').text(); // Hämtar all text i elementet fritt ifrån alla html-element
Så får vi...
"En liten men mästerlig katt, satt en solig dag och beundrade en hatt."
Vilket ju är fantastiskt, för om vi istället hade använt metoden html()
eller JavaScript's egna innerHTML
hade vi istället fått...
"En liten men <span>mästerlig</span> katt, satt en <span>solig dag </span> och beundrade en hatt."
Vilket förstås skulle vara mycket jobbigt att arbeta med om det skulle vara så att vi faktiskt bara vill åt texten, oavsett dess inre elementstruktur.
Låt oss nu prata om två metoder som till synes verkar likna varandra. Metoden attr()
hämtar och sätter attribut
(attributes
), medan metoden prop
hämtar och sätter egenskaper
(properties
). På samma sätt som text()
och html()
hämtar båda metoderna ifrån det första elementet, och skriver till alla i en kollektion. Men för att förstå skillnaderna mellan de två metoderna attr()
och prop()
behöver vi först förstå skillnaden mellan attribut
och egenskaper
.
När vi pratar om attribut så pratar vi attribut på html-element. Exempel på attribut är alltså id
, class
, name
och name
, för att nämna några. Med andra ord, attribut är sådant som vi "tilldelar" ett element i vår html. Självklart kan vi både hämta och sätta attribut via JavaScript, men för att förstå skillnaden mellan attribut och egenskaper är det lättast att tänka på attribut som det som vi sätter i html. Nedan följer ett exempel på ett html-element med ett antal attribut.
Om vi har ett element med ett antal attribut...
<p class="quote" id="welcome">
What a wonderful world
</p>
Så kan vi förstås läsa attributen med JavaScript som så...
document.getElementsByTagName('p')[0].getAttribute('class');
// => "quote"
document.getElementsByTagName('p')[0].getAttribute('id');
// => "welcome"
Och förstås med jQuery som så...
$('p').attr('class'); // => "quote"
$('p').attr('id'); // => "welcome"
Egenskaper å andra sidan, refererar till egenskaper på ett HTMLElement
-objekt när den hanteras i JavaScript. Med andra ord (löst uttryckt) publika instansvariabler på JavaScript-representationen av samma element. Vad skulle då egenskaper kunna vara för någonting?
Anta att vi har ett <p>
-element på sidan..
var p = document.getElementsByTagName('p')[0];
var i = document.getElementsByTagName('input')[0];
p.tagName; // => "P"
i.tagName; // => "INPUT"
Således kan vi förstås göra samma sak genom jQuery...
$('p').prop('tagName'); // => "P"
$('input').prop('tagName'); // => "INPUT"
Av historiska skäl och förändringar över tid så beteer sig ovan metoder, i tidigare versioner av jQuery, tyvärr inte alltid såsom man kan tänka sig. Du kan läsa mer om det i jQuery-dokumentationen förprop ellerattr eller helt enkelt på följande tråd på StackOverflow.
En av styrkorna med jQuery är att vi kan binda event-lyssnare till flera element samtidigt. Jämför detta med att behöva iterera över en array med element, plocka ut ett för ett och applicera samma lyssnare. Markant enklare att göra det på alla samtidigt!
jQuery har några generella metoder för att applicera event-lyssnare (som med fördel kan jämföras med JavaScripts addEventListener()
). Men jQuery har även några specifika för vanligt förekommande events som click
och hover
. Låt oss kolla på ett exempel, och kom ihåg att jQuery arbetar på kollektioner av element, alltså flera stycken samtidigt.
<div>Oh no no no...</div>
<div>Please do not click me, oh no no, please...</div>
<div>I don't want to fade, please...</div>
$('div').click(function(){
$(this).fadeOut();
});
Om du försöker bygga ovan exempel med ren JavaScript kommer du snabbt märka varför det gör livet så hiskelens mycket enklare att arbeta med ett JavaScript-bibliotek såsom jQuery.
Som du kanske märkte i senaste exemplet, så använde vi oss av följande syntax: $(this)
. Mystiskt kan tyckas, men mycket smidigt faktiskt. När vi kör jQuery-funktionen ($()
) och ger den en css-selektor så kommer jQuery att välja alla element som matchar den selektorn. Men vi kan faktiskt också ge jQuery-metoden ett annat HTMLElement
. T.ex. så här:
// First grab the first <p>-element using regular JavaScript
var paragraph = document.getElementsByTagName('p')[0];
// Then wrap it as a jQuery object
var $paragraph = $(paragrah);
// And now we can do all the regular cool jQuery stuff
$paragraph.fadeOut();
Men vänta nu, i det tidigare diskuterade exemplet så skickade vi ju faktiskt inte ett html-element, utan vi skickade nyckelordet this
. Löst uttryckt så refererar nyckelordet this
i JavaScript alltid till den nuvarande kontexten. Det intressanta är alltså att om vi skickar den nuvarande kontexten till jQuery-metoden så kommer den försöka skapa ett jQuery-objekt av det. Om vi då befinner oss i en event handler
(t.ex. den kod som exekveras när en knapp klickas på) så kommer det element som avfyrade elementet bli det objekt som hamnar i jQuery-objektet. Låt oss se till ett exempel.
Om vi har följande html...
<p>Hello world</p>
Kan vi göra följande i jQuery...
// First define a click function, that we want to
// execute whenever something is clicked.
var onClick = function(){
var sender = $(this); // Creates a jQuery object of the element that was clicked
alert(sender.text()); // Sends a message with the contents of the clicked element
}
// Then we need to attach our click function to the <p>-element,
// so that it will be fired when the element is clicked.
$('p').click(onClick);
Detta kommer nu alltså att ge oss följande...
Hello world
Detta går självklart även att göra med helt vanlig JavaScript, såsom du förhoppningsvis kommer ihåg ifrån JavaScript-kapitlet. Att det är enklare att göra i jQuery är ju förstås ett plus! Men det viktiga att komma ihåg är här alltså att detta gör det möjligt för oss att skriva mycket mer generella event handlers
(alltså metoden som körs när ett event avfyras). Om vi t.ex. har tre knappar som alla gör väldigt liknande saker, så finns det alltså ingen anledning att binda separata event-hanterare (event handler
) för varje knapp. Istället generaliserar vi koden i event-hanteraren och binder alla events till samma hanterare. Sött som sylt!
Kommer snart...
Kommer snart...
Kommer snart...
Kommer snart...
Kommer snart...
Kommer snart...
De flesta webbsidor vi idag besöker bygger inte bara på teknikerna html
, javascript
och css
. De flesta webbsidor vi besöker idag byggs även upp med hjälp av ett s.k. server-side
-språk.
Vad är då ett server-side-språk? Tänk så här. När vår webbläsare ber om en sida så skickar den ett request
till en server
som svarar med ett response
. Detta response
innehåller ju alltså den html
vår webbläsare kommer att rendera. Om vi inte har ett server-side-språk så kommer denna html
-fil alltid att vara densamma. I detta fall skulle vi kunna säga att den webbserver
som hanterar vår webbläsares request
agerar som en statisk filserver. Den serverar oss helt enkelt filer. Vi ber om en fil. Och vi får den tillbaka. Denna typ av webbsida är vad som generellt kallas för statiska webbsidor.
Med server-side-språk så kan vi helt enkelt skapa dynamiska webbsidor. Den huvudsakliga skillnaden ligger alltså just i termerna dynamisk vs. statisk. En statisk webbserver kan ses som en filserver, medan en dynamisk webbserver snarare kan ses som en applikationsserver. Jämför t.ex. din egen hårddisk med en webbshop på nätet. Om du öppnar en .html
-fil ifrån din hårddisk kommer den alltid se likadan ut. Inte förrän du förrändrar filen kommer webbsidan att se annorlunda ut. Webbshopen på nätet däremot kanske har en varukorg. Varende gång du besöker varukorgen kan det hända att du (t.ex.) hamnar på url
:en www.example.com/checkout.php
. Trots att det alltid är samma sida vi öppnar så innehåller inte alltid den renderade sidan samma sak. Om jag öppnar sidan med min användare visas min shoppingvagn, och om du öppnar den visas din. Men filen ser fortfarande exakt likadan ut i båda fallen.
I essens så handlar server-side-språk alltså om språk som i slutändan genererar (renderar) html
. Beroende på variabel information såsom databaser, uträkningar, sessioner, http headers
o.s.v. Men för att dessa språk ska kunna generera sin output (alltså html
) så krävs det att vi har en server som kan "förstå" det språket. I fallet av php
så kan vi t.ex. använda webbservern Apache. Om vi sedan installerar php så kan vi konfigurera Apache
så att det använder sig av php
, och voíla, så har vi en server som kan tolka och rendera php
. Oroa dig inte om detta låter snurrigt, vi kommer titta närmare på själva installationen alldeles strax.
Noteras bör, att det självklart är så att de stora giganterna till webbsidor där ute i världen inte bygger på endast ett enda server-side-språk och en enda server. Men detta är ett perfekt tillfälle att prata om det välkända idiomet optimize later. Idiomet säger — lös problemet först — och när du löst det — oroa dig då över optimering. Just nu menar vi alltså att verkligheten är mer komplex, men vi behöver inte bry oss om det förrän vi kommer till den situation då vi förstår varför vi behöver bry oss om det. Men vi vill ändå poängtera, eftersom det är viktigt att inte vara naiv :)
Låt oss innan vi går vidare iterera på poängen att php
självklart inte är det enda server-side-språket. Det finns otaliga andra —Ruby, Python, ASP.NET, o.s.v. Här på htmlhunden så har vi valt att använda php
som språk för att exemplifiera och diskutera. Men försök komma ihåg att server-side-språk i grund och botten har många likheter och gör ungefär samma sak. Så om du väl har lärt dig ett, så kommer du ha lättare att komma "up to speed" med ett annat.
Anledningen till att vi har valt just php
är att det är ett språk som är dynamiskt typat och inte syntantiskt fullkomligt olika ifrån språk som Java och C#. De två sistnämda är ju förstås statiskt typade språk och alltså inte dynamiskt typade språk som php
. Men om du kommer ifrån ett språk såsom Java
eller C#
så kommer du iallafall känna igen många saker såsom måsvingar, parantesanvändning, nyckelord såsom static
, final
, public
, private
och så vidare. Detta kan tyckas som trivialiteter men faktum är att språk som t.ex. Ruby
bl.a. känns markant annorlunda p.g.a. andra sätt att hantera dessa nämnda trivialiteter. Så därför har vi valt att lära ut och diskutera php
. Lita på oss, du kommer inte ha några problem att lära dig ett annat språk på egen hand senare! :)
En tidigare (och faktiskt fortfarande) populär akronym var termen LAMP. Eller — LAMP-stacken som den ofta refereras till som. Denna akronym står (bl.a.) för Linux, Apache, MySQL, och PHP. Vi säger "bl.a." eftersom det finns versioner av denna stack där php
t.ex. byts ut emot perl
o.s.v. Denna stack har blivit mycket populär för webbutveckling.
Apache
används som webbserver, php
som server-side-språk, MySQL
som databas, och slutligen Linux
som operativsystem där alla nämnda körs.
Två andra "stackar" som är adaptioner av den tidigare nämnda LAMP
-stacken är MAMP och WAMP. I förstnämnda så står M:et för Macintosh, och i sistnämnda så står W:et för Windows. Det är alltså samma mjukvarustackar som LAMP fast med operativsystemet Linux utbytt emot någon av dessa andra två.
För nuvarande ger vi inga steg-för-steg-instruktioner gällande hur du installerar MAMP, WAMP eller LAMP. Men om du sitter på antingen Macintosh eller Windows så är det en otroligt enkel match. För Macintosh finns nämligen det MAMP som app, och för Windows finns WAMP som program. Dessa är alltså hela *AMP-stacken i ett enda program. Alltså, ett mycket smidigt sätt att komma igång.
Om du ändå är fundersam över hur du ska gå tillväga så föreslår vi att du tar en titt på några tutorials på YouTube.
PHP
är ett av de där språken som de flesta kan känna igen sig lite i. Nästan oavsett vilket/vilka språk du kommer ifrån innan. Eftersom vi förväntar oss att du redan har en viss grundläggande kunskap om objektorienterad programmering så kommer vi alltså i huvudsak fokusera på syntax och språkkonstruktorer. Vi kommer således inte gå in på djupet och diskutera varför. Poängen med detta kapitel är att ge dig tillräckligt mycket kunskap för att kunna läsa identifiera olika delar av ett program skrivet i PHP
.
För att skriva ut information till användaren genom att använda någon av de två language constructs:en echo
eller print
. Skillnaderna mellan dessa är subtila, och vet du inte varför du väljer den ena över den andra var åtminstone konsistent i ditt användande.
<?php
echo "Hello worldizzle";
?>
Hello woldizzle
Självklart kan vi ju inte bara ge echo
strängar. Vi kan ge den vilket uttryck som helst som evaluerar till en sträng, eller implicit kan omvandlas till en sträng. Det senare är sant för nedan uyttryck:
<?php
echo 100 + 20 + 33;
// Skriver ut 153
?>
echo
tar alltså antingen ett literal-värde, ett uttryck eller en variabel.
html
och php
Eftersom vi använder php
för att bygga webbsidor så är ju alltså målet att få våra php
-filer att mata ut html
. Detta betyder att vi behöver blanda de två språken. Detta är egentligen enkelt gjort eftersom php
kräver att all php
-kod måste skrivas inom taggarna <?php
och ?>
. Detta betyder att allt som skrivs utanför dessa block, kommer att renderas som rå text, och kan således vara html
. Låt oss se till ett exempel.
php
och html
Mixing
<b>
<?php echo "languages"; ?>
</b>
Mixing languages
Vi kan förstås även vända på steken och låta php
själv echo
:a ut html
. Som så:
Mixing
<?php
echo "<b>languages</b>";
?>
Det finns även en kortnotation som motsvarar notationen <?php .. ?>
. Den notationen saknar ordet "php" och ser helt enkelt ut som så: <? .. ?>
. Dock avråder php-manualen ifrån att använda den korta syntaxen då den är beronde av en konfigurationsinställning för att fungera. Närmare bestämt så måste short_open_tag vara påslaget i konfigurationsfilen php.ini.
Notera att vi i denna guide ibland använder kortnotationen för att spara plats. Och i många exempel skriver vi inte ens ut start och sluttaggarna för php
— återigen, för att spara plats.
Om du har en fil som uteslutande innehåller php
, så är det helt ok att öppna med <?php
i början av filen och sedan strunta att stänga den längst ned. Konstigt nog så är inte det bara någonting som är ok men även någonting som rekommenderas av manualen.
Men, för att sammanfatta så behöver du alltså komma ihåg att all php
-kod måste skrivas inom php
-taggarna. Alltså emellan <?php
och ?>
.
Låt oss nu istället prata om variabler. En variabel deklareras i php
genom att placera ett dollartecken ($
) före ett ord. Ordet blir då vår identifierare för variabeln.
$my_variable_name;
Ett par regler gäller när vi namnger våra variabler.
_
).A-z
, 0-9
, och _
).$hej
och $Hej
är alltså två olika variabler.Men nu har vi ju bara deklarerat en variabel. Vi har inte diskuterat hur man tilldelar till den. Som i de flesta språk tilldelar vi med hjälp av likhetstecknet (=
). Och som i de flesta språk kan vi tilldela en varibel ett literal-värde, en annan variabel, eller evalueringen av ett uttryck. Alla nedan är alltså rimliga tilldelningar.
php
// Assigning literals
$age = 22;
$name = "Snow"
// Assigning variables
$anos = $age;
$nombre = $name;
// Assigning an evaluated expression
$born = $current_year - $age;
I php
kan vi, precis som i de flesta andra språk, förändra ett programs exekveringsbana genom att konditionella selektioner. Låt oss börja med att kika på ett exempel på hur vi skriver en enkel if-else
-selektion i php
.
php
if (3 > 5){
echo "The world has gone mad!";
}else{
echo "Puh.. sanity remains..";
}
Självklart kan vi ju som vanligt "kedja" hur många else if
's vi vill. Såsom nedan:
php
if (timeOfDay() == "morning"){
echo "Good morning.";
}else if(timeOfDay() == "day"){
echo "Good day.";
}else if(timeOfDay == "evening"){
echo "Good evening."
}else{
echo "Good night... sleep well."
}
// The above assumes we have a function called timeOfDay()
// that returns the time of day as a nice string :)
Men eftersom php
även stödjer switch case
-satser så kan vi likväl använda en sådan om vi skulle vilja lösa ovan problem.
php
switch( timeOfDay() ){
"morning":
echo "Good morning.";
break;
"day":
echo "Good day.";
break;
"evening":
echo "Good evening."
break;
default:
echo "Good night... sleep well."
}
När vi använder if-else
konstruktionen så behöver vi ju förstås göra jämförelser. En if
-sats förväntar sig ett boolskt värde. Och eftersom alla värden kan ersättas med uttryck så kan vi ju (precis som i nästan alla programmeringsspråk) ge ett uttryck istället för ett värde. Detta uttryck skulle kunna vara en jämförelse emellan två ting. Och om vi ska göra jämförelser så behöver vi förstås som vanligt jämförelseoperatorer. Även i php
hittar vi då de vanligaste jämförelseoperatorer.
Operator | Namn | Förklaring |
---|---|---|
== | Equality | Sant om $A och $B är exakt ekvivalenta. |
=== | Identical | Sant om $A och $B är exakt ekvivalenta, och de är av samma datatyp. |
!= eller <> | Not equal | Sant om $A och $B inte är lika. |
!== | Not identical | Sant om $A och $B inte är lika, eller om de inte är av samma datatyp. |
< | Less than | Sant om $A är lägre än $B. |
> | Greater than | Sant om $A är högre än $B. |
<= | Less than or equal | Sant om $A är lägre än eller lika låg som $B. |
>= | Greater than or equal | Sant om $A är högre eller lika hög som än $B. |
Ok, låt oss nu prata om iteration. Vi börjar med att kika på den gamla gode while
-loopen. Ett smidigt alternativ om vi vill definera ett villkor, och sedan helt enkelt bara blint loopa tills villkoret uppfylls.
while
-loop i php
$x = 0;
$y = 10;
while($x < $y){
echo $x . ", ";
$x++;
}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
Men om det hade varit det ovanstående problemet vi ville lösa så hade vi förstås lika väl kunnat använda en gammal hederlig for
-loop.
for
-loop i php
for($i=0; $i<10; $i++){="" echo="" $i="" .="" ",="" ";="" }<="" code="">10;>
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
Och när vi ändå talar om for
-loopen, så har ju även php
en implementation av den välanvända foreach
-loopen. Anta att vi har en kollektion av något slag, och att vi sedan vill iterera över hela kollektionen. Låt oss kika på ett exempel där kollektionen består av en array.
foreach
-loop i php
$apples = ["red", "green", "blue"];
foreach($apples as $a){
echo $a . "<br>";
}
red
green
blue
Om du har använt en foreach
-loop i något annat språk så märker du säkert att implementationen i php
känns lite "baklänges". I de flesta språk är loopen konstruerad så att syntaxen säger: foreach(item in collection)
. Men i php
så säger syntaxen: foreach(collection as item)
. Men håll tungan rätt i mun, och tänk baklänges så ska nog allt gå sin väg! :)
Funktioner i php
beter sig väldigt mycket som funktioner i de flesta andra stora språk. Du kan skriva funktioner som inte returnerar någonting eller funktioner som returnerar någonting. Om du kommer ifrån ett språk som C#
eller Java
så gäller det ju dock förstås att komma ihåg att php
är ett dynamiskt språk. I php
behöver vi alltså inte deklarera huruvida en funktion returnerar någonting eller ej. Inte heller behöver vi berätta vad den returnerar om den gör det. Låt oss se till några exempel.
php
function add($x, $y){
return $x + $y;
}
function show_points($person, $points){
echo $person . " has " . $points . " points<br>";
}
$john = add(2, 5);
$jane = add(10, 3);
show_points("John", $john);
show_points("Jane", $jane);
John has 7 points
Jane has 13 points
Om du studerar ovan exempel lite närmare så märker du att vad vi nämnde innan om dynamiska språk onekligen är sant. Den första metoden tar berättar inte att den ska returnera någonting, på något annat sätt än att den helt enkelt returnerar det den vill returnera. Inte heller berättar den att den vill returnera en siffra.
En viktig sak att förstå, är att inte heller argumenten är statiskt typade. Funktionen (vi pratar nu om add($x,$y)
i ovan exempel) antar helt sonika att den kommer att få två siffror. Eller om vi ska vara riktigt strikta i våra uttalanden så antar metoden att den kommer att anropas med två ting som argument, där båda dessa ting går att addera genom +
-operatorn. Det antagande om att någonting har ett visst beteende (plusoperatorn i detta fall) är det som brukar benämnas Duck typing. Men vi kommer prata om det mer när vi återkommer till att tala om styrkor och risker med dynamiskt typade språk.
En intressant idé som finns implementerad i många språk är idéen om default-värden för funktionsargument. Valbara funktionsargument. Detta betyder alltså att vi kan anropa en metod utan att ange alla argument. Och att vi i funktionsdeklarationen definerar vad standardvärdet för ett visst argument är. Det värdet används då istället om den som anropar funktionen skulle ignorera att ange ett värde för det argumentet. Låt oss se till ett par exempel för att göra det lite tydligare.
php
Om vi deklarerar en funktion med valfria argument...
function get_ticket($name, $pickup=false, $discount=0){
$price = 100 - $discount;
if($pickup){
$price += 25;
$pickup_message = "Pickup included.";
}else{
$pickup_message = "No pickup.";
}
return $name . ": " . $price. " kr. " . $pickup_message;
}
...Så kan vi sedan anropa den utan att ange värden för de valfria argumenten. Och de fördefinerade värdena för de avsaknade argumenten kommer istället att användas.
get_ticket("John", true);
//=> "John: 125kr. Pickup included."
get_ticket("Jane", false, 100);
//=> "Jane: 0kr. No pickup."
get_ticket("Carl");
//=> "Carl: 125kr. No pickup."
Något att tänka på är att vi i ovan exempel inte kan ange ett värde för det sista argumentet utan att ange ett värde för det andra. Med andra ord: om vi vill ange ett värde för ett valfri argument, så måste vi ange värden för alla argument före. Med andra ord gäller det att välja sin argumentordning noga.
Allt går fel. Ingenting är perfekt. Och ibland vet man exakt vad man vill programmera men hur man än gör så verkar det som om man inte kommer dit. Om du kommer ifrån ett statiskt typat språk, eller har arbetat med kodeditorer såsom Visual Studio så är risken överhängande stor att du kommer hitta dig i en situation där du inser att du behöver kämpa mycket hårdare för att hitta fel i ett språk som php
. Dynamisk typning är ett tvåeggaat svärd. En välsignelse såväl som en förbannelse. I det här kapitlet börjar vi med att prata om felsökningsstrategier på en övergripande nivå. Sedan ger vi några snabba, konkreta tips för felsökning av php
-kod.
All felsökning som inte är strategisk är idiotisk. Inga undantag. Om du någon gång försöker övertyga någon om någonting annat bör du läsa en bok om programmering och tänka om. För att vara värdig att kalla dig själv för mjukvaruutvecklare så behöver du vara strategisk. Tycker du att vi är hårda? Tänk efter. Bygger arkitekter hus på måfå? Ingenjörer broar genom att chansa? När en TV-reparatör ska laga en TV. Tror du hen bankar på den med en hammare tills den fungerar? Nej? Behöver vi ens ge fler exempel? Självklart är svaret nej. Professionella ämbetsmän bankar inte på måfå. Visst kanske vi bankar på TV-apparaten ibland. Men om vi gör det så vet vi varför vi gör det.
Så, vad innebär det att strategiskt felsöka? Det finns förstås många olika sätt att se på det. Men vi föreslår följande övervy.
Du kanske tycker att detta låter som självklarheter. Men vi lovar dig. Det är inte självklart inte självklarheter. Vi har jobbat med tillräckligt mycket pedagogisk verksamhet för att lova dig att människor gång på gång efter gång på gång kan sitta i timmar och banka på samma TV-apparat och ändå inte acceptera att de behöver angripa problemet ifrån en mer strategisk vinkel.
Vad menar vi då med ovan lista. Låt oss prata om de en efter en.
Detta kan tyckas det mest självklara av alla steg. Men glöm inte vad vi sa. Vi har sett det här så många gånger att det är lika bra att på en gång acceptera att det inte är en självklarhet. Innan vi kan börja angripa problemet måste vi identifiera det. Vad är det som inte fungerar?
För att kunna svara på frågan om vad som är fel. Behöver vi först förstå vad vi egentligen menar när vi pratar om mjukvarufel. Vad innebär det att någonting "inte fungerar". Återigen finns det säkert en miljon definitioner men vi ser det så här. Om vi förväntar oss A, men upplever B — så har ett fel uppstått. Denna definition är en viktig del av en realisation. Realisationen att det inte bara finns en lösning. Det finns också ett problem. Faktum är att det inte kan finnas en lösning utan ett problem. Varför? Eftersom en lösning är en lösning på ett problem. En lösning utan ett problem är ingen lösning. Så innan vi ens kan börja diskutera en lösning behöver vi definiera vårt problem.
Det är mycket vanligt att vi blir blinda inför detta faktum när vi suttit och programmerat i timmar. Vi "hade sönder" någonting två timmar tidigare, och sen dess har ingenting fungerat som det ska. "Allt är trasigt". Detta är inte konstigt. Det är lätt att bli uppgiven när vi inte går uppleva uppgångar på för länge. Det är jobbigt att bli konstant nedslagen. Därför är det superviktig att vara strategisk istället för naiv. Istället för att fortsätta slåss emot den imaginära fienden — "ingenting fungerar" — måste vi ta ett steg tillbaka. Reflektera. Kontemplera. Och välja strategi. Börja med att definiera problemet. Vad är det som inte fungerar? Hur tycker jag att det borde fungera? Förhoppningsvis kommer du då upptäcka att det som "inte fungerar" inte alls består av ett problem utan snarare många.
Acceptera således aldrig när någon uppgivet säger: "det fungerar inte". Det enda riktiga svaret på kommentaren "det fungerar inte" är — "vad fungerar inte?".
Utkomsten av detta steg är alltså två saker. Att vi har identifierat hur vi (1) skulle vilja att någonting beteer sig. Samt att vi har identifierat hur (2) detta ting faktiskt beteer sig nu.
När vi väl har identifierat ett problem så behöver vi förstås lösa det. Det är oftast i denna ände som folk börjar. Men om vi börjar i denna ände kan vi omöjligen veta när vi har löst problemet. Eftersom vi inte definierat hur vi skulle önska att programmet (eller det vi nu felsöker) skulle betee sig.
Att isolera felet handlar inte om att lösa problemet. Att isolera felet handlar om att hitta det. Innan vi kan lösa någonting så behöver vi hitta vad som orsakar felet. Vi behöver inte alltid förstå varför det händer. Men vi behöver absolut hitta var det händer.
Vi ska nu prata om några olika högnivåstrategier vi kan använda för att isolera fel.
Anta alltid att strömmen inte är på. Detta är förvånansvärt effektivt. Oftare än sällan är fel bara en oturlig effekt av slarvighet. Har vi t.ex. glömt att slå på servern? Databasen? Bytte vi lösenord igår och glömde bort att ändra det idag? Tror vi att vi redigerar en fil men egentligen redigerar vi en annan.
Alla dessa exempel låter som dumheter. Men jag slåss emot dessa dagligen. Vi är människor och människor gör klantiga misstag. Det viktigaste är att vi kliver ned ifrån våra höga hästar och aldrig anta att vi gjort alla dessa självklara grejer rätt.
Börja alltid med att snabbt gå igenom alla självklara grejer som skulle kunna orsaka felet. Om du inte har bevisat att det inte är någon av dessa självklara saker som orsakat felet så vet du fortfarande inte att det inte är någon av dem som faktiskt orskar det.
Ett bra sätt att testa huruvida någon av dessa grundläggande antaganden är orsaken till felet är att göra en snabb "sanity check" för varje antagande. Tänk så här — "om servern fungerar så borde jag kunna göra X". Eller — "om databasen är påslagen så borde jag kunna göra Y". Välj scenarion som är så triviala som möjligt och kräver så lite arbete ifrån dig som möjligt. Om jag t.ex. skulle vilja avgöra ifall den fil jag arbetar med faktiskt processas av php
så skulle jag klippa ut allt i filen jag arbetar med och sedan skriva echo "hello";
. Om jag inte får meddelandet "hello" när jag sedan kör filen så vet jag med säkerhet att det är det som är felet (eller en del av). Om jag faktiskt fick "hello" så kan jag utesluta att det var det som var felet. Istället fortsätter jag till nästa triviala antagande.
Kom ihåg att alltid verifiera att en applikations "boundries" är fungerande. Vi skriver sällan mjukvara i isolation. Verifiera att vi gör korrekta antaganden om tredjepartsbibliotek.
Det säkraste sättet att hitta felet är oftast att följa exekvering. Ifrån absolut början till absolut slut. Utan undantag. Tyvärr blir den ofta också ofta den mest kostsamma eftersom vi måste gå igenom så ruskigt mycket kod. Självklart är det dock mycket bättre än att banka på måfå. Det är mycket mer troligt att vi faktiskt kommer att hitta felet med denna metod.
Börja i applikations "entry-point". Den första filen, den första metoden, den första raden. Läs koden och följ flödet. När du anropar en metod. Läs varje rad i den metoden. Följ flödet precis såsom det kommer att göras när koden exekveras. I varje steg där det finns en potentiell risk att du gjort ett inkorrekt antagande av något slag behöver du verifiera att du gjort rätt antagande. Inspektera variabelvärden och returnerade värden om funktioner i alla relevanta steg. Med IDE:en som Visual Studio blir vi förstås bortskämda och har möjligheten att genom debugging hela tiden undersöka variablers värden. När vi kodar i språk som t.ex. php
har vi oftast inte den lyxen. Vi kan då undersöka "state" genom att output:a variablers värden. Det viktiga är helt enkelt att säkerställa att vi går in i varje metod med de värden vi antagit, och att vi lämnar varje metod med de värden vi antagit.
Med triangulering försöker vi föreslå ett snabbare sätt än att som ovan diskuterat, följa exekveringen. Jag börjar nästan alltid med triangulering (efter att jag som ovan nämnt verifierat att elen är påslagen). Om jag misslyckas med att isolera felet med hjälp av triangulering så börjar jag följa exekveringsflödet.
Med triangulering så menar vi helt enkelt att en väljer några strategiskt valda positioner. Och verifierar att input och output stämmer överens med de antaganden vi gjort. Om du tittar på din kod och gör ett par intuitiva antaganden. Vart skulle du då gissa att det är fel? Gå sedan in på dessa ställen och verifiera att "världen" (state) vid dessa tillfällen faktiskt ser ut såsom du antar att den gör. Med andra ord. Om du antar att en viss funktion returnerar ett visst värde, undersök att den verkligen gör det. Om du antar att en variabel innehåller ett visst värde, verifiera att den verkligen gör det. Och så vidare.
Ofta räcker det till och med med att skriva ut vad som helst till skärmen. Ibland kan vi applicera strategin "hur långt kommer vi?". Eller "vilken väg tar koden?". Med andra ord. Istället för att själva läsa vår kod så försöker vi får vår kod att berätta för oss vilken väg vi tar. Ett sätt att göra det är t.ex. att strategiskt välja några platser och sedan printa ut siffror. Vi kan t.ex. printa siffror i stigande ordning i den väg vi antar att programmet tar. Om vi sedan får en annan ordning av siffror på skärmen så vet vi två saker. Att koden tar en annan väg än den vi antog. Samt att vi vet lite mer om vilken väg den faktiskt tar.
När vi väl hittat felet är det bara att försöka söka rätt på en lösning. Om du inte spontant vet vad lösningen är så är internet ofta det bästa vapnet vi har. Försök att generalisera ditt problem lite utanför din specifika domän och ge dig ut i internetdjungeln. Alternativt, försök vara superspecifik. Om du t.ex. har en felkod — sök efter felkoden. När vi väl får en förklaring till vad felet betyder kan det hända att det blir uppenbart hur vi ska fixa det.
Det absolut viktigaste när vi väl har åtgärdat felet är förstås att lära oss av våra misstag. Fundera över det klassiska idiomet:
Fool me once, shame on you. Fool me twice, shame on me.
Många gånger har vi inte resurser nog att faktiskt förstå varför ett fel inträffade. Om vi väl lyckats lösa det behöver vi istället tuta på. Men låt oss bara säga att om vi inte förstår varför ett fel inträffade så spelar det absolut ingen roll att vi har löst det. Det kommer att hända igen.
Om felet detsutom berodde på att vi felanvände kod vi själva har skrivit så bör vi lära oss någonting om oss själva. Och sedan refaktorera. Alltså skriva om vår kod så att den gör samma sak men på ett mer uppenbart sätt.
Vi skulle vilja ge ett par konkreta felsökningstips för dig som arbetar i php
.
Det första är att se till att du har konfigurerat din php
-installation så att felmeddelande faktiskt visas. Med andra ord behöver du sätta display_errors till On i filen php.ini. I samma fil behöver du i samma veva även se till att inte bara kritiska fel, utan alla fel, rapporteras. Detta genom att sätta error_reporting till E_ALL. Notera att detta förslag inte alls är en bra idé för en "produktionskonfiguration". Med andra ord. När du väl ska konfigurera php
på en maskin som ska servera webbsidor publikt på internet, så är dessa direktiv direkt farliga. Alla felmeddelanden bör absolut inte visas för obehöriga användare. Det är en annan historia men vi vill att du ska vara medveten om att det förstås är jättesvårt att isolera fel om vår utvecklingsmiljö inte rapporterar fel.
Det andra tipset vi har är en liten men ofantligt trevlig metod som heter var_dump
. Denna metod används som så: var_dump(expression)
. Denna metod printar precis som echo
ut strängrepresentationen av en variabel till skärmen. Men utöver det så printar den också en massa annan matnyttig information. Om vi t.ex. har en array som innehåller de två elementen "apples" och "pears". Så skulle vi få följande resultat av var_dump
array(2) { [0]=> string(6) "apples" [1]=> string(5) "pears" }
Medan vi endast skulle få följande om vi använde echo
.
Array
Med andra ord kan vi snabbt vad en array innehåller och t.o.m. om den är tom. Men den verkliga styrkan kommer i att vi lätt kan upptäcka om en variabel innehåller null
. Om vi t.ex. använder echo
med en variabel som råkar innehålla null
— så printas helt enkelt ingenting till skärmen. Om vi däremot gör samma sak med var_dump
så printas istället texten NULL
. Mycket, mycket användbart.
Det sista essentiella tipset vi har, är en metod som heter die
och som används som så: die(expression)
. Denna metod är särskilt användbar i kombination med tidigare nämnd var_dump
. Metoden är enkel. Den avbryter pågående exekvering, och printar ut strängrepresentationen av det uttryck den får på skärmen. Detta är t.ex. mycket användbart när vi jobbar med webbsidor. Istället för att fortsätta att rendera hela vår webbsida så kan vi avbryta resten av exekveringen och istället printa ut någonting till skärmen. Alltså, t.ex undersöka värdet i en variabel. Vi säger att det är smidigt eftersom vi kan undvika saker som layout, och således undvika saker såsom att vi rent visuellt råkar "gömma" det värde vi försöker undersöka.
När vi pratade om formulär i html
så fokuserade vi endast på hur vi får webbläsaren att visa upp de formulärkomponenter vi vill. Textfält, radioknappar, checkboxar, knappar och så vidare. Dock pratade vi aldrig om hur vi som utvecklare kan använda oss av och komma åt den datan som användaren skriver i formulären. Vi nämnde att vi behöver ett server-side-språk för att kunna göra någonting permanent med datat — såsom att spara det i en databas. Så, eftersom vi nu befinner oss i kapitlet om server-side-språk så är det just det vi ska prata om. Alltså, hur vi på olika sätt kan läsa "input" ifrån användaren.
Faktum är att detta kapitels rubrik egentligen är ganska missvisande. Vi pratar inte bara om hur du kan läsa data ifrån ett formulär ifyllt av en användare. De tekniker vi kommer att prata om här har mycket bredare användning. Och borde kanske snarare kalla detta kapitel för — "Att genom php och html läsa och skicka ifrån och till ett http request". Men den titeln var inte lika "catchy" :)
Låt oss börja med att repetera hur ett vanligt html
-formulär ser ut. Beakta nedan exempel, och fundera över faktumet att attributet method
är tillskrivet värdet GET
. Fundera även över faktumet att attributet action
är tillskrivet värdet process.php
.
<form action="process.php" method="GET">
<label for="field-name">Ditt namn</label>
<input type="text" id="field-name" name="name" placeholder="Ditt namn" required>
<label>Vad vill du äta till frukost?</label>
<label for="field-pancakes">Pannkakor</label>
<input type="radio" id="field-pancakes" name="breakfast" value="pancakes">
<label for="field-scrambled">Äggröra</label>
<input type="radio" id="field-scrambled" name="breakfast" value="scrambled">
<label for="field-toast">Övrigt</label>
<input type="radio" id="field-toast" name="breakfast" value="toast">
<input type="submit" value="Skicka!">
</form>
Vi bad dig att framförallt notera användandet av nyckel-värde-paren action="process.php"
och method="GET"
. Kanske har du redan ett hum. Men låt oss prata om det. Vad betyder dessa två egentligen? Varför behöver vi ange dessa attribut? Och vad kan vi ge de för värden? Låt oss ta de en efter en.
När användaren klickar på submit
-knappen i ovan formulär så kommer formulärdatat att skickas till sidan process.php
. Eller mer specifikt uttryckt så kommer klickandet på knappen att orska användarens webbläsare att utfärda ett nytt HTTP Request. Alltså i stort sätt samma sak som händer när man trycker på en länk. Webbläsaren utfärdar ett nytt request till den url som gömmer sig under länken. Vi väntar. Och så fort webbläsaren får ett response
så är vi på den nya sidan.
När användaren trycker på "submit"-knappen så "skickas" denne helt enkelt till en annan sida. Vilken sida användaren skickas till bestäms utav den url
vi anger som nyckel i attributet action="[url]"
.
I fallet ovan så hade vi angett filnamnet process.php
. Eftersom vår url varken börjar med ett protokoll eller ett slashtecken (/
) så är det en relativ url och vi kommer helt enkelt att skicka användarens webbläsare till sidan process.php
om det finns en sådan sida på den "plats" användaren befinner sig just nu. Med andra ord. Om användaren (och således även formuläret) befann sig på adressen http://example.com/breakfast.php
så skulle användaren, när den klickar på "submit" i formuläret att skickas till adressen http://example.com/process.php
.
Det kanske viktigaste med allt detta — är ju förstås inte att vi skickar användaren till en ny url
. Det viktigaste är att den data som användaren fyllt i i formuläret skickas med i detta http request.
Så vad gör då attributet method="[HTTP METHOD]"
? Egentligen är det inte särskilt konstigt. Vi har redan klargjort att när användaren klickar på formulärets "submit"-knapp så kommer användarens webbläsare att utföra ett nytt http request
. Alltså omdirigeras till en ny url
. Samt att den data som användaren matat in i formuläret faktiskt skickas med i samma request. Notera ordvalet "i samma request". Den uppenbara frågan är ju förstås — hur? Hur kan en massa data som användaren har fyllt i skickas med ett http request
. Det kan ju vara precis viiiilken data som helst. Det är alltså just här attributet method="[HTTP METHOD]"
kommer in i bilden.
Det går alltså att bädda in en massa data i ett http request
. Gärna på nyckel-värde-par-form. Men eftersom det finns olika typer av http requests så kan data alltså skickas på olika sätt. Eller snarare olika "form" (no pun intended). Detta är vad attributet method
styr över. Attributet avgör alltså vilken typ av http request
vi ber användarens webbläsare att göra.
De två vanligaste typerna av http request
är GET och POST. Det finns ett par andra typer, men eftersom formulär i html
endast tillåter dessa två så är det endast dessa vi kommer att fokusera på här. Du kan läsa om fler metoder hos standardsorganet W3C.
När användaren klickar på submit
-knappen i detta formulär så kommer formulärdatat att skickas genom metoden HTTP GET
. Eller mer specifikt — när användaren klickar på knappen kommer användarens webbläsare utföra ett nytt HTTP Request. Detta request kommer att göras till den url
som är specificerad av attribtuet action
. Men eftersom det finns olika typer av HTTP requests
(t.ex. POST
och GET
) så anger vi vilken typ av request vi vill göra under action
.
Låt oss nu diskutera lite skillnader emellan POST och GET. Det enda rätta är väl egentligen att gräva ned oss och snabbt kika på hur dessa olika requests ser ut. Så att vi är överens om att det inte är någon slags svart magi. Men låt oss först kika på deras utvärtes skillnader.
När du knappar in en url
i din webbläsare och trycker enter så är det alltid ett GET
request du kommer att skicka. Så länge du inte använder en webbläsare som är designad för att skicka andra typer av requests. Rent "design"-mässigt så används GET
för att hämta resurser. Alltså inte för att förändra. Med andra termer skulle vi kunna säga att idealfallet är att ett GET request
är fritt ifrån sidoeffekter på servern. Vi använder ordet "design" eftersom detta ju är en rekommendation. Således en rekommendation som inte alltid följs. Om vi låter användare göra GET requests
till en given sida, och sedan ökar en räknare i en databas för att föra användarstatistik så är det ju en s.k. "sidoeffekt". Denna diskussion är helt enkelt inte svartvit.
Det är även rimligt att anta att vi i exempelrutan ovan med frukosten har brutit emot regeln om att använda GET
för sidoeffektsfria anrop. Men eftersom vi ännu inte berättat vad vi ska göra med datat som användaren skickar så är det förstås svårt att säga :)
Ett lättare sätt att belysa distinktionen lär vara detta. Vi skulle hävda att ett sökformulär bör skicka sin data via GET
. Medan ett registreringsformulär rimligen bör skickas över POST
. Även ett inloggningsformulär skulle vi nog skicka över POST
. De två senare förändrar ju faktiskt "state" på servern. I första fallet genom att lagra en till användare i databasen. I andra fallet genom att skapa en session för användaren (alltså markera den som inloggad). Medan i det allra första fallet — sökfallet — så vill vi ju faktiskt bara filtrera information. Ingenting förändras på servern. Vi vill, till användaren, returnera information som rimligen är ett subset av någonting annat.
Det är fullt förståeligt om du tycker att det här är förvirrande. Det blir mycket på en gång. Men låt oss se det ifrån en annan vinkel. Låt oss fundera på hur data (exempelvis alltså formulärdata) skickas med de olika anropstyperna. Nedan följer två exempel på hur ett request till en sida skulle kunna se ut. Den ena varianten användet GET
och den andra använder POST
. Det viktiga är att du fokuserar på hur datat skickas.
Låt oss kika på hur dessa requests faktiskt ser ut. Vi gör anrop till adressen example.com
. Och vi vill skicka med följande data: color = red
och shape = circle
. Fokusera framförallt på hur denna data i de två exemplena skickas olika.
Ett request genom HTTP GET
skulle då kunna se ut som så...
GET https://example.com/?shape=circle&color=red
Accept: */*
User-Agent: runscope/0.1
Och ett request genom HTTP POST
skulle istället kunna se ut som så...
POST https://example.com/
Accept: */*
Content-Length: 22
Content-Type: application/x-www-form-urlencoded
User-Agent: runscope/0.1
shape=circle&color=red
Båda dessa requests skickades tjänsten hurl.it. Prova gärna och utförska själv!
Märkte du vad som var lustigt i ovan exempel? Det är förstås mycket som känns (o)lustigt med http requests
första gången man ser dem. Rena mumbo jumbon. Men det viktiga vi behöver förstå är att den data som vi skickar med i ett GET
-anrop blir en del av själva url
:en vi anropar. Studera de två exemplena ovan. I det första fallet så anropar vi en url med ett frågetecken och en massa text på slutet. Detta är vår data "inkodad" i url:en enligt en standard som kallas url encoding. Själva strängen med det kodade datat kallas för en query string. I det andra fallet däremot. Där anropade vi helt enkelt bara den url
vi ville till. Utan en massa urlkodad information i slutet av adressen. Istället skickade vi informationen längst ned i vårt request
.
Som du kanske har misstänkt betyder detta alltså att när vi gör anrop via GET
så syns all data vi skickar med i anropet i url
:en själv. När vi däremot gör ett POST
-anrop så syns inte all data i adressen. Bara denna information gör att vi kan göra smartare val kring när vi ska använda den ena och när den andra. En fördel med att t.ex. skicka data i url
:en själv är ju att en användare kan kopiera den adressen och skicka till en kompis. Om vi istället skulle skicka datat "gömt" genom POST
så skulle datat inte "följa" med när användaren kopierade url:en. Användaren skulle då istället behöva kopiera hela HTTP requestet
. Vilket inte riktigt är något den vardaglige användaren ens skulle orka bry sig om att tänka på.
Låt oss nu istället prata om hur vi i php
kan läsa den data som kan skickas genom ett request. När en php
-fil körs (i kontexten av en webbapplikation) så körs den rimligen av en webbserver (t.ex. Apache). En webbserver kan ju, som vi långt tidigare klargjort, ta emot request
:s. Och som vi nu klargjort kan ju request
:s (bl.a.) vara av typen POST
eller GET
. Och i båda fallen kan de innehålla data ifrån den som skickat request
:et. Och eftersom det är en just en webbserver som delegerar neråt till php
så är det så fantastiskt att de flesta webbservrar låter anropet till server-side-språket få veta vad den har fått för request. Det betyder alltså att om vi använder Apache och php
så kommer vi i vår kod åt en hel del information om det anrop som faktiskt orsakat att vi kör den kod vi just nu kör. Låt oss se till några exempel.
$_REQUEST | En superglobal associativ array som innehåller alla requestvariabler oavsett om de kommer ifrån GET, POST eller Cookies. |
$_GET | En superglobal associativ array som innehåller alla requestvariabler som återfinns i den nuvarande url:en. |
$_SERVER | En superglobal associativ array som innehåller en mängd matnyttig information. Bland annat vilken requesttyp nuvarande request är av, nuvarande querystring eller vilken port skriptet körs på, o.s.v. |
getallheaders() | En metod som returnerar en array innehållandes alla http headers för det nuvarande http request:et. |
Notera att det är fullt möjligt att skicka GET
- och POST
-data samtidigt eftersom vi kan göra ett POST
-request till en url i vilken vi manuellt adderar urlkodade nyckel-värde-par. Anta alltså t.ex. att vi skulle efterfråga url:en http://example.com/?shape=triangle
. Om vi t.ex. skulle ange den url:en som värde till action
-attributet i ett formulär, och sedan ange att formulärets method
skulle vara POST
. Då kommer formulärets data ju alltså skickas i ett POST
-request, men eftersom url:en vi angav redan innehöll data formaterad som i ett GET
-request så kommer vi även ha skickat med den datan.
Så med andra ord är det i ärlighetens namn inte möjligt att göra ett POST
- och GET
-request samtidigt. Men det är däremot absolut möjligt att skicka urlparametrar trots att vi gör ett POST
-request. Således kan det, i relation till php
, hända att vi har värden i $_GET
trots att vi har värden i $_POST
. Och trots att ett request var av typen POST
. Mycket behändigt kommer du märka!
Låt oss nu då faktiskt se till ett konkret exempel på hur vi kommer åt informationen genom php
. Anta att vi anropat följande url: http://example.com/?name=Johnny&breakfast=pancakes
. Om du kikar på formuläret vi hade som exempel högre upp i detta kapitel, så ser du nog hur den här url:en faktiskt skulle kunna vara ett resultat av en användare som fyllt i formuläret. I vår php
skulle vi då alltså kunna hantera formulärdatat som så:
// Assuming an incoming GET request for the following url:
// http://example.com/?name=Johnny&breakfast=pancakes
$name = $_GET["name"]
$choice = $_GET["breakfast"]
echo "Dear $name, I'll certainly make you some $choice!";
Dear Johnny, I'll certainly make you some pancakes.
Som tidigare nämnt så kan querystring-variabler förekomma även utan att vi använder ett formulär. Som tidigare nämnt så är ju de flesta requests faktiskt GET
requests, och således uppenbara kandidater för att innehålla querystring-variabler.
Vi skulle t.ex. kunna skicka data med en helt vanlig länk.
<a href="http://example.com/?name=Johnny&breakfast=pancakes">
Johnny likes pancakes
</a>
Vilket skulle ge:
Vi skulle förstås även kunna använda php
för att göra precis samma sak men istället byta ut de konkreta värdena emot variabler.
<a href="http://example.com/?name=<?= $name; ?> &breakfast=<?= $choice; ?>">
Johnny likes pancakes
</a>
Men eftersom det ändå är så pass vanligt att konstruera en querystring så erbjuder php
oss en fantastisk metod som hjälper oss genom att automatiskt transformera en associativ array till en querystring. Med andra ord så här:
<?php
$data = array(
'name' => $name,
'breakfast' => $choice
);
$querystring = http_build_query($data);
?>
<a href="http://example.com/?<?= $querystring; ?>">
Johnny likes pancakes
</a>
Om vi ifrån en php
-sida skulle vilja automatiskt omdirigera (redirect) användaren till en annan sida så kan vi använda php
-metoden header('Location: [url]')
. Och om vi vill även vill passa på att skicka med parametrar, så kan vi förstås göra det genom att som vanligt lägga till en querystring i slutet av url
:en. Alltså som så:
<?php
$data = array(
'name' => $name,
'breakfast' => $choice
);
$querystring = http_build_query($data);
?>
// And then redirect
header("Location: http://example.com/?$querystring");
Om du inte redan märkt det, så är alltså syntaxen för att bygga upp en querystring följande:
nyckel=värde
där varje nyckel-värde-par separeras genom ett och-tecken (&
). Sedan är det förstås viktigt att komma ihåg att om vi ska lägga till en querystring till en url
så måste vi avgränsa slutet på url
:en och början på querystring:en med ett frågetecken (?
). Så sammantaget blir det alltså:
http://example.com/?key1=value1&key2=value2&keyN=valueN
Dags för objektorienterad php
! Äntligen! Språket anses inte av alla vara objektorienterat. Ifrån början gick det inte ens att skriva objektorienterad kod överhuvudtaget. Men, sedan en tid tillbaka har php
börjat stödja klassisk oop
i den bemärkelsen att vi själva kan skriva objektorienterade program (med klasser, arv, instansmetoder etc.). Men det finns som sagt en debatt kring huruvida språket verkligen kan anses objektorienterat ändå. En anledning till denna skepticism grundar sig i faktumet att, eftersom php
inte alltid varit objektorienterat, så finns det många gamla kvarlevor i form av "fria" metoder. Allt är inte objekt i php
.
Låt oss exemplifiera vad vi menar med att det finns en massa icke-objektorienterade kvarlevor kvar i språket. För att hämta en substräng av en sträng i php
skulle vi kunna skriva följande:
substr($mystring, $n);
Om språket hade varit mer uppenbart objektorienterat hade vi rimligen skrivit följande:
$mystring->substring($n);
Men, allt detta hindrar oss inte ifrån erövra världen med allsmäktig och objektorienterad php
. Bara för att många befintliga metoder inte är objektorienterade, så betyder det inte att den kod vi själva skriver inte kan vara objektorienterad.
Som nämnt i syntax-kapitlet så räknar vi med att du har en viss erfarenhet av objektorienterad programmering sedan tidigare. Så vi kommer i detta kapitel i huvdsak fokusera på att redogöra för syntax.
En klass deklareras i php
rätt och slätt genom keyword:et class
. Låt oss se till ett exempel.
php
class Animal{ ... }
Om vi har en klass, så kan vi förstås instantiera den. Och när vi instantierar ett objekt så anropas förstås dess konstruktor. En klass som inte har en konstruktur kan förstås konstrueras i vilket fall, och klassen har då en implicit konstruktor som inte tar några argument. Om vi däremot vill deklarera en konstruktor själva så använder vi det "magiska" namnet __construct
.
php
class Animal{
function __construct($name){
echo "Hello, my name is $name.";
}
}
Vi använde termen "magisk" tidigare eftersom dokumentationen för php
själv kallar de metoder som börjar med två underscore-tecken (i.e. __someMethodName
) för "magiska" metoder. Det är inte förbjudet i språket att deklarera egna metoder som börjar med två understreck, men det är rekommenderat att undvika det. Av den enkla anledning att språket har en del inbyggda metoder som är namngivna på just detta sätt.
Låt oss nu se på hur vi instantierar en en klass, alltså skapar ett objekt, i php
.
php
$dog = new Animal('Whiskey');
När vi instantierar en klass så körs ju, som nämnt och bekant, konstruktorn. Låt oss då kombinera dessa två (ovan) exempel för att se vad som händer när vi kör programmet.
// Assume we have a class with a constructor...
class Animal{
function __construct($name){
echo "Hello, my name is $name.";
}
}
// And then instantiate it...
$dog = new Animal('Whiskey');
$cat = new Animal('Socks');
Hello, my name is Whiskey.
Hello, my name is Socks.
Precis som i vilket annat språk med klasser som helst så kan vi definera både klass- och instansmedlemmar för en given klass. När vi pratar om "medlemmar" så pratar vi alltså både om metoder och variabler. Så när vi pratar om klass- och instalsmedlemmar så pratar vi alltså både om (1) klass- och instansvariabler, samt (2) klass- och instansmetoder.
Låt oss börja med att repetera vad skillnaden mellan en klass- och instansmedlem är. En klassmedlem är vad som brukar kallas för en statisk medlem. En klassmedlem tillhör klassen medan en instansmedlem tillhör instansen. Låt oss omformulera. En klassmedlem tillhör alla instansmedlemmar samtidigt, medan en instansmedlem är unik per instans. Detta betyder alltså att om vi deklarerar en klassvariabel — så existerar det endast en enda av den variabeln (tänk: minnesplats). Men om vi deklarerar en instansvariabel så kommer det existera exakt lika många variabler (tänk återigen: minnesplatser) som vi skapar instanser av den klassen.
Låt oss tänka i termer av ett exempel. Anta att vi har en klass som heter Animal
. Anta att varje instans av ett djur har ett namn. Vi lagrar namnet i en instansvariabel. Varför? Jo, eftersom varje djur har ett unikt namn. Och om det är unikt per djur så måste vi skapa lika många variabler som instanser. Varje variabel måste tillhöra varje unik instans.
Anta nu istället att vi vill ha en variabel i vilken vi kan hålla koll på det totala antal djur som existerar i vår applikation. Visst skulle vi skulle kunna skapa en variabel vartsomhelst och sedan öka den varje gång vi skapar ett nytt djur. Men det är riskabelt. Om vi glömmer att öka variabeln en enda gång så har vi helt plötsligt en applikation som talar osanning. Som du kanske redan har gissat så kommer vi istället föreslå att vi skapar en statisk (alltså en klass-) variabel. Om vi sedan i klassens konstruktor ser till att öka denna statiska variabel med ett så kommer det betyda att vi omöjligen kunna hamna i ett läge där vi har instantierat fler djur än gånger vi ökat variabeln. En klassvariabel är alltså en bra idé för detta scenario eftersom vi vill lagra information som är relaterad till alla instanser av klassen animal
och inte till varje specifik instans.
Låt oss snabbt kika på hur syntaxen ser ut för att deklarera klass- och instansvariabler.
class Animal{
// class variables are declared
// by using the keyword 'static'
private static $foo;
// instance variables are declared
// by not using the keyword 'static'
private $bar;
}
Notera alltså att skillnaden emellan att deklarera en statisk (klass-) variabel och en instansvariabel helt enkelt är existensen eller avsaknaden av nyckelordet static
. Skriver vi static
blir variabeln statisk (alltså en klassvariabel). Om vi inte skriver någonting så implicerar det att det är en instansvariabel.
Vi kommer att implementera det exempel som diskuterats ovan i helhet, i detta kapitel. Men vi måste ta det steg för steg för att verkligen förstå alla delar. Låt oss först diskutera det som kallas access modifiers
. Detta handlar alltså om huruvida en medlem är public
, private
eller protected
. Som du kanske märkte så markerade vi i ovan exempel att de två variablerna var private
. På samma sätt som en medlem måste markeras som statisk eller inte, måste en medlem också ha en access modifier
. Denna avgör "vilka" som kommer att få komma åt medlemen. Den "modify":ar alltså "access" — för att prata svengelska :)
Keyword | Förklaring |
---|---|
public | Alla som har tillgång till klassen/instansen har tillgång till medlemmen. |
private | Endast klassen/instansen själv har tillgång till medlemmen. |
protected | Endast klassen/instansen själv, samt klasser/instanser i samma arvskedja, har tillgång till medlemmen. |
Att deklarera klass- och instansmetoder påminner väldigt mycket om att deklarera klass- eller instansvariabler. Vi börjar med att ange en access modifier. Sedan anger vi huruvida den är statisk inte. Och slutligen en metod som vanligt. Det vill säga med namn och parametrar. Låt oss se till några exempel.
class Animal{
private static function foo(){
// Not accessible from the outside since it's private.
// Lives on the class because it's static.
}
private function bar(){
// Accessible from the outside since it's private.
// Lives on the instance beacuse it's not static.
}
}
Nu har vi bara pratat om hur vi deklarerar medlemmar. Vi har inte pratat om hur vi "kommer åt" dem. Egentligen är det väldigt enkelt. Instansmedlemmar når vi med hjälp av pil-notation (->
). Klassmedlemmar å andra sidan, når vi med hjälp av kolon-kolon-notation (::
). Det blir antagligen klarare om vi ser till ett par exempel.
// Assuming we have a class called Dog...
$dog = new Dog("Brian");
// accessing a public instance variable
$dog->someVariable;
// accessing a public static/class variable
Dog::$someVariable;
// accessing a public instance method
$dog->someMethod();
// accessing a public static/class method
Dog::someMethod();
Ovan exempel visar endast hur vi anropar publika medlemmar utifrån. Det vill säga inte ifrån instansen eller klassen själv. Om vi istället vill anropa medlemmar tillhörande klassen eller instansen själv ifrån klassen eller instansen själv så kan vi använda nästan samma syntax. Högersidan om kolon-kolon- eller pil-syntaxen förblir densamma, eftersom metoden/variabeln vi vill anropa är densamma. Däremot förändras vänstersidan. Eftersom kontexten vi försöker anropa medlemmen ifrån har förändrats. Låt oss se till ett exempel.
class Dog{
function __construct(){
/* We're making our calls from the constructor
for the sole reason of illustrating that we're
calling the members from inside an instance. */
// accessing a private instance variable
$this->someVariable;
// accessing a private static/class variable
self::$someVariable;
// accessing a private instance method
$this->someMethod();
// accessing a private static/class method
self::someMethod();
}
...
}
Det viktigaste att notera med ovan exempel är användandet av $this
och self
. Förstnämnda refererar alltså till den instans som anropet görs i. Om vi befinner oss i en instansmetod i en instans av en animal och använder nyckelordet $this
så refererar det ordet alltså till den egna instansen. Om vi å andra sidan använder uttrycket self
så refererar vi till klassen. Inte instansen utan klassen. Om vi befinner oss i en instans av (eller en klassmetod för) klassen Animal
, så refererar self
alltså till själva klassen Animal
. Inte till någon unik instans utan den generella klassen. Där vi förstås även kan ha statiska medlemmar definerade.
Ok, låt oss nu se till en full implementation av klassen Animal
som den tidigare diskuterades.
class Animal{
private static $count = 0;
private $name = "Unnamed";
function __construct($name){
$this->name = $name;
self::$count++;
}
public function speak(){
echo "Hi, I am $this->name.<br>";
}
public static function count(){
$num = self::$count;
echo "There's $num animal(s) in the world.<br>";
}
}
$cat = new Animal("Whiskers");
$cat->speak();
Animal::count();
$dog = new Animal("Frolic");
$dog->speak();
Animal::count();
Hi, I am Whiskers.
There's 1 animal(s) in the world.
Hi, I am Frolic.
There's 2 animal(s) in the world.
Kommer snart...
Kommer snart...
Uttrycket responding to change, har blivit mycket viktigt inom mjuvaruutveckling. I essens belyser det idéen om att världen runtomkring oss (kunder, teknik, krav, etc.) förändras så snabbt att vi alltid måste vara beredda att förändra våra applikationer. Därför är det alltså viktigt att vi skriver kod som är hanterbar. Vi måste kontrollera vår kod och inte låta vår kod kontrollera oss. I detta kapitel ska vi alltså prata om lite olika strategier för att öka nivån av struktur i våra php
-applikationer. Med andra ord ska vi prata om refaktorering. Alltså hur vi kan uttrycka samma sak på "bättre" sätt.
De flesta server-side-språk har ett sätt att inkludera innehållet av en fil i en annan. Detta är ett ypperligt sätt att undvika duplicering av kod. Föreställ dig att vi bygger en blogg. Vi har (t.ex.) en sida som listar alla inlägg, och vi har en sida per specifikt inlägg. Båda dessa sidor behöver göra queries till databasen och således behöver alltså båda sidorna sätta upp en databaskoppling. De två frågorna vi vill ställa databasen är olika, men just själva uppsättandet av databaskopplingen kommer vara exakt likadan.
Duplicerad kod kan alltid tolkas som en varningssignal för att vi antagligen behöver refaktorera och generalisera. Låt oss kort reflektera över varför duplicerad kod är så farligt. Föreställ dig igen ovan nämnt bloggexempel. Anta att vi sätter upp databaskopplingen först i varenda fil som behöver komma åt databasen. Säg omkring 25 filer. Om vi nu av någon anledning t.ex. behöver byta namn på databasen. Då behöver vi ändra databaskopplingen i alla dessa 25 filer. Trivialt kan tyckas. Men anta att det är 125 filer. Eller 1025 filer. Anta att vi bara ändrar i 24 filer och glömmer en. Anta att vi glömmer att kolla att just den sidan fungerar. Då har vi helt plötsligt "haft sönder" vår applikation utan att ens veta om det. Duplicerad kod är farlig kod. Duplicerad kod luktar illa.
Låt oss återgå till idéen om att inkludera innehållet av en fil i en annan fil. Konceptet kan egentligen liknas vid att bryta ut ett gäng rader kod till en metod. När vi märker att vi har duplicerat kod så bryter vi ut den duplicerade koden till en metod och anropar istället metoden på de båda ställena. Om vi i php
märker att vi har duplicerat kod emellan två filer, så bryter vi ut den duplicerade koden till en separat fil och inkluderar istället den filen i de andra två. Låt oss se till ett exempel.
include
i php
.Anta att vi har byggt en blogg. Anta att vi har två sidor. Där den ena listar alla inlägg och den visar ett specifikt inlägg. Så i filen som listar alla inlägg har vi någonting i stil med nedan:
/* list_all_posts.php */
$link = mysqli_connect("host","user","pwd","db") or die("Error " . mysqli_error($link));
$query = "SELECT * FROM posts";
$result = $link->query($query) or die("Query error: " . mysqli_error($link));
...
Och i filen som visar ett specifikt inlägg har vi någonting typ nedan:
/* show_single_post.php */
$link = mysqli_connect("host","user","pwd","db") or die("Error " . mysqli_error($link));
$query = "SELECT * FROM posts WHERE id='.$post_id.'";
$result = $link->query($query) or die("Query error: " . mysqli_error($link));
...
Jämför de två kodexemplena med varandra en stund. Uppenbart har vi en hel del duplikation. Låt oss naivt flytta över själva databaskopplingen.
/* db_connect.php */
$link = mysqli_connect("host","user","pwd","db") or die("Error " . mysqli_error($link));
/* list_all_posts.php */
include 'db_connect.php';
$query = "SELECT * FROM posts";
$result = $link->query($query) or die("Query error: " . mysqli_error($link));
/* show_single_post.php */
include 'db_connect.php';
$query = "SELECT * FROM posts WHERE id='.$post_id.'";
$result = $link->query($query) or die("Query error: " . mysqli_error($link));
Lite bättre. Men om vi kombinerar inkluderingsstrategin tillsammans med att bryta ut metoder kan vi förstås göra det ännu bättre.
/* db_functions.php */
function db_connect(){
$link = mysqli_connect("host","user","pwd","db") or die("Error " . mysqli_error($link));
return $link;
}
function db_query($query){
$link = db_connect();
return $link->query($query) or die("Query error: " . mysqli_error($link));
}
/* list_all_posts.php */
include 'db_connect.php';
$result = db_query("SELECT * FROM posts");
/* show_single_post.php */
include 'db_connect.php';
$result = db_query("SELECT * FROM posts WHERE id='.$post_id.'");
Mycket bättre :) Poängen här är alltså att vi har generaliserat och brytit ut vanligt förekommande kod till metoder.
Om vi även skulle introducera objektorienterad programmering så skulle vi kunna snygga upp kod ännu ytterligare. Då skulle vi kunna låta db_connect
-filen innehålla en klass istället för ett gäng globalt exponerade funktioner. Låt oss kika på ett exempel på hur vi skulle kunna gå tillväga:
/* db.php */
class Database{
function __construct(){
$this->db = mysqli_connect("host","user","pwd","db");
if ($this->db->connect_error) {
$code = $mysqli->connect_errno;
die("Error: ($code) $this->conncetion->connect_error");
}
}
public function query($sql){
return $this->db->query($sql)
or die("Query error: " . $this->db->error;
}
}
/* list_all_posts.php */
include 'db.php';
$db = new Database();
$result = $db->query("SELECT * FROM posts");
/* show_single_post.php */
include 'db.php';
$db = new Database();
$result = $db->query("SELECT * FROM posts WHERE id='.$post_id.'");
I detta sista exempel får vi även en "win" genom att det nu blir omöjligt att exekvera den metod vi skrivit som kör queries emot databasen, utan att vi först upprättat en koppling till databasen. Hur? Jo eftersom vår query
-metod nu är en instansmetod på Database
-objektet. Och eftersom klassens konstruktor upprättar en koppling till databasen, och eftersom det är omöjligt att instantiera objektet utan att konstruktorn körs så kan vi helt enkelt vara säkra på att det redan finns en databaskoppling när vi anropar query
-metoden.
Så här kan vi fortsätta och fortsätta. Vi kan nästan alltid refaktorera vidare kod. Det är t.ex. lite lustigt att vi har hårdkodat databasens "användaruppgifter" direkt i databasklassens konstruktur. Försök alltid att hitta nackdelar med din egen kod, och sök efter refaktoreringar så kommer du med tiden skriva bättre och bättre kod. Och om du är mer intresserad så kan du läsa böcker om refaktorering såsom t.ex. Refactoring — av Martin Fowler.
Det kan tyckas konstigt att ovan exempel skulle vara någonting smart. Vi gick ifrån mindre kod och färre filer till mer kod och fler filer. Men faktum är att rader kod är ett mycket dåligt mått på en kodbas komplexitet. Eller som Bill Gates uttryckte det — "Measuring software productivity by lines of code is like measuring progress on an airplane by how much it weighs".
Låt oss istället, i relation till ovan exempel, kontemplera hur redo vi är på att reagera på inkommande förändringskrav. Om vi nu t.ex. skulle vilja döpa om databasen behöver vi bara göra det på en plats, oavsett hur många filer vi har som kopplar till databasen. Om vi skulle vilja förändra felhanteringen om en query misslyckas behöver vi bara göra det på ett ställe. Eller felhanteringen för om själva databaskopplingen misslyckas.
Om vi skulle hamna i en situation då vi skulle vilja byta databas, till någonting annat än mysql
så är vi mer beredda än tidigare — men vi har fortfarande en lång väg att gå. För att hantera den typen av scenarion måste vi gå längre. Ett mycket vanligt tillvägagångssätt är att skriva egna eller använda sig av befintliga ORM:er (Object Relational Mapping). En ORM är helt enkelt kod som skapar ett abstraktionslager emellan databasen och vår kod. Så när vi vill ställa frågor (queries) till databasen gör vi det helt i vårt programspråk och aldrig i vårt databasspråk. Det betyder att om vi i framtiden vill byta databas, behöver vi bara förändra vår ORM och inte all vår applikationskod.
include
Det finns olika sätt att inkludera filer med php
. I ovan exempel använde vi oss utav include
. Låt oss repetera syntaxen.
include
include 'some_file.php';
// Or using a variable..
$filename = 'another_file.php';
include $filename;
Om den fil vi försöker inkludera av någon anledning inte går att hitta, kommer php
att spotta ur sig en warning
såsom nedan.
Warning: include(non_existent_file.php): failed to open stream: No such file or directory in /www/syntax.php on line 5
Warning: include(): Failed opening 'non_existent_file.php' for inclusion (include_path='.:') in /www/syntax.php on line 5
Det viktiga att förstå är att en varning inte avbryter exekveringen. Med andra ord — om en fil inte hittas kommer först en varning att spottas ut på sidan med sedan kommer resten av filen ändå exekveras precis som vanligt. Detta betyder att just konstruktionen include
faktiskt inte lämpar sig för applikationskritiska inkluderingar. Såsom databaskopplingen ovan, eller autentisering av användare. Låt oss nu diskutera några andra alternativ...
require
Ett annat alternativ vi kan använda för att inkludera filer är konsturktionen require
. Till skillnad ifrån include
så orsakar inte denna metod en warning
om det skulle vara så att den inte lyckas ladda en fil. Istället orsakar den ett fatal error
. Detta avbryter exekveringen. Alltså kommer ingen kod efter den misslyckade require
:en att köras.
Således är require
ett bättre sätt att inkludera applikationskritiska filer, än include
. Föreställ dig t.ex. att alla sidor som kräver inloggning inkluderar en fil som heter authorize.php
. I det fallet förlitar vi oss på att autentiseringsfilen omdirigerar användaren bort ifrån sidan den försöker komma åt. Anta att vi skulle använda include
för att inkludera filen authorize.php
. Om det av någon anledning skulle vara så att php
inte lyckas få tag på filen så skulle vår sida som kräver inloggning inte längre vara skyddad. Eftersom exekveringen fortsätter trots att vi inte lyckats hämta filen som ska autentisera användaren.
Syntaxmässigt så används require
på samma sätt som include
. Alltså:
require
require 'some_file.php';
// Or using a variable..
$filename = 'another_file.php';
require $filename;
Warning: require(non_existent_file.php): failed to open stream: No such file or directory in /www/syntax.php on line 5
Fatal error: require(): Failed opening required 'non_existent_file.php' (include_path='.:') in /www/syntax.php on line 5
require_once
Det sista alternativet för att inkludera filer i filer som vi ska kika på är require_once
. Denna konstruktion beteer sig egentligen precis som sin syster require
. Den viktiga skillnaden är dock att require_once
håller koll på om en fil tidigare har laddats in. Med andra ord laddas en fil endast in en gång när vi använder require_once
. Ett exempel gör det rimligen tydligare.
require
och require_once
.Anta att vi har följande fil.
/* hello.php */
echo 'hello ';
Låt oss använda en for
-loop för att inkludera samma fil tre gånger. Om vi använder oss av require
eller include
får vi följande resultat:
for($i=0; $i<3; $i++){="" require="" 'hello.php'="" ;="" }<="" code="">3;>
hello hello hello
Men om vi kör samma for
-loop men istället använder oss av require_once
så får vi följande resultat:
for($i=0; $i<3; $i++){="" require_once="" 'hello.php'="" ;="" }<="" code="">3;>
hello
html
Eftersom vi pratar om att använda php
till att skapa webbsidor så behöver vi förstås blanda html
och php
. Något vi tidigare diskuterat är idéen om separera presentation och content. Då i relation till ansvarsskillnader mellan html
och css
. Ett annat vanligt idiom inom programmering är separation of concerns. Det är en mer generell idé än den om just behovet av att separera presentation ifrån content. När vi nu använder ett kraftfullare skriptspråk, såsom php
, kommer vi plötsligt ha en mängd nya "concerns". T.ex. Business logic (affärslogik) och databaslogik. Om vi ska respektera idéen om att separera concerns så behöver vi alltså hitta strategier för att separera t.ex. affärslogik ifrån presentation.
Låt oss först prata om varför det är viktigt att separera olika "concerns". Kom ihåg tidigare diskussion om att vi behöver sikta på en hög nivå av beredskap inför förändring. Vår applikation behöver vara lätt att förändra. Så när (ps: vi väljer medvetet ordet när och inte om) vi väl behöver utföra en förändring löper vi inte risken att behöva koda om hela systemet ifrån grunden. Föreställ dig t.ex. att vi utvecklar en webbshop. Anta att vi har listat shoppens produkterna på listform. Anta nu att vi får in ett nytt krav på att även kunna presentera produkterna som ett rutnät. Jahapp tänker vi, och börjar kika på koden. Om vår databaskod då är beblandad med vår presentationskod så kommer förändringen bli mycket dyrare än om de är ordentligt separerade.
Utan att gräva ned oss i det här för mycket så kan vi säga att det finns en uppsjö av design patterns — alltså dokumenterade kodstruktureringsstrategier — som syftar just till att angripa denna typ av problem. Men det får vi diskutera på djupet en annan gång. Vad vi vill belysa i detta kapitel är hur det går att identifiera två vanliga strategier utvecklare använder för att "blanda" html
och php
. Antingen så...
php
och echo
:ar html
,html
och lägger in små block av php
php
och html
.Om vi hela tiden echo
/print
:ar ut html
blir det lätt att få sig en uppfattning om det "logiska" flödet i programmet, men mycket svårt med det visuella. Vid första anblick skulle vi argumentera att det inte är solklart vad nedan program gör.
<?php
$name = "John";
$number = "070 123 45 67";
echo "<h1>Hello, $name ($number).</h1>";
echo "<ul>";
for($i=0; $i<3; $i++){="" echo="" "<li>missed="" call="" ($i).<="" li>"="" }="" "<="" ul>"="" ?><="" code="">3;>
Hello, John (070 123 45 67)
Missed call (1).
Missed call (2).
Missed call (3).
Således är det ofta bättre att försöka hålla de filer som arbetar med html
fokuserade på just det — html
. Och istället se det som att php
kommer in i små korta svängar — antingen för att kontrollera programflödet eller hålla variabel data. Låt oss se hur det skulle kunna se ut.
<?
$name = "John";
$number = "070 123 45 67";
?>
<p>Hello, <?=$name?> (<?=$number?>)</p>
<? for($i=0; $i<3; $++):="" ?>="" <p>missed="" call="" (<?="$i?>)</p>" <?="" endfor;?><="" code="">3;>
Denna andra strategi har den mycket positiva effekten att vi även kan indentera vår html
. Och vi skulle argumentera för att just denna indentering verkligen hjälper till att öka kodens läsbarhet.
Som du kanske märkte använde vi i ovan en alternativ syntax för konstruktionen for
. Låt oss kalla dessa för "kolon-varianter". Det finns några grundläggande konstruktioner i php
som har just sådana här kolon-motsvarigheter. Varför de som skapade språket valt att implementera dessa kan vi inte svara på — men det kan vara rimligt att anta att även de insåg att det snabbt blir problematiskt när vi försöker blanda php
och något annat språk.
Med hjälp av dessa kolon-varianter kan vi istället skriva våra dokument som om de var skrivna i just det språket vi vill nå som output. I vårat fall alltså html
. Sedan kan vi strategiskt placera ett antal php
-kommandon som kontrollerar applikationsflödet. Låt oss se till några exempel på konstruktioner som har kolon-motsvarigheter.
Konstruktion | Kolon-motsvarighet |
---|---|
|
|
|
|
|
|
|
|
Dessa "kolon-motsvarigheter" är alltså mycket användbara när vi vill blanda html
och php
eftersom vi kan "bryta upp" våra php
-block. Beakta följande exempel.
php
Om vi vill "bryta" ett php-block efter en kontrollstruktur såsom t.ex. if
kan vi absolut göra det så här...
<? if(someCondition){ ?>
<p>Then display this text</p>
<? } ?>
Men det är förstås inte lika tydligt som att använda kolon-motsvarigheterna så här...
<? if(someCondition): ?>
<p>Then display this text</p>
<? endif; ?>
Fundera t.ex. över hur förvirrande det skulle vara att försöka avgöra vilken "stängande måsvinge" som hör till vilken "öppnande" när vi börjar hantera komplexare fall såsom det nedan...
<? if(someCondition){ ?>
<p>Then display
<? for($i=0; $i<10; $i++){="" ?>="" this="" <?="" if($i%2="=0){" text<="" p>="" }="" for($i="0;" $i<10;="" many="" times="" ?><="" code="">10;>
Nu säger vi förstås inte att alla måste prioritera att använda "kolon-versionerna". Inte heller säger vi att det i alla fallet är det bästa sättet att designa sina php
-filer. Men om du inte har en annan medveten strategi du tror på, så skulle vi rösta för att du följer ovan.
Det finns som sagt en uppsjö av design patterns som hjälper oss att strukturera kod på sätt som gör att vi kan öka nivån av kontroll över vår kod. Ett mycket vanligt design pattern för webben idag är MVC (Model View Controller). Det finns även en uppsjö av variationer av det pattern:et som generellt brukar refereras till som MV* (e.g. Model View Viewmodel, etc.).
Det är för tidigt att gräva in oss i design patterns nu. Men för att du ska ha något att luta dig på när du designar dina applikationer skulle vi vilja föreslå följande strategi:
Se till att de sidor som agerar "entry-points" (i.e. den fil som du pekar webbläsaren till) är så absolut enkla som möjligt. Låt de filerna istället inkludera andra filer. Och delegera komplext arbete till de inkluderade filerna. Säg att du t.ex. har fått en mängd data, som motsvarar en blogpost, och ska spara den i en databas. Istället för att processa och spara datat direkt i "entry-point"-filen — inkludera en annan fil som du t.ex. kallar process_post.php
. Låt den filen deklarera en metod som t.ex. heter process_post($post_data)
. Om den metoden istället utför allt det komplexa processnings, och sparningsarbetet så kommer "entry-point"-filen helt enkelt bara behöva anropa den metoden med relevant data som argument. Detta är alltså samma strategi som vi använde för att illustrera include
i första exemplet i detta kapitel.
Om du vill ha ännu mer struktur men ändå inte är redo för komplexare patterns, skulle vi rekommendera att du kikar lite på Front Controller Pattern.
AJAX (Asynkron JavaScript och XML) är ett samlingsnamn som innefattar flera olika tekniker. Kombinationen används för att kunna uppdatera en sida utan att ladda om hela dokumentet. Detta kan vara praktiskt då vi kan behöva göra något på servern utan att störa användaren genom att behöva vänta på att sidan laddas om.
De tekniker som i huvudsak används i samband med AJAX är...
XMLHttpRequest
är ett API för att kunna överföra och ta emot XML mellan server och klienten.DOM
(Document Object Model) är ett API som tillåter oss att interagera med ett dokument (HTML/XML/XHTML). Det är språk- och plattformsoberoende men vi kommer använda JavaScript-implementationen.HTML
& CSS
är ett märkesspråk och stylesheet-språk som vi använder för att först "märka upp" ett dokument och sedan definera hur det ska presenteras.XML
är ett märkesspråk där dokumentkreatören själv kan välja (i stort sett) vilka element- och attributnamn som helst. Om HTML är mer dokumentfokuserat så är XML mer datafokuserat. Det används ofta för att transportera data. Javascript
är det programmeringspråk vi använder för att kunna interagera med DOM:en, alltså den nuvarande webbsidan.AJAX är som sagt en teknikfamilj. Notera att denna familj inte inkluderar något server-side-språk. Detta betyder att vi självklart kan använda AJAX-tekniker i kombination med i stort sett vilket server-side-språk som helst. ASP.NET
eller Ruby
till exempel. Men eftersom vi i denna guide använt oss av php
så är det just det vi kommer att fokusera på här.
Vi kommer även använda oss av jQuery
istället för att skriva "ren" JavaScript
. Detta av den enkla anledningen att det är krångligare att arbeta med AJAX direkt i JavaScript
. Vi föreslår dock att du för eller senare absolut undersöker provar på att arbeta med AJAX i ren JavaScript. Det är alltid bra att veta vad som händer "under huven".
Så, låt oss då utan om och men sluta jiddra och börja trolla!
Denna metod kan vi använda för att göra ett asynkront GET request
. Alltså i JavsaScript
. Och alltså utan att sidan vi är på laddas om.
$('button').click(function(){
$.get('test.php',function(data, status){
// the argument 'data' now contains the response
$('.result').html(data);
});
});
Som ni ser har vi på en knapp som vid ett knapptryck gör ett HTTP GET
request till servern. Innan vi tittar lite närmare på vad som faktiskt kommer att hända. Låt oss först undersöka vad sidan vi skickar ett request till (alltså test.php
) innehåller.
test.php
)<?php
echo "Hello from AJAX!";
?>
jQuery-metoden get tar emot två argument. Först en (1) URL som motsvarar den sida vi vill request:a. Vidare en (2) callback-funktion. Alltså en funktion som kommer att anropas när request:et är avslutat och vi får ett response tillbaka.
Tänk på hur ett HTTP GET request
fungerar. Kanske kommer du ihåg att vi alltid kan skicka parametrar. Alltså såsom nedan:
http://example.com?name=Tarzan&breed=manape
Självklart tillåter även jQuery-metoden GET att vi skickar parametrar. För att vi ska slippa konstruera denna url med url-enkodade parametrar själva, så är jQuery-metoden get konstruerad så att vi istället kan skicka ett javascript-objekt med nycklar och värden som motsvarar den data vi vill skicka.
Parametrarna skickar vi som andra argument. Och eftersom vårt callback ska vara det sista argument så blir det nu det tredje argumentet. Såsom i exemplet nedan.
$('button').click(function(){
$.get('test.php',
{
name: 'Tarzan',
breed: 'manape'
},
function(data, status){
alert(data);
}
);
});
Anta då att vår php-sida (test.php
) istället ser ut så här:
<?php
$name = $_GET["name"]
$breed = $_GET["breed"]
echo "$name is a $breed";
?>
Om vi har ovan server-side-sida, och kör klientkoden — så kommer callbacket som avfyras att öppna en alert
-ruta. I den rutan kommer det att stå:
Tarzan is a manape
Denna metod är mycket lik get()
. Men den är till för att snabbare kunna lösa ett vanligt problem. Eftersom det är mycket vanligt att asynkront göra ett request till en annan sida och sedan visa body:t av det response vi får tillbaka i ett element så gör denna metod just dessa två saker. Vi kan alltså se metoden load()
som en kombination av get()
och html()
. Låt oss exemplifiera.
Anta att vi uttrycker följande med hjälp av load()
...
$('.result').load('test.php', function() {
alert('Success! The contents of .result are now updated.');
});
..då ger det alltså samma effekt som om vi hade uttryckt följande med hjälp av get()
...
$.get('test.php', function(data, status){
alert('Success! The contents of .result are now updated.');
$('.result').html(data);
});
Vi slipper alltså explicit uppdatera html-elementet genom html()
. Så om vi skippar callback:et helt så skulle vi t.o.m. kunna kondensera vår kod ner till följande...
$('.result').load('test.php');
Vi vill sedan även bara poängtera att vi nu bara har pratat om hur vi använder jQuery för att göra GET request
:s. Men det går förstås även utmärkt att göra POST
-anrop. Detta genom post()
. Men eftersom användandet är mycket likt, så lämnar vi det till dig att läsa på hur metoden ska användas.
Att jobba med asynkrona anrop är kraftfullt och bör vara en viktig kunskap i verktygslådan som webbutvecklare. Dagens webb är snabb och interaktiv. Vi kan inte alltid låta användare vänta på att hela sidan laddas om. Och det finns mycket vi med fördel gör i bakgrunden. Tänk på det!
Dags att snacka databaser! Vad vore väl en interaktiv applikation om vi inte kunde spara någon information? Vad vore en webbshop om vi inte kunde ta emot ordrar, eller lagra produkter. Vad vore webben utan data?
Som du kanske märkt så är det en del grundidéer och -idiom som konstant återkommer. En av dessa mycket, mycket viktiga idiom är den tidigare diskuterade — separation of concerns. Även gällande databaser kan vi börja diskutera "separation of concerns". Idéen om databaser bygger ju nämligen på att en applikation och den data en applikation använder, inte är samma sak. Sjävklart egentligen. Men bara för att något är självklart betyder det inte att det alltid har varit enkelt.
Låt oss tänka på det. Data och applikationer är inte samma sak. Eller kanske kan vi uttrycka oss i termer av att data och processer inte är samma sak. Om vi ser applikationer som formaliserade processer. Steg för steg. Så är alltså inte data och processer inte samma sak. Jag tror inte vi behöver understryka detta mera utan förhoppningsvis finner du det förhållandevis naturligt.
För att en data ska vara agnostisk ifrån sin applikation så krävs det dels att vi (1) har någon form av formaliserad standard för hur data ska lagras. Eftersom datat någonstans måste skrivas till disk eller minne så behöver vi ett strukturerat format att spara det i. Sedan behöver vi förstås även (2) ett sätt att interagera med denna data, eller snarare denna databas. Detta är vad vi vanligtvis benämner som en DBMS (Database Management System).
I fallet som vi kommer arbeta med, så är vår DBMS MySQL och vår databas är en relationsdatabas. Språket vi använder för att kommunicera med DBMS:en är SQL (Structured Query Language).
Det finns egentligen ingenting som säger att språket vi använder för att kommunicera med en viss DBMS måste vara ett annat än det vi skriver vår applikation i. Visst, med språket sql
så kan vi ju omöjligen skriva ett helt program. Eftersom språket just är ett språk designat för att ställa frågor till en relationsdatabas. Men däremot finns det många DBMS:ar som går att interagera med i samma språk som de är tänkta att användas i.
Kommer snart...
Även om vi förväntar oss att du har en viss erfarenhet av sql
sedan tidigare, så tänkte vi att vi tar en snabb återblick här.
Kommer snart...
Kommer snart...
Nu är det dags att knyta ihop säcken lite och diskutera hur vi kan använda php
för att arbeta emot en mysql
-databas. Vi ska alltså prata om det API som php
erbjuder för att intergagera med MySQL
.
Om det inte redan är självklart varför vi skulle vilja göra detta — låt oss iterera. Om vi ifrån vår php
-applikation kan koppla till en mysql
-databas så innebär detta att vi kan spara och förändra data i databasen. Självklart är detta essentiellt! Helt plötsligt har vi möjlighet att t.ex. skapa användarkonton. Genom att lagra e-post-adresser tillsammans med lösenord. Och när användare sedan ska logga in så jämför vi bara de uppgifter vi får ifrån användaren med de som finns i databasen. Om uppgifterna stämmer med någon användare så kan vi logga in personen. Men du har som sagt säkert redan förstått varför detta är viktigt, så låt oss övergå till hur vi ifrån php
kan använda oss utav mysql
.
Efter några sökningar på nätet kommer du att märka att när vi letar resurser relaterade till det API
som php
erbjuder oss för att interagera med mysql
— så hittar vi inte bara en syntax. Istället hittar vi minst tre. Är vi riktigt noga så hittar vi fyra. För att undvika förvirring när du sedan letar information på egen hand så ska vi först bara bena ut vad alla dessa olika syntaxer är och handlar om.
Först fanns ett API som hette mysql. Detta finns och fungerar fortfarande. Men med php 5.0.0 så kom ett nytt api
vid namn mysqli. I essens så är detta helt enkelt en uppdaterad version av mysql
och det extra "i":et på slutet står för "improved". Således finns det idag egentligen ingen anledning att använda det gamla mysql
-api:et när det finns ett nyare. När du surfar omkring i nätdjungeln så kan du lätt avgöra om en metod tillhör det gamla eller det nya api:et genom att helt enkelt läsa namnet på metoden. Alla de gamla metoderna innehåller termen mysql
, medan alla de nya innehåller mysqli
. Lätt som en plätt! Vi kommer således endast att fokusera på det nyare mysqli
.
Men vi nämnde ju att det fanns tre, och t.o.m. fyra olika API:er. Låt oss förklara. Kanske kommer du ihåg ifrån php
-kapitlet att vi diskuterade hur php
inte var objektorienterat ifrån början? Men hur det successivt håller på att utvecklas till att stödja mer och mer objektorienterad syntax. Så är det iallafall. Och det speglar även av sig här.
Det äldre api:et, alltså mysql
, är helt procedurellt, och stödjer ingen objektorienterad syntax. Det nyare mysqli
stödjer däremot två olika syntaxer. Först en procedurell syntax (som påminner otroligt mycket om det gamla api:et). Men sedan stödjer det även en objektorienterad syntax.
Eftersom vi på htmlhunden subjektivt tror på objektorientering skulle vi vilja slå ett starkt slag för att du redan ifrån början vänjer dig vid att använda den objektorienterade syntaxen. Men vi kommer att redogöra för båda två i detta kapitel.
Men vad hände med den fjärde syntaxen då? Jo, det finns ett sätt till. Med php 5
så introducerades även någonging som heter PDO (PHP Data Objects) som i essens är ett abstraktionslager emellan databasen och applikationskoden. För att hålla det så enkelt som möjligt så kommer vi inte att kika någonting på PDO
i detta kapitel, men däremot kommer vi att titta lite på att bygga vårt eget abstraktionslager för en databas, i ett kommande kapitel :)
Innan vi kan börja ställa frågor till databasen behöver vi ansluta till den. Detta skulle kunna jämföras med att logga in till en host genom phpMyAdmin, MySQL Workbench, genom en terminal (eller någon annan mysql klient). Men när vi kopplar upp till mysql
via php
så väljer vi även databasschema. Vilket kan jämföras med att köra sql
-kommandot USE name_of_db;
, eller att helt enkelt välja en databas/ett schema i något av de tidigare nämnda grafiska klienterna.
Så, låt oss se till ett faktiskt exempel. Viktigt att notera är alltså att vi i båda lagrar resultatet av evalueringen i en variabel. Vi får alltså tillbaka en instans av mysqli
. Som alltså motsvarar en uppkoppling till vår databas. Läs mer om att sätta upp en koppling i manualen.
OOP syntax.
$mysqli = new mysqli('host', 'user', 'password', 'database');
Procedurell syntax.
$mysqli = mysqli_connect('host', 'user', 'password', 'database');
Låt oss även snabbt se till hur vi skulle kunna hantera eventuella fel som kan uppstå när vi försöker koppla upp oss till databasen. Om vi t.ex. råkat ange fel lösenord, eller inte har läsrättigheter till den databas vi försöker koppla till med den användaren vi angett, eller om datbasen helt enkelt inte existerar (o.s.v.).
OOP syntax.
$error = $mysqli->connect_error;
if ($error) {
$code = $mysqli->connect_errno;
die("Error: ($code) $error");
}
Procedurell syntax.
$error = mysqli_connect_error();
if ($error) {
$code = mysqli_connect_errno();
die("Error: ($code) $error");
}
Notera i ovan exempel att vi alltid har tillgång till både själva felmeddelandet som sträng (conect_error), men även dess felkod (connect_errno).
Notera också att den jämförelse vi gör för att upptäcka om ett fel har inträffat eller inte sker genom att vi helt enkelt slänger in den sträng som potentiellt innehåller ett felmeddelande. Om vi läser dokumentationen för metoden så ser vi att den returnerar null
om inget fel har inträffat och en sträng innehållandes ett felmeddelande om ett fel har inträffat. Detta är en av styrkorna med dynamiska språk. Vi kan returnera helt olika saker utan att i förväg bestämma oss. Men det finns en anledning till att detta fungerar — nämligen att jämförelsen (null == true)
evaluerar till false
i php
. En sträng däremot, evaluerar till true
så länge den inte är tom eller endast innehåller en nolla (0
). Vi behöver alltså inte ens uttrycka vårt boolska uttryck som ($error != null)
. Återigen, eftersom null
i sig evaluerar till false
. Och eftersom om vi har fått ett fel så kommer vi ha fått en sträng, och alla strängar utom tom sträng och noll evaluerar till true
. Du kan läsa mer om vilka värden som ger true
och vilka som ger false
här.
När vi väl har en koppling uppe kan vi använda vår variabel som innehåller kopplingen (i.e. mysqli
-objektet) för att köra queries emot databasen. Oavsett vi vill skriva en query som ska skapa en tabell, lägga in en ny rad, uppdatera en existerande, förändra en kolumn eller någonting helt annat — så kan vi använda samma metod. Nämligen mysqli->query($sql)
. Låt oss se till ett par exempel.
OOP syntax.
$result = $mysqli->query("SELECT * FROM posts");
if ($result) {
echo "Number of rows: " . $result->num_rows;
}
Procedurell syntax.
$result = $mysqli->query("SELECT * FROM posts")
if ($result) {
echo "Number of rows: " . mysqli_num_rows($result);
}
Det mest intressanta med denna php
-metod är att den returnerar olika typer av saker beroende på vad vi kör för query. Detta denoteras genom returtypen mixed
i dokumentationen. Om vi t.ex. skulle köra en INSERT INTO
får vi en bool tillbaka som innehåller true
om query:n lyckades och false
om den ej gjorde det. Faktum är att vi i alla fall får tillbaka en bool som säger false
om en query misslyckas p.g.a. t.ex. fel syntax eller en icke-existerande kolumn.
Om vi däremot t.ex. skulle köra query:n SELECT * FROM users
. Så får vi tillbaka ett objekt av typen mysqli_result
. Så länge vår query är en valid query får vi den typen av resultat tillbaka. Även om det skulle vara så att den inte hittade några rader alls. I ovan kodexempel är det just den typen av ett objekt vi får tillbaka, och det är således därför vi kan fråga objektet om dess antal rader.
Om du skrivit och exekverat en query som returnerar ett mysqli_result
så kan du inte direkt enumerera över datan. Det finns två enkla sätt. Antingen så använder du en while
-loop och snurrar över varje rad i resultatset:et en för en. Detta gör du med hjälp av metoden $result->fetch_row() eller $result->fetch_assoc(). Eller så hämtar du helt enkelt alla rader på en gång så att du kan lagra resultatet i en array. Detta gör du genom $result->fetch_all(). Det senare kan ju förstås vara smidigt om du inte vill använda resultatet på en gång utan snarare skicka det vidare. Låt oss se till ett par användningsexempel. Vi börjar med att iterera och hämta rad för rad.
OOP syntax.
// Iterates over each row (into $row) as a numeric array
while($row = $result->fetch_row()){
var_dump($row);
}
// But we could also iterate over each row as an associative array
while($row = $result->fetch_assoc()){
var_dump($row);
}
Procedurell syntax.
// Iterates over each row (into $row) as a numeric array
while($row = mysqli_fetch_row($result)){
var_dump($row);
}
// But we could also iterate over each row as an associative array
while($row = mysqli_fetch_assoc($result)){
var_dump($row);
}
I de fall där vi hämtar varje rad som en associativ array så innebär det att varje rads nycklar motsvarar namnet på databasens kolumner. I de numeriska fallen så når vi helt enkelt argumenten i samma ordning som de är definierade i datbasen.
Men som sagt, vi kan ju, som tidigare nämnt, även hämta alla rader på en gång och slänga in de i en array. Vilket ju kan vara smidigt om vi inte vill iterera över resultatet än. Detta kan vi göra genom att använda oss av $result-fetch_all(). Denna metod tar även ytterligare en valfri parameter. Denna parameter ska vara en konstant som berättar om vi vill ha resultatet som en numrerad array (MYSQLI_NUM
), eller en associativ array (MYSQLI_ASSOC
). Om inget värde anges så är "default" numerisk. Låt oss kika på några exempel.
OOP syntax.
// As numbered array
$arr = $result->fetch_all();
// Or as an associative array
$arr = $result->fetch_all(MYSQLI_ASSOC);
Procedurell syntax.
// As numbered array
$arr = mysqli_fetch_all($result);
// Or as an associative array
$arr = mysqli_fetch_all($result, MYSQLI_ASSOC);
Kommer snart...
HTML-hunden är skriven av
Projektet har öppen källkod, och om du således är intresserad av att bidra på något sätt så välkomnar vi alla tankar, idéer och korrigeringar. Projektet tillsammans med dokumentation finns på GitHub.