fullcalendar.js 192 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196
  1. /**
  2. * @preserve
  3. * FullCalendar v1.5.4
  4. * http://arshaw.com/fullcalendar/
  5. *
  6. * Use fullcalendar.css for basic styling.
  7. * For event drag & drop, requires jQuery UI draggable.
  8. * For event resizing, requires jQuery UI resizable.
  9. *
  10. * Copyright (c) 2011 Adam Shaw
  11. * Dual licensed under the MIT and GPL licenses, located in
  12. * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
  13. *
  14. * Date: Tue Sep 4 23:38:33 2012 -0700
  15. *
  16. */
  17. (function($, undefined) {
  18. var defaults = {
  19. // display
  20. defaultView: 'month',
  21. aspectRatio: 1.35,
  22. header: {
  23. left: 'title',
  24. center: '',
  25. right: 'today prev,next'
  26. },
  27. weekends: true,
  28. currentTimeIndicator: false,
  29. // editing
  30. //editable: false,
  31. //disableDragging: false,
  32. //disableResizing: false,
  33. allDayDefault: true,
  34. ignoreTimezone: true,
  35. // event ajax
  36. lazyFetching: true,
  37. startParam: 'start',
  38. endParam: 'end',
  39. // time formats
  40. titleFormat: {
  41. month: 'MMMM yyyy',
  42. multiWeek: "MMM d[ yyyy]{ '–'[ MMM] d yyyy}",
  43. week: "MMM d[ yyyy]{ '–'[ MMM] d yyyy}",
  44. day: 'dddd, MMM d, yyyy',
  45. list: 'MMM d, yyyy',
  46. table: "MMM d[ yyyy]{ '–'[ MMM] d yyyy}",
  47. todo: "MMM d[ yyyy]{ '–'[ MMM] d yyyy}",
  48. },
  49. columnFormat: {
  50. month: 'ddd',
  51. multiWeek: 'ddd',
  52. week: 'ddd M/d',
  53. day: 'dddd M/d',
  54. list: 'dddd, MMM d, yyyy',
  55. table: 'MMM d, yyyy',
  56. todo: 'MMM d, yyyy',
  57. },
  58. timeFormat: { // for event elements
  59. '': 'h(:mm)t', // default
  60. agenda: 'h:mm{ – h:mm}', //agenda views
  61. list: 'hh:mm{ – hh:mm}', //list and table views
  62. listFull: 'hh:mm M d yyyy{ – hh:mm M d yyyy}', //list and table views for events that span multiple days
  63. listFullAllDay: 'M d yyyy{ – M d yyyy}', //list and table views for allday events that span multiple days
  64. },
  65. // locale
  66. isRTL: false,
  67. firstDay: 0,
  68. weekendDays: [0, 6],
  69. monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
  70. monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
  71. dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
  72. dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
  73. buttonText: {
  74. prev: ' ❮ ',
  75. next: ' ❯ ',
  76. prevYear: ' << ',
  77. nextYear: ' >> ',
  78. today: 'today',
  79. month: 'month',
  80. multiWeek: 'mweek',
  81. week: 'week',
  82. day: 'day',
  83. list: 'list',
  84. table: 'table',
  85. todo: 'todo',
  86. prevMonth: 'Load previous month',
  87. nextMonth: 'Load next month',
  88. filtersHeader: 'Filters',
  89. filtersFooter: '* completed at or after %date%',
  90. filterAction: 'Needs action',
  91. filterProgress: 'In progress',
  92. filterCompleted: 'Completed',
  93. filterCanceled: 'Canceled'
  94. },
  95. listTexts: {
  96. until: 'until',
  97. past: 'Past events',
  98. today: 'Today',
  99. tomorrow: 'Tomorrow',
  100. thisWeek: 'This week',
  101. nextWeek: 'Next week',
  102. thisMonth: 'This month',
  103. nextMonth: 'Next month',
  104. future: 'Future events',
  105. week: 'W'
  106. },
  107. // list/table options
  108. listSections: 'smart', // false|'day'|'week'|'month'|'smart'
  109. listRange: 30, // number of days to be displayed
  110. listPage: 7, // number of days to jump when paging
  111. tableCols: ['handle', 'date', 'time', 'title'],
  112. todoCols: ['handle', 'check', 'priority', 'time', 'title', 'location', 'status', 'percent'],
  113. todoColThresholds: [],
  114. todoOptionalCols: [],
  115. //defaultFilters: ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'],
  116. defaultFilters: [],
  117. // jquery-ui theming
  118. theme: false,
  119. buttonIcons: {
  120. prev: 'circle-triangle-w',
  121. next: 'circle-triangle-e'
  122. },
  123. //selectable: false,
  124. unselectAuto: true,
  125. dropAccept: '*',
  126. headerContainer: false,
  127. bindingMode: 'single',
  128. dayEventSizeStrict: false,
  129. startOfBusiness: 0,
  130. endOfBusiness: 0,
  131. showWeekNumbers: true,
  132. multiWeekSize: 3,
  133. showDatepicker: false,
  134. eventMode: true,
  135. showUnstartedEvents: false,
  136. simpleFilters: false,
  137. };
  138. // right-to-left defaults
  139. var rtlDefaults = {
  140. header: {
  141. left: 'next,prev today',
  142. center: '',
  143. right: 'title'
  144. },
  145. headerContainer: '',
  146. buttonText: {
  147. prev: ' ► ',
  148. next: ' ◄ ',
  149. prevYear: ' >> ',
  150. nextYear: ' << '
  151. },
  152. buttonIcons: {
  153. prev: 'circle-triangle-e',
  154. next: 'circle-triangle-w'
  155. }
  156. };
  157. var fc = $.fullCalendar = { version: "1.5.4" };
  158. var fcViews = fc.views = {};
  159. $.fn.fullCalendar = function(options) {
  160. // method calling
  161. if (typeof options == 'string') {
  162. var args = Array.prototype.slice.call(arguments, 1);
  163. var res;
  164. this.each(function() {
  165. var calendar = $.data(this, 'fullCalendar');
  166. if (calendar && $.isFunction(calendar[options])) {
  167. var r = calendar[options].apply(calendar, args);
  168. if (res === undefined) {
  169. res = r;
  170. }
  171. if (options == 'destroy') {
  172. $.removeData(this, 'fullCalendar');
  173. }
  174. }
  175. });
  176. if (res !== undefined) {
  177. return res;
  178. }
  179. return this;
  180. }
  181. // would like to have this logic in EventManager, but needs to happen before options are recursively extended
  182. var eventSources = options.eventSources || [];
  183. delete options.eventSources;
  184. if (options.events) {
  185. eventSources.push(options.events);
  186. delete options.events;
  187. }
  188. options = $.extend(true, {},
  189. defaults,
  190. (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
  191. options
  192. );
  193. this.each(function(i, _element) {
  194. var element = $(_element);
  195. var calendar = new Calendar(element, options, eventSources);
  196. element.data('fullCalendar', calendar); // TODO: look into memory leak implications
  197. calendar.render();
  198. });
  199. return this;
  200. };
  201. // function for adding/overriding defaults
  202. function setDefaults(d) {
  203. $.extend(true, defaults, d);
  204. }
  205. function Calendar(element, options, eventSources) {
  206. var t = this;
  207. // exports
  208. t.options = options;
  209. t.render = render;
  210. t.destroy = destroy;
  211. t.refetchEvents = refetchEvents;
  212. t.reportEvents = reportEvents;
  213. t.reportEventChange = reportEventChange;
  214. t.rerenderEvents = rerenderEvents;
  215. t.changeView = changeView;
  216. t.select = select;
  217. t.unselect = unselect;
  218. t.prev = prev;
  219. t.next = next;
  220. t.prevYear = prevYear;
  221. t.nextYear = nextYear;
  222. t.today = today;
  223. t.findToday = findToday;
  224. t.gotoDate = gotoDate;
  225. t.incrementDate = incrementDate;
  226. t.formatDate = function(format, date) { return formatDate(format, date, options) };
  227. t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
  228. t.getDate = getDate;
  229. t.getView = getView;
  230. t.option = option;
  231. t.trigger = trigger;
  232. t.selectEvent = selectEvent;
  233. t.allowSelectEvent = allowSelectEvent;
  234. t.updateToday = updateToday;
  235. t.updateGrid = updateGrid;
  236. t.renderViews = renderViews;
  237. t.setOptions = setOptions;
  238. t.getOption = getOption;
  239. t.viewInstances = {};
  240. // imports
  241. EventManager.call(t, options, eventSources);
  242. var isFetchNeeded = t.isFetchNeeded;
  243. var fetchEvents = t.fetchEvents;
  244. // locals
  245. var _element = element[0];
  246. var header;
  247. var headerElement;
  248. var content;
  249. var tm; // for making theme classes
  250. var currentView;
  251. var elementOuterWidth;
  252. var suggestedViewHeight;
  253. var absoluteViewElement;
  254. var resizeUID = 0;
  255. var ignoreWindowResize = 0;
  256. var date = new Date();
  257. var events = [];
  258. var _dragElement;
  259. /* Main Rendering
  260. -----------------------------------------------------------------------------*/
  261. setYMD(date, options.year, options.month, options.date);
  262. function render(inc) {
  263. if (!content) {
  264. initialRender();
  265. }else{
  266. calcSize();
  267. markSizesDirty();
  268. markEventsDirty();
  269. renderView(inc);
  270. }
  271. }
  272. function initialRender() {
  273. tm = options.theme ? 'ui' : 'fc';
  274. element.addClass('fc');
  275. if (options.isRTL) {
  276. element.addClass('fc-rtl');
  277. }
  278. if (options.theme) {
  279. element.addClass('ui-widget');
  280. }
  281. content = $("<div class='fc-content' style='position:relative'/>")
  282. .prependTo(element);
  283. header = new Header(t, options);
  284. headerElement = header.render();
  285. if (headerElement) {
  286. options.headerContainer ? options.headerContainer.prepend(headerElement) : element.prepend(headerElement);
  287. }
  288. changeView(options.defaultView);
  289. $(window).resize(windowResize);
  290. // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
  291. if (!bodyVisible()) {
  292. lateRender();
  293. }
  294. }
  295. // called when we know the calendar couldn't be rendered when it was initialized,
  296. // but we think it's ready now
  297. function lateRender() {
  298. setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
  299. if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
  300. renderView();
  301. }
  302. },0);
  303. }
  304. function updateToday()
  305. {
  306. for(var view in t.viewInstances)
  307. t.viewInstances[view].updateToday();
  308. }
  309. function updateGrid()
  310. {
  311. for(var view in t.viewInstances)
  312. t.viewInstances[view].updateGrid();
  313. }
  314. function renderViews()
  315. {
  316. //Force rerender of all views
  317. for(var view in t.viewInstances)
  318. t.viewInstances[view].start=null;
  319. renderView();
  320. }
  321. function setOptions(newOptions)
  322. {
  323. var rerender=false;
  324. $.each(newOptions, function(key,value){
  325. if($.isPlainObject(value))
  326. $.extend(options[key],value);
  327. else
  328. options[key]=value;
  329. if(key=='firstDay' || key=='timeFormat')
  330. rerender=true;
  331. else
  332. {
  333. for(var view in t.viewInstances)
  334. t.viewInstances[view]['set'+key.charAt(0).toUpperCase()+key.slice(1)]();
  335. }
  336. });
  337. if(rerender)
  338. renderViews();
  339. }
  340. function getOption(option)
  341. {
  342. return options[option];
  343. }
  344. function destroy() {
  345. $(window).unbind('resize', windowResize);
  346. header.destroy();
  347. content.remove();
  348. element.removeClass('fc fc-rtl ui-widget');
  349. }
  350. function elementVisible() {
  351. return _element.offsetWidth !== 0;
  352. }
  353. function bodyVisible() {
  354. return $('body')[0].offsetWidth !== 0;
  355. }
  356. /* View Rendering
  357. -----------------------------------------------------------------------------*/
  358. // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
  359. function changeView(newViewName) {
  360. if (!currentView || newViewName != currentView.name) {
  361. ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
  362. unselect();
  363. var oldView = currentView;
  364. var newViewElement;
  365. if (oldView) {
  366. (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
  367. //setMinHeight(content, content.height()); why is this necessary?
  368. oldView.element.hide();
  369. if(oldView.addedView) {
  370. oldView.addedView.element.hide();
  371. }
  372. }else{
  373. setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
  374. }
  375. content.css('overflow', 'hidden');
  376. currentView = t.viewInstances[newViewName];
  377. if (currentView) {
  378. currentView.element.show();
  379. }else{
  380. currentView = t.viewInstances[newViewName] = new fcViews[newViewName](
  381. newViewElement = absoluteViewElement =
  382. $("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
  383. .appendTo(content),
  384. t // the calendar object
  385. );
  386. }
  387. if(newViewName == 'agendaDay') {
  388. addedView = t.viewInstances['table'];
  389. if (addedView) {
  390. addedView.element.show();
  391. }else{
  392. addedView = t.viewInstances['table'] = new fcViews['table'](
  393. addedNewViewElement = addedAbsoluteViewElement =
  394. $("<div class='fc-view fc-view-" + 'table' + "' style='position:absolute'/>")
  395. .appendTo(content),
  396. t // the calendar object
  397. );
  398. currentView.addedView = addedView;
  399. }
  400. }
  401. if (oldView) {
  402. header.deactivateButton(oldView.name);
  403. }
  404. header.activateButton(newViewName);
  405. renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
  406. content.css('overflow', '');
  407. if (oldView) {
  408. setMinHeight(content, 1);
  409. }
  410. if (!newViewElement) {
  411. (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
  412. }
  413. ignoreWindowResize--;
  414. currentView.trigger('viewChanged', _element);
  415. }
  416. }
  417. function renderView(inc) {
  418. if (elementVisible()) {
  419. currentView.trigger('beforeViewDisplay', _element);
  420. ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
  421. unselect();
  422. if (suggestedViewHeight === undefined) {
  423. calcSize();
  424. }
  425. if(currentView.addedView && currentView.start && cloneDate(date, true).getTime() == currentView.start.getTime()) {
  426. currentView.addedView.scrollToDate(date);
  427. }
  428. var forceEventRender = false;
  429. if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
  430. // view must render an entire new date range (and refetch/render events)
  431. currentView.render(date, inc || 0); // responsible for clearing events
  432. setSize(true);
  433. forceEventRender = true;
  434. }
  435. else if (currentView.sizeDirty) {
  436. // view must resize (and rerender events)
  437. currentView.clearEvents();
  438. setSize();
  439. forceEventRender = true;
  440. }
  441. else if (currentView.eventsDirty) {
  442. currentView.clearEvents();
  443. forceEventRender = true;
  444. }
  445. currentView.sizeDirty = false;
  446. currentView.eventsDirty = false;
  447. updateEvents(forceEventRender);
  448. elementOuterWidth = element.outerWidth();
  449. header.updateTitle(currentView.title);
  450. var today = new Date();
  451. if (today >= currentView.start && today < currentView.end) {
  452. //header.disableButton('today');
  453. header.setTodayScroll(element);
  454. findToday();
  455. }else{
  456. //header.enableButton('today');
  457. header.setTodayDefault();
  458. }
  459. ignoreWindowResize--;
  460. currentView.trigger('viewDisplay', _element);
  461. }
  462. }
  463. /* Resizing
  464. -----------------------------------------------------------------------------*/
  465. function updateSize() {
  466. markSizesDirty();
  467. if (elementVisible()) {
  468. calcSize();
  469. setSize();
  470. if(currentView.name!='todo')
  471. {
  472. unselect();
  473. currentView.clearEvents();
  474. currentView.renderEvents(events);
  475. }
  476. currentView.sizeDirty = false;
  477. }
  478. }
  479. function markSizesDirty() {
  480. $.each(t.viewInstances, function(i, inst) {
  481. inst.sizeDirty = true;
  482. });
  483. }
  484. function calcSize() {
  485. if (options.contentHeight) {
  486. suggestedViewHeight = options.contentHeight;
  487. }
  488. else if (options.height) {
  489. suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
  490. }
  491. else {
  492. suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
  493. }
  494. }
  495. function setSize(dateChanged) { // todo: dateChanged?
  496. ignoreWindowResize++;
  497. currentView.setWidth(content.width(), dateChanged);
  498. currentView.setHeight(suggestedViewHeight, dateChanged);
  499. if (absoluteViewElement) {
  500. absoluteViewElement.css('position', 'relative');
  501. absoluteViewElement = null;
  502. }
  503. /*if(currentView.addedView) {
  504. currentView.addedView.setWidth(content.width(), dateChanged);
  505. var tmpContentWidth = Math.floor(content.width() / 2);
  506. currentView.element.width(tmpContentWidth);
  507. currentView.addedView.element.css({'left' : tmpContentWidth,
  508. 'width' : tmpContentWidth - 2});
  509. }*/
  510. ignoreWindowResize--;
  511. }
  512. function windowResize() {
  513. if (!ignoreWindowResize) {
  514. if (currentView.start) { // view has already been rendered
  515. var uid = ++resizeUID;
  516. //setTimeout(function() { // add a delay
  517. if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
  518. if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
  519. ignoreWindowResize++; // in case the windowResize callback changes the height
  520. updateSize();
  521. currentView.trigger('windowResize', _element);
  522. ignoreWindowResize--;
  523. }
  524. }
  525. //}, 200);
  526. }else{
  527. // calendar must have been initialized in a 0x0 iframe that has just been resized
  528. lateRender();
  529. }
  530. }
  531. }
  532. /* Event Fetching/Rendering
  533. -----------------------------------------------------------------------------*/
  534. // fetches events if necessary, rerenders events if necessary (or if forced)
  535. function updateEvents(forceRender) {
  536. if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
  537. refetchEvents();
  538. }
  539. else if (forceRender) {
  540. rerenderEvents();
  541. }
  542. }
  543. function refetchEvents() {
  544. fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
  545. }
  546. // called when event data arrives
  547. function reportEvents(_events) {
  548. events = _events;
  549. rerenderEvents();
  550. }
  551. // called when a single event's data has been changed
  552. function reportEventChange(eventID) {
  553. rerenderEvents(eventID);
  554. }
  555. // attempts to rerenderEvents
  556. function rerenderEvents(modifiedEventID) {
  557. markEventsDirty();
  558. if (elementVisible()) {
  559. currentView.clearEvents();
  560. currentView.renderEvents(events, modifiedEventID);
  561. currentView.eventsDirty = false;
  562. }
  563. }
  564. function markEventsDirty() {
  565. $.each(t.viewInstances, function(i, inst) {
  566. inst.eventsDirty = true;
  567. });
  568. }
  569. /* Selection
  570. -----------------------------------------------------------------------------*/
  571. function select(start, end, allDay) {
  572. currentView.select(start, end, allDay===undefined ? true : allDay);
  573. }
  574. function unselect() { // safe to be called before renderView
  575. if(currentView)
  576. currentView.unselect();
  577. }
  578. /* Date
  579. -----------------------------------------------------------------------------*/
  580. function prev() {
  581. renderView(-1);
  582. trigger('prevClick');
  583. }
  584. function next() {
  585. renderView(1);
  586. trigger('nextClick');
  587. }
  588. function prevYear() {
  589. addYears(date, -1);
  590. renderView();
  591. }
  592. function nextYear() {
  593. addYears(date, 1);
  594. renderView();
  595. }
  596. function today() {
  597. date = new Date();
  598. renderView();
  599. findToday();
  600. trigger('todayClick');
  601. }
  602. function findToday() {
  603. if(currentView.addedView) {
  604. if(currentView.addedView.getDaySegmentContainer().find('.fc-today').length>0) {
  605. if(new Date().getDate()==1) {
  606. currentView.addedView.getDaySegmentContainer().parent().scrollTop(0);
  607. }
  608. else {
  609. offset = currentView.addedView.getDaySegmentContainer().find('.fc-today').position().top;
  610. var top = currentView.addedView.getDaySegmentContainer().parent().scrollTop();
  611. currentView.addedView.getDaySegmentContainer().parent().scrollTop(top + offset);
  612. }
  613. }
  614. }
  615. else if(currentView.name == 'todo') {
  616. if(currentView.getDaySegmentContainer().find('.fc-today').length>0) {
  617. offset = currentView.getDaySegmentContainer().find('.fc-today').position().top;
  618. var top = currentView.getDaySegmentContainer().parent().scrollTop();
  619. currentView.getDaySegmentContainer().parent().scrollTop(top + offset);
  620. }
  621. }
  622. else {
  623. var todayElem = currentView.element.find('.fc-today');
  624. if(todayElem.length>0) {
  625. var offset = 0;
  626. if(!todayElem.parent().hasClass('fc-week0')) {
  627. offset = todayElem.position().top;
  628. }
  629. element.parent().scrollTop(offset);
  630. }
  631. }
  632. }
  633. function gotoDate(year, month, dateOfMonth) {
  634. if (year instanceof Date)
  635. date = cloneDate(year); // provided 1 argument, a Date
  636. else
  637. setYMD(date, year, month, dateOfMonth);
  638. renderView();
  639. }
  640. function incrementDate(years, months, days) {
  641. if(years !== undefined)
  642. addYears(date, years);
  643. if(months !== undefined)
  644. addMonths(date, months);
  645. if(days !== undefined)
  646. addDays(date, days);
  647. renderView();
  648. }
  649. function getDate() {
  650. return cloneDate(date);
  651. }
  652. /* Misc
  653. -----------------------------------------------------------------------------*/
  654. function getView() {
  655. return currentView;
  656. }
  657. function option(name, value) {
  658. if (value === undefined) {
  659. return options[name];
  660. }
  661. if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
  662. options[name] = value;
  663. updateSize();
  664. } else if (name.indexOf('list') == 0 || name == 'tableCols') {
  665. options[name] = value;
  666. currentView.start = null; // force re-render
  667. }
  668. }
  669. function trigger(name, thisObj) {
  670. if (options[name]) {
  671. return options[name].apply(
  672. thisObj || _element,
  673. Array.prototype.slice.call(arguments, 2)
  674. );
  675. }
  676. }
  677. function selectEvent(eventElement, noClick) {
  678. currentView.selectEvent(eventElement, noClick);
  679. }
  680. function allowSelectEvent(value) {
  681. currentView.allowSelectEvent(value);
  682. }
  683. /* External Dragging
  684. ------------------------------------------------------------------------*/
  685. if (options.droppable) {
  686. $(document)
  687. .bind('dragstart', function(ev, ui) {
  688. var _e = ev.target;
  689. var e = $(_e);
  690. if (!e.parents('.fc').length) { // not already inside a calendar
  691. var accept = options.dropAccept;
  692. if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
  693. _dragElement = _e;
  694. currentView.dragStart(_dragElement, ev, ui);
  695. }
  696. }
  697. })
  698. .bind('dragstop', function(ev, ui) {
  699. if (_dragElement) {
  700. currentView.dragStop(_dragElement, ev, ui);
  701. _dragElement = null;
  702. }
  703. });
  704. }
  705. }
  706. function Header(calendar, options) {
  707. var t = this;
  708. // exports
  709. t.render = render;
  710. t.destroy = destroy;
  711. t.updateTitle = updateTitle;
  712. t.activateButton = activateButton;
  713. t.deactivateButton = deactivateButton;
  714. t.disableButton = disableButton;
  715. t.enableButton = enableButton;
  716. t.setTodayDefault = setTodayDefault;
  717. t.setTodayScroll = setTodayScroll;
  718. // locals
  719. var element = $([]);
  720. var tm;
  721. function render() {
  722. tm = options.theme ? 'ui' : 'fc';
  723. var sections = options.header;
  724. if (sections) {
  725. element = $("<table class='fc-header' style='width:100%'/>")
  726. .append(
  727. $("<tr/>")
  728. .append(renderSection('left'))
  729. .append(renderSection('center'))
  730. .append(renderSection('right'))
  731. );
  732. return element;
  733. }
  734. }
  735. function destroy() {
  736. element.remove();
  737. }
  738. function renderSection(position) {
  739. var e = $("<td class='fc-header-" + position + "'/>");
  740. var buttonStr = options.header[position];
  741. if (buttonStr) {
  742. $.each(buttonStr.split(' '), function(i) {
  743. if (i > 0) {
  744. e.append("<span class='fc-header-space'/>");
  745. }
  746. var prevButton;
  747. $.each(this.split(','), function(j, buttonName) {
  748. if (buttonName == 'title') {
  749. e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
  750. if (prevButton) {
  751. prevButton.addClass(tm + '-corner-right');
  752. }
  753. prevButton = null;
  754. }else{
  755. var buttonClick;
  756. if (calendar[buttonName]) {
  757. buttonClick = calendar[buttonName]; // calendar method
  758. }
  759. else if (fcViews[buttonName]) {
  760. buttonClick = function() {
  761. button.removeClass(tm + '-state-hover'); // forget why
  762. calendar.changeView(buttonName);
  763. };
  764. }
  765. if (buttonClick) {
  766. // var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
  767. var icon = (buttonName=='prev' || buttonName=='next') ? buttonName : null;
  768. var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
  769. var button = $(
  770. "<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
  771. "<span class='fc-button-inner'>" +
  772. "<span class='fc-button-content'>" +
  773. (icon ?
  774. "<img src='images/arrow_" + icon + ".svg'/>" :
  775. text
  776. ) +
  777. "</span>" +
  778. "<span class='fc-button-effect'><span></span></span>" +
  779. "</span>" +
  780. "</span>"
  781. );
  782. if (button) {
  783. button
  784. .click(function() {
  785. if (!button.hasClass(tm + '-state-disabled')) {
  786. buttonClick();
  787. }
  788. })
  789. .mousedown(function() {
  790. button
  791. .not('.' + tm + '-state-active')
  792. .not('.' + tm + '-state-disabled')
  793. .addClass(tm + '-state-down');
  794. })
  795. .mouseup(function() {
  796. button.removeClass(tm + '-state-down');
  797. })
  798. .hover(
  799. function() {
  800. button
  801. .not('.' + tm + '-state-active')
  802. .not('.' + tm + '-state-disabled')
  803. .addClass(tm + '-state-hover');
  804. },
  805. function() {
  806. button
  807. .removeClass(tm + '-state-hover')
  808. .removeClass(tm + '-state-down');
  809. }
  810. )
  811. .appendTo(e);
  812. if (!prevButton) {
  813. button.addClass(tm + '-corner-left');
  814. }
  815. prevButton = button;
  816. }
  817. }
  818. }
  819. });
  820. if (prevButton) {
  821. prevButton.addClass(tm + '-corner-right');
  822. }
  823. });
  824. }
  825. return e;
  826. }
  827. function updateTitle(html) {
  828. element.find('h2')
  829. .html(html)
  830. .attr('title', $("<div/>").html(html).text());
  831. }
  832. function activateButton(buttonName) {
  833. element.find('span.fc-button-' + buttonName)
  834. .addClass(tm + '-state-active');
  835. }
  836. function deactivateButton(buttonName) {
  837. element.find('span.fc-button-' + buttonName)
  838. .removeClass(tm + '-state-active');
  839. }
  840. function disableButton(buttonName) {
  841. element.find('span.fc-button-' + buttonName)
  842. .addClass(tm + '-state-disabled');
  843. }
  844. function enableButton(buttonName) {
  845. element.find('span.fc-button-' + buttonName)
  846. .removeClass(tm + '-state-disabled');
  847. }
  848. function setTodayDefault() {
  849. var todayBt = element.find('span.fc-button-' + 'today');
  850. var todayBtClc = calendar['today'];
  851. todayBt.unbind('click');
  852. todayBt.click(function(){
  853. if(!todayBt.hasClass(tm + '-state-disabled')) {
  854. todayBtClc();
  855. }
  856. });
  857. }
  858. function setTodayScroll(body) {
  859. var todayBt = element.find('span.fc-button-' + 'today');
  860. var todayBtClc = calendar['findToday'];
  861. todayBt.unbind('click');
  862. todayBt.click(function(){
  863. if(!todayBt.hasClass(tm + '-state-disabled'))
  864. todayBtClc();
  865. });
  866. }
  867. }
  868. fc.sourceNormalizers = [];
  869. fc.sourceFetchers = [];
  870. var ajaxDefaults = {
  871. dataType: 'json',
  872. cache: false
  873. };
  874. var eventGUID = 1;
  875. function EventManager(options, _sources) {
  876. var t = this;
  877. // exports
  878. t.isFetchNeeded = isFetchNeeded;
  879. t.fetchEvents = fetchEvents;
  880. t.addEventSource = addEventSource;
  881. t.removeEventSource = removeEventSource;
  882. t.removeEventSources = removeEventSources;
  883. t.updateEvent = updateEvent;
  884. t.renderEvent = renderEvent;
  885. t.removeEvents = removeEvents;
  886. t.clientEvents = clientEvents;
  887. t.normalizeEvent = normalizeEvent;
  888. // imports
  889. var trigger = t.trigger;
  890. var getView = t.getView;
  891. var reportEvents = t.reportEvents;
  892. // locals
  893. var stickySource = { events: [] };
  894. var sources = [ stickySource ];
  895. var rangeStart, rangeEnd;
  896. var currentFetchID = 0;
  897. var pendingSourceCnt = 0;
  898. var loadingLevel = 0;
  899. var cache = [];
  900. for (var i=0; i<_sources.length; i++) {
  901. _addEventSource(_sources[i]);
  902. }
  903. /* Fetching
  904. -----------------------------------------------------------------------------*/
  905. function isFetchNeeded(start, end) {
  906. return !rangeStart || start < rangeStart || end > rangeEnd;
  907. }
  908. function fetchEvents(start, end) {
  909. rangeStart = start;
  910. rangeEnd = end;
  911. cache = [];
  912. var fetchID = ++currentFetchID;
  913. var len = sources.length;
  914. pendingSourceCnt = len;
  915. for (var i=0; i<len; i++) {
  916. fetchEventSource(sources[i], fetchID);
  917. }
  918. }
  919. function fetchEventSource(source, fetchID) {
  920. _fetchEventSource(source, function(events) {
  921. if (fetchID == currentFetchID) {
  922. if (events) {
  923. for (var i=0; i<events.length; i++) {
  924. events[i].source = source;
  925. normalizeEvent(events[i]);
  926. }
  927. cache = cache.concat(events);
  928. }
  929. pendingSourceCnt--;
  930. if (!pendingSourceCnt) {
  931. reportEvents(cache);
  932. }
  933. }
  934. });
  935. }
  936. function _fetchEventSource(source, callback) {
  937. var i;
  938. var fetchers = fc.sourceFetchers;
  939. var res;
  940. for (i=0; i<fetchers.length; i++) {
  941. res = fetchers[i](source, rangeStart, rangeEnd, callback);
  942. if (res === true) {
  943. // the fetcher is in charge. made its own async request
  944. return;
  945. }
  946. else if (typeof res == 'object') {
  947. // the fetcher returned a new source. process it
  948. _fetchEventSource(res, callback);
  949. return;
  950. }
  951. }
  952. var events = source.events;
  953. if (events) {
  954. if ($.isFunction(events)) {
  955. pushLoading();
  956. events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
  957. callback(events);
  958. popLoading();
  959. });
  960. }
  961. else if ($.isArray(events)) {
  962. callback(events);
  963. }
  964. else {
  965. callback();
  966. }
  967. }else{
  968. var url = source.url;
  969. if (url) {
  970. var success = source.success;
  971. var error = source.error;
  972. var complete = source.complete;
  973. var data = $.extend({}, source.data || {});
  974. var startParam = firstDefined(source.startParam, options.startParam);
  975. var endParam = firstDefined(source.endParam, options.endParam);
  976. if (startParam) {
  977. data[startParam] = Math.round(+rangeStart / 1000);
  978. }
  979. if (endParam) {
  980. data[endParam] = Math.round(+rangeEnd / 1000);
  981. }
  982. pushLoading();
  983. $.ajax($.extend({}, ajaxDefaults, source, {
  984. data: data,
  985. success: function(events) {
  986. events = events || [];
  987. var res = applyAll(success, this, arguments);
  988. if ($.isArray(res)) {
  989. events = res;
  990. }
  991. callback(events);
  992. },
  993. error: function() {
  994. applyAll(error, this, arguments);
  995. callback();
  996. },
  997. complete: function() {
  998. applyAll(complete, this, arguments);
  999. popLoading();
  1000. }
  1001. }));
  1002. }else{
  1003. callback();
  1004. }
  1005. }
  1006. }
  1007. /* Sources
  1008. -----------------------------------------------------------------------------*/
  1009. function addEventSource(source) {
  1010. source = _addEventSource(source);
  1011. if (source) {
  1012. pendingSourceCnt++;
  1013. fetchEventSource(source, currentFetchID); // will eventually call reportEvents
  1014. }
  1015. return source;
  1016. }
  1017. function _addEventSource(source) {
  1018. if ($.isFunction(source) || $.isArray(source)) {
  1019. source = { events: source };
  1020. }
  1021. else if (typeof source == 'string') {
  1022. source = { url: source };
  1023. }
  1024. if (typeof source == 'object') {
  1025. normalizeSource(source);
  1026. sources.push(source);
  1027. return source;
  1028. }
  1029. }
  1030. function removeEventSource(source) {
  1031. sources = $.grep(sources, function(src) {
  1032. return !isSourcesEqual(src, source);
  1033. });
  1034. // remove all client events from that source
  1035. cache = $.grep(cache, function(e) {
  1036. return !isSourcesEqual(e.source, source);
  1037. });
  1038. reportEvents(cache);
  1039. }
  1040. function removeEventSources() {
  1041. while(source = sources.shift()) {
  1042. // remove all client events from that source
  1043. cache = $.grep(cache, function(e) {
  1044. return !isSourcesEqual(e.source, source);
  1045. });
  1046. reportEvents(cache);
  1047. }
  1048. }
  1049. /* Manipulation
  1050. -----------------------------------------------------------------------------*/
  1051. function updateEvent(event) { // update an existing event
  1052. var i, len = cache.length, e,
  1053. defaultEventEnd = getView().defaultEventEnd, // getView???
  1054. startDelta = event.start - event._start,
  1055. endDelta = event.end ?
  1056. (event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
  1057. : 0; // was null and event was just resized
  1058. for (i=0; i<len; i++) {
  1059. e = cache[i];
  1060. if (e._id == event._id && e != event) {
  1061. e.start = new Date(+e.start + startDelta);
  1062. if (event.end) {
  1063. if (e.end) {
  1064. e.end = new Date(+e.end + endDelta);
  1065. }else{
  1066. e.end = new Date(+defaultEventEnd(e) + endDelta);
  1067. }
  1068. }else{
  1069. e.end = null;
  1070. }
  1071. e.title = event.title;
  1072. e.url = event.url;
  1073. e.allDay = event.allDay;
  1074. e.className = event.className;
  1075. e.editable = event.editable;
  1076. e.color = event.color;
  1077. e.backgroudColor = event.backgroudColor;
  1078. e.borderColor = event.borderColor;
  1079. e.textColor = event.textColor;
  1080. normalizeEvent(e);
  1081. }
  1082. }
  1083. normalizeEvent(event);
  1084. reportEvents(cache);
  1085. }
  1086. function renderEvent(event, stick) {
  1087. normalizeEvent(event);
  1088. if (!event.source) {
  1089. if (stick) {
  1090. stickySource.events.push(event);
  1091. event.source = stickySource;
  1092. }
  1093. }
  1094. // always push event to cache (issue #1112:)
  1095. cache.push(event);
  1096. reportEvents(cache);
  1097. }
  1098. function removeEvents(filter) {
  1099. var oldCache = cache;
  1100. if (!filter) { // remove all
  1101. cache = [];
  1102. // clear all array sources
  1103. /*for (var i=0; i<sources.length; i++) {
  1104. if ($.isArray(sources[i].events)) {
  1105. sources[i].events = [];
  1106. }
  1107. }*/
  1108. }else{
  1109. if (!$.isFunction(filter)) { // an event ID
  1110. var id = filter + '';
  1111. filter = function(e) {
  1112. return e._id == id;
  1113. };
  1114. }
  1115. cache = $.grep(cache, filter, true);
  1116. // remove events from array sources
  1117. /*for (var i=0; i<sources.length; i++) {
  1118. if ($.isArray(sources[i].events)) {
  1119. sources[i].events = $.grep(sources[i].events, filter, true);
  1120. }
  1121. }*/
  1122. }
  1123. if(oldCache.length != cache.length)
  1124. reportEvents(cache);
  1125. }
  1126. function clientEvents(filter) {
  1127. if ($.isFunction(filter)) {
  1128. return $.grep(cache, filter);
  1129. }
  1130. else if (filter) { // an event ID
  1131. filter += '';
  1132. return $.grep(cache, function(e) {
  1133. return e._id == filter;
  1134. });
  1135. }
  1136. return cache; // else, return all
  1137. }
  1138. /* Loading State
  1139. -----------------------------------------------------------------------------*/
  1140. function pushLoading() {
  1141. if (!loadingLevel++) {
  1142. trigger('loading', null, true);
  1143. }
  1144. }
  1145. function popLoading() {
  1146. if (!--loadingLevel) {
  1147. trigger('loading', null, false);
  1148. }
  1149. }
  1150. /* Event Normalization
  1151. -----------------------------------------------------------------------------*/
  1152. function normalizeEvent(event) {
  1153. var source = event.source || {};
  1154. var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
  1155. event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
  1156. if (event.date) {
  1157. if (!event.start) {
  1158. event.start = event.date;
  1159. }
  1160. delete event.date;
  1161. }
  1162. event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
  1163. event.end = parseDate(event.end, ignoreTimezone);
  1164. if (event.end && ((options.eventMode && event.end <= event.start) || (!options.eventMode && event.end < event.start))) {
  1165. event.end = null;
  1166. }
  1167. event._end = event.end ? cloneDate(event.end) : null;
  1168. if (event.allDay === undefined) {
  1169. event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
  1170. }
  1171. if (event.className) {
  1172. if (typeof event.className == 'string') {
  1173. event.className = event.className.split(/\s+/);
  1174. }
  1175. }else{
  1176. event.className = [];
  1177. }
  1178. // TODO: if there is no start date, return false to indicate an invalid event
  1179. }
  1180. /* Utils
  1181. ------------------------------------------------------------------------------*/
  1182. function normalizeSource(source) {
  1183. if (source.className) {
  1184. // TODO: repeat code, same code for event classNames
  1185. if (typeof source.className == 'string') {
  1186. source.className = source.className.split(/\s+/);
  1187. }
  1188. }else{
  1189. source.className = [];
  1190. }
  1191. var normalizers = fc.sourceNormalizers;
  1192. for (var i=0; i<normalizers.length; i++) {
  1193. normalizers[i](source);
  1194. }
  1195. }
  1196. function isSourcesEqual(source1, source2) {
  1197. return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
  1198. }
  1199. function getSourcePrimitive(source) {
  1200. return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
  1201. }
  1202. }
  1203. fc.addDays = addDays;
  1204. fc.cloneDate = cloneDate;
  1205. fc.parseDate = parseDate;
  1206. fc.parseISO8601 = parseISO8601;
  1207. fc.parseTime = parseTime;
  1208. fc.formatDate = formatDate;
  1209. fc.formatDates = formatDates;
  1210. /* Date Math
  1211. -----------------------------------------------------------------------------*/
  1212. var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
  1213. DAY_MS = 86400000,
  1214. HOUR_MS = 3600000,
  1215. MINUTE_MS = 60000;
  1216. function addYears(d, n, keepTime) {
  1217. d.setFullYear(d.getFullYear() + n);
  1218. if (!keepTime) {
  1219. clearTime(d);
  1220. }
  1221. return d;
  1222. }
  1223. function addMonths(d, n, keepTime) { // prevents day overflow/underflow
  1224. if (+d) { // prevent infinite looping on invalid dates
  1225. var m = d.getMonth() + n,
  1226. check = cloneDate(d);
  1227. check.setDate(1);
  1228. check.setMonth(m);
  1229. d.setMonth(m);
  1230. if (!keepTime) {
  1231. clearTime(d);
  1232. }
  1233. while (d.getMonth() != check.getMonth()) {
  1234. d.setDate(d.getDate() + (d < check ? 1 : -1));
  1235. }
  1236. }
  1237. return d;
  1238. }
  1239. function addDays(d, n, keepTime) { // deals with daylight savings
  1240. if (+d) {
  1241. var dd = d.getDate() + n,
  1242. check = cloneDate(d);
  1243. check.setHours(9); // set to middle of day
  1244. check.setDate(dd);
  1245. d.setDate(dd);
  1246. if (!keepTime) {
  1247. clearTime(d);
  1248. }
  1249. fixDate(d, check);
  1250. }
  1251. return d;
  1252. }
  1253. function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
  1254. if (+d) { // prevent infinite looping on invalid dates
  1255. while (d.getDate() != check.getDate()) {
  1256. d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
  1257. }
  1258. }
  1259. }
  1260. function addMinutes(d, n) {
  1261. d.setMinutes(d.getMinutes() + n);
  1262. return d;
  1263. }
  1264. function clearTime(d) {
  1265. d.setHours(0);
  1266. d.setMinutes(0);
  1267. d.setSeconds(0);
  1268. d.setMilliseconds(0);
  1269. return d;
  1270. }
  1271. function cloneDate(d, dontKeepTime) {
  1272. if(d==null) {
  1273. return null;
  1274. }
  1275. else if (dontKeepTime) {
  1276. return clearTime(new Date(+d));
  1277. }
  1278. return new Date(+d);
  1279. }
  1280. function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
  1281. var i=0, d;
  1282. do {
  1283. d = new Date(1970, i++, 1);
  1284. } while (d.getHours()); // != 0
  1285. return d;
  1286. }
  1287. function skipWeekend(date, inc, excl) {
  1288. inc = inc || 1;
  1289. while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
  1290. addDays(date, inc);
  1291. }
  1292. return date;
  1293. }
  1294. function dayDiff(d1, d2) { // d1 - d2
  1295. return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
  1296. }
  1297. function minDiff(d1, d2) { // d1 - d2
  1298. return Math.round((cloneDate(d1, false) - cloneDate(d2, false)) / MINUTE_MS);
  1299. }
  1300. function setYMD(date, y, m, d) {
  1301. if (y !== undefined && y != date.getFullYear()) {
  1302. date.setDate(1);
  1303. date.setMonth(0);
  1304. date.setFullYear(y);
  1305. }
  1306. if (m !== undefined && m != date.getMonth()) {
  1307. date.setDate(1);
  1308. date.setMonth(m);
  1309. }
  1310. if (d !== undefined) {
  1311. date.setDate(d);
  1312. }
  1313. }
  1314. /* Date Parsing
  1315. -----------------------------------------------------------------------------*/
  1316. function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
  1317. if (typeof s == 'object') { // already a Date object
  1318. return s;
  1319. }
  1320. if (typeof s == 'number') { // a UNIX timestamp
  1321. return new Date(s * 1000);
  1322. }
  1323. if (typeof s == 'string') {
  1324. if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
  1325. return new Date(parseFloat(s) * 1000);
  1326. }
  1327. if (ignoreTimezone === undefined) {
  1328. ignoreTimezone = true;
  1329. }
  1330. return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
  1331. }
  1332. // TODO: never return invalid dates (like from new Date(<string>)), return null instead
  1333. return null;
  1334. }
  1335. function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
  1336. // derived from http://delete.me.uk/2005/03/iso8601.html
  1337. // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
  1338. var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
  1339. if (!m) {
  1340. return null;
  1341. }
  1342. var date = new Date(m[1], 0, 1);
  1343. if (ignoreTimezone || !m[13]) {
  1344. var check = new Date(m[1], 0, 1, 12, 0);
  1345. fixDate(date, check);
  1346. if (m[3]) {
  1347. date.setMonth(m[3] - 1);
  1348. check.setMonth(m[3] - 1);
  1349. }
  1350. if (m[5]) {
  1351. date.setDate(m[5]);
  1352. check.setDate(m[5]);
  1353. }
  1354. fixDate(date, check);
  1355. if (m[7]) {
  1356. date.setHours(m[7]);
  1357. }
  1358. if (m[8]) {
  1359. date.setMinutes(m[8]);
  1360. }
  1361. if (m[10]) {
  1362. date.setSeconds(m[10]);
  1363. }
  1364. if (m[12]) {
  1365. date.setMilliseconds(Number("0." + m[12]) * 1000);
  1366. }
  1367. fixDate(date, check);
  1368. }else{
  1369. date.setUTCFullYear(
  1370. m[1],
  1371. m[3] ? m[3] - 1 : 0,
  1372. m[5] || 1
  1373. );
  1374. date.setUTCHours(
  1375. m[7] || 0,
  1376. m[8] || 0,
  1377. m[10] || 0,
  1378. m[12] ? Number("0." + m[12]) * 1000 : 0
  1379. );
  1380. if (m[14]) {
  1381. var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
  1382. offset *= m[15] == '-' ? 1 : -1;
  1383. date = new Date(+date + (offset * 60 * 1000));
  1384. }
  1385. }
  1386. return date;
  1387. }
  1388. function parseTime(s) { // returns minutes since start of day
  1389. if (typeof s == 'number') { // an hour
  1390. return s * 60;
  1391. }
  1392. if (typeof s == 'object') { // a Date object
  1393. return s.getHours() * 60 + s.getMinutes();
  1394. }
  1395. var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
  1396. if (m) {
  1397. var h = parseInt(m[1], 10);
  1398. if (m[3]) {
  1399. h %= 12;
  1400. if (m[3].toLowerCase().charAt(0) == 'p') {
  1401. h += 12;
  1402. }
  1403. }
  1404. return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
  1405. }
  1406. }
  1407. /* Date Formatting
  1408. -----------------------------------------------------------------------------*/
  1409. // TODO: use same function formatDate(date, [date2], format, [options])
  1410. function formatDate(date, format, options) {
  1411. return formatDates(date, null, format, options);
  1412. }
  1413. function formatDates(date1, date2, format, options) {
  1414. options = options || defaults;
  1415. var date = date1,
  1416. otherDate = date2,
  1417. i, len = format.length, c,
  1418. i2, formatter,
  1419. res = '';
  1420. for (i=0; i<len; i++) {
  1421. c = format.charAt(i);
  1422. if (c == "'") {
  1423. for (i2=i+1; i2<len; i2++) {
  1424. if (format.charAt(i2) == "'") {
  1425. if (date) {
  1426. if (i2 == i+1) {
  1427. res += "'";
  1428. }else{
  1429. res += format.substring(i+1, i2);
  1430. }
  1431. i = i2;
  1432. }
  1433. break;
  1434. }
  1435. }
  1436. }
  1437. else if (c == '(') {
  1438. for (i2=i+1; i2<len; i2++) {
  1439. if (format.charAt(i2) == ')') {
  1440. var subres = formatDate(date, format.substring(i+1, i2), options);
  1441. if (parseInt(subres.replace(/\D/, ''), 10)) {
  1442. res += subres;
  1443. }
  1444. i = i2;
  1445. break;
  1446. }
  1447. }
  1448. }
  1449. else if (c == '[') {
  1450. for (i2=i+1; i2<len; i2++) {
  1451. if (format.charAt(i2) == ']') {
  1452. var subformat = format.substring(i+1, i2);
  1453. var subres = formatDate(date, subformat, options);
  1454. if (subres != formatDate(otherDate, subformat, options)) {
  1455. res += subres;
  1456. }
  1457. i = i2;
  1458. break;
  1459. }
  1460. }
  1461. }
  1462. else if (c == '{') {
  1463. date = date2;
  1464. otherDate = date1;
  1465. }
  1466. else if (c == '}') {
  1467. date = date1;
  1468. otherDate = date2;
  1469. }
  1470. else {
  1471. for (i2=len; i2>i; i2--) {
  1472. if (formatter = dateFormatters[format.substring(i, i2)]) {
  1473. if (date) {
  1474. res += formatter(date, options);
  1475. }
  1476. i = i2 - 1;
  1477. break;
  1478. }
  1479. }
  1480. if (i2 == i) {
  1481. if (date) {
  1482. res += c;
  1483. }
  1484. }
  1485. }
  1486. }
  1487. return res;
  1488. };
  1489. var dateFormatters = {
  1490. s : function(d) {return d.getSeconds() },
  1491. ss : function(d) {return zeroPad(d.getSeconds())},
  1492. m : function(d) {return d.getMinutes()},
  1493. mm : function(d) {return zeroPad(d.getMinutes())},
  1494. h : function(d) {return d.getHours() % 12 || 12},
  1495. hh : function(d) {return zeroPad(d.getHours() % 12 || 12)},
  1496. H : function(d) {return d.getHours()},
  1497. HH : function(d) {return zeroPad(d.getHours())},
  1498. d : function(d) {return d.getDate()},
  1499. dd : function(d) {return zeroPad(d.getDate())},
  1500. ddd : function(d,o) {return o.dayNamesShort[d.getDay()]},
  1501. dddd: function(d,o) {return o.dayNames[d.getDay()]},
  1502. W : function(d) {return getWeekNumber(d)},
  1503. M : function(d) {return d.getMonth() + 1},
  1504. MM : function(d) {return zeroPad(d.getMonth() + 1)},
  1505. MMM : function(d,o) {return o.monthNamesShort[d.getMonth()]},
  1506. MMMM: function(d,o) {return o.monthNames[d.getMonth()]},
  1507. yy : function(d) {return (d.getFullYear()+'').substring(2)},
  1508. yyyy: function(d) {return d.getFullYear()},
  1509. t : function(d) {return d.getHours() < 12 ? 'a' : 'p'},
  1510. tt : function(d) {return d.getHours() < 12 ? 'am' : 'pm'},
  1511. T : function(d) {return d.getHours() < 12 ? 'A' : 'P'},
  1512. TT : function(d) {return d.getHours() < 12 ? 'AM' : 'PM'},
  1513. u : function(d) {return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'")},
  1514. S : function(d) {
  1515. var date = d.getDate();
  1516. if (date > 10 && date < 20) {
  1517. return 'th';
  1518. }
  1519. return ['st', 'nd', 'rd'][date%10-1] || 'th';
  1520. }
  1521. };
  1522. fc.applyAll = applyAll;
  1523. /* Event Date Math
  1524. -----------------------------------------------------------------------------*/
  1525. function exclEndDay(event) {
  1526. if (event.end) {
  1527. return _exclEndDay(event.end, event.allDay);
  1528. }else{
  1529. return addDays(cloneDate(event.start), 1);
  1530. }
  1531. }
  1532. function _exclEndDay(end, allDay) {
  1533. end = cloneDate(end);
  1534. return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
  1535. }
  1536. function segCmp(a, b) {
  1537. return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
  1538. }
  1539. function segsCollide(seg1, seg2) {
  1540. return seg1.end > seg2.start && seg1.start < seg2.end;
  1541. }
  1542. /* Event Sorting
  1543. -----------------------------------------------------------------------------*/
  1544. // event rendering utilities
  1545. function sliceSegs(events, visEventEnds, start, end) {
  1546. var segs = [],
  1547. i, len=events.length, event,
  1548. eventStart, eventEnd,
  1549. segStart, segEnd,
  1550. isStart, isEnd;
  1551. for (i=0; i<len; i++) {
  1552. event = events[i];
  1553. eventStart = event.start;
  1554. eventEnd = visEventEnds[i];
  1555. if (eventEnd > start && eventStart < end) {
  1556. if (eventStart < start) {
  1557. segStart = cloneDate(start);
  1558. isStart = false;
  1559. }else{
  1560. segStart = eventStart;
  1561. isStart = true;
  1562. }
  1563. if (eventEnd > end) {
  1564. segEnd = cloneDate(end);
  1565. isEnd = false;
  1566. }else{
  1567. segEnd = eventEnd;
  1568. isEnd = true;
  1569. }
  1570. segs.push({
  1571. event: event,
  1572. start: segStart,
  1573. end: segEnd,
  1574. isStart: isStart,
  1575. isEnd: isEnd,
  1576. msLength: segEnd - segStart
  1577. });
  1578. }
  1579. }
  1580. return segs.sort(segCmp);
  1581. }
  1582. // event rendering calculation utilities
  1583. function stackSegs(segs) {
  1584. var levels = [],
  1585. i, len = segs.length, seg,
  1586. j, collide, k;
  1587. for (i=0; i<len; i++) {
  1588. seg = segs[i];
  1589. j = 0; // the level index where seg should belong
  1590. while (true) {
  1591. collide = false;
  1592. if (levels[j]) {
  1593. for (k=0; k<levels[j].length; k++) {
  1594. if (segsCollide(levels[j][k], seg)) {
  1595. collide = true;
  1596. break;
  1597. }
  1598. }
  1599. }
  1600. if (collide) {
  1601. j++;
  1602. }else{
  1603. break;
  1604. }
  1605. }
  1606. if (levels[j]) {
  1607. levels[j].push(seg);
  1608. }else{
  1609. levels[j] = [seg];
  1610. }
  1611. }
  1612. return levels;
  1613. }
  1614. /* Event Element Binding
  1615. -----------------------------------------------------------------------------*/
  1616. function lazySegBind(container, segs, bindHandlers) {
  1617. container.unbind('mouseover').mouseover(function(ev) {
  1618. var parent=ev.target, e,
  1619. i, seg;
  1620. while (parent != this) {
  1621. e = parent;
  1622. parent = parent.parentNode;
  1623. }
  1624. if ((i = e._fci) !== undefined) {
  1625. e._fci = undefined;
  1626. seg = segs[i];
  1627. bindHandlers(seg.event, seg.element, seg);
  1628. $(ev.target).trigger(ev);
  1629. }
  1630. ev.stopPropagation();
  1631. });
  1632. }
  1633. /* Element Dimensions
  1634. -----------------------------------------------------------------------------*/
  1635. function setOuterWidth(element, width, includeMargins) {
  1636. for (var i=0, e; i<element.length; i++) {
  1637. e = $(element[i]);
  1638. e.width(Math.max(0, width - hsides(e, includeMargins)));
  1639. }
  1640. }
  1641. function setOuterHeight(element, height, includeMargins) {
  1642. for (var i=0, e; i<element.length; i++) {
  1643. e = $(element[i]);
  1644. e.height(Math.max(0, height - vsides(e, includeMargins)));
  1645. }
  1646. }
  1647. function hsides(element, includeMargins) {
  1648. return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
  1649. }
  1650. function hpadding(element) {
  1651. return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +
  1652. (parseFloat($.css(element[0], 'paddingRight', true)) || 0);
  1653. }
  1654. function hmargins(element) {
  1655. return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +
  1656. (parseFloat($.css(element[0], 'marginRight', true)) || 0);
  1657. }
  1658. function hborders(element) {
  1659. return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +
  1660. (parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);
  1661. }
  1662. function vsides(element, includeMargins) {
  1663. return vpadding(element) + vborders(element) + (includeMargins ? vmargins(element) : 0);
  1664. }
  1665. function vpadding(element) {
  1666. return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +
  1667. (parseFloat($.css(element[0], 'paddingBottom', true)) || 0);
  1668. }
  1669. function vmargins(element) {
  1670. return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +
  1671. (parseFloat($.css(element[0], 'marginBottom', true)) || 0);
  1672. }
  1673. function vborders(element) {
  1674. return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +
  1675. (parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);
  1676. }
  1677. function setMinHeight(element, height) {
  1678. height = (typeof height == 'number' ? height + 'px' : height);
  1679. element.each(function(i, _element) {
  1680. _element.style.cssText += ';min-height:' + height + ';_height:' + height;
  1681. // why can't we just use .css() ? i forget
  1682. });
  1683. }
  1684. /* Misc Utils
  1685. -----------------------------------------------------------------------------*/
  1686. //TODO: arraySlice
  1687. //TODO: isFunction, grep ?
  1688. function noop() { }
  1689. function cmp(a, b) {
  1690. return a - b;
  1691. }
  1692. function arrayMax(a) {
  1693. return Math.max.apply(Math, a);
  1694. }
  1695. function zeroPad(n) {
  1696. return (n < 10 ? '0' : '') + n;
  1697. }
  1698. function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
  1699. if (obj[name] !== undefined) {
  1700. return obj[name];
  1701. }
  1702. var parts = name.split(/(?=[A-Z])/),
  1703. i=parts.length-1, res;
  1704. for (; i>=0; i--) {
  1705. res = obj[parts[i].toLowerCase()];
  1706. if (res !== undefined) {
  1707. return res;
  1708. }
  1709. }
  1710. return obj[''];
  1711. }
  1712. function htmlEscape(s) {
  1713. return s.replace(/&/g, '&amp;')
  1714. .replace(/</g, '&lt;')
  1715. .replace(/>/g, '&gt;')
  1716. .replace(/'/g, '&#039;')
  1717. .replace(/"/g, '&quot;')
  1718. .replace(/\n/g, '<br />');
  1719. }
  1720. function cssKey(_element) {
  1721. return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
  1722. }
  1723. function disableTextSelection(element) {
  1724. element
  1725. .attr('unselectable', 'on')
  1726. .css('MozUserSelect', 'none')
  1727. .bind('selectstart.ui', function() { return false; });
  1728. }
  1729. /*
  1730. function enableTextSelection(element) {
  1731. element
  1732. .attr('unselectable', 'off')
  1733. .css('MozUserSelect', '')
  1734. .unbind('selectstart.ui');
  1735. }
  1736. */
  1737. function markFirstLast(e) {
  1738. e.children()
  1739. .removeClass('fc-first fc-last')
  1740. .filter(':first-child')
  1741. .addClass('fc-first')
  1742. .end()
  1743. .filter(':last-child')
  1744. .addClass('fc-last');
  1745. }
  1746. function setDayID(cell, date, opt) {
  1747. cell.each(function(i, _cell) {
  1748. _cell.className = _cell.className.replace(/^fc-\w*( fc-weekend-day)?/, 'fc-' + dayIDs[date.getDay()] + (opt('weekendDays').length>0 && opt('weekendDays').indexOf(date.getDay())!=-1 ? ' fc-weekend-day' : ''));
  1749. // TODO: make a way that doesn't rely on order of classes
  1750. });
  1751. }
  1752. function getSkinCss(event, opt) {
  1753. var source = event.source || {};
  1754. var eventColor = event.color;
  1755. var sourceColor = source.color;
  1756. var optionColor = opt('eventColor');
  1757. var backgroundColor =
  1758. event.backgroundColor ||
  1759. eventColor ||
  1760. source.backgroundColor ||
  1761. sourceColor ||
  1762. opt('eventBackgroundColor') ||
  1763. optionColor;
  1764. var borderColor =
  1765. event.borderColor ||
  1766. eventColor ||
  1767. source.borderColor ||
  1768. sourceColor ||
  1769. opt('eventBorderColor') ||
  1770. optionColor;
  1771. var textColor =
  1772. event.textColor ||
  1773. source.textColor ||
  1774. opt('eventTextColor');
  1775. var statements = [];
  1776. if (backgroundColor) {
  1777. statements.push('background-color:' + backgroundColor);
  1778. }
  1779. if (borderColor) {
  1780. statements.push('border-color:' + borderColor);
  1781. }
  1782. if (textColor) {
  1783. statements.push('color:' + textColor);
  1784. }
  1785. return statements.join(';');
  1786. }
  1787. function applyAll(functions, thisObj, args) {
  1788. if ($.isFunction(functions)) {
  1789. functions = [ functions ];
  1790. }
  1791. if (functions) {
  1792. var i;
  1793. var ret;
  1794. for (i=0; i<functions.length; i++) {
  1795. ret = functions[i].apply(thisObj, args) || ret;
  1796. }
  1797. return ret;
  1798. }
  1799. }
  1800. function firstDefined() {
  1801. for (var i=0; i<arguments.length; i++) {
  1802. if (arguments[i] !== undefined) {
  1803. return arguments[i];
  1804. }
  1805. }
  1806. }
  1807. fcViews.month = MonthView;
  1808. function MonthView(element, calendar) {
  1809. var t = this;
  1810. // exports
  1811. t.render = render;
  1812. // imports
  1813. BasicView.call(t, element, calendar, 'month');
  1814. var opt = t.opt;
  1815. var renderBasic = t.renderBasic;
  1816. var formatDate = calendar.formatDate;
  1817. function render(date, delta) {
  1818. if (delta) {
  1819. addMonths(date, delta);
  1820. date.setDate(1);
  1821. }
  1822. var start = cloneDate(date, true);
  1823. start.setDate(1);
  1824. var end = addMonths(cloneDate(start), 1);
  1825. var visStart = cloneDate(start);
  1826. var visEnd = cloneDate(end);
  1827. var firstDay = opt('firstDay');
  1828. var nwe = opt('weekends') ? 0 : 1;
  1829. if (nwe) {
  1830. skipWeekend(visStart);
  1831. skipWeekend(visEnd, -1, true);
  1832. }
  1833. addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
  1834. addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
  1835. var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
  1836. if (opt('weekMode') == 'fixed') {
  1837. addDays(visEnd, (6 - rowCnt) * 7);
  1838. rowCnt = 6;
  1839. }
  1840. t.title = formatDate(start, opt('titleFormat'));
  1841. t.start = start;
  1842. t.end = end;
  1843. t.visStart = visStart;
  1844. t.visEnd = visEnd;
  1845. renderBasic(6, rowCnt, nwe ? 5 : 7, true);
  1846. }
  1847. }
  1848. fcViews.multiWeek = MultiWeekView;
  1849. function MultiWeekView(element, calendar) {
  1850. var t = this;
  1851. // exports
  1852. t.render = render;
  1853. // imports
  1854. BasicView.call(t, element, calendar, 'multiWeek');
  1855. var opt = t.opt;
  1856. var renderBasic = t.renderBasic;
  1857. var formatDates = calendar.formatDates;
  1858. function render(date, delta) {
  1859. if (delta) {
  1860. addDays(date, delta * opt('multiWeekSize') * 7);
  1861. }
  1862. //Adjust displayed date-range, to make sure today will always stay in the top row
  1863. var currentDate = cloneDate(new Date(), true);
  1864. var dateWeekStart = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1865. var currentWeekStart = addDays(cloneDate(currentDate), -((currentDate.getDay() - opt('firstDay') + 7) % 7));
  1866. if(opt('multiWeekSize')>0)
  1867. addDays(date, -(( - (Math.abs(Math.ceil(dayDiff(dateWeekStart, currentWeekStart) / 7)) % opt('multiWeekSize'))) % opt('multiWeekSize')) * 7);
  1868. //var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1869. var start = cloneDate(date);
  1870. //var end = addDays(cloneDate(start), opt('multiWeekSize') * 7);
  1871. //var visStart = cloneDate(start);
  1872. var visStart = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1873. var end = addDays(cloneDate(visStart), opt('multiWeekSize') * 7);
  1874. var visEnd = cloneDate(end);
  1875. var firstDay = opt('firstDay');
  1876. var nwe = opt('weekends') ? 0 : 1;
  1877. if (nwe) {
  1878. skipWeekend(visStart);
  1879. skipWeekend(visEnd, -1, true);
  1880. }
  1881. addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
  1882. addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
  1883. t.title = formatDates(
  1884. visStart,
  1885. addDays(cloneDate(visEnd), -1),
  1886. opt('titleFormat')
  1887. );
  1888. t.start = start;
  1889. t.end = end;
  1890. t.visStart = visStart;
  1891. t.visEnd = visEnd;
  1892. renderBasic(opt('multiWeekSize'), opt('multiWeekSize'), nwe ? 5 : 7, true);
  1893. }
  1894. }
  1895. fcViews.basicWeek = BasicWeekView;
  1896. function BasicWeekView(element, calendar) {
  1897. var t = this;
  1898. // exports
  1899. t.render = render;
  1900. // imports
  1901. BasicView.call(t, element, calendar, 'basicWeek');
  1902. var opt = t.opt;
  1903. var renderBasic = t.renderBasic;
  1904. var formatDates = calendar.formatDates;
  1905. function render(date, delta) {
  1906. if (delta) {
  1907. addDays(date, delta * 7);
  1908. }
  1909. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1910. var end = addDays(cloneDate(start), 7);
  1911. var visStart = cloneDate(start);
  1912. var visEnd = cloneDate(end);
  1913. var weekends = opt('weekends');
  1914. if (!weekends) {
  1915. skipWeekend(visStart);
  1916. skipWeekend(visEnd, -1, true);
  1917. }
  1918. t.title = formatDates(
  1919. visStart,
  1920. addDays(cloneDate(visEnd), -1),
  1921. opt('titleFormat')
  1922. );
  1923. t.start = start;
  1924. t.end = end;
  1925. t.visStart = visStart;
  1926. t.visEnd = visEnd;
  1927. renderBasic(1, 1, weekends ? 7 : 5, false);
  1928. }
  1929. }
  1930. fcViews.basicDay = BasicDayView;
  1931. //TODO: when calendar's date starts out on a weekend, shouldn't happen
  1932. function BasicDayView(element, calendar) {
  1933. var t = this;
  1934. // exports
  1935. t.render = render;
  1936. // imports
  1937. BasicView.call(t, element, calendar, 'basicDay');
  1938. var opt = t.opt;
  1939. var renderBasic = t.renderBasic;
  1940. var formatDate = calendar.formatDate;
  1941. function render(date, delta) {
  1942. if (delta) {
  1943. addDays(date, delta);
  1944. if (!opt('weekends')) {
  1945. skipWeekend(date, delta < 0 ? -1 : 1);
  1946. }
  1947. }
  1948. t.title = formatDate(date, opt('titleFormat'));
  1949. t.start = t.visStart = cloneDate(date, true);
  1950. t.end = t.visEnd = addDays(cloneDate(t.start), 1);
  1951. renderBasic(1, 1, 1, false);
  1952. }
  1953. }
  1954. setDefaults({
  1955. weekMode: 'fixed'
  1956. });
  1957. function BasicView(element, calendar, viewName) {
  1958. var t = this;
  1959. // exports
  1960. t.renderBasic = renderBasic;
  1961. t.setHeight = setHeight;
  1962. t.setWidth = setWidth;
  1963. t.renderDayOverlay = renderDayOverlay;
  1964. t.defaultSelectionEnd = defaultSelectionEnd;
  1965. t.renderSelection = renderSelection;
  1966. t.clearSelection = clearSelection;
  1967. t.reportDayClick = reportDayClick; // for selection (kinda hacky)
  1968. t.dragStart = dragStart;
  1969. t.dragStop = dragStop;
  1970. t.defaultEventEnd = defaultEventEnd;
  1971. t.getHoverListener = function() { return hoverListener };
  1972. t.colContentLeft = colContentLeft;
  1973. t.colContentRight = colContentRight;
  1974. t.dayOfWeekCol = dayOfWeekCol;
  1975. t.dateCell = dateCell;
  1976. t.cellDate = cellDate;
  1977. t.cellIsAllDay = function() { return true };
  1978. t.allDayRow = allDayRow;
  1979. t.allDayBounds = allDayBounds;
  1980. t.getRowCnt = function() { return rowCnt };
  1981. t.getColCnt = function() { return colCnt };
  1982. t.getColWidth = function() { return colWidth };
  1983. t.getDaySegmentContainer = function() { return daySegmentContainer };
  1984. t.updateGrid = updateGrid;
  1985. t.updateToday = updateToday;
  1986. t.setAxisFormat = setAxisFormat;
  1987. t.setStartOfBusiness = setStartOfBusiness;
  1988. t.setEndOfBusiness = setEndOfBusiness;
  1989. t.setWeekendDays = setWeekendDays;
  1990. t.setBindingMode = setBindingMode;
  1991. t.setSelectable = setSelectable;
  1992. // imports
  1993. View.call(t, element, calendar, viewName);
  1994. OverlayManager.call(t);
  1995. SelectionManager.call(t);
  1996. BasicEventRenderer.call(t);
  1997. var opt = t.opt;
  1998. var trigger = t.trigger;
  1999. var clearEvents = t.clearEvents;
  2000. var renderOverlay = t.renderOverlay;
  2001. var clearOverlays = t.clearOverlays;
  2002. var daySelectionMousedown = t.daySelectionMousedown;
  2003. var formatDate = calendar.formatDate;
  2004. // locals
  2005. var head;
  2006. var headCells;
  2007. var body;
  2008. var bodyRows;
  2009. var bodyCells;
  2010. var bodyFirstCells;
  2011. var bodyCellTopInners;
  2012. var daySegmentContainer;
  2013. var viewWidth;
  2014. var viewHeight;
  2015. var colWidth;
  2016. var rowCnt, colCnt;
  2017. var coordinateGrid;
  2018. var hoverListener;
  2019. var colContentPositions;
  2020. var rtl, dis, dit;
  2021. var firstDay;
  2022. var nwe;
  2023. var tm;
  2024. var colFormat;
  2025. /* Rendering
  2026. ------------------------------------------------------------*/
  2027. disableTextSelection(element.addClass('fc-grid'));
  2028. function renderBasic(maxr, r, c, showNumbers) {
  2029. rowCnt = r;
  2030. colCnt = c;
  2031. updateOptions();
  2032. var firstTime = !body;
  2033. if (firstTime) {
  2034. buildSkeleton(maxr, showNumbers);
  2035. }else{
  2036. clearEvents();
  2037. }
  2038. updateCells(true);
  2039. }
  2040. function updateOptions() {
  2041. rtl = opt('isRTL');
  2042. if (rtl) {
  2043. dis = -1;
  2044. dit = colCnt - 1;
  2045. }else{
  2046. dis = 1;
  2047. dit = 0;
  2048. }
  2049. firstDay = opt('firstDay');
  2050. nwe = opt('weekends') ? 0 : 1;
  2051. tm = opt('theme') ? 'ui' : 'fc';
  2052. colFormat = opt('columnFormat');
  2053. }
  2054. function buildSkeleton(maxRowCnt, showNumbers) {
  2055. var s;
  2056. var headerClass = tm + "-widget-header";
  2057. var contentClass = tm + "-widget-content";
  2058. var i, j;
  2059. var table;
  2060. s =
  2061. "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  2062. "<thead>" +
  2063. "<tr>";
  2064. for (i=0; i<colCnt; i++) {
  2065. s +=
  2066. "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
  2067. }
  2068. s +=
  2069. "</tr>" +
  2070. "</thead>" +
  2071. "<tbody>";
  2072. for (i=0; i<maxRowCnt; i++) {
  2073. s +=
  2074. "<tr class='fc-week" + i + "'>";
  2075. for (j=0; j<colCnt; j++) {
  2076. s +=
  2077. "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
  2078. "<div>" +
  2079. (showNumbers ?
  2080. "<div class='fc-day-header'><div class='fc-week-number'/><div class='fc-day-text'/><div class='fc-day-number'/></div>" :
  2081. ''
  2082. ) +
  2083. "<div class='fc-day-content'>" +
  2084. "<div style='position:relative'>&nbsp;</div>" +
  2085. "</div>" +
  2086. "</div>" +
  2087. "</td>";
  2088. }
  2089. s +=
  2090. "</tr>";
  2091. }
  2092. s +=
  2093. "</tbody>" +
  2094. "</table>";
  2095. table = $(s).appendTo(element);
  2096. head = table.find('thead');
  2097. headCells = head.find('th');
  2098. body = table.find('tbody');
  2099. bodyRows = body.find('tr');
  2100. bodyCells = body.find('td');
  2101. bodyFirstCells = bodyCells.filter(':first-child');
  2102. bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
  2103. markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
  2104. markFirstLast(bodyRows); // marks first+last td's
  2105. bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
  2106. dayBind(bodyCells);
  2107. daySegmentContainer =
  2108. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2109. .appendTo(element);
  2110. }
  2111. function updateCells(firstTime) {
  2112. var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
  2113. var month = t.start.getMonth();
  2114. var today = clearTime(new Date());
  2115. var cell;
  2116. var date;
  2117. var row;
  2118. if (dowDirty) {
  2119. headCells.each(function(i, _cell) {
  2120. cell = $(_cell);
  2121. date = indexDate(i);
  2122. cell.html(formatDate(date, colFormat));
  2123. setDayID(cell, date, opt);
  2124. });
  2125. }
  2126. bodyCells.each(function(i, _cell) {
  2127. cell = $(_cell);
  2128. date = indexDate(i);
  2129. if (date.getMonth() == month) {
  2130. cell.removeClass('fc-other-month');
  2131. }else{
  2132. cell.addClass('fc-other-month');
  2133. }
  2134. if(opt('showWeekNumbers') && (i % 7 == 0)) {
  2135. removeWeekNumber(cell, date);
  2136. addWeekNumber(cell, date);
  2137. }
  2138. if (+date == +today) {
  2139. cell.addClass(tm + '-state-highlight fc-today');
  2140. removeTodayText(cell, opt('buttonText', 'today'));
  2141. addTodayText(cell, opt('buttonText', 'today'));
  2142. }else{
  2143. cell.removeClass(tm + '-state-highlight fc-today');
  2144. removeTodayText(cell, opt('buttonText', 'today'));
  2145. }
  2146. cell.find('div.fc-day-number').text(date.getDate());
  2147. if (dowDirty) {
  2148. setDayID(cell, date, opt);
  2149. }
  2150. });
  2151. bodyRows.each(function(i, _row) {
  2152. row = $(_row);
  2153. if (i < rowCnt) {
  2154. row.show();
  2155. if (i == rowCnt-1) {
  2156. row.addClass('fc-last');
  2157. }else{
  2158. row.removeClass('fc-last');
  2159. }
  2160. }else{
  2161. row.hide();
  2162. }
  2163. });
  2164. }
  2165. function updateGrid()
  2166. {
  2167. updateToday();
  2168. setAxisFormat();
  2169. setStartOfBusiness();
  2170. setEndOfBusiness();
  2171. setWeekendDays();
  2172. setBindingMode();
  2173. setSelectable();
  2174. }
  2175. function updateToday()
  2176. {
  2177. var today = clearTime(new Date());
  2178. var cell;
  2179. var date;
  2180. bodyCells.each(function(i, _cell) {
  2181. cell = $(_cell);
  2182. date = indexDate(i);
  2183. if (+date == +today) {
  2184. cell.addClass(tm + '-state-highlight fc-today');
  2185. removeTodayText(cell, opt('buttonText', 'today'));
  2186. addTodayText(cell, opt('buttonText', 'today'));
  2187. }else{
  2188. cell.removeClass(tm + '-state-highlight fc-today');
  2189. removeTodayText(cell, opt('buttonText', 'today'));
  2190. }
  2191. });
  2192. }
  2193. function setAxisFormat()
  2194. {
  2195. // dummy
  2196. }
  2197. function setStartOfBusiness()
  2198. {
  2199. // dummy
  2200. }
  2201. function setEndOfBusiness()
  2202. {
  2203. // dummy
  2204. }
  2205. function setWeekendDays()
  2206. {
  2207. headCells.each(function(i, _cell) {
  2208. setDayID($(_cell), indexDate(i), opt);
  2209. });
  2210. bodyCells.each(function(i, _cell) {
  2211. setDayID($(_cell), indexDate(i), opt);
  2212. });
  2213. }
  2214. function setBindingMode()
  2215. {
  2216. dayBind(bodyCells);
  2217. }
  2218. function setSelectable()
  2219. {
  2220. dayBind(bodyCells);
  2221. }
  2222. function setHeight(height) {
  2223. viewHeight = height;
  2224. var bodyHeight = viewHeight - head.height();
  2225. var rowHeight;
  2226. var rowHeightLast;
  2227. var cell;
  2228. if (opt('weekMode') == 'variable') {
  2229. rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
  2230. }else{
  2231. rowHeight = Math.floor(bodyHeight / rowCnt);
  2232. rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
  2233. }
  2234. bodyFirstCells.each(function(i, _cell) {
  2235. if (i < rowCnt) {
  2236. cell = $(_cell);
  2237. setMinHeight(
  2238. cell.find('> div'),
  2239. (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
  2240. );
  2241. }
  2242. });
  2243. }
  2244. function setWidth(width) {
  2245. viewWidth = width;
  2246. colContentPositions.clear();
  2247. colWidth = Math.floor(viewWidth / colCnt);
  2248. setOuterWidth(headCells.slice(0, -1), colWidth);
  2249. }
  2250. /* Day clicking and binding
  2251. -----------------------------------------------------------*/
  2252. function dayBind(days) {
  2253. days.unbind('click dblclick');
  2254. if(opt('bindingMode') == 'double')
  2255. days.dblclick(dayClick).mousedown(daySelectionMousedown);
  2256. else
  2257. days.click(dayClick).mousedown(daySelectionMousedown);
  2258. }
  2259. function dayClick(ev) {
  2260. //if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  2261. var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
  2262. var date = indexDate(index);
  2263. trigger('dayClick', this, date, true, ev);
  2264. //}
  2265. }
  2266. /* Semi-transparent Overlay Helpers
  2267. ------------------------------------------------------*/
  2268. function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
  2269. if (refreshCoordinateGrid) {
  2270. coordinateGrid.build();
  2271. }
  2272. var rowStart = cloneDate(t.visStart);
  2273. var rowEnd = addDays(cloneDate(rowStart), colCnt);
  2274. for (var i=0; i<rowCnt; i++) {
  2275. var stretchStart = new Date(Math.max(rowStart, overlayStart));
  2276. var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
  2277. if (stretchStart < stretchEnd) {
  2278. var colStart, colEnd;
  2279. if (rtl) {
  2280. colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
  2281. colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
  2282. }else{
  2283. colStart = dayDiff(stretchStart, rowStart);
  2284. colEnd = dayDiff(stretchEnd, rowStart);
  2285. }
  2286. dayBind(
  2287. renderCellOverlay(i, colStart, i, colEnd-1)
  2288. );
  2289. }
  2290. addDays(rowStart, 7);
  2291. addDays(rowEnd, 7);
  2292. }
  2293. }
  2294. function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
  2295. var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
  2296. return renderOverlay(rect, element);
  2297. }
  2298. /* Selection
  2299. -----------------------------------------------------------------------*/
  2300. function defaultSelectionEnd(startDate, allDay) {
  2301. return cloneDate(startDate);
  2302. }
  2303. function renderSelection(startDate, endDate, allDay) {
  2304. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
  2305. }
  2306. function clearSelection() {
  2307. clearOverlays();
  2308. }
  2309. function reportDayClick(date, allDay, ev) {
  2310. var cell = dateCell(date);
  2311. var _element = bodyCells[cell.row*colCnt + cell.col];
  2312. trigger('dayClick', _element, date, allDay, ev);
  2313. }
  2314. /* External Dragging
  2315. -----------------------------------------------------------------------*/
  2316. function dragStart(_dragElement, ev, ui) {
  2317. hoverListener.start(function(cell) {
  2318. clearOverlays();
  2319. if (cell) {
  2320. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  2321. }
  2322. }, ev);
  2323. }
  2324. function dragStop(_dragElement, ev, ui) {
  2325. var cell = hoverListener.stop();
  2326. clearOverlays();
  2327. if (cell) {
  2328. var d = cellDate(cell);
  2329. trigger('drop', _dragElement, d, true, ev, ui);
  2330. }
  2331. }
  2332. /* Utilities
  2333. --------------------------------------------------------*/
  2334. function defaultEventEnd(event) {
  2335. return cloneDate(event.start);
  2336. }
  2337. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  2338. var e, n, p;
  2339. headCells.each(function(i, _e) {
  2340. e = $(_e);
  2341. n = e.offset().left;
  2342. if (i) {
  2343. p[1] = n;
  2344. }
  2345. p = [n];
  2346. cols[i] = p;
  2347. });
  2348. p[1] = n + e.outerWidth();
  2349. bodyRows.each(function(i, _e) {
  2350. if (i < rowCnt) {
  2351. e = $(_e);
  2352. n = e.offset().top;
  2353. if (i) {
  2354. p[1] = n;
  2355. }
  2356. p = [n];
  2357. rows[i] = p;
  2358. }
  2359. });
  2360. p[1] = n + e.outerHeight();
  2361. });
  2362. hoverListener = new HoverListener(coordinateGrid);
  2363. colContentPositions = new HorizontalPositionCache(function(col) {
  2364. return bodyCellTopInners.eq(col);
  2365. });
  2366. function colContentLeft(col) {
  2367. return colContentPositions.left(col);
  2368. }
  2369. function colContentRight(col) {
  2370. return colContentPositions.right(col);
  2371. }
  2372. function dateCell(date) {
  2373. return {
  2374. row: Math.floor(dayDiff(date, t.visStart) / 7),
  2375. col: dayOfWeekCol(date.getDay())
  2376. };
  2377. }
  2378. function cellDate(cell) {
  2379. return _cellDate(cell.row, cell.col);
  2380. }
  2381. function _cellDate(row, col) {
  2382. return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
  2383. // what about weekends in middle of week?
  2384. }
  2385. function indexDate(index) {
  2386. return _cellDate(Math.floor(index/colCnt), index%colCnt);
  2387. }
  2388. function dayOfWeekCol(dayOfWeek) {
  2389. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
  2390. }
  2391. function allDayRow(i) {
  2392. return bodyRows.eq(i);
  2393. }
  2394. function allDayBounds(i) {
  2395. return {
  2396. left: 0,
  2397. right: viewWidth
  2398. };
  2399. }
  2400. }
  2401. function BasicEventRenderer() {
  2402. var t = this;
  2403. // exports
  2404. t.renderEvents = renderEvents;
  2405. t.compileDaySegs = compileSegs; // for DayEventRenderer
  2406. t.clearEvents = clearEvents;
  2407. t.bindDaySeg = bindDaySeg;
  2408. // imports
  2409. DayEventRenderer.call(t);
  2410. var opt = t.opt;
  2411. var trigger = t.trigger;
  2412. //var setOverflowHidden = t.setOverflowHidden;
  2413. var isEventDraggable = t.isEventDraggable;
  2414. var isEventResizable = t.isEventResizable;
  2415. var reportEvents = t.reportEvents;
  2416. var reportEventClear = t.reportEventClear;
  2417. var eventElementHandlers = t.eventElementHandlers;
  2418. var showEvents = t.showEvents;
  2419. var hideEvents = t.hideEvents;
  2420. var eventDrop = t.eventDrop;
  2421. var getDaySegmentContainer = t.getDaySegmentContainer;
  2422. var getHoverListener = t.getHoverListener;
  2423. var renderDayOverlay = t.renderDayOverlay;
  2424. var clearOverlays = t.clearOverlays;
  2425. var getRowCnt = t.getRowCnt;
  2426. var getColCnt = t.getColCnt;
  2427. var renderDaySegs = t.renderDaySegs;
  2428. var resizableDayEvent = t.resizableDayEvent;
  2429. /* Rendering
  2430. --------------------------------------------------------------------*/
  2431. function renderEvents(events, modifiedEventId) {
  2432. reportEvents(events);
  2433. renderDaySegs(compileSegs(events), modifiedEventId, false);
  2434. }
  2435. function clearEvents() {
  2436. reportEventClear();
  2437. getDaySegmentContainer().empty();
  2438. }
  2439. function compileSegs(events) {
  2440. var rowCnt = getRowCnt(),
  2441. colCnt = getColCnt(),
  2442. d1 = cloneDate(t.visStart),
  2443. d2 = addDays(cloneDate(d1), colCnt),
  2444. visEventsEnds = $.map(events, exclEndDay),
  2445. i, row,
  2446. j, level,
  2447. k, seg,
  2448. segs=[];
  2449. for (i=0; i<rowCnt; i++) {
  2450. row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
  2451. for (j=0; j<row.length; j++) {
  2452. level = row[j];
  2453. for (k=0; k<level.length; k++) {
  2454. seg = level[k];
  2455. seg.row = i;
  2456. seg.level = j; // not needed anymore
  2457. segs.push(seg);
  2458. }
  2459. }
  2460. addDays(d1, 7);
  2461. addDays(d2, 7);
  2462. }
  2463. return segs;
  2464. }
  2465. function bindDaySeg(event, eventElement, seg) {
  2466. if (isEventDraggable(event)) {
  2467. draggableDayEvent(event, eventElement);
  2468. }
  2469. if (seg.isEnd && isEventResizable(event)) {
  2470. resizableDayEvent(event, eventElement, seg);
  2471. }
  2472. eventElementHandlers(event, eventElement);
  2473. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  2474. }
  2475. /* Dragging
  2476. ----------------------------------------------------------------------------*/
  2477. function draggableDayEvent(event, eventElement) {
  2478. var hoverListener = getHoverListener();
  2479. var dayDelta;
  2480. eventElement.draggable({
  2481. zIndex: 9,
  2482. delay: 50,
  2483. scroll: false,
  2484. opacity: opt('dragOpacity'),
  2485. revertDuration: opt('dragRevertDuration'),
  2486. start: function(ev, ui) {
  2487. trigger('eventDragStart', eventElement, event, ev, ui);
  2488. //hideEvents(event, eventElement);
  2489. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  2490. eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
  2491. clearOverlays();
  2492. if (cell) {
  2493. //setOverflowHidden(true);
  2494. dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
  2495. renderDayOverlay(
  2496. addDays(cloneDate(event.start), dayDelta),
  2497. addDays(exclEndDay(event), dayDelta)
  2498. );
  2499. }else{
  2500. //setOverflowHidden(false);
  2501. dayDelta = 0;
  2502. }
  2503. }, ev, 'drag');
  2504. },
  2505. stop: function(ev, ui) {
  2506. hoverListener.stop();
  2507. clearOverlays();
  2508. trigger('eventDragStop', eventElement, event, ev, ui);
  2509. if (dayDelta) {
  2510. eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
  2511. }else{
  2512. eventElement.css('filter', ''); // clear IE opacity side-effects
  2513. //showEvents(event, eventElement);
  2514. }
  2515. //setOverflowHidden(false);
  2516. }
  2517. });
  2518. }
  2519. }
  2520. fcViews.agendaWeek = AgendaWeekView;
  2521. function AgendaWeekView(element, calendar) {
  2522. var t = this;
  2523. // exports
  2524. t.render = render;
  2525. // imports
  2526. AgendaView.call(t, element, calendar, 'agendaWeek');
  2527. var opt = t.opt;
  2528. var renderAgenda = t.renderAgenda;
  2529. var formatDates = calendar.formatDates;
  2530. function render(date, delta) {
  2531. if (delta) {
  2532. addDays(date, delta * 7);
  2533. }
  2534. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  2535. var end = addDays(cloneDate(start), 7);
  2536. var visStart = cloneDate(start);
  2537. var visEnd = cloneDate(end);
  2538. var weekends = opt('weekends');
  2539. if (!weekends) {
  2540. skipWeekend(visStart);
  2541. skipWeekend(visEnd, -1, true);
  2542. }
  2543. t.title = formatDates(
  2544. visStart,
  2545. addDays(cloneDate(visEnd), -1),
  2546. opt('titleFormat')
  2547. );
  2548. t.start = start;
  2549. t.end = end;
  2550. t.visStart = visStart;
  2551. t.visEnd = visEnd;
  2552. renderAgenda(weekends ? 7 : 5);
  2553. }
  2554. }
  2555. fcViews.agendaDay = AgendaDayView;
  2556. function AgendaDayView(element, calendar) {
  2557. var t = this;
  2558. // exports
  2559. t.render = render;
  2560. t.addedView = null;
  2561. // imports
  2562. AgendaView.call(t, element, calendar, 'agendaDay');
  2563. var opt = t.opt;
  2564. var renderAgenda = t.renderAgenda;
  2565. var formatDate = calendar.formatDate;
  2566. function render(date, delta) {
  2567. if (delta) {
  2568. addDays(date, delta);
  2569. if (!opt('weekends')) {
  2570. skipWeekend(date, delta < 0 ? -1 : 1);
  2571. }
  2572. }
  2573. var start = cloneDate(date, true);
  2574. var end = addDays(cloneDate(start), 1);
  2575. t.title = formatDate(date, opt('titleFormat'));
  2576. t.start = t.visStart = start;
  2577. t.end = t.visEnd = end;
  2578. renderAgenda(1);
  2579. if(t.addedView) {
  2580. t.addedView.render(date);
  2581. }
  2582. }
  2583. }
  2584. setDefaults({
  2585. allDaySlot: true,
  2586. allDayText: 'all-day',
  2587. firstHour: 6,
  2588. slotMinutes: 30,
  2589. defaultEventMinutes: 120,
  2590. axisFormat: 'h(:mm)tt',
  2591. timeFormat: {
  2592. agenda: 'h:mm{ – h:mm}'
  2593. },
  2594. dragOpacity: {
  2595. agenda: .5
  2596. },
  2597. minTime: 0,
  2598. maxTime: 24
  2599. });
  2600. // TODO: make it work in quirks mode (event corners, all-day height)
  2601. // TODO: test liquid width, especially in IE6
  2602. function AgendaView(element, calendar, viewName) {
  2603. var t = this;
  2604. // exports
  2605. t.renderAgenda = renderAgenda;
  2606. t.setWidth = setWidth;
  2607. t.setHeight = setHeight;
  2608. t.beforeHide = beforeHide;
  2609. t.afterShow = afterShow;
  2610. t.defaultEventEnd = defaultEventEnd;
  2611. t.timePosition = timePosition;
  2612. t.dayOfWeekCol = dayOfWeekCol;
  2613. t.dateCell = dateCell;
  2614. t.cellDate = cellDate;
  2615. t.cellIsAllDay = cellIsAllDay;
  2616. t.allDayRow = getAllDayRow;
  2617. t.allDayBounds = allDayBounds;
  2618. t.getHoverListener = function() { return hoverListener };
  2619. t.colContentLeft = colContentLeft;
  2620. t.colContentRight = colContentRight;
  2621. t.getDaySegmentContainer = function() { return daySegmentContainer };
  2622. t.getSlotJumpersTop = function() { return slotJumpersTop };
  2623. t.getSlotJumpersBottom = function() { return slotJumpersBottom };
  2624. t.getslotScroller = function() { return slotScroller };
  2625. t.getSlotContent = function() { return slotContent };
  2626. t.getSlotSegmentContainer = function() { return slotSegmentContainer };
  2627. t.getMinMinute = function() { return minMinute };
  2628. t.getMaxMinute = function() { return maxMinute };
  2629. t.getBodyContent = function() { return slotContent }; // !!??
  2630. t.getRowCnt = function() { return 1 };
  2631. t.getColCnt = function() { return colCnt };
  2632. t.getColWidth = function() { return colWidth };
  2633. t.getSlotHeight = function() { return slotHeight };
  2634. t.defaultSelectionEnd = defaultSelectionEnd;
  2635. t.renderDayOverlay = renderDayOverlay;
  2636. t.renderSelection = renderSelection;
  2637. t.renderSlotSelection = renderSlotSelection;
  2638. t.clearSelection = clearSelection;
  2639. t.reportDayClick = reportDayClick; // selection mousedown hack
  2640. t.dragStart = dragStart;
  2641. t.dragStop = dragStop;
  2642. t.updateGrid = updateGrid;
  2643. t.updateToday = updateToday;
  2644. t.setAxisFormat = setAxisFormat;
  2645. t.setStartOfBusiness = setStartOfBusiness;
  2646. t.setEndOfBusiness = setEndOfBusiness;
  2647. t.setWeekendDays = setWeekendDays;
  2648. t.setBindingMode = setBindingMode;
  2649. t.setSelectable = setSelectable;
  2650. // imports
  2651. View.call(t, element, calendar, viewName);
  2652. OverlayManager.call(t);
  2653. SelectionManager.call(t);
  2654. AgendaEventRenderer.call(t);
  2655. var opt = t.opt;
  2656. var trigger = t.trigger;
  2657. var clearEvents = t.clearEvents;
  2658. var renderOverlay = t.renderOverlay;
  2659. var clearOverlays = t.clearOverlays;
  2660. var reportSelection = t.reportSelection;
  2661. var unselect = t.unselect;
  2662. var daySelectionMousedown = t.daySelectionMousedown;
  2663. var slotSegHtml = t.slotSegHtml;
  2664. var formatDate = calendar.formatDate;
  2665. var setTimeIndicator = t.setTimeIndicator;
  2666. // locals
  2667. var dayTable;
  2668. var dayHead;
  2669. var dayHeadCells;
  2670. var dayBody;
  2671. var dayBodyCells;
  2672. var dayBodyCellInners;
  2673. var dayBodyFirstCell;
  2674. var dayBodyFirstCellStretcher;
  2675. var slotLayer;
  2676. var daySegmentContainer;
  2677. var allDayTable;
  2678. var allDayRow;
  2679. var slotJumpersTopContainer;
  2680. var slotJumpersTop;
  2681. var slotJumpersBottomContainer;
  2682. var slotJumpersBottom;
  2683. var slotScroller;
  2684. var slotContent;
  2685. var slotSegmentContainer;
  2686. var dayScroller;
  2687. var dayContent;
  2688. var daySegmentContainer;
  2689. var slotTable;
  2690. var slotTableFirstInner;
  2691. var axisFirstCells;
  2692. var gutterCells;
  2693. var divider;
  2694. var selectionHelper;
  2695. var viewWidth;
  2696. var viewHeight;
  2697. var axisWidth;
  2698. var colWidth;
  2699. var gutterWidth;
  2700. //var gutterAck = false;
  2701. var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
  2702. var savedScrollTop;
  2703. var colCnt;
  2704. var slotCnt;
  2705. var coordinateGrid;
  2706. var hoverListener;
  2707. var colContentPositions;
  2708. var slotTopCache = {};
  2709. var tm;
  2710. var firstDay;
  2711. var nwe; // no weekends (int)
  2712. var rtl, dis, dit; // day index sign / translate
  2713. var minMinute, maxMinute;
  2714. var colFormat;
  2715. /* Rendering
  2716. -----------------------------------------------------------------------------*/
  2717. disableTextSelection(element.addClass('fc-agenda'));
  2718. function renderAgenda(c) {
  2719. colCnt = c;
  2720. updateOptions();
  2721. if (!dayTable) {
  2722. buildSkeleton();
  2723. }else{
  2724. clearEvents();
  2725. }
  2726. updateCells();
  2727. }
  2728. function updateOptions() {
  2729. tm = opt('theme') ? 'ui' : 'fc';
  2730. nwe = opt('weekends') ? 0 : 1;
  2731. firstDay = opt('firstDay');
  2732. if (rtl = opt('isRTL')) {
  2733. dis = -1;
  2734. dit = colCnt - 1;
  2735. }else{
  2736. dis = 1;
  2737. dit = 0;
  2738. }
  2739. minMinute = parseTime(opt('minTime'));
  2740. maxMinute = parseTime(opt('maxTime'));
  2741. colFormat = opt('columnFormat');
  2742. }
  2743. function buildSkeleton() {
  2744. var headerClass = tm + "-widget-header";
  2745. var contentClass = tm + "-widget-content";
  2746. var s;
  2747. var i;
  2748. var d;
  2749. var maxd;
  2750. var minutes;
  2751. var slotNormal = opt('slotMinutes') % 15 == 0;
  2752. s =
  2753. "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
  2754. "<thead>" +
  2755. "<tr>" +
  2756. "<th class='fc-agenda-axis " + headerClass + "'><div class='fc-week-number'/></th>";
  2757. for (i=0; i<colCnt; i++) {
  2758. s +=
  2759. "<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
  2760. }
  2761. s +=
  2762. "<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
  2763. "</tr>" +
  2764. "</thead>" +
  2765. "<tbody>" +
  2766. "<tr>" +
  2767. "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2768. for (i=0; i<colCnt; i++) {
  2769. s +=
  2770. "<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
  2771. "<div>" +
  2772. "<div class='fc-day-content'>" +
  2773. "<div style='position:relative'>&nbsp;</div>" +
  2774. "</div>" +
  2775. "</div>" +
  2776. "</td>";
  2777. }
  2778. s +=
  2779. "<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
  2780. "</tr>" +
  2781. "</tbody>" +
  2782. "</table>";
  2783. dayTable = $(s).appendTo(element);
  2784. dayHead = dayTable.find('thead');
  2785. dayHeadCells = dayHead.find('th').slice(1, -1);
  2786. dayBody = dayTable.find('tbody');
  2787. dayBodyCells = dayBody.find('td').slice(0, -1);
  2788. dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
  2789. dayBodyFirstCell = dayBodyCells.eq(0);
  2790. dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
  2791. markFirstLast(dayHead.add(dayHead.find('tr')));
  2792. markFirstLast(dayBody.add(dayBody.find('tr')));
  2793. axisFirstCells = dayHead.find('th:first');
  2794. gutterCells = dayTable.find('.fc-agenda-gutter');
  2795. slotLayer =
  2796. $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
  2797. .appendTo(element);
  2798. if(opt('allDaySlot')) {
  2799. dayScroller = $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto;'/>").appendTo(slotLayer);
  2800. dayContent = $("<div style='position:relative;width:100%;overflow:hidden;min-height:37px'/>").appendTo(dayScroller);
  2801. daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(dayContent);
  2802. s =
  2803. "<table style='width:100%;' class='fc-agenda-allday' cellspacing='0'>" +
  2804. "<tr>" +
  2805. "<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
  2806. "<td>" +
  2807. "<div class='fc-day-content'><div style='position:relative;min-height:34px'/></div>" +
  2808. "</td>" +
  2809. "</tr>" +
  2810. "</table>";
  2811. allDayTable = $(s).appendTo(dayScroller);
  2812. allDayRow = allDayTable.find('tr');
  2813. dayBind(allDayRow.find('td'));
  2814. axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
  2815. gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
  2816. divider = $(
  2817. "<div class='fc-agenda-divider " + headerClass + "'>" +
  2818. "<div class='fc-agenda-divider-inner'/>" +
  2819. "</div>"
  2820. ).appendTo(slotLayer);
  2821. }else{
  2822. daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
  2823. }
  2824. slotJumpersTopContainer = $("<div style='position:relative;width:100%;'/>").appendTo(slotLayer);
  2825. slotJumpersBottomContainer = $("<div style='position:relative;width:100%;'/>").appendTo(slotLayer);
  2826. slotScroller = $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>").appendTo(slotLayer);
  2827. slotContent = $("<div style='position:relative;width:100%;overflow:hidden'/>").appendTo(slotScroller);
  2828. slotSegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(slotContent);
  2829. for (i=0; i<colCnt; i++) {
  2830. slotJumpersTopContainer.append($('<div class="fc-slot-jumper-top"/>'));
  2831. slotJumpersBottomContainer.append($('<div class="fc-slot-jumper-bottom"/>'));
  2832. }
  2833. slotJumpersTop = slotJumpersTopContainer.children();
  2834. slotJumpersBottom = slotJumpersBottomContainer.children();
  2835. s =
  2836. "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
  2837. "<tbody>";
  2838. d = zeroDate();
  2839. maxd = addMinutes(cloneDate(d), maxMinute);
  2840. addMinutes(d, minMinute);
  2841. slotCnt = 0;
  2842. var startOfBusiness = opt("startOfBusiness") * (60/opt("slotMinutes"));
  2843. var endOfBusiness = (opt("endOfBusiness") - (opt("slotMinutes")/60)) * (60/opt("slotMinutes"));
  2844. for (i=0; d < maxd; i++) {
  2845. minutes = d.getMinutes();
  2846. var nonBusinessHours = (i < startOfBusiness || i > endOfBusiness) ? " fc-non-business-hours" : "";
  2847. s +=
  2848. "<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + nonBusinessHours + "'>" +
  2849. "<th class='fc-agenda-axis " + headerClass + "'>" +
  2850. ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
  2851. "</th>" +
  2852. "<td class='" + contentClass + "'>" +
  2853. "<div style='position:relative'>&nbsp;</div>" +
  2854. "</td>" +
  2855. "</tr>";
  2856. addMinutes(d, opt('slotMinutes'));
  2857. slotCnt++;
  2858. }
  2859. s +=
  2860. "</tbody>" +
  2861. "</table>";
  2862. slotTable = $(s).appendTo(slotContent);
  2863. slotTableFirstInner = slotTable.find('div:first');
  2864. slotBind(slotTable.find('td'));
  2865. axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
  2866. }
  2867. function updateCells() {
  2868. var i;
  2869. var headCell;
  2870. var bodyCell;
  2871. var axisCell;
  2872. var date;
  2873. var today = clearTime(new Date());
  2874. axisCell = axisFirstCells[0];
  2875. if(opt('showWeekNumbers')) {
  2876. removeWeekNumber($(axisCell), colDate(0));
  2877. addWeekNumber($(axisCell), colDate(0));
  2878. }
  2879. for (i=0; i<colCnt; i++) {
  2880. date = colDate(i);
  2881. headCell = dayHeadCells.eq(i);
  2882. headCell.html(formatDate(date, colFormat));
  2883. bodyCell = dayBodyCells.eq(i);
  2884. setDayID(headCell.add(bodyCell), date, opt);
  2885. if (+date == +today) {
  2886. bodyCell.addClass(tm + '-state-highlight fc-today');
  2887. addTodayClass(bodyCell);
  2888. }else{
  2889. bodyCell.removeClass(tm + '-state-highlight fc-today');
  2890. removeTodayClass(bodyCell);
  2891. }
  2892. }
  2893. }
  2894. function updateGrid()
  2895. {
  2896. updateToday();
  2897. setTimeIndicator();
  2898. setAxisFormat();
  2899. setStartOfBusiness();
  2900. setEndOfBusiness();
  2901. setWeekendDays();
  2902. setBindingMode();
  2903. setSelectable();
  2904. }
  2905. function updateToday()
  2906. {
  2907. var i;
  2908. var bodyCell;
  2909. var date;
  2910. var today = clearTime(new Date());
  2911. for (i=0; i<colCnt; i++) {
  2912. date = colDate(i);
  2913. bodyCell = dayBodyCells.eq(i);
  2914. if (+date == +today) {
  2915. bodyCell.addClass(tm + '-state-highlight fc-today');
  2916. addTodayClass(bodyCell);
  2917. }else{
  2918. bodyCell.removeClass(tm + '-state-highlight fc-today');
  2919. removeTodayClass(bodyCell);
  2920. }
  2921. }
  2922. }
  2923. function setAxisFormat()
  2924. {
  2925. var slotNormal = opt('slotMinutes') % 15 == 0;
  2926. var d = zeroDate();
  2927. addMinutes(d, minMinute);
  2928. slotTable.find('th').each(function(index, element){
  2929. var minutes = d.getMinutes();
  2930. $(element).html((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;');
  2931. addMinutes(d, opt('slotMinutes'));
  2932. });
  2933. }
  2934. function setStartOfBusiness()
  2935. {
  2936. updateBusinessHours();
  2937. }
  2938. function setEndOfBusiness()
  2939. {
  2940. updateBusinessHours();
  2941. }
  2942. function updateBusinessHours()
  2943. {
  2944. var startOfBusiness = opt("startOfBusiness") * (60/opt("slotMinutes"));
  2945. var endOfBusiness = (opt("endOfBusiness") - (opt("slotMinutes")/60)) * (60/opt("slotMinutes"));
  2946. slotTable.find('tr').each(function(index, element){
  2947. if(index < startOfBusiness || index > endOfBusiness)
  2948. $(element).addClass('fc-non-business-hours');
  2949. else
  2950. $(element).removeClass('fc-non-business-hours');
  2951. });
  2952. }
  2953. function setWeekendDays()
  2954. {
  2955. dayHeadCells.each(function(i, _cell) {
  2956. setDayID($(_cell), colDate(i), opt);
  2957. });
  2958. dayBodyCells.each(function(i, _cell) {
  2959. setDayID($(_cell), colDate(i), opt);
  2960. });
  2961. }
  2962. function setBindingMode()
  2963. {
  2964. dayBind(allDayRow.find('td'));
  2965. slotBind(slotTable.find('td'));
  2966. }
  2967. function setSelectable()
  2968. {
  2969. dayBind(allDayRow.find('td'));
  2970. slotBind(slotTable.find('td'));
  2971. }
  2972. function setHeight(height, dateChanged) {
  2973. if (height === undefined) {
  2974. height = viewHeight;
  2975. }
  2976. viewHeight = height;
  2977. slotTopCache = {};
  2978. var headHeight = dayBody.position().top;
  2979. var allDayHeight = opt('allDaySlot') ? 4 : 0; //if divider is present
  2980. var bodyHeight = Math.min( // total body height, including borders
  2981. height - headHeight, // when scrollbars
  2982. slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
  2983. );
  2984. var maxAllDayHeight = Math.floor((bodyHeight - allDayHeight - 1) / 3);
  2985. dayScroller.css('max-height', maxAllDayHeight + 3);
  2986. allDayRow.find('div:first').children().css('max-height', maxAllDayHeight);
  2987. allDayHeight = allDayTable.height();
  2988. if(opt('allDaySlot')) {
  2989. divider.css('position', 'relative');
  2990. divider.css('top', allDayHeight);
  2991. slotScroller.css('top', allDayHeight + 4);
  2992. }
  2993. //allDayHeight = slotScroller.position().top; // including divider
  2994. bodyHeight = Math.min( // total body height, including borders
  2995. height - headHeight, // when scrollbars
  2996. slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
  2997. );
  2998. dayBodyFirstCellStretcher
  2999. .height(bodyHeight - vsides(dayBodyFirstCell));
  3000. var slotScrollerHeight = bodyHeight - allDayHeight - 1 - (opt('allDaySlot') ? 4 : 0);
  3001. slotLayer.css('top', headHeight);
  3002. slotScroller.height(slotScrollerHeight);
  3003. slotHeight = slotTableFirstInner.height() + 1; // +1 for border
  3004. slotJumpersTopContainer.css('top', allDayHeight+1);
  3005. slotJumpersBottomContainer.css('top', slotScrollerHeight + allDayHeight + 1 - slotJumpersBottom.first().height());
  3006. if (dateChanged) {
  3007. resetScroll();
  3008. }
  3009. if(t.addedView) {
  3010. t.addedView.setHeight(height, dateChanged);
  3011. }
  3012. }
  3013. function setWidth(width) {
  3014. if (width === undefined) {
  3015. width = viewWidth;
  3016. }
  3017. viewWidth = width;
  3018. if(t.addedView) {
  3019. var outerWidth = Math.floor(element.parent().width() / 2);
  3020. element.css({'width' : outerWidth});
  3021. viewWidth = outerWidth;
  3022. }
  3023. colContentPositions.clear();
  3024. axisWidth = 0;
  3025. setOuterWidth(
  3026. axisFirstCells
  3027. .width('')
  3028. .each(function(i, _cell) {
  3029. axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
  3030. }),
  3031. axisWidth
  3032. );
  3033. var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
  3034. //slotTable.width(slotTableWidth);
  3035. //var oldGutterWidth = gutterWidth;
  3036. gutterWidth = slotScroller.width() - slotTableWidth || dayScroller.width() - dayContent.width();
  3037. if (gutterWidth) {
  3038. /*if(!gutterAck) {
  3039. viewWidth -= gutterWidth;
  3040. gutterAck = true;
  3041. }*/
  3042. setOuterWidth(gutterCells, gutterWidth);
  3043. gutterCells
  3044. .show()
  3045. .prev()
  3046. .removeClass('fc-last');
  3047. }else{
  3048. /*if(gutterAck) {
  3049. viewWidth += oldGutterWidth;
  3050. gutterAck = false;
  3051. }*/
  3052. gutterCells
  3053. .hide()
  3054. .prev()
  3055. .addClass('fc-last');
  3056. }
  3057. colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
  3058. setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
  3059. slotJumpersTop.each(function(i,e){
  3060. var jumper=$(e);
  3061. jumper.css('left',axisWidth + (colWidth*(i+1)) - 1 - jumper.width());
  3062. });
  3063. slotJumpersBottom.each(function(i,e){
  3064. var jumper=$(e);
  3065. jumper.css('left',axisWidth + (colWidth*(i+1)) - 1 - jumper.width());
  3066. });
  3067. if(t.addedView) {
  3068. t.addedView.setWidth(outerWidth);
  3069. }
  3070. }
  3071. function resetScroll() {
  3072. var d0 = zeroDate();
  3073. var scrollDate = cloneDate(d0);
  3074. scrollDate.setHours(opt('firstHour'));
  3075. var top = timePosition(d0, scrollDate) + 1; // +1 for the border
  3076. function scroll() {
  3077. slotScroller.scrollTop(top);
  3078. }
  3079. scroll();
  3080. setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
  3081. }
  3082. function beforeHide() {
  3083. savedScrollTop = slotScroller.scrollTop();
  3084. }
  3085. function afterShow() {
  3086. slotScroller.scrollTop(savedScrollTop);
  3087. }
  3088. /* Slot/Day clicking and binding
  3089. -----------------------------------------------------------------------*/
  3090. function dayBind(cells) {
  3091. cells.unbind('click dblclick');
  3092. if(opt('bindingMode') == 'double')
  3093. cells.dblclick(daySlotClick).mousedown(daySelectionMousedown);
  3094. else
  3095. cells.click(daySlotClick).mousedown(daySelectionMousedown);
  3096. }
  3097. function slotBind(cells) {
  3098. cells.unbind('click dblclick');
  3099. if(opt('bindingMode') == 'double')
  3100. cells.dblclick(slotClick).mousedown(slotSelectionMousedown);
  3101. else
  3102. cells.click(slotClick).mousedown(slotSelectionMousedown);
  3103. }
  3104. function daySlotClick(ev) {
  3105. var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
  3106. var date = colDate(col);
  3107. trigger('dayClick', dayBodyCells[col], date, true, ev);
  3108. }
  3109. function slotClick(ev) {
  3110. //if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  3111. var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
  3112. var date = colDate(col);
  3113. var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
  3114. if (rowMatch) {
  3115. var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
  3116. var hours = Math.floor(mins/60);
  3117. date.setHours(hours);
  3118. date.setMinutes(mins%60 + minMinute);
  3119. trigger('dayClick', dayBodyCells[col], date, false, ev);
  3120. }else{
  3121. trigger('dayClick', dayBodyCells[col], date, true, ev);
  3122. }
  3123. //}
  3124. }
  3125. /* Semi-transparent Overlay Helpers
  3126. -----------------------------------------------------*/
  3127. function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
  3128. if (refreshCoordinateGrid) {
  3129. coordinateGrid.build();
  3130. }
  3131. var visStart = cloneDate(t.visStart);
  3132. var startCol, endCol;
  3133. if (rtl) {
  3134. startCol = dayDiff(endDate, visStart)*dis+dit+1;
  3135. endCol = dayDiff(startDate, visStart)*dis+dit+1;
  3136. }else{
  3137. startCol = dayDiff(startDate, visStart);
  3138. endCol = dayDiff(endDate, visStart);
  3139. }
  3140. startCol = Math.max(0, startCol);
  3141. endCol = Math.min(colCnt, endCol);
  3142. if (startCol < endCol) {
  3143. dayBind(
  3144. renderCellOverlay(0, startCol, 0, endCol-1)
  3145. );
  3146. }
  3147. }
  3148. function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
  3149. var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
  3150. return renderOverlay(rect, slotLayer);
  3151. }
  3152. function renderSlotOverlay(overlayStart, overlayEnd) {
  3153. var dayStart = cloneDate(t.visStart);
  3154. var dayEnd = addDays(cloneDate(dayStart), 1);
  3155. for (var i=0; i<colCnt; i++) {
  3156. var stretchStart = new Date(Math.max(dayStart, overlayStart));
  3157. var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
  3158. if (stretchStart < stretchEnd) {
  3159. var col = i*dis+dit;
  3160. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
  3161. var top = timePosition(dayStart, stretchStart);
  3162. var bottom = timePosition(dayStart, stretchEnd);
  3163. rect.top = top;
  3164. rect.height = bottom - top;
  3165. slotBind(
  3166. renderOverlay(rect, slotContent)
  3167. );
  3168. }
  3169. addDays(dayStart, 1);
  3170. addDays(dayEnd, 1);
  3171. }
  3172. }
  3173. /* Coordinate Utilities
  3174. -----------------------------------------------------------------------------*/
  3175. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  3176. var e, n, p;
  3177. dayHeadCells.each(function(i, _e) {
  3178. e = $(_e);
  3179. n = e.offset().left;
  3180. if (i) {
  3181. p[1] = n;
  3182. }
  3183. p = [n];
  3184. cols[i] = p;
  3185. });
  3186. p[1] = n + e.outerWidth();
  3187. if (opt('allDaySlot')) {
  3188. e = allDayRow;
  3189. n = e.offset().top;
  3190. rows[0] = [n, n+e.outerHeight()];
  3191. }
  3192. var slotTableTop = slotContent.offset().top;
  3193. var slotScrollerTop = slotScroller.offset().top;
  3194. var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
  3195. function constrain(n) {
  3196. return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
  3197. }
  3198. for (var i=0; i<slotCnt; i++) {
  3199. rows.push([
  3200. constrain(slotTableTop + slotHeight*i),
  3201. constrain(slotTableTop + slotHeight*(i+1))
  3202. ]);
  3203. }
  3204. });
  3205. hoverListener = new HoverListener(coordinateGrid);
  3206. colContentPositions = new HorizontalPositionCache(function(col) {
  3207. return dayBodyCellInners.eq(col);
  3208. });
  3209. function colContentLeft(col) {
  3210. return colContentPositions.left(col);
  3211. }
  3212. function colContentRight(col) {
  3213. return colContentPositions.right(col);
  3214. }
  3215. function dateCell(date) { // "cell" terminology is now confusing
  3216. return {
  3217. row: Math.floor(dayDiff(date, t.visStart) / 7),
  3218. col: dayOfWeekCol(date.getDay())
  3219. };
  3220. }
  3221. function cellDate(cell) {
  3222. var d = colDate(cell.col);
  3223. var slotIndex = cell.row;
  3224. if (opt('allDaySlot')) {
  3225. slotIndex--;
  3226. }
  3227. if (slotIndex >= 0) {
  3228. addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
  3229. }
  3230. return d;
  3231. }
  3232. function colDate(col) { // returns dates with 00:00:00
  3233. return addDays(cloneDate(t.visStart), col*dis+dit);
  3234. }
  3235. function cellIsAllDay(cell) {
  3236. return opt('allDaySlot') && !cell.row;
  3237. }
  3238. function dayOfWeekCol(dayOfWeek) {
  3239. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
  3240. }
  3241. // get the Y coordinate of the given time on the given day (both Date objects)
  3242. function timePosition(day, time) { // both date objects. day holds 00:00 of current day
  3243. day = cloneDate(day, true);
  3244. if (time < addMinutes(cloneDate(day), minMinute)) {
  3245. return 0;
  3246. }
  3247. if (time >= addMinutes(cloneDate(day), maxMinute)) {
  3248. return slotTable.height();
  3249. }
  3250. var slotMinutes = opt('slotMinutes'),
  3251. minutes = time.getHours()*60 + time.getMinutes() - minMinute,
  3252. slotI = Math.floor(minutes / slotMinutes),
  3253. slotTop = slotTopCache[slotI];
  3254. if (slotTop === undefined) {
  3255. slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
  3256. }
  3257. return Math.max(0, Math.round(
  3258. slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
  3259. ));
  3260. }
  3261. function allDayBounds() {
  3262. return {
  3263. left: axisWidth,
  3264. right: viewWidth - gutterWidth
  3265. }
  3266. }
  3267. function getAllDayRow(index) {
  3268. return allDayRow;
  3269. }
  3270. function defaultEventEnd(event) {
  3271. var start = cloneDate(event.start);
  3272. if (event.allDay) {
  3273. return start;
  3274. }
  3275. return addMinutes(start, opt('defaultEventMinutes'));
  3276. }
  3277. /* Selection
  3278. ---------------------------------------------------------------------------------*/
  3279. function defaultSelectionEnd(startDate, allDay) {
  3280. if (allDay) {
  3281. return cloneDate(startDate);
  3282. }
  3283. return addMinutes(cloneDate(startDate), opt('slotMinutes'));
  3284. }
  3285. function renderSelection(startDate, endDate, allDay) { // only for all-day
  3286. if (allDay) {
  3287. if (opt('allDaySlot')) {
  3288. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
  3289. }
  3290. }else{
  3291. renderSlotSelection(startDate, endDate);
  3292. }
  3293. }
  3294. function renderSlotSelection(startDate, endDate) {
  3295. var helperOption = opt('selectHelper');
  3296. coordinateGrid.build();
  3297. if (helperOption) {
  3298. var col = dayDiff(startDate, t.visStart) * dis + dit;
  3299. if (col >= 0 && col < colCnt) { // only works when times are on same day
  3300. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
  3301. var top = timePosition(startDate, startDate);
  3302. var bottom = timePosition(startDate, endDate);
  3303. if (bottom > top) { // protect against selections that are entirely before or after visible range
  3304. rect.top = top;
  3305. rect.height = bottom - top;
  3306. rect.left += 2;
  3307. rect.width -= 5;
  3308. if ($.isFunction(helperOption)) {
  3309. var helperRes = helperOption(startDate, endDate);
  3310. if (helperRes) {
  3311. rect.position = 'absolute';
  3312. rect.zIndex = 8;
  3313. selectionHelper = $(helperRes)
  3314. .css(rect)
  3315. .appendTo(slotContent);
  3316. }
  3317. }else{
  3318. rect.isStart = true; // conside rect a "seg" now
  3319. rect.isEnd = true; //
  3320. selectionHelper = $(slotSegHtml(
  3321. {
  3322. title: '',
  3323. start: startDate,
  3324. end: endDate,
  3325. className: ['fc-select-helper'],
  3326. editable: false
  3327. },
  3328. rect
  3329. ));
  3330. selectionHelper.css('opacity', opt('dragOpacity'));
  3331. }
  3332. if (selectionHelper) {
  3333. slotBind(selectionHelper);
  3334. slotContent.append(selectionHelper);
  3335. setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
  3336. setOuterHeight(selectionHelper, rect.height, true);
  3337. }
  3338. }
  3339. }
  3340. }else{
  3341. renderSlotOverlay(startDate, endDate);
  3342. }
  3343. }
  3344. function clearSelection() {
  3345. clearOverlays();
  3346. if (selectionHelper) {
  3347. selectionHelper.remove();
  3348. selectionHelper = null;
  3349. }
  3350. }
  3351. function slotSelectionMousedown(ev) {
  3352. if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
  3353. unselect(ev);
  3354. var dates;
  3355. hoverListener.start(function(cell, origCell) {
  3356. clearSelection();
  3357. if (cell && (cell.col == origCell.col || !opt('selectHelper')) && !cellIsAllDay(cell)) {
  3358. var d1 = cellDate(origCell);
  3359. var d2 = cellDate(cell);
  3360. dates = [
  3361. d1,
  3362. addMinutes(cloneDate(d1), opt('slotMinutes')),
  3363. d2,
  3364. addMinutes(cloneDate(d2), opt('slotMinutes'))
  3365. ].sort(cmp);
  3366. renderSlotSelection(dates[0], dates[3]);
  3367. }else{
  3368. dates = null;
  3369. }
  3370. }, ev);
  3371. $(document).one('mouseup', function(ev) {
  3372. hoverListener.stop();
  3373. if (dates) {
  3374. if (+dates[0] == +dates[1]) {
  3375. //reportDayClick(dates[0], false, ev);
  3376. }
  3377. reportSelection(dates[0], dates[3], false, ev);
  3378. }
  3379. });
  3380. }
  3381. }
  3382. function reportDayClick(date, allDay, ev) {
  3383. trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
  3384. }
  3385. /* External Dragging
  3386. --------------------------------------------------------------------------------*/
  3387. function dragStart(_dragElement, ev, ui) {
  3388. hoverListener.start(function(cell) {
  3389. clearOverlays();
  3390. if (cell) {
  3391. if (cellIsAllDay(cell)) {
  3392. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  3393. }else{
  3394. var d1 = cellDate(cell);
  3395. var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
  3396. renderSlotOverlay(d1, d2);
  3397. }
  3398. }
  3399. }, ev);
  3400. }
  3401. function dragStop(_dragElement, ev, ui) {
  3402. var cell = hoverListener.stop();
  3403. clearOverlays();
  3404. if (cell) {
  3405. trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
  3406. }
  3407. }
  3408. }
  3409. function AgendaEventRenderer() {
  3410. var t = this;
  3411. // exports
  3412. t.renderEvents = renderEvents;
  3413. t.compileDaySegs = compileDaySegs; // for DayEventRenderer
  3414. t.clearEvents = clearEvents;
  3415. t.slotSegHtml = slotSegHtml;
  3416. t.bindDaySeg = bindDaySeg;
  3417. t.setTimeIndicator = setTimeIndicator;
  3418. // imports
  3419. DayEventRenderer.call(t);
  3420. var opt = t.opt;
  3421. var trigger = t.trigger;
  3422. //var setOverflowHidden = t.setOverflowHidden;
  3423. var isEventDraggable = t.isEventDraggable;
  3424. var isEventResizable = t.isEventResizable;
  3425. var eventEnd = t.eventEnd;
  3426. var reportEvents = t.reportEvents;
  3427. var reportEventClear = t.reportEventClear;
  3428. var eventElementHandlers = t.eventElementHandlers;
  3429. var setHeight = t.setHeight;
  3430. var setWidth = t.setWidth;
  3431. var getDaySegmentContainer = t.getDaySegmentContainer;
  3432. var getSlotJumpersTop = t.getSlotJumpersTop;
  3433. var getSlotJumpersBottom = t.getSlotJumpersBottom;
  3434. var getslotScroller = t.getslotScroller;
  3435. var getSlotContent = t.getSlotContent;
  3436. var getSlotSegmentContainer = t.getSlotSegmentContainer;
  3437. var getHoverListener = t.getHoverListener;
  3438. var getMaxMinute = t.getMaxMinute;
  3439. var getMinMinute = t.getMinMinute;
  3440. var timePosition = t.timePosition;
  3441. var colContentLeft = t.colContentLeft;
  3442. var colContentRight = t.colContentRight;
  3443. var renderDaySegs = t.renderDaySegs;
  3444. var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
  3445. var getColCnt = t.getColCnt;
  3446. var getColWidth = t.getColWidth;
  3447. var getSlotHeight = t.getSlotHeight;
  3448. var getBodyContent = t.getBodyContent;
  3449. var reportEventElement = t.reportEventElement;
  3450. var showEvents = t.showEvents;
  3451. var hideEvents = t.hideEvents;
  3452. var eventDrop = t.eventDrop;
  3453. var eventResize = t.eventResize;
  3454. var renderDayOverlay = t.renderDayOverlay;
  3455. var renderSlotSelection = t.renderSlotSelection;
  3456. var clearOverlays = t.clearOverlays;
  3457. var calendar = t.calendar;
  3458. var formatDate = calendar.formatDate;
  3459. var formatDates = calendar.formatDates;
  3460. var timeLineInterval;
  3461. /* Rendering
  3462. ----------------------------------------------------------------------------*/
  3463. // draw a horizontal line indicating the current time (#143)
  3464. function setTimeIndicator()
  3465. {
  3466. var container = getBodyContent();
  3467. var timeline = container.children('.fc-timeline');
  3468. var arrow = container.children('.fc-timeline-arrow');
  3469. if (timeline.length == 0 || arrow.length == 0) { // if timeline isn't there, add it
  3470. timeline = $('<hr>').addClass('fc-timeline').appendTo(container);
  3471. arrow = $('<div>').addClass('fc-timeline-arrow').appendTo(container);
  3472. }
  3473. var cur_time = new Date();
  3474. var daycol = $('.fc-today', t.element);
  3475. if (daycol.length > 0) {
  3476. timeline.show();
  3477. arrow.show();
  3478. }
  3479. else {
  3480. timeline.hide();
  3481. arrow.hide();
  3482. return;
  3483. }
  3484. var secs = (cur_time.getHours() * 60 * 60) + (cur_time.getMinutes() * 60) + cur_time.getSeconds();
  3485. var percents = secs / 86400; // 24 * 60 * 60 = 86400, # of seconds in a day
  3486. timeline.css('top', Math.floor(container.height() * percents - 1) + 'px');
  3487. arrow.css('top', Math.floor(container.height() * percents - 1) - 5 + 'px');
  3488. var left = daycol.position().left;
  3489. var width = daycol.width();
  3490. timeline.css({ left: left + 'px', width: width + 'px' });
  3491. }
  3492. function renderEvents(events, modifiedEventId) {
  3493. reportEvents(events);
  3494. var i, len=events.length,
  3495. dayEvents=[],
  3496. slotEvents=[];
  3497. for (i=0; i<len; i++) {
  3498. if (events[i].allDay) {
  3499. dayEvents.push(events[i]);
  3500. }else{
  3501. slotEvents.push(events[i]);
  3502. }
  3503. }
  3504. if (opt('allDaySlot')) {
  3505. renderDaySegs(compileDaySegs(dayEvents), modifiedEventId, true);
  3506. setHeight(); // no params means set to viewHeight
  3507. setWidth(); // no params means set to viewWidth
  3508. }
  3509. renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
  3510. if (opt('currentTimeIndicator')) {
  3511. window.clearInterval(timeLineInterval);
  3512. timeLineInterval = window.setInterval(setTimeIndicator, 30000);
  3513. setTimeIndicator();
  3514. }
  3515. if(t.addedView) {
  3516. t.addedView.renderEvents(events, modifiedEventId);
  3517. }
  3518. }
  3519. function clearEvents() {
  3520. reportEventClear();
  3521. getDaySegmentContainer().empty();
  3522. getSlotSegmentContainer().empty();
  3523. if(t.addedView) {
  3524. t.addedView.clearEvents();
  3525. }
  3526. }
  3527. function compileDaySegs(events) {
  3528. var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
  3529. i, levelCnt=levels.length, level,
  3530. j, seg,
  3531. segs=[];
  3532. for (i=0; i<levelCnt; i++) {
  3533. level = levels[i];
  3534. for (j=0; j<level.length; j++) {
  3535. seg = level[j];
  3536. seg.row = 0;
  3537. seg.level = i; // not needed anymore
  3538. segs.push(seg);
  3539. }
  3540. }
  3541. return segs;
  3542. }
  3543. function compileSlotSegs(events) {
  3544. var colCnt = getColCnt(),
  3545. minMinute = getMinMinute(),
  3546. maxMinute = getMaxMinute(),
  3547. d = addMinutes(cloneDate(t.visStart), minMinute),
  3548. visEventEnds = $.map(events, slotEventEnd),
  3549. i, col,
  3550. j, level,
  3551. k, seg,
  3552. segs=[];
  3553. for (i=0; i<colCnt; i++) {
  3554. col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
  3555. countForwardSegs(col);
  3556. for (j=0; j<col.length; j++) {
  3557. level = col[j];
  3558. for (k=0; k<level.length; k++) {
  3559. seg = level[k];
  3560. seg.col = i;
  3561. seg.level = j;
  3562. segs.push(seg);
  3563. }
  3564. }
  3565. addDays(d, 1, true);
  3566. }
  3567. return segs;
  3568. }
  3569. function slotEventEnd(event) {
  3570. if (event.end) {
  3571. return cloneDate(event.end);
  3572. }else{
  3573. return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
  3574. }
  3575. }
  3576. // renders events in the 'time slots' at the bottom
  3577. function renderSlotSegs(segs, modifiedEventId) {
  3578. var i, segCnt=segs.length, seg,
  3579. event,
  3580. classes,
  3581. top, bottom,
  3582. colI, levelI, forward,
  3583. leftmost,
  3584. availWidth,
  3585. outerWidth,
  3586. left,
  3587. html='',
  3588. eventElements,
  3589. eventElement,
  3590. triggerRes,
  3591. vsideCache={},
  3592. hsideCache={},
  3593. key, val,
  3594. contentElement,
  3595. height,
  3596. slotJumpersTop = getSlotJumpersTop(),
  3597. slotJumpersBottom = getSlotJumpersBottom(),
  3598. slotSegmentContainer = getSlotSegmentContainer(),
  3599. slotScroller = getslotScroller(),
  3600. rtl, dis, dit,
  3601. colCnt = getColCnt(),
  3602. colBoundaries = new Array(colCnt),
  3603. jumperReserve = 10;
  3604. if (rtl = opt('isRTL')) {
  3605. dis = -1;
  3606. dit = colCnt - 1;
  3607. }else{
  3608. dis = 1;
  3609. dit = 0;
  3610. }
  3611. // init column tops array
  3612. for(i=0;i<colCnt;i++) {
  3613. colBoundaries[i]={positions:new Array()};
  3614. }
  3615. // calculate position/dimensions, create html
  3616. for (i=0; i<segCnt; i++) {
  3617. seg = segs[i];
  3618. event = seg.event;
  3619. top = timePosition(seg.start, seg.start);
  3620. bottom = timePosition(seg.start, seg.end);
  3621. colI = seg.col;
  3622. levelI = seg.level;
  3623. forward = seg.forward || 0;
  3624. leftmost = colContentLeft(colI*dis + dit);
  3625. availWidth = colContentRight(colI*dis + dit) - leftmost;
  3626. availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
  3627. if (levelI) {
  3628. // indented and thin
  3629. outerWidth = availWidth / (levelI + forward + 1);
  3630. }else{
  3631. if (forward) {
  3632. // moderately wide, aligned left still
  3633. outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
  3634. }else{
  3635. // can be entire width, aligned left
  3636. outerWidth = availWidth;
  3637. }
  3638. }
  3639. left = leftmost + // leftmost possible
  3640. (availWidth / (levelI + forward + 1) * levelI) // indentation
  3641. * dis + (rtl ? availWidth - outerWidth : 0); // rtl
  3642. seg.top = top;
  3643. seg.left = left;
  3644. seg.outerWidth = outerWidth;
  3645. seg.outerHeight = bottom - top;
  3646. html += slotSegHtml(event, seg);
  3647. }
  3648. slotSegmentContainer[0].innerHTML = html; // faster than html()
  3649. eventElements = slotSegmentContainer.children();
  3650. // retrieve elements, run through eventRender callback, bind event handlers
  3651. for (i=0; i<segCnt; i++) {
  3652. seg = segs[i];
  3653. event = seg.event;
  3654. eventElement = $(eventElements[i]); // faster than eq()
  3655. triggerRes = trigger('eventRender', event, event, eventElement);
  3656. if (triggerRes === false) {
  3657. eventElement.remove();
  3658. }else{
  3659. if (triggerRes && triggerRes !== true) {
  3660. eventElement.remove();
  3661. eventElement = $(triggerRes)
  3662. .css({
  3663. position: 'absolute',
  3664. top: seg.top,
  3665. left: seg.left
  3666. })
  3667. .appendTo(slotSegmentContainer);
  3668. }
  3669. seg.element = eventElement;
  3670. if (event._id === modifiedEventId) {
  3671. bindSlotSeg(event, eventElement, seg);
  3672. }else{
  3673. eventElement[0]._fci = i; // for lazySegBind
  3674. }
  3675. reportEventElement(event, eventElement);
  3676. }
  3677. }
  3678. lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
  3679. // record event sides and title positions
  3680. for (i=0; i<segCnt; i++) {
  3681. seg = segs[i];
  3682. if (eventElement = seg.element) {
  3683. val = vsideCache[key = seg.key = cssKey(eventElement[0])];
  3684. seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
  3685. val = hsideCache[key];
  3686. seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
  3687. contentElement = eventElement.find('div.fc-event-content');
  3688. if (contentElement.length) {
  3689. seg.contentTop = contentElement[0].offsetTop;
  3690. }
  3691. }
  3692. }
  3693. // set all positions/dimensions at once
  3694. for (i=0; i<segCnt; i++) {
  3695. seg = segs[i];
  3696. if (eventElement = seg.element) {
  3697. eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3698. height = Math.max(t.getSlotHeight() - seg.vsides, seg.outerHeight - seg.vsides);
  3699. eventElement[0].style.height = height + 'px';
  3700. event = seg.event;
  3701. if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
  3702. // not enough room for title, put it in the time header
  3703. eventElement.find('div.fc-event-time')
  3704. .text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
  3705. //.text(formatDates(event.start, event.end, opt('timeFormat')) + ' - ' + event.title);
  3706. eventElement.find('div.fc-event-title')
  3707. .remove();
  3708. }
  3709. colBoundaries[seg.col].positions.push({top:seg.top, bottom:seg.top+height+seg.vsides});
  3710. trigger('eventAfterRender', event, event, eventElement);
  3711. }
  3712. }
  3713. // sort column boundaries on top values and set min and max values
  3714. for(i=0;i<colCnt;i++) {
  3715. var min = null;
  3716. var currentCol = colBoundaries[i];
  3717. var currentColPositions = currentCol.positions;
  3718. currentColPositions = currentColPositions.sort(function(a,b){return a.top-b.top;});
  3719. $.each(currentColPositions,function(ei,ee){
  3720. if(min==null)
  3721. min=ee.bottom;
  3722. else
  3723. min=Math.min(min,ee.bottom);
  3724. });
  3725. currentCol.min=min;
  3726. currentCol.max=currentColPositions.length?currentColPositions[currentColPositions.length-1].top:null;
  3727. }
  3728. slotScroller.unbind('scroll').scroll(function(){
  3729. var currentPosition = $(this).scrollTop();
  3730. for(i=0;i<colCnt;i++) {
  3731. var currentCol = colBoundaries[i];
  3732. if(currentCol.min!=null && currentCol.min<=currentPosition+jumperReserve)
  3733. $(slotJumpersTop[i]).css('display','');
  3734. else
  3735. $(slotJumpersTop[i]).css('display','none');
  3736. if(currentCol.max!=null && currentCol.max>=currentPosition+slotScroller.height()-jumperReserve)
  3737. $(slotJumpersBottom[i]).css('display','');
  3738. else
  3739. $(slotJumpersBottom[i]).css('display','none');
  3740. }
  3741. }).trigger('scroll');
  3742. slotJumpersTop.each(function(i, jumper){
  3743. $(jumper).unbind('click').click(function(){
  3744. var targetTop=0;
  3745. var currentPosition = slotScroller.scrollTop();
  3746. $.each(colBoundaries[i].positions,function(ei,ee){
  3747. if(ee.bottom<=currentPosition+jumperReserve)
  3748. targetTop=ee.top;
  3749. return ee.top<currentPosition;
  3750. });
  3751. slotScroller.scrollTop(targetTop-t.getSlotHeight());
  3752. });
  3753. });
  3754. slotJumpersBottom.each(function(i, jumper){
  3755. $(jumper).unbind('click').click(function(){
  3756. var targetPosition=0;
  3757. var currentPosition = slotScroller.scrollTop();
  3758. $.each(colBoundaries[i].positions,function(ei,ee){
  3759. if(ee.top>=currentPosition+slotScroller.height()-jumperReserve)
  3760. {
  3761. targetPosition = ee;
  3762. return false;
  3763. }
  3764. });
  3765. slotScroller.scrollTop(
  3766. targetPosition.bottom-targetPosition.top+t.getSlotHeight()>slotScroller.height()?
  3767. targetPosition.top-t.getSlotHeight():
  3768. targetPosition.bottom-slotScroller.height()+t.getSlotHeight()+1 // +1 is a magic independent constant, used just to make the default scroll position look better
  3769. );
  3770. });
  3771. });
  3772. for (i=0; i<segCnt; i++) {
  3773. seg = segs[i];
  3774. if(seg.event.source && seg.event.source.background) {
  3775. $('td.fc-col' + seg.col, t.element).addClass('fc-source-bg');
  3776. }
  3777. }
  3778. }
  3779. function slotSegHtml(event, seg) {
  3780. var html = "<";
  3781. var url = event.url;
  3782. var skinCss = getSkinCss(event, opt);
  3783. var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  3784. var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
  3785. if (isEventDraggable(event)) {
  3786. classes.push('fc-event-draggable');
  3787. }
  3788. if (seg.isStart) {
  3789. classes.push('fc-corner-top');
  3790. }
  3791. if (seg.isEnd) {
  3792. classes.push('fc-corner-bottom');
  3793. }
  3794. classes = classes.concat(event.className);
  3795. if (event.source) {
  3796. classes = classes.concat(event.source.className || []);
  3797. }
  3798. if (url) {
  3799. html += "a href='" + htmlEscape(event.url) + "'";
  3800. }else{
  3801. html += "div";
  3802. }
  3803. html +=
  3804. " class='" + classes.join(' ') + "'" +
  3805. " style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
  3806. ">" +
  3807. "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
  3808. "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
  3809. "<div class='fc-event-time'>" +
  3810. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3811. "</div>" +
  3812. "</div>" +
  3813. "<div class='fc-event-content'>" +
  3814. "<div class='fc-event-title'>" +
  3815. htmlEscape(event.title) +
  3816. "</div>" +
  3817. "</div>" +
  3818. "<div class='fc-event-bg'></div>" +
  3819. "</div>"; // close inner
  3820. if (seg.isEnd && isEventResizable(event)) {
  3821. html +=
  3822. "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
  3823. }
  3824. html +=
  3825. "</" + (url ? "a" : "div") + ">";
  3826. return html;
  3827. }
  3828. function bindDaySeg(event, eventElement, seg) {
  3829. if (isEventDraggable(event)) {
  3830. draggableDayEvent(event, eventElement, seg.isStart);
  3831. }
  3832. if (seg.isEnd && isEventResizable(event)) {
  3833. resizableDayEvent(event, eventElement, seg);
  3834. }
  3835. eventElementHandlers(event, eventElement);
  3836. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  3837. }
  3838. function bindSlotSeg(event, eventElement, seg) {
  3839. var timeElement = eventElement.find('div.fc-event-time');
  3840. if (isEventDraggable(event)) {
  3841. draggableSlotEvent(event, eventElement, timeElement);
  3842. }
  3843. if (seg.isEnd && isEventResizable(event)) {
  3844. resizableSlotEvent(event, eventElement, timeElement);
  3845. }
  3846. eventElementHandlers(event, eventElement);
  3847. }
  3848. /* Dragging
  3849. -----------------------------------------------------------------------------------*/
  3850. // when event starts out FULL-DAY
  3851. function draggableDayEvent(event, eventElement, isStart) {
  3852. var origWidth;
  3853. var revert;
  3854. var allDay=true;
  3855. var dayDelta;
  3856. var dis = opt('isRTL') ? -1 : 1;
  3857. var hoverListener = getHoverListener();
  3858. var colWidth = getColWidth();
  3859. var slotHeight = getSlotHeight();
  3860. var minMinute = getMinMinute();
  3861. eventElement.draggable({
  3862. zIndex: 9,
  3863. scroll: false,
  3864. opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
  3865. revertDuration: opt('dragRevertDuration'),
  3866. start: function(ev, ui) {
  3867. trigger('eventDragStart', eventElement, event, ev, ui);
  3868. //hideEvents(event, eventElement);
  3869. origWidth = eventElement.width();
  3870. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3871. clearOverlays();
  3872. if (cell) {
  3873. //setOverflowHidden(true);
  3874. revert = false;
  3875. dayDelta = colDelta * dis;
  3876. if (!cell.row) {
  3877. // on full-days
  3878. renderDayOverlay(
  3879. addDays(cloneDate(event.start), dayDelta),
  3880. addDays(exclEndDay(event), dayDelta)
  3881. );
  3882. resetElement();
  3883. }else{
  3884. // mouse is over bottom slots
  3885. if (isStart) {
  3886. if (allDay) {
  3887. // convert event to temporary slot-event
  3888. eventElement.width(colWidth - 10); // don't use entire width
  3889. setOuterHeight(
  3890. eventElement,
  3891. slotHeight * Math.round(
  3892. (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
  3893. / opt('slotMinutes')
  3894. )
  3895. );
  3896. eventElement.draggable('option', 'grid', [colWidth, 1]);
  3897. allDay = false;
  3898. }
  3899. else{
  3900. var cellDate = t.cellDate;
  3901. if (cell && (cell.col == origCell.col || !opt('selectHelper'))) {
  3902. var d1 = cellDate(cell);
  3903. var duration = event.end ? minDiff(event.end, event.start) : opt('defaultEventMinutes');
  3904. var d2 = addMinutes(cloneDate(d1, false), duration);
  3905. dates = [d1, d2].sort(cmp);
  3906. renderSlotSelection(dates[0], dates[1]);
  3907. }
  3908. }
  3909. }else{
  3910. revert = true;
  3911. }
  3912. }
  3913. revert = revert || (allDay && !dayDelta);
  3914. }else{
  3915. resetElement();
  3916. //setOverflowHidden(false);
  3917. revert = true;
  3918. }
  3919. eventElement.draggable('option', 'revert', revert);
  3920. }, ev, 'drag');
  3921. },
  3922. stop: function(ev, ui) {
  3923. hoverListener.stop();
  3924. clearOverlays();
  3925. trigger('eventDragStop', eventElement, event, ev, ui);
  3926. if (revert) {
  3927. // hasn't moved or is out of bounds (draggable has already reverted)
  3928. resetElement();
  3929. eventElement.css('filter', ''); // clear IE opacity side-effects
  3930. //showEvents(event, eventElement);
  3931. }else{
  3932. // changed!
  3933. var minuteDelta = 0;
  3934. if (!allDay) {
  3935. minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
  3936. * opt('slotMinutes')
  3937. + minMinute
  3938. - (event.start.getHours() * 60 + event.start.getMinutes());
  3939. }
  3940. eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
  3941. }
  3942. //setOverflowHidden(false);
  3943. }
  3944. });
  3945. function resetElement() {
  3946. if (!allDay) {
  3947. eventElement
  3948. .width(origWidth)
  3949. .height('')
  3950. .draggable('option', 'grid', null);
  3951. allDay = true;
  3952. }
  3953. }
  3954. }
  3955. // when event starts out IN TIMESLOTS
  3956. function draggableSlotEvent(event, eventElement, timeElement) {
  3957. var origPosition;
  3958. var allDay=false;
  3959. var dayDelta;
  3960. var minuteDelta;
  3961. var prevMinuteDelta;
  3962. var dis = opt('isRTL') ? -1 : 1;
  3963. var hoverListener = getHoverListener();
  3964. var colCnt = getColCnt();
  3965. var colWidth = getColWidth();
  3966. var slotHeight = getSlotHeight();
  3967. eventElement.draggable({
  3968. zIndex: 9,
  3969. scroll: false,
  3970. grid: [colWidth, slotHeight],
  3971. axis: colCnt==1 ? 'y' : false,
  3972. opacity: opt('dragOpacity'),
  3973. revertDuration: opt('dragRevertDuration'),
  3974. start: function(ev, ui) {
  3975. trigger('eventDragStart', eventElement, event, ev, ui);
  3976. //hideEvents(event, eventElement);
  3977. origPosition = eventElement.position();
  3978. minuteDelta = prevMinuteDelta = 0;
  3979. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3980. eventElement.draggable('option', 'revert', !cell);
  3981. clearOverlays();
  3982. if (cell) {
  3983. dayDelta = colDelta * dis;
  3984. if (opt('allDaySlot') && !cell.row) {
  3985. // over full days
  3986. if (!allDay) {
  3987. // convert to temporary all-day event
  3988. allDay = true;
  3989. timeElement.hide();
  3990. eventElement.draggable('option', 'grid', null);
  3991. }
  3992. renderDayOverlay(
  3993. addDays(cloneDate(event.start), dayDelta),
  3994. addDays(exclEndDay(event), dayDelta)
  3995. );
  3996. }else{
  3997. // on slots
  3998. resetElement();
  3999. }
  4000. }
  4001. }, ev, 'drag');
  4002. },
  4003. drag: function(ev, ui) {
  4004. ui.position.left = origPosition.left + (dayDelta * dis) * colWidth;
  4005. minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
  4006. if (minuteDelta != prevMinuteDelta) {
  4007. if (!allDay) {
  4008. updateTimeText(minuteDelta);
  4009. }
  4010. prevMinuteDelta = minuteDelta;
  4011. }
  4012. },
  4013. stop: function(ev, ui) {
  4014. var cell = hoverListener.stop();
  4015. clearOverlays();
  4016. trigger('eventDragStop', eventElement, event, ev, ui);
  4017. if (cell && (dayDelta || minuteDelta || allDay)) {
  4018. // changed!
  4019. eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
  4020. }else{
  4021. // either no change or out-of-bounds (draggable has already reverted)
  4022. resetElement();
  4023. eventElement.css('filter', ''); // clear IE opacity side-effects
  4024. eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
  4025. updateTimeText(0);
  4026. //showEvents(event, eventElement);
  4027. }
  4028. }
  4029. });
  4030. function updateTimeText(minuteDelta) {
  4031. var newStart = addMinutes(cloneDate(event.start), minuteDelta);
  4032. var newEnd;
  4033. if (event.end) {
  4034. newEnd = addMinutes(cloneDate(event.end), minuteDelta);
  4035. }
  4036. timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
  4037. }
  4038. function resetElement() {
  4039. // convert back to original slot-event
  4040. if (allDay) {
  4041. timeElement.css('display', ''); // show() was causing display=inline
  4042. eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
  4043. allDay = false;
  4044. }
  4045. }
  4046. }
  4047. /* Resizing
  4048. --------------------------------------------------------------------------------------*/
  4049. function resizableSlotEvent(event, eventElement, timeElement) {
  4050. var slotDelta, prevSlotDelta;
  4051. var slotHeight = getSlotHeight();
  4052. eventElement.resizable({
  4053. handles: {
  4054. s: 'div.ui-resizable-s'
  4055. },
  4056. grid: slotHeight,
  4057. start: function(ev, ui) {
  4058. slotDelta = prevSlotDelta = 0;
  4059. //hideEvents(event, eventElement);
  4060. eventElement.css('z-index', 9);
  4061. trigger('eventResizeStart', this, event, ev, ui);
  4062. },
  4063. resize: function(ev, ui) {
  4064. // don't rely on ui.size.height, doesn't take grid into account
  4065. slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
  4066. if (slotDelta != prevSlotDelta) {
  4067. timeElement.text(
  4068. formatDates(
  4069. event.start,
  4070. (!slotDelta && !event.end) ? null : // no change, so don't display time range
  4071. addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
  4072. opt('timeFormat')
  4073. )
  4074. );
  4075. prevSlotDelta = slotDelta;
  4076. }
  4077. },
  4078. stop: function(ev, ui) {
  4079. trigger('eventResizeStop', this, event, ev, ui);
  4080. var minutesDelta = opt('slotMinutes')*slotDelta;
  4081. if(event.end===null) {
  4082. minutesDelta+=opt('defaultEventMinutes');
  4083. }
  4084. if (slotDelta) {
  4085. eventResize(this, event, 0, minutesDelta, ev, ui);
  4086. }else{
  4087. eventElement.css('z-index', 8);
  4088. //showEvents(event, eventElement);
  4089. // BUG: if event was really short, need to put title back in span
  4090. }
  4091. }
  4092. });
  4093. }
  4094. }
  4095. function countForwardSegs(levels) {
  4096. var i, j, k, level, segForward, segBack;
  4097. for (i=levels.length-1; i>0; i--) {
  4098. level = levels[i];
  4099. for (j=0; j<level.length; j++) {
  4100. segForward = level[j];
  4101. for (k=0; k<levels[i-1].length; k++) {
  4102. segBack = levels[i-1][k];
  4103. if (segsCollide(segForward, segBack)) {
  4104. segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
  4105. }
  4106. }
  4107. }
  4108. }
  4109. }
  4110. function View(element, calendar, viewName) {
  4111. var t = this;
  4112. // exports
  4113. t.element = element;
  4114. t.calendar = calendar;
  4115. t.name = viewName;
  4116. t.opt = opt;
  4117. t.trigger = trigger;
  4118. //t.setOverflowHidden = setOverflowHidden;
  4119. t.isEventDraggable = isEventDraggable;
  4120. t.isEventResizable = isEventResizable;
  4121. t.reportEvents = reportEvents;
  4122. t.eventEnd = eventEnd;
  4123. t.reportEventElement = reportEventElement;
  4124. t.reportEventClear = reportEventClear;
  4125. t.eventElementHandlers = eventElementHandlers;
  4126. t.showEvents = showEvents;
  4127. t.hideEvents = hideEvents;
  4128. t.eventDrop = eventDrop;
  4129. t.eventResize = eventResize;
  4130. t.selectedElement = null;
  4131. t.selectEvent = selectEvent;
  4132. // t.title
  4133. // t.start, t.end
  4134. // t.visStart, t.visEnd
  4135. // imports
  4136. var defaultEventEnd = t.defaultEventEnd;
  4137. var normalizeEvent = calendar.normalizeEvent; // in EventManager
  4138. var reportEventChange = calendar.reportEventChange;
  4139. // locals
  4140. var eventsByID = {};
  4141. var eventElements = [];
  4142. var eventElementsByID = {};
  4143. var options = calendar.options;
  4144. function opt(name, viewNameOverride) {
  4145. var v = options[name];
  4146. if (typeof v == 'object' && !v.length && !$.isArray(v)) {
  4147. return smartProperty(v, viewNameOverride || viewName);
  4148. }
  4149. return v;
  4150. }
  4151. function trigger(name, thisObj) {
  4152. return calendar.trigger.apply(
  4153. calendar,
  4154. [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
  4155. );
  4156. }
  4157. function isEventDraggable(event) {
  4158. return isEventEditable(event) && !opt('disableDragging');
  4159. }
  4160. function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
  4161. return isEventEditable(event) && !opt('disableResizing');
  4162. }
  4163. function isEventEditable(event) {
  4164. return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
  4165. }
  4166. /* Event Data
  4167. ------------------------------------------------------------------------------*/
  4168. // report when view receives new events
  4169. function reportEvents(events) { // events are already normalized at this point
  4170. eventsByID = {};
  4171. var i, len=events.length, event;
  4172. for (i=0; i<len; i++) {
  4173. event = events[i];
  4174. if (eventsByID[event._id]) {
  4175. eventsByID[event._id].push(event);
  4176. }else{
  4177. eventsByID[event._id] = [event];
  4178. }
  4179. }
  4180. }
  4181. // returns a Date object for an event's end
  4182. function eventEnd(event) {
  4183. return event.end ? cloneDate(event.end) : defaultEventEnd(event);
  4184. }
  4185. /* Event Elements
  4186. ------------------------------------------------------------------------------*/
  4187. // report when view creates an element for an event
  4188. function reportEventElement(event, element) {
  4189. eventElements.push(element);
  4190. if (eventElementsByID[event._id]) {
  4191. eventElementsByID[event._id].push(element);
  4192. }else{
  4193. eventElementsByID[event._id] = [element];
  4194. }
  4195. }
  4196. function reportEventClear() {
  4197. eventElements = [];
  4198. eventElementsByID = {};
  4199. }
  4200. // attaches eventClick, eventMouseover, eventMouseout
  4201. function eventElementHandlers(event, eventElement) {
  4202. eventElement
  4203. .click(function(ev) {
  4204. if (!eventElement.hasClass('ui-draggable-dragging') &&
  4205. !eventElement.hasClass('ui-resizable-resizing')) {
  4206. selectEvent(eventElement, true);
  4207. return trigger('eventClick', this, event, ev);
  4208. }
  4209. })
  4210. .hover(
  4211. function(ev) {
  4212. trigger('eventMouseover', this, event, ev);
  4213. },
  4214. function(ev) {
  4215. trigger('eventMouseout', this, event, ev);
  4216. }
  4217. );
  4218. eventElement.find('.fc-event-checkbox').click(function(ev) {
  4219. trigger('eventCheckClicked', this, $(this), event, ev);
  4220. });
  4221. // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
  4222. // TODO: same for resizing
  4223. }
  4224. function selectEvent(eventElement, noClick) {
  4225. if(t.name!='todo' || t.eventSelectLock<0) {
  4226. return false;
  4227. }
  4228. if(typeof eventElement=='undefined' || eventElement==null || eventElement.length==0) {
  4229. eventElement=t.getDaySegmentContainer().find($('.fc-event[data-repeat-hash="'+t.selectedElement+'"]:visible'));
  4230. }
  4231. if(eventElement.length==0) {
  4232. eventElement=t.element.find('.fc-event:visible:first');
  4233. }
  4234. if(eventElement.length==0) {
  4235. trigger('selectEmpty');
  4236. return false;
  4237. }
  4238. t.selectedElement=eventElement.attr('data-repeat-hash');
  4239. t.element.find('.fc-event-selected').removeClass('fc-event-selected');
  4240. eventElement.addClass('fc-event-selected');
  4241. var offset=eventElement.position().top;
  4242. if(offset<eventElement.outerHeight() || offset>t.getDaySegmentContainer().parent().height())
  4243. {
  4244. var top=t.getDaySegmentContainer().parent().scrollTop();
  4245. t.getDaySegmentContainer().parent().scrollTop(top+offset-(t.getDaySegmentContainer().parent().height()*0.2));
  4246. }
  4247. // Force event click callback, although its not pretty
  4248. if(!noClick) {
  4249. eventElement.trigger('mouseover').trigger('click');
  4250. }
  4251. }
  4252. function showEvents(event, exceptElement) {
  4253. eachEventElement(event, exceptElement, 'show');
  4254. }
  4255. function hideEvents(event, exceptElement) {
  4256. eachEventElement(event, exceptElement, 'hide');
  4257. }
  4258. function eachEventElement(event, exceptElement, funcName) {
  4259. event[funcName]();
  4260. // var elements = eventElementsByID[event._id],
  4261. // i, len = elements.length;
  4262. // for (i=0; i<len; i++) {
  4263. // if (!exceptElement || elements[i][0] != exceptElement[0]) {
  4264. // elements[i][funcName]();
  4265. // }
  4266. // }
  4267. }
  4268. /* Event Modification Reporting
  4269. ---------------------------------------------------------------------------------*/
  4270. function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
  4271. var oldAllDay = event.allDay;
  4272. var eventId = event._id;
  4273. //moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
  4274. moveEvents([event], dayDelta, minuteDelta, allDay);
  4275. trigger(
  4276. 'eventDrop',
  4277. e,
  4278. event,
  4279. dayDelta,
  4280. minuteDelta,
  4281. allDay,
  4282. function() {
  4283. // TODO: investigate cases where this inverse technique might not work
  4284. //moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
  4285. moveEvents([event], -dayDelta, -minuteDelta, oldAllDay);
  4286. reportEventChange(eventId);
  4287. },
  4288. ev,
  4289. ui
  4290. );
  4291. reportEventChange(eventId);
  4292. }
  4293. function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
  4294. var eventId = event._id;
  4295. //elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
  4296. elongateEvents([event], dayDelta, minuteDelta);
  4297. trigger(
  4298. 'eventResize',
  4299. e,
  4300. event,
  4301. dayDelta,
  4302. minuteDelta,
  4303. function() {
  4304. // TODO: investigate cases where this inverse technique might not work
  4305. //elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
  4306. elongateEvents([event], -dayDelta, -minuteDelta);
  4307. reportEventChange(eventId);
  4308. },
  4309. ev,
  4310. ui
  4311. );
  4312. reportEventChange(eventId);
  4313. }
  4314. /* Event Modification Math
  4315. ---------------------------------------------------------------------------------*/
  4316. function moveEvents(events, dayDelta, minuteDelta, allDay) {
  4317. minuteDelta = minuteDelta || 0;
  4318. for (var e, len=events.length, i=0; i<len; i++) {
  4319. e = events[i];
  4320. if (allDay !== undefined) {
  4321. e.allDay = allDay;
  4322. }
  4323. addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
  4324. if (e.end) {
  4325. e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
  4326. }
  4327. normalizeEvent(e, options);
  4328. }
  4329. }
  4330. function elongateEvents(events, dayDelta, minuteDelta) {
  4331. minuteDelta = minuteDelta || 0;
  4332. for (var e, len=events.length, i=0; i<len; i++) {
  4333. e = events[i];
  4334. e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
  4335. normalizeEvent(e, options);
  4336. }
  4337. }
  4338. }
  4339. function DayEventRenderer() {
  4340. var t = this;
  4341. // exports
  4342. t.renderDaySegs = renderDaySegs;
  4343. t.resizableDayEvent = resizableDayEvent;
  4344. // imports
  4345. var opt = t.opt;
  4346. var trigger = t.trigger;
  4347. var isEventDraggable = t.isEventDraggable;
  4348. var isEventResizable = t.isEventResizable;
  4349. var eventEnd = t.eventEnd;
  4350. var reportEventElement = t.reportEventElement;
  4351. var showEvents = t.showEvents;
  4352. var hideEvents = t.hideEvents;
  4353. var eventResize = t.eventResize;
  4354. var getRowCnt = t.getRowCnt;
  4355. var getColCnt = t.getColCnt;
  4356. var getColWidth = t.getColWidth;
  4357. var allDayRow = t.allDayRow;
  4358. var allDayBounds = t.allDayBounds;
  4359. var colContentLeft = t.colContentLeft;
  4360. var colContentRight = t.colContentRight;
  4361. var dayOfWeekCol = t.dayOfWeekCol;
  4362. var dateCell = t.dateCell;
  4363. var compileDaySegs = t.compileDaySegs;
  4364. var getDaySegmentContainer = t.getDaySegmentContainer;
  4365. var bindDaySeg = t.bindDaySeg; //TODO: streamline this
  4366. var formatDates = t.calendar.formatDates;
  4367. var renderDayOverlay = t.renderDayOverlay;
  4368. var clearOverlays = t.clearOverlays;
  4369. var clearSelection = t.clearSelection;
  4370. /* Rendering
  4371. -----------------------------------------------------------------------------*/
  4372. function renderDaySegs(segs, modifiedEventId, isAllDay) {
  4373. var segmentContainer = getDaySegmentContainer();
  4374. var rowDivs;
  4375. var rowCnt = getRowCnt();
  4376. var colCnt = getColCnt();
  4377. var i = 0;
  4378. var rowI;
  4379. var levelI;
  4380. var colHeights;
  4381. var j;
  4382. var segCnt = segs.length;
  4383. var seg;
  4384. var top;
  4385. var k;
  4386. segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  4387. daySegElementResolve(segs, segmentContainer.children());
  4388. daySegElementReport(segs);
  4389. daySegHandlers(segs, segmentContainer, modifiedEventId);
  4390. daySegCalcHSides(segs);
  4391. daySegSetWidths(segs);
  4392. daySegCalcHeights(segs);
  4393. rowDivs = getRowDivs();
  4394. // set row heights, calculate event tops (in relation to row top)
  4395. for (rowI=0; rowI<rowCnt; rowI++) {
  4396. levelI = 0;
  4397. colHeights = [];
  4398. for (j=0; j<colCnt; j++) {
  4399. colHeights[j] = 0;
  4400. }
  4401. while (i<segCnt && (seg = segs[i]).row == rowI) {
  4402. // loop through segs in a row
  4403. top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
  4404. seg.top = top;
  4405. if (typeof seg.outerHeight != "undefined") top += seg.outerHeight;
  4406. for (k=seg.startCol; k<seg.endCol; k++) {
  4407. colHeights[k] = top;
  4408. }
  4409. i++;
  4410. }
  4411. if(isAllDay) {
  4412. segmentContainer.parent().height(arrayMax(colHeights) ? arrayMax(colHeights) + 3 : 0);
  4413. }
  4414. rowDivs[rowI].height(arrayMax(colHeights));
  4415. }
  4416. daySegSetTops(segs, getRowTops(rowDivs));
  4417. $('.fc-source-bg', t.element).removeClass('fc-source-bg');
  4418. if(!isAllDay) { // month or multiweek view
  4419. for (i=0; i<segCnt; i++) {
  4420. seg = segs[i];
  4421. if(seg.event.source && seg.event.source.background) {
  4422. for(c=seg.startCol; c<seg.endCol; c++) {
  4423. $('td.fc-day' + (seg.row*7+c), t.element).addClass('fc-source-bg');
  4424. }
  4425. }
  4426. }
  4427. }
  4428. else { // agenda views
  4429. for (i=0; i<segCnt; i++) {
  4430. seg = segs[i];
  4431. if(seg.event.source && seg.event.source.background) {
  4432. for(c=seg.startCol; c<seg.endCol; c++) {
  4433. $('td.fc-col' + c, t.element).addClass('fc-source-bg');
  4434. }
  4435. }
  4436. }
  4437. }
  4438. }
  4439. function renderTempDaySegs(segs, adjustRow, adjustTop) {
  4440. var tempContainer = $("<div/>");
  4441. var elements;
  4442. var segmentContainer = getDaySegmentContainer();
  4443. var i;
  4444. var segCnt = segs.length;
  4445. var element;
  4446. tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  4447. elements = tempContainer.children();
  4448. segmentContainer.append(elements);
  4449. daySegElementResolve(segs, elements);
  4450. daySegCalcHSides(segs);
  4451. daySegSetWidths(segs);
  4452. daySegCalcHeights(segs);
  4453. daySegSetTops(segs, getRowTops(getRowDivs()));
  4454. elements = [];
  4455. for (i=0; i<segCnt; i++) {
  4456. element = segs[i].element;
  4457. if (element) {
  4458. if (segs[i].row === adjustRow) {
  4459. element.css('top', adjustTop);
  4460. }
  4461. elements.push(element[0]);
  4462. }
  4463. }
  4464. return $(elements);
  4465. }
  4466. function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
  4467. var rtl = opt('isRTL');
  4468. var i;
  4469. var segCnt=segs.length;
  4470. var seg;
  4471. var event;
  4472. var url;
  4473. var classes;
  4474. var bounds = allDayBounds();
  4475. var minLeft = bounds.left;
  4476. var maxLeft = bounds.right;
  4477. var leftCol;
  4478. var rightCol;
  4479. var left;
  4480. var right;
  4481. var titleWidth;
  4482. var skinCss;
  4483. var html = '';
  4484. // calculate desired position/dimensions, create html
  4485. for (i=0; i<segCnt; i++) {
  4486. seg = segs[i];
  4487. event = seg.event;
  4488. classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
  4489. if (isEventDraggable(event)) {
  4490. classes.push('fc-event-draggable');
  4491. }
  4492. if (rtl) {
  4493. if (seg.isStart) {
  4494. classes.push('fc-corner-right');
  4495. }
  4496. if (seg.isEnd) {
  4497. classes.push('fc-corner-left');
  4498. }
  4499. leftCol = dayOfWeekCol(seg.end.getDay()-1);
  4500. rightCol = dayOfWeekCol(seg.start.getDay());
  4501. left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
  4502. right = seg.isStart ? colContentRight(rightCol) : maxLeft;
  4503. }else{
  4504. if (seg.isStart) {
  4505. classes.push('fc-corner-left');
  4506. }
  4507. if (seg.isEnd) {
  4508. classes.push('fc-corner-right');
  4509. }
  4510. leftCol = dayOfWeekCol(seg.start.getDay());
  4511. rightCol = dayOfWeekCol(seg.end.getDay()-1);
  4512. left = seg.isStart ? colContentLeft(leftCol) : minLeft;
  4513. right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
  4514. }
  4515. titleWidth = right - left - 2 - 2 - 2;
  4516. classes = classes.concat(event.className);
  4517. if (event.source) {
  4518. classes = classes.concat(event.source.className || []);
  4519. }
  4520. url = event.url;
  4521. skinCss = getSkinCss(event, opt);
  4522. if (url) {
  4523. html += "<a href='" + htmlEscape(url) + "'";
  4524. }else{
  4525. html += "<div";
  4526. }
  4527. html +=
  4528. " class='" + classes.join(' ') + "'" +
  4529. " style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
  4530. ">" +
  4531. "<div" +
  4532. " class='fc-event-inner fc-event-skin'" +
  4533. " style='width:" + titleWidth + "px;z-index:inherit;" +
  4534. (skinCss ? skinCss : '') +
  4535. "'" +
  4536. //(skinCss ? " style='" + skinCss + "'" : '') +
  4537. ">";
  4538. if (opt('dayEventSizeStrict')) {
  4539. html += "<div class='fc-event-title-strict'>";
  4540. }
  4541. if (!event.allDay && seg.isStart && opt('timeFormat')) {
  4542. html +=
  4543. "<span class='fc-event-time'>" +
  4544. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  4545. "</span>";
  4546. }
  4547. html += "<span class='fc-event-title'>" + htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) + "</span>";
  4548. if (opt('dayEventSizeStrict')) {
  4549. html += "</div>";
  4550. }
  4551. html += "</div>";
  4552. if (seg.isEnd && isEventResizable(event)) {
  4553. html +=
  4554. "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
  4555. "&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
  4556. "</div>";
  4557. }
  4558. html +=
  4559. "<div class='fc-event-bg'></div>" +
  4560. "</" + (url ? "a" : "div" ) + ">";
  4561. seg.left = left;
  4562. seg.outerWidth = right - left;
  4563. seg.startCol = leftCol;
  4564. seg.endCol = rightCol + 1; // needs to be exclusive
  4565. }
  4566. return html;
  4567. }
  4568. function daySegElementResolve(segs, elements) { // sets seg.element
  4569. var i;
  4570. var segCnt = segs.length;
  4571. var seg;
  4572. var event;
  4573. var element;
  4574. var triggerRes;
  4575. for (i=0; i<segCnt; i++) {
  4576. seg = segs[i];
  4577. event = seg.event;
  4578. element = $(elements[i]); // faster than .eq()
  4579. triggerRes = trigger('eventRender', event, event, element);
  4580. if (triggerRes === false) {
  4581. element.remove();
  4582. }else{
  4583. if (triggerRes && triggerRes !== true) {
  4584. triggerRes = $(triggerRes)
  4585. .css({
  4586. position: 'absolute',
  4587. left: seg.left
  4588. });
  4589. element.replaceWith(triggerRes);
  4590. element = triggerRes;
  4591. }
  4592. seg.element = element;
  4593. }
  4594. }
  4595. }
  4596. function daySegElementReport(segs) {
  4597. var i;
  4598. var segCnt = segs.length;
  4599. var seg;
  4600. var element;
  4601. for (i=0; i<segCnt; i++) {
  4602. seg = segs[i];
  4603. element = seg.element;
  4604. if (element) {
  4605. reportEventElement(seg.event, element);
  4606. }
  4607. }
  4608. }
  4609. function daySegHandlers(segs, segmentContainer, modifiedEventId) {
  4610. var i;
  4611. var segCnt = segs.length;
  4612. var seg;
  4613. var element;
  4614. var event;
  4615. // retrieve elements, run through eventRender callback, bind handlers
  4616. for (i=0; i<segCnt; i++) {
  4617. seg = segs[i];
  4618. element = seg.element;
  4619. if (element) {
  4620. event = seg.event;
  4621. if (event._id === modifiedEventId) {
  4622. bindDaySeg(event, element, seg);
  4623. }else{
  4624. element[0]._fci = i; // for lazySegBind
  4625. }
  4626. }
  4627. }
  4628. lazySegBind(segmentContainer, segs, bindDaySeg);
  4629. }
  4630. function daySegCalcHSides(segs) { // also sets seg.key
  4631. var i;
  4632. var segCnt = segs.length;
  4633. var seg;
  4634. var element;
  4635. var key, val;
  4636. var hsideCache = {};
  4637. // record event horizontal sides
  4638. for (i=0; i<segCnt; i++) {
  4639. seg = segs[i];
  4640. element = seg.element;
  4641. if (element) {
  4642. key = seg.key = cssKey(element[0]);
  4643. val = hsideCache[key];
  4644. if (val === undefined) {
  4645. val = hsideCache[key] = hsides(element, true);
  4646. }
  4647. seg.hsides = val;
  4648. }
  4649. }
  4650. }
  4651. function daySegSetWidths(segs) {
  4652. var i;
  4653. var segCnt = segs.length;
  4654. var seg;
  4655. var element;
  4656. for (i=0; i<segCnt; i++) {
  4657. seg = segs[i];
  4658. element = seg.element;
  4659. if (element) {
  4660. element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  4661. }
  4662. }
  4663. }
  4664. function daySegCalcHeights(segs) {
  4665. var i;
  4666. var segCnt = segs.length;
  4667. var seg;
  4668. var element;
  4669. var key, val;
  4670. var vmarginCache = {};
  4671. // record event heights
  4672. for (i=0; i<segCnt; i++) {
  4673. seg = segs[i];
  4674. element = seg.element;
  4675. if (element) {
  4676. key = seg.key; // created in daySegCalcHSides
  4677. val = vmarginCache[key];
  4678. if (val === undefined) {
  4679. val = vmarginCache[key] = vmargins(element);
  4680. }
  4681. seg.outerHeight = element[0].offsetHeight + val;
  4682. }
  4683. else // always set a value (issue #1108 )
  4684. seg.outerHeight = 0;
  4685. }
  4686. }
  4687. function getRowDivs() {
  4688. var i;
  4689. var rowCnt = getRowCnt();
  4690. var rowDivs = [];
  4691. for (i=0; i<rowCnt; i++) {
  4692. rowDivs[i] = allDayRow(i)
  4693. .find('td:first div.fc-day-content > div'); // optimal selector?
  4694. }
  4695. return rowDivs;
  4696. }
  4697. function getRowTops(rowDivs) {
  4698. var i;
  4699. var rowCnt = rowDivs.length;
  4700. var tops = [];
  4701. for (i=0; i<rowCnt; i++) {
  4702. tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
  4703. }
  4704. return tops;
  4705. }
  4706. function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
  4707. var i;
  4708. var segCnt = segs.length;
  4709. var seg;
  4710. var element;
  4711. var event;
  4712. for (i=0; i<segCnt; i++) {
  4713. seg = segs[i];
  4714. element = seg.element;
  4715. if (element) {
  4716. element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
  4717. event = seg.event;
  4718. trigger('eventAfterRender', event, event, element);
  4719. }
  4720. }
  4721. }
  4722. /* Resizing
  4723. -----------------------------------------------------------------------------------*/
  4724. function resizableDayEvent(event, element, seg) {
  4725. var rtl = opt('isRTL');
  4726. var direction = rtl ? 'w' : 'e';
  4727. var handle = element.find('div.ui-resizable-' + direction);
  4728. var isResizing = false;
  4729. // TODO: look into using jquery-ui mouse widget for this stuff
  4730. disableTextSelection(element); // prevent native <a> selection for IE
  4731. element
  4732. .mousedown(function(ev) { // prevent native <a> selection for others
  4733. ev.preventDefault();
  4734. })
  4735. .click(function(ev) {
  4736. if (isResizing) {
  4737. ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
  4738. ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
  4739. // (eventElementHandlers needs to be bound after resizableDayEvent)
  4740. }
  4741. });
  4742. handle.mousedown(function(ev) {
  4743. if (ev.which != 1) {
  4744. return; // needs to be left mouse button
  4745. }
  4746. isResizing = true;
  4747. var hoverListener = t.getHoverListener();
  4748. var rowCnt = getRowCnt();
  4749. var colCnt = getColCnt();
  4750. var dis = rtl ? -1 : 1;
  4751. var dit = rtl ? colCnt-1 : 0;
  4752. var elementTop = element.css('top');
  4753. var dayDelta;
  4754. var helpers;
  4755. var eventCopy = $.extend({}, event);
  4756. var minCell = dateCell(event.start);
  4757. clearSelection();
  4758. $('body')
  4759. .css('cursor', direction + '-resize')
  4760. .one('mouseup', mouseup);
  4761. trigger('eventResizeStart', this, event, ev);
  4762. hoverListener.start(function(cell, origCell) {
  4763. if (cell) {
  4764. var r = Math.max(minCell.row, cell.row);
  4765. var c = cell.col;
  4766. if (rowCnt == 1) {
  4767. r = 0; // hack for all-day area in agenda views
  4768. }
  4769. if (r == minCell.row) {
  4770. if (rtl) {
  4771. c = Math.min(minCell.col, c);
  4772. }else{
  4773. c = Math.max(minCell.col, c);
  4774. }
  4775. }
  4776. dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
  4777. var newEnd = addDays(eventEnd(event), dayDelta, true);
  4778. if (dayDelta) {
  4779. eventCopy.end = newEnd;
  4780. var oldHelpers = helpers;
  4781. helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
  4782. helpers.find('*').css('cursor', direction + '-resize');
  4783. trigger('eventResizeHelperCreated', this, event, ev, element, helpers);
  4784. if (oldHelpers) {
  4785. oldHelpers.remove();
  4786. }
  4787. //hideEvents(event);
  4788. hideEvents(element);
  4789. }else{
  4790. if (helpers) {
  4791. //showEvents(event);
  4792. showEvents(element);
  4793. helpers.remove();
  4794. helpers = null;
  4795. }
  4796. }
  4797. clearOverlays();
  4798. renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
  4799. }
  4800. }, ev);
  4801. function mouseup(ev) {
  4802. trigger('eventResizeStop', this, event, ev);
  4803. $('body').css('cursor', '');
  4804. hoverListener.stop();
  4805. clearOverlays();
  4806. if (dayDelta) {
  4807. eventResize(this, event, dayDelta, 0, ev);
  4808. // event redraw will clear helpers
  4809. }
  4810. // otherwise, the drag handler already restored the old events
  4811. setTimeout(function() { // make this happen after the element's click event
  4812. isResizing = false;
  4813. },0);
  4814. }
  4815. });
  4816. }
  4817. }
  4818. //BUG: unselect needs to be triggered when events are dragged+dropped
  4819. function SelectionManager() {
  4820. var t = this;
  4821. // exports
  4822. t.select = select;
  4823. t.unselect = unselect;
  4824. t.reportSelection = reportSelection;
  4825. t.daySelectionMousedown = daySelectionMousedown;
  4826. // imports
  4827. var opt = t.opt;
  4828. var trigger = t.trigger;
  4829. var defaultSelectionEnd = t.defaultSelectionEnd;
  4830. var renderSelection = t.renderSelection;
  4831. var clearSelection = t.clearSelection;
  4832. // locals
  4833. var selected = false;
  4834. // unselectAuto
  4835. if (opt('selectable') && opt('unselectAuto')) {
  4836. $(document).mousedown(function(ev) {
  4837. var ignore = opt('unselectCancel');
  4838. if (ignore) {
  4839. if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
  4840. return;
  4841. }
  4842. }
  4843. unselect(ev);
  4844. });
  4845. }
  4846. function select(startDate, endDate, allDay) {
  4847. unselect();
  4848. if (!endDate) {
  4849. endDate = defaultSelectionEnd(startDate, allDay);
  4850. }
  4851. renderSelection(startDate, endDate, allDay);
  4852. reportSelection(startDate, endDate, allDay);
  4853. }
  4854. function unselect(ev) {
  4855. if (selected) {
  4856. selected = false;
  4857. clearSelection();
  4858. trigger('unselect', null, ev);
  4859. }
  4860. }
  4861. function reportSelection(startDate, endDate, allDay, ev) {
  4862. selected = true;
  4863. trigger('select', null, startDate, endDate, allDay, ev);
  4864. }
  4865. function daySelectionMousedown(ev) { // not really a generic manager method, oh well
  4866. var cellDate = t.cellDate;
  4867. var cellIsAllDay = t.cellIsAllDay;
  4868. var hoverListener = t.getHoverListener();
  4869. var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
  4870. if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
  4871. unselect(ev);
  4872. var _mousedownElement = this;
  4873. var dates;
  4874. hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
  4875. clearSelection();
  4876. if (cell && cellIsAllDay(cell)) {
  4877. dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
  4878. renderSelection(dates[0], dates[1], true);
  4879. }else{
  4880. dates = null;
  4881. }
  4882. }, ev);
  4883. $(document).one('mouseup', function(ev) {
  4884. hoverListener.stop();
  4885. if (dates) {
  4886. if (+dates[0] == +dates[1]) {
  4887. //reportDayClick(dates[0], true, ev);
  4888. }
  4889. reportSelection(dates[0], dates[1], true, ev);
  4890. }
  4891. });
  4892. }
  4893. }
  4894. }
  4895. function OverlayManager() {
  4896. var t = this;
  4897. // exports
  4898. t.renderOverlay = renderOverlay;
  4899. t.clearOverlays = clearOverlays;
  4900. // locals
  4901. var usedOverlays = [];
  4902. var unusedOverlays = [];
  4903. function renderOverlay(rect, parent) {
  4904. var e = unusedOverlays.shift();
  4905. if (!e) {
  4906. e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
  4907. }
  4908. if (e[0].parentNode != parent[0]) {
  4909. e.appendTo(parent);
  4910. }
  4911. usedOverlays.push(e.css(rect).show());
  4912. return e;
  4913. }
  4914. function clearOverlays() {
  4915. var e;
  4916. while (e = usedOverlays.shift()) {
  4917. unusedOverlays.push(e.hide().unbind());
  4918. }
  4919. }
  4920. }
  4921. function CoordinateGrid(buildFunc) {
  4922. var t = this;
  4923. var rows;
  4924. var cols;
  4925. t.build = function() {
  4926. rows = [];
  4927. cols = [];
  4928. buildFunc(rows, cols);
  4929. };
  4930. t.cell = function(x, y) {
  4931. var rowCnt = rows.length;
  4932. var colCnt = cols.length;
  4933. var i, r=-1, c=-1;
  4934. for (i=0; i<rowCnt; i++) {
  4935. if (y >= rows[i][0] && y < rows[i][1]) {
  4936. r = i;
  4937. break;
  4938. }
  4939. }
  4940. for (i=0; i<colCnt; i++) {
  4941. if (x >= cols[i][0] && x < cols[i][1]) {
  4942. c = i;
  4943. break;
  4944. }
  4945. }
  4946. return (r>=0 && c>=0) ? { row:r, col:c } : null;
  4947. };
  4948. t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
  4949. var origin = originElement.offset();
  4950. return {
  4951. top: rows[row0][0] - origin.top,
  4952. left: cols[col0][0] - origin.left,
  4953. width: cols[col1][1] - cols[col0][0],
  4954. height: rows[row1][1] - rows[row0][0]
  4955. };
  4956. };
  4957. }
  4958. function HoverListener(coordinateGrid) {
  4959. var t = this;
  4960. var bindType;
  4961. var change;
  4962. var firstCell;
  4963. var cell;
  4964. var origEvent;
  4965. t.start = function(_change, ev, _bindType) {
  4966. origEvent = ev;
  4967. change = _change;
  4968. firstCell = cell = null;
  4969. coordinateGrid.build();
  4970. mouse(ev);
  4971. bindType = _bindType || 'mousemove';
  4972. $(document).bind(bindType, mouse);
  4973. };
  4974. function mouse(ev) {
  4975. _fixUIEvent(ev); // see below
  4976. if(origEvent.pageX - ev.pageX == 0 && origEvent.pageY - ev.pageY == 0) {
  4977. return false;
  4978. }
  4979. var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
  4980. if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
  4981. if (newCell) {
  4982. if (!firstCell) {
  4983. firstCell = newCell;
  4984. }
  4985. change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
  4986. }else{
  4987. change(newCell, firstCell);
  4988. }
  4989. cell = newCell;
  4990. }
  4991. }
  4992. t.stop = function() {
  4993. $(document).unbind(bindType, mouse);
  4994. return cell;
  4995. };
  4996. }
  4997. // this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
  4998. // upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
  4999. // but keep this in here for 1.8.16 users
  5000. // and maybe remove it down the line
  5001. function _fixUIEvent(event) { // for issue 1168
  5002. if (event.pageX === undefined) {
  5003. event.pageX = event.originalEvent.pageX;
  5004. event.pageY = event.originalEvent.pageY;
  5005. }
  5006. }
  5007. function HorizontalPositionCache(getElement) {
  5008. var t = this,
  5009. elements = {},
  5010. lefts = {},
  5011. rights = {};
  5012. function e(i) {
  5013. return elements[i] = elements[i] || getElement(i);
  5014. }
  5015. t.left = function(i) {
  5016. return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
  5017. };
  5018. t.right = function(i) {
  5019. return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
  5020. };
  5021. t.clear = function() {
  5022. elements = {};
  5023. lefts = {};
  5024. rights = {};
  5025. };
  5026. }
  5027. function addTodayText(cell, todayText)
  5028. {
  5029. target = cell.find(".fc-day-text");
  5030. target.html(todayText);
  5031. }
  5032. function removeTodayText(cell, todayText)
  5033. {
  5034. target = cell.find(".fc-day-text");
  5035. target.html('');
  5036. }
  5037. function addWeekNumber(cell, date)
  5038. {
  5039. target = cell.find(".fc-week-number");
  5040. target.html(getWeekNumber(date));
  5041. }
  5042. function removeWeekNumber(cell, date)
  5043. {
  5044. target = cell.find(".fc-week-number");
  5045. target.html('');
  5046. }
  5047. function addTodayClass(cell)
  5048. {
  5049. var classes = cell.attr('class').split(' ');
  5050. var filter = ['fc-state-highlight', 'fc-today', 'fc-widget-content', 'fc-source-bg'];
  5051. classes = $.grep(classes, function(el) {
  5052. if ($.inArray(el, filter) > -1) {
  5053. return false;
  5054. }
  5055. return true;
  5056. });
  5057. classes.push('fc-widget-header');
  5058. var target = $('.' + classes.join('.'));
  5059. target.addClass('fc-today');
  5060. }
  5061. function removeTodayClass(cell)
  5062. {
  5063. var classes = cell.attr('class').split(' ');
  5064. var filter = ['fc-state-highlight', 'fc-today', 'fc-widget-content', 'fc-source-bg'];
  5065. classes = $.grep(classes, function(el) {
  5066. if ($.inArray(el, filter) > -1) {
  5067. return false;
  5068. }
  5069. return true;
  5070. });
  5071. classes.push('fc-widget-header');
  5072. var target = $('.' + classes.join('.'));
  5073. target.removeClass('fc-today');
  5074. }
  5075. function getWeekNumber(date) {
  5076. //By tanguy.pruvot at gmail.com (2010)
  5077. //first week of year always contains 4th Jan, or 28 Dec (ISO)
  5078. var jan4 = new Date(date.getFullYear(),0,4 ,date.getHours());
  5079. //ISO weeks numbers begins on monday, so rotate monday:sunday to 0:6
  5080. var jan4Day = (jan4.getDay() - 1 + 7) % 7;
  5081. var days = Math.round((date - jan4) / 86400000);
  5082. var week = Math.floor((days + jan4Day ) / 7)+1;
  5083. //special cases
  5084. var thisDay = (date.getDay() - 1 + 7) % 7;
  5085. if (date.getMonth()==11 && date.getDate() >= 28) {
  5086. jan4 = new Date(date.getFullYear()+1,0,4 ,date.getHours());
  5087. jan4Day = (jan4.getDay() - 1 + 7) % 7;
  5088. if (thisDay < jan4Day) return 1;
  5089. var prevWeek = new Date(date.valueOf()-(86400000*7));
  5090. return getWeekNumber(prevWeek) + 1;
  5091. }
  5092. if (week == 0 && thisDay > 3 && date.getMonth()==0) {
  5093. var prevWeek = new Date(date.valueOf()-(86400000*7));
  5094. return getWeekNumber(prevWeek) + 1;
  5095. }
  5096. return week;
  5097. }
  5098. /* Additional view: list (by bruederli@kolabsys.com)
  5099. ---------------------------------------------------------------------------------*/
  5100. function ListEventRenderer() {
  5101. var t = this;
  5102. // exports
  5103. t.renderEvents = renderEvents;
  5104. t.renderEventTime = renderEventTime;
  5105. t.compileDaySegs = compileSegs; // for DayEventRenderer
  5106. t.clearEvents = clearEvents;
  5107. t.lazySegBind = lazySegBind;
  5108. t.sortCmp = sortCmp;
  5109. // imports
  5110. DayEventRenderer.call(t);
  5111. var opt = t.opt;
  5112. var trigger = t.trigger;
  5113. var reportEvents = t.reportEvents;
  5114. var reportEventClear = t.reportEventClear;
  5115. var reportEventElement = t.reportEventElement;
  5116. var eventElementHandlers = t.eventElementHandlers;
  5117. var showEvents = t.showEvents;
  5118. var hideEvents = t.hideEvents;
  5119. var getListContainer = t.getDaySegmentContainer;
  5120. var calendar = t.calendar;
  5121. var formatDate = calendar.formatDate;
  5122. var formatDates = calendar.formatDates;
  5123. /* Rendering
  5124. --------------------------------------------------------------------*/
  5125. function clearEvents() {
  5126. reportEventClear();
  5127. getListContainer().empty();
  5128. }
  5129. function renderEvents(events, modifiedEventId) {
  5130. events.sort(sortCmp);
  5131. reportEvents(events);
  5132. renderSegs(compileSegs(events), modifiedEventId);
  5133. }
  5134. /*function compileSegs(events) {
  5135. var segs = [];
  5136. var colFormat = opt('titleFormat', 'day');
  5137. var firstDay = opt('firstDay');
  5138. var segmode = opt('listSections');
  5139. var event, i, dd, wd, md, seg, segHash, curSegHash, segDate, curSeg = -1;
  5140. var today = clearTime(new Date());
  5141. var weekstart = addDays(cloneDate(today), -((today.getDay() - firstDay + 7) % 7));
  5142. for (i=0; i < events.length; i++) {
  5143. event = events[i];
  5144. var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
  5145. // skip events out of range
  5146. if (eventEnd < t.start || event.start > t.visEnd)
  5147. continue;
  5148. // define sections of this event
  5149. // create smart sections such as today, tomorrow, this week, next week, next month, ect.
  5150. segDate = cloneDate(event.start < t.start && eventEnd > t.start ? t.start : event.start, true);
  5151. dd = dayDiff(segDate, today);
  5152. wd = Math.floor(dayDiff(segDate, weekstart) / 7);
  5153. md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth();
  5154. // build section title
  5155. if (segmode == 'smart') {
  5156. if (dd < 0) {
  5157. segHash = opt('listTexts', 'past');
  5158. } else if (dd == 0) {
  5159. segHash = opt('listTexts', 'today');
  5160. } else if (dd == 1) {
  5161. segHash = opt('listTexts', 'tomorrow');
  5162. } else if (wd == 0) {
  5163. segHash = opt('listTexts', 'thisWeek');
  5164. } else if (wd == 1) {
  5165. segHash = opt('listTexts', 'nextWeek');
  5166. } else if (md == 0) {
  5167. segHash = opt('listTexts', 'thisMonth');
  5168. } else if (md == 1) {
  5169. segHash = opt('listTexts', 'nextMonth');
  5170. } else if (md > 1) {
  5171. segHash = opt('listTexts', 'future');
  5172. }
  5173. } else if (segmode == 'month') {
  5174. segHash = formatDate(segDate, 'MMMM yyyy');
  5175. } else if (segmode == 'week') {
  5176. segHash = opt('listTexts', 'week') + formatDate(segDate, ' W');
  5177. } else if (segmode == 'day') {
  5178. segHash = formatDate(segDate, colFormat);
  5179. } else {
  5180. segHash = '';
  5181. }
  5182. // start new segment
  5183. if (segHash != curSegHash) {
  5184. segs[++curSeg] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md };
  5185. curSegHash = segHash;
  5186. }
  5187. segs[curSeg].events.push(event);
  5188. }
  5189. return segs;
  5190. }*/
  5191. function compileSegs(events) {
  5192. var segs = {};
  5193. var colFormat = opt('columnFormat', t.name);
  5194. var firstDay = opt('firstDay');
  5195. var segmode = opt('listSections');
  5196. var event, i, j, dd, wd, md, seg, segHash, segDate;
  5197. var today = clearTime(new Date());
  5198. var weekstart = addDays(cloneDate(today), -((today.getDay() - firstDay + 7) % 7));
  5199. for (i=0; i < events.length; i++) {
  5200. event = events[i];
  5201. var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
  5202. // skip events out of range
  5203. if (eventEnd < t.start || event.start > t.visEnd)
  5204. continue;
  5205. var boundEventStart = cloneDate(event.start < t.start ? t.start : event.start, true);
  5206. var boundEventEnd = cloneDate(eventEnd > t.visEnd ? t.visEnd : eventEnd, true);
  5207. var dayDuration = dayDiff(boundEventEnd, boundEventStart);
  5208. for(j = 0; j <= dayDuration; j++) {
  5209. segDate = cloneDate(boundEventStart);
  5210. segDate.setDate(segDate.getDate() + j);
  5211. // define sections of this event
  5212. // create smart sections such as today, tomorrow, this week, next week, next month, ect.
  5213. //segDate = cloneDate(event.start < t.start && eventEnd > t.start ? t.start : event.start, true);
  5214. dd = dayDiff(segDate, today);
  5215. wd = Math.floor(dayDiff(segDate, weekstart) / 7);
  5216. md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth();
  5217. // build section title
  5218. if (segmode == 'smart') {
  5219. if (dd < 0) {
  5220. segHash = opt('listTexts', 'past');
  5221. } else if (dd == 0) {
  5222. segHash = opt('listTexts', 'today');
  5223. } else if (dd == 1) {
  5224. segHash = opt('listTexts', 'tomorrow');
  5225. } else if (wd == 0) {
  5226. segHash = opt('listTexts', 'thisWeek');
  5227. } else if (wd == 1) {
  5228. segHash = opt('listTexts', 'nextWeek');
  5229. } else if (md == 0) {
  5230. segHash = opt('listTexts', 'thisMonth');
  5231. } else if (md == 1) {
  5232. segHash = opt('listTexts', 'nextMonth');
  5233. } else if (md > 1) {
  5234. segHash = opt('listTexts', 'future');
  5235. }
  5236. } else if (segmode == 'month') {
  5237. segHash = formatDate(segDate, 'MMMM yyyy');
  5238. } else if (segmode == 'week') {
  5239. segHash = opt('listTexts', 'week') + formatDate(segDate, ' W');
  5240. } else if (segmode == 'day') {
  5241. segHash = formatDate(segDate, colFormat);
  5242. } else {
  5243. segHash = '';
  5244. }
  5245. // start new segment
  5246. if (!(segHash in segs)) {
  5247. segs[segHash] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md };
  5248. }
  5249. segs[segHash].events.push(event);
  5250. }
  5251. }
  5252. return segs;
  5253. }
  5254. function sortCmp(a, b) {
  5255. /*var datediff = 0;
  5256. if(a.start != null && b.start != null) {
  5257. datediff = a.start.getTime() - b.start.getTime();
  5258. }
  5259. if(datediff == 0 && a.end != null && b.end != null) {
  5260. datediff = a.end.getTime() - b.end.getTime();
  5261. }
  5262. return datediff;*/
  5263. var retVal = a.start.getTime() - b.start.getTime();
  5264. if(retVal == 0) {
  5265. var aEnd = a.end ? a.end : a.start;
  5266. var bEnd = b.end ? b.end : b.start;
  5267. retVal = aEnd.getTime() - bEnd.getTime();
  5268. }
  5269. if(retVal == 0) {
  5270. if(a.compareString < b.compareString) {
  5271. retVal = -1;
  5272. }
  5273. else if(b.compareString < a.compareString) {
  5274. retVal = 1;
  5275. }
  5276. }
  5277. return retVal;
  5278. }
  5279. function renderSegs(segs, modifiedEventId) {
  5280. var tm = opt('theme') ? 'ui' : 'fc';
  5281. var headerClass = tm + "-widget-header";
  5282. var contentClass = tm + "-widget-content";
  5283. var i, j, seg, event, times, s, skinCss, skinCssAttr, classes, segContainer, eventElement, eventElements, triggerRes;
  5284. for (j=0; j < segs.length; j++) {
  5285. seg = segs[j];
  5286. if (seg.title) {
  5287. $('<div class="fc-list-header ' + headerClass + '">' + htmlEscape(seg.title) + '</div>').appendTo(getListContainer());
  5288. }
  5289. segContainer = $('<div>').addClass('fc-list-section ' + contentClass).appendTo(getListContainer());
  5290. s = '';
  5291. for (i=0; i < seg.events.length; i++) {
  5292. event = seg.events[i];
  5293. times = renderEventTime(event, seg);
  5294. skinCss = getSkinCss(event, opt);
  5295. skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  5296. classes = ['fc-event', 'fc-event-skin', 'fc-event-vert', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
  5297. if (event.source && event.source.className) {
  5298. classes = classes.concat(event.source.className);
  5299. }
  5300. s +=
  5301. "<div class='" + classes.join(' ') + "'" + skinCssAttr + ">" +
  5302. "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
  5303. "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
  5304. "<div class='fc-event-time'>" +
  5305. (times[0] ? '<span class="fc-col-date">' + times[0] + '</span> ' : '') +
  5306. (times[1] ? '<span class="fc-col-time">' + times[1] + '</span>' : '') +
  5307. "</div>" +
  5308. "</div>" +
  5309. "<div class='fc-event-content'>" +
  5310. "<div class='fc-event-title'>" +
  5311. htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) +
  5312. "</div>" +
  5313. "</div>" +
  5314. "<div class='fc-event-bg'></div>" +
  5315. "</div>" + // close inner
  5316. "</div>"; // close outer
  5317. }
  5318. segContainer[0].innerHTML = s;
  5319. eventElements = segContainer.children();
  5320. // retrieve elements, run through eventRender callback, bind event handlers
  5321. for (i=0; i < seg.events.length; i++) {
  5322. event = seg.events[i];
  5323. eventElement = $(eventElements[i]); // faster than eq()
  5324. triggerRes = trigger('eventRender', event, event, eventElement);
  5325. if (triggerRes === false) {
  5326. eventElement.remove();
  5327. } else {
  5328. if (triggerRes && triggerRes !== true) {
  5329. eventElement.remove();
  5330. eventElement = $(triggerRes).appendTo(segContainer);
  5331. }
  5332. if (event._id === modifiedEventId) {
  5333. eventElementHandlers(event, eventElement, seg);
  5334. } else {
  5335. eventElement[0]._fci = i; // for lazySegBind
  5336. }
  5337. reportEventElement(event, eventElement);
  5338. }
  5339. }
  5340. lazySegBind(segContainer, seg, eventElementHandlers);
  5341. }
  5342. markFirstLast(getListContainer());
  5343. }
  5344. // event time/date range to display
  5345. function renderEventTime(event, seg) {
  5346. var timeFormat = opt('timeFormat', 'list');
  5347. var timeFormatFull = opt('timeFormat', 'listFull');
  5348. var timeFormatFullAllDay = opt('timeFormat', 'listFullAllDay');
  5349. var dateFormat = opt('columnFormat');
  5350. var segmode = opt('listSections');
  5351. var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
  5352. var duration = eventEnd.getTime() - event.start.getTime();
  5353. var datestr = '', timestr = '';
  5354. if (segmode == 'smart') {
  5355. if (event.start < seg.start) {
  5356. datestr = opt('listTexts', 'until') + ' ' + formatDate(eventEnd, (event.allDay || eventEnd.getDate() != seg.start.getDate()) ? dateFormat : timeFormat);
  5357. } else if (duration > DAY_MS) {
  5358. datestr = formatDates(event.start, eventEnd, dateFormat + '{ – ' + dateFormat + '}');
  5359. } else if (seg.daydiff == 0) {
  5360. datestr = opt('listTexts', 'today');
  5361. } else if (seg.daydiff == 1) {
  5362. datestr = opt('listTexts', 'tomorrow');
  5363. } else if (seg.weekdiff == 0 || seg.weekdiff == 1) {
  5364. datestr = formatDate(event.start, 'dddd');
  5365. } else if (seg.daydiff > 1 || seg.daydiff < 0) {
  5366. datestr = formatDate(event.start, dateFormat);
  5367. }
  5368. } else if (segmode != 'day') {
  5369. datestr = formatDates(event.start, eventEnd, dateFormat + (duration > DAY_MS ? '{ – ' + dateFormat + '}' : ''));
  5370. }
  5371. if (!datestr && event.allDay) {
  5372. if(dayDiff(eventEnd, event.start)) { //spans multiple days
  5373. timestr = formatDates(event.start, eventEnd, timeFormatFullAllDay);
  5374. }
  5375. else {
  5376. timestr = opt('allDayText');
  5377. }
  5378. } else if ((!datestr || !dayDiff(eventEnd, event.start)) && !event.allDay) {
  5379. if(dayDiff(eventEnd, event.start)) //spans multiple days
  5380. timestr = formatDates(event.start, eventEnd, timeFormatFull);
  5381. else if(duration)
  5382. timestr = formatDates(event.start, eventEnd, timeFormat);
  5383. else
  5384. timestr = formatDates(event.start, null, timeFormat);
  5385. }
  5386. return [datestr, timestr];
  5387. }
  5388. function lazySegBind(container, seg, bindHandlers) {
  5389. container.unbind('mouseover').mouseover(function(ev) {
  5390. var parent = ev.target, e = parent, i, event;
  5391. while (parent != this) {
  5392. e = parent;
  5393. parent = parent.parentNode;
  5394. }
  5395. if ((i = e._fci) !== undefined) {
  5396. e._fci = undefined;
  5397. event = seg.events[i];
  5398. bindHandlers(event, container.children().eq(i), seg);
  5399. $(ev.target).trigger(ev);
  5400. }
  5401. ev.stopPropagation();
  5402. });
  5403. }
  5404. }
  5405. fcViews.list = ListView;
  5406. function ListView(element, calendar) {
  5407. var t = this;
  5408. // exports
  5409. t.render = render;
  5410. t.select = dummy;
  5411. t.unselect = dummy;
  5412. t.getDaySegmentContainer = function(){ return body; };
  5413. // imports
  5414. View.call(t, element, calendar, 'list');
  5415. ListEventRenderer.call(t);
  5416. var opt = t.opt;
  5417. var trigger = t.trigger;
  5418. var clearEvents = t.clearEvents;
  5419. var reportEventClear = t.reportEventClear;
  5420. var formatDates = calendar.formatDates;
  5421. var formatDate = calendar.formatDate;
  5422. // overrides
  5423. t.setWidth = setWidth;
  5424. t.setHeight = setHeight;
  5425. // locals
  5426. var body;
  5427. var firstDay;
  5428. var nwe;
  5429. var tm;
  5430. var colFormat;
  5431. function render(date, delta) {
  5432. if (delta) {
  5433. addDays(date, opt('listPage') * delta);
  5434. }
  5435. t.start = t.visStart = cloneDate(date, true);
  5436. t.end = addDays(cloneDate(t.start), opt('listPage'));
  5437. t.visEnd = addDays(cloneDate(t.start), opt('listRange'));
  5438. addMinutes(t.visEnd, -1); // set end to 23:59
  5439. t.title = formatDates(date, t.visEnd, opt('titleFormat'));
  5440. updateOptions();
  5441. if (!body) {
  5442. buildSkeleton();
  5443. } else {
  5444. clearEvents();
  5445. }
  5446. }
  5447. function updateOptions() {
  5448. firstDay = opt('firstDay');
  5449. nwe = opt('weekends') ? 0 : 1;
  5450. tm = opt('theme') ? 'ui' : 'fc';
  5451. colFormat = opt('columnFormat', 'day');
  5452. }
  5453. function buildSkeleton() {
  5454. body = $('<div>').addClass('fc-list-content').appendTo(element);
  5455. }
  5456. function setHeight(height, dateChanged) {
  5457. body.css('height', (height-1)+'px').css('overflow', 'auto');
  5458. }
  5459. function setWidth(width) {
  5460. // nothing to be done here
  5461. }
  5462. function dummy() {
  5463. // Stub.
  5464. }
  5465. }
  5466. /* Additional view: table (by bruederli@kolabsys.com)
  5467. ---------------------------------------------------------------------------------*/
  5468. function TableEventRenderer() {
  5469. var t = this;
  5470. // imports
  5471. ListEventRenderer.call(t);
  5472. var opt = t.opt;
  5473. var sortCmp = t.sortCmp;
  5474. var trigger = t.trigger;
  5475. var getOrigDate = t.getOrigDate;
  5476. var compileSegs = t.compileDaySegs;
  5477. var reportEvents = t.reportEvents;
  5478. var reportEventClear = t.reportEventClear;
  5479. var reportEventElement = t.reportEventElement;
  5480. var eventElementHandlers = t.eventElementHandlers;
  5481. var renderEventTime = t.renderEventTime;
  5482. var showEvents = t.showEvents;
  5483. var hideEvents = t.hideEvents;
  5484. var getListContainer = t.getDaySegmentContainer;
  5485. var lazySegBind = t.lazySegBind;
  5486. var calendar = t.calendar;
  5487. var formatDate = calendar.formatDate;
  5488. var formatDates = calendar.formatDates;
  5489. var prevMonth;
  5490. var nextMonth;
  5491. // exports
  5492. t.renderEvents = renderEvents;
  5493. t.scrollToDate = scrollToDate;
  5494. t.clearEvents = clearEvents;
  5495. t.prevMonthNav = prevMonth;
  5496. t.nextMonthNav = nextMonth;
  5497. /* Rendering
  5498. --------------------------------------------------------------------*/
  5499. function scrollToDate(date) {
  5500. var colFormat = opt('columnFormat', t.name);
  5501. var currentDate = cloneDate(date, false);
  5502. var nextDate;
  5503. var segHash;
  5504. var currSegHash;
  5505. var segFound = false;
  5506. if(currentDate.getDate() == 1) {
  5507. getListContainer().parent().scrollTop(0);
  5508. }
  5509. else {
  5510. while(!segFound) {
  5511. segHash = formatDate(currentDate, colFormat);
  5512. getListContainer().find('td.fc-list-header.fc-widget-header').each(function(){
  5513. currSegHash = $(this).html();
  5514. if(currSegHash == segHash) {
  5515. segFound = true;
  5516. var offset = $(this).position().top;
  5517. var top = getListContainer().parent().scrollTop();
  5518. getListContainer().parent().scrollTop(top + offset);
  5519. }
  5520. });
  5521. if(!segFound) {
  5522. nextDate = cloneDate(currentDate, false);
  5523. nextDate.setDate(nextDate.getDate()+1);
  5524. if(nextDate.getDate() > currentDate.getDate()) {
  5525. currentDate = cloneDate(nextDate, false);
  5526. }
  5527. else {
  5528. segFound = true;
  5529. getListContainer().parent().scrollTop(getListContainer().height());
  5530. }
  5531. }
  5532. }
  5533. }
  5534. }
  5535. function clearEvents() {
  5536. reportEventClear();
  5537. getListContainer().children('tbody').remove();
  5538. }
  5539. function renderEvents(events, modifiedEventId) {
  5540. events.sort(sortCmp);
  5541. reportEvents(events);
  5542. renderSegs(compileSegs(events), modifiedEventId);
  5543. getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections'));
  5544. scrollToDate(getOrigDate());
  5545. }
  5546. function renderSegs(segs, modifiedEventId) {
  5547. var tm = opt('theme') ? 'ui' : 'fc';
  5548. var table = getListContainer();
  5549. var headerClass = tm + "-widget-header";
  5550. var contentClass = tm + "-widget-content";
  5551. var segHeader = null;
  5552. var tableCols = opt('tableCols');
  5553. var timecol = $.inArray('time', tableCols) >= 0;
  5554. var i, j, seg, event, times, s, bg, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements, eventElement, triggerRes;
  5555. prevMonth = $('<tbody class="fc-list-header"><tr><td class="fc-list-header fc-month-nav fc-month-prev ' + headerClass + '" colspan="' + tableCols.length + '">' + opt('buttonText', 'prevMonth') + '</td></tr></tbody>').appendTo(table);
  5556. prevMonth.click(function(){
  5557. var prevMonthDate = cloneDate(t.getOrigDate(), true);
  5558. prevMonthDate.setDate(0);
  5559. calendar.gotoDate(prevMonthDate);
  5560. trigger('prevClick');
  5561. });
  5562. for (j in segs) {
  5563. seg = segs[j];
  5564. bg = false;
  5565. if (seg.title) {
  5566. var segHeader = $('<tbody class="fc-list-header"><tr><td class="fc-list-header ' + headerClass + '" colspan="' + tableCols.length + '">' + htmlEscape(seg.title) + '</td></tr></tbody>').appendTo(table);
  5567. }
  5568. segContainer = $('<tbody>').addClass('fc-list-section ' + contentClass).appendTo(table);
  5569. s = '';
  5570. for (i=0; i < seg.events.length; i++) {
  5571. event = seg.events[i];
  5572. times = renderEventTime(event, seg);
  5573. skinCss = getSkinCss(event, opt);
  5574. skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  5575. skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
  5576. if (event.source && event.source.className) {
  5577. skinClasses = skinClasses.concat(event.source.className);
  5578. }
  5579. if(event.source && event.source.background) {
  5580. bg = true;
  5581. }
  5582. rowClasses = ['fc-'+dayIDs[event.start.getDay()], 'fc-event', 'fc-event-row'];
  5583. if(opt('weekendDays').length>0 && opt('weekendDays').indexOf(segs[j].start.getDay())!=-1)
  5584. rowClasses.splice(1, 0, 'fc-weekend-day');
  5585. if (seg.daydiff == 0) {
  5586. if(segHeader)
  5587. segHeader.addClass('fc-today');
  5588. rowClasses.push('fc-today');
  5589. rowClasses.push('fc-state-highlight');
  5590. }
  5591. s += "<tr class='" + rowClasses.join(' ') + "'>";
  5592. for (var col, c=0; c < tableCols.length; c++) {
  5593. col = tableCols[c];
  5594. if (col == 'handle') {
  5595. s += "<td class='fc-event-handle'" + skinCssAttr + "></td>";
  5596. } else if (col == 'title') {
  5597. s += "<td class='fc-event-title'>" + (event.title ? htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm," ")) : '&nbsp;') + "</td>";
  5598. } else if (col == 'date') {
  5599. s += "<td class='fc-event-date' colspan='" + (times[1] || !timecol ? 1 : 2) + "'>" + htmlEscape(times[0]) + "</td>";
  5600. } else if (col == 'time') {
  5601. if (times[1]) {
  5602. s += "<td class='fc-event-time' style='text-overflow: ellipsis; overflow: hidden;'>" + htmlEscape(times[1]) + "</td>";
  5603. }
  5604. } else {
  5605. s += "<td class='fc-event-" + col + "'>" + (event[col] ? htmlEscape(event[col]) : '&nbsp;') + "</td>";
  5606. }
  5607. }
  5608. s += "</tr>";
  5609. // IE doesn't like innerHTML on tbody elements so we insert every row individually
  5610. if (document.all) {
  5611. $(s).appendTo(segContainer);
  5612. s = '';
  5613. }
  5614. }
  5615. if (!document.all)
  5616. segContainer[0].innerHTML = s;
  5617. eventElements = segContainer.children();
  5618. // retrieve elements, run through eventRender callback, bind event handlers
  5619. for (i=0; i < seg.events.length; i++) {
  5620. event = seg.events[i];
  5621. eventElement = $(eventElements[i]); // faster than eq()
  5622. if(bg) {
  5623. eventElement.addClass('fc-source-bg');
  5624. }
  5625. triggerRes = trigger('eventRender', event, event, eventElement);
  5626. if (triggerRes === false) {
  5627. eventElement.remove();
  5628. } else {
  5629. if (triggerRes && triggerRes !== true) {
  5630. eventElement.remove();
  5631. eventElement = $(triggerRes).appendTo(segContainer);
  5632. }
  5633. if (event._id === modifiedEventId) {
  5634. eventElementHandlers(event, eventElement, seg);
  5635. } else {
  5636. eventElement[0]._fci = i; // for lazySegBind
  5637. }
  5638. reportEventElement(event, eventElement);
  5639. }
  5640. trigger('eventAfterRender', event, event, eventElement);
  5641. }
  5642. lazySegBind(segContainer, seg, eventElementHandlers);
  5643. markFirstLast(segContainer);
  5644. segContainer.addClass('fc-day-'+seg.start.getDay());
  5645. }
  5646. nextMonth = $('<tbody class="fc-list-header"><tr><td class="fc-list-header fc-month-nav fc-month-next ' + headerClass + '" colspan="' + tableCols.length + '">' + opt('buttonText', 'nextMonth') + '</td></tr></tbody>').appendTo(table);
  5647. nextMonth.click(function(){
  5648. var nextMonthDate = cloneDate(t.getOrigDate(), true);
  5649. nextMonthDate.setDate(1);
  5650. nextMonthDate.setMonth(nextMonthDate.getMonth() + 1);
  5651. calendar.gotoDate(nextMonthDate);
  5652. trigger('nextClick');
  5653. });
  5654. //markFirstLast(table);
  5655. }
  5656. }
  5657. fcViews.table = TableView;
  5658. function TableView(element, calendar) {
  5659. var t = this;
  5660. // exports
  5661. t.render = render;
  5662. t.select = dummy;
  5663. t.unselect = dummy;
  5664. t.getDaySegmentContainer = function(){return table;};
  5665. t.getOrigDate = function() {return origDate;};
  5666. t.updateGrid = updateGrid;
  5667. t.updateToday = updateToday;
  5668. t.setAxisFormat = setAxisFormat;
  5669. t.setStartOfBusiness = setStartOfBusiness;
  5670. t.setEndOfBusiness = setEndOfBusiness;
  5671. t.setWeekendDays = setWeekendDays;
  5672. t.setBindingMode = setBindingMode;
  5673. t.setSelectable = setSelectable;
  5674. // imports
  5675. View.call(t, element, calendar, 'table');
  5676. TableEventRenderer.call(t);
  5677. var opt = t.opt;
  5678. var trigger = t.trigger;
  5679. var clearEvents = t.clearEvents;
  5680. var reportEventClear = t.reportEventClear;
  5681. var formatDates = calendar.formatDates;
  5682. var formatDate = calendar.formatDate;
  5683. // overrides
  5684. t.setWidth = setWidth;
  5685. t.setHeight = setHeight;
  5686. // locals
  5687. var div;
  5688. var table;
  5689. var firstDay;
  5690. var nwe;
  5691. var tm;
  5692. var colFormat;
  5693. var datepicker;
  5694. var dateInfo;
  5695. var dateInfoNumber;
  5696. var dateInfoNumberDiv;
  5697. var dateInfoText;
  5698. var origDate;
  5699. function render(date, delta) {
  5700. /*if (delta) {
  5701. addDays(date, opt('listPage') * delta);
  5702. }
  5703. t.start = t.visStart = cloneDate(date, true);
  5704. t.end = addDays(cloneDate(t.start), opt('listPage'));
  5705. t.visEnd = addDays(cloneDate(t.start), opt('listRange'));*/
  5706. origDate = date;
  5707. if (delta) {
  5708. addMonths(date, delta);
  5709. date.setDate(1);
  5710. }
  5711. t.start = cloneDate(date, true);
  5712. t.start.setDate(1);
  5713. t.end = addMonths(cloneDate(t.start), 1);
  5714. t.visStart = cloneDate(t.start);
  5715. t.visEnd = cloneDate(t.end);
  5716. addMinutes(t.visEnd, -1); // set end to 23:59
  5717. t.title = formatDates(
  5718. t.visStart,
  5719. t.visEnd,
  5720. opt('titleFormat')
  5721. );
  5722. //t.title = (t.visEnd.getTime() - t.visStart.getTime() < DAY_MS) ? formatDate(date, opt('titleFormat')) : formatDates(date, t.visEnd, opt('titleFormat'));
  5723. updateOptions();
  5724. if (!table) {
  5725. buildSkeleton(origDate);
  5726. } else {
  5727. clearEvents();
  5728. if(opt('showDatepicker')) {
  5729. dateInfoNumberDiv.html(origDate.getDate());
  5730. dateInfoText.html(formatDates(origDate, null, opt('titleFormat', 'table')));
  5731. datepicker.datepicker('option','firstDay',firstDay);
  5732. datepicker.datepicker('setDate', origDate);
  5733. }
  5734. }
  5735. }
  5736. function updateOptions() {
  5737. firstDay = opt('firstDay');
  5738. nwe = opt('weekends') ? 0 : 1;
  5739. tm = opt('theme') ? 'ui' : 'fc';
  5740. colFormat = opt('columnFormat');
  5741. }
  5742. function buildSkeleton(date) {
  5743. var tableCols = opt('tableCols');
  5744. var s =
  5745. "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  5746. "<colgroup>";
  5747. for (var c=0; c < tableCols.length; c++) {
  5748. s += "<col class='fc-event-" + tableCols[c] + "' />";
  5749. }
  5750. s += "</colgroup>" +
  5751. "</table>";
  5752. if(opt('showDatepicker')) {
  5753. dateInfo = $('<div>').addClass('fc-table-dateinfo').appendTo(element);
  5754. dateInfoNumber = $('<div>').addClass('fc-table-dateinfo-number').appendTo(dateInfo);
  5755. dateInfoNumberDiv = $('<div>').appendTo(dateInfoNumber);
  5756. dateInfoNumberDiv.html(date.getDate());
  5757. dateInfoText = $('<div>').addClass('fc-table-dateinfo-text').appendTo(dateInfo);
  5758. dateInfoText.html(formatDates(origDate, null, opt('titleFormat', 'table')));
  5759. datepicker = $('<div>').addClass('fc-table-datepicker').appendTo(element);
  5760. datepicker.datepicker({
  5761. firstDay: opt('firstDay'),
  5762. weekendDays: opt('weekendDays'),
  5763. defaultDate: date,
  5764. showWeek: true,
  5765. weekHeader: '',
  5766. onSelect: function(dateText, inst) {
  5767. var date = new Date(dateText);
  5768. calendar.gotoDate(date);
  5769. trigger('datepickerClick', this, date);
  5770. },
  5771. });
  5772. }
  5773. div = $('<div>').addClass('fc-list-content').appendTo(element);
  5774. table = $(s).appendTo(div);
  5775. }
  5776. function updateGrid()
  5777. {
  5778. updateToday();
  5779. setAxisFormat();
  5780. setStartOfBusiness();
  5781. setEndOfBusiness();
  5782. setWeekendDays();
  5783. setBindingMode();
  5784. setSelectable();
  5785. }
  5786. function updateToday()
  5787. {
  5788. var today = clearTime(new Date());
  5789. var segHash = formatDate(today, colFormat);
  5790. $(table).find('.fc-list-header').each(function() {
  5791. $(this).removeClass('fc-today');
  5792. $(this).next().children().removeClass('fc-state-highlight');
  5793. if(segHash == $(this).find('td').html()) {
  5794. $(this).addClass('fc-today');
  5795. $(this).next().children().addClass('fc-state-highlight');
  5796. }
  5797. });
  5798. datepicker.datepicker('refresh');
  5799. }
  5800. function setAxisFormat()
  5801. {
  5802. // dummy
  5803. }
  5804. function setStartOfBusiness()
  5805. {
  5806. // dummy
  5807. }
  5808. function setEndOfBusiness()
  5809. {
  5810. // dummy
  5811. }
  5812. function setWeekendDays()
  5813. {
  5814. var weekendDays = opt('weekendDays');
  5815. $(table).find('.fc-list-section').each(function() {
  5816. var day=parseInt(this.className.match(/fc-day-(\d)/)[1],10);
  5817. if(weekendDays.indexOf(day)==-1)
  5818. $(this).children().removeClass('fc-weekend-day');
  5819. else
  5820. $(this).children().addClass('fc-weekend-day');
  5821. });
  5822. if(opt('showDatepicker'))
  5823. datepicker.datepicker('option','weekendDays',weekendDays);
  5824. }
  5825. function setBindingMode()
  5826. {
  5827. // dummy
  5828. }
  5829. function setSelectable()
  5830. {
  5831. // dummy
  5832. }
  5833. function setHeight(height, dateChanged) {
  5834. if(opt('showDatepicker')) {
  5835. var datepickerHeight = datepicker.height();
  5836. dateInfoText.css('padding-bottom', datepickerHeight - datepicker.children().outerHeight() + 3); //+3 for paddings
  5837. var textHeight = dateInfoText.outerHeight();
  5838. dateInfoNumber.css({'height': datepickerHeight - textHeight,
  5839. 'font-size': 145 - textHeight});
  5840. dateInfoNumberDiv.height(145 - textHeight);
  5841. }
  5842. div.css('height', (height-div.position().top-2)+'px').css('overflow', 'auto');
  5843. }
  5844. function setWidth(width) {
  5845. var outerWidth = Math.floor(element.parent().width() / 2) - 8;
  5846. element.css({'left' : width, 'width' : outerWidth});
  5847. }
  5848. function dummy() {
  5849. // Stub.
  5850. }
  5851. }
  5852. function TodoEventRenderer() {
  5853. var t = this;
  5854. // exports
  5855. t.renderEvents = renderEvents;
  5856. t.clearEvents = clearEvents;
  5857. t.renderEventTime = renderEventTime;
  5858. t.compileDaySegs = compileSegs; // for DayEventRenderer
  5859. t.lazySegBind = lazySegBind;
  5860. t.sortCmp = sortCmp;
  5861. // imports
  5862. DayEventRenderer.call(t);
  5863. var opt = t.opt;
  5864. var sortCmp = t.sortCmp;
  5865. var trigger = t.trigger;
  5866. var compileSegs = t.compileDaySegs;
  5867. var reportEvents = t.reportEvents;
  5868. var reportEventClear = t.reportEventClear;
  5869. var reportEventElement = t.reportEventElement;
  5870. var eventElementHandlers = t.eventElementHandlers;
  5871. var renderEventTime = t.renderEventTime;
  5872. var showEvents = t.showEvents;
  5873. var hideEvents = t.hideEvents;
  5874. var getListContainer = t.getDaySegmentContainer;
  5875. var lazySegBind = t.lazySegBind;
  5876. var calendar = t.calendar;
  5877. var formatDate = calendar.formatDate;
  5878. var formatDates = calendar.formatDates;
  5879. var prevMonth;
  5880. var nextMonth;
  5881. function compileSegs(events) {
  5882. var segs = {};
  5883. var event, i;
  5884. //for (i=0; i < events.length; i++) {
  5885. for (i=events.length-1; i > -1; i--) {
  5886. event = events[i];
  5887. var segHash = event.repeatHash;
  5888. var eventEnd = event.end ? cloneDate(event.end) : cloneDate(event.start);
  5889. // skip events out of range
  5890. if ((event.completedOn && event.completedOn < t.start && (opt('showUnstartedEvents') || !event.start || event.completedOn > event.start)) ||
  5891. (!opt('showUnstartedEvents') && event.start && event.start > t.visEnd)) {
  5892. continue;
  5893. }
  5894. // start new segment
  5895. if (!(segHash in segs)) {
  5896. segs[segHash] = { events: [], id: segHash};
  5897. }
  5898. segs[segHash].events.push(event);
  5899. }
  5900. return segs;
  5901. }
  5902. function reverseSegs(oldSegs) {
  5903. var newSegs = {};
  5904. var keys = $.map(oldSegs, function (value, key) { return key; });
  5905. var values = $.map(oldSegs, function (value, key) { return value; });
  5906. for (i=keys.length-1; i > -1; i--) {
  5907. newSegs[keys[i]] = values[i];
  5908. }
  5909. return newSegs;
  5910. }
  5911. function sortCmp(a, b) {
  5912. /*var sd = a.start.getTime() - b.start.getTime();
  5913. var aEnd = a.end ? a.end : a.start;
  5914. var bEnd = b.end ? b.end : b.start;
  5915. return sd + (sd ? 0 : aEnd.getTime() - bEnd.getTime());*/
  5916. var aEnd = a.end ? a.end.getTime() : Infinity;
  5917. var bEnd = b.end ? b.end.getTime() : Infinity;
  5918. var aStart = a.start ? a.start.getTime() : Infinity;
  5919. var bStart = b.start ? b.start.getTime() : Infinity;
  5920. var aPriority = parseInt(a.priority, 10) || 10;
  5921. var bPriority = parseInt(b.priority, 10) || 10;
  5922. var statusSort = {
  5923. "NEEDS-ACTION": 1,
  5924. "IN-PROCESS": 2,
  5925. "COMPLETED": 3,
  5926. "CANCELLED": 4
  5927. };
  5928. if(aEnd < bEnd) {
  5929. return -1;
  5930. }
  5931. else if(bEnd < aEnd) {
  5932. return 1;
  5933. }
  5934. else if(aStart < bStart){
  5935. return -1;
  5936. }
  5937. else if(bStart < aStart) {
  5938. return 1;
  5939. }
  5940. else if(aPriority < bPriority) {
  5941. return -1;
  5942. }
  5943. else if(bPriority < aPriority) {
  5944. return 1;
  5945. }
  5946. else if(statusSort[a.status] < statusSort[b.status]) {
  5947. return -1;
  5948. }
  5949. else if(statusSort[b.status] < statusSort[a.status]) {
  5950. return 1;
  5951. }
  5952. else if(a.percent < b.percent) {
  5953. return -1;
  5954. }
  5955. else if(b.percent < a.percent) {
  5956. return 1;
  5957. }
  5958. else if(a.compareString < b.compareString) {
  5959. return -1;
  5960. }
  5961. else if(b.compareString < a.compareString) {
  5962. return 1;
  5963. }
  5964. else {
  5965. return 0;
  5966. }
  5967. }
  5968. // event time/date range to display
  5969. function renderEventTime(event) {
  5970. var timeFormat = opt('timeFormat', 'list');
  5971. return event.end? formatDate(event.end, timeFormat) : '';
  5972. }
  5973. function lazySegBind(container, seg, bindHandlers) {
  5974. container.unbind('mouseover').mouseover(function(ev) {
  5975. var parent = ev.target, e = parent, i, event;
  5976. while (parent != this) {
  5977. e = parent;
  5978. parent = parent.parentNode;
  5979. }
  5980. if ((i = e._fci) !== undefined) {
  5981. e._fci = undefined;
  5982. event = seg.events[i];
  5983. bindHandlers(event, container.children().eq(0), seg);
  5984. $(ev.target).trigger(ev);
  5985. }
  5986. ev.stopPropagation();
  5987. });
  5988. }
  5989. function clearEvents() {
  5990. reportEventClear();
  5991. getListContainer().children('tbody').remove();
  5992. }
  5993. function renderEvents(events, modifiedEventId) {
  5994. events.sort(sortCmp);
  5995. reportEvents(events);
  5996. renderSegs(reverseSegs(compileSegs(events)), modifiedEventId);
  5997. getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections'));
  5998. //t.selectEvent();
  5999. t.applyFilters();
  6000. }
  6001. function renderSegs(segs, modifiedEventId) {
  6002. var tm = opt('theme') ? 'ui' : 'fc';
  6003. var table = getListContainer();
  6004. var headerClass = tm + "-widget-header";
  6005. var contentClass = tm + "-widget-content";
  6006. var segHeader = null;
  6007. var tableCols = opt('todoCols');
  6008. var timecol = $.inArray('time', tableCols) >= 0;
  6009. var i, j, iter, seg, event, times, s, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements, eventElement, triggerRes;
  6010. for (j in segs) {
  6011. seg = segs[j];
  6012. segContainer = $('<tbody>').addClass('fc-list-section ' + contentClass).appendTo(table);
  6013. s = '';
  6014. event = seg.events[0];
  6015. iter=0;
  6016. if(opt('showUnstartedEvents') && seg.events.length>1) {
  6017. for(;iter<seg.events.length; iter++) {
  6018. if(seg.events[iter].start<t.end) {
  6019. event = seg.events[iter];
  6020. break;
  6021. }
  6022. }
  6023. if(iter==seg.events.length) {
  6024. continue;
  6025. }
  6026. }
  6027. dueTime = renderEventTime(event);
  6028. skinCss = getSkinCss(event, opt);
  6029. skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  6030. skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
  6031. if (event.source && event.source.className) {
  6032. skinClasses = skinClasses.concat(event.source.className);
  6033. }
  6034. rowClasses = ['fc-event', 'fc-event-row'];
  6035. if(event.end && event.end.getTime() < cloneDate(t.start, true)) {
  6036. rowClasses.push('fc-event-pastdue');
  6037. }
  6038. else if(event.end && event.end.getTime() < addDays(cloneDate(t.start), 2, false).getTime()) {
  6039. rowClasses.push('fc-event-urgent');
  6040. }
  6041. if(event.filterStatus) {
  6042. rowClasses.push('fc-event-'+event.filterStatus);
  6043. }
  6044. s += "<tr class='" + rowClasses.join(' ') + "'>";
  6045. for (var col, c=0; c < tableCols.length; c++) {
  6046. col = tableCols[c];
  6047. if (col == 'handle') {
  6048. s += "<td class='fc-event-handle'" + skinCssAttr + "></td>";
  6049. } else if (col == 'check') {
  6050. s += "<td class='fc-event-check'>" + '<input type="checkbox" class="fc-event-checkbox" data-ind="false"/>' + "</td>";
  6051. } else if (col == 'priority') {
  6052. s += "<td class='fc-event-priority fc-event-priority-" + event.renderPriority + "'>" + (event.renderPriority ? '&nbsp;' : '') + "</td>";
  6053. } else if (col == 'time') {
  6054. s += "<td class='fc-event-time'>" + htmlEscape(dueTime) + "</td>";
  6055. } else if (col == 'title') {
  6056. s += "<td class='fc-event-title'>" + htmlEscape(event.title.replace(/(\r\n|\n|\r)+/gm, " ")) + "</td>";
  6057. } else if (col == 'location') {
  6058. s += "<td class='fc-event-location'>" + htmlEscape(event.location.replace(/(\r\n|\n|\r)+/gm, " ")) + "</td>";
  6059. } else if (col == 'status') {
  6060. s += "<td class='fc-event-status'></td>";
  6061. } else if (col == 'percent') {
  6062. s += "<td class='fc-event-percent'>" + event.percent + '%' + "</td>";
  6063. }
  6064. else {
  6065. s += "<td class='fc-event-" + col + "'>" + (event[col] ? htmlEscape(event[col]) : '&nbsp;') + "</td>";
  6066. }
  6067. }
  6068. s += "</tr>";
  6069. // IE doesn't like innerHTML on tbody elements so we insert every row individually
  6070. if (document.all) {
  6071. $(s).appendTo(segContainer);
  6072. s = '';
  6073. }
  6074. if (!document.all)
  6075. segContainer[0].innerHTML = s;
  6076. eventElements = segContainer.children();
  6077. // retrieve elements, run through eventRender callback, bind event handlers
  6078. eventElement = $(eventElements[0]); // faster than eq()
  6079. triggerRes = trigger('eventRender', event, event, eventElement);
  6080. if (triggerRes === false) {
  6081. eventElement.remove();
  6082. } else {
  6083. if (triggerRes && triggerRes !== true) {
  6084. eventElement.remove();
  6085. eventElement = $(triggerRes).appendTo(segContainer);
  6086. }
  6087. if (event._id === modifiedEventId) {
  6088. eventElementHandlers(event, eventElement, seg);
  6089. } else {
  6090. eventElement[0]._fci = iter; // for lazySegBind
  6091. }
  6092. reportEventElement(event, eventElement);
  6093. }
  6094. trigger('eventCheckDefault', event, event, eventElement.find('.fc-event-checkbox'));
  6095. trigger('eventAfterRender', event, event, eventElement);
  6096. lazySegBind(segContainer, seg, eventElementHandlers);
  6097. markFirstLast(segContainer);
  6098. }
  6099. //markFirstLast(table);
  6100. }
  6101. }
  6102. fcViews.todo = TodoView;
  6103. function TodoView(element, calendar) {
  6104. var t = this;
  6105. // exports
  6106. t.render = render;
  6107. t.select = dummy;
  6108. t.unselect = dummy;
  6109. t.getDaySegmentContainer = function(){ return table; };
  6110. t.applyFilters = applyFilters;
  6111. t.allowSelectEvent = allowSelectEvent;
  6112. t.eventSelectLock = 0;
  6113. t.updateGrid = updateGrid;
  6114. t.updateToday = updateToday;
  6115. t.setAxisFormat = setAxisFormat;
  6116. t.setStartOfBusiness = setStartOfBusiness;
  6117. t.setEndOfBusiness = setEndOfBusiness;
  6118. t.setWeekendDays = setWeekendDays;
  6119. t.setBindingMode = setBindingMode;
  6120. t.setSelectable = setSelectable;
  6121. // imports
  6122. View.call(t, element, calendar, 'todo');
  6123. TodoEventRenderer.call(t);
  6124. var opt = t.opt;
  6125. var trigger = t.trigger;
  6126. var clearEvents = t.clearEvents;
  6127. var reportEventClear = t.reportEventClear;
  6128. var formatDates = calendar.formatDates;
  6129. var formatDate = calendar.formatDate;
  6130. // overrides
  6131. t.setWidth = setWidth;
  6132. t.setHeight = setHeight;
  6133. // locals
  6134. var div;
  6135. var table;
  6136. var filter;
  6137. var filterTable;
  6138. var firstDay;
  6139. var nwe;
  6140. var tm;
  6141. var colFormat;
  6142. var currentDate;
  6143. var datepickers;
  6144. var dateInfo;
  6145. var dateInfoNumber;
  6146. var dateInfoNumberDiv;
  6147. var dateInfoText;
  6148. function render(date, delta) {
  6149. if (delta) {
  6150. addMonths(date, delta);
  6151. date.setDate(1);
  6152. }
  6153. currentDate = date;
  6154. var start = cloneDate(date, true);
  6155. var end = addDays(cloneDate(start), 1);
  6156. t.title = formatDate(date, opt('titleFormat'));
  6157. t.start = t.visStart = start;
  6158. t.end = t.visEnd = end;
  6159. updateOptions();
  6160. if (!table) {
  6161. buildSkeleton(date);
  6162. initFilters();
  6163. } else {
  6164. clearEvents();
  6165. filterTable.find('.fc-filter-table-footer').text(opt('buttonText', 'filtersFooter').replace('%date%', formatDates(date, null, opt('columnFormat', 'todo'))));
  6166. if(opt('showDatepicker')) {
  6167. dateInfoNumberDiv.html(date.getDate());
  6168. dateInfoText.html(formatDates(date, null, opt('titleFormat', 'todo')));
  6169. var defaultDate = cloneDate(date, true);
  6170. defaultDate.setHours(12);
  6171. defaultDate.setDate(1);
  6172. defaultDate.setMonth(currentDate.getMonth() - datepickers.length + 1);
  6173. datepickers.forEach(function(e, i){
  6174. defaultDate.setMonth(defaultDate.getMonth() + 1);
  6175. e.datepicker('option','firstDay',firstDay);
  6176. if((i===0 && datepickers.length<3) || (i===datepickers.length-2 && datepickers.length>2))
  6177. e.datepicker('setDate', date);
  6178. else
  6179. e.datepicker('setDate', defaultDate);
  6180. });
  6181. }
  6182. }
  6183. }
  6184. function updateOptions() {
  6185. firstDay = opt('firstDay');
  6186. nwe = opt('weekends') ? 0 : 1;
  6187. tm = opt('theme') ? 'ui' : 'fc';
  6188. colFormat = opt('columnFormat');
  6189. }
  6190. function buildSkeleton(date) {
  6191. var tableCols = opt('todoCols');
  6192. var s =
  6193. "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  6194. "<colgroup>";
  6195. for (var c=0; c < tableCols.length; c++) {
  6196. s += "<col class='fc-event-" + tableCols[c] + "' />";
  6197. }
  6198. s += "</colgroup>" +
  6199. "</table>";
  6200. if(opt('showDatepicker')) {
  6201. dateInfo = $('<div>').addClass('fc-table-dateinfo').appendTo(element);
  6202. dateInfoNumber = $('<div>').addClass('fc-table-dateinfo-number').appendTo(dateInfo);
  6203. dateInfoNumberDiv = $('<div>').appendTo(dateInfoNumber);
  6204. dateInfoNumberDiv.html(date.getDate());
  6205. dateInfoText = $('<div>').addClass('fc-table-dateinfo-text').appendTo(dateInfo);
  6206. dateInfoText.html(formatDates(date, null, opt('titleFormat', 'todo')));
  6207. datepickers = [$('<div>').addClass('fc-table-datepicker fc-table-datepicker-current').appendTo(element)];
  6208. datepickers[0].datepicker({
  6209. firstDay: opt('firstDay'),
  6210. weekendDays: opt('weekendDays'),
  6211. defaultDate: date,
  6212. showWeek: true,
  6213. weekHeader: '',
  6214. onSelect: function(dateText, inst) {
  6215. var date = new Date(dateText);
  6216. calendar.gotoDate(date);
  6217. trigger('datepickerClick', this, date);
  6218. }
  6219. });
  6220. }
  6221. filter = $('<div>').addClass('fc-filter').appendTo(element);
  6222. var ft = '<table class="fc-filter-table">' +
  6223. '<tr>' +
  6224. '<td class="fc-filter-table-header" colspan="2">'+opt('buttonText', 'filtersHeader')+'</td>' +
  6225. '</tr>';
  6226. if(opt('simpleFilters')) {
  6227. ft += '<tr>' +
  6228. '<td class="fc-filter-option fc-filter-action" data-type="filterAction">'+ opt('buttonText', 'filterAction') +'</td>' +
  6229. '<td class="fc-filter-option fc-filter-completed fc-filter-option-last" data-type="filterCompleted">'+ opt('buttonText', 'filterCompleted') +' *</td>' +
  6230. '</tr>';
  6231. }
  6232. else {
  6233. ft += '<tr>' +
  6234. '<td class="fc-filter-option fc-filter-action" data-type="filterAction">'+ opt('buttonText', 'filterAction') +'</td>' +
  6235. '<td class="fc-filter-option fc-filter-progress" data-type="filterProgress">'+ opt('buttonText', 'filterProgress') +'</td>' +
  6236. '</tr>' +
  6237. '<tr>' +
  6238. '<td class="fc-filter-option fc-filter-completed" data-type="filterCompleted">'+ opt('buttonText', 'filterCompleted') +' *</td>' +
  6239. '<td class="fc-filter-option fc-filter-canceled fc-filter-option-last" data-type="filterCanceled">'+ opt('buttonText', 'filterCanceled') +'</td>' +
  6240. '</tr>';
  6241. }
  6242. ft += '<tr>' +
  6243. '<td class="fc-filter-table-footer" colspan="2">'+opt('buttonText', 'filtersFooter').replace('%date%', formatDates(date, null, opt('columnFormat', 'todo')))+'</td>' +
  6244. '</tr>' +
  6245. '</table>';
  6246. filterTable = $(ft).appendTo(filter);
  6247. div = $('<div>').addClass('fc-list-content').appendTo(element);
  6248. table = $(s).appendTo(div);
  6249. }
  6250. function updateGrid()
  6251. {
  6252. updateToday();
  6253. setAxisFormat();
  6254. setStartOfBusiness();
  6255. setEndOfBusiness();
  6256. setWeekendDays();
  6257. setBindingMode();
  6258. setSelectable();
  6259. }
  6260. function updateToday()
  6261. {
  6262. if(opt('showDatepicker'))
  6263. datepickers.forEach(function(e){
  6264. e.datepicker('refresh');
  6265. });
  6266. }
  6267. function setAxisFormat()
  6268. {
  6269. // dummy
  6270. }
  6271. function setStartOfBusiness()
  6272. {
  6273. // dummy
  6274. }
  6275. function setEndOfBusiness()
  6276. {
  6277. // dummy
  6278. }
  6279. function setWeekendDays()
  6280. {
  6281. if(opt('showDatepicker'))
  6282. datepickers.forEach(function(e){
  6283. e.datepicker('option','weekendDays',opt('weekendDays'));
  6284. });
  6285. }
  6286. function setBindingMode()
  6287. {
  6288. // dummy
  6289. }
  6290. function setSelectable()
  6291. {
  6292. // dummy
  6293. }
  6294. function initFilters() {
  6295. filterTable.find('.fc-filter-option').each(function() {
  6296. if(opt('defaultFilters').indexOf($(this).attr('data-type')) != -1) {
  6297. filterToggle($(this));
  6298. }
  6299. $(this).click(function(){
  6300. filterToggle($(this));
  6301. });
  6302. });
  6303. }
  6304. function filterToggle(button) {
  6305. if(button.hasClass('fc-filter-option-selected')) {
  6306. button.removeClass('fc-filter-option-selected');
  6307. }
  6308. else {
  6309. button.addClass('fc-filter-option-selected');
  6310. }
  6311. applyFilters();
  6312. }
  6313. function applyFilters() {
  6314. filterTable.find('.fc-filter-option').each(function(){
  6315. if($(this).hasClass('fc-filter-option-selected')) {
  6316. t.getDaySegmentContainer().find('.fc-event-' + $(this).attr('data-type')).removeClass('fc-filter-hide');
  6317. }
  6318. else {
  6319. t.getDaySegmentContainer().find('.fc-event-' + $(this).attr('data-type')).addClass('fc-filter-hide');
  6320. }
  6321. });
  6322. opt('todoOptionalCols').forEach(function(item){
  6323. var itemsFilled = $('.fc-event-'+item.col+':visible').filter(function(){
  6324. return this.innerHTML!=='';
  6325. });
  6326. $('col.fc-event-'+item.col).toggleClass('fc-hidden-empty', !itemsFilled.length);
  6327. });
  6328. //if(!t.getDaySegmentContainer().find('.fc-event-selected:visible').length) {
  6329. t.selectEvent();
  6330. //}
  6331. }
  6332. function setHeight(height, dateChanged) {
  6333. if(opt('showDatepicker')) {
  6334. var datepickerHeight = datepickers[0].height();
  6335. dateInfoText.css('padding-bottom', datepickerHeight - datepickers[0].children().outerHeight() + 3); //+3 for paddings
  6336. var textHeight = dateInfoText.outerHeight();
  6337. dateInfoNumber.css({'height': datepickerHeight - textHeight,
  6338. 'font-size': 145 - textHeight});
  6339. dateInfoNumberDiv.height(145 - textHeight);
  6340. }
  6341. div.css({'height': height-div.position().top-2, 'overflow': 'auto'});
  6342. }
  6343. function setWidth(width) {
  6344. element.width(width);
  6345. var slots = Math.floor((width - dateInfo.outerWidth() - 1) / datepickers[0].outerWidth());
  6346. if(slots > datepickers.length) {
  6347. var defaultDate = cloneDate(currentDate, true);
  6348. defaultDate.setHours(12);
  6349. defaultDate.setDate(1);
  6350. defaultDate.setMonth(currentDate.getMonth() + 1);
  6351. if(datepickers.length==1) {
  6352. datepickers.push($('<div>').addClass('fc-table-datepicker fc-table-datepicker-no-default').prependTo(element).datepicker({
  6353. firstDay: opt('firstDay'),
  6354. weekendDays: opt('weekendDays'),
  6355. defaultDate: cloneDate(defaultDate),
  6356. showWeek: true,
  6357. weekHeader: '',
  6358. hideIfNoPrevNext: true,
  6359. onSelect: function(dateText, inst) {
  6360. var date = new Date(dateText);
  6361. calendar.gotoDate(date);
  6362. trigger('datepickerClick', this, date);
  6363. }
  6364. }));
  6365. }
  6366. defaultDate.setMonth(defaultDate.getMonth() - datepickers.length + 1);
  6367. for(var i=datepickers.length; i<slots; i++) {
  6368. defaultDate.setMonth(defaultDate.getMonth() - 1);
  6369. datepickers.unshift($('<div>').addClass('fc-table-datepicker fc-table-datepicker-no-default').insertBefore(filter).datepicker({
  6370. firstDay: opt('firstDay'),
  6371. weekendDays: opt('weekendDays'),
  6372. defaultDate: cloneDate(defaultDate),
  6373. showWeek: true,
  6374. weekHeader: '',
  6375. hideIfNoPrevNext: true,
  6376. onSelect: function(dateText, inst) {
  6377. var date = new Date(dateText);
  6378. calendar.gotoDate(date);
  6379. trigger('datepickerClick', this, date);
  6380. }
  6381. }));
  6382. }
  6383. }
  6384. else {
  6385. while(datepickers.length>slots && datepickers.length>1) {
  6386. if(datepickers.length==2)
  6387. datepickers.pop().remove();
  6388. else
  6389. datepickers.shift().remove();
  6390. }
  6391. }
  6392. var hiddenWidth = 0;
  6393. opt('todoOptionalCols').forEach(function(e){
  6394. hiddenWidth += $('col.fc-event-'+e.col).hasClass('fc-hidden-empty') ? e.width : 0;
  6395. });
  6396. opt('todoColThresholds').forEach(function(e){
  6397. $('col.fc-event-'+e.col).toggleClass('fc-hidden-width', width<e.width-hiddenWidth);
  6398. });
  6399. }
  6400. function allowSelectEvent(value) {
  6401. if(value)
  6402. t.eventSelectLock++;
  6403. else
  6404. t.eventSelectLock--;
  6405. }
  6406. function dummy() {
  6407. // Stub.
  6408. }
  6409. }
  6410. })(jQuery);