fullcalendar.orig.js 122 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227
  1. /**
  2. * @preserve
  3. * FullCalendar v1.5.4
  4. * http://arshaw.com/fullcalendar/
  5. *
  6. * Use fullcalendar.css for basic styling.
  7. * For event drag & drop, requires jQuery UI draggable.
  8. * For event resizing, requires jQuery UI resizable.
  9. *
  10. * Copyright (c) 2011 Adam Shaw
  11. * Dual licensed under the MIT and GPL licenses, located in
  12. * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
  13. *
  14. * Date: Tue Sep 4 23:38:33 2012 -0700
  15. *
  16. */
  17. (function($, undefined) {
  18. var defaults = {
  19. // display
  20. defaultView: 'month',
  21. aspectRatio: 1.35,
  22. header: {
  23. left: 'title',
  24. center: '',
  25. right: 'today prev,next'
  26. },
  27. weekends: true,
  28. // 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.4" };
  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. function hsides(element, includeMargins) {
  1423. return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
  1424. }
  1425. function hpadding(element) {
  1426. return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +
  1427. (parseFloat($.css(element[0], 'paddingRight', true)) || 0);
  1428. }
  1429. function hmargins(element) {
  1430. return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +
  1431. (parseFloat($.css(element[0], 'marginRight', true)) || 0);
  1432. }
  1433. function hborders(element) {
  1434. return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +
  1435. (parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);
  1436. }
  1437. function vsides(element, includeMargins) {
  1438. return vpadding(element) + vborders(element) + (includeMargins ? vmargins(element) : 0);
  1439. }
  1440. function vpadding(element) {
  1441. return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +
  1442. (parseFloat($.css(element[0], 'paddingBottom', true)) || 0);
  1443. }
  1444. function vmargins(element) {
  1445. return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +
  1446. (parseFloat($.css(element[0], 'marginBottom', true)) || 0);
  1447. }
  1448. function vborders(element) {
  1449. return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +
  1450. (parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);
  1451. }
  1452. function setMinHeight(element, height) {
  1453. height = (typeof height == 'number' ? height + 'px' : height);
  1454. element.each(function(i, _element) {
  1455. _element.style.cssText += ';min-height:' + height + ';_height:' + height;
  1456. // why can't we just use .css() ? i forget
  1457. });
  1458. }
  1459. /* Misc Utils
  1460. -----------------------------------------------------------------------------*/
  1461. //TODO: arraySlice
  1462. //TODO: isFunction, grep ?
  1463. function noop() { }
  1464. function cmp(a, b) {
  1465. return a - b;
  1466. }
  1467. function arrayMax(a) {
  1468. return Math.max.apply(Math, a);
  1469. }
  1470. function zeroPad(n) {
  1471. return (n < 10 ? '0' : '') + n;
  1472. }
  1473. function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
  1474. if (obj[name] !== undefined) {
  1475. return obj[name];
  1476. }
  1477. var parts = name.split(/(?=[A-Z])/),
  1478. i=parts.length-1, res;
  1479. for (; i>=0; i--) {
  1480. res = obj[parts[i].toLowerCase()];
  1481. if (res !== undefined) {
  1482. return res;
  1483. }
  1484. }
  1485. return obj[''];
  1486. }
  1487. function htmlEscape(s) {
  1488. return s.replace(/&/g, '&amp;')
  1489. .replace(/</g, '&lt;')
  1490. .replace(/>/g, '&gt;')
  1491. .replace(/'/g, '&#039;')
  1492. .replace(/"/g, '&quot;')
  1493. .replace(/\n/g, '<br />');
  1494. }
  1495. function cssKey(_element) {
  1496. return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
  1497. }
  1498. function disableTextSelection(element) {
  1499. element
  1500. .attr('unselectable', 'on')
  1501. .css('MozUserSelect', 'none')
  1502. .bind('selectstart.ui', function() { return false; });
  1503. }
  1504. /*
  1505. function enableTextSelection(element) {
  1506. element
  1507. .attr('unselectable', 'off')
  1508. .css('MozUserSelect', '')
  1509. .unbind('selectstart.ui');
  1510. }
  1511. */
  1512. function markFirstLast(e) {
  1513. e.children()
  1514. .removeClass('fc-first fc-last')
  1515. .filter(':first-child')
  1516. .addClass('fc-first')
  1517. .end()
  1518. .filter(':last-child')
  1519. .addClass('fc-last');
  1520. }
  1521. function setDayID(cell, date) {
  1522. cell.each(function(i, _cell) {
  1523. _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
  1524. // TODO: make a way that doesn't rely on order of classes
  1525. });
  1526. }
  1527. function getSkinCss(event, opt) {
  1528. var source = event.source || {};
  1529. var eventColor = event.color;
  1530. var sourceColor = source.color;
  1531. var optionColor = opt('eventColor');
  1532. var backgroundColor =
  1533. event.backgroundColor ||
  1534. eventColor ||
  1535. source.backgroundColor ||
  1536. sourceColor ||
  1537. opt('eventBackgroundColor') ||
  1538. optionColor;
  1539. var borderColor =
  1540. event.borderColor ||
  1541. eventColor ||
  1542. source.borderColor ||
  1543. sourceColor ||
  1544. opt('eventBorderColor') ||
  1545. optionColor;
  1546. var textColor =
  1547. event.textColor ||
  1548. source.textColor ||
  1549. opt('eventTextColor');
  1550. var statements = [];
  1551. if (backgroundColor) {
  1552. statements.push('background-color:' + backgroundColor);
  1553. }
  1554. if (borderColor) {
  1555. statements.push('border-color:' + borderColor);
  1556. }
  1557. if (textColor) {
  1558. statements.push('color:' + textColor);
  1559. }
  1560. return statements.join(';');
  1561. }
  1562. function applyAll(functions, thisObj, args) {
  1563. if ($.isFunction(functions)) {
  1564. functions = [ functions ];
  1565. }
  1566. if (functions) {
  1567. var i;
  1568. var ret;
  1569. for (i=0; i<functions.length; i++) {
  1570. ret = functions[i].apply(thisObj, args) || ret;
  1571. }
  1572. return ret;
  1573. }
  1574. }
  1575. function firstDefined() {
  1576. for (var i=0; i<arguments.length; i++) {
  1577. if (arguments[i] !== undefined) {
  1578. return arguments[i];
  1579. }
  1580. }
  1581. }
  1582. fcViews.month = MonthView;
  1583. function MonthView(element, calendar) {
  1584. var t = this;
  1585. // exports
  1586. t.render = render;
  1587. // imports
  1588. BasicView.call(t, element, calendar, 'month');
  1589. var opt = t.opt;
  1590. var renderBasic = t.renderBasic;
  1591. var formatDate = calendar.formatDate;
  1592. function render(date, delta) {
  1593. if (delta) {
  1594. addMonths(date, delta);
  1595. date.setDate(1);
  1596. }
  1597. var start = cloneDate(date, true);
  1598. start.setDate(1);
  1599. var end = addMonths(cloneDate(start), 1);
  1600. var visStart = cloneDate(start);
  1601. var visEnd = cloneDate(end);
  1602. var firstDay = opt('firstDay');
  1603. var nwe = opt('weekends') ? 0 : 1;
  1604. if (nwe) {
  1605. skipWeekend(visStart);
  1606. skipWeekend(visEnd, -1, true);
  1607. }
  1608. addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
  1609. addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
  1610. var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
  1611. if (opt('weekMode') == 'fixed') {
  1612. addDays(visEnd, (6 - rowCnt) * 7);
  1613. rowCnt = 6;
  1614. }
  1615. t.title = formatDate(start, opt('titleFormat'));
  1616. t.start = start;
  1617. t.end = end;
  1618. t.visStart = visStart;
  1619. t.visEnd = visEnd;
  1620. renderBasic(6, rowCnt, nwe ? 5 : 7, true);
  1621. }
  1622. }
  1623. fcViews.basicWeek = BasicWeekView;
  1624. function BasicWeekView(element, calendar) {
  1625. var t = this;
  1626. // exports
  1627. t.render = render;
  1628. // imports
  1629. BasicView.call(t, element, calendar, 'basicWeek');
  1630. var opt = t.opt;
  1631. var renderBasic = t.renderBasic;
  1632. var formatDates = calendar.formatDates;
  1633. function render(date, delta) {
  1634. if (delta) {
  1635. addDays(date, delta * 7);
  1636. }
  1637. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  1638. var end = addDays(cloneDate(start), 7);
  1639. var visStart = cloneDate(start);
  1640. var visEnd = cloneDate(end);
  1641. var weekends = opt('weekends');
  1642. if (!weekends) {
  1643. skipWeekend(visStart);
  1644. skipWeekend(visEnd, -1, true);
  1645. }
  1646. t.title = formatDates(
  1647. visStart,
  1648. addDays(cloneDate(visEnd), -1),
  1649. opt('titleFormat')
  1650. );
  1651. t.start = start;
  1652. t.end = end;
  1653. t.visStart = visStart;
  1654. t.visEnd = visEnd;
  1655. renderBasic(1, 1, weekends ? 7 : 5, false);
  1656. }
  1657. }
  1658. fcViews.basicDay = BasicDayView;
  1659. //TODO: when calendar's date starts out on a weekend, shouldn't happen
  1660. function BasicDayView(element, calendar) {
  1661. var t = this;
  1662. // exports
  1663. t.render = render;
  1664. // imports
  1665. BasicView.call(t, element, calendar, 'basicDay');
  1666. var opt = t.opt;
  1667. var renderBasic = t.renderBasic;
  1668. var formatDate = calendar.formatDate;
  1669. function render(date, delta) {
  1670. if (delta) {
  1671. addDays(date, delta);
  1672. if (!opt('weekends')) {
  1673. skipWeekend(date, delta < 0 ? -1 : 1);
  1674. }
  1675. }
  1676. t.title = formatDate(date, opt('titleFormat'));
  1677. t.start = t.visStart = cloneDate(date, true);
  1678. t.end = t.visEnd = addDays(cloneDate(t.start), 1);
  1679. renderBasic(1, 1, 1, false);
  1680. }
  1681. }
  1682. setDefaults({
  1683. weekMode: 'fixed'
  1684. });
  1685. function BasicView(element, calendar, viewName) {
  1686. var t = this;
  1687. // exports
  1688. t.renderBasic = renderBasic;
  1689. t.setHeight = setHeight;
  1690. t.setWidth = setWidth;
  1691. t.renderDayOverlay = renderDayOverlay;
  1692. t.defaultSelectionEnd = defaultSelectionEnd;
  1693. t.renderSelection = renderSelection;
  1694. t.clearSelection = clearSelection;
  1695. t.reportDayClick = reportDayClick; // for selection (kinda hacky)
  1696. t.dragStart = dragStart;
  1697. t.dragStop = dragStop;
  1698. t.defaultEventEnd = defaultEventEnd;
  1699. t.getHoverListener = function() { return hoverListener };
  1700. t.colContentLeft = colContentLeft;
  1701. t.colContentRight = colContentRight;
  1702. t.dayOfWeekCol = dayOfWeekCol;
  1703. t.dateCell = dateCell;
  1704. t.cellDate = cellDate;
  1705. t.cellIsAllDay = function() { return true };
  1706. t.allDayRow = allDayRow;
  1707. t.allDayBounds = allDayBounds;
  1708. t.getRowCnt = function() { return rowCnt };
  1709. t.getColCnt = function() { return colCnt };
  1710. t.getColWidth = function() { return colWidth };
  1711. t.getDaySegmentContainer = function() { return daySegmentContainer };
  1712. // imports
  1713. View.call(t, element, calendar, viewName);
  1714. OverlayManager.call(t);
  1715. SelectionManager.call(t);
  1716. BasicEventRenderer.call(t);
  1717. var opt = t.opt;
  1718. var trigger = t.trigger;
  1719. var clearEvents = t.clearEvents;
  1720. var renderOverlay = t.renderOverlay;
  1721. var clearOverlays = t.clearOverlays;
  1722. var daySelectionMousedown = t.daySelectionMousedown;
  1723. var formatDate = calendar.formatDate;
  1724. // locals
  1725. var head;
  1726. var headCells;
  1727. var body;
  1728. var bodyRows;
  1729. var bodyCells;
  1730. var bodyFirstCells;
  1731. var bodyCellTopInners;
  1732. var daySegmentContainer;
  1733. var viewWidth;
  1734. var viewHeight;
  1735. var colWidth;
  1736. var rowCnt, colCnt;
  1737. var coordinateGrid;
  1738. var hoverListener;
  1739. var colContentPositions;
  1740. var rtl, dis, dit;
  1741. var firstDay;
  1742. var nwe;
  1743. var tm;
  1744. var colFormat;
  1745. /* Rendering
  1746. ------------------------------------------------------------*/
  1747. disableTextSelection(element.addClass('fc-grid'));
  1748. function renderBasic(maxr, r, c, showNumbers) {
  1749. rowCnt = r;
  1750. colCnt = c;
  1751. updateOptions();
  1752. var firstTime = !body;
  1753. if (firstTime) {
  1754. buildSkeleton(maxr, showNumbers);
  1755. }else{
  1756. clearEvents();
  1757. }
  1758. updateCells(firstTime);
  1759. }
  1760. function updateOptions() {
  1761. rtl = opt('isRTL');
  1762. if (rtl) {
  1763. dis = -1;
  1764. dit = colCnt - 1;
  1765. }else{
  1766. dis = 1;
  1767. dit = 0;
  1768. }
  1769. firstDay = opt('firstDay');
  1770. nwe = opt('weekends') ? 0 : 1;
  1771. tm = opt('theme') ? 'ui' : 'fc';
  1772. colFormat = opt('columnFormat');
  1773. }
  1774. function buildSkeleton(maxRowCnt, showNumbers) {
  1775. var s;
  1776. var headerClass = tm + "-widget-header";
  1777. var contentClass = tm + "-widget-content";
  1778. var i, j;
  1779. var table;
  1780. s =
  1781. "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  1782. "<thead>" +
  1783. "<tr>";
  1784. for (i=0; i<colCnt; i++) {
  1785. s +=
  1786. "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
  1787. }
  1788. s +=
  1789. "</tr>" +
  1790. "</thead>" +
  1791. "<tbody>";
  1792. for (i=0; i<maxRowCnt; i++) {
  1793. s +=
  1794. "<tr class='fc-week" + i + "'>";
  1795. for (j=0; j<colCnt; j++) {
  1796. s +=
  1797. "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
  1798. "<div>" +
  1799. (showNumbers ?
  1800. "<div class='fc-day-number'/>" :
  1801. ''
  1802. ) +
  1803. "<div class='fc-day-content'>" +
  1804. "<div style='position:relative'>&nbsp;</div>" +
  1805. "</div>" +
  1806. "</div>" +
  1807. "</td>";
  1808. }
  1809. s +=
  1810. "</tr>";
  1811. }
  1812. s +=
  1813. "</tbody>" +
  1814. "</table>";
  1815. table = $(s).appendTo(element);
  1816. head = table.find('thead');
  1817. headCells = head.find('th');
  1818. body = table.find('tbody');
  1819. bodyRows = body.find('tr');
  1820. bodyCells = body.find('td');
  1821. bodyFirstCells = bodyCells.filter(':first-child');
  1822. bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
  1823. markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
  1824. markFirstLast(bodyRows); // marks first+last td's
  1825. bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
  1826. dayBind(bodyCells);
  1827. daySegmentContainer =
  1828. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  1829. .appendTo(element);
  1830. }
  1831. function updateCells(firstTime) {
  1832. var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
  1833. var month = t.start.getMonth();
  1834. var today = clearTime(new Date());
  1835. var cell;
  1836. var date;
  1837. var row;
  1838. if (dowDirty) {
  1839. headCells.each(function(i, _cell) {
  1840. cell = $(_cell);
  1841. date = indexDate(i);
  1842. cell.html(formatDate(date, colFormat));
  1843. setDayID(cell, date);
  1844. });
  1845. }
  1846. bodyCells.each(function(i, _cell) {
  1847. cell = $(_cell);
  1848. date = indexDate(i);
  1849. if (date.getMonth() == month) {
  1850. cell.removeClass('fc-other-month');
  1851. }else{
  1852. cell.addClass('fc-other-month');
  1853. }
  1854. if (+date == +today) {
  1855. cell.addClass(tm + '-state-highlight fc-today');
  1856. }else{
  1857. cell.removeClass(tm + '-state-highlight fc-today');
  1858. }
  1859. cell.find('div.fc-day-number').text(date.getDate());
  1860. if (dowDirty) {
  1861. setDayID(cell, date);
  1862. }
  1863. });
  1864. bodyRows.each(function(i, _row) {
  1865. row = $(_row);
  1866. if (i < rowCnt) {
  1867. row.show();
  1868. if (i == rowCnt-1) {
  1869. row.addClass('fc-last');
  1870. }else{
  1871. row.removeClass('fc-last');
  1872. }
  1873. }else{
  1874. row.hide();
  1875. }
  1876. });
  1877. }
  1878. function setHeight(height) {
  1879. viewHeight = height;
  1880. var bodyHeight = viewHeight - head.height();
  1881. var rowHeight;
  1882. var rowHeightLast;
  1883. var cell;
  1884. if (opt('weekMode') == 'variable') {
  1885. rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
  1886. }else{
  1887. rowHeight = Math.floor(bodyHeight / rowCnt);
  1888. rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
  1889. }
  1890. bodyFirstCells.each(function(i, _cell) {
  1891. if (i < rowCnt) {
  1892. cell = $(_cell);
  1893. setMinHeight(
  1894. cell.find('> div'),
  1895. (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
  1896. );
  1897. }
  1898. });
  1899. }
  1900. function setWidth(width) {
  1901. viewWidth = width;
  1902. colContentPositions.clear();
  1903. colWidth = Math.floor(viewWidth / colCnt);
  1904. setOuterWidth(headCells.slice(0, -1), colWidth);
  1905. }
  1906. /* Day clicking and binding
  1907. -----------------------------------------------------------*/
  1908. function dayBind(days) {
  1909. days.click(dayClick)
  1910. .mousedown(daySelectionMousedown);
  1911. }
  1912. function dayClick(ev) {
  1913. if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  1914. var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
  1915. var date = indexDate(index);
  1916. trigger('dayClick', this, date, true, ev);
  1917. }
  1918. }
  1919. /* Semi-transparent Overlay Helpers
  1920. ------------------------------------------------------*/
  1921. function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
  1922. if (refreshCoordinateGrid) {
  1923. coordinateGrid.build();
  1924. }
  1925. var rowStart = cloneDate(t.visStart);
  1926. var rowEnd = addDays(cloneDate(rowStart), colCnt);
  1927. for (var i=0; i<rowCnt; i++) {
  1928. var stretchStart = new Date(Math.max(rowStart, overlayStart));
  1929. var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
  1930. if (stretchStart < stretchEnd) {
  1931. var colStart, colEnd;
  1932. if (rtl) {
  1933. colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
  1934. colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
  1935. }else{
  1936. colStart = dayDiff(stretchStart, rowStart);
  1937. colEnd = dayDiff(stretchEnd, rowStart);
  1938. }
  1939. dayBind(
  1940. renderCellOverlay(i, colStart, i, colEnd-1)
  1941. );
  1942. }
  1943. addDays(rowStart, 7);
  1944. addDays(rowEnd, 7);
  1945. }
  1946. }
  1947. function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
  1948. var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
  1949. return renderOverlay(rect, element);
  1950. }
  1951. /* Selection
  1952. -----------------------------------------------------------------------*/
  1953. function defaultSelectionEnd(startDate, allDay) {
  1954. return cloneDate(startDate);
  1955. }
  1956. function renderSelection(startDate, endDate, allDay) {
  1957. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
  1958. }
  1959. function clearSelection() {
  1960. clearOverlays();
  1961. }
  1962. function reportDayClick(date, allDay, ev) {
  1963. var cell = dateCell(date);
  1964. var _element = bodyCells[cell.row*colCnt + cell.col];
  1965. trigger('dayClick', _element, date, allDay, ev);
  1966. }
  1967. /* External Dragging
  1968. -----------------------------------------------------------------------*/
  1969. function dragStart(_dragElement, ev, ui) {
  1970. hoverListener.start(function(cell) {
  1971. clearOverlays();
  1972. if (cell) {
  1973. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  1974. }
  1975. }, ev);
  1976. }
  1977. function dragStop(_dragElement, ev, ui) {
  1978. var cell = hoverListener.stop();
  1979. clearOverlays();
  1980. if (cell) {
  1981. var d = cellDate(cell);
  1982. trigger('drop', _dragElement, d, true, ev, ui);
  1983. }
  1984. }
  1985. /* Utilities
  1986. --------------------------------------------------------*/
  1987. function defaultEventEnd(event) {
  1988. return cloneDate(event.start);
  1989. }
  1990. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  1991. var e, n, p;
  1992. headCells.each(function(i, _e) {
  1993. e = $(_e);
  1994. n = e.offset().left;
  1995. if (i) {
  1996. p[1] = n;
  1997. }
  1998. p = [n];
  1999. cols[i] = p;
  2000. });
  2001. p[1] = n + e.outerWidth();
  2002. bodyRows.each(function(i, _e) {
  2003. if (i < rowCnt) {
  2004. e = $(_e);
  2005. n = e.offset().top;
  2006. if (i) {
  2007. p[1] = n;
  2008. }
  2009. p = [n];
  2010. rows[i] = p;
  2011. }
  2012. });
  2013. p[1] = n + e.outerHeight();
  2014. });
  2015. hoverListener = new HoverListener(coordinateGrid);
  2016. colContentPositions = new HorizontalPositionCache(function(col) {
  2017. return bodyCellTopInners.eq(col);
  2018. });
  2019. function colContentLeft(col) {
  2020. return colContentPositions.left(col);
  2021. }
  2022. function colContentRight(col) {
  2023. return colContentPositions.right(col);
  2024. }
  2025. function dateCell(date) {
  2026. return {
  2027. row: Math.floor(dayDiff(date, t.visStart) / 7),
  2028. col: dayOfWeekCol(date.getDay())
  2029. };
  2030. }
  2031. function cellDate(cell) {
  2032. return _cellDate(cell.row, cell.col);
  2033. }
  2034. function _cellDate(row, col) {
  2035. return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
  2036. // what about weekends in middle of week?
  2037. }
  2038. function indexDate(index) {
  2039. return _cellDate(Math.floor(index/colCnt), index%colCnt);
  2040. }
  2041. function dayOfWeekCol(dayOfWeek) {
  2042. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
  2043. }
  2044. function allDayRow(i) {
  2045. return bodyRows.eq(i);
  2046. }
  2047. function allDayBounds(i) {
  2048. return {
  2049. left: 0,
  2050. right: viewWidth
  2051. };
  2052. }
  2053. }
  2054. function BasicEventRenderer() {
  2055. var t = this;
  2056. // exports
  2057. t.renderEvents = renderEvents;
  2058. t.compileDaySegs = compileSegs; // for DayEventRenderer
  2059. t.clearEvents = clearEvents;
  2060. t.bindDaySeg = bindDaySeg;
  2061. // imports
  2062. DayEventRenderer.call(t);
  2063. var opt = t.opt;
  2064. var trigger = t.trigger;
  2065. //var setOverflowHidden = t.setOverflowHidden;
  2066. var isEventDraggable = t.isEventDraggable;
  2067. var isEventResizable = t.isEventResizable;
  2068. var reportEvents = t.reportEvents;
  2069. var reportEventClear = t.reportEventClear;
  2070. var eventElementHandlers = t.eventElementHandlers;
  2071. var showEvents = t.showEvents;
  2072. var hideEvents = t.hideEvents;
  2073. var eventDrop = t.eventDrop;
  2074. var getDaySegmentContainer = t.getDaySegmentContainer;
  2075. var getHoverListener = t.getHoverListener;
  2076. var renderDayOverlay = t.renderDayOverlay;
  2077. var clearOverlays = t.clearOverlays;
  2078. var getRowCnt = t.getRowCnt;
  2079. var getColCnt = t.getColCnt;
  2080. var renderDaySegs = t.renderDaySegs;
  2081. var resizableDayEvent = t.resizableDayEvent;
  2082. /* Rendering
  2083. --------------------------------------------------------------------*/
  2084. function renderEvents(events, modifiedEventId) {
  2085. reportEvents(events);
  2086. renderDaySegs(compileSegs(events), modifiedEventId);
  2087. }
  2088. function clearEvents() {
  2089. reportEventClear();
  2090. getDaySegmentContainer().empty();
  2091. }
  2092. function compileSegs(events) {
  2093. var rowCnt = getRowCnt(),
  2094. colCnt = getColCnt(),
  2095. d1 = cloneDate(t.visStart),
  2096. d2 = addDays(cloneDate(d1), colCnt),
  2097. visEventsEnds = $.map(events, exclEndDay),
  2098. i, row,
  2099. j, level,
  2100. k, seg,
  2101. segs=[];
  2102. for (i=0; i<rowCnt; i++) {
  2103. row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
  2104. for (j=0; j<row.length; j++) {
  2105. level = row[j];
  2106. for (k=0; k<level.length; k++) {
  2107. seg = level[k];
  2108. seg.row = i;
  2109. seg.level = j; // not needed anymore
  2110. segs.push(seg);
  2111. }
  2112. }
  2113. addDays(d1, 7);
  2114. addDays(d2, 7);
  2115. }
  2116. return segs;
  2117. }
  2118. function bindDaySeg(event, eventElement, seg) {
  2119. if (isEventDraggable(event)) {
  2120. draggableDayEvent(event, eventElement);
  2121. }
  2122. if (seg.isEnd && isEventResizable(event)) {
  2123. resizableDayEvent(event, eventElement, seg);
  2124. }
  2125. eventElementHandlers(event, eventElement);
  2126. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  2127. }
  2128. /* Dragging
  2129. ----------------------------------------------------------------------------*/
  2130. function draggableDayEvent(event, eventElement) {
  2131. var hoverListener = getHoverListener();
  2132. var dayDelta;
  2133. eventElement.draggable({
  2134. zIndex: 9,
  2135. delay: 50,
  2136. opacity: opt('dragOpacity'),
  2137. revertDuration: opt('dragRevertDuration'),
  2138. start: function(ev, ui) {
  2139. trigger('eventDragStart', eventElement, event, ev, ui);
  2140. hideEvents(event, eventElement);
  2141. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  2142. eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
  2143. clearOverlays();
  2144. if (cell) {
  2145. //setOverflowHidden(true);
  2146. dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
  2147. renderDayOverlay(
  2148. addDays(cloneDate(event.start), dayDelta),
  2149. addDays(exclEndDay(event), dayDelta)
  2150. );
  2151. }else{
  2152. //setOverflowHidden(false);
  2153. dayDelta = 0;
  2154. }
  2155. }, ev, 'drag');
  2156. },
  2157. stop: function(ev, ui) {
  2158. hoverListener.stop();
  2159. clearOverlays();
  2160. trigger('eventDragStop', eventElement, event, ev, ui);
  2161. if (dayDelta) {
  2162. eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
  2163. }else{
  2164. eventElement.css('filter', ''); // clear IE opacity side-effects
  2165. showEvents(event, eventElement);
  2166. }
  2167. //setOverflowHidden(false);
  2168. }
  2169. });
  2170. }
  2171. }
  2172. fcViews.agendaWeek = AgendaWeekView;
  2173. function AgendaWeekView(element, calendar) {
  2174. var t = this;
  2175. // exports
  2176. t.render = render;
  2177. // imports
  2178. AgendaView.call(t, element, calendar, 'agendaWeek');
  2179. var opt = t.opt;
  2180. var renderAgenda = t.renderAgenda;
  2181. var formatDates = calendar.formatDates;
  2182. function render(date, delta) {
  2183. if (delta) {
  2184. addDays(date, delta * 7);
  2185. }
  2186. var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  2187. var end = addDays(cloneDate(start), 7);
  2188. var visStart = cloneDate(start);
  2189. var visEnd = cloneDate(end);
  2190. var weekends = opt('weekends');
  2191. if (!weekends) {
  2192. skipWeekend(visStart);
  2193. skipWeekend(visEnd, -1, true);
  2194. }
  2195. t.title = formatDates(
  2196. visStart,
  2197. addDays(cloneDate(visEnd), -1),
  2198. opt('titleFormat')
  2199. );
  2200. t.start = start;
  2201. t.end = end;
  2202. t.visStart = visStart;
  2203. t.visEnd = visEnd;
  2204. renderAgenda(weekends ? 7 : 5);
  2205. }
  2206. }
  2207. fcViews.agendaDay = AgendaDayView;
  2208. function AgendaDayView(element, calendar) {
  2209. var t = this;
  2210. // exports
  2211. t.render = render;
  2212. // imports
  2213. AgendaView.call(t, element, calendar, 'agendaDay');
  2214. var opt = t.opt;
  2215. var renderAgenda = t.renderAgenda;
  2216. var formatDate = calendar.formatDate;
  2217. function render(date, delta) {
  2218. if (delta) {
  2219. addDays(date, delta);
  2220. if (!opt('weekends')) {
  2221. skipWeekend(date, delta < 0 ? -1 : 1);
  2222. }
  2223. }
  2224. var start = cloneDate(date, true);
  2225. var end = addDays(cloneDate(start), 1);
  2226. t.title = formatDate(date, opt('titleFormat'));
  2227. t.start = t.visStart = start;
  2228. t.end = t.visEnd = end;
  2229. renderAgenda(1);
  2230. }
  2231. }
  2232. setDefaults({
  2233. allDaySlot: true,
  2234. allDayText: 'all-day',
  2235. firstHour: 6,
  2236. slotMinutes: 30,
  2237. defaultEventMinutes: 120,
  2238. axisFormat: 'h(:mm)tt',
  2239. timeFormat: {
  2240. agenda: 'h:mm{ - h:mm}'
  2241. },
  2242. dragOpacity: {
  2243. agenda: .5
  2244. },
  2245. minTime: 0,
  2246. maxTime: 24
  2247. });
  2248. // TODO: make it work in quirks mode (event corners, all-day height)
  2249. // TODO: test liquid width, especially in IE6
  2250. function AgendaView(element, calendar, viewName) {
  2251. var t = this;
  2252. // exports
  2253. t.renderAgenda = renderAgenda;
  2254. t.setWidth = setWidth;
  2255. t.setHeight = setHeight;
  2256. t.beforeHide = beforeHide;
  2257. t.afterShow = afterShow;
  2258. t.defaultEventEnd = defaultEventEnd;
  2259. t.timePosition = timePosition;
  2260. t.dayOfWeekCol = dayOfWeekCol;
  2261. t.dateCell = dateCell;
  2262. t.cellDate = cellDate;
  2263. t.cellIsAllDay = cellIsAllDay;
  2264. t.allDayRow = getAllDayRow;
  2265. t.allDayBounds = allDayBounds;
  2266. t.getHoverListener = function() { return hoverListener };
  2267. t.colContentLeft = colContentLeft;
  2268. t.colContentRight = colContentRight;
  2269. t.getDaySegmentContainer = function() { return daySegmentContainer };
  2270. t.getSlotSegmentContainer = function() { return slotSegmentContainer };
  2271. t.getMinMinute = function() { return minMinute };
  2272. t.getMaxMinute = function() { return maxMinute };
  2273. t.getBodyContent = function() { return slotContent }; // !!??
  2274. t.getRowCnt = function() { return 1 };
  2275. t.getColCnt = function() { return colCnt };
  2276. t.getColWidth = function() { return colWidth };
  2277. t.getSlotHeight = function() { return slotHeight };
  2278. t.defaultSelectionEnd = defaultSelectionEnd;
  2279. t.renderDayOverlay = renderDayOverlay;
  2280. t.renderSelection = renderSelection;
  2281. t.clearSelection = clearSelection;
  2282. t.reportDayClick = reportDayClick; // selection mousedown hack
  2283. t.dragStart = dragStart;
  2284. t.dragStop = dragStop;
  2285. // imports
  2286. View.call(t, element, calendar, viewName);
  2287. OverlayManager.call(t);
  2288. SelectionManager.call(t);
  2289. AgendaEventRenderer.call(t);
  2290. var opt = t.opt;
  2291. var trigger = t.trigger;
  2292. var clearEvents = t.clearEvents;
  2293. var renderOverlay = t.renderOverlay;
  2294. var clearOverlays = t.clearOverlays;
  2295. var reportSelection = t.reportSelection;
  2296. var unselect = t.unselect;
  2297. var daySelectionMousedown = t.daySelectionMousedown;
  2298. var slotSegHtml = t.slotSegHtml;
  2299. var formatDate = calendar.formatDate;
  2300. // locals
  2301. var dayTable;
  2302. var dayHead;
  2303. var dayHeadCells;
  2304. var dayBody;
  2305. var dayBodyCells;
  2306. var dayBodyCellInners;
  2307. var dayBodyFirstCell;
  2308. var dayBodyFirstCellStretcher;
  2309. var slotLayer;
  2310. var daySegmentContainer;
  2311. var allDayTable;
  2312. var allDayRow;
  2313. var slotScroller;
  2314. var slotContent;
  2315. var slotSegmentContainer;
  2316. var slotTable;
  2317. var slotTableFirstInner;
  2318. var axisFirstCells;
  2319. var gutterCells;
  2320. var selectionHelper;
  2321. var viewWidth;
  2322. var viewHeight;
  2323. var axisWidth;
  2324. var colWidth;
  2325. var gutterWidth;
  2326. var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
  2327. var savedScrollTop;
  2328. var colCnt;
  2329. var slotCnt;
  2330. var coordinateGrid;
  2331. var hoverListener;
  2332. var colContentPositions;
  2333. var slotTopCache = {};
  2334. var tm;
  2335. var firstDay;
  2336. var nwe; // no weekends (int)
  2337. var rtl, dis, dit; // day index sign / translate
  2338. var minMinute, maxMinute;
  2339. var colFormat;
  2340. /* Rendering
  2341. -----------------------------------------------------------------------------*/
  2342. disableTextSelection(element.addClass('fc-agenda'));
  2343. function renderAgenda(c) {
  2344. colCnt = c;
  2345. updateOptions();
  2346. if (!dayTable) {
  2347. buildSkeleton();
  2348. }else{
  2349. clearEvents();
  2350. }
  2351. updateCells();
  2352. }
  2353. function updateOptions() {
  2354. tm = opt('theme') ? 'ui' : 'fc';
  2355. nwe = opt('weekends') ? 0 : 1;
  2356. firstDay = opt('firstDay');
  2357. if (rtl = opt('isRTL')) {
  2358. dis = -1;
  2359. dit = colCnt - 1;
  2360. }else{
  2361. dis = 1;
  2362. dit = 0;
  2363. }
  2364. minMinute = parseTime(opt('minTime'));
  2365. maxMinute = parseTime(opt('maxTime'));
  2366. colFormat = opt('columnFormat');
  2367. }
  2368. function buildSkeleton() {
  2369. var headerClass = tm + "-widget-header";
  2370. var contentClass = tm + "-widget-content";
  2371. var s;
  2372. var i;
  2373. var d;
  2374. var maxd;
  2375. var minutes;
  2376. var slotNormal = opt('slotMinutes') % 15 == 0;
  2377. s =
  2378. "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
  2379. "<thead>" +
  2380. "<tr>" +
  2381. "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2382. for (i=0; i<colCnt; i++) {
  2383. s +=
  2384. "<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
  2385. }
  2386. s +=
  2387. "<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
  2388. "</tr>" +
  2389. "</thead>" +
  2390. "<tbody>" +
  2391. "<tr>" +
  2392. "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2393. for (i=0; i<colCnt; i++) {
  2394. s +=
  2395. "<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
  2396. "<div>" +
  2397. "<div class='fc-day-content'>" +
  2398. "<div style='position:relative'>&nbsp;</div>" +
  2399. "</div>" +
  2400. "</div>" +
  2401. "</td>";
  2402. }
  2403. s +=
  2404. "<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
  2405. "</tr>" +
  2406. "</tbody>" +
  2407. "</table>";
  2408. dayTable = $(s).appendTo(element);
  2409. dayHead = dayTable.find('thead');
  2410. dayHeadCells = dayHead.find('th').slice(1, -1);
  2411. dayBody = dayTable.find('tbody');
  2412. dayBodyCells = dayBody.find('td').slice(0, -1);
  2413. dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
  2414. dayBodyFirstCell = dayBodyCells.eq(0);
  2415. dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
  2416. markFirstLast(dayHead.add(dayHead.find('tr')));
  2417. markFirstLast(dayBody.add(dayBody.find('tr')));
  2418. axisFirstCells = dayHead.find('th:first');
  2419. gutterCells = dayTable.find('.fc-agenda-gutter');
  2420. slotLayer =
  2421. $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
  2422. .appendTo(element);
  2423. if (opt('allDaySlot')) {
  2424. daySegmentContainer =
  2425. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2426. .appendTo(slotLayer);
  2427. s =
  2428. "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
  2429. "<tr>" +
  2430. "<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
  2431. "<td>" +
  2432. "<div class='fc-day-content'><div style='position:relative'/></div>" +
  2433. "</td>" +
  2434. "<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
  2435. "</tr>" +
  2436. "</table>";
  2437. allDayTable = $(s).appendTo(slotLayer);
  2438. allDayRow = allDayTable.find('tr');
  2439. dayBind(allDayRow.find('td'));
  2440. axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
  2441. gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
  2442. slotLayer.append(
  2443. "<div class='fc-agenda-divider " + headerClass + "'>" +
  2444. "<div class='fc-agenda-divider-inner'/>" +
  2445. "</div>"
  2446. );
  2447. }else{
  2448. daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
  2449. }
  2450. slotScroller =
  2451. $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
  2452. .appendTo(slotLayer);
  2453. slotContent =
  2454. $("<div style='position:relative;width:100%;overflow:hidden'/>")
  2455. .appendTo(slotScroller);
  2456. slotSegmentContainer =
  2457. $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2458. .appendTo(slotContent);
  2459. s =
  2460. "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
  2461. "<tbody>";
  2462. d = zeroDate();
  2463. maxd = addMinutes(cloneDate(d), maxMinute);
  2464. addMinutes(d, minMinute);
  2465. slotCnt = 0;
  2466. for (i=0; d < maxd; i++) {
  2467. minutes = d.getMinutes();
  2468. s +=
  2469. "<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
  2470. "<th class='fc-agenda-axis " + headerClass + "'>" +
  2471. ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
  2472. "</th>" +
  2473. "<td class='" + contentClass + "'>" +
  2474. "<div style='position:relative'>&nbsp;</div>" +
  2475. "</td>" +
  2476. "</tr>";
  2477. addMinutes(d, opt('slotMinutes'));
  2478. slotCnt++;
  2479. }
  2480. s +=
  2481. "</tbody>" +
  2482. "</table>";
  2483. slotTable = $(s).appendTo(slotContent);
  2484. slotTableFirstInner = slotTable.find('div:first');
  2485. slotBind(slotTable.find('td'));
  2486. axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
  2487. }
  2488. function updateCells() {
  2489. var i;
  2490. var headCell;
  2491. var bodyCell;
  2492. var date;
  2493. var today = clearTime(new Date());
  2494. for (i=0; i<colCnt; i++) {
  2495. date = colDate(i);
  2496. headCell = dayHeadCells.eq(i);
  2497. headCell.html(formatDate(date, colFormat));
  2498. bodyCell = dayBodyCells.eq(i);
  2499. if (+date == +today) {
  2500. bodyCell.addClass(tm + '-state-highlight fc-today');
  2501. }else{
  2502. bodyCell.removeClass(tm + '-state-highlight fc-today');
  2503. }
  2504. setDayID(headCell.add(bodyCell), date);
  2505. }
  2506. }
  2507. function setHeight(height, dateChanged) {
  2508. if (height === undefined) {
  2509. height = viewHeight;
  2510. }
  2511. viewHeight = height;
  2512. slotTopCache = {};
  2513. var headHeight = dayBody.position().top;
  2514. var allDayHeight = slotScroller.position().top; // including divider
  2515. var bodyHeight = Math.min( // total body height, including borders
  2516. height - headHeight, // when scrollbars
  2517. slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
  2518. );
  2519. dayBodyFirstCellStretcher
  2520. .height(bodyHeight - vsides(dayBodyFirstCell));
  2521. slotLayer.css('top', headHeight);
  2522. slotScroller.height(bodyHeight - allDayHeight - 1);
  2523. slotHeight = slotTableFirstInner.height() + 1; // +1 for border
  2524. if (dateChanged) {
  2525. resetScroll();
  2526. }
  2527. }
  2528. function setWidth(width) {
  2529. viewWidth = width;
  2530. colContentPositions.clear();
  2531. axisWidth = 0;
  2532. setOuterWidth(
  2533. axisFirstCells
  2534. .width('')
  2535. .each(function(i, _cell) {
  2536. axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
  2537. }),
  2538. axisWidth
  2539. );
  2540. var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
  2541. //slotTable.width(slotTableWidth);
  2542. gutterWidth = slotScroller.width() - slotTableWidth;
  2543. if (gutterWidth) {
  2544. setOuterWidth(gutterCells, gutterWidth);
  2545. gutterCells
  2546. .show()
  2547. .prev()
  2548. .removeClass('fc-last');
  2549. }else{
  2550. gutterCells
  2551. .hide()
  2552. .prev()
  2553. .addClass('fc-last');
  2554. }
  2555. colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
  2556. setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
  2557. }
  2558. function resetScroll() {
  2559. var d0 = zeroDate();
  2560. var scrollDate = cloneDate(d0);
  2561. scrollDate.setHours(opt('firstHour'));
  2562. var top = timePosition(d0, scrollDate) + 1; // +1 for the border
  2563. function scroll() {
  2564. slotScroller.scrollTop(top);
  2565. }
  2566. scroll();
  2567. setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
  2568. }
  2569. function beforeHide() {
  2570. savedScrollTop = slotScroller.scrollTop();
  2571. }
  2572. function afterShow() {
  2573. slotScroller.scrollTop(savedScrollTop);
  2574. }
  2575. /* Slot/Day clicking and binding
  2576. -----------------------------------------------------------------------*/
  2577. function dayBind(cells) {
  2578. cells.click(slotClick)
  2579. .mousedown(daySelectionMousedown);
  2580. }
  2581. function slotBind(cells) {
  2582. cells.click(slotClick)
  2583. .mousedown(slotSelectionMousedown);
  2584. }
  2585. function slotClick(ev) {
  2586. if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  2587. var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
  2588. var date = colDate(col);
  2589. var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
  2590. if (rowMatch) {
  2591. var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
  2592. var hours = Math.floor(mins/60);
  2593. date.setHours(hours);
  2594. date.setMinutes(mins%60 + minMinute);
  2595. trigger('dayClick', dayBodyCells[col], date, false, ev);
  2596. }else{
  2597. trigger('dayClick', dayBodyCells[col], date, true, ev);
  2598. }
  2599. }
  2600. }
  2601. /* Semi-transparent Overlay Helpers
  2602. -----------------------------------------------------*/
  2603. function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
  2604. if (refreshCoordinateGrid) {
  2605. coordinateGrid.build();
  2606. }
  2607. var visStart = cloneDate(t.visStart);
  2608. var startCol, endCol;
  2609. if (rtl) {
  2610. startCol = dayDiff(endDate, visStart)*dis+dit+1;
  2611. endCol = dayDiff(startDate, visStart)*dis+dit+1;
  2612. }else{
  2613. startCol = dayDiff(startDate, visStart);
  2614. endCol = dayDiff(endDate, visStart);
  2615. }
  2616. startCol = Math.max(0, startCol);
  2617. endCol = Math.min(colCnt, endCol);
  2618. if (startCol < endCol) {
  2619. dayBind(
  2620. renderCellOverlay(0, startCol, 0, endCol-1)
  2621. );
  2622. }
  2623. }
  2624. function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
  2625. var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
  2626. return renderOverlay(rect, slotLayer);
  2627. }
  2628. function renderSlotOverlay(overlayStart, overlayEnd) {
  2629. var dayStart = cloneDate(t.visStart);
  2630. var dayEnd = addDays(cloneDate(dayStart), 1);
  2631. for (var i=0; i<colCnt; i++) {
  2632. var stretchStart = new Date(Math.max(dayStart, overlayStart));
  2633. var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
  2634. if (stretchStart < stretchEnd) {
  2635. var col = i*dis+dit;
  2636. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
  2637. var top = timePosition(dayStart, stretchStart);
  2638. var bottom = timePosition(dayStart, stretchEnd);
  2639. rect.top = top;
  2640. rect.height = bottom - top;
  2641. slotBind(
  2642. renderOverlay(rect, slotContent)
  2643. );
  2644. }
  2645. addDays(dayStart, 1);
  2646. addDays(dayEnd, 1);
  2647. }
  2648. }
  2649. /* Coordinate Utilities
  2650. -----------------------------------------------------------------------------*/
  2651. coordinateGrid = new CoordinateGrid(function(rows, cols) {
  2652. var e, n, p;
  2653. dayHeadCells.each(function(i, _e) {
  2654. e = $(_e);
  2655. n = e.offset().left;
  2656. if (i) {
  2657. p[1] = n;
  2658. }
  2659. p = [n];
  2660. cols[i] = p;
  2661. });
  2662. p[1] = n + e.outerWidth();
  2663. if (opt('allDaySlot')) {
  2664. e = allDayRow;
  2665. n = e.offset().top;
  2666. rows[0] = [n, n+e.outerHeight()];
  2667. }
  2668. var slotTableTop = slotContent.offset().top;
  2669. var slotScrollerTop = slotScroller.offset().top;
  2670. var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
  2671. function constrain(n) {
  2672. return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
  2673. }
  2674. for (var i=0; i<slotCnt; i++) {
  2675. rows.push([
  2676. constrain(slotTableTop + slotHeight*i),
  2677. constrain(slotTableTop + slotHeight*(i+1))
  2678. ]);
  2679. }
  2680. });
  2681. hoverListener = new HoverListener(coordinateGrid);
  2682. colContentPositions = new HorizontalPositionCache(function(col) {
  2683. return dayBodyCellInners.eq(col);
  2684. });
  2685. function colContentLeft(col) {
  2686. return colContentPositions.left(col);
  2687. }
  2688. function colContentRight(col) {
  2689. return colContentPositions.right(col);
  2690. }
  2691. function dateCell(date) { // "cell" terminology is now confusing
  2692. return {
  2693. row: Math.floor(dayDiff(date, t.visStart) / 7),
  2694. col: dayOfWeekCol(date.getDay())
  2695. };
  2696. }
  2697. function cellDate(cell) {
  2698. var d = colDate(cell.col);
  2699. var slotIndex = cell.row;
  2700. if (opt('allDaySlot')) {
  2701. slotIndex--;
  2702. }
  2703. if (slotIndex >= 0) {
  2704. addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
  2705. }
  2706. return d;
  2707. }
  2708. function colDate(col) { // returns dates with 00:00:00
  2709. return addDays(cloneDate(t.visStart), col*dis+dit);
  2710. }
  2711. function cellIsAllDay(cell) {
  2712. return opt('allDaySlot') && !cell.row;
  2713. }
  2714. function dayOfWeekCol(dayOfWeek) {
  2715. return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
  2716. }
  2717. // get the Y coordinate of the given time on the given day (both Date objects)
  2718. function timePosition(day, time) { // both date objects. day holds 00:00 of current day
  2719. day = cloneDate(day, true);
  2720. if (time < addMinutes(cloneDate(day), minMinute)) {
  2721. return 0;
  2722. }
  2723. if (time >= addMinutes(cloneDate(day), maxMinute)) {
  2724. return slotTable.height();
  2725. }
  2726. var slotMinutes = opt('slotMinutes'),
  2727. minutes = time.getHours()*60 + time.getMinutes() - minMinute,
  2728. slotI = Math.floor(minutes / slotMinutes),
  2729. slotTop = slotTopCache[slotI];
  2730. if (slotTop === undefined) {
  2731. slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
  2732. }
  2733. return Math.max(0, Math.round(
  2734. slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
  2735. ));
  2736. }
  2737. function allDayBounds() {
  2738. return {
  2739. left: axisWidth,
  2740. right: viewWidth - gutterWidth
  2741. }
  2742. }
  2743. function getAllDayRow(index) {
  2744. return allDayRow;
  2745. }
  2746. function defaultEventEnd(event) {
  2747. var start = cloneDate(event.start);
  2748. if (event.allDay) {
  2749. return start;
  2750. }
  2751. return addMinutes(start, opt('defaultEventMinutes'));
  2752. }
  2753. /* Selection
  2754. ---------------------------------------------------------------------------------*/
  2755. function defaultSelectionEnd(startDate, allDay) {
  2756. if (allDay) {
  2757. return cloneDate(startDate);
  2758. }
  2759. return addMinutes(cloneDate(startDate), opt('slotMinutes'));
  2760. }
  2761. function renderSelection(startDate, endDate, allDay) { // only for all-day
  2762. if (allDay) {
  2763. if (opt('allDaySlot')) {
  2764. renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
  2765. }
  2766. }else{
  2767. renderSlotSelection(startDate, endDate);
  2768. }
  2769. }
  2770. function renderSlotSelection(startDate, endDate) {
  2771. var helperOption = opt('selectHelper');
  2772. coordinateGrid.build();
  2773. if (helperOption) {
  2774. var col = dayDiff(startDate, t.visStart) * dis + dit;
  2775. if (col >= 0 && col < colCnt) { // only works when times are on same day
  2776. var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
  2777. var top = timePosition(startDate, startDate);
  2778. var bottom = timePosition(startDate, endDate);
  2779. if (bottom > top) { // protect against selections that are entirely before or after visible range
  2780. rect.top = top;
  2781. rect.height = bottom - top;
  2782. rect.left += 2;
  2783. rect.width -= 5;
  2784. if ($.isFunction(helperOption)) {
  2785. var helperRes = helperOption(startDate, endDate);
  2786. if (helperRes) {
  2787. rect.position = 'absolute';
  2788. rect.zIndex = 8;
  2789. selectionHelper = $(helperRes)
  2790. .css(rect)
  2791. .appendTo(slotContent);
  2792. }
  2793. }else{
  2794. rect.isStart = true; // conside rect a "seg" now
  2795. rect.isEnd = true; //
  2796. selectionHelper = $(slotSegHtml(
  2797. {
  2798. title: '',
  2799. start: startDate,
  2800. end: endDate,
  2801. className: ['fc-select-helper'],
  2802. editable: false
  2803. },
  2804. rect
  2805. ));
  2806. selectionHelper.css('opacity', opt('dragOpacity'));
  2807. }
  2808. if (selectionHelper) {
  2809. slotBind(selectionHelper);
  2810. slotContent.append(selectionHelper);
  2811. setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
  2812. setOuterHeight(selectionHelper, rect.height, true);
  2813. }
  2814. }
  2815. }
  2816. }else{
  2817. renderSlotOverlay(startDate, endDate);
  2818. }
  2819. }
  2820. function clearSelection() {
  2821. clearOverlays();
  2822. if (selectionHelper) {
  2823. selectionHelper.remove();
  2824. selectionHelper = null;
  2825. }
  2826. }
  2827. function slotSelectionMousedown(ev) {
  2828. if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
  2829. unselect(ev);
  2830. var dates;
  2831. hoverListener.start(function(cell, origCell) {
  2832. clearSelection();
  2833. if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
  2834. var d1 = cellDate(origCell);
  2835. var d2 = cellDate(cell);
  2836. dates = [
  2837. d1,
  2838. addMinutes(cloneDate(d1), opt('slotMinutes')),
  2839. d2,
  2840. addMinutes(cloneDate(d2), opt('slotMinutes'))
  2841. ].sort(cmp);
  2842. renderSlotSelection(dates[0], dates[3]);
  2843. }else{
  2844. dates = null;
  2845. }
  2846. }, ev);
  2847. $(document).one('mouseup', function(ev) {
  2848. hoverListener.stop();
  2849. if (dates) {
  2850. if (+dates[0] == +dates[1]) {
  2851. reportDayClick(dates[0], false, ev);
  2852. }
  2853. reportSelection(dates[0], dates[3], false, ev);
  2854. }
  2855. });
  2856. }
  2857. }
  2858. function reportDayClick(date, allDay, ev) {
  2859. trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
  2860. }
  2861. /* External Dragging
  2862. --------------------------------------------------------------------------------*/
  2863. function dragStart(_dragElement, ev, ui) {
  2864. hoverListener.start(function(cell) {
  2865. clearOverlays();
  2866. if (cell) {
  2867. if (cellIsAllDay(cell)) {
  2868. renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  2869. }else{
  2870. var d1 = cellDate(cell);
  2871. var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
  2872. renderSlotOverlay(d1, d2);
  2873. }
  2874. }
  2875. }, ev);
  2876. }
  2877. function dragStop(_dragElement, ev, ui) {
  2878. var cell = hoverListener.stop();
  2879. clearOverlays();
  2880. if (cell) {
  2881. trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
  2882. }
  2883. }
  2884. }
  2885. function AgendaEventRenderer() {
  2886. var t = this;
  2887. // exports
  2888. t.renderEvents = renderEvents;
  2889. t.compileDaySegs = compileDaySegs; // for DayEventRenderer
  2890. t.clearEvents = clearEvents;
  2891. t.slotSegHtml = slotSegHtml;
  2892. t.bindDaySeg = bindDaySeg;
  2893. // imports
  2894. DayEventRenderer.call(t);
  2895. var opt = t.opt;
  2896. var trigger = t.trigger;
  2897. //var setOverflowHidden = t.setOverflowHidden;
  2898. var isEventDraggable = t.isEventDraggable;
  2899. var isEventResizable = t.isEventResizable;
  2900. var eventEnd = t.eventEnd;
  2901. var reportEvents = t.reportEvents;
  2902. var reportEventClear = t.reportEventClear;
  2903. var eventElementHandlers = t.eventElementHandlers;
  2904. var setHeight = t.setHeight;
  2905. var getDaySegmentContainer = t.getDaySegmentContainer;
  2906. var getSlotSegmentContainer = t.getSlotSegmentContainer;
  2907. var getHoverListener = t.getHoverListener;
  2908. var getMaxMinute = t.getMaxMinute;
  2909. var getMinMinute = t.getMinMinute;
  2910. var timePosition = t.timePosition;
  2911. var colContentLeft = t.colContentLeft;
  2912. var colContentRight = t.colContentRight;
  2913. var renderDaySegs = t.renderDaySegs;
  2914. var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
  2915. var getColCnt = t.getColCnt;
  2916. var getColWidth = t.getColWidth;
  2917. var getSlotHeight = t.getSlotHeight;
  2918. var getBodyContent = t.getBodyContent;
  2919. var reportEventElement = t.reportEventElement;
  2920. var showEvents = t.showEvents;
  2921. var hideEvents = t.hideEvents;
  2922. var eventDrop = t.eventDrop;
  2923. var eventResize = t.eventResize;
  2924. var renderDayOverlay = t.renderDayOverlay;
  2925. var clearOverlays = t.clearOverlays;
  2926. var calendar = t.calendar;
  2927. var formatDate = calendar.formatDate;
  2928. var formatDates = calendar.formatDates;
  2929. /* Rendering
  2930. ----------------------------------------------------------------------------*/
  2931. function renderEvents(events, modifiedEventId) {
  2932. reportEvents(events);
  2933. var i, len=events.length,
  2934. dayEvents=[],
  2935. slotEvents=[];
  2936. for (i=0; i<len; i++) {
  2937. if (events[i].allDay) {
  2938. dayEvents.push(events[i]);
  2939. }else{
  2940. slotEvents.push(events[i]);
  2941. }
  2942. }
  2943. if (opt('allDaySlot')) {
  2944. renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
  2945. setHeight(); // no params means set to viewHeight
  2946. }
  2947. renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
  2948. }
  2949. function clearEvents() {
  2950. reportEventClear();
  2951. getDaySegmentContainer().empty();
  2952. getSlotSegmentContainer().empty();
  2953. }
  2954. function compileDaySegs(events) {
  2955. var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
  2956. i, levelCnt=levels.length, level,
  2957. j, seg,
  2958. segs=[];
  2959. for (i=0; i<levelCnt; i++) {
  2960. level = levels[i];
  2961. for (j=0; j<level.length; j++) {
  2962. seg = level[j];
  2963. seg.row = 0;
  2964. seg.level = i; // not needed anymore
  2965. segs.push(seg);
  2966. }
  2967. }
  2968. return segs;
  2969. }
  2970. function compileSlotSegs(events) {
  2971. var colCnt = getColCnt(),
  2972. minMinute = getMinMinute(),
  2973. maxMinute = getMaxMinute(),
  2974. d = addMinutes(cloneDate(t.visStart), minMinute),
  2975. visEventEnds = $.map(events, slotEventEnd),
  2976. i, col,
  2977. j, level,
  2978. k, seg,
  2979. segs=[];
  2980. for (i=0; i<colCnt; i++) {
  2981. col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
  2982. countForwardSegs(col);
  2983. for (j=0; j<col.length; j++) {
  2984. level = col[j];
  2985. for (k=0; k<level.length; k++) {
  2986. seg = level[k];
  2987. seg.col = i;
  2988. seg.level = j;
  2989. segs.push(seg);
  2990. }
  2991. }
  2992. addDays(d, 1, true);
  2993. }
  2994. return segs;
  2995. }
  2996. function slotEventEnd(event) {
  2997. if (event.end) {
  2998. return cloneDate(event.end);
  2999. }else{
  3000. return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
  3001. }
  3002. }
  3003. // renders events in the 'time slots' at the bottom
  3004. function renderSlotSegs(segs, modifiedEventId) {
  3005. var i, segCnt=segs.length, seg,
  3006. event,
  3007. classes,
  3008. top, bottom,
  3009. colI, levelI, forward,
  3010. leftmost,
  3011. availWidth,
  3012. outerWidth,
  3013. left,
  3014. html='',
  3015. eventElements,
  3016. eventElement,
  3017. triggerRes,
  3018. vsideCache={},
  3019. hsideCache={},
  3020. key, val,
  3021. contentElement,
  3022. height,
  3023. slotSegmentContainer = getSlotSegmentContainer(),
  3024. rtl, dis, dit,
  3025. colCnt = getColCnt();
  3026. if (rtl = opt('isRTL')) {
  3027. dis = -1;
  3028. dit = colCnt - 1;
  3029. }else{
  3030. dis = 1;
  3031. dit = 0;
  3032. }
  3033. // calculate position/dimensions, create html
  3034. for (i=0; i<segCnt; i++) {
  3035. seg = segs[i];
  3036. event = seg.event;
  3037. top = timePosition(seg.start, seg.start);
  3038. bottom = timePosition(seg.start, seg.end);
  3039. colI = seg.col;
  3040. levelI = seg.level;
  3041. forward = seg.forward || 0;
  3042. leftmost = colContentLeft(colI*dis + dit);
  3043. availWidth = colContentRight(colI*dis + dit) - leftmost;
  3044. availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
  3045. if (levelI) {
  3046. // indented and thin
  3047. outerWidth = availWidth / (levelI + forward + 1);
  3048. }else{
  3049. if (forward) {
  3050. // moderately wide, aligned left still
  3051. outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
  3052. }else{
  3053. // can be entire width, aligned left
  3054. outerWidth = availWidth;
  3055. }
  3056. }
  3057. left = leftmost + // leftmost possible
  3058. (availWidth / (levelI + forward + 1) * levelI) // indentation
  3059. * dis + (rtl ? availWidth - outerWidth : 0); // rtl
  3060. seg.top = top;
  3061. seg.left = left;
  3062. seg.outerWidth = outerWidth;
  3063. seg.outerHeight = bottom - top;
  3064. html += slotSegHtml(event, seg);
  3065. }
  3066. slotSegmentContainer[0].innerHTML = html; // faster than html()
  3067. eventElements = slotSegmentContainer.children();
  3068. // retrieve elements, run through eventRender callback, bind event handlers
  3069. for (i=0; i<segCnt; i++) {
  3070. seg = segs[i];
  3071. event = seg.event;
  3072. eventElement = $(eventElements[i]); // faster than eq()
  3073. triggerRes = trigger('eventRender', event, event, eventElement);
  3074. if (triggerRes === false) {
  3075. eventElement.remove();
  3076. }else{
  3077. if (triggerRes && triggerRes !== true) {
  3078. eventElement.remove();
  3079. eventElement = $(triggerRes)
  3080. .css({
  3081. position: 'absolute',
  3082. top: seg.top,
  3083. left: seg.left
  3084. })
  3085. .appendTo(slotSegmentContainer);
  3086. }
  3087. seg.element = eventElement;
  3088. if (event._id === modifiedEventId) {
  3089. bindSlotSeg(event, eventElement, seg);
  3090. }else{
  3091. eventElement[0]._fci = i; // for lazySegBind
  3092. }
  3093. reportEventElement(event, eventElement);
  3094. }
  3095. }
  3096. lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
  3097. // record event sides and title positions
  3098. for (i=0; i<segCnt; i++) {
  3099. seg = segs[i];
  3100. if (eventElement = seg.element) {
  3101. val = vsideCache[key = seg.key = cssKey(eventElement[0])];
  3102. seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
  3103. val = hsideCache[key];
  3104. seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
  3105. contentElement = eventElement.find('div.fc-event-content');
  3106. if (contentElement.length) {
  3107. seg.contentTop = contentElement[0].offsetTop;
  3108. }
  3109. }
  3110. }
  3111. // set all positions/dimensions at once
  3112. for (i=0; i<segCnt; i++) {
  3113. seg = segs[i];
  3114. if (eventElement = seg.element) {
  3115. eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3116. height = Math.max(0, seg.outerHeight - seg.vsides);
  3117. eventElement[0].style.height = height + 'px';
  3118. event = seg.event;
  3119. if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
  3120. // not enough room for title, put it in the time header
  3121. eventElement.find('div.fc-event-time')
  3122. .text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
  3123. eventElement.find('div.fc-event-title')
  3124. .remove();
  3125. }
  3126. trigger('eventAfterRender', event, event, eventElement);
  3127. }
  3128. }
  3129. }
  3130. function slotSegHtml(event, seg) {
  3131. var html = "<";
  3132. var url = event.url;
  3133. var skinCss = getSkinCss(event, opt);
  3134. var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  3135. var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
  3136. if (isEventDraggable(event)) {
  3137. classes.push('fc-event-draggable');
  3138. }
  3139. if (seg.isStart) {
  3140. classes.push('fc-corner-top');
  3141. }
  3142. if (seg.isEnd) {
  3143. classes.push('fc-corner-bottom');
  3144. }
  3145. classes = classes.concat(event.className);
  3146. if (event.source) {
  3147. classes = classes.concat(event.source.className || []);
  3148. }
  3149. if (url) {
  3150. html += "a href='" + htmlEscape(event.url) + "'";
  3151. }else{
  3152. html += "div";
  3153. }
  3154. html +=
  3155. " class='" + classes.join(' ') + "'" +
  3156. " style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
  3157. ">" +
  3158. "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
  3159. "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
  3160. "<div class='fc-event-time'>" +
  3161. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3162. "</div>" +
  3163. "</div>" +
  3164. "<div class='fc-event-content'>" +
  3165. "<div class='fc-event-title'>" +
  3166. htmlEscape(event.title) +
  3167. "</div>" +
  3168. "</div>" +
  3169. "<div class='fc-event-bg'></div>" +
  3170. "</div>"; // close inner
  3171. if (seg.isEnd && isEventResizable(event)) {
  3172. html +=
  3173. "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
  3174. }
  3175. html +=
  3176. "</" + (url ? "a" : "div") + ">";
  3177. return html;
  3178. }
  3179. function bindDaySeg(event, eventElement, seg) {
  3180. if (isEventDraggable(event)) {
  3181. draggableDayEvent(event, eventElement, seg.isStart);
  3182. }
  3183. if (seg.isEnd && isEventResizable(event)) {
  3184. resizableDayEvent(event, eventElement, seg);
  3185. }
  3186. eventElementHandlers(event, eventElement);
  3187. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  3188. }
  3189. function bindSlotSeg(event, eventElement, seg) {
  3190. var timeElement = eventElement.find('div.fc-event-time');
  3191. if (isEventDraggable(event)) {
  3192. draggableSlotEvent(event, eventElement, timeElement);
  3193. }
  3194. if (seg.isEnd && isEventResizable(event)) {
  3195. resizableSlotEvent(event, eventElement, timeElement);
  3196. }
  3197. eventElementHandlers(event, eventElement);
  3198. }
  3199. /* Dragging
  3200. -----------------------------------------------------------------------------------*/
  3201. // when event starts out FULL-DAY
  3202. function draggableDayEvent(event, eventElement, isStart) {
  3203. var origWidth;
  3204. var revert;
  3205. var allDay=true;
  3206. var dayDelta;
  3207. var dis = opt('isRTL') ? -1 : 1;
  3208. var hoverListener = getHoverListener();
  3209. var colWidth = getColWidth();
  3210. var slotHeight = getSlotHeight();
  3211. var minMinute = getMinMinute();
  3212. eventElement.draggable({
  3213. zIndex: 9,
  3214. opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
  3215. revertDuration: opt('dragRevertDuration'),
  3216. start: function(ev, ui) {
  3217. trigger('eventDragStart', eventElement, event, ev, ui);
  3218. hideEvents(event, eventElement);
  3219. origWidth = eventElement.width();
  3220. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3221. clearOverlays();
  3222. if (cell) {
  3223. //setOverflowHidden(true);
  3224. revert = false;
  3225. dayDelta = colDelta * dis;
  3226. if (!cell.row) {
  3227. // on full-days
  3228. renderDayOverlay(
  3229. addDays(cloneDate(event.start), dayDelta),
  3230. addDays(exclEndDay(event), dayDelta)
  3231. );
  3232. resetElement();
  3233. }else{
  3234. // mouse is over bottom slots
  3235. if (isStart) {
  3236. if (allDay) {
  3237. // convert event to temporary slot-event
  3238. eventElement.width(colWidth - 10); // don't use entire width
  3239. setOuterHeight(
  3240. eventElement,
  3241. slotHeight * Math.round(
  3242. (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
  3243. / opt('slotMinutes')
  3244. )
  3245. );
  3246. eventElement.draggable('option', 'grid', [colWidth, 1]);
  3247. allDay = false;
  3248. }
  3249. }else{
  3250. revert = true;
  3251. }
  3252. }
  3253. revert = revert || (allDay && !dayDelta);
  3254. }else{
  3255. resetElement();
  3256. //setOverflowHidden(false);
  3257. revert = true;
  3258. }
  3259. eventElement.draggable('option', 'revert', revert);
  3260. }, ev, 'drag');
  3261. },
  3262. stop: function(ev, ui) {
  3263. hoverListener.stop();
  3264. clearOverlays();
  3265. trigger('eventDragStop', eventElement, event, ev, ui);
  3266. if (revert) {
  3267. // hasn't moved or is out of bounds (draggable has already reverted)
  3268. resetElement();
  3269. eventElement.css('filter', ''); // clear IE opacity side-effects
  3270. showEvents(event, eventElement);
  3271. }else{
  3272. // changed!
  3273. var minuteDelta = 0;
  3274. if (!allDay) {
  3275. minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
  3276. * opt('slotMinutes')
  3277. + minMinute
  3278. - (event.start.getHours() * 60 + event.start.getMinutes());
  3279. }
  3280. eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
  3281. }
  3282. //setOverflowHidden(false);
  3283. }
  3284. });
  3285. function resetElement() {
  3286. if (!allDay) {
  3287. eventElement
  3288. .width(origWidth)
  3289. .height('')
  3290. .draggable('option', 'grid', null);
  3291. allDay = true;
  3292. }
  3293. }
  3294. }
  3295. // when event starts out IN TIMESLOTS
  3296. function draggableSlotEvent(event, eventElement, timeElement) {
  3297. var origPosition;
  3298. var allDay=false;
  3299. var dayDelta;
  3300. var minuteDelta;
  3301. var prevMinuteDelta;
  3302. var dis = opt('isRTL') ? -1 : 1;
  3303. var hoverListener = getHoverListener();
  3304. var colCnt = getColCnt();
  3305. var colWidth = getColWidth();
  3306. var slotHeight = getSlotHeight();
  3307. eventElement.draggable({
  3308. zIndex: 9,
  3309. scroll: false,
  3310. grid: [colWidth, slotHeight],
  3311. axis: colCnt==1 ? 'y' : false,
  3312. opacity: opt('dragOpacity'),
  3313. revertDuration: opt('dragRevertDuration'),
  3314. start: function(ev, ui) {
  3315. trigger('eventDragStart', eventElement, event, ev, ui);
  3316. hideEvents(event, eventElement);
  3317. origPosition = eventElement.position();
  3318. minuteDelta = prevMinuteDelta = 0;
  3319. hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3320. eventElement.draggable('option', 'revert', !cell);
  3321. clearOverlays();
  3322. if (cell) {
  3323. dayDelta = colDelta * dis;
  3324. if (opt('allDaySlot') && !cell.row) {
  3325. // over full days
  3326. if (!allDay) {
  3327. // convert to temporary all-day event
  3328. allDay = true;
  3329. timeElement.hide();
  3330. eventElement.draggable('option', 'grid', null);
  3331. }
  3332. renderDayOverlay(
  3333. addDays(cloneDate(event.start), dayDelta),
  3334. addDays(exclEndDay(event), dayDelta)
  3335. );
  3336. }else{
  3337. // on slots
  3338. resetElement();
  3339. }
  3340. }
  3341. }, ev, 'drag');
  3342. },
  3343. drag: function(ev, ui) {
  3344. minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
  3345. if (minuteDelta != prevMinuteDelta) {
  3346. if (!allDay) {
  3347. updateTimeText(minuteDelta);
  3348. }
  3349. prevMinuteDelta = minuteDelta;
  3350. }
  3351. },
  3352. stop: function(ev, ui) {
  3353. var cell = hoverListener.stop();
  3354. clearOverlays();
  3355. trigger('eventDragStop', eventElement, event, ev, ui);
  3356. if (cell && (dayDelta || minuteDelta || allDay)) {
  3357. // changed!
  3358. eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
  3359. }else{
  3360. // either no change or out-of-bounds (draggable has already reverted)
  3361. resetElement();
  3362. eventElement.css('filter', ''); // clear IE opacity side-effects
  3363. eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
  3364. updateTimeText(0);
  3365. showEvents(event, eventElement);
  3366. }
  3367. }
  3368. });
  3369. function updateTimeText(minuteDelta) {
  3370. var newStart = addMinutes(cloneDate(event.start), minuteDelta);
  3371. var newEnd;
  3372. if (event.end) {
  3373. newEnd = addMinutes(cloneDate(event.end), minuteDelta);
  3374. }
  3375. timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
  3376. }
  3377. function resetElement() {
  3378. // convert back to original slot-event
  3379. if (allDay) {
  3380. timeElement.css('display', ''); // show() was causing display=inline
  3381. eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
  3382. allDay = false;
  3383. }
  3384. }
  3385. }
  3386. /* Resizing
  3387. --------------------------------------------------------------------------------------*/
  3388. function resizableSlotEvent(event, eventElement, timeElement) {
  3389. var slotDelta, prevSlotDelta;
  3390. var slotHeight = getSlotHeight();
  3391. eventElement.resizable({
  3392. handles: {
  3393. s: 'div.ui-resizable-s'
  3394. },
  3395. grid: slotHeight,
  3396. start: function(ev, ui) {
  3397. console.log("event resize started");
  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. console.log("event resizing");
  3405. // don't rely on ui.size.height, doesn't take grid into account
  3406. slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
  3407. if (slotDelta != prevSlotDelta) {
  3408. timeElement.text(
  3409. formatDates(
  3410. event.start,
  3411. (!slotDelta && !event.end) ? null : // no change, so don't display time range
  3412. addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
  3413. opt('timeFormat')
  3414. )
  3415. );
  3416. prevSlotDelta = slotDelta;
  3417. }
  3418. },
  3419. stop: function(ev, ui) {
  3420. console.log("event resize stopped");
  3421. trigger('eventResizeStop', this, event, ev, ui);
  3422. if (slotDelta) {
  3423. eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
  3424. }else{
  3425. eventElement.css('z-index', 8);
  3426. showEvents(event, eventElement);
  3427. // BUG: if event was really short, need to put title back in span
  3428. }
  3429. }
  3430. });
  3431. }
  3432. }
  3433. function countForwardSegs(levels) {
  3434. var i, j, k, level, segForward, segBack;
  3435. for (i=levels.length-1; i>0; i--) {
  3436. level = levels[i];
  3437. for (j=0; j<level.length; j++) {
  3438. segForward = level[j];
  3439. for (k=0; k<levels[i-1].length; k++) {
  3440. segBack = levels[i-1][k];
  3441. if (segsCollide(segForward, segBack)) {
  3442. segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
  3443. }
  3444. }
  3445. }
  3446. }
  3447. }
  3448. function View(element, calendar, viewName) {
  3449. var t = this;
  3450. // exports
  3451. t.element = element;
  3452. t.calendar = calendar;
  3453. t.name = viewName;
  3454. t.opt = opt;
  3455. t.trigger = trigger;
  3456. //t.setOverflowHidden = setOverflowHidden;
  3457. t.isEventDraggable = isEventDraggable;
  3458. t.isEventResizable = isEventResizable;
  3459. t.reportEvents = reportEvents;
  3460. t.eventEnd = eventEnd;
  3461. t.reportEventElement = reportEventElement;
  3462. t.reportEventClear = reportEventClear;
  3463. t.eventElementHandlers = eventElementHandlers;
  3464. t.showEvents = showEvents;
  3465. t.hideEvents = hideEvents;
  3466. t.eventDrop = eventDrop;
  3467. t.eventResize = eventResize;
  3468. // t.title
  3469. // t.start, t.end
  3470. // t.visStart, t.visEnd
  3471. // imports
  3472. var defaultEventEnd = t.defaultEventEnd;
  3473. var normalizeEvent = calendar.normalizeEvent; // in EventManager
  3474. var reportEventChange = calendar.reportEventChange;
  3475. // locals
  3476. var eventsByID = {};
  3477. var eventElements = [];
  3478. var eventElementsByID = {};
  3479. var options = calendar.options;
  3480. function opt(name, viewNameOverride) {
  3481. var v = options[name];
  3482. if (typeof v == 'object') {
  3483. return smartProperty(v, viewNameOverride || viewName);
  3484. }
  3485. return v;
  3486. }
  3487. function trigger(name, thisObj) {
  3488. return calendar.trigger.apply(
  3489. calendar,
  3490. [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
  3491. );
  3492. }
  3493. /*
  3494. function setOverflowHidden(bool) {
  3495. element.css('overflow', bool ? 'hidden' : '');
  3496. }
  3497. */
  3498. function isEventDraggable(event) {
  3499. return isEventEditable(event) && !opt('disableDragging');
  3500. }
  3501. function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
  3502. return isEventEditable(event) && !opt('disableResizing');
  3503. }
  3504. function isEventEditable(event) {
  3505. return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
  3506. }
  3507. /* Event Data
  3508. ------------------------------------------------------------------------------*/
  3509. // report when view receives new events
  3510. function reportEvents(events) { // events are already normalized at this point
  3511. eventsByID = {};
  3512. var i, len=events.length, event;
  3513. for (i=0; i<len; i++) {
  3514. event = events[i];
  3515. if (eventsByID[event._id]) {
  3516. eventsByID[event._id].push(event);
  3517. }else{
  3518. eventsByID[event._id] = [event];
  3519. }
  3520. }
  3521. }
  3522. // returns a Date object for an event's end
  3523. function eventEnd(event) {
  3524. return event.end ? cloneDate(event.end) : defaultEventEnd(event);
  3525. }
  3526. /* Event Elements
  3527. ------------------------------------------------------------------------------*/
  3528. // report when view creates an element for an event
  3529. function reportEventElement(event, element) {
  3530. eventElements.push(element);
  3531. if (eventElementsByID[event._id]) {
  3532. eventElementsByID[event._id].push(element);
  3533. }else{
  3534. eventElementsByID[event._id] = [element];
  3535. }
  3536. }
  3537. function reportEventClear() {
  3538. eventElements = [];
  3539. eventElementsByID = {};
  3540. }
  3541. // attaches eventClick, eventMouseover, eventMouseout
  3542. function eventElementHandlers(event, eventElement) {
  3543. eventElement
  3544. .click(function(ev) {
  3545. if (!eventElement.hasClass('ui-draggable-dragging') &&
  3546. !eventElement.hasClass('ui-resizable-resizing')) {
  3547. return trigger('eventClick', this, event, ev);
  3548. }
  3549. })
  3550. .hover(
  3551. function(ev) {
  3552. trigger('eventMouseover', this, event, ev);
  3553. },
  3554. function(ev) {
  3555. trigger('eventMouseout', this, event, ev);
  3556. }
  3557. );
  3558. // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
  3559. // TODO: same for resizing
  3560. }
  3561. function showEvents(event, exceptElement) {
  3562. eachEventElement(event, exceptElement, 'show');
  3563. }
  3564. function hideEvents(event, exceptElement) {
  3565. eachEventElement(event, exceptElement, 'hide');
  3566. }
  3567. function eachEventElement(event, exceptElement, funcName) {
  3568. var elements = eventElementsByID[event._id],
  3569. i, len = elements.length;
  3570. for (i=0; i<len; i++) {
  3571. if (!exceptElement || elements[i][0] != exceptElement[0]) {
  3572. elements[i][funcName]();
  3573. }
  3574. }
  3575. }
  3576. /* Event Modification Reporting
  3577. ---------------------------------------------------------------------------------*/
  3578. function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
  3579. var oldAllDay = event.allDay;
  3580. var eventId = event._id;
  3581. moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
  3582. trigger(
  3583. 'eventDrop',
  3584. e,
  3585. event,
  3586. dayDelta,
  3587. minuteDelta,
  3588. allDay,
  3589. function() {
  3590. // TODO: investigate cases where this inverse technique might not work
  3591. moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
  3592. reportEventChange(eventId);
  3593. },
  3594. ev,
  3595. ui
  3596. );
  3597. reportEventChange(eventId);
  3598. }
  3599. function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
  3600. var eventId = event._id;
  3601. elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
  3602. trigger(
  3603. 'eventResize',
  3604. e,
  3605. event,
  3606. dayDelta,
  3607. minuteDelta,
  3608. function() {
  3609. // TODO: investigate cases where this inverse technique might not work
  3610. elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
  3611. reportEventChange(eventId);
  3612. },
  3613. ev,
  3614. ui
  3615. );
  3616. reportEventChange(eventId);
  3617. }
  3618. /* Event Modification Math
  3619. ---------------------------------------------------------------------------------*/
  3620. function moveEvents(events, dayDelta, minuteDelta, allDay) {
  3621. minuteDelta = minuteDelta || 0;
  3622. for (var e, len=events.length, i=0; i<len; i++) {
  3623. e = events[i];
  3624. if (allDay !== undefined) {
  3625. e.allDay = allDay;
  3626. }
  3627. addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
  3628. if (e.end) {
  3629. e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
  3630. }
  3631. normalizeEvent(e, options);
  3632. }
  3633. }
  3634. function elongateEvents(events, dayDelta, minuteDelta) {
  3635. minuteDelta = minuteDelta || 0;
  3636. for (var e, len=events.length, i=0; i<len; i++) {
  3637. e = events[i];
  3638. e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
  3639. normalizeEvent(e, options);
  3640. }
  3641. }
  3642. }
  3643. function DayEventRenderer() {
  3644. var t = this;
  3645. // exports
  3646. t.renderDaySegs = renderDaySegs;
  3647. t.resizableDayEvent = resizableDayEvent;
  3648. // imports
  3649. var opt = t.opt;
  3650. var trigger = t.trigger;
  3651. var isEventDraggable = t.isEventDraggable;
  3652. var isEventResizable = t.isEventResizable;
  3653. var eventEnd = t.eventEnd;
  3654. var reportEventElement = t.reportEventElement;
  3655. var showEvents = t.showEvents;
  3656. var hideEvents = t.hideEvents;
  3657. var eventResize = t.eventResize;
  3658. var getRowCnt = t.getRowCnt;
  3659. var getColCnt = t.getColCnt;
  3660. var getColWidth = t.getColWidth;
  3661. var allDayRow = t.allDayRow;
  3662. var allDayBounds = t.allDayBounds;
  3663. var colContentLeft = t.colContentLeft;
  3664. var colContentRight = t.colContentRight;
  3665. var dayOfWeekCol = t.dayOfWeekCol;
  3666. var dateCell = t.dateCell;
  3667. var compileDaySegs = t.compileDaySegs;
  3668. var getDaySegmentContainer = t.getDaySegmentContainer;
  3669. var bindDaySeg = t.bindDaySeg; //TODO: streamline this
  3670. var formatDates = t.calendar.formatDates;
  3671. var renderDayOverlay = t.renderDayOverlay;
  3672. var clearOverlays = t.clearOverlays;
  3673. var clearSelection = t.clearSelection;
  3674. /* Rendering
  3675. -----------------------------------------------------------------------------*/
  3676. function renderDaySegs(segs, modifiedEventId) {
  3677. var segmentContainer = getDaySegmentContainer();
  3678. var rowDivs;
  3679. var rowCnt = getRowCnt();
  3680. var colCnt = getColCnt();
  3681. var i = 0;
  3682. var rowI;
  3683. var levelI;
  3684. var colHeights;
  3685. var j;
  3686. var segCnt = segs.length;
  3687. var seg;
  3688. var top;
  3689. var k;
  3690. segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  3691. daySegElementResolve(segs, segmentContainer.children());
  3692. daySegElementReport(segs);
  3693. daySegHandlers(segs, segmentContainer, modifiedEventId);
  3694. daySegCalcHSides(segs);
  3695. daySegSetWidths(segs);
  3696. daySegCalcHeights(segs);
  3697. rowDivs = getRowDivs();
  3698. // set row heights, calculate event tops (in relation to row top)
  3699. for (rowI=0; rowI<rowCnt; rowI++) {
  3700. levelI = 0;
  3701. colHeights = [];
  3702. for (j=0; j<colCnt; j++) {
  3703. colHeights[j] = 0;
  3704. }
  3705. while (i<segCnt && (seg = segs[i]).row == rowI) {
  3706. // loop through segs in a row
  3707. top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
  3708. seg.top = top;
  3709. top += seg.outerHeight;
  3710. for (k=seg.startCol; k<seg.endCol; k++) {
  3711. colHeights[k] = top;
  3712. }
  3713. i++;
  3714. }
  3715. rowDivs[rowI].height(arrayMax(colHeights));
  3716. }
  3717. daySegSetTops(segs, getRowTops(rowDivs));
  3718. }
  3719. function renderTempDaySegs(segs, adjustRow, adjustTop) {
  3720. var tempContainer = $("<div/>");
  3721. var elements;
  3722. var segmentContainer = getDaySegmentContainer();
  3723. var i;
  3724. var segCnt = segs.length;
  3725. var element;
  3726. tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  3727. elements = tempContainer.children();
  3728. segmentContainer.append(elements);
  3729. daySegElementResolve(segs, elements);
  3730. daySegCalcHSides(segs);
  3731. daySegSetWidths(segs);
  3732. daySegCalcHeights(segs);
  3733. daySegSetTops(segs, getRowTops(getRowDivs()));
  3734. elements = [];
  3735. for (i=0; i<segCnt; i++) {
  3736. element = segs[i].element;
  3737. if (element) {
  3738. if (segs[i].row === adjustRow) {
  3739. element.css('top', adjustTop);
  3740. }
  3741. elements.push(element[0]);
  3742. }
  3743. }
  3744. return $(elements);
  3745. }
  3746. function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
  3747. var rtl = opt('isRTL');
  3748. var i;
  3749. var segCnt=segs.length;
  3750. var seg;
  3751. var event;
  3752. var url;
  3753. var classes;
  3754. var bounds = allDayBounds();
  3755. var minLeft = bounds.left;
  3756. var maxLeft = bounds.right;
  3757. var leftCol;
  3758. var rightCol;
  3759. var left;
  3760. var right;
  3761. var skinCss;
  3762. var html = '';
  3763. // calculate desired position/dimensions, create html
  3764. for (i=0; i<segCnt; i++) {
  3765. seg = segs[i];
  3766. event = seg.event;
  3767. classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
  3768. if (isEventDraggable(event)) {
  3769. classes.push('fc-event-draggable');
  3770. }
  3771. if (rtl) {
  3772. if (seg.isStart) {
  3773. classes.push('fc-corner-right');
  3774. }
  3775. if (seg.isEnd) {
  3776. classes.push('fc-corner-left');
  3777. }
  3778. leftCol = dayOfWeekCol(seg.end.getDay()-1);
  3779. rightCol = dayOfWeekCol(seg.start.getDay());
  3780. left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
  3781. right = seg.isStart ? colContentRight(rightCol) : maxLeft;
  3782. }else{
  3783. if (seg.isStart) {
  3784. classes.push('fc-corner-left');
  3785. }
  3786. if (seg.isEnd) {
  3787. classes.push('fc-corner-right');
  3788. }
  3789. leftCol = dayOfWeekCol(seg.start.getDay());
  3790. rightCol = dayOfWeekCol(seg.end.getDay()-1);
  3791. left = seg.isStart ? colContentLeft(leftCol) : minLeft;
  3792. right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
  3793. }
  3794. classes = classes.concat(event.className);
  3795. if (event.source) {
  3796. classes = classes.concat(event.source.className || []);
  3797. }
  3798. url = event.url;
  3799. skinCss = getSkinCss(event, opt);
  3800. if (url) {
  3801. html += "<a href='" + htmlEscape(url) + "'";
  3802. }else{
  3803. html += "<div";
  3804. }
  3805. html +=
  3806. " class='" + classes.join(' ') + "'" +
  3807. " style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
  3808. ">" +
  3809. "<div" +
  3810. " class='fc-event-inner fc-event-skin'" +
  3811. (skinCss ? " style='" + skinCss + "'" : '') +
  3812. ">";
  3813. if (!event.allDay && seg.isStart) {
  3814. html +=
  3815. "<span class='fc-event-time'>" +
  3816. htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3817. "</span>";
  3818. }
  3819. html +=
  3820. "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
  3821. "</div>";
  3822. if (seg.isEnd && isEventResizable(event)) {
  3823. html +=
  3824. "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
  3825. "&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
  3826. "</div>";
  3827. }
  3828. html +=
  3829. "</" + (url ? "a" : "div" ) + ">";
  3830. seg.left = left;
  3831. seg.outerWidth = right - left;
  3832. seg.startCol = leftCol;
  3833. seg.endCol = rightCol + 1; // needs to be exclusive
  3834. }
  3835. return html;
  3836. }
  3837. function daySegElementResolve(segs, elements) { // sets seg.element
  3838. var i;
  3839. var segCnt = segs.length;
  3840. var seg;
  3841. var event;
  3842. var element;
  3843. var triggerRes;
  3844. for (i=0; i<segCnt; i++) {
  3845. seg = segs[i];
  3846. event = seg.event;
  3847. element = $(elements[i]); // faster than .eq()
  3848. triggerRes = trigger('eventRender', event, event, element);
  3849. if (triggerRes === false) {
  3850. element.remove();
  3851. }else{
  3852. if (triggerRes && triggerRes !== true) {
  3853. triggerRes = $(triggerRes)
  3854. .css({
  3855. position: 'absolute',
  3856. left: seg.left
  3857. });
  3858. element.replaceWith(triggerRes);
  3859. element = triggerRes;
  3860. }
  3861. seg.element = element;
  3862. }
  3863. }
  3864. }
  3865. function daySegElementReport(segs) {
  3866. var i;
  3867. var segCnt = segs.length;
  3868. var seg;
  3869. var element;
  3870. for (i=0; i<segCnt; i++) {
  3871. seg = segs[i];
  3872. element = seg.element;
  3873. if (element) {
  3874. reportEventElement(seg.event, element);
  3875. }
  3876. }
  3877. }
  3878. function daySegHandlers(segs, segmentContainer, modifiedEventId) {
  3879. var i;
  3880. var segCnt = segs.length;
  3881. var seg;
  3882. var element;
  3883. var event;
  3884. // retrieve elements, run through eventRender callback, bind handlers
  3885. for (i=0; i<segCnt; i++) {
  3886. seg = segs[i];
  3887. element = seg.element;
  3888. if (element) {
  3889. event = seg.event;
  3890. if (event._id === modifiedEventId) {
  3891. bindDaySeg(event, element, seg);
  3892. }else{
  3893. element[0]._fci = i; // for lazySegBind
  3894. }
  3895. }
  3896. }
  3897. lazySegBind(segmentContainer, segs, bindDaySeg);
  3898. }
  3899. function daySegCalcHSides(segs) { // also sets seg.key
  3900. var i;
  3901. var segCnt = segs.length;
  3902. var seg;
  3903. var element;
  3904. var key, val;
  3905. var hsideCache = {};
  3906. // record event horizontal sides
  3907. for (i=0; i<segCnt; i++) {
  3908. seg = segs[i];
  3909. element = seg.element;
  3910. if (element) {
  3911. key = seg.key = cssKey(element[0]);
  3912. val = hsideCache[key];
  3913. if (val === undefined) {
  3914. val = hsideCache[key] = hsides(element, true);
  3915. }
  3916. seg.hsides = val;
  3917. }
  3918. }
  3919. }
  3920. function daySegSetWidths(segs) {
  3921. var i;
  3922. var segCnt = segs.length;
  3923. var seg;
  3924. var element;
  3925. for (i=0; i<segCnt; i++) {
  3926. seg = segs[i];
  3927. element = seg.element;
  3928. if (element) {
  3929. element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3930. }
  3931. }
  3932. }
  3933. function daySegCalcHeights(segs) {
  3934. var i;
  3935. var segCnt = segs.length;
  3936. var seg;
  3937. var element;
  3938. var key, val;
  3939. var vmarginCache = {};
  3940. // record event heights
  3941. for (i=0; i<segCnt; i++) {
  3942. seg = segs[i];
  3943. element = seg.element;
  3944. if (element) {
  3945. key = seg.key; // created in daySegCalcHSides
  3946. val = vmarginCache[key];
  3947. if (val === undefined) {
  3948. val = vmarginCache[key] = vmargins(element);
  3949. }
  3950. seg.outerHeight = element[0].offsetHeight + val;
  3951. }
  3952. }
  3953. }
  3954. function getRowDivs() {
  3955. var i;
  3956. var rowCnt = getRowCnt();
  3957. var rowDivs = [];
  3958. for (i=0; i<rowCnt; i++) {
  3959. rowDivs[i] = allDayRow(i)
  3960. .find('td:first div.fc-day-content > div'); // optimal selector?
  3961. }
  3962. return rowDivs;
  3963. }
  3964. function getRowTops(rowDivs) {
  3965. var i;
  3966. var rowCnt = rowDivs.length;
  3967. var tops = [];
  3968. for (i=0; i<rowCnt; i++) {
  3969. tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
  3970. }
  3971. return tops;
  3972. }
  3973. function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
  3974. var i;
  3975. var segCnt = segs.length;
  3976. var seg;
  3977. var element;
  3978. var event;
  3979. for (i=0; i<segCnt; i++) {
  3980. seg = segs[i];
  3981. element = seg.element;
  3982. if (element) {
  3983. element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
  3984. event = seg.event;
  3985. trigger('eventAfterRender', event, event, element);
  3986. }
  3987. }
  3988. }
  3989. /* Resizing
  3990. -----------------------------------------------------------------------------------*/
  3991. function resizableDayEvent(event, element, seg) {
  3992. var rtl = opt('isRTL');
  3993. var direction = rtl ? 'w' : 'e';
  3994. var handle = element.find('div.ui-resizable-' + direction);
  3995. var isResizing = false;
  3996. // TODO: look into using jquery-ui mouse widget for this stuff
  3997. disableTextSelection(element); // prevent native <a> selection for IE
  3998. element
  3999. .mousedown(function(ev) { // prevent native <a> selection for others
  4000. ev.preventDefault();
  4001. })
  4002. .click(function(ev) {
  4003. if (isResizing) {
  4004. ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
  4005. ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
  4006. // (eventElementHandlers needs to be bound after resizableDayEvent)
  4007. }
  4008. });
  4009. handle.mousedown(function(ev) {
  4010. if (ev.which != 1) {
  4011. return; // needs to be left mouse button
  4012. }
  4013. isResizing = true;
  4014. var hoverListener = t.getHoverListener();
  4015. var rowCnt = getRowCnt();
  4016. var colCnt = getColCnt();
  4017. var dis = rtl ? -1 : 1;
  4018. var dit = rtl ? colCnt-1 : 0;
  4019. var elementTop = element.css('top');
  4020. var dayDelta;
  4021. var helpers;
  4022. var eventCopy = $.extend({}, event);
  4023. var minCell = dateCell(event.start);
  4024. clearSelection();
  4025. $('body')
  4026. .css('cursor', direction + '-resize')
  4027. .one('mouseup', mouseup);
  4028. trigger('eventResizeStart', this, event, ev);
  4029. hoverListener.start(function(cell, origCell) {
  4030. if (cell) {
  4031. var r = Math.max(minCell.row, cell.row);
  4032. var c = cell.col;
  4033. if (rowCnt == 1) {
  4034. r = 0; // hack for all-day area in agenda views
  4035. }
  4036. if (r == minCell.row) {
  4037. if (rtl) {
  4038. c = Math.min(minCell.col, c);
  4039. }else{
  4040. c = Math.max(minCell.col, c);
  4041. }
  4042. }
  4043. dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
  4044. var newEnd = addDays(eventEnd(event), dayDelta, true);
  4045. if (dayDelta) {
  4046. eventCopy.end = newEnd;
  4047. var oldHelpers = helpers;
  4048. helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
  4049. helpers.find('*').css('cursor', direction + '-resize');
  4050. if (oldHelpers) {
  4051. oldHelpers.remove();
  4052. }
  4053. hideEvents(event);
  4054. }else{
  4055. if (helpers) {
  4056. showEvents(event);
  4057. helpers.remove();
  4058. helpers = null;
  4059. }
  4060. }
  4061. clearOverlays();
  4062. renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
  4063. }
  4064. }, ev);
  4065. function mouseup(ev) {
  4066. trigger('eventResizeStop', this, event, ev);
  4067. $('body').css('cursor', '');
  4068. hoverListener.stop();
  4069. clearOverlays();
  4070. if (dayDelta) {
  4071. eventResize(this, event, dayDelta, 0, ev);
  4072. // event redraw will clear helpers
  4073. }
  4074. // otherwise, the drag handler already restored the old events
  4075. setTimeout(function() { // make this happen after the element's click event
  4076. isResizing = false;
  4077. },0);
  4078. }
  4079. });
  4080. }
  4081. }
  4082. //BUG: unselect needs to be triggered when events are dragged+dropped
  4083. function SelectionManager() {
  4084. var t = this;
  4085. // exports
  4086. t.select = select;
  4087. t.unselect = unselect;
  4088. t.reportSelection = reportSelection;
  4089. t.daySelectionMousedown = daySelectionMousedown;
  4090. // imports
  4091. var opt = t.opt;
  4092. var trigger = t.trigger;
  4093. var defaultSelectionEnd = t.defaultSelectionEnd;
  4094. var renderSelection = t.renderSelection;
  4095. var clearSelection = t.clearSelection;
  4096. // locals
  4097. var selected = false;
  4098. // unselectAuto
  4099. if (opt('selectable') && opt('unselectAuto')) {
  4100. $(document).mousedown(function(ev) {
  4101. var ignore = opt('unselectCancel');
  4102. if (ignore) {
  4103. if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
  4104. return;
  4105. }
  4106. }
  4107. unselect(ev);
  4108. });
  4109. }
  4110. function select(startDate, endDate, allDay) {
  4111. unselect();
  4112. if (!endDate) {
  4113. endDate = defaultSelectionEnd(startDate, allDay);
  4114. }
  4115. renderSelection(startDate, endDate, allDay);
  4116. reportSelection(startDate, endDate, allDay);
  4117. }
  4118. function unselect(ev) {
  4119. if (selected) {
  4120. selected = false;
  4121. clearSelection();
  4122. trigger('unselect', null, ev);
  4123. }
  4124. }
  4125. function reportSelection(startDate, endDate, allDay, ev) {
  4126. selected = true;
  4127. trigger('select', null, startDate, endDate, allDay, ev);
  4128. }
  4129. function daySelectionMousedown(ev) { // not really a generic manager method, oh well
  4130. var cellDate = t.cellDate;
  4131. var cellIsAllDay = t.cellIsAllDay;
  4132. var hoverListener = t.getHoverListener();
  4133. var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
  4134. if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
  4135. unselect(ev);
  4136. var _mousedownElement = this;
  4137. var dates;
  4138. hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
  4139. clearSelection();
  4140. if (cell && cellIsAllDay(cell)) {
  4141. dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
  4142. renderSelection(dates[0], dates[1], true);
  4143. }else{
  4144. dates = null;
  4145. }
  4146. }, ev);
  4147. $(document).one('mouseup', function(ev) {
  4148. hoverListener.stop();
  4149. if (dates) {
  4150. if (+dates[0] == +dates[1]) {
  4151. reportDayClick(dates[0], true, ev);
  4152. }
  4153. reportSelection(dates[0], dates[1], true, ev);
  4154. }
  4155. });
  4156. }
  4157. }
  4158. }
  4159. function OverlayManager() {
  4160. var t = this;
  4161. // exports
  4162. t.renderOverlay = renderOverlay;
  4163. t.clearOverlays = clearOverlays;
  4164. // locals
  4165. var usedOverlays = [];
  4166. var unusedOverlays = [];
  4167. function renderOverlay(rect, parent) {
  4168. var e = unusedOverlays.shift();
  4169. if (!e) {
  4170. e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
  4171. }
  4172. if (e[0].parentNode != parent[0]) {
  4173. e.appendTo(parent);
  4174. }
  4175. usedOverlays.push(e.css(rect).show());
  4176. return e;
  4177. }
  4178. function clearOverlays() {
  4179. var e;
  4180. while (e = usedOverlays.shift()) {
  4181. unusedOverlays.push(e.hide().unbind());
  4182. }
  4183. }
  4184. }
  4185. function CoordinateGrid(buildFunc) {
  4186. var t = this;
  4187. var rows;
  4188. var cols;
  4189. t.build = function() {
  4190. rows = [];
  4191. cols = [];
  4192. buildFunc(rows, cols);
  4193. };
  4194. t.cell = function(x, y) {
  4195. var rowCnt = rows.length;
  4196. var colCnt = cols.length;
  4197. var i, r=-1, c=-1;
  4198. for (i=0; i<rowCnt; i++) {
  4199. if (y >= rows[i][0] && y < rows[i][1]) {
  4200. r = i;
  4201. break;
  4202. }
  4203. }
  4204. for (i=0; i<colCnt; i++) {
  4205. if (x >= cols[i][0] && x < cols[i][1]) {
  4206. c = i;
  4207. break;
  4208. }
  4209. }
  4210. return (r>=0 && c>=0) ? { row:r, col:c } : null;
  4211. };
  4212. t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
  4213. var origin = originElement.offset();
  4214. return {
  4215. top: rows[row0][0] - origin.top,
  4216. left: cols[col0][0] - origin.left,
  4217. width: cols[col1][1] - cols[col0][0],
  4218. height: rows[row1][1] - rows[row0][0]
  4219. };
  4220. };
  4221. }
  4222. function HoverListener(coordinateGrid) {
  4223. var t = this;
  4224. var bindType;
  4225. var change;
  4226. var firstCell;
  4227. var cell;
  4228. t.start = function(_change, ev, _bindType) {
  4229. change = _change;
  4230. firstCell = cell = null;
  4231. coordinateGrid.build();
  4232. mouse(ev);
  4233. bindType = _bindType || 'mousemove';
  4234. $(document).bind(bindType, mouse);
  4235. };
  4236. function mouse(ev) {
  4237. _fixUIEvent(ev); // see below
  4238. var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
  4239. if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
  4240. if (newCell) {
  4241. if (!firstCell) {
  4242. firstCell = newCell;
  4243. }
  4244. change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
  4245. }else{
  4246. change(newCell, firstCell);
  4247. }
  4248. cell = newCell;
  4249. }
  4250. }
  4251. t.stop = function() {
  4252. $(document).unbind(bindType, mouse);
  4253. return cell;
  4254. };
  4255. }
  4256. // this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
  4257. // upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
  4258. // but keep this in here for 1.8.16 users
  4259. // and maybe remove it down the line
  4260. function _fixUIEvent(event) { // for issue 1168
  4261. if (event.pageX === undefined) {
  4262. event.pageX = event.originalEvent.pageX;
  4263. event.pageY = event.originalEvent.pageY;
  4264. }
  4265. }
  4266. function HorizontalPositionCache(getElement) {
  4267. var t = this,
  4268. elements = {},
  4269. lefts = {},
  4270. rights = {};
  4271. function e(i) {
  4272. return elements[i] = elements[i] || getElement(i);
  4273. }
  4274. t.left = function(i) {
  4275. return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
  4276. };
  4277. t.right = function(i) {
  4278. return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
  4279. };
  4280. t.clear = function() {
  4281. elements = {};
  4282. lefts = {};
  4283. rights = {};
  4284. };
  4285. }
  4286. })(jQuery);