fullcalendar.js 122 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224
  1. /**
  2. * @preserve
  3. * FullCalendar v1.5.3
  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: Mon Feb 6 22:40:40 2012 -0800
  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. // editing
  29. //editable: false,
  30. //disableDragging: false,
  31. //disableResizing: false,
  32. allDayDefault: true,
  33. ignoreTimezone: true,
  34. // event ajax
  35. lazyFetching: true,
  36. startParam: 'start',
  37. endParam: 'end',
  38. // time formats
  39. titleFormat: {
  40. month: 'MMMM yyyy',
  41. week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
  42. day: 'dddd, MMM d, yyyy'
  43. },
  44. columnFormat: {
  45. month: 'ddd',
  46. week: 'ddd M/d',
  47. day: 'dddd M/d'
  48. },
  49. timeFormat: { // for event elements
  50. '': 'h(:mm)t' // default
  51. },
  52. // locale
  53. isRTL: false,
  54. firstDay: 0,
  55. monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
  56. monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
  57. dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
  58. dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
  59. buttonText: {
  60. prev: ' ◄ ',
  61. next: ' ► ',
  62. prevYear: ' << ',
  63. nextYear: ' >> ',
  64. today: 'today',
  65. month: 'month',
  66. week: 'week',
  67. day: 'day'
  68. },
  69. // jquery-ui theming
  70. theme: false,
  71. buttonIcons: {
  72. prev: 'circle-triangle-w',
  73. next: 'circle-triangle-e'
  74. },
  75. //selectable: false,
  76. unselectAuto: true,
  77. dropAccept: '*'
  78. };
  79. // right-to-left defaults
  80. var rtlDefaults = {
  81. header: {
  82. left: 'next,prev today',
  83. center: '',
  84. right: 'title'
  85. },
  86. buttonText: {
  87. prev: ' ► ',
  88. next: ' ◄ ',
  89. prevYear: ' >> ',
  90. nextYear: ' << '
  91. },
  92. buttonIcons: {
  93. prev: 'circle-triangle-e',
  94. next: 'circle-triangle-w'
  95. }
  96. };
  97. var fc = $.fullCalendar = { version: "1.5.3" };
  98. var fcViews = fc.views = {};
  99. $.fn.fullCalendar = function(options) {
  100. // method calling
  101. if (typeof options == 'string') {
  102. var args = Array.prototype.slice.call(arguments, 1);
  103. var res;
  104. this.each(function() {
  105. var calendar = $.data(this, 'fullCalendar');
  106. if (calendar && $.isFunction(calendar[options])) {
  107. var r = calendar[options].apply(calendar, args);
  108. if (res === undefined) {
  109. res = r;
  110. }
  111. if (options == 'destroy') {
  112. $.removeData(this, 'fullCalendar');
  113. }
  114. }
  115. });
  116. if (res !== undefined) {
  117. return res;
  118. }
  119. return this;
  120. }
  121. // would like to have this logic in EventManager, but needs to happen before options are recursively extended
  122. var eventSources = options.eventSources || [];
  123. delete options.eventSources;
  124. if (options.events) {
  125. eventSources.push(options.events);
  126. delete options.events;
  127. }
  128. options = $.extend(true, {},
  129. defaults,
  130. (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
  131. options
  132. );
  133. this.each(function(i, _element) {
  134. var element = $(_element);
  135. var calendar = new Calendar(element, options, eventSources);
  136. element.data('fullCalendar', calendar); // TODO: look into memory leak implications
  137. calendar.render();
  138. });
  139. return this;
  140. };
  141. // function for adding/overriding defaults
  142. function setDefaults(d) {
  143. $.extend(true, defaults, d);
  144. }
  145. function Calendar(element, options, eventSources) {
  146. var t = this;
  147. // exports
  148. t.options = options;
  149. t.render = render;
  150. t.destroy = destroy;
  151. t.refetchEvents = refetchEvents;
  152. t.reportEvents = reportEvents;
  153. t.reportEventChange = reportEventChange;
  154. t.rerenderEvents = rerenderEvents;
  155. t.changeView = changeView;
  156. t.select = select;
  157. t.unselect = unselect;
  158. t.prev = prev;
  159. t.next = next;
  160. t.prevYear = prevYear;
  161. t.nextYear = nextYear;
  162. t.today = today;
  163. t.gotoDate = gotoDate;
  164. t.incrementDate = incrementDate;
  165. t.formatDate = function(format, date) { return formatDate(format, date, options) };
  166. t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
  167. t.getDate = getDate;
  168. t.getView = getView;
  169. t.option = option;
  170. t.trigger = trigger;
  171. // imports
  172. EventManager.call(t, options, eventSources);
  173. var isFetchNeeded = t.isFetchNeeded;
  174. var fetchEvents = t.fetchEvents;
  175. // locals
  176. var _element = element[0];
  177. var header;
  178. var headerElement;
  179. var content;
  180. var tm; // for making theme classes
  181. var currentView;
  182. var viewInstances = {};
  183. var elementOuterWidth;
  184. var suggestedViewHeight;
  185. var absoluteViewElement;
  186. var resizeUID = 0;
  187. var ignoreWindowResize = 0;
  188. var date = new Date();
  189. var events = [];
  190. var _dragElement;
  191. /* Main Rendering
  192. -----------------------------------------------------------------------------*/
  193. setYMD(date, options.year, options.month, options.date);
  194. function render(inc) {
  195. if (!content) {
  196. initialRender();
  197. }else{
  198. calcSize();
  199. markSizesDirty();
  200. markEventsDirty();
  201. renderView(inc);
  202. }
  203. }
  204. function initialRender() {
  205. tm = options.theme ? 'ui' : 'fc';
  206. element.addClass('fc');
  207. if (options.isRTL) {
  208. element.addClass('fc-rtl');
  209. }
  210. if (options.theme) {
  211. element.addClass('ui-widget');
  212. }
  213. content = $("<div class='fc-content' style='position:relative'/>")
  214. .prependTo(element);
  215. header = new Header(t, options);
  216. headerElement = header.render();
  217. if (headerElement) {
  218. element.prepend(headerElement);
  219. }
  220. changeView(options.defaultView);
  221. $(window).resize(windowResize);
  222. // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
  223. if (!bodyVisible()) {
  224. lateRender();
  225. }
  226. }
  227. // called when we know the calendar couldn't be rendered when it was initialized,
  228. // but we think it's ready now
  229. function lateRender() {
  230. setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
  231. if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
  232. renderView();
  233. }
  234. },0);
  235. }
  236. function destroy() {
  237. $(window).unbind('resize', windowResize);
  238. header.destroy();
  239. content.remove();
  240. element.removeClass('fc fc-rtl ui-widget');
  241. }
  242. function elementVisible() {
  243. return _element.offsetWidth !== 0;
  244. }
  245. function bodyVisible() {
  246. return $('body')[0].offsetWidth !== 0;
  247. }
  248. /* View Rendering
  249. -----------------------------------------------------------------------------*/
  250. // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
  251. function changeView(newViewName) {
  252. if (!currentView || newViewName != currentView.name) {
  253. ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
  254. unselect();
  255. var oldView = currentView;
  256. var newViewElement;
  257. if (oldView) {
  258. (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
  259. setMinHeight(content, content.height());
  260. oldView.element.hide();
  261. }else{
  262. setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
  263. }
  264. content.css('overflow', 'hidden');
  265. currentView = viewInstances[newViewName];
  266. if (currentView) {
  267. currentView.element.show();
  268. }else{
  269. currentView = viewInstances[newViewName] = new fcViews[newViewName](
  270. newViewElement = absoluteViewElement =
  271. $("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
  272. .appendTo(content),
  273. t // the calendar object
  274. );
  275. }
  276. if (oldView) {
  277. header.deactivateButton(oldView.name);
  278. }
  279. header.activateButton(newViewName);
  280. renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
  281. content.css('overflow', '');
  282. if (oldView) {
  283. setMinHeight(content, 1);
  284. }
  285. if (!newViewElement) {
  286. (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
  287. }
  288. ignoreWindowResize--;
  289. }
  290. }
  291. function renderView(inc) {
  292. if (elementVisible()) {
  293. ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
  294. unselect();
  295. if (suggestedViewHeight === undefined) {
  296. calcSize();
  297. }
  298. var forceEventRender = false;
  299. if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
  300. // view must render an entire new date range (and refetch/render events)
  301. currentView.render(date, inc || 0); // responsible for clearing events
  302. setSize(true);
  303. forceEventRender = true;
  304. }
  305. else if (currentView.sizeDirty) {
  306. // view must resize (and rerender events)
  307. currentView.clearEvents();
  308. setSize();
  309. forceEventRender = true;
  310. }
  311. else if (currentView.eventsDirty) {
  312. currentView.clearEvents();
  313. forceEventRender = true;
  314. }
  315. currentView.sizeDirty = false;
  316. currentView.eventsDirty = false;
  317. updateEvents(forceEventRender);
  318. elementOuterWidth = element.outerWidth();
  319. header.updateTitle(currentView.title);
  320. var today = new Date();
  321. if (today >= currentView.start && today < currentView.end) {
  322. header.disableButton('today');
  323. }else{
  324. header.enableButton('today');
  325. }
  326. ignoreWindowResize--;
  327. currentView.trigger('viewDisplay', _element);
  328. }
  329. }
  330. /* Resizing
  331. -----------------------------------------------------------------------------*/
  332. function updateSize() {
  333. markSizesDirty();
  334. if (elementVisible()) {
  335. calcSize();
  336. setSize();
  337. unselect();
  338. currentView.clearEvents();
  339. currentView.renderEvents(events);
  340. currentView.sizeDirty = false;
  341. }
  342. }
  343. function markSizesDirty() {
  344. $.each(viewInstances, function(i, inst) {
  345. inst.sizeDirty = true;
  346. });
  347. }
  348. function calcSize() {
  349. if (options.contentHeight) {
  350. suggestedViewHeight = options.contentHeight;
  351. }
  352. else if (options.height) {
  353. suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
  354. }
  355. else {
  356. suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
  357. }
  358. }
  359. function setSize(dateChanged) { // todo: dateChanged?
  360. ignoreWindowResize++;
  361. currentView.setHeight(suggestedViewHeight, dateChanged);
  362. if (absoluteViewElement) {
  363. absoluteViewElement.css('position', 'relative');
  364. absoluteViewElement = null;
  365. }
  366. currentView.setWidth(content.width(), dateChanged);
  367. ignoreWindowResize--;
  368. }
  369. function windowResize() {
  370. if (!ignoreWindowResize) {
  371. if (currentView.start) { // view has already been rendered
  372. var uid = ++resizeUID;
  373. setTimeout(function() { // add a delay
  374. if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
  375. if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
  376. ignoreWindowResize++; // in case the windowResize callback changes the height
  377. updateSize();
  378. currentView.trigger('windowResize', _element);
  379. ignoreWindowResize--;
  380. }
  381. }
  382. }, 200);
  383. }else{
  384. // calendar must have been initialized in a 0x0 iframe that has just been resized
  385. lateRender();
  386. }
  387. }
  388. }
  389. /* Event Fetching/Rendering
  390. -----------------------------------------------------------------------------*/
  391. // fetches events if necessary, rerenders events if necessary (or if forced)
  392. function updateEvents(forceRender) {
  393. if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
  394. refetchEvents();
  395. }
  396. else if (forceRender) {
  397. rerenderEvents();
  398. }
  399. }
  400. function refetchEvents() {
  401. fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
  402. }
  403. // called when event data arrives
  404. function reportEvents(_events) {
  405. events = _events;
  406. rerenderEvents();
  407. }
  408. // called when a single event's data has been changed
  409. function reportEventChange(eventID) {
  410. rerenderEvents(eventID);
  411. }
  412. // attempts to rerenderEvents
  413. function rerenderEvents(modifiedEventID) {
  414. markEventsDirty();
  415. if (elementVisible()) {
  416. currentView.clearEvents();
  417. currentView.renderEvents(events, modifiedEventID);
  418. currentView.eventsDirty = false;
  419. }
  420. }
  421. function markEventsDirty() {
  422. $.each(viewInstances, function(i, inst) {
  423. inst.eventsDirty = true;
  424. });
  425. }
  426. /* Selection
  427. -----------------------------------------------------------------------------*/
  428. function select(start, end, allDay) {
  429. currentView.select(start, end, allDay===undefined ? true : allDay);
  430. }
  431. function unselect() { // safe to be called before renderView
  432. if (currentView) {
  433. currentView.unselect();
  434. }
  435. }
  436. /* Date
  437. -----------------------------------------------------------------------------*/
  438. function prev() {
  439. renderView(-1);
  440. }
  441. function next() {
  442. renderView(1);
  443. }
  444. function prevYear() {
  445. addYears(date, -1);
  446. renderView();
  447. }
  448. function nextYear() {
  449. addYears(date, 1);
  450. renderView();
  451. }
  452. function today() {
  453. date = new Date();
  454. renderView();
  455. }
  456. function gotoDate(year, month, dateOfMonth) {
  457. if (year instanceof Date) {
  458. date = cloneDate(year); // provided 1 argument, a Date
  459. }else{
  460. setYMD(date, year, month, dateOfMonth);
  461. }
  462. renderView();
  463. }
  464. function incrementDate(years, months, days) {
  465. if (years !== undefined) {
  466. addYears(date, years);
  467. }
  468. if (months !== undefined) {
  469. addMonths(date, months);
  470. }
  471. if (days !== undefined) {
  472. addDays(date, days);
  473. }
  474. renderView();
  475. }
  476. function getDate() {
  477. return cloneDate(date);
  478. }
  479. /* Misc
  480. -----------------------------------------------------------------------------*/
  481. function getView() {
  482. return currentView;
  483. }
  484. function option(name, value) {
  485. if (value === undefined) {
  486. return options[name];
  487. }
  488. if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
  489. options[name] = value;
  490. updateSize();
  491. }
  492. }
  493. function trigger(name, thisObj) {
  494. if (options[name]) {
  495. return options[name].apply(
  496. thisObj || _element,
  497. Array.prototype.slice.call(arguments, 2)
  498. );
  499. }
  500. }
  501. /* External Dragging
  502. ------------------------------------------------------------------------*/
  503. if (options.droppable) {
  504. $(document)
  505. .bind('dragstart', function(ev, ui) {
  506. var _e = ev.target;
  507. var e = $(_e);
  508. if (!e.parents('.fc').length) { // not already inside a calendar
  509. var accept = options.dropAccept;
  510. if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
  511. _dragElement = _e;
  512. currentView.dragStart(_dragElement, ev, ui);
  513. }
  514. }
  515. })
  516. .bind('dragstop', function(ev, ui) {
  517. if (_dragElement) {
  518. currentView.dragStop(_dragElement, ev, ui);
  519. _dragElement = null;
  520. }
  521. });
  522. }
  523. }
  524. function Header(calendar, options) {
  525. var t = this;
  526. // exports
  527. t.render = render;
  528. t.destroy = destroy;
  529. t.updateTitle = updateTitle;
  530. t.activateButton = activateButton;
  531. t.deactivateButton = deactivateButton;
  532. t.disableButton = disableButton;
  533. t.enableButton = enableButton;
  534. // locals
  535. var element = $([]);
  536. var tm;
  537. function render() {
  538. tm = options.theme ? 'ui' : 'fc';
  539. var sections = options.header;
  540. if (sections) {
  541. element = $("<table class='fc-header' style='width:100%'/>")
  542. .append(
  543. $("<tr/>")
  544. .append(renderSection('left'))
  545. .append(renderSection('center'))
  546. .append(renderSection('right'))
  547. );
  548. return element;
  549. }
  550. }
  551. function destroy() {
  552. element.remove();
  553. }
  554. function renderSection(position) {
  555. var e = $("<td class='fc-header-" + position + "'/>");
  556. var buttonStr = options.header[position];
  557. if (buttonStr) {
  558. $.each(buttonStr.split(' '), function(i) {
  559. if (i > 0) {
  560. e.append("<span class='fc-header-space'/>");
  561. }
  562. var prevButton;
  563. $.each(this.split(','), function(j, buttonName) {
  564. if (buttonName == 'title') {
  565. e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
  566. if (prevButton) {
  567. prevButton.addClass(tm + '-corner-right');
  568. }
  569. prevButton = null;
  570. }else{
  571. var buttonClick;
  572. if (calendar[buttonName]) {
  573. buttonClick = calendar[buttonName]; // calendar method
  574. }
  575. else if (fcViews[buttonName]) {
  576. buttonClick = function() {
  577. button.removeClass(tm + '-state-hover'); // forget why
  578. calendar.changeView(buttonName);
  579. };
  580. }
  581. if (buttonClick) {
  582. var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
  583. var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
  584. var button = $(
  585. "<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
  586. "<span class='fc-button-inner'>" +
  587. "<span class='fc-button-content'>" +
  588. (icon ?
  589. "<span class='fc-icon-wrap'>" +
  590. "<span class='ui-icon ui-icon-" + icon + "'/>" +
  591. "</span>" :
  592. text
  593. ) +
  594. "</span>" +
  595. "<span class='fc-button-effect'><span></span></span>" +
  596. "</span>" +
  597. "</span>"
  598. );
  599. if (button) {
  600. button
  601. .click(function() {
  602. if (!button.hasClass(tm + '-state-disabled')) {
  603. buttonClick();
  604. }
  605. })
  606. .mousedown(function() {
  607. button
  608. .not('.' + tm + '-state-active')
  609. .not('.' + tm + '-state-disabled')
  610. .addClass(tm + '-state-down');
  611. })
  612. .mouseup(function() {
  613. button.removeClass(tm + '-state-down');
  614. })
  615. .hover(
  616. function() {
  617. button
  618. .not('.' + tm + '-state-active')
  619. .not('.' + tm + '-state-disabled')
  620. .addClass(tm + '-state-hover');
  621. },
  622. function() {
  623. button
  624. .removeClass(tm + '-state-hover')
  625. .removeClass(tm + '-state-down');
  626. }
  627. )
  628. .appendTo(e);
  629. if (!prevButton) {
  630. button.addClass(tm + '-corner-left');
  631. }
  632. prevButton = button;
  633. }
  634. }
  635. }
  636. });
  637. if (prevButton) {
  638. prevButton.addClass(tm + '-corner-right');
  639. }
  640. });
  641. }
  642. return e;
  643. }
  644. function updateTitle(html) {
  645. element.find('h2')
  646. .html(html);
  647. }
  648. function activateButton(buttonName) {
  649. element.find('span.fc-button-' + buttonName)
  650. .addClass(tm + '-state-active');
  651. }
  652. function deactivateButton(buttonName) {
  653. element.find('span.fc-button-' + buttonName)
  654. .removeClass(tm + '-state-active');
  655. }
  656. function disableButton(buttonName) {
  657. element.find('span.fc-button-' + buttonName)
  658. .addClass(tm + '-state-disabled');
  659. }
  660. function enableButton(buttonName) {
  661. element.find('span.fc-button-' + buttonName)
  662. .removeClass(tm + '-state-disabled');
  663. }
  664. }
  665. fc.sourceNormalizers = [];
  666. fc.sourceFetchers = [];
  667. var ajaxDefaults = {
  668. dataType: 'json',
  669. cache: false
  670. };
  671. var eventGUID = 1;
  672. function EventManager(options, _sources) {
  673. var t = this;
  674. // exports
  675. t.isFetchNeeded = isFetchNeeded;
  676. t.fetchEvents = fetchEvents;
  677. t.addEventSource = addEventSource;
  678. t.removeEventSource = removeEventSource;
  679. t.updateEvent = updateEvent;
  680. t.renderEvent = renderEvent;
  681. t.removeEvents = removeEvents;
  682. t.clientEvents = clientEvents;
  683. t.normalizeEvent = normalizeEvent;
  684. // imports
  685. var trigger = t.trigger;
  686. var getView = t.getView;
  687. var reportEvents = t.reportEvents;
  688. // locals
  689. var stickySource = { events: [] };
  690. var sources = [ stickySource ];
  691. var rangeStart, rangeEnd;
  692. var currentFetchID = 0;
  693. var pendingSourceCnt = 0;
  694. var loadingLevel = 0;
  695. var cache = [];
  696. for (var i=0; i<_sources.length; i++) {
  697. _addEventSource(_sources[i]);
  698. }
  699. /* Fetching
  700. -----------------------------------------------------------------------------*/
  701. function isFetchNeeded(start, end) {
  702. return !rangeStart || start < rangeStart || end > rangeEnd;
  703. }
  704. function fetchEvents(start, end) {
  705. rangeStart = start;
  706. rangeEnd = end;
  707. cache = [];
  708. var fetchID = ++currentFetchID;
  709. var len = sources.length;
  710. pendingSourceCnt = len;
  711. for (var i=0; i<len; i++) {
  712. fetchEventSource(sources[i], fetchID);
  713. }
  714. }
  715. function fetchEventSource(source, fetchID) {
  716. _fetchEventSource(source, function(events) {
  717. if (fetchID == currentFetchID) {
  718. if (events) {
  719. for (var i=0; i<events.length; i++) {
  720. events[i].source = source;
  721. normalizeEvent(events[i]);
  722. }
  723. cache = cache.concat(events);
  724. }
  725. pendingSourceCnt--;
  726. if (!pendingSourceCnt) {
  727. reportEvents(cache);
  728. }
  729. }
  730. });
  731. }
  732. function _fetchEventSource(source, callback) {
  733. var i;
  734. var fetchers = fc.sourceFetchers;
  735. var res;
  736. for (i=0; i<fetchers.length; i++) {
  737. res = fetchers[i](source, rangeStart, rangeEnd, callback);
  738. if (res === true) {
  739. // the fetcher is in charge. made its own async request
  740. return;
  741. }
  742. else if (typeof res == 'object') {
  743. // the fetcher returned a new source. process it
  744. _fetchEventSource(res, callback);
  745. return;
  746. }
  747. }
  748. var events = source.events;
  749. if (events) {
  750. if ($.isFunction(events)) {
  751. pushLoading();
  752. events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
  753. callback(events);
  754. popLoading();
  755. });
  756. }
  757. else if ($.isArray(events)) {
  758. callback(events);
  759. }
  760. else {
  761. callback();
  762. }
  763. }else{
  764. var url = source.url;
  765. if (url) {
  766. var success = source.success;
  767. var error = source.error;
  768. var complete = source.complete;
  769. var data = $.extend({}, source.data || {});
  770. var startParam = firstDefined(source.startParam, options.startParam);
  771. var endParam = firstDefined(source.endParam, options.endParam);
  772. if (startParam) {
  773. data[startParam] = Math.round(+rangeStart / 1000);
  774. }
  775. if (endParam) {
  776. data[endParam] = Math.round(+rangeEnd / 1000);
  777. }
  778. pushLoading();
  779. $.ajax($.extend({}, ajaxDefaults, source, {
  780. data: data,
  781. success: function(events) {
  782. events = events || [];
  783. var res = applyAll(success, this, arguments);
  784. if ($.isArray(res)) {
  785. events = res;
  786. }
  787. callback(events);
  788. },
  789. error: function() {
  790. applyAll(error, this, arguments);
  791. callback();
  792. },
  793. complete: function() {
  794. applyAll(complete, this, arguments);
  795. popLoading();
  796. }
  797. }));
  798. }else{
  799. callback();
  800. }
  801. }
  802. }
  803. /* Sources
  804. -----------------------------------------------------------------------------*/
  805. function addEventSource(source) {
  806. source = _addEventSource(source);
  807. if (source) {
  808. pendingSourceCnt++;
  809. fetchEventSource(source, currentFetchID); // will eventually call reportEvents
  810. }
  811. }
  812. function _addEventSource(source) {
  813. if ($.isFunction(source) || $.isArray(source)) {
  814. source = { events: source };
  815. }
  816. else if (typeof source == 'string') {
  817. source = { url: source };
  818. }
  819. if (typeof source == 'object') {
  820. normalizeSource(source);
  821. sources.push(source);
  822. return source;
  823. }
  824. }
  825. function removeEventSource(source) {
  826. sources = $.grep(sources, function(src) {
  827. return !isSourcesEqual(src, source);
  828. });
  829. // remove all client events from that source
  830. cache = $.grep(cache, function(e) {
  831. return !isSourcesEqual(e.source, source);
  832. });
  833. reportEvents(cache);
  834. }
  835. /* Manipulation
  836. -----------------------------------------------------------------------------*/
  837. function updateEvent(event) { // update an existing event
  838. var i, len = cache.length, e,
  839. defaultEventEnd = getView().defaultEventEnd, // getView???
  840. startDelta = event.start - event._start,
  841. endDelta = event.end ?
  842. (event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
  843. : 0; // was null and event was just resized
  844. for (i=0; i<len; i++) {
  845. e = cache[i];
  846. if (e._id == event._id && e != event) {
  847. e.start = new Date(+e.start + startDelta);
  848. if (event.end) {
  849. if (e.end) {
  850. e.end = new Date(+e.end + endDelta);
  851. }else{
  852. e.end = new Date(+defaultEventEnd(e) + endDelta);
  853. }
  854. }else{
  855. e.end = null;
  856. }
  857. e.title = event.title;
  858. e.url = event.url;
  859. e.allDay = event.allDay;
  860. e.className = event.className;
  861. e.editable = event.editable;
  862. e.color = event.color;
  863. e.backgroudColor = event.backgroudColor;
  864. e.borderColor = event.borderColor;
  865. e.textColor = event.textColor;
  866. normalizeEvent(e);
  867. }
  868. }
  869. normalizeEvent(event);
  870. reportEvents(cache);
  871. }
  872. function renderEvent(event, stick) {
  873. normalizeEvent(event);
  874. if (!event.source) {
  875. if (stick) {
  876. stickySource.events.push(event);
  877. event.source = stickySource;
  878. }
  879. cache.push(event);
  880. }
  881. reportEvents(cache);
  882. }
  883. function removeEvents(filter) {
  884. if (!filter) { // remove all
  885. cache = [];
  886. // clear all array sources
  887. for (var i=0; i<sources.length; i++) {
  888. if ($.isArray(sources[i].events)) {
  889. sources[i].events = [];
  890. }
  891. }
  892. }else{
  893. if (!$.isFunction(filter)) { // an event ID
  894. var id = filter + '';
  895. filter = function(e) {
  896. return e._id == id;
  897. };
  898. }
  899. cache = $.grep(cache, filter, true);
  900. // remove events from array sources
  901. for (var i=0; i<sources.length; i++) {
  902. if ($.isArray(sources[i].events)) {
  903. sources[i].events = $.grep(sources[i].events, filter, true);
  904. }
  905. }
  906. }
  907. reportEvents(cache);
  908. }
  909. function clientEvents(filter) {
  910. if ($.isFunction(filter)) {
  911. return $.grep(cache, filter);
  912. }
  913. else if (filter) { // an event ID
  914. filter += '';
  915. return $.grep(cache, function(e) {
  916. return e._id == filter;
  917. });
  918. }
  919. return cache; // else, return all
  920. }
  921. /* Loading State
  922. -----------------------------------------------------------------------------*/
  923. function pushLoading() {
  924. if (!loadingLevel++) {
  925. trigger('loading', null, true);
  926. }
  927. }
  928. function popLoading() {
  929. if (!--loadingLevel) {
  930. trigger('loading', null, false);
  931. }
  932. }
  933. /* Event Normalization
  934. -----------------------------------------------------------------------------*/
  935. function normalizeEvent(event) {
  936. var source = event.source || {};
  937. var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
  938. event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
  939. if (event.date) {
  940. if (!event.start) {
  941. event.start = event.date;
  942. }
  943. delete event.date;
  944. }
  945. event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
  946. event.end = parseDate(event.end, ignoreTimezone);
  947. if (event.end && event.end <= event.start) {
  948. event.end = null;
  949. }
  950. event._end = event.end ? cloneDate(event.end) : null;
  951. if (event.allDay === undefined) {
  952. event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
  953. }
  954. if (event.className) {
  955. if (typeof event.className == 'string') {
  956. event.className = event.className.split(/\s+/);
  957. }
  958. }else{
  959. event.className = [];
  960. }
  961. // TODO: if there is no start date, return false to indicate an invalid event
  962. }
  963. /* Utils
  964. ------------------------------------------------------------------------------*/
  965. function normalizeSource(source) {
  966. if (source.className) {
  967. // TODO: repeat code, same code for event classNames
  968. if (typeof source.className == 'string') {
  969. source.className = source.className.split(/\s+/);
  970. }
  971. }else{
  972. source.className = [];
  973. }
  974. var normalizers = fc.sourceNormalizers;
  975. for (var i=0; i<normalizers.length; i++) {
  976. normalizers[i](source);
  977. }
  978. }
  979. function isSourcesEqual(source1, source2) {
  980. return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
  981. }
  982. function getSourcePrimitive(source) {
  983. return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
  984. }
  985. }
  986. fc.addDays = addDays;
  987. fc.cloneDate = cloneDate;
  988. fc.parseDate = parseDate;
  989. fc.parseISO8601 = parseISO8601;
  990. fc.parseTime = parseTime;
  991. fc.formatDate = formatDate;
  992. fc.formatDates = formatDates;
  993. /* Date Math
  994. -----------------------------------------------------------------------------*/
  995. var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
  996. DAY_MS = 86400000,
  997. HOUR_MS = 3600000,
  998. MINUTE_MS = 60000;
  999. function addYears(d, n, keepTime) {
  1000. d.setFullYear(d.getFullYear() + n);
  1001. if (!keepTime) {
  1002. clearTime(d);
  1003. }
  1004. return d;
  1005. }
  1006. function addMonths(d, n, keepTime) { // prevents day overflow/underflow
  1007. if (+d) { // prevent infinite looping on invalid dates
  1008. var m = d.getMonth() + n,
  1009. check = cloneDate(d);
  1010. check.setDate(1);
  1011. check.setMonth(m);
  1012. d.setMonth(m);
  1013. if (!keepTime) {
  1014. clearTime(d);
  1015. }
  1016. while (d.getMonth() != check.getMonth()) {
  1017. d.setDate(d.getDate() + (d < check ? 1 : -1));
  1018. }
  1019. }
  1020. return d;
  1021. }
  1022. function addDays(d, n, keepTime) { // deals with daylight savings
  1023. if (+d) {
  1024. var dd = d.getDate() + n,
  1025. check = cloneDate(d);
  1026. check.setHours(9); // set to middle of day
  1027. check.setDate(dd);
  1028. d.setDate(dd);
  1029. if (!keepTime) {
  1030. clearTime(d);
  1031. }
  1032. fixDate(d, check);
  1033. }
  1034. return d;
  1035. }
  1036. function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
  1037. if (+d) { // prevent infinite looping on invalid dates
  1038. while (d.getDate() != check.getDate()) {
  1039. d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
  1040. }
  1041. }
  1042. }
  1043. function addMinutes(d, n) {
  1044. d.setMinutes(d.getMinutes() + n);
  1045. return d;
  1046. }
  1047. function clearTime(d) {
  1048. d.setHours(0);
  1049. d.setMinutes(0);
  1050. d.setSeconds(0);
  1051. d.setMilliseconds(0);
  1052. return d;
  1053. }
  1054. function cloneDate(d, dontKeepTime) {
  1055. if (dontKeepTime) {
  1056. return clearTime(new Date(+d));
  1057. }
  1058. return new Date(+d);
  1059. }
  1060. function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
  1061. var i=0, d;
  1062. do {
  1063. d = new Date(1970, i++, 1);
  1064. } while (d.getHours()); // != 0
  1065. return d;
  1066. }
  1067. function skipWeekend(date, inc, excl) {
  1068. inc = inc || 1;
  1069. while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
  1070. addDays(date, inc);
  1071. }
  1072. return date;
  1073. }
  1074. function dayDiff(d1, d2) { // d1 - d2
  1075. return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
  1076. }
  1077. function setYMD(date, y, m, d) {
  1078. if (y !== undefined && y != date.getFullYear()) {
  1079. date.setDate(1);
  1080. date.setMonth(0);
  1081. date.setFullYear(y);
  1082. }
  1083. if (m !== undefined && m != date.getMonth()) {
  1084. date.setDate(1);
  1085. date.setMonth(m);
  1086. }
  1087. if (d !== undefined) {
  1088. date.setDate(d);
  1089. }
  1090. }
  1091. /* Date Parsing
  1092. -----------------------------------------------------------------------------*/
  1093. function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
  1094. if (typeof s == 'object') { // already a Date object
  1095. return s;
  1096. }
  1097. if (typeof s == 'number') { // a UNIX timestamp
  1098. return new Date(s * 1000);
  1099. }
  1100. if (typeof s == 'string') {
  1101. if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
  1102. return new Date(parseFloat(s) * 1000);
  1103. }
  1104. if (ignoreTimezone === undefined) {
  1105. ignoreTimezone = true;
  1106. }
  1107. return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
  1108. }
  1109. // TODO: never return invalid dates (like from new Date(<string>)), return null instead
  1110. return null;
  1111. }
  1112. function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
  1113. // derived from http://delete.me.uk/2005/03/iso8601.html
  1114. // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
  1115. 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}))?))?)?)?)?$/);
  1116. if (!m) {
  1117. return null;
  1118. }
  1119. var date = new Date(m[1], 0, 1);
  1120. if (ignoreTimezone || !m[13]) {
  1121. var check = new Date(m[1], 0, 1, 9, 0);
  1122. if (m[3]) {
  1123. date.setMonth(m[3] - 1);
  1124. check.setMonth(m[3] - 1);
  1125. }
  1126. if (m[5]) {
  1127. date.setDate(m[5]);
  1128. check.setDate(m[5]);
  1129. }
  1130. fixDate(date, check);
  1131. if (m[7]) {
  1132. date.setHours(m[7]);
  1133. }
  1134. if (m[8]) {
  1135. date.setMinutes(m[8]);
  1136. }
  1137. if (m[10]) {
  1138. date.setSeconds(m[10]);
  1139. }
  1140. if (m[12]) {
  1141. date.setMilliseconds(Number("0." + m[12]) * 1000);
  1142. }
  1143. fixDate(date, check);
  1144. }else{
  1145. date.setUTCFullYear(
  1146. m[1],
  1147. m[3] ? m[3] - 1 : 0,
  1148. m[5] || 1
  1149. );
  1150. date.setUTCHours(
  1151. m[7] || 0,
  1152. m[8] || 0,
  1153. m[10] || 0,
  1154. m[12] ? Number("0." + m[12]) * 1000 : 0
  1155. );
  1156. if (m[14]) {
  1157. var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
  1158. offset *= m[15] == '-' ? 1 : -1;
  1159. date = new Date(+date + (offset * 60 * 1000));
  1160. }
  1161. }
  1162. return date;
  1163. }
  1164. function parseTime(s) { // returns minutes since start of day
  1165. if (typeof s == 'number') { // an hour
  1166. return s * 60;
  1167. }
  1168. if (typeof s == 'object') { // a Date object
  1169. return s.getHours() * 60 + s.getMinutes();
  1170. }
  1171. var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
  1172. if (m) {
  1173. var h = parseInt(m[1], 10);
  1174. if (m[3]) {
  1175. h %= 12;
  1176. if (m[3].toLowerCase().charAt(0) == 'p') {
  1177. h += 12;
  1178. }
  1179. }
  1180. return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
  1181. }
  1182. }
  1183. /* Date Formatting
  1184. -----------------------------------------------------------------------------*/
  1185. // TODO: use same function formatDate(date, [date2], format, [options])
  1186. function formatDate(date, format, options) {
  1187. return formatDates(date, null, format, options);
  1188. }
  1189. function formatDates(date1, date2, format, options) {
  1190. options = options || defaults;
  1191. var date = date1,
  1192. otherDate = date2,
  1193. i, len = format.length, c,
  1194. i2, formatter,
  1195. res = '';
  1196. for (i=0; i<len; i++) {
  1197. c = format.charAt(i);
  1198. if (c == "'") {
  1199. for (i2=i+1; i2<len; i2++) {
  1200. if (format.charAt(i2) == "'") {
  1201. if (date) {
  1202. if (i2 == i+1) {
  1203. res += "'";
  1204. }else{
  1205. res += format.substring(i+1, i2);
  1206. }
  1207. i = i2;
  1208. }
  1209. break;
  1210. }
  1211. }
  1212. }
  1213. else if (c == '(') {
  1214. for (i2=i+1; i2<len; i2++) {
  1215. if (format.charAt(i2) == ')') {
  1216. var subres = formatDate(date, format.substring(i+1, i2), options);
  1217. if (parseInt(subres.replace(/\D/, ''), 10)) {
  1218. res += subres;
  1219. }
  1220. i = i2;
  1221. break;
  1222. }
  1223. }
  1224. }
  1225. else if (c == '[') {
  1226. for (i2=i+1; i2<len; i2++) {
  1227. if (format.charAt(i2) == ']') {
  1228. var subformat = format.substring(i+1, i2);
  1229. var subres = formatDate(date, subformat, options);
  1230. if (subres != formatDate(otherDate, subformat, options)) {
  1231. res += subres;
  1232. }
  1233. i = i2;
  1234. break;
  1235. }
  1236. }
  1237. }
  1238. else if (c == '{') {
  1239. date = date2;
  1240. otherDate = date1;
  1241. }
  1242. else if (c == '}') {
  1243. date = date1;
  1244. otherDate = date2;
  1245. }
  1246. else {
  1247. for (i2=len; i2>i; i2--) {
  1248. if (formatter = dateFormatters[format.substring(i, i2)]) {
  1249. if (date) {
  1250. res += formatter(date, options);
  1251. }
  1252. i = i2 - 1;
  1253. break;
  1254. }
  1255. }
  1256. if (i2 == i) {
  1257. if (date) {
  1258. res += c;
  1259. }
  1260. }
  1261. }
  1262. }
  1263. return res;
  1264. };
  1265. var dateFormatters = {
  1266. s : function(d) { return d.getSeconds() },
  1267. ss : function(d) { return zeroPad(d.getSeconds()) },
  1268. m : function(d) { return d.getMinutes() },
  1269. mm : function(d) { return zeroPad(d.getMinutes()) },
  1270. h : function(d) { return d.getHours() % 12 || 12 },
  1271. hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
  1272. H : function(d) { return d.getHours() },
  1273. HH : function(d) { return zeroPad(d.getHours()) },
  1274. d : function(d) { return d.getDate() },
  1275. dd : function(d) { return zeroPad(d.getDate()) },
  1276. ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },
  1277. dddd: function(d,o) { return o.dayNames[d.getDay()] },
  1278. M : function(d) { return d.getMonth() + 1 },
  1279. MM : function(d) { return zeroPad(d.getMonth() + 1) },
  1280. MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },
  1281. MMMM: function(d,o) { return o.monthNames[d.getMonth()] },
  1282. yy : function(d) { return (d.getFullYear()+'').substring(2) },
  1283. yyyy: function(d) { return d.getFullYear() },
  1284. t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },
  1285. tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
  1286. T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },
  1287. TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
  1288. u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
  1289. S : function(d) {
  1290. var date = d.getDate();
  1291. if (date > 10 && date < 20) {
  1292. return 'th';
  1293. }
  1294. return ['st', 'nd', 'rd'][date%10-1] || 'th';
  1295. }
  1296. };
  1297. fc.applyAll = applyAll;
  1298. /* Event Date Math
  1299. -----------------------------------------------------------------------------*/
  1300. function exclEndDay(event) {
  1301. if (event.end) {
  1302. return _exclEndDay(event.end, event.allDay);
  1303. }else{
  1304. return addDays(cloneDate(event.start), 1);
  1305. }
  1306. }
  1307. function _exclEndDay(end, allDay) {
  1308. end = cloneDate(end);
  1309. return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
  1310. }
  1311. function segCmp(a, b) {
  1312. return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
  1313. }
  1314. function segsCollide(seg1, seg2) {
  1315. return seg1.end > seg2.start && seg1.start < seg2.end;
  1316. }
  1317. /* Event Sorting
  1318. -----------------------------------------------------------------------------*/
  1319. // event rendering utilities
  1320. function sliceSegs(events, visEventEnds, start, end) {
  1321. var segs = [],
  1322. i, len=events.length, event,
  1323. eventStart, eventEnd,
  1324. segStart, segEnd,
  1325. isStart, isEnd;
  1326. for (i=0; i<len; i++) {
  1327. event = events[i];
  1328. eventStart = event.start;
  1329. eventEnd = visEventEnds[i];
  1330. if (eventEnd > start && eventStart < end) {
  1331. if (eventStart < start) {
  1332. segStart = cloneDate(start);
  1333. isStart = false;
  1334. }else{
  1335. segStart = eventStart;
  1336. isStart = true;
  1337. }
  1338. if (eventEnd > end) {
  1339. segEnd = cloneDate(end);
  1340. isEnd = false;
  1341. }else{
  1342. segEnd = eventEnd;
  1343. isEnd = true;
  1344. }
  1345. segs.push({
  1346. event: event,
  1347. start: segStart,
  1348. end: segEnd,
  1349. isStart: isStart,
  1350. isEnd: isEnd,
  1351. msLength: segEnd - segStart
  1352. });
  1353. }
  1354. }
  1355. return segs.sort(segCmp);
  1356. }
  1357. // event rendering calculation utilities
  1358. function stackSegs(segs) {
  1359. var levels = [],
  1360. i, len = segs.length, seg,
  1361. j, collide, k;
  1362. for (i=0; i<len; i++) {
  1363. seg = segs[i];
  1364. j = 0; // the level index where seg should belong
  1365. while (true) {
  1366. collide = false;
  1367. if (levels[j]) {
  1368. for (k=0; k<levels[j].length; k++) {
  1369. if (segsCollide(levels[j][k], seg)) {
  1370. collide = true;
  1371. break;
  1372. }
  1373. }
  1374. }
  1375. if (collide) {
  1376. j++;
  1377. }else{
  1378. break;
  1379. }
  1380. }
  1381. if (levels[j]) {
  1382. levels[j].push(seg);
  1383. }else{
  1384. levels[j] = [seg];
  1385. }
  1386. }
  1387. return levels;
  1388. }
  1389. /* Event Element Binding
  1390. -----------------------------------------------------------------------------*/
  1391. function lazySegBind(container, segs, bindHandlers) {
  1392. container.unbind('mouseover').mouseover(function(ev) {
  1393. var parent=ev.target, e,
  1394. i, seg;
  1395. while (parent != this) {
  1396. e = parent;
  1397. parent = parent.parentNode;
  1398. }
  1399. if ((i = e._fci) !== undefined) {
  1400. e._fci = undefined;
  1401. seg = segs[i];
  1402. bindHandlers(seg.event, seg.element, seg);
  1403. $(ev.target).trigger(ev);
  1404. }
  1405. ev.stopPropagation();
  1406. });
  1407. }
  1408. /* Element Dimensions
  1409. -----------------------------------------------------------------------------*/
  1410. function setOuterWidth(element, width, includeMargins) {
  1411. for (var i=0, e; i<element.length; i++) {
  1412. e = $(element[i]);
  1413. e.width(Math.max(0, width - hsides(e, includeMargins)));
  1414. }
  1415. }
  1416. function setOuterHeight(element, height, includeMargins) {
  1417. for (var i=0, e; i<element.length; i++) {
  1418. e = $(element[i]);
  1419. e.height(Math.max(0, height - vsides(e, includeMargins)));
  1420. }
  1421. }
  1422. // TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
  1423. function hsides(element, includeMargins) {
  1424. return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
  1425. }
  1426. function hpadding(element) {
  1427. return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
  1428. (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
  1429. }
  1430. function hmargins(element) {
  1431. return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
  1432. (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
  1433. }
  1434. function hborders(element) {
  1435. return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0) +
  1436. (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) || 0);
  1437. }
  1438. function vsides(element, includeMargins) {
  1439. return vpadding(element) + vborders(element) + (includeMargins ? vmargins(element) : 0);
  1440. }
  1441. function vpadding(element) {
  1442. return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
  1443. (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
  1444. }
  1445. function vmargins(element) {
  1446. return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
  1447. (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
  1448. }
  1449. function vborders(element) {
  1450. return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
  1451. (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) || 0);
  1452. }
  1453. function setMinHeight(element, height) {
  1454. height = (typeof height == 'number' ? height + 'px' : height);
  1455. element.each(function(i, _element) {
  1456. _element.style.cssText += ';min-height:' + height + ';_height:' + height;
  1457. // why can't we just use .css() ? i forget
  1458. });
  1459. }
  1460. /* Misc Utils
  1461. -----------------------------------------------------------------------------*/
  1462. //TODO: arraySlice
  1463. //TODO: isFunction, grep ?
  1464. function noop() { }
  1465. function cmp(a, b) {
  1466. return a - b;
  1467. }
  1468. function arrayMax(a) {
  1469. return Math.max.apply(Math, a);
  1470. }
  1471. function zeroPad(n) {
  1472. return (n < 10 ? '0' : '') + n;
  1473. }
  1474. function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
  1475. if (obj[name] !== undefined) {
  1476. return obj[name];
  1477. }
  1478. var parts = name.split(/(?=[A-Z])/),
  1479. i=parts.length-1, res;
  1480. for (; i>=0; i--) {
  1481. res = obj[parts[i].toLowerCase()];
  1482. if (res !== undefined) {
  1483. return res;
  1484. }
  1485. }
  1486. return obj[''];
  1487. }
  1488. function htmlEscape(s) {
  1489. return s.replace(/&/g, '&amp;')
  1490. .replace(/</g, '&lt;')
  1491. .replace(/>/g, '&gt;')
  1492. .replace(/'/g, '&#039;')
  1493. .replace(/"/g, '&quot;')
  1494. .replace(/\n/g, '<br />');
  1495. }
  1496. function cssKey(_element) {
  1497. return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
  1498. }
  1499. function disableTextSelection(element) {
  1500. element
  1501. .attr('unselectable', 'on')
  1502. .css('MozUserSelect', 'none')
  1503. .bind('selectstart.ui', function() { return false; });
  1504. }
  1505. /*
  1506. function enableTextSelection(element) {
  1507. element
  1508. .attr('unselectable', 'off')
  1509. .css('MozUserSelect', '')
  1510. .unbind('selectstart.ui');
  1511. }
  1512. */
  1513. function markFirstLast(e) {
  1514. e.children()
  1515. .removeClass('fc-first fc-last')
  1516. .filter(':first-child')
  1517. .addClass('fc-first')
  1518. .end()
  1519. .filter(':last-child')
  1520. .addClass('fc-last');
  1521. }
  1522. function setDayID(cell, date) {
  1523. cell.each(function(i, _cell) {
  1524. _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
  1525. // TODO: make a way that doesn't rely on order of classes
  1526. });
  1527. }
  1528. function getSkinCss(event, opt) {
  1529. var source = event.source || {};
  1530. var eventColor = event.color;
  1531. var sourceColor = source.color;
  1532. var optionColor = opt('eventColor');
  1533. var backgroundColor =
  1534. event.backgroundColor ||
  1535. eventColor ||
  1536. source.backgroundColor ||
  1537. sourceColor ||
  1538. opt('eventBackgroundColor') ||
  1539. optionColor;
  1540. var borderColor =
  1541. event.borderColor ||
  1542. eventColor ||
  1543. source.borderColor ||
  1544. sourceColor ||
  1545. opt('eventBorderColor') ||
  1546. optionColor;
  1547. var textColor =
  1548. event.textColor ||
  1549. source.textColor ||
  1550. opt('eventTextColor');
  1551. var statements = [];
  1552. if (backgroundColor) {
  1553. statements.push('background-color:' + backgroundColor);
  1554. }
  1555. if (borderColor) {
  1556. statements.push('border-color:' + borderColor);
  1557. }
  1558. if (textColor) {
  1559. statements.push('color:' + textColor);
  1560. }
  1561. return statements.join(';');
  1562. }
  1563. function applyAll(functions, thisObj, args) {
  1564. if ($.isFunction(functions)) {
  1565. functions = [ functions ];
  1566. }
  1567. if (functions) {
  1568. var i;
  1569. var ret;
  1570. for (i=0; i<functions.length; i++) {
  1571. ret = functions[i].apply(thisObj, args) || ret;
  1572. }
  1573. return ret;
  1574. }
  1575. }
  1576. function firstDefined() {
  1577. for (var i=0; i<arguments.length; i++) {
  1578. if (arguments[i] !== undefined) {
  1579. return arguments[i];
  1580. }
  1581. }
  1582. }
  1583. fcViews.month = MonthView;
  1584. function MonthView(element, calendar) {
  1585. var t = this;
  1586. // exports
  1587. t.render = render;
  1588. // imports
  1589. BasicView.call(t, element, calendar, 'month');
  1590. var opt = t.opt;
  1591. var renderBasic = t.renderBasic;
  1592. var formatDate = calendar.formatDate;
  1593. function render(date, delta) {
  1594. if (delta) {
  1595. addMonths(date, delta);
  1596. date.setDate(1);
  1597. }
  1598. var start = cloneDate(date, true);
  1599. start.setDate(1);
  1600. var end = addMonths(cloneDate(start), 1);
  1601. var visStart = cloneDate(start);
  1602. var visEnd = cloneDate(end);
  1603. var firstDay = opt('firstDay');
  1604. var nwe = opt('weekends') ? 0 : 1;
  1605. if (nwe) {
  1606. skipWeekend(visStart);
  1607. skipWeekend(visEnd, -1, true);
  1608. }
  1609. addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
  1610. addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
  1611. var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
  1612. if (opt('weekMode') == 'fixed') {
  1613. addDays(visEnd, (6 - rowCnt) * 7);
  1614. rowCnt = 6;
  1615. }
  1616. t.title = formatDate(start, opt('titleFormat'));
  1617. t.start = start;
  1618. t.end = end;
  1619. t.visStart = visStart;
  1620. t.visEnd = visEnd;
  1621. renderBasic(6, rowCnt, nwe ? 5 : 7, true);
  1622. }
  1623. }
  1624. fcViews.basicWeek = BasicWeekView;
  1625. function BasicWeekView(element, calendar) {
  1626. var t = this;
  1627. // exports
  1628. t.render = render;
  1629. // imports
  1630. BasicView.call(t, element, calendar, 'basicWeek');
  1631. var opt = t.opt;
  1632. var renderBasic = t.renderBasic;
  1633. var formatDates = calendar.formatDates;
  1634. function render(date, delta) {
  1635. if (delta) {
  1636. addDays(date, delta * 7);
  1637. }
  1638. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1639. var end = addDays(cloneDate(start), 7);
  1640. var visStart = cloneDate(start);
  1641. var visEnd = cloneDate(end);
  1642. var weekends = opt('weekends');
  1643. if (!weekends) {
  1644. skipWeekend(visStart);
  1645. skipWeekend(visEnd, -1, true);
  1646. }
  1647. t.title = formatDates(
  1648. visStart,
  1649. addDays(cloneDate(visEnd), -1),
  1650. opt('titleFormat')
  1651. );
  1652. t.start = start;
  1653. t.end = end;
  1654. t.visStart = visStart;
  1655. t.visEnd = visEnd;
  1656. renderBasic(1, 1, weekends ? 7 : 5, false);
  1657. }
  1658. }
  1659. fcViews.basicDay = BasicDayView;
  1660. //TODO: when calendar's date starts out on a weekend, shouldn't happen
  1661. function BasicDayView(element, calendar) {
  1662. var t = this;
  1663. // exports
  1664. t.render = render;
  1665. // imports
  1666. BasicView.call(t, element, calendar, 'basicDay');
  1667. var opt = t.opt;
  1668. var renderBasic = t.renderBasic;
  1669. var formatDate = calendar.formatDate;
  1670. function render(date, delta) {
  1671. if (delta) {
  1672. addDays(date, delta);
  1673. if (!opt('weekends')) {
  1674. skipWeekend(date, delta < 0 ? -1 : 1);
  1675. }
  1676. }
  1677. t.title = formatDate(date, opt('titleFormat'));
  1678. t.start = t.visStart = cloneDate(date, true);
  1679. t.end = t.visEnd = addDays(cloneDate(t.start), 1);
  1680. renderBasic(1, 1, 1, false);
  1681. }
  1682. }
  1683. setDefaults({
  1684. weekMode: 'fixed'
  1685. });
  1686. function BasicView(element, calendar, viewName) {
  1687. var t = this;
  1688. // exports
  1689. t.renderBasic = renderBasic;
  1690. t.setHeight = setHeight;
  1691. t.setWidth = setWidth;
  1692. t.renderDayOverlay = renderDayOverlay;
  1693. t.defaultSelectionEnd = defaultSelectionEnd;
  1694. t.renderSelection = renderSelection;
  1695. t.clearSelection = clearSelection;
  1696. t.reportDayClick = reportDayClick; // for selection (kinda hacky)
  1697. t.dragStart = dragStart;
  1698. t.dragStop = dragStop;
  1699. t.defaultEventEnd = defaultEventEnd;
  1700. t.getHoverListener = function() { return hoverListener };
  1701. t.colContentLeft = colContentLeft;
  1702. t.colContentRight = colContentRight;
  1703. t.dayOfWeekCol = dayOfWeekCol;
  1704. t.dateCell = dateCell;
  1705. t.cellDate = cellDate;
  1706. t.cellIsAllDay = function() { return true };
  1707. t.allDayRow = allDayRow;
  1708. t.allDayBounds = allDayBounds;
  1709. t.getRowCnt = function() { return rowCnt };
  1710. t.getColCnt = function() { return colCnt };
  1711. t.getColWidth = function() { return colWidth };
  1712. t.getDaySegmentContainer = function() { return daySegmentContainer };
  1713. // imports
  1714. View.call(t, element, calendar, viewName);
  1715. OverlayManager.call(t);
  1716. SelectionManager.call(t);
  1717. BasicEventRenderer.call(t);
  1718. var opt = t.opt;
  1719. var trigger = t.trigger;
  1720. var clearEvents = t.clearEvents;
  1721. var renderOverlay = t.renderOverlay;
  1722. var clearOverlays = t.clearOverlays;
  1723. var daySelectionMousedown = t.daySelectionMousedown;
  1724. var formatDate = calendar.formatDate;
  1725. // locals
  1726. var head;
  1727. var headCells;
  1728. var body;
  1729. var bodyRows;
  1730. var bodyCells;
  1731. var bodyFirstCells;
  1732. var bodyCellTopInners;
  1733. var daySegmentContainer;
  1734. var viewWidth;
  1735. var viewHeight;
  1736. var colWidth;
  1737. var rowCnt, colCnt;
  1738. var coordinateGrid;
  1739. var hoverListener;
  1740. var colContentPositions;
  1741. var rtl, dis, dit;
  1742. var firstDay;
  1743. var nwe;
  1744. var tm;
  1745. var colFormat;
  1746. /* Rendering
  1747. ------------------------------------------------------------*/
  1748. disableTextSelection(element.addClass('fc-grid'));
  1749. function renderBasic(maxr, r, c, showNumbers) {
  1750. rowCnt = r;
  1751. colCnt = c;
  1752. updateOptions();
  1753. var firstTime = !body;
  1754. if (firstTime) {
  1755. buildSkeleton(maxr, showNumbers);
  1756. }else{
  1757. clearEvents();
  1758. }
  1759. updateCells(firstTime);
  1760. }
  1761. function updateOptions() {
  1762. rtl = opt('isRTL');
  1763. if (rtl) {
  1764. dis = -1;
  1765. dit = colCnt - 1;
  1766. }else{
  1767. dis = 1;
  1768. dit = 0;
  1769. }
  1770. firstDay = opt('firstDay');
  1771. nwe = opt('weekends') ? 0 : 1;
  1772. tm = opt('theme') ? 'ui' : 'fc';
  1773. colFormat = opt('columnFormat');
  1774. }
  1775. function buildSkeleton(maxRowCnt, showNumbers) {
  1776. var s;
  1777. var headerClass = tm + "-widget-header";
  1778. var contentClass = tm + "-widget-content";
  1779. var i, j;
  1780. var table;
  1781. s =
  1782. "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  1783. "<thead>" +
  1784. "<tr>";
  1785. for (i=0; i<colCnt; i++) {
  1786. s +=
  1787. "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
  1788. }
  1789. s +=
  1790. "</tr>" +
  1791. "</thead>" +
  1792. "<tbody>";
  1793. for (i=0; i<maxRowCnt; i++) {
  1794. s +=
  1795. "<tr class='fc-week" + i + "'>";
  1796. for (j=0; j<colCnt; j++) {
  1797. s +=
  1798. "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
  1799. "<div>" +
  1800. (showNumbers ?
  1801. "<div class='fc-day-number'/>" :
  1802. ''
  1803. ) +
  1804. "<div class='fc-day-content'>" +
  1805. "<div style='position:relative'>&nbsp;</div>" +
  1806. "</div>" +
  1807. "</div>" +
  1808. "</td>";
  1809. }
  1810. s +=
  1811. "</tr>";
  1812. }
  1813. s +=
  1814. "</tbody>" +
  1815. "</table>";
  1816. table = $(s).appendTo(element);
  1817. head = table.find('thead');
  1818. headCells = head.find('th');
  1819. body = table.find('tbody');
  1820. bodyRows = body.find('tr');
  1821. bodyCells = body.find('td');
  1822. bodyFirstCells = bodyCells.filter(':first-child');
  1823. bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
  1824. markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
  1825. markFirstLast(bodyRows); // marks first+last td's
  1826. bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
  1827. dayBind(bodyCells);
  1828. daySegmentContainer =
  1829. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  1830. .appendTo(element);
  1831. }
  1832. function updateCells(firstTime) {
  1833. var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
  1834. var month = t.start.getMonth();
  1835. var today = clearTime(new Date());
  1836. var cell;
  1837. var date;
  1838. var row;
  1839. if (dowDirty) {
  1840. headCells.each(function(i, _cell) {
  1841. cell = $(_cell);
  1842. date = indexDate(i);
  1843. cell.html(formatDate(date, colFormat));
  1844. setDayID(cell, date);
  1845. });
  1846. }
  1847. bodyCells.each(function(i, _cell) {
  1848. cell = $(_cell);
  1849. date = indexDate(i);
  1850. if (date.getMonth() == month) {
  1851. cell.removeClass('fc-other-month');
  1852. }else{
  1853. cell.addClass('fc-other-month');
  1854. }
  1855. if (+date == +today) {
  1856. cell.addClass(tm + '-state-highlight fc-today');
  1857. }else{
  1858. cell.removeClass(tm + '-state-highlight fc-today');
  1859. }
  1860. cell.find('div.fc-day-number').text(date.getDate());
  1861. if (dowDirty) {
  1862. setDayID(cell, date);
  1863. }
  1864. });
  1865. bodyRows.each(function(i, _row) {
  1866. row = $(_row);
  1867. if (i < rowCnt) {
  1868. row.show();
  1869. if (i == rowCnt-1) {
  1870. row.addClass('fc-last');
  1871. }else{
  1872. row.removeClass('fc-last');
  1873. }
  1874. }else{
  1875. row.hide();
  1876. }
  1877. });
  1878. }
  1879. function setHeight(height) {
  1880. viewHeight = height;
  1881. var bodyHeight = viewHeight - head.height();
  1882. var rowHeight;
  1883. var rowHeightLast;
  1884. var cell;
  1885. if (opt('weekMode') == 'variable') {
  1886. rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
  1887. }else{
  1888. rowHeight = Math.floor(bodyHeight / rowCnt);
  1889. rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
  1890. }
  1891. bodyFirstCells.each(function(i, _cell) {
  1892. if (i < rowCnt) {
  1893. cell = $(_cell);
  1894. setMinHeight(
  1895. cell.find('> div'),
  1896. (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
  1897. );
  1898. }
  1899. });
  1900. }
  1901. function setWidth(width) {
  1902. viewWidth = width;
  1903. colContentPositions.clear();
  1904. colWidth = Math.floor(viewWidth / colCnt);
  1905. setOuterWidth(headCells.slice(0, -1), colWidth);
  1906. }
  1907. /* Day clicking and binding
  1908. -----------------------------------------------------------*/
  1909. function dayBind(days) {
  1910. days.click(dayClick)
  1911. .mousedown(daySelectionMousedown);
  1912. }
  1913. function dayClick(ev) {
  1914. if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  1915. var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
  1916. var date = indexDate(index);
  1917. trigger('dayClick', this, date, true, ev);
  1918. }
  1919. }
  1920. /* Semi-transparent Overlay Helpers
  1921. ------------------------------------------------------*/
  1922. function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
  1923. if (refreshCoordinateGrid) {
  1924. coordinateGrid.build();
  1925. }
  1926. var rowStart = cloneDate(t.visStart);
  1927. var rowEnd = addDays(cloneDate(rowStart), colCnt);
  1928. for (var i=0; i<rowCnt; i++) {
  1929. var stretchStart = new Date(Math.max(rowStart, overlayStart));
  1930. var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
  1931. if (stretchStart < stretchEnd) {
  1932. var colStart, colEnd;
  1933. if (rtl) {
  1934. colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
  1935. colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
  1936. }else{
  1937. colStart = dayDiff(stretchStart, rowStart);
  1938. colEnd = dayDiff(stretchEnd, rowStart);
  1939. }
  1940. dayBind(
  1941. renderCellOverlay(i, colStart, i, colEnd-1)
  1942. );
  1943. }
  1944. addDays(rowStart, 7);
  1945. addDays(rowEnd, 7);
  1946. }
  1947. }
  1948. function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
  1949. var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
  1950. return renderOverlay(rect, element);
  1951. }
  1952. /* Selection
  1953. -----------------------------------------------------------------------*/
  1954. function defaultSelectionEnd(startDate, allDay) {
  1955. return cloneDate(startDate);
  1956. }
  1957. function renderSelection(startDate, endDate, allDay) {
  1958. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
  1959. }
  1960. function clearSelection() {
  1961. clearOverlays();
  1962. }
  1963. function reportDayClick(date, allDay, ev) {
  1964. var cell = dateCell(date);
  1965. var _element = bodyCells[cell.row*colCnt + cell.col];
  1966. trigger('dayClick', _element, date, allDay, ev);
  1967. }
  1968. /* External Dragging
  1969. -----------------------------------------------------------------------*/
  1970. function dragStart(_dragElement, ev, ui) {
  1971. hoverListener.start(function(cell) {
  1972. clearOverlays();
  1973. if (cell) {
  1974. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  1975. }
  1976. }, ev);
  1977. }
  1978. function dragStop(_dragElement, ev, ui) {
  1979. var cell = hoverListener.stop();
  1980. clearOverlays();
  1981. if (cell) {
  1982. var d = cellDate(cell);
  1983. trigger('drop', _dragElement, d, true, ev, ui);
  1984. }
  1985. }
  1986. /* Utilities
  1987. --------------------------------------------------------*/
  1988. function defaultEventEnd(event) {
  1989. return cloneDate(event.start);
  1990. }
  1991. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  1992. var e, n, p;
  1993. headCells.each(function(i, _e) {
  1994. e = $(_e);
  1995. n = e.offset().left;
  1996. if (i) {
  1997. p[1] = n;
  1998. }
  1999. p = [n];
  2000. cols[i] = p;
  2001. });
  2002. p[1] = n + e.outerWidth();
  2003. bodyRows.each(function(i, _e) {
  2004. if (i < rowCnt) {
  2005. e = $(_e);
  2006. n = e.offset().top;
  2007. if (i) {
  2008. p[1] = n;
  2009. }
  2010. p = [n];
  2011. rows[i] = p;
  2012. }
  2013. });
  2014. p[1] = n + e.outerHeight();
  2015. });
  2016. hoverListener = new HoverListener(coordinateGrid);
  2017. colContentPositions = new HorizontalPositionCache(function(col) {
  2018. return bodyCellTopInners.eq(col);
  2019. });
  2020. function colContentLeft(col) {
  2021. return colContentPositions.left(col);
  2022. }
  2023. function colContentRight(col) {
  2024. return colContentPositions.right(col);
  2025. }
  2026. function dateCell(date) {
  2027. return {
  2028. row: Math.floor(dayDiff(date, t.visStart) / 7),
  2029. col: dayOfWeekCol(date.getDay())
  2030. };
  2031. }
  2032. function cellDate(cell) {
  2033. return _cellDate(cell.row, cell.col);
  2034. }
  2035. function _cellDate(row, col) {
  2036. return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
  2037. // what about weekends in middle of week?
  2038. }
  2039. function indexDate(index) {
  2040. return _cellDate(Math.floor(index/colCnt), index%colCnt);
  2041. }
  2042. function dayOfWeekCol(dayOfWeek) {
  2043. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
  2044. }
  2045. function allDayRow(i) {
  2046. return bodyRows.eq(i);
  2047. }
  2048. function allDayBounds(i) {
  2049. return {
  2050. left: 0,
  2051. right: viewWidth
  2052. };
  2053. }
  2054. }
  2055. function BasicEventRenderer() {
  2056. var t = this;
  2057. // exports
  2058. t.renderEvents = renderEvents;
  2059. t.compileDaySegs = compileSegs; // for DayEventRenderer
  2060. t.clearEvents = clearEvents;
  2061. t.bindDaySeg = bindDaySeg;
  2062. // imports
  2063. DayEventRenderer.call(t);
  2064. var opt = t.opt;
  2065. var trigger = t.trigger;
  2066. //var setOverflowHidden = t.setOverflowHidden;
  2067. var isEventDraggable = t.isEventDraggable;
  2068. var isEventResizable = t.isEventResizable;
  2069. var reportEvents = t.reportEvents;
  2070. var reportEventClear = t.reportEventClear;
  2071. var eventElementHandlers = t.eventElementHandlers;
  2072. var showEvents = t.showEvents;
  2073. var hideEvents = t.hideEvents;
  2074. var eventDrop = t.eventDrop;
  2075. var getDaySegmentContainer = t.getDaySegmentContainer;
  2076. var getHoverListener = t.getHoverListener;
  2077. var renderDayOverlay = t.renderDayOverlay;
  2078. var clearOverlays = t.clearOverlays;
  2079. var getRowCnt = t.getRowCnt;
  2080. var getColCnt = t.getColCnt;
  2081. var renderDaySegs = t.renderDaySegs;
  2082. var resizableDayEvent = t.resizableDayEvent;
  2083. /* Rendering
  2084. --------------------------------------------------------------------*/
  2085. function renderEvents(events, modifiedEventId) {
  2086. reportEvents(events);
  2087. renderDaySegs(compileSegs(events), modifiedEventId);
  2088. }
  2089. function clearEvents() {
  2090. reportEventClear();
  2091. getDaySegmentContainer().empty();
  2092. }
  2093. function compileSegs(events) {
  2094. var rowCnt = getRowCnt(),
  2095. colCnt = getColCnt(),
  2096. d1 = cloneDate(t.visStart),
  2097. d2 = addDays(cloneDate(d1), colCnt),
  2098. visEventsEnds = $.map(events, exclEndDay),
  2099. i, row,
  2100. j, level,
  2101. k, seg,
  2102. segs=[];
  2103. for (i=0; i<rowCnt; i++) {
  2104. row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
  2105. for (j=0; j<row.length; j++) {
  2106. level = row[j];
  2107. for (k=0; k<level.length; k++) {
  2108. seg = level[k];
  2109. seg.row = i;
  2110. seg.level = j; // not needed anymore
  2111. segs.push(seg);
  2112. }
  2113. }
  2114. addDays(d1, 7);
  2115. addDays(d2, 7);
  2116. }
  2117. return segs;
  2118. }
  2119. function bindDaySeg(event, eventElement, seg) {
  2120. if (isEventDraggable(event)) {
  2121. draggableDayEvent(event, eventElement);
  2122. }
  2123. if (seg.isEnd && isEventResizable(event)) {
  2124. resizableDayEvent(event, eventElement, seg);
  2125. }
  2126. eventElementHandlers(event, eventElement);
  2127. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  2128. }
  2129. /* Dragging
  2130. ----------------------------------------------------------------------------*/
  2131. function draggableDayEvent(event, eventElement) {
  2132. var hoverListener = getHoverListener();
  2133. var dayDelta;
  2134. eventElement.draggable({
  2135. zIndex: 9,
  2136. delay: 50,
  2137. opacity: opt('dragOpacity'),
  2138. revertDuration: opt('dragRevertDuration'),
  2139. start: function(ev, ui) {
  2140. trigger('eventDragStart', eventElement, event, ev, ui);
  2141. hideEvents(event, eventElement);
  2142. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  2143. eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
  2144. clearOverlays();
  2145. if (cell) {
  2146. //setOverflowHidden(true);
  2147. dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
  2148. renderDayOverlay(
  2149. addDays(cloneDate(event.start), dayDelta),
  2150. addDays(exclEndDay(event), dayDelta)
  2151. );
  2152. }else{
  2153. //setOverflowHidden(false);
  2154. dayDelta = 0;
  2155. }
  2156. }, ev, 'drag');
  2157. },
  2158. stop: function(ev, ui) {
  2159. hoverListener.stop();
  2160. clearOverlays();
  2161. trigger('eventDragStop', eventElement, event, ev, ui);
  2162. if (dayDelta) {
  2163. eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
  2164. }else{
  2165. eventElement.css('filter', ''); // clear IE opacity side-effects
  2166. showEvents(event, eventElement);
  2167. }
  2168. //setOverflowHidden(false);
  2169. }
  2170. });
  2171. }
  2172. }
  2173. fcViews.agendaWeek = AgendaWeekView;
  2174. function AgendaWeekView(element, calendar) {
  2175. var t = this;
  2176. // exports
  2177. t.render = render;
  2178. // imports
  2179. AgendaView.call(t, element, calendar, 'agendaWeek');
  2180. var opt = t.opt;
  2181. var renderAgenda = t.renderAgenda;
  2182. var formatDates = calendar.formatDates;
  2183. function render(date, delta) {
  2184. if (delta) {
  2185. addDays(date, delta * 7);
  2186. }
  2187. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  2188. var end = addDays(cloneDate(start), 7);
  2189. var visStart = cloneDate(start);
  2190. var visEnd = cloneDate(end);
  2191. var weekends = opt('weekends');
  2192. if (!weekends) {
  2193. skipWeekend(visStart);
  2194. skipWeekend(visEnd, -1, true);
  2195. }
  2196. t.title = formatDates(
  2197. visStart,
  2198. addDays(cloneDate(visEnd), -1),
  2199. opt('titleFormat')
  2200. );
  2201. t.start = start;
  2202. t.end = end;
  2203. t.visStart = visStart;
  2204. t.visEnd = visEnd;
  2205. renderAgenda(weekends ? 7 : 5);
  2206. }
  2207. }
  2208. fcViews.agendaDay = AgendaDayView;
  2209. function AgendaDayView(element, calendar) {
  2210. var t = this;
  2211. // exports
  2212. t.render = render;
  2213. // imports
  2214. AgendaView.call(t, element, calendar, 'agendaDay');
  2215. var opt = t.opt;
  2216. var renderAgenda = t.renderAgenda;
  2217. var formatDate = calendar.formatDate;
  2218. function render(date, delta) {
  2219. if (delta) {
  2220. addDays(date, delta);
  2221. if (!opt('weekends')) {
  2222. skipWeekend(date, delta < 0 ? -1 : 1);
  2223. }
  2224. }
  2225. var start = cloneDate(date, true);
  2226. var end = addDays(cloneDate(start), 1);
  2227. t.title = formatDate(date, opt('titleFormat'));
  2228. t.start = t.visStart = start;
  2229. t.end = t.visEnd = end;
  2230. renderAgenda(1);
  2231. }
  2232. }
  2233. setDefaults({
  2234. allDaySlot: true,
  2235. allDayText: 'all-day',
  2236. firstHour: 6,
  2237. slotMinutes: 30,
  2238. defaultEventMinutes: 120,
  2239. axisFormat: 'h(:mm)tt',
  2240. timeFormat: {
  2241. agenda: 'h:mm{ - h:mm}'
  2242. },
  2243. dragOpacity: {
  2244. agenda: .5
  2245. },
  2246. minTime: 0,
  2247. maxTime: 24
  2248. });
  2249. // TODO: make it work in quirks mode (event corners, all-day height)
  2250. // TODO: test liquid width, especially in IE6
  2251. function AgendaView(element, calendar, viewName) {
  2252. var t = this;
  2253. // exports
  2254. t.renderAgenda = renderAgenda;
  2255. t.setWidth = setWidth;
  2256. t.setHeight = setHeight;
  2257. t.beforeHide = beforeHide;
  2258. t.afterShow = afterShow;
  2259. t.defaultEventEnd = defaultEventEnd;
  2260. t.timePosition = timePosition;
  2261. t.dayOfWeekCol = dayOfWeekCol;
  2262. t.dateCell = dateCell;
  2263. t.cellDate = cellDate;
  2264. t.cellIsAllDay = cellIsAllDay;
  2265. t.allDayRow = getAllDayRow;
  2266. t.allDayBounds = allDayBounds;
  2267. t.getHoverListener = function() { return hoverListener };
  2268. t.colContentLeft = colContentLeft;
  2269. t.colContentRight = colContentRight;
  2270. t.getDaySegmentContainer = function() { return daySegmentContainer };
  2271. t.getSlotSegmentContainer = function() { return slotSegmentContainer };
  2272. t.getMinMinute = function() { return minMinute };
  2273. t.getMaxMinute = function() { return maxMinute };
  2274. t.getBodyContent = function() { return slotContent }; // !!??
  2275. t.getRowCnt = function() { return 1 };
  2276. t.getColCnt = function() { return colCnt };
  2277. t.getColWidth = function() { return colWidth };
  2278. t.getSlotHeight = function() { return slotHeight };
  2279. t.defaultSelectionEnd = defaultSelectionEnd;
  2280. t.renderDayOverlay = renderDayOverlay;
  2281. t.renderSelection = renderSelection;
  2282. t.clearSelection = clearSelection;
  2283. t.reportDayClick = reportDayClick; // selection mousedown hack
  2284. t.dragStart = dragStart;
  2285. t.dragStop = dragStop;
  2286. // imports
  2287. View.call(t, element, calendar, viewName);
  2288. OverlayManager.call(t);
  2289. SelectionManager.call(t);
  2290. AgendaEventRenderer.call(t);
  2291. var opt = t.opt;
  2292. var trigger = t.trigger;
  2293. var clearEvents = t.clearEvents;
  2294. var renderOverlay = t.renderOverlay;
  2295. var clearOverlays = t.clearOverlays;
  2296. var reportSelection = t.reportSelection;
  2297. var unselect = t.unselect;
  2298. var daySelectionMousedown = t.daySelectionMousedown;
  2299. var slotSegHtml = t.slotSegHtml;
  2300. var formatDate = calendar.formatDate;
  2301. // locals
  2302. var dayTable;
  2303. var dayHead;
  2304. var dayHeadCells;
  2305. var dayBody;
  2306. var dayBodyCells;
  2307. var dayBodyCellInners;
  2308. var dayBodyFirstCell;
  2309. var dayBodyFirstCellStretcher;
  2310. var slotLayer;
  2311. var daySegmentContainer;
  2312. var allDayTable;
  2313. var allDayRow;
  2314. var slotScroller;
  2315. var slotContent;
  2316. var slotSegmentContainer;
  2317. var slotTable;
  2318. var slotTableFirstInner;
  2319. var axisFirstCells;
  2320. var gutterCells;
  2321. var selectionHelper;
  2322. var viewWidth;
  2323. var viewHeight;
  2324. var axisWidth;
  2325. var colWidth;
  2326. var gutterWidth;
  2327. var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
  2328. var savedScrollTop;
  2329. var colCnt;
  2330. var slotCnt;
  2331. var coordinateGrid;
  2332. var hoverListener;
  2333. var colContentPositions;
  2334. var slotTopCache = {};
  2335. var tm;
  2336. var firstDay;
  2337. var nwe; // no weekends (int)
  2338. var rtl, dis, dit; // day index sign / translate
  2339. var minMinute, maxMinute;
  2340. var colFormat;
  2341. /* Rendering
  2342. -----------------------------------------------------------------------------*/
  2343. disableTextSelection(element.addClass('fc-agenda'));
  2344. function renderAgenda(c) {
  2345. colCnt = c;
  2346. updateOptions();
  2347. if (!dayTable) {
  2348. buildSkeleton();
  2349. }else{
  2350. clearEvents();
  2351. }
  2352. updateCells();
  2353. }
  2354. function updateOptions() {
  2355. tm = opt('theme') ? 'ui' : 'fc';
  2356. nwe = opt('weekends') ? 0 : 1;
  2357. firstDay = opt('firstDay');
  2358. if (rtl = opt('isRTL')) {
  2359. dis = -1;
  2360. dit = colCnt - 1;
  2361. }else{
  2362. dis = 1;
  2363. dit = 0;
  2364. }
  2365. minMinute = parseTime(opt('minTime'));
  2366. maxMinute = parseTime(opt('maxTime'));
  2367. colFormat = opt('columnFormat');
  2368. }
  2369. function buildSkeleton() {
  2370. var headerClass = tm + "-widget-header";
  2371. var contentClass = tm + "-widget-content";
  2372. var s;
  2373. var i;
  2374. var d;
  2375. var maxd;
  2376. var minutes;
  2377. var slotNormal = opt('slotMinutes') % 15 == 0;
  2378. s =
  2379. "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
  2380. "<thead>" +
  2381. "<tr>" +
  2382. "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2383. for (i=0; i<colCnt; i++) {
  2384. s +=
  2385. "<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
  2386. }
  2387. s +=
  2388. "<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
  2389. "</tr>" +
  2390. "</thead>" +
  2391. "<tbody>" +
  2392. "<tr>" +
  2393. "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2394. for (i=0; i<colCnt; i++) {
  2395. s +=
  2396. "<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
  2397. "<div>" +
  2398. "<div class='fc-day-content'>" +
  2399. "<div style='position:relative'>&nbsp;</div>" +
  2400. "</div>" +
  2401. "</div>" +
  2402. "</td>";
  2403. }
  2404. s +=
  2405. "<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
  2406. "</tr>" +
  2407. "</tbody>" +
  2408. "</table>";
  2409. dayTable = $(s).appendTo(element);
  2410. dayHead = dayTable.find('thead');
  2411. dayHeadCells = dayHead.find('th').slice(1, -1);
  2412. dayBody = dayTable.find('tbody');
  2413. dayBodyCells = dayBody.find('td').slice(0, -1);
  2414. dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
  2415. dayBodyFirstCell = dayBodyCells.eq(0);
  2416. dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
  2417. markFirstLast(dayHead.add(dayHead.find('tr')));
  2418. markFirstLast(dayBody.add(dayBody.find('tr')));
  2419. axisFirstCells = dayHead.find('th:first');
  2420. gutterCells = dayTable.find('.fc-agenda-gutter');
  2421. slotLayer =
  2422. $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
  2423. .appendTo(element);
  2424. if (opt('allDaySlot')) {
  2425. daySegmentContainer =
  2426. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2427. .appendTo(slotLayer);
  2428. s =
  2429. "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
  2430. "<tr>" +
  2431. "<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
  2432. "<td>" +
  2433. "<div class='fc-day-content'><div style='position:relative'/></div>" +
  2434. "</td>" +
  2435. "<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
  2436. "</tr>" +
  2437. "</table>";
  2438. allDayTable = $(s).appendTo(slotLayer);
  2439. allDayRow = allDayTable.find('tr');
  2440. dayBind(allDayRow.find('td'));
  2441. axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
  2442. gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
  2443. slotLayer.append(
  2444. "<div class='fc-agenda-divider " + headerClass + "'>" +
  2445. "<div class='fc-agenda-divider-inner'/>" +
  2446. "</div>"
  2447. );
  2448. }else{
  2449. daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
  2450. }
  2451. slotScroller =
  2452. $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
  2453. .appendTo(slotLayer);
  2454. slotContent =
  2455. $("<div style='position:relative;width:100%;overflow:hidden'/>")
  2456. .appendTo(slotScroller);
  2457. slotSegmentContainer =
  2458. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2459. .appendTo(slotContent);
  2460. s =
  2461. "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
  2462. "<tbody>";
  2463. d = zeroDate();
  2464. maxd = addMinutes(cloneDate(d), maxMinute);
  2465. addMinutes(d, minMinute);
  2466. slotCnt = 0;
  2467. for (i=0; d < maxd; i++) {
  2468. minutes = d.getMinutes();
  2469. s +=
  2470. "<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
  2471. "<th class='fc-agenda-axis " + headerClass + "'>" +
  2472. ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
  2473. "</th>" +
  2474. "<td class='" + contentClass + "'>" +
  2475. "<div style='position:relative'>&nbsp;</div>" +
  2476. "</td>" +
  2477. "</tr>";
  2478. addMinutes(d, opt('slotMinutes'));
  2479. slotCnt++;
  2480. }
  2481. s +=
  2482. "</tbody>" +
  2483. "</table>";
  2484. slotTable = $(s).appendTo(slotContent);
  2485. slotTableFirstInner = slotTable.find('div:first');
  2486. slotBind(slotTable.find('td'));
  2487. axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
  2488. }
  2489. function updateCells() {
  2490. var i;
  2491. var headCell;
  2492. var bodyCell;
  2493. var date;
  2494. var today = clearTime(new Date());
  2495. for (i=0; i<colCnt; i++) {
  2496. date = colDate(i);
  2497. headCell = dayHeadCells.eq(i);
  2498. headCell.html(formatDate(date, colFormat));
  2499. bodyCell = dayBodyCells.eq(i);
  2500. if (+date == +today) {
  2501. bodyCell.addClass(tm + '-state-highlight fc-today');
  2502. }else{
  2503. bodyCell.removeClass(tm + '-state-highlight fc-today');
  2504. }
  2505. setDayID(headCell.add(bodyCell), date);
  2506. }
  2507. }
  2508. function setHeight(height, dateChanged) {
  2509. if (height === undefined) {
  2510. height = viewHeight;
  2511. }
  2512. viewHeight = height;
  2513. slotTopCache = {};
  2514. var headHeight = dayBody.position().top;
  2515. var allDayHeight = slotScroller.position().top; // including divider
  2516. var bodyHeight = Math.min( // total body height, including borders
  2517. height - headHeight, // when scrollbars
  2518. slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
  2519. );
  2520. dayBodyFirstCellStretcher
  2521. .height(bodyHeight - vsides(dayBodyFirstCell));
  2522. slotLayer.css('top', headHeight);
  2523. slotScroller.height(bodyHeight - allDayHeight - 1);
  2524. slotHeight = slotTableFirstInner.height() + 1; // +1 for border
  2525. if (dateChanged) {
  2526. resetScroll();
  2527. }
  2528. }
  2529. function setWidth(width) {
  2530. viewWidth = width;
  2531. colContentPositions.clear();
  2532. axisWidth = 0;
  2533. setOuterWidth(
  2534. axisFirstCells
  2535. .width('')
  2536. .each(function(i, _cell) {
  2537. axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
  2538. }),
  2539. axisWidth
  2540. );
  2541. var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
  2542. //slotTable.width(slotTableWidth);
  2543. gutterWidth = slotScroller.width() - slotTableWidth;
  2544. if (gutterWidth) {
  2545. setOuterWidth(gutterCells, gutterWidth);
  2546. gutterCells
  2547. .show()
  2548. .prev()
  2549. .removeClass('fc-last');
  2550. }else{
  2551. gutterCells
  2552. .hide()
  2553. .prev()
  2554. .addClass('fc-last');
  2555. }
  2556. colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
  2557. setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
  2558. }
  2559. function resetScroll() {
  2560. var d0 = zeroDate();
  2561. var scrollDate = cloneDate(d0);
  2562. scrollDate.setHours(opt('firstHour'));
  2563. var top = timePosition(d0, scrollDate) + 1; // +1 for the border
  2564. function scroll() {
  2565. slotScroller.scrollTop(top);
  2566. }
  2567. scroll();
  2568. setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
  2569. }
  2570. function beforeHide() {
  2571. savedScrollTop = slotScroller.scrollTop();
  2572. }
  2573. function afterShow() {
  2574. slotScroller.scrollTop(savedScrollTop);
  2575. }
  2576. /* Slot/Day clicking and binding
  2577. -----------------------------------------------------------------------*/
  2578. function dayBind(cells) {
  2579. cells.click(slotClick)
  2580. .mousedown(daySelectionMousedown);
  2581. }
  2582. function slotBind(cells) {
  2583. cells.click(slotClick)
  2584. .mousedown(slotSelectionMousedown);
  2585. }
  2586. function slotClick(ev) {
  2587. if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  2588. var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
  2589. var date = colDate(col);
  2590. var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
  2591. if (rowMatch) {
  2592. var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
  2593. var hours = Math.floor(mins/60);
  2594. date.setHours(hours);
  2595. date.setMinutes(mins%60 + minMinute);
  2596. trigger('dayClick', dayBodyCells[col], date, false, ev);
  2597. }else{
  2598. trigger('dayClick', dayBodyCells[col], date, true, ev);
  2599. }
  2600. }
  2601. }
  2602. /* Semi-transparent Overlay Helpers
  2603. -----------------------------------------------------*/
  2604. function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
  2605. if (refreshCoordinateGrid) {
  2606. coordinateGrid.build();
  2607. }
  2608. var visStart = cloneDate(t.visStart);
  2609. var startCol, endCol;
  2610. if (rtl) {
  2611. startCol = dayDiff(endDate, visStart)*dis+dit+1;
  2612. endCol = dayDiff(startDate, visStart)*dis+dit+1;
  2613. }else{
  2614. startCol = dayDiff(startDate, visStart);
  2615. endCol = dayDiff(endDate, visStart);
  2616. }
  2617. startCol = Math.max(0, startCol);
  2618. endCol = Math.min(colCnt, endCol);
  2619. if (startCol < endCol) {
  2620. dayBind(
  2621. renderCellOverlay(0, startCol, 0, endCol-1)
  2622. );
  2623. }
  2624. }
  2625. function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
  2626. var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
  2627. return renderOverlay(rect, slotLayer);
  2628. }
  2629. function renderSlotOverlay(overlayStart, overlayEnd) {
  2630. var dayStart = cloneDate(t.visStart);
  2631. var dayEnd = addDays(cloneDate(dayStart), 1);
  2632. for (var i=0; i<colCnt; i++) {
  2633. var stretchStart = new Date(Math.max(dayStart, overlayStart));
  2634. var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
  2635. if (stretchStart < stretchEnd) {
  2636. var col = i*dis+dit;
  2637. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
  2638. var top = timePosition(dayStart, stretchStart);
  2639. var bottom = timePosition(dayStart, stretchEnd);
  2640. rect.top = top;
  2641. rect.height = bottom - top;
  2642. slotBind(
  2643. renderOverlay(rect, slotContent)
  2644. );
  2645. }
  2646. addDays(dayStart, 1);
  2647. addDays(dayEnd, 1);
  2648. }
  2649. }
  2650. /* Coordinate Utilities
  2651. -----------------------------------------------------------------------------*/
  2652. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  2653. var e, n, p;
  2654. dayHeadCells.each(function(i, _e) {
  2655. e = $(_e);
  2656. n = e.offset().left;
  2657. if (i) {
  2658. p[1] = n;
  2659. }
  2660. p = [n];
  2661. cols[i] = p;
  2662. });
  2663. p[1] = n + e.outerWidth();
  2664. if (opt('allDaySlot')) {
  2665. e = allDayRow;
  2666. n = e.offset().top;
  2667. rows[0] = [n, n+e.outerHeight()];
  2668. }
  2669. var slotTableTop = slotContent.offset().top;
  2670. var slotScrollerTop = slotScroller.offset().top;
  2671. var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
  2672. function constrain(n) {
  2673. return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
  2674. }
  2675. for (var i=0; i<slotCnt; i++) {
  2676. rows.push([
  2677. constrain(slotTableTop + slotHeight*i),
  2678. constrain(slotTableTop + slotHeight*(i+1))
  2679. ]);
  2680. }
  2681. });
  2682. hoverListener = new HoverListener(coordinateGrid);
  2683. colContentPositions = new HorizontalPositionCache(function(col) {
  2684. return dayBodyCellInners.eq(col);
  2685. });
  2686. function colContentLeft(col) {
  2687. return colContentPositions.left(col);
  2688. }
  2689. function colContentRight(col) {
  2690. return colContentPositions.right(col);
  2691. }
  2692. function dateCell(date) { // "cell" terminology is now confusing
  2693. return {
  2694. row: Math.floor(dayDiff(date, t.visStart) / 7),
  2695. col: dayOfWeekCol(date.getDay())
  2696. };
  2697. }
  2698. function cellDate(cell) {
  2699. var d = colDate(cell.col);
  2700. var slotIndex = cell.row;
  2701. if (opt('allDaySlot')) {
  2702. slotIndex--;
  2703. }
  2704. if (slotIndex >= 0) {
  2705. addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
  2706. }
  2707. return d;
  2708. }
  2709. function colDate(col) { // returns dates with 00:00:00
  2710. return addDays(cloneDate(t.visStart), col*dis+dit);
  2711. }
  2712. function cellIsAllDay(cell) {
  2713. return opt('allDaySlot') && !cell.row;
  2714. }
  2715. function dayOfWeekCol(dayOfWeek) {
  2716. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
  2717. }
  2718. // get the Y coordinate of the given time on the given day (both Date objects)
  2719. function timePosition(day, time) { // both date objects. day holds 00:00 of current day
  2720. day = cloneDate(day, true);
  2721. if (time < addMinutes(cloneDate(day), minMinute)) {
  2722. return 0;
  2723. }
  2724. if (time >= addMinutes(cloneDate(day), maxMinute)) {
  2725. return slotTable.height();
  2726. }
  2727. var slotMinutes = opt('slotMinutes'),
  2728. minutes = time.getHours()*60 + time.getMinutes() - minMinute,
  2729. slotI = Math.floor(minutes / slotMinutes),
  2730. slotTop = slotTopCache[slotI];
  2731. if (slotTop === undefined) {
  2732. slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
  2733. }
  2734. return Math.max(0, Math.round(
  2735. slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
  2736. ));
  2737. }
  2738. function allDayBounds() {
  2739. return {
  2740. left: axisWidth,
  2741. right: viewWidth - gutterWidth
  2742. }
  2743. }
  2744. function getAllDayRow(index) {
  2745. return allDayRow;
  2746. }
  2747. function defaultEventEnd(event) {
  2748. var start = cloneDate(event.start);
  2749. if (event.allDay) {
  2750. return start;
  2751. }
  2752. return addMinutes(start, opt('defaultEventMinutes'));
  2753. }
  2754. /* Selection
  2755. ---------------------------------------------------------------------------------*/
  2756. function defaultSelectionEnd(startDate, allDay) {
  2757. if (allDay) {
  2758. return cloneDate(startDate);
  2759. }
  2760. return addMinutes(cloneDate(startDate), opt('slotMinutes'));
  2761. }
  2762. function renderSelection(startDate, endDate, allDay) { // only for all-day
  2763. if (allDay) {
  2764. if (opt('allDaySlot')) {
  2765. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
  2766. }
  2767. }else{
  2768. renderSlotSelection(startDate, endDate);
  2769. }
  2770. }
  2771. function renderSlotSelection(startDate, endDate) {
  2772. var helperOption = opt('selectHelper');
  2773. coordinateGrid.build();
  2774. if (helperOption) {
  2775. var col = dayDiff(startDate, t.visStart) * dis + dit;
  2776. if (col >= 0 && col < colCnt) { // only works when times are on same day
  2777. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
  2778. var top = timePosition(startDate, startDate);
  2779. var bottom = timePosition(startDate, endDate);
  2780. if (bottom > top) { // protect against selections that are entirely before or after visible range
  2781. rect.top = top;
  2782. rect.height = bottom - top;
  2783. rect.left += 2;
  2784. rect.width -= 5;
  2785. if ($.isFunction(helperOption)) {
  2786. var helperRes = helperOption(startDate, endDate);
  2787. if (helperRes) {
  2788. rect.position = 'absolute';
  2789. rect.zIndex = 8;
  2790. selectionHelper = $(helperRes)
  2791. .css(rect)
  2792. .appendTo(slotContent);
  2793. }
  2794. }else{
  2795. rect.isStart = true; // conside rect a "seg" now
  2796. rect.isEnd = true; //
  2797. selectionHelper = $(slotSegHtml(
  2798. {
  2799. title: '',
  2800. start: startDate,
  2801. end: endDate,
  2802. className: ['fc-select-helper'],
  2803. editable: false
  2804. },
  2805. rect
  2806. ));
  2807. selectionHelper.css('opacity', opt('dragOpacity'));
  2808. }
  2809. if (selectionHelper) {
  2810. slotBind(selectionHelper);
  2811. slotContent.append(selectionHelper);
  2812. setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
  2813. setOuterHeight(selectionHelper, rect.height, true);
  2814. }
  2815. }
  2816. }
  2817. }else{
  2818. renderSlotOverlay(startDate, endDate);
  2819. }
  2820. }
  2821. function clearSelection() {
  2822. clearOverlays();
  2823. if (selectionHelper) {
  2824. selectionHelper.remove();
  2825. selectionHelper = null;
  2826. }
  2827. }
  2828. function slotSelectionMousedown(ev) {
  2829. if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
  2830. unselect(ev);
  2831. var dates;
  2832. hoverListener.start(function(cell, origCell) {
  2833. clearSelection();
  2834. if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
  2835. var d1 = cellDate(origCell);
  2836. var d2 = cellDate(cell);
  2837. dates = [
  2838. d1,
  2839. addMinutes(cloneDate(d1), opt('slotMinutes')),
  2840. d2,
  2841. addMinutes(cloneDate(d2), opt('slotMinutes'))
  2842. ].sort(cmp);
  2843. renderSlotSelection(dates[0], dates[3]);
  2844. }else{
  2845. dates = null;
  2846. }
  2847. }, ev);
  2848. $(document).one('mouseup', function(ev) {
  2849. hoverListener.stop();
  2850. if (dates) {
  2851. if (+dates[0] == +dates[1]) {
  2852. reportDayClick(dates[0], false, ev);
  2853. }
  2854. reportSelection(dates[0], dates[3], false, ev);
  2855. }
  2856. });
  2857. }
  2858. }
  2859. function reportDayClick(date, allDay, ev) {
  2860. trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
  2861. }
  2862. /* External Dragging
  2863. --------------------------------------------------------------------------------*/
  2864. function dragStart(_dragElement, ev, ui) {
  2865. hoverListener.start(function(cell) {
  2866. clearOverlays();
  2867. if (cell) {
  2868. if (cellIsAllDay(cell)) {
  2869. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  2870. }else{
  2871. var d1 = cellDate(cell);
  2872. var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
  2873. renderSlotOverlay(d1, d2);
  2874. }
  2875. }
  2876. }, ev);
  2877. }
  2878. function dragStop(_dragElement, ev, ui) {
  2879. var cell = hoverListener.stop();
  2880. clearOverlays();
  2881. if (cell) {
  2882. trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
  2883. }
  2884. }
  2885. }
  2886. function AgendaEventRenderer() {
  2887. var t = this;
  2888. // exports
  2889. t.renderEvents = renderEvents;
  2890. t.compileDaySegs = compileDaySegs; // for DayEventRenderer
  2891. t.clearEvents = clearEvents;
  2892. t.slotSegHtml = slotSegHtml;
  2893. t.bindDaySeg = bindDaySeg;
  2894. // imports
  2895. DayEventRenderer.call(t);
  2896. var opt = t.opt;
  2897. var trigger = t.trigger;
  2898. //var setOverflowHidden = t.setOverflowHidden;
  2899. var isEventDraggable = t.isEventDraggable;
  2900. var isEventResizable = t.isEventResizable;
  2901. var eventEnd = t.eventEnd;
  2902. var reportEvents = t.reportEvents;
  2903. var reportEventClear = t.reportEventClear;
  2904. var eventElementHandlers = t.eventElementHandlers;
  2905. var setHeight = t.setHeight;
  2906. var getDaySegmentContainer = t.getDaySegmentContainer;
  2907. var getSlotSegmentContainer = t.getSlotSegmentContainer;
  2908. var getHoverListener = t.getHoverListener;
  2909. var getMaxMinute = t.getMaxMinute;
  2910. var getMinMinute = t.getMinMinute;
  2911. var timePosition = t.timePosition;
  2912. var colContentLeft = t.colContentLeft;
  2913. var colContentRight = t.colContentRight;
  2914. var renderDaySegs = t.renderDaySegs;
  2915. var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
  2916. var getColCnt = t.getColCnt;
  2917. var getColWidth = t.getColWidth;
  2918. var getSlotHeight = t.getSlotHeight;
  2919. var getBodyContent = t.getBodyContent;
  2920. var reportEventElement = t.reportEventElement;
  2921. var showEvents = t.showEvents;
  2922. var hideEvents = t.hideEvents;
  2923. var eventDrop = t.eventDrop;
  2924. var eventResize = t.eventResize;
  2925. var renderDayOverlay = t.renderDayOverlay;
  2926. var clearOverlays = t.clearOverlays;
  2927. var calendar = t.calendar;
  2928. var formatDate = calendar.formatDate;
  2929. var formatDates = calendar.formatDates;
  2930. /* Rendering
  2931. ----------------------------------------------------------------------------*/
  2932. function renderEvents(events, modifiedEventId) {
  2933. reportEvents(events);
  2934. var i, len=events.length,
  2935. dayEvents=[],
  2936. slotEvents=[];
  2937. for (i=0; i<len; i++) {
  2938. if (events[i].allDay) {
  2939. dayEvents.push(events[i]);
  2940. }else{
  2941. slotEvents.push(events[i]);
  2942. }
  2943. }
  2944. if (opt('allDaySlot')) {
  2945. renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
  2946. setHeight(); // no params means set to viewHeight
  2947. }
  2948. renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
  2949. }
  2950. function clearEvents() {
  2951. reportEventClear();
  2952. getDaySegmentContainer().empty();
  2953. getSlotSegmentContainer().empty();
  2954. }
  2955. function compileDaySegs(events) {
  2956. var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
  2957. i, levelCnt=levels.length, level,
  2958. j, seg,
  2959. segs=[];
  2960. for (i=0; i<levelCnt; i++) {
  2961. level = levels[i];
  2962. for (j=0; j<level.length; j++) {
  2963. seg = level[j];
  2964. seg.row = 0;
  2965. seg.level = i; // not needed anymore
  2966. segs.push(seg);
  2967. }
  2968. }
  2969. return segs;
  2970. }
  2971. function compileSlotSegs(events) {
  2972. var colCnt = getColCnt(),
  2973. minMinute = getMinMinute(),
  2974. maxMinute = getMaxMinute(),
  2975. d = addMinutes(cloneDate(t.visStart), minMinute),
  2976. visEventEnds = $.map(events, slotEventEnd),
  2977. i, col,
  2978. j, level,
  2979. k, seg,
  2980. segs=[];
  2981. for (i=0; i<colCnt; i++) {
  2982. col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
  2983. countForwardSegs(col);
  2984. for (j=0; j<col.length; j++) {
  2985. level = col[j];
  2986. for (k=0; k<level.length; k++) {
  2987. seg = level[k];
  2988. seg.col = i;
  2989. seg.level = j;
  2990. segs.push(seg);
  2991. }
  2992. }
  2993. addDays(d, 1, true);
  2994. }
  2995. return segs;
  2996. }
  2997. function slotEventEnd(event) {
  2998. if (event.end) {
  2999. return cloneDate(event.end);
  3000. }else{
  3001. return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
  3002. }
  3003. }
  3004. // renders events in the 'time slots' at the bottom
  3005. function renderSlotSegs(segs, modifiedEventId) {
  3006. var i, segCnt=segs.length, seg,
  3007. event,
  3008. classes,
  3009. top, bottom,
  3010. colI, levelI, forward,
  3011. leftmost,
  3012. availWidth,
  3013. outerWidth,
  3014. left,
  3015. html='',
  3016. eventElements,
  3017. eventElement,
  3018. triggerRes,
  3019. vsideCache={},
  3020. hsideCache={},
  3021. key, val,
  3022. contentElement,
  3023. height,
  3024. slotSegmentContainer = getSlotSegmentContainer(),
  3025. rtl, dis, dit,
  3026. colCnt = getColCnt();
  3027. if (rtl = opt('isRTL')) {
  3028. dis = -1;
  3029. dit = colCnt - 1;
  3030. }else{
  3031. dis = 1;
  3032. dit = 0;
  3033. }
  3034. // calculate position/dimensions, create html
  3035. for (i=0; i<segCnt; i++) {
  3036. seg = segs[i];
  3037. event = seg.event;
  3038. top = timePosition(seg.start, seg.start);
  3039. bottom = timePosition(seg.start, seg.end);
  3040. colI = seg.col;
  3041. levelI = seg.level;
  3042. forward = seg.forward || 0;
  3043. leftmost = colContentLeft(colI*dis + dit);
  3044. availWidth = colContentRight(colI*dis + dit) - leftmost;
  3045. availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
  3046. if (levelI) {
  3047. // indented and thin
  3048. outerWidth = availWidth / (levelI + forward + 1);
  3049. }else{
  3050. if (forward) {
  3051. // moderately wide, aligned left still
  3052. outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
  3053. }else{
  3054. // can be entire width, aligned left
  3055. outerWidth = availWidth;
  3056. }
  3057. }
  3058. left = leftmost + // leftmost possible
  3059. (availWidth / (levelI + forward + 1) * levelI) // indentation
  3060. * dis + (rtl ? availWidth - outerWidth : 0); // rtl
  3061. seg.top = top;
  3062. seg.left = left;
  3063. seg.outerWidth = outerWidth;
  3064. seg.outerHeight = bottom - top;
  3065. html += slotSegHtml(event, seg);
  3066. }
  3067. slotSegmentContainer[0].innerHTML = html; // faster than html()
  3068. eventElements = slotSegmentContainer.children();
  3069. // retrieve elements, run through eventRender callback, bind event handlers
  3070. for (i=0; i<segCnt; i++) {
  3071. seg = segs[i];
  3072. event = seg.event;
  3073. eventElement = $(eventElements[i]); // faster than eq()
  3074. triggerRes = trigger('eventRender', event, event, eventElement);
  3075. if (triggerRes === false) {
  3076. eventElement.remove();
  3077. }else{
  3078. if (triggerRes && triggerRes !== true) {
  3079. eventElement.remove();
  3080. eventElement = $(triggerRes)
  3081. .css({
  3082. position: 'absolute',
  3083. top: seg.top,
  3084. left: seg.left
  3085. })
  3086. .appendTo(slotSegmentContainer);
  3087. }
  3088. seg.element = eventElement;
  3089. if (event._id === modifiedEventId) {
  3090. bindSlotSeg(event, eventElement, seg);
  3091. }else{
  3092. eventElement[0]._fci = i; // for lazySegBind
  3093. }
  3094. reportEventElement(event, eventElement);
  3095. }
  3096. }
  3097. lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
  3098. // record event sides and title positions
  3099. for (i=0; i<segCnt; i++) {
  3100. seg = segs[i];
  3101. if (eventElement = seg.element) {
  3102. val = vsideCache[key = seg.key = cssKey(eventElement[0])];
  3103. seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
  3104. val = hsideCache[key];
  3105. seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
  3106. contentElement = eventElement.find('div.fc-event-content');
  3107. if (contentElement.length) {
  3108. seg.contentTop = contentElement[0].offsetTop;
  3109. }
  3110. }
  3111. }
  3112. // set all positions/dimensions at once
  3113. for (i=0; i<segCnt; i++) {
  3114. seg = segs[i];
  3115. if (eventElement = seg.element) {
  3116. eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3117. height = Math.max(0, seg.outerHeight - seg.vsides);
  3118. eventElement[0].style.height = height + 'px';
  3119. event = seg.event;
  3120. if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
  3121. // not enough room for title, put it in the time header
  3122. eventElement.find('div.fc-event-time')
  3123. .text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
  3124. eventElement.find('div.fc-event-title')
  3125. .remove();
  3126. }
  3127. trigger('eventAfterRender', event, event, eventElement);
  3128. }
  3129. }
  3130. }
  3131. function slotSegHtml(event, seg) {
  3132. var html = "<";
  3133. var url = event.url;
  3134. var skinCss = getSkinCss(event, opt);
  3135. var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  3136. var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
  3137. if (isEventDraggable(event)) {
  3138. classes.push('fc-event-draggable');
  3139. }
  3140. if (seg.isStart) {
  3141. classes.push('fc-corner-top');
  3142. }
  3143. if (seg.isEnd) {
  3144. classes.push('fc-corner-bottom');
  3145. }
  3146. classes = classes.concat(event.className);
  3147. if (event.source) {
  3148. classes = classes.concat(event.source.className || []);
  3149. }
  3150. if (url) {
  3151. html += "a href='" + htmlEscape(event.url) + "'";
  3152. }else{
  3153. html += "div";
  3154. }
  3155. html +=
  3156. " class='" + classes.join(' ') + "'" +
  3157. " style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
  3158. ">" +
  3159. "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
  3160. "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
  3161. "<div class='fc-event-time'>" +
  3162. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3163. "</div>" +
  3164. "</div>" +
  3165. "<div class='fc-event-content'>" +
  3166. "<div class='fc-event-title'>" +
  3167. htmlEscape(event.title) +
  3168. "</div>" +
  3169. "</div>" +
  3170. "<div class='fc-event-bg'></div>" +
  3171. "</div>"; // close inner
  3172. if (seg.isEnd && isEventResizable(event)) {
  3173. html +=
  3174. "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
  3175. }
  3176. html +=
  3177. "</" + (url ? "a" : "div") + ">";
  3178. return html;
  3179. }
  3180. function bindDaySeg(event, eventElement, seg) {
  3181. if (isEventDraggable(event)) {
  3182. draggableDayEvent(event, eventElement, seg.isStart);
  3183. }
  3184. if (seg.isEnd && isEventResizable(event)) {
  3185. resizableDayEvent(event, eventElement, seg);
  3186. }
  3187. eventElementHandlers(event, eventElement);
  3188. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  3189. }
  3190. function bindSlotSeg(event, eventElement, seg) {
  3191. var timeElement = eventElement.find('div.fc-event-time');
  3192. if (isEventDraggable(event)) {
  3193. draggableSlotEvent(event, eventElement, timeElement);
  3194. }
  3195. if (seg.isEnd && isEventResizable(event)) {
  3196. resizableSlotEvent(event, eventElement, timeElement);
  3197. }
  3198. eventElementHandlers(event, eventElement);
  3199. }
  3200. /* Dragging
  3201. -----------------------------------------------------------------------------------*/
  3202. // when event starts out FULL-DAY
  3203. function draggableDayEvent(event, eventElement, isStart) {
  3204. var origWidth;
  3205. var revert;
  3206. var allDay=true;
  3207. var dayDelta;
  3208. var dis = opt('isRTL') ? -1 : 1;
  3209. var hoverListener = getHoverListener();
  3210. var colWidth = getColWidth();
  3211. var slotHeight = getSlotHeight();
  3212. var minMinute = getMinMinute();
  3213. eventElement.draggable({
  3214. zIndex: 9,
  3215. opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
  3216. revertDuration: opt('dragRevertDuration'),
  3217. start: function(ev, ui) {
  3218. trigger('eventDragStart', eventElement, event, ev, ui);
  3219. hideEvents(event, eventElement);
  3220. origWidth = eventElement.width();
  3221. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3222. clearOverlays();
  3223. if (cell) {
  3224. //setOverflowHidden(true);
  3225. revert = false;
  3226. dayDelta = colDelta * dis;
  3227. if (!cell.row) {
  3228. // on full-days
  3229. renderDayOverlay(
  3230. addDays(cloneDate(event.start), dayDelta),
  3231. addDays(exclEndDay(event), dayDelta)
  3232. );
  3233. resetElement();
  3234. }else{
  3235. // mouse is over bottom slots
  3236. if (isStart) {
  3237. if (allDay) {
  3238. // convert event to temporary slot-event
  3239. eventElement.width(colWidth - 10); // don't use entire width
  3240. setOuterHeight(
  3241. eventElement,
  3242. slotHeight * Math.round(
  3243. (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
  3244. / opt('slotMinutes')
  3245. )
  3246. );
  3247. eventElement.draggable('option', 'grid', [colWidth, 1]);
  3248. allDay = false;
  3249. }
  3250. }else{
  3251. revert = true;
  3252. }
  3253. }
  3254. revert = revert || (allDay && !dayDelta);
  3255. }else{
  3256. resetElement();
  3257. //setOverflowHidden(false);
  3258. revert = true;
  3259. }
  3260. eventElement.draggable('option', 'revert', revert);
  3261. }, ev, 'drag');
  3262. },
  3263. stop: function(ev, ui) {
  3264. hoverListener.stop();
  3265. clearOverlays();
  3266. trigger('eventDragStop', eventElement, event, ev, ui);
  3267. if (revert) {
  3268. // hasn't moved or is out of bounds (draggable has already reverted)
  3269. resetElement();
  3270. eventElement.css('filter', ''); // clear IE opacity side-effects
  3271. showEvents(event, eventElement);
  3272. }else{
  3273. // changed!
  3274. var minuteDelta = 0;
  3275. if (!allDay) {
  3276. minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
  3277. * opt('slotMinutes')
  3278. + minMinute
  3279. - (event.start.getHours() * 60 + event.start.getMinutes());
  3280. }
  3281. eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
  3282. }
  3283. //setOverflowHidden(false);
  3284. }
  3285. });
  3286. function resetElement() {
  3287. if (!allDay) {
  3288. eventElement
  3289. .width(origWidth)
  3290. .height('')
  3291. .draggable('option', 'grid', null);
  3292. allDay = true;
  3293. }
  3294. }
  3295. }
  3296. // when event starts out IN TIMESLOTS
  3297. function draggableSlotEvent(event, eventElement, timeElement) {
  3298. var origPosition;
  3299. var allDay=false;
  3300. var dayDelta;
  3301. var minuteDelta;
  3302. var prevMinuteDelta;
  3303. var dis = opt('isRTL') ? -1 : 1;
  3304. var hoverListener = getHoverListener();
  3305. var colCnt = getColCnt();
  3306. var colWidth = getColWidth();
  3307. var slotHeight = getSlotHeight();
  3308. eventElement.draggable({
  3309. zIndex: 9,
  3310. scroll: false,
  3311. grid: [colWidth, slotHeight],
  3312. axis: colCnt==1 ? 'y' : false,
  3313. opacity: opt('dragOpacity'),
  3314. revertDuration: opt('dragRevertDuration'),
  3315. start: function(ev, ui) {
  3316. trigger('eventDragStart', eventElement, event, ev, ui);
  3317. hideEvents(event, eventElement);
  3318. origPosition = eventElement.position();
  3319. minuteDelta = prevMinuteDelta = 0;
  3320. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3321. eventElement.draggable('option', 'revert', !cell);
  3322. clearOverlays();
  3323. if (cell) {
  3324. dayDelta = colDelta * dis;
  3325. if (opt('allDaySlot') && !cell.row) {
  3326. // over full days
  3327. if (!allDay) {
  3328. // convert to temporary all-day event
  3329. allDay = true;
  3330. timeElement.hide();
  3331. eventElement.draggable('option', 'grid', null);
  3332. }
  3333. renderDayOverlay(
  3334. addDays(cloneDate(event.start), dayDelta),
  3335. addDays(exclEndDay(event), dayDelta)
  3336. );
  3337. }else{
  3338. // on slots
  3339. resetElement();
  3340. }
  3341. }
  3342. }, ev, 'drag');
  3343. },
  3344. drag: function(ev, ui) {
  3345. minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
  3346. if (minuteDelta != prevMinuteDelta) {
  3347. if (!allDay) {
  3348. updateTimeText(minuteDelta);
  3349. }
  3350. prevMinuteDelta = minuteDelta;
  3351. }
  3352. },
  3353. stop: function(ev, ui) {
  3354. var cell = hoverListener.stop();
  3355. clearOverlays();
  3356. trigger('eventDragStop', eventElement, event, ev, ui);
  3357. if (cell && (dayDelta || minuteDelta || allDay)) {
  3358. // changed!
  3359. eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
  3360. }else{
  3361. // either no change or out-of-bounds (draggable has already reverted)
  3362. resetElement();
  3363. eventElement.css('filter', ''); // clear IE opacity side-effects
  3364. eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
  3365. updateTimeText(0);
  3366. showEvents(event, eventElement);
  3367. }
  3368. }
  3369. });
  3370. function updateTimeText(minuteDelta) {
  3371. var newStart = addMinutes(cloneDate(event.start), minuteDelta);
  3372. var newEnd;
  3373. if (event.end) {
  3374. newEnd = addMinutes(cloneDate(event.end), minuteDelta);
  3375. }
  3376. timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
  3377. }
  3378. function resetElement() {
  3379. // convert back to original slot-event
  3380. if (allDay) {
  3381. timeElement.css('display', ''); // show() was causing display=inline
  3382. eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
  3383. allDay = false;
  3384. }
  3385. }
  3386. }
  3387. /* Resizing
  3388. --------------------------------------------------------------------------------------*/
  3389. function resizableSlotEvent(event, eventElement, timeElement) {
  3390. var slotDelta, prevSlotDelta;
  3391. var slotHeight = getSlotHeight();
  3392. eventElement.resizable({
  3393. handles: {
  3394. s: 'div.ui-resizable-s'
  3395. },
  3396. grid: slotHeight,
  3397. start: function(ev, ui) {
  3398. slotDelta = prevSlotDelta = 0;
  3399. hideEvents(event, eventElement);
  3400. eventElement.css('z-index', 9);
  3401. trigger('eventResizeStart', this, event, ev, ui);
  3402. },
  3403. resize: function(ev, ui) {
  3404. // don't rely on ui.size.height, doesn't take grid into account
  3405. slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
  3406. if (slotDelta != prevSlotDelta) {
  3407. timeElement.text(
  3408. formatDates(
  3409. event.start,
  3410. (!slotDelta && !event.end) ? null : // no change, so don't display time range
  3411. addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
  3412. opt('timeFormat')
  3413. )
  3414. );
  3415. prevSlotDelta = slotDelta;
  3416. }
  3417. },
  3418. stop: function(ev, ui) {
  3419. trigger('eventResizeStop', this, event, ev, ui);
  3420. if (slotDelta) {
  3421. eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
  3422. }else{
  3423. eventElement.css('z-index', 8);
  3424. showEvents(event, eventElement);
  3425. // BUG: if event was really short, need to put title back in span
  3426. }
  3427. }
  3428. });
  3429. }
  3430. }
  3431. function countForwardSegs(levels) {
  3432. var i, j, k, level, segForward, segBack;
  3433. for (i=levels.length-1; i>0; i--) {
  3434. level = levels[i];
  3435. for (j=0; j<level.length; j++) {
  3436. segForward = level[j];
  3437. for (k=0; k<levels[i-1].length; k++) {
  3438. segBack = levels[i-1][k];
  3439. if (segsCollide(segForward, segBack)) {
  3440. segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
  3441. }
  3442. }
  3443. }
  3444. }
  3445. }
  3446. function View(element, calendar, viewName) {
  3447. var t = this;
  3448. // exports
  3449. t.element = element;
  3450. t.calendar = calendar;
  3451. t.name = viewName;
  3452. t.opt = opt;
  3453. t.trigger = trigger;
  3454. //t.setOverflowHidden = setOverflowHidden;
  3455. t.isEventDraggable = isEventDraggable;
  3456. t.isEventResizable = isEventResizable;
  3457. t.reportEvents = reportEvents;
  3458. t.eventEnd = eventEnd;
  3459. t.reportEventElement = reportEventElement;
  3460. t.reportEventClear = reportEventClear;
  3461. t.eventElementHandlers = eventElementHandlers;
  3462. t.showEvents = showEvents;
  3463. t.hideEvents = hideEvents;
  3464. t.eventDrop = eventDrop;
  3465. t.eventResize = eventResize;
  3466. // t.title
  3467. // t.start, t.end
  3468. // t.visStart, t.visEnd
  3469. // imports
  3470. var defaultEventEnd = t.defaultEventEnd;
  3471. var normalizeEvent = calendar.normalizeEvent; // in EventManager
  3472. var reportEventChange = calendar.reportEventChange;
  3473. // locals
  3474. var eventsByID = {};
  3475. var eventElements = [];
  3476. var eventElementsByID = {};
  3477. var options = calendar.options;
  3478. function opt(name, viewNameOverride) {
  3479. var v = options[name];
  3480. if (typeof v == 'object') {
  3481. return smartProperty(v, viewNameOverride || viewName);
  3482. }
  3483. return v;
  3484. }
  3485. function trigger(name, thisObj) {
  3486. return calendar.trigger.apply(
  3487. calendar,
  3488. [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
  3489. );
  3490. }
  3491. /*
  3492. function setOverflowHidden(bool) {
  3493. element.css('overflow', bool ? 'hidden' : '');
  3494. }
  3495. */
  3496. function isEventDraggable(event) {
  3497. return isEventEditable(event) && !opt('disableDragging');
  3498. }
  3499. function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
  3500. return isEventEditable(event) && !opt('disableResizing');
  3501. }
  3502. function isEventEditable(event) {
  3503. return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
  3504. }
  3505. /* Event Data
  3506. ------------------------------------------------------------------------------*/
  3507. // report when view receives new events
  3508. function reportEvents(events) { // events are already normalized at this point
  3509. eventsByID = {};
  3510. var i, len=events.length, event;
  3511. for (i=0; i<len; i++) {
  3512. event = events[i];
  3513. if (eventsByID[event._id]) {
  3514. eventsByID[event._id].push(event);
  3515. }else{
  3516. eventsByID[event._id] = [event];
  3517. }
  3518. }
  3519. }
  3520. // returns a Date object for an event's end
  3521. function eventEnd(event) {
  3522. return event.end ? cloneDate(event.end) : defaultEventEnd(event);
  3523. }
  3524. /* Event Elements
  3525. ------------------------------------------------------------------------------*/
  3526. // report when view creates an element for an event
  3527. function reportEventElement(event, element) {
  3528. eventElements.push(element);
  3529. if (eventElementsByID[event._id]) {
  3530. eventElementsByID[event._id].push(element);
  3531. }else{
  3532. eventElementsByID[event._id] = [element];
  3533. }
  3534. }
  3535. function reportEventClear() {
  3536. eventElements = [];
  3537. eventElementsByID = {};
  3538. }
  3539. // attaches eventClick, eventMouseover, eventMouseout
  3540. function eventElementHandlers(event, eventElement) {
  3541. eventElement
  3542. .click(function(ev) {
  3543. if (!eventElement.hasClass('ui-draggable-dragging') &&
  3544. !eventElement.hasClass('ui-resizable-resizing')) {
  3545. return trigger('eventClick', this, event, ev);
  3546. }
  3547. })
  3548. .hover(
  3549. function(ev) {
  3550. trigger('eventMouseover', this, event, ev);
  3551. },
  3552. function(ev) {
  3553. trigger('eventMouseout', this, event, ev);
  3554. }
  3555. );
  3556. // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
  3557. // TODO: same for resizing
  3558. }
  3559. function showEvents(event, exceptElement) {
  3560. eachEventElement(event, exceptElement, 'show');
  3561. }
  3562. function hideEvents(event, exceptElement) {
  3563. eachEventElement(event, exceptElement, 'hide');
  3564. }
  3565. function eachEventElement(event, exceptElement, funcName) {
  3566. var elements = eventElementsByID[event._id],
  3567. i, len = elements.length;
  3568. for (i=0; i<len; i++) {
  3569. if (!exceptElement || elements[i][0] != exceptElement[0]) {
  3570. elements[i][funcName]();
  3571. }
  3572. }
  3573. }
  3574. /* Event Modification Reporting
  3575. ---------------------------------------------------------------------------------*/
  3576. function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
  3577. var oldAllDay = event.allDay;
  3578. var eventId = event._id;
  3579. moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
  3580. trigger(
  3581. 'eventDrop',
  3582. e,
  3583. event,
  3584. dayDelta,
  3585. minuteDelta,
  3586. allDay,
  3587. function() {
  3588. // TODO: investigate cases where this inverse technique might not work
  3589. moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
  3590. reportEventChange(eventId);
  3591. },
  3592. ev,
  3593. ui
  3594. );
  3595. reportEventChange(eventId);
  3596. }
  3597. function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
  3598. var eventId = event._id;
  3599. elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
  3600. trigger(
  3601. 'eventResize',
  3602. e,
  3603. event,
  3604. dayDelta,
  3605. minuteDelta,
  3606. function() {
  3607. // TODO: investigate cases where this inverse technique might not work
  3608. elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
  3609. reportEventChange(eventId);
  3610. },
  3611. ev,
  3612. ui
  3613. );
  3614. reportEventChange(eventId);
  3615. }
  3616. /* Event Modification Math
  3617. ---------------------------------------------------------------------------------*/
  3618. function moveEvents(events, dayDelta, minuteDelta, allDay) {
  3619. minuteDelta = minuteDelta || 0;
  3620. for (var e, len=events.length, i=0; i<len; i++) {
  3621. e = events[i];
  3622. if (allDay !== undefined) {
  3623. e.allDay = allDay;
  3624. }
  3625. addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
  3626. if (e.end) {
  3627. e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
  3628. }
  3629. normalizeEvent(e, options);
  3630. }
  3631. }
  3632. function elongateEvents(events, dayDelta, minuteDelta) {
  3633. minuteDelta = minuteDelta || 0;
  3634. for (var e, len=events.length, i=0; i<len; i++) {
  3635. e = events[i];
  3636. e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
  3637. normalizeEvent(e, options);
  3638. }
  3639. }
  3640. }
  3641. function DayEventRenderer() {
  3642. var t = this;
  3643. // exports
  3644. t.renderDaySegs = renderDaySegs;
  3645. t.resizableDayEvent = resizableDayEvent;
  3646. // imports
  3647. var opt = t.opt;
  3648. var trigger = t.trigger;
  3649. var isEventDraggable = t.isEventDraggable;
  3650. var isEventResizable = t.isEventResizable;
  3651. var eventEnd = t.eventEnd;
  3652. var reportEventElement = t.reportEventElement;
  3653. var showEvents = t.showEvents;
  3654. var hideEvents = t.hideEvents;
  3655. var eventResize = t.eventResize;
  3656. var getRowCnt = t.getRowCnt;
  3657. var getColCnt = t.getColCnt;
  3658. var getColWidth = t.getColWidth;
  3659. var allDayRow = t.allDayRow;
  3660. var allDayBounds = t.allDayBounds;
  3661. var colContentLeft = t.colContentLeft;
  3662. var colContentRight = t.colContentRight;
  3663. var dayOfWeekCol = t.dayOfWeekCol;
  3664. var dateCell = t.dateCell;
  3665. var compileDaySegs = t.compileDaySegs;
  3666. var getDaySegmentContainer = t.getDaySegmentContainer;
  3667. var bindDaySeg = t.bindDaySeg; //TODO: streamline this
  3668. var formatDates = t.calendar.formatDates;
  3669. var renderDayOverlay = t.renderDayOverlay;
  3670. var clearOverlays = t.clearOverlays;
  3671. var clearSelection = t.clearSelection;
  3672. /* Rendering
  3673. -----------------------------------------------------------------------------*/
  3674. function renderDaySegs(segs, modifiedEventId) {
  3675. var segmentContainer = getDaySegmentContainer();
  3676. var rowDivs;
  3677. var rowCnt = getRowCnt();
  3678. var colCnt = getColCnt();
  3679. var i = 0;
  3680. var rowI;
  3681. var levelI;
  3682. var colHeights;
  3683. var j;
  3684. var segCnt = segs.length;
  3685. var seg;
  3686. var top;
  3687. var k;
  3688. segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  3689. daySegElementResolve(segs, segmentContainer.children());
  3690. daySegElementReport(segs);
  3691. daySegHandlers(segs, segmentContainer, modifiedEventId);
  3692. daySegCalcHSides(segs);
  3693. daySegSetWidths(segs);
  3694. daySegCalcHeights(segs);
  3695. rowDivs = getRowDivs();
  3696. // set row heights, calculate event tops (in relation to row top)
  3697. for (rowI=0; rowI<rowCnt; rowI++) {
  3698. levelI = 0;
  3699. colHeights = [];
  3700. for (j=0; j<colCnt; j++) {
  3701. colHeights[j] = 0;
  3702. }
  3703. while (i<segCnt && (seg = segs[i]).row == rowI) {
  3704. // loop through segs in a row
  3705. top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
  3706. seg.top = top;
  3707. top += seg.outerHeight;
  3708. for (k=seg.startCol; k<seg.endCol; k++) {
  3709. colHeights[k] = top;
  3710. }
  3711. i++;
  3712. }
  3713. rowDivs[rowI].height(arrayMax(colHeights));
  3714. }
  3715. daySegSetTops(segs, getRowTops(rowDivs));
  3716. }
  3717. function renderTempDaySegs(segs, adjustRow, adjustTop) {
  3718. var tempContainer = $("<div/>");
  3719. var elements;
  3720. var segmentContainer = getDaySegmentContainer();
  3721. var i;
  3722. var segCnt = segs.length;
  3723. var element;
  3724. tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  3725. elements = tempContainer.children();
  3726. segmentContainer.append(elements);
  3727. daySegElementResolve(segs, elements);
  3728. daySegCalcHSides(segs);
  3729. daySegSetWidths(segs);
  3730. daySegCalcHeights(segs);
  3731. daySegSetTops(segs, getRowTops(getRowDivs()));
  3732. elements = [];
  3733. for (i=0; i<segCnt; i++) {
  3734. element = segs[i].element;
  3735. if (element) {
  3736. if (segs[i].row === adjustRow) {
  3737. element.css('top', adjustTop);
  3738. }
  3739. elements.push(element[0]);
  3740. }
  3741. }
  3742. return $(elements);
  3743. }
  3744. function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
  3745. var rtl = opt('isRTL');
  3746. var i;
  3747. var segCnt=segs.length;
  3748. var seg;
  3749. var event;
  3750. var url;
  3751. var classes;
  3752. var bounds = allDayBounds();
  3753. var minLeft = bounds.left;
  3754. var maxLeft = bounds.right;
  3755. var leftCol;
  3756. var rightCol;
  3757. var left;
  3758. var right;
  3759. var skinCss;
  3760. var html = '';
  3761. // calculate desired position/dimensions, create html
  3762. for (i=0; i<segCnt; i++) {
  3763. seg = segs[i];
  3764. event = seg.event;
  3765. classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
  3766. if (isEventDraggable(event)) {
  3767. classes.push('fc-event-draggable');
  3768. }
  3769. if (rtl) {
  3770. if (seg.isStart) {
  3771. classes.push('fc-corner-right');
  3772. }
  3773. if (seg.isEnd) {
  3774. classes.push('fc-corner-left');
  3775. }
  3776. leftCol = dayOfWeekCol(seg.end.getDay()-1);
  3777. rightCol = dayOfWeekCol(seg.start.getDay());
  3778. left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
  3779. right = seg.isStart ? colContentRight(rightCol) : maxLeft;
  3780. }else{
  3781. if (seg.isStart) {
  3782. classes.push('fc-corner-left');
  3783. }
  3784. if (seg.isEnd) {
  3785. classes.push('fc-corner-right');
  3786. }
  3787. leftCol = dayOfWeekCol(seg.start.getDay());
  3788. rightCol = dayOfWeekCol(seg.end.getDay()-1);
  3789. left = seg.isStart ? colContentLeft(leftCol) : minLeft;
  3790. right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
  3791. }
  3792. classes = classes.concat(event.className);
  3793. if (event.source) {
  3794. classes = classes.concat(event.source.className || []);
  3795. }
  3796. url = event.url;
  3797. skinCss = getSkinCss(event, opt);
  3798. if (url) {
  3799. html += "<a href='" + htmlEscape(url) + "'";
  3800. }else{
  3801. html += "<div";
  3802. }
  3803. html +=
  3804. " class='" + classes.join(' ') + "'" +
  3805. " style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
  3806. ">" +
  3807. "<div" +
  3808. " class='fc-event-inner fc-event-skin'" +
  3809. (skinCss ? " style='" + skinCss + "'" : '') +
  3810. ">";
  3811. if (!event.allDay && seg.isStart) {
  3812. html +=
  3813. "<span class='fc-event-time'>" +
  3814. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3815. "</span>";
  3816. }
  3817. html +=
  3818. "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
  3819. "</div>";
  3820. if (seg.isEnd && isEventResizable(event)) {
  3821. html +=
  3822. "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
  3823. "&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
  3824. "</div>";
  3825. }
  3826. html +=
  3827. "</" + (url ? "a" : "div" ) + ">";
  3828. seg.left = left;
  3829. seg.outerWidth = right - left;
  3830. seg.startCol = leftCol;
  3831. seg.endCol = rightCol + 1; // needs to be exclusive
  3832. }
  3833. return html;
  3834. }
  3835. function daySegElementResolve(segs, elements) { // sets seg.element
  3836. var i;
  3837. var segCnt = segs.length;
  3838. var seg;
  3839. var event;
  3840. var element;
  3841. var triggerRes;
  3842. for (i=0; i<segCnt; i++) {
  3843. seg = segs[i];
  3844. event = seg.event;
  3845. element = $(elements[i]); // faster than .eq()
  3846. triggerRes = trigger('eventRender', event, event, element);
  3847. if (triggerRes === false) {
  3848. element.remove();
  3849. }else{
  3850. if (triggerRes && triggerRes !== true) {
  3851. triggerRes = $(triggerRes)
  3852. .css({
  3853. position: 'absolute',
  3854. left: seg.left
  3855. });
  3856. element.replaceWith(triggerRes);
  3857. element = triggerRes;
  3858. }
  3859. seg.element = element;
  3860. }
  3861. }
  3862. }
  3863. function daySegElementReport(segs) {
  3864. var i;
  3865. var segCnt = segs.length;
  3866. var seg;
  3867. var element;
  3868. for (i=0; i<segCnt; i++) {
  3869. seg = segs[i];
  3870. element = seg.element;
  3871. if (element) {
  3872. reportEventElement(seg.event, element);
  3873. }
  3874. }
  3875. }
  3876. function daySegHandlers(segs, segmentContainer, modifiedEventId) {
  3877. var i;
  3878. var segCnt = segs.length;
  3879. var seg;
  3880. var element;
  3881. var event;
  3882. // retrieve elements, run through eventRender callback, bind handlers
  3883. for (i=0; i<segCnt; i++) {
  3884. seg = segs[i];
  3885. element = seg.element;
  3886. if (element) {
  3887. event = seg.event;
  3888. if (event._id === modifiedEventId) {
  3889. bindDaySeg(event, element, seg);
  3890. }else{
  3891. element[0]._fci = i; // for lazySegBind
  3892. }
  3893. }
  3894. }
  3895. lazySegBind(segmentContainer, segs, bindDaySeg);
  3896. }
  3897. function daySegCalcHSides(segs) { // also sets seg.key
  3898. var i;
  3899. var segCnt = segs.length;
  3900. var seg;
  3901. var element;
  3902. var key, val;
  3903. var hsideCache = {};
  3904. // record event horizontal sides
  3905. for (i=0; i<segCnt; i++) {
  3906. seg = segs[i];
  3907. element = seg.element;
  3908. if (element) {
  3909. key = seg.key = cssKey(element[0]);
  3910. val = hsideCache[key];
  3911. if (val === undefined) {
  3912. val = hsideCache[key] = hsides(element, true);
  3913. }
  3914. seg.hsides = val;
  3915. }
  3916. }
  3917. }
  3918. function daySegSetWidths(segs) {
  3919. var i;
  3920. var segCnt = segs.length;
  3921. var seg;
  3922. var element;
  3923. for (i=0; i<segCnt; i++) {
  3924. seg = segs[i];
  3925. element = seg.element;
  3926. if (element) {
  3927. element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3928. }
  3929. }
  3930. }
  3931. function daySegCalcHeights(segs) {
  3932. var i;
  3933. var segCnt = segs.length;
  3934. var seg;
  3935. var element;
  3936. var key, val;
  3937. var vmarginCache = {};
  3938. // record event heights
  3939. for (i=0; i<segCnt; i++) {
  3940. seg = segs[i];
  3941. element = seg.element;
  3942. if (element) {
  3943. key = seg.key; // created in daySegCalcHSides
  3944. val = vmarginCache[key];
  3945. if (val === undefined) {
  3946. val = vmarginCache[key] = vmargins(element);
  3947. }
  3948. seg.outerHeight = element[0].offsetHeight + val;
  3949. }
  3950. }
  3951. }
  3952. function getRowDivs() {
  3953. var i;
  3954. var rowCnt = getRowCnt();
  3955. var rowDivs = [];
  3956. for (i=0; i<rowCnt; i++) {
  3957. rowDivs[i] = allDayRow(i)
  3958. .find('td:first div.fc-day-content > div'); // optimal selector?
  3959. }
  3960. return rowDivs;
  3961. }
  3962. function getRowTops(rowDivs) {
  3963. var i;
  3964. var rowCnt = rowDivs.length;
  3965. var tops = [];
  3966. for (i=0; i<rowCnt; i++) {
  3967. tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
  3968. }
  3969. return tops;
  3970. }
  3971. function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
  3972. var i;
  3973. var segCnt = segs.length;
  3974. var seg;
  3975. var element;
  3976. var event;
  3977. for (i=0; i<segCnt; i++) {
  3978. seg = segs[i];
  3979. element = seg.element;
  3980. if (element) {
  3981. element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
  3982. event = seg.event;
  3983. trigger('eventAfterRender', event, event, element);
  3984. }
  3985. }
  3986. }
  3987. /* Resizing
  3988. -----------------------------------------------------------------------------------*/
  3989. function resizableDayEvent(event, element, seg) {
  3990. var rtl = opt('isRTL');
  3991. var direction = rtl ? 'w' : 'e';
  3992. var handle = element.find('div.ui-resizable-' + direction);
  3993. var isResizing = false;
  3994. // TODO: look into using jquery-ui mouse widget for this stuff
  3995. disableTextSelection(element); // prevent native <a> selection for IE
  3996. element
  3997. .mousedown(function(ev) { // prevent native <a> selection for others
  3998. ev.preventDefault();
  3999. })
  4000. .click(function(ev) {
  4001. if (isResizing) {
  4002. ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
  4003. ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
  4004. // (eventElementHandlers needs to be bound after resizableDayEvent)
  4005. }
  4006. });
  4007. handle.mousedown(function(ev) {
  4008. if (ev.which != 1) {
  4009. return; // needs to be left mouse button
  4010. }
  4011. isResizing = true;
  4012. var hoverListener = t.getHoverListener();
  4013. var rowCnt = getRowCnt();
  4014. var colCnt = getColCnt();
  4015. var dis = rtl ? -1 : 1;
  4016. var dit = rtl ? colCnt-1 : 0;
  4017. var elementTop = element.css('top');
  4018. var dayDelta;
  4019. var helpers;
  4020. var eventCopy = $.extend({}, event);
  4021. var minCell = dateCell(event.start);
  4022. clearSelection();
  4023. $('body')
  4024. .css('cursor', direction + '-resize')
  4025. .one('mouseup', mouseup);
  4026. trigger('eventResizeStart', this, event, ev);
  4027. hoverListener.start(function(cell, origCell) {
  4028. if (cell) {
  4029. var r = Math.max(minCell.row, cell.row);
  4030. var c = cell.col;
  4031. if (rowCnt == 1) {
  4032. r = 0; // hack for all-day area in agenda views
  4033. }
  4034. if (r == minCell.row) {
  4035. if (rtl) {
  4036. c = Math.min(minCell.col, c);
  4037. }else{
  4038. c = Math.max(minCell.col, c);
  4039. }
  4040. }
  4041. dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
  4042. var newEnd = addDays(eventEnd(event), dayDelta, true);
  4043. if (dayDelta) {
  4044. eventCopy.end = newEnd;
  4045. var oldHelpers = helpers;
  4046. helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
  4047. helpers.find('*').css('cursor', direction + '-resize');
  4048. if (oldHelpers) {
  4049. oldHelpers.remove();
  4050. }
  4051. hideEvents(event);
  4052. }else{
  4053. if (helpers) {
  4054. showEvents(event);
  4055. helpers.remove();
  4056. helpers = null;
  4057. }
  4058. }
  4059. clearOverlays();
  4060. renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
  4061. }
  4062. }, ev);
  4063. function mouseup(ev) {
  4064. trigger('eventResizeStop', this, event, ev);
  4065. $('body').css('cursor', '');
  4066. hoverListener.stop();
  4067. clearOverlays();
  4068. if (dayDelta) {
  4069. eventResize(this, event, dayDelta, 0, ev);
  4070. // event redraw will clear helpers
  4071. }
  4072. // otherwise, the drag handler already restored the old events
  4073. setTimeout(function() { // make this happen after the element's click event
  4074. isResizing = false;
  4075. },0);
  4076. }
  4077. });
  4078. }
  4079. }
  4080. //BUG: unselect needs to be triggered when events are dragged+dropped
  4081. function SelectionManager() {
  4082. var t = this;
  4083. // exports
  4084. t.select = select;
  4085. t.unselect = unselect;
  4086. t.reportSelection = reportSelection;
  4087. t.daySelectionMousedown = daySelectionMousedown;
  4088. // imports
  4089. var opt = t.opt;
  4090. var trigger = t.trigger;
  4091. var defaultSelectionEnd = t.defaultSelectionEnd;
  4092. var renderSelection = t.renderSelection;
  4093. var clearSelection = t.clearSelection;
  4094. // locals
  4095. var selected = false;
  4096. // unselectAuto
  4097. if (opt('selectable') && opt('unselectAuto')) {
  4098. $(document).mousedown(function(ev) {
  4099. var ignore = opt('unselectCancel');
  4100. if (ignore) {
  4101. if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
  4102. return;
  4103. }
  4104. }
  4105. unselect(ev);
  4106. });
  4107. }
  4108. function select(startDate, endDate, allDay) {
  4109. unselect();
  4110. if (!endDate) {
  4111. endDate = defaultSelectionEnd(startDate, allDay);
  4112. }
  4113. renderSelection(startDate, endDate, allDay);
  4114. reportSelection(startDate, endDate, allDay);
  4115. }
  4116. function unselect(ev) {
  4117. if (selected) {
  4118. selected = false;
  4119. clearSelection();
  4120. trigger('unselect', null, ev);
  4121. }
  4122. }
  4123. function reportSelection(startDate, endDate, allDay, ev) {
  4124. selected = true;
  4125. trigger('select', null, startDate, endDate, allDay, ev);
  4126. }
  4127. function daySelectionMousedown(ev) { // not really a generic manager method, oh well
  4128. var cellDate = t.cellDate;
  4129. var cellIsAllDay = t.cellIsAllDay;
  4130. var hoverListener = t.getHoverListener();
  4131. var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
  4132. if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
  4133. unselect(ev);
  4134. var _mousedownElement = this;
  4135. var dates;
  4136. hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
  4137. clearSelection();
  4138. if (cell && cellIsAllDay(cell)) {
  4139. dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
  4140. renderSelection(dates[0], dates[1], true);
  4141. }else{
  4142. dates = null;
  4143. }
  4144. }, ev);
  4145. $(document).one('mouseup', function(ev) {
  4146. hoverListener.stop();
  4147. if (dates) {
  4148. if (+dates[0] == +dates[1]) {
  4149. reportDayClick(dates[0], true, ev);
  4150. }
  4151. reportSelection(dates[0], dates[1], true, ev);
  4152. }
  4153. });
  4154. }
  4155. }
  4156. }
  4157. function OverlayManager() {
  4158. var t = this;
  4159. // exports
  4160. t.renderOverlay = renderOverlay;
  4161. t.clearOverlays = clearOverlays;
  4162. // locals
  4163. var usedOverlays = [];
  4164. var unusedOverlays = [];
  4165. function renderOverlay(rect, parent) {
  4166. var e = unusedOverlays.shift();
  4167. if (!e) {
  4168. e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
  4169. }
  4170. if (e[0].parentNode != parent[0]) {
  4171. e.appendTo(parent);
  4172. }
  4173. usedOverlays.push(e.css(rect).show());
  4174. return e;
  4175. }
  4176. function clearOverlays() {
  4177. var e;
  4178. while (e = usedOverlays.shift()) {
  4179. unusedOverlays.push(e.hide().unbind());
  4180. }
  4181. }
  4182. }
  4183. function CoordinateGrid(buildFunc) {
  4184. var t = this;
  4185. var rows;
  4186. var cols;
  4187. t.build = function() {
  4188. rows = [];
  4189. cols = [];
  4190. buildFunc(rows, cols);
  4191. };
  4192. t.cell = function(x, y) {
  4193. var rowCnt = rows.length;
  4194. var colCnt = cols.length;
  4195. var i, r=-1, c=-1;
  4196. for (i=0; i<rowCnt; i++) {
  4197. if (y >= rows[i][0] && y < rows[i][1]) {
  4198. r = i;
  4199. break;
  4200. }
  4201. }
  4202. for (i=0; i<colCnt; i++) {
  4203. if (x >= cols[i][0] && x < cols[i][1]) {
  4204. c = i;
  4205. break;
  4206. }
  4207. }
  4208. return (r>=0 && c>=0) ? { row:r, col:c } : null;
  4209. };
  4210. t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
  4211. var origin = originElement.offset();
  4212. return {
  4213. top: rows[row0][0] - origin.top,
  4214. left: cols[col0][0] - origin.left,
  4215. width: cols[col1][1] - cols[col0][0],
  4216. height: rows[row1][1] - rows[row0][0]
  4217. };
  4218. };
  4219. }
  4220. function HoverListener(coordinateGrid) {
  4221. var t = this;
  4222. var bindType;
  4223. var change;
  4224. var firstCell;
  4225. var cell;
  4226. t.start = function(_change, ev, _bindType) {
  4227. change = _change;
  4228. firstCell = cell = null;
  4229. coordinateGrid.build();
  4230. mouse(ev);
  4231. bindType = _bindType || 'mousemove';
  4232. $(document).bind(bindType, mouse);
  4233. };
  4234. function mouse(ev) {
  4235. _fixUIEvent(ev); // see below
  4236. var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
  4237. if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
  4238. if (newCell) {
  4239. if (!firstCell) {
  4240. firstCell = newCell;
  4241. }
  4242. change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
  4243. }else{
  4244. change(newCell, firstCell);
  4245. }
  4246. cell = newCell;
  4247. }
  4248. }
  4249. t.stop = function() {
  4250. $(document).unbind(bindType, mouse);
  4251. return cell;
  4252. };
  4253. }
  4254. // this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
  4255. // upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
  4256. // but keep this in here for 1.8.16 users
  4257. // and maybe remove it down the line
  4258. function _fixUIEvent(event) { // for issue 1168
  4259. if (event.pageX === undefined) {
  4260. event.pageX = event.originalEvent.pageX;
  4261. event.pageY = event.originalEvent.pageY;
  4262. }
  4263. }
  4264. function HorizontalPositionCache(getElement) {
  4265. var t = this,
  4266. elements = {},
  4267. lefts = {},
  4268. rights = {};
  4269. function e(i) {
  4270. return elements[i] = elements[i] || getElement(i);
  4271. }
  4272. t.left = function(i) {
  4273. return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
  4274. };
  4275. t.right = function(i) {
  4276. return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
  4277. };
  4278. t.clear = function() {
  4279. elements = {};
  4280. lefts = {};
  4281. rights = {};
  4282. };
  4283. }
  4284. })(jQuery);