Hur man bygger en vacker kalender widget

I dagens premie handledning och screencast, Jag ska visa dig hur man bygger en smidig kalender widget. Vi använder CSS3 för att ge det ett blankt utseende och sedan lägga till en ganska snygg funktionalitet med JavaScript.


Steg 0. Idén

Jag följer bloggen 365PSD, en riktigt snygg webbplats som erbjuder en gratis PSD-vanligtvis en liten bit av användargränssnitt-varje dag. För dag 81 var det riktigt snygg kalender widget. Jag tänkte att det inte skulle bli för svårt att bygga den riktiga saken, så jag ska visa dig hur man gör det idag!



Steg 1. HTML

Vi börjar med att bygga vår HTML-struktur. Självklart börjar vi med skelettet:

     Kalender Widget     

Så, inuti kroppen börjar vi med en div att paketera allt då har vi tre huvuddelar inom det:

 

Först har vi div.header; tittar tillbaka på vår PSD, kan vi se att detta motsvarar den övre delen, den sektionen som håller månaden, månadsledarna och bindningarna. Då har vi ett bord för dagens namn. Slutligen har vi en div # cal-frame. Detta är kalendernätet.

Jag släpper in på en hemlighet: När jag ursprungligen byggde den här kalendergränssnittet hade jag bara ett bord med en thead för dagarna och a tbody för kalendernätet; men när jag började skriva JavaScript för att växla mellan månader blev det uppenbart att jag behövde använda något mer flexibelt. Du kommer att se varför när vi kommer till JavaScript.

Så kasta upp det i den här rubriken:

    20 juni och 0   

Vi har fem element här; på utsidan har vi vänster och höger kalenderomkopplare; Eftersom jag inte ville använda några bilder i det här projektet hittade jag HTML-enheterna ⟨ och & rang (⟨och ⌫, respektive). Sedan har vi två tomma spänningar för kalenderbindningarna. Slutligen har vi månad / år etiketten i mitten.

Innehållet för tabell # dagar är ganska enkelt:

 Sol mon tue wed thu fre sat

Slutligen har vi tarmarna av div # cal-frame; kolla in det och sedan diskuterar vi det:

 
12345
6789101112
13141516171819
20212223242526
27282930

Full Screencast



Så vad har vi kommit hit? I grund och botten skapar vi kalendernätet med ett bord (senare lägger vi in ​​den aktuella månaden dynamiskt). Lämpliga celler har datumnummer; om cellerna är tomma har de klassen "noll"; Slutligen har dagens datum klassen "idag".

Och det är verkligen HTML-omfattningen. det finns inte mycket att se just nu, men här är vad vi har:



Steg 2. CSS

Låt oss börja med en viss miljö:

 kropp bakgrund: # e0e0e0;  #cal -moz-box-shadow: 0px 3px 3px rgba (0, 0, 0, 0.25); -webkit-box-skugga: 0px 3px 3px rgba (0, 0, 0, 0,25); marginal: 50px auto; typsnitt: 13px / 1.5 "Helvetica Neue", Helvatica, Arial, san-serif; display: tabell; 

Ganska uppenbart, va? När vi har ställt in en bakgrundsfärg centrerar vi kalendergardinen horisontellt och ger den en rutaskugga. Självklart ställer vi in ​​teckensnittet. Men varför ställer vi skärmen på bordet? Som standard a div kommer att visas i block, vilket innebär att det tar upp hela tillgänglig bredd; genom att visa den som ett bord, tar den upp den minsta bredden den kan (samtidigt som den innehåller barn) och fortfarande är ett blockelement.

Låt oss sedan fokusera på rubrikfältet:

 #cal .header cursor: default; bakgrund: # cd310d; bakgrund: -moz-linjär-gradient (topp, # b32b0c, # cd310d); bakgrund: -webkit-gradient (linjär, vänster topp, vänster botten, från (# b32b0c), till (# cd310d)); höjd: 34px; position: relativ; färg: #fff; -webkit-gränsen-övre-vänstra radien: 5px; -webkit-gräns-topp-högra radie: 5px; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; gränsen till vänstra radien: 5px; gränsen till höger-radie: 5px; font-weight: bold; textskugga: 0px -1px 0 # 87260C; text-transform: stor bokstav;  #cal .header span display: inline-block; line-height: 34px; 

Här är den första delen av rubrikstyling; vi börjar med att placera markören på en pekare; På så vis verkar texten inte vara valbar. Sedan ställer vi in ​​en röd bakgrundsfärg; Om webbläsaren stöder det använder vi dock en bakgrundsgradient: glöm inte att lägga till den för både mozilla och webkit! Ställ sedan höjden på 34px; Vi ställer ställningen till relativ, eftersom barnen kommer att vara helt placerade; Genom att placera moderelementet relativt, kommer barnen att placeras absolut mot föräldern istället för kroppen. Ställ in textfärgen till vit, runt de övre vänstra och högra hörnen, och gör teckensnittet djärvt. Sedan, ge en liten textskugga för att få texten att se indryckta. Slutligen omvandla texten till stor bokstav.

Var och en av föremålen i rubriken är a spänna; var och en av dessa kommer att visas som ett inline-block. Ge också en linjehöjd på 34px (höjden på huvudet).

Dessa spänner har också några speciella klasser, så låt oss titta på dem:

 #cal .header .hook width: 9px; höjd: 28px; position: absolut; botten: 60%; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; bakgrund: #ececec; bakgrund: -moz-linjär-gradient (höger topp, #fff, # 827e7d); bakgrund: -webkit-gradient (linjär, höger topp, högerbotten, från (#fff) till (# 827e7d)); boxskugga: 0px -1px 2px rgba (0, 0, 0, 0,65); -moz-box-skugga: 0px -1px 2px rgba (0, 0, 0, 0,65); -webkit-box-skugga: 0px -1px 2px rgba (0, 0, 0, 0,65);  .right.hook höger: 15%;  .left.hook vänster: 15%; 

För det första har vi "krok" klassen; kom ihåg att det här är kakorna eller bindningarna i kalendern. Vi ställer in bredd och höjd. Placera sedan det absolut. Sedan flyttar vi upp den från botten 60%. Vi runda runt hörnet för att bindningarna ska se runt. Då ställer vi in ​​en bakgrundsfärg; Om webbläsaren stöder gradienter överstyrar vi den solida bakgrunden med en gradient. Då ska vi ge dem en boxskugga.

Vi använder sedan placeringsklasserna för att placera krokarna horisontellt; om elementet har både "krok" och "höger" klass, flytta den 15% från höger; om den har klassen "vänster", flytta den 15% från vänster.

Nu har vi knapparna för månadsläge:

 #cal .header. button width: 24px; text-align: center; position: absolute;  #cal .header .left.button left: 0; -webkit-gränsen-övre-vänstra radien: 5px; -moz-border-radius-topleft: 5px; gränsen till vänstra radien: 5px; gränsen-höger: 1px solid # ae2a0c;  #cal .header. right.button höger: 0; top: 0; gränsen till vänster: 1px solid # ae2a0c; -webkit-gräns-topp-högra radie: 5px; -moz-border-radius-topright: 5px; gränsen till höger-radie: 5px;  #cal .header. button: svävar (bakgrund: -moz-linjär-gradient (topp, # d94215, # bb330f); bakgrund: -webkit-gradient (linjär, vänster topp, vänster botten, från (# d94215), till (# bb330f)); 

Vi ställer in bredden på dessa knappar och centrerar texten. Naturligtvis måste vi också placera dem absolut. Sedan, för den vänstra knappen, flyttar vi hela vägen till vänster och runt överst till vänster för. För höger knapp går den till höger och rundar det övre högra hörnet.

Slutligen lägger vi till en svängareffekt för knapparna; Naturligtvis använder vi en gradient.

Det finns ytterligare ett element att stila: det är månadslabeln.

 #cal .header .month-år brevavstånd: 1px; bredd: 100%; text-align: center; 

Vi använder teckenavstånd för att ge karaktärerna lite mer andningsrum. Sedan ger vi spännvidden en bredd på 100% och centrerar texten. Eftersom alla syskonelementen är placerade absolut, ger detta hela bredden precis vad vi vill ha.

Så det är hela huvudet! Jag borde nämna det trots att vi har placerat det mesta av elementen helt, eftersom vi använder procentandelar för att placera dem, vetter allt perfekt när du höjer eller sänker teckenstorleken i webbläsaren.

Okej, låt oss gå vidare till daghuvudena.

 #cal tabell bakgrund: #fff; border-kollaps: kollaps;  #cal td färg: # 2b2b2b; bredd: 30px; höjd: 30px; line-height: 30px; text-align: center; gränsen: 1px solid # e6e6e6; markör: default;  #cal #days td höjd: 26px; linjehöjd: 26px; text-trans: versaler; font-size: 90%; color: # 9e9e9e;  #cal #days td: not (: last-child) gränsen-höger: 1px solid #fff; 

Vi börjar med två lite mer generiska väljare: daghuvudet och kalendernätet är båda tabellerna, så den första regeln gäller för dem båda: vi ställer bakgrunden till vit och kolliderar gränserna. När bordgränserna är kollapsade, har de ingen polstring mellan dem och angränsande celler delar gränser. Sedan, för alla bordcellerna, ger vi dem en textfärg, sätter bredden, höjden och linjens höjd till 30px och centrerar texten. De får alla en gräns och standardmarkören (en pil / pekare);

Sedan lägger vi till en viss specifik styling för tabellcellerna i dagtabellen: vi minskar deras höjd och linjehöjd en bit, se till att de är stora och återställ teckensnittsstorleken och textfärgen. ( Notera: i den medföljande skärmbilden skrev jag #dag istället för #days i väljaren för det tredje blocket ovan och aldrig korrigerat det; se till att du får det rätt!)

Vad är den slutliga regeln ovan för? Tja, för närvarande finns det ljusgränsa på dagens namnceller. Vi vill ändra gränserna på höger till vit, så de är inte synliga. Vi vill dock inte göra det till den sista cellen i raden. Så vi kan använda två pseudoklasser. : kommer inte att ta en exklusiv väljare "parameter." : sista barnet griper det sista barnet av de element som vi redan har valt: i det här fallet är det tabellcellerna. Sedan satte vi just den rätta gränsen till fast vit.

 #cal # cal-frame td.today bakgrund: #ededed; color: # 8c8c8c; boxskugga: 1px 1px 0px #fff inset; -moz-box-skugga: 1px 1px 0px #fff-inset; -webkit-box-skugga: 1px 1px 0px #fff inset;  #cal # cal-frame td: inte (.nil): svävar färg: #fff; textskugga: # 6C1A07 0px -1px; Bakgrund: # CD310D; bakgrund: -moz-linjär-gradient (topp, # b32b0c, # cd310d); bakgrund: -webkit-gradient (linjär, vänster topp, vänster botten, från (# b32b0c), till (# cd310d)); -moz-box-skugga: 0px 0px 0px; -webkit-box-skugga: 0px 0px 0px; 

Dessa två regler är inriktade på kalendernätet. För tabellcellen med klassen "idag" ställer vi bakgrunden till en ljusgrå och texten till en mörkgrå. Därefter sätter vi en boxskugga: det är en vit skugga, men vi använder inte någon suddighet, så det är en vit linje; Vi skjuter upp den och till den högra pixeln, så vi får en sekundärgränsseffekt. Observera att vi lägger till "inset" i boxskuggdeklarationen, så att skuggan är inne i cellen.

Nästa regel gäller en svängningseffekt för alla bordceller i kalendern, förutom de som med klassen "noll"; Vi ställer texten till vit och lägger till en textskugga. Sedan ändrar vi bakgrunden till rött, använder en gradient när vi kan. Vi inkluderar borttagning av boxskuggan speciellt för "idag" -cellen.

Det finns ett speciellt fall som vi inte har nämnt än ta tag i din närmaste kalender-nej, inte iCal, jag pratar om en riktig dagbok för papper och titta på, åh, säg, oktober 2010. Du kommer märka att det finns den sista veckan med en fördubblad cell, med både 24th och 31st i samma torg. Vi måste göra det, så låt oss stila för det.

Sättet som vi markerar det är att sätta varje datum i en spänn inuti bordcellen.

 #cal # cal-frame td span font-size: 80%; positioner: relativ;  #cal # cal-frame td span: första barnet botten: 5px;  #cal # cal-frame td span: sista barnet topp: 5px; 

Först placerar vi spännas relativt och krympa deras typsnitt bara ett hår; Sedan flytta vi den första upp 5px och den andra ned 5px.

Vi gör ännu en sak; När vi byter mellan månader vill vi blekna från varandra till varandra. Detta kräver att de två tabellerna ligger ovanpå varandra. Vi kan uppnå det med detta:

 #cal # cal-frame table.curr float: left;  #cal # cal-frame table.temp position: absolute; 

Den vi försvinner kommer att ha en klass av "temp"; den nya som vi tar med oss ​​för att stanna (ett tag) kommer att ha klassen "curr."

Och det är det för CSS! Låt oss nu gå vidare till någon funktionalitet.


Steg 3. JavaScript

Vi gör funktionen för vår kalender lätt att återanvända. I ljuset av det börjar vi med detta skelett:

 Var CALENDAR = funktion () varla, etikett, månader = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September" , "Oktober", "november", "december"); funktion init (newWrap)  funktion switchMonth (nästa, månad, år)  funktion createCal (år, månad)  ​​createCal.cache = ; returnera init: init, switchMonth: switchMonth, createCal: createCal; ;

Så vi skapar tre funktioner inom vår CALENDAR-funktion; en kommer att initiera kalender widgeten, den andra kommer att flytta mellan månader, och den tredje kommer faktiskt att skapa kalendernätet; märka den raden efter det: createCal.cache = ; vi diskuterar det också!

Vi skapade också tre variabler överst: vi ska ge slå in och märka värden inom i det, men månader är en matris med namnen på månaderna.

Här är innehållet i vårt i det fungera:

 wrap = $ (newWrap || "#cal"); label = wrap.find ("# label"); wrap.find ("# prev"). bind ("click.calendar", funktion () switchMonth (false);); wrap.find ("# next"). bind ("click.calendar", funktion () switchMonth (true);); label.bind ("click", funktion () switchMonth (null, ny Date (). getMonth (), ny Date (). getFullYear ());); label.click ();

Vi börjar med att ge slå in och märka lämpliga värden: varsel att vi använder väljaren skickad till i det att hitta wrap men faller tillbaka till "#cal" om man inte tillhandahåller. Sedan binder vi på händelser till nästa och föregående kalenderknappar. dessa kallar switchMonth fungera; Om vi ​​vill ha nästa kalender passerar vi sant, annars passerar vi falskt.

dock, switchMonth kan också ta ytterligare två parametrar; Vi använder dem för klickhändelsen på etiketten. När användaren klickar på månadsnamnet växlar vi till den aktuella månaden. så vi kommer att passera i den aktuella månaden och året, vilket vi kan få från JavaScript Datum objekt. Glöm inte att ställa in nästa parameter till null!

En sak (och ett bonusspets, det är inte i skärmen!): När användaren laddar sidan vill vi ladda den rätta månaden under den månad som är kodad. Det enklaste sättet att göra det är att ringa jQuery-klickmetoden på etiketten utan några parametrar; Detta simulerar ett musklick och tar kalendern till den aktuella månaden.

Låt oss gå vidare till switchMonth fungera:

 var curr = label.text (). trim (). split (""), kalender, tempYear = parseInt (curr [1], 10); månad = månad || ((curr = 0) === "december")? 0: months.indexOf (curr [0]) + 1): ((curr [0] === "januari")? 11: months.indexOf (curr [0]) - 1)); år = år || ((nästa && månad === 0)? tempYear + 1: (! nästa && månad === 11)? tempYear - 1: tempYear);

Vi ställer upp några variabler överst; Vi delar upp etiketten i en upptagen grupp curr; vi skapar också en kalender variabel och ta tag i kalenderåret som visas.

Då blir det komplicerat. Jag har använt javascript villkorliga operatörer här så jag kan lägga allt på en rad. Snarare än att försöka förklara det, kolla här: det här är vad de gör:

 om (! månad) om (nästa) om (curr [0] === "december") månad = 0;  annat månad = months.indexOf (curr [0]) + 1;  annars om (curr [0] === "januari") month = 11;  annat månad = months.indexOf (curr [0]) - 1; 

Du kan se varför den villkorliga operatören är attraktiv: det är bara en rad. Här är den utvidgade versionen av året variabel:

 om (! år) om (nästa && månad === 0) år = tempYear + 1;  annars om (! nästa && månad === 11) år = tempYear - 1;  annat år = tempYear; 

I slutet av allt, månad och år kommer att vara de korrekta värdena för kalendern som vi försöker visa användaren. Om du skulle känna sig mer bekväm kan du ersätta de två raderna med utdragna ovan.

Nästa skapar vi kalendern och justerar DOM i enlighet med följande:

 kalender = createCal (år, månad); $ ("# cal-frame", wrap) .find (". curr") .removeClass ("curr") .addClass ("temp") .end () .prepend (calendar.calendar ()) .find (" .temp ") .fadeOut (" slow ", function () $ (this) .remove ();); $ (# Etiketten ") text (calendar.label).

Vad finns i kalenderobjektet som returnerar från createCal fungera? Det är ett objekt, så här:

 kalender: funktion () / * returnerar ett jquery-objekt i kalendern * /, etikett: "Månadår"

Vi diskuterar varför kalenderegenskapen är en metod när vi kommer att bygga den. För nu, låt oss återvända till att sätta den på sidan. Vi kommer att få kalenderramen och hitta den kurrerade kalendern. Sedan tar vi bort klassen "curr" och tillämpar klassen "temp"; då lägger vi in ​​den nya kalendern (som förresten kommer med klassen "curr") och bleknar ut och tar bort den gamla.

Jo, vi har bara en funktion att gå: createCal.

 var dag = 1, jag, j, haveDays = true, startDay = ny Datum (år, månad, dag) .getDay (), daysInMonths = [31, ((år% 4 == 0) && (år% 100! = 0)) || (år% 400 == 0))? 29: 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], kalender = [];

Här är vår start: variablerna. Vi har dag, inställd på 1; vi har två vars för iteration: jag och j. Sedan räknar vi ut vilken dag i veckan månaden börjar Vi kan göra detta genom att skapa ett datumobjekt för första dagen i månaden och ringa getDay.

Därefter skapar vi en array som håller antalet dagar i varje månad. för februari måste vi redogöra för språngår, så använd ett annat ternärt uttryck för att beräkna det.

Slutligen har vi det mycket viktiga kalender variabel, vilken är en array.

Därefter vill vi använda det cache egendom vi sätter på createCal fungera. (Eftersom allt i JavaScript är ett objekt kan även funktioner ha egenskaper.)

 om (createCal.cache [år]) if (createCal.cache [år] [månad]) return createCal.cache [år] [månad];  else createCal.cache [year] = ; 

Det här är vad som händer: det finns en möjlighet att användaren kommer att "begära" samma månad mer än en gång. När vi skapar det första gången behöver vi inte göra det igen. Vi lägger det i cachen och drar ut det senare.

Om det cache objektet har en egendom med namnet på det år vi letar efter, vi kan sedan kontrollera månadens tillgänglighet Om vi ​​redan har gjort månaden returnerar vi det cachade objektet. Om det inte finns någon egendom för året gör vi det, för vi måste lägga den månad som vi ska skapa i den.

Om vi ​​passerar den här punkten måste vi börja skapa kalendern för den begärda månaden.

 i = 0; medan (haveDays) calendar [i] = []; för (j = 0; j < 7; j++)  if (i === 0)  if (j === startDay)  calendar[i][j] = day++; startDay++;   else if (day <= daysInMonths[month])  calendar[i][j] = day++;  else  calendar[i][j] = ""; haveDays = false;  if (day > daysInMonths [month]) hasDays = false;  i ++; 

Detta är en komplicerad bit; medan haveDays variabeln är sant, vi vet att vi har dagar kvar i månaden. Därför använder vi vår jag iterator för att lägga till en vecka-array i kalendermatrisen. Sedan använder vi en för-loop på j iterator, medan det är mindre än 7; Sedan vi börjar med 0, kommer det att ge oss 7 dagar för veckoslutet. Inom vår for-loop finns det tre fall.

Först måste vi kontrollera om vi är i den första veckan i månaden; Om vi ​​är, börjar vi inte nödvändigtvis på den första dagen. Vi vet redan vilken dag månaden börjar det finns i vårt startDay variabel. Därför, om j === startDay, vi är på rätt dag för att börja, så vi ska värdera dag i rätt plats. Då ökar vi dag och startDay av en. Nästa gång 'round the for-loop, j och startDay kommer att vara densamma, så det fortsätter att fungera under resten av veckan.

Om vi ​​inte är i den första veckan (jag! == 0), så ska vi se till att vi fortfarande har dagar kvar för att lägga till kalendern; Om så är fallet slår vi dem på plats. Slutligen, om vi inte är på den första veckan och vi inte har några dagar kvar för att lägga till månaden, lägger vi in ​​en tom sträng istället. Sedan ställer vi in haveDays till falskt.

I slutet kommer vi att kolla för att se om dag är större än antalet dagar i månaden; om det är så ställer vi in haveDays till falskt. Detta är för det speciella fallet där månaden slutar på en lördag.

Naturligtvis glöm inte att öka jag strax utanför förbandet!

 om (kalender [5]) för (i = 0; i < calendar[5].length; i++)  if (calendar[5][i] !== "")  calendar[4][i] = ""+ kalender [4] [i] +""+ kalender [5] [i] +""; kalender = calendar.slice (0, 5);

Vi vill inte att vår kalender ska ha mer än 5 veckor; Om en dag eller två släpper in i vecka 6 delar vi cellerna i vecka 5, som vi har förberett för i vår CSS. Så, om det finns en 6th array i kalendermatrisen slår vi över den. Om innehållet i array-elementet inte är en tom sträng kommer vi sedan omfördela värdet av cellen direkt "över" rad 6: vi sätter in det här värdet i ett span och sammanfogar ett annat span med lämpligt värde i rad 6 inuti. Det är vettigt, rätt?

När vi har allt på plats, skar vi det sista elementet ur kalender.

 för (i = 0; i < calendar.length; i++)  calendar[i] = ""+ kalender [i] .join ("") +""; kalender = $ (""+ calendar.join (" ") +"
") .addClass (" curr "); $ (" td: tom ", kalender) .addClass (" nil "); om (månad === ny Datum (). getMonth ()) $ ('td' kalender) .filter (funktion () return $ (this) .text () === nytt datum (). getDate (). toString ();). addClass ("idag"); createCal.cache ] [month] = kalender: funktion () return calendar.clone (), etikett: månader [månad] + "" + år, returnera createCal.cache [år] [månad];

Nu är det dags att sammanfatta varje vecka i vårt kalender; vi slungar över var och en i en för-loop och vrider in uppgifterna i tabellrader, med varje dag i en tabellcell. Därefter vänder vi hela grejen till ett jQuery-objekt, efter att vi har lagt alla bordserierna samman och lindat dem med ett bord. Vi lägger sedan till klassen "curr" till den tabellen.

Alla tomma tabellceller (vi kan använda jQuery pseudo-väljaren: tom för att hitta dem), få ​​klassen "noll".

Om vi ​​skapar en kalender för den aktuella månaden hittar vi cellen för idag och ger den klassen "idag"; Vi kan hitta det genom att skicka en funktion till jQuery-filtermetoden. Funktionen returnerar sant om texten i cellen matchar datumet.

Slutligen skapar vi vårt färdiga objekt och lägger det i cachen. Varför gör vi det kalender egendom en funktion? Tja, om vi just återvänt ett jQuery-objekt, när vi en gång lagt till det i kalendern och sedan vidare till en annan månad, skulle tabellen tas bort från DOM; senare, om vi kommer tillbaka till den månaden kommer elementet inte att visas eftersom cachen refererar till samma DOM-element. Så vi använder jQuerys klonmetod för att returnera en kopia av DOM-elementet. Då får etiketten månadsnamnet från månadssamlingen och sammanfogas det med året. Slutligen returnerar vi objektet.

Var gjort! Tillbaka i index.html-filen lägger vi till en skriptikett med följande:

 var cal = KALENDER (); cal.init ();

Det är allt! Så här ser vår färdiga produkt ut!


Men jag kan inte visa dig funktionaliteten; du måste själv kolla in koden! Tack för att du läser!