Använda jQuery att manipulera och filtrera data

När en webbsida är utformad för att visa stora datatabeller, bör en stor hänsyn tas till att användaren kan sortera data på ett strukturerat sätt. I denna artikel kommer jag att gå över fyra tekniker: svävar effekter, zebra rader, filtrering och sortering.

Ställa in tabellerna

Det finns några viktiga anteckningar vi måste adressera innan vi tittar på vår Javascript-kod. HTML-tabelluppsättningen kommer att vara som vilken annan tabell du kanske har skapat, förutom vi
kräver två taggar som många lämnar. Huvuddelen av bordet måste vikas in . Kroppen av
tabell, där alla data vi vill visa hålls, måste vara inslagna i . Denna lilla granskning gör det lättare för oss att skilja
mellan data och tabellhuvuden.

 ... 
Förnamn Efternamn Stad stat
Mannix Skruva på

Zebra Rows

Zebra Rows är en mycket vanlig dataanalysteknik som både är enkel att implementera och har en kraftfull inverkan på. Zebra Rows in
essensen växlar stilen av udda och jämn rader för att göra data lättare att läsa horisontellt. Detta är mycket viktigt med
data med flera kolumner så att användarna kan titta på en kolumn och lätt läsa de associerade data i samma rad under andra rubriker. I
exempel som jag kommer att använda genom denna handledning, jag har en lista över personer med fyra egenskaper: förnamn, efternamn, stad och
stat. Lägg märke till hur jag har radfärgerna alternerande samt teckensnittsfärg för att maximera effekten.

Nu vidare till själva Zebra Rows. Den första platsen att starta är en extern CSS-fil kopplad till dokumentet. Det första elementet till
målet är tabellen.

 bord bakgrundsfärg: vit; bredd: 100%; 

Detta är ganska trivialt; Vi säger att bordets bakgrund är vit och sträcker sig till 100% av moderelementets bredd. Nästa kommer vi att rikta oss
cellelementen . Nu kan det tyckas märkligt för några - varför skulle vi rikta in sig på cellerna och inte hela raden? Jo det visar sig att, i
Villkor för cross-browser adoption, det är effektivare att rikta celler vid tillämpning av bakgrundsstilar.

 tbody td bakgrundsfärg: vit;  tbody td.odd bakgrundsfärg: # 666; färg vit; 

Här ställer vi upp en klass för de "udda" tabellraderna som ställer in en alternativ bakgrundsfärg och fontfärg. Vi ställer också in en standardstil för alla td
element som i sig kommer att tillämpas på "jämn" rader. Detta är alla CSS som krävs. Jag sa att det var enkelt! Låt oss nu titta på jQuery-koden. De
kraften hos jQuery-selektorer gör det lika enkelt som CSS-koden. Allt vi behöver göra är att rikta in cellerna och använd funktionen addClassName.

 $ (dokument) .ready (funktion () zebraRows ('tbody tr: odd td', 'odd');); // används för att tillämpa alternativa radstilar funktion zebraRows (selector, className) $ (selector) .removeClass (className) .addClass (className); 

Denna kod, men kort, har några gotchas att överväga. Först och främst uppsägning hur vi abstraherade implementeringen till en funktion; Detta är idealiskt för att om vi ändrar
data i tabellen asynkront, utan en siduppdatering, då vill vi försäkra att raderna fortfarande är växlande stil. Av samma anledning åberopar vi också
removeClass funktion så att om en rad är initialt udda men blir jämn, är vi säkra på att den udda klassdefinitionen inte förblir. Det kan tyckas förvirrande just nu,
men när vi tittar på filtrering senare blir det tydligare. För återanvändbar kod kräver vi också att väljare och klassnamn skickas till
funktionen - så den kan användas i projekt som har olika klassnamn eller väljarkrav (dvs. en webbplats som har flera tabeller endast en av
som du vill rikta in). Om du granskar funktionen ready (), utförs en jQuery-funktion när sidan är
färdig laddning, ser du vårt samtal till zebraRows (). Här passerar vi väljaren och klassnamnet. Väljaren använder en särskild jQuery-syntax
: udda, som kommer att hitta alla udda rader. Sedan letar vi efter alla barnelement i raden som är celler. Denna kod
är ganska enkelt för alla som tidigare har använt jQuery, men enkelheten borde göra koden ganska läsbar till någon annan.

Notera: När du använder jQuery för att tillämpa alternerande radfärger är en enkel lösning, det kan inte nedbrytas om en användare har JavaScript inaktiverat. jag skulle
rekommenderar att du använder den udda klassen på servern antingen i PHP-kod eller den statiska HTML-filen, men detta ligger utanför ramen för denna artikel.

Hover Effect

En riktigt fin effekt för användarna är att markera den rad som de för närvarande svävar på. Detta är ett utmärkt sätt att utesluta specifika data som de kanske är intresserade av
in. Det här är dött enkelt att implementera med jQuery, men först en liten CSS.

... td.hovered bakgrundsfärg: ljusblå; färg: # 666;  ... 

Det här är all CSS vi behöver, i grund och botten när vi svävar över en rad vill vi göra alla celler i den raden har en ljusblå bakgrund och en grå teckensfärg. De
jQuery för att göra detta hända är lika enkelt.

... $ ('tbody tr'). Sväng (funktion () $ (detta) .find ('td'). AddClass ('hovered');, funktion () $ ) .removeClass ("hovered");); ... 

Vi använder funktionen hover () i jQuery-biblioteket. Det tar två argument som är
funktioner vi vill ha exekverat när musen svävar över och när musen flyttas av respektive element. När de svävar över en rad vill vi hitta allt
cellerna i raden och lägg den svävande klassen till dem. När musen lämnar elementet vill vi sedan ta bort den klassen. Detta är allt vi måste göra med
hämta effekten, gå prova den!

Filtrera data

Nu är de köttiga grejerna - faktiskt manipulera de data som visas. Om en webbplats kräver att många register över data ska visas, i mitt exempel 1000 rader, då det
är mer än lämpligt för att erbjuda användaren ett sätt att sikta igenom data. Ett särskilt effektivt sätt som har sprungit upp på webben de senaste åren som en del av
Web2.0 / AJAX-rörelsen filtrerar. Det här är också något som Apple driver hårt i applikationer som iTunes. Målet för oss är att tillåta användaren
för att skriva en sökfråga till en vanlig textinmatning och ett levande filter visas tabellraderna nedan bara de som innehåller matchande text. Detta är förmodligen mer
avancerade sedan de alternerande radstilarna, men i all verklighet kräver minimal kod, på grund av jQuerys inbyggda funktionalitet.

Först ska vi skriva en generisk funktion som tar en väljare och en sträng text. Den här funktionen söker sedan alla element som matchar den väljaren som letar efter
tråden. Om den finner strängen kommer den att visa elementet och ange ett klassnamn som är synligt för elementet, annars döljer det elementet. Varför tillämpar vi
klassen av synlig? Tja när objekten är sorterade kommer vi att vilja köra zebraRows-funktionen igen, men vi måste berätta för jQuery att ignorera de dolda raderna, och
Det bästa sättet jag har hittat att göra är att använda en klass av synliga.

Den faktiska sökningen görs av JavaScript-funktionen, lämpligt namngiven, sökning (). Även om DOM fungerar, om vi inte använder jQuery-funktionen,
text () kommer rutan även att titta på några HTML-taggar som råkar vara i tabellraden, till exempel . Vi
kommer att använda lite mer funktionalitet genom att inte bara söka efter den exakta strängen användaren har skrivit, men snarare om något av orden i frågan är i rad.
Detta är idealiskt eftersom det tillåter "lat sökning", användaren behöver inte komma ihåg en exakt sträng utan snarare bara delar av den. Sökningen () -funktionen tar
ett regelbundet uttryck som dess parameter, och så måste vi ta bort allt vitt utrymme från början och slutet av vår fråga och sätt "|" tecken mellan varje ord till
uppnå OR-funktionaliteten vi önskar. Regelbundna uttryck är ett mycket komplicerat ämne, så du måste ta min kod till nominellt värde eller jag kan hänvisa till dig
Regelbundna uttryck för dummies video- serien på ThemeForest-bloggen.

 // filterresultat baserat på sökfunktionsfilter (väljare, fråga) query = $ .trim (fråga); // trim blank space query = query.replace (/ / gi, '|'); // lägg till ELLER för regex query $ (selector) .each (funktion () ($ (this) .text (). search (ny RegExp (fråga, "i")) < 0) ? $(this).hide().removeClass('visible') : $(this).show().addClass('visible'); ); 

Den sjätte raden är där magiken händer, och kräver förmodligen lite förklaring. Med början på rad 5 berättar vi koden att slinga igenom alla element
som matchar väljaren, dvs raderna, och sedan vill vi utföra koden på rad 6 med hjälp av var och en. Linje 6 är lite komplicerat om du är ny på programmering,
men det är ganska lätt att förstå om vi delar upp det. Tänk på allt innan frågetecknet är en fråga, om svaret på den frågan är sant då
kör koden till vänster om kolon, men efter frågetecknet. Om svaret är felaktigt, kör koden efter kolon. Detta är i huvudsak en om
uttalande men i en mer kortfattad form känd som en ternär operatör och skulle inte vara annorlunda än att skriva:

... om ($ (this) .text (). Search (ny RegExp (fråga, "i")) < 0)  $(this).hide().removeClass('visible')  else  $(this).show().addClass('visible'); … 

Anledningen till att vi frågar om sökning () returnerar "mindre än noll, vi testar det tillståndet. I teorin är det inget fel med att kontrollera om den returnerar (==) -1, men i praktiken är det säkrare att bara försäkra att det är
mindre än noll.

Okej nu när vi har en komplett filterfunktion, låt oss använda jQuery-händelser för att koppla upp den till ingången. För att uppnå levande effekt önskar vi händelsen vi vill
titta på är när användaren släpper en nyckel medan de är inriktade på textrutan, känd som nyckeluppgift i JavaScript. Det är viktigt att vi sätter ID-attributet till
inmatas så att vi kan rikta in det med jQuery. Tillbaka i vår färdiga funktion måste vi lägga till kod efter vårt samtal till zebraRows ().

  

Och jQuery-koden:

... // standard varje rad till synlig $ ('tbody tr'). AddClass ('visible'); $ ('# filter'). keyup (funktion (händelse) // om esc trycks eller inget anges om (event.keyCode == 27 || $ (this) .val () == ") // Om esc är tryckt vill vi rensa värdet av sökrutan $ (detta) .val ("); // Vi vill att varje rad ska vara synlig eftersom om inget // är inmatat matchas alla rader. $ ('tbody tr ') .removeClass (' visible '). show (). addClass (' visible '); // om det finns text, låter vi filtrera annat filter (' tbody tr ', $ (this) .val ());  // använd zebra rader $ ('. visible td'). removeClass ('udda'); zebraRows ('. visible: odd td', 'odd'); ... 

Denna kod är den överlägset mest komplexa vi har sett hittills så vi kommer att gå igenom det linjen för rad.

  1. Från och med addClass ("synlig") linje lägger vi till en klass
    av synliga för varje rad, eftersom de som standard är alla synliga.
  2. Nästa rad är din standardväljare, som i mitt fall är inriktad på min filter textrutan och säger
    varje gång en nyckel släpps för att utföra följande funktion. Observera att vi skickar in en parameter som heter händelse som har olika uppgifter om vad användaren
    gjorde precis som nyckeln de pressade.
  3. Följaktligen använder nästa rad kod den händelseparametern, vi har ett if-meddelande som kontrollerar om användaren trycker på
    Esc-tangenten. Det är viktigt att notera att varje nyckel är mappad till ett nummer och det är hur vår kod kan avgöra vilken nyckel användaren trycker på. Det här är en bra funktion
    så att användarna enkelt kan avbryta filtret och se all data igen. Många applikationer med filterlådor använder denna typ
    av funktionen, och vi vill vara säkra på att vår ansökan stannar i linje med vad som förväntas.
  4. På samma sätt om vi också tar hand om det särskilda fallet när
    Värdet på filterlådan är tomt (de slår bara backspace för att ta bort alla tecken). I det här fallet vill vi att användaren ska se alla raderna som verkar självklara, men
    Vi måste uttryckligen förse detta eftersom
    den filterfunktion som vi skrev tidigare skulle leta efter en rad som inte har något innehåll och vi kommer att gömma alla rader som har något innehåll, det exakta motsatsen till vad vi
    vilja!
  5. Om någon av dessa villkor är uppfyllda vill vi ställa in filterkassans värde till tomt om de trycks på esc, exekveras det också om värdet är tomt
    vilket inte spelar någon roll för oss.
  6. Nästa visar vi alla raderna som vi ville och lägger till en klass av synliga för dem alla. Återigen använder vi den säkra övningen av
    först avlägsna eventuella dröjande synliga klassdeklarationer för att undvika att du ställer in den dubbla. Om värdet på filterlådan inte är tomt och användaren inte tryckte på
    fly, vi vill faktiskt filtrera raderna.
  7. Så efter det andra uttalandet kallar vi vår filterfunktion från tidigare och ger raderna i vår bordkropp att fråga
    mot.
  8. Slutligen, efter att vi har gömt och visat lämpliga rader, vill vi sätta tillbaka zebraRows till de återstående synliga raderna. Först tar vi bort eventuella dröjsmål
    udda klassdeklarationer för att ta hand om de fall där en rad var udda och blir jämn. Samtalet till zebraRows är exakt samma som den första på sidbelastningen,
    förutom att vi bara bryr oss om de som för närvarande är synliga och udda.

Notera: En bra anteckning kan vara att använda CSS för att dölja filterrutan, och precis ovanför uppringningsanropet för att visa det, så användare med JavaScript-avstängt är inte
förvirrad när de försöker sortera data, så skulle det se ut som:

style.css
... #filter display: none;  ... 

application.js

... $ ('# filter'). Visa (); ... 

Wow det var mycket kod, gärna ta en te / kaffepaus innan vi fortsätter att sortera ...

Kolonn sortering

Okej, allt redo? Bra, låt oss gå!

Som den sista uppgiften kommer vi att tillåta sortering av tabellen med någon av kolumnrubrikerna. Detta är väldigt vanlig praxis som användare förväntar sig känd som klick för att sortera.
När användaren klickar på en av rubrikerna vill vi sortera bordet stigande och om de klickar igen vill vi sortera nedåt i den kolumnen. Denna kod är ganska
avancerad och inte för svimma i hjärtat. Det ursprungliga konceptet kom från
Lärande jQuery 1.3. jag
har omarbetat det för att bättre passa våra behov av enkelhet, men om du vill ha mer bra kornkontroll kommer jag att hänvisa dig till kapitel 7 i boken där
tabeller och jQuery diskuteras i stor detalj.

Innan vi verkligen dyker in i själva koden är det viktigt att vi diskuterar begreppet hur vi planerar att ta itu med detta problem. Vi kommer att använda javascript internt
sort () -metoden som är utformad för att ta en array och sortera den
med hjälp av en anpassad funktion som levereras av koden. I vårt fall vill vi helt enkelt sortera alfabetiskt och numeriskt, så vi ska bara jämföra de två objekten som levererar
och returnera vilken ordning de två ska gå in baserat på den designen. Eftersom vi vill sortera både stigande och nedåtgående använder vi en CSS-klassdeklaration för att se
vad är nuvarande sortsläge för den kolumnen och omvänd det om det behövs. När vi har vår array i ordning, kommer vi att använda ordningen för att sätta in raderna igen
in i bordet en-för-en. Det låter som mycket, men på grund av hur snabbt det är javascript, blir det väldigt smidigt för användaren. Alla
Detta kommer att vara knutet till klickhändelsen i kolumnrubrikerna i tabellen.

Som vanligt, låt oss få CSS-koden ur vägen, eftersom det är det enklaste.

 th.sortable color: # 666; markör: pekare; text-dekoration: understryka;  th.sortable: svävar färg: svart;  th.sorted-asc, th.sorted-desc färg: svart; 

Alla våra sorterbara rubriker kommer att ha en klass av sorterbar, och svängmodifieraren i CSS gör det emulera en hyperlänk för användare. Vi utnyttjar också
CSS klassen som vi nämnde om sorterad-asc och sorterad-desc så att vi kan visa användaren den nuvarande kolumnen som sorterar bordet. Jag inkluderade inte det men
Detta skulle vara ett bra ställe att lägga bakgrundsbilder av pilar som pekar upp och ner som en ytterligare visuell signal till användaren. Nu flyttar vi på JavaScript-koden och
komplexitet sortering, tack och lov lättat med jQuery. Koden nedan hör till funktionen ready (), vi började vägen tillbaka i början. Placerar den här rätten
över slutet av funktionen är bäst.

 // grab alla rubrikrader $ ('thead th'). varje (funktion (kolumn) $ (detta) .addClass ("sorterbar"). klicka (funktion () var findSortKey = funktion ($ cell) cell.find ('. sort-key') .text (). toUpperCase () + "+ $ cell.text (). toUpperCase ();; var sortDirection = $ (detta) .is ')? -1: 1; // steg tillbaka upp trädet och få raderna med data // för att sortera var $ rader = $ (detta) .parent () .förälder () .förälder (). Hitta (' tbody tr () (kolumn));); // jämför och sortera raderna alfabetiskt $ rows.sort (funktion (a, b) if (a.sortKey < b.sortKey) return -sortDirection; if (a.sortKey > b.sortKey) returnera sortDirection; returnera 0; ); // lägg till raderna i rätt ordning till botten av tabellen $ .each ($ rader, funktion (index, rad) $ ('tbody'). append (rad); row.sortKey = null;); // identifiera kolumn sorteringsordningen $ ('th'). removeClass ('sorterad-asc sorterad-desc'); var $ sortHead = $ ('th'). filter (': nth-child (' + (kolumn + 1) + ')'); sortDirection == 1? $ sortHead.addClass ('sorted-asc'): $ sortHead.addClass ('sorted-desc'); // identifiera kolumnen som ska sorteras efter $ ('td'). removeClass ('sorted') .filter (': nth-child (' + (kolumn + 1) + ')') .addClass ; $ ('. visible td'). removeClass ('udda'); zebraRows ('. visible: even td', 'odd'); ); );

Woo, det är mycket kod. Låt oss bryta ner det i stora bitar. Den första delen av koden tar alla huvudhuvud och slingrar igenom dem. Den första saken
det är att lägga till en klass av sorterbar, och börjar att klicka binda.

... // ta alla rubrikrader $ ('thead th'). Varje (funktion (kolumn) $ (detta) .addClass ("sorterbar"). Klicka (funktion 

Observera att det här enkelt kan ändras så att vissa kolumner kan sorteras genom att ta bort addClass () -samtalet och ändra väljaren från 'thead th' till
något som "thead th.sortable". Naturligtvis kräver det att du manuellt anger vilken av dina kolumner som ska sorteras genom att lägga till
lämpliga rubriker i HTML-koden.

Nästa bit kod är en funktionsdeklaration kopplad till en variabel. Det kan tyckas lite konstigt för dem som inte är kända för programmering, men det är vanligt. Detta
tillåter oss att lätt hänvisa till funktionen specifikt i samband med den rubrik vi jobbar med. Den förklaringen är förmodligen lite förvirrande, men
exakt resonemang slags överträffar omfattningen av denna artikel. Poängen med findSortKey-funktionen bestämmer vilken kolumn vi sorterar efter, vi kan göra det här
eftersom vi vet att elementet som de klickade på är samma index i tabellen för alla kolumner vi ska jämföra. Till exempel om de klickar på den tredje rubriken vi
vill titta på den tredje kolumnen i varje rad för att jämföra vilken ordning som ska placeras i raderna. När vi har deklarerat den här funktionen bestämmer vi sorteringsordningen, stigande
eller nedåtgående. Detta görs genom att leta efter klassnamnet "sorted-asc" i tabellrubriken om det är där vi vet att det för närvarande sorteras som stigande och
vi måste göra nedåtgående, annars använd standard som stigande. Detta tar hand om fallet där det faller och vi måste stiga uppåt igen.
Denna bit av kod returnerar 1 eller -1, vi kommer att förklara varför senare.

... var findSortKey = funktion ($ cell) return $ cell.find ('.sort-key') .text (). ToUpperCase () + "+ $ cell.text (). ToUpperCase ();; var sortDirection = $ (detta) .is ('. sorterat-asc')? -1: 1; ... 

Nu vill vi få den specifika kolumnen från varje rad och placera den i en matris, detta görs med jQuery-metoden för
get () som tar raderna och lägger dem i en array som sorten () -funktionen kan förstå. Eftersom strömmen
väljare var tabellhuvudet måste vi backa upp DOM-trädet 3 ställen för att hitta bordet> tbody> tr> td. Verkar lite komplicerat, men i verkligheten är det enkelt. Efter
att vi kretsar igenom var och en av raderna vi just hittat och hitta kolumnen vi vill använda vid sortering. Detta görs genom att kontrollera om dess index (antalet platser
börjar vid 0 från den första kolumnen i tabellen) är lika med indexet för den klickade rubriken. Detta skickas sedan in i funktionen FindSortKey så vi kan
Ange sedan ett anpassat attribut som heter sortKey som innehåller kolumnrubriken vi sorterar efter och texten i den aktuella kolumnen vi ser båda är
inställd på stor bokstav, så sorteringen är otillräcklig. Det här är ett sätt att effektivisera sorteringen så att vi gör det mer mottagligt för stora mängder data.

... // för sortering var $ rader = $ (detta) .parent () .förälder () .förälder (). Hitta ('tbody tr'). Get (); // loop genom alla rader och hitta $ .each ($ rader, funktion (index, rad) row.sortKey = findSortKey ($ (rad) .children ('td'). eq (kolumn));); ... 

Därefter kommer den faktiska sort () funktionen som jag har pågått om. Detta kallas för raden av rader som vi skapat med get (). Den enda parametern vi skickar är den
funktion vi vill bestämma sorteringen. Den funktionen får två attribut att jämföra och returnerar 1 om den första är större, -1 om den andra är stor och 0
om de är lika. Det här är variabel sortDirection kommer att spelas in, för det sätt som det fungerar är att vi ställer 1 eller -1 till det och multiplicerar sedan antingen
1 eller -1 ska funktionen returnera genom sortDirection, uppnå den stigande / nedåtgående påverkan vi önskar.

... // jämföra och sortera raderna alfabetiskt $ rows.sort (funktion (a, b) if (a.sortKey < b.sortKey) return -sortDirection; if (a.sortKey > b.sortKey) returnera sortDirection; returnera 0; ); ... 

Nästa bit kod lägger helt enkelt till varje rad från den nu sorterade matrisen tillbaka till DOM-strukturen. Detta görs med tilläggsfunktionen vilket är bra för det
kopierar inte raden och placerar den i slutet som den faktiskt tar bort den från den aktuella platsen i DOM och platser där vi berättar det, i det här fallet i slutet av
tabell. När det har gjort det för varje element i matrisen har det flyttat varje rad till sin nya plats. Också för att göra lite städning tar vi bort sortKey
attribut vi satt tidigare.

... // lägg till raderna i rätt ordning till botten av tabellen $ .each ($ rader, funktion (index, rad) $ ('tbody'). Append (rad); row.sortKey = null;) ; ... 

Nu går vi in ​​i städningsfasen av vår funktion, eftersom all den tunga lyftningen har gjorts. Nästa tar vi alla cellerna i bordskroppen, tar bort alla
långvariga sorterade attribut i klassdeklarationerna, och filtrera sedan ut allt utom kolumnerna som är samma index som vår sorterade rubrik och tillämpa den "sorterade"
klass till dem. Det här är trevligt för CSS-inriktning om vi till exempel vill göra kolumnen vi sorterar med en annan färg vi kan förklara denna CSS:

... sorterad bakgrundsfärg: grön;  ... 

Det sista vi gör är att ta bort alla "udda" CSS-deklarationer och använd Zebra Rows igen, precis som vi gjorde i filterdelen.

... $ ('. Visible td'). RemoveClass ('udda'); zebraRows ('. visible: even td', 'odd'); ... 

Så gör vi väldigt enkelt sortering. Det är viktigt att notera att detta endast sorterar objekt i alfabetiskt eller numeriskt format och fungerar inte med datum eller valuta
till exempel. Det kräver mer specialiserad hantering som ligger bortom våra mål om enkel tabellmanipulation.

Sammanfatta

I den här artikeln lärde vi oss att rulla vår egen tabellmanipulationskod med jQuery. Detta är
mycket bekvämt för både användaren och oss. Användaren får de förväntade kontrollerna för sortering och filtrering av data och vi har kod som är både liten och lätt att
förstå. Eftersom vi skrev det här själva kan vi nu utöka det på våra egna sätt. Vår metod är utmärkt för enkel manipulation, men om du behöver kökshandfat rekommenderar jag att du tittar på
Datatabeller plugin för jQuery. Jag skulle gärna svara på några frågor i kommentarerna eller på Twitter
(@Noahendrix). Tack för att du läser!

  • Följ oss på Twitter, eller prenumerera på NETTUTS RSS-flödet för fler dagliga webbutvecklingstoppar och artiklar.