Path: blob/master/web-gui/buildyourownbotnet/assets/js/fullcalendar/fullcalendar.js
1293 views
/*!1* FullCalendar v1.6.42* Docs & License: http://arshaw.com/fullcalendar/3* (c) 2013 Adam Shaw4*/56/*7* Use fullcalendar.css for basic styling.8* For event drag & drop, requires jQuery UI draggable.9* For event resizing, requires jQuery UI resizable.10*/1112(function($, undefined) {131415;;1617var defaults = {1819// display20defaultView: 'month',21aspectRatio: 1.35,22header: {23left: 'title',24center: '',25right: 'today prev,next'26},27weekends: true,28weekNumbers: false,29weekNumberCalculation: 'iso',30weekNumberTitle: 'W',3132// editing33//editable: false,34//disableDragging: false,35//disableResizing: false,3637allDayDefault: true,38ignoreTimezone: true,3940// event ajax41lazyFetching: true,42startParam: 'start',43endParam: 'end',4445// time formats46titleFormat: {47month: 'MMMM yyyy',48week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",49day: 'dddd, MMM d, yyyy'50},51columnFormat: {52month: 'ddd',53week: 'ddd M/d',54day: 'dddd M/d'55},56timeFormat: { // for event elements57'': 'h(:mm)t' // default58},5960// locale61isRTL: false,62firstDay: 0,63monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],64monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],65dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],66dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],67buttonText: {68prev: "<span class='fc-text-arrow'>‹</span>",69next: "<span class='fc-text-arrow'>›</span>",70prevYear: "<span class='fc-text-arrow'>«</span>",71nextYear: "<span class='fc-text-arrow'>»</span>",72today: 'today',73month: 'month',74week: 'week',75day: 'day'76},7778// jquery-ui theming79theme: false,80buttonIcons: {81prev: 'circle-triangle-w',82next: 'circle-triangle-e'83},8485//selectable: false,86unselectAuto: true,8788dropAccept: '*',8990handleWindowResize: true9192};9394// right-to-left defaults95var rtlDefaults = {96header: {97left: 'next,prev today',98center: '',99right: 'title'100},101buttonText: {102prev: "<span class='fc-text-arrow'>›</span>",103next: "<span class='fc-text-arrow'>‹</span>",104prevYear: "<span class='fc-text-arrow'>»</span>",105nextYear: "<span class='fc-text-arrow'>«</span>"106},107buttonIcons: {108prev: 'circle-triangle-e',109next: 'circle-triangle-w'110}111};112113114115;;116117var fc = $.fullCalendar = { version: "1.6.4" };118var fcViews = fc.views = {};119120121$.fn.fullCalendar = function(options) {122123124// method calling125if (typeof options == 'string') {126var args = Array.prototype.slice.call(arguments, 1);127var res;128this.each(function() {129var calendar = $.data(this, 'fullCalendar');130if (calendar && $.isFunction(calendar[options])) {131var r = calendar[options].apply(calendar, args);132if (res === undefined) {133res = r;134}135if (options == 'destroy') {136$.removeData(this, 'fullCalendar');137}138}139});140if (res !== undefined) {141return res;142}143return this;144}145146options = options || {};147148// would like to have this logic in EventManager, but needs to happen before options are recursively extended149var eventSources = options.eventSources || [];150delete options.eventSources;151if (options.events) {152eventSources.push(options.events);153delete options.events;154}155156157options = $.extend(true, {},158defaults,159(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},160options161);162163164this.each(function(i, _element) {165var element = $(_element);166var calendar = new Calendar(element, options, eventSources);167element.data('fullCalendar', calendar); // TODO: look into memory leak implications168calendar.render();169});170171172return this;173174};175176177// function for adding/overriding defaults178function setDefaults(d) {179$.extend(true, defaults, d);180}181182183184;;185186187function Calendar(element, options, eventSources) {188var t = this;189190191// exports192t.options = options;193t.render = render;194t.destroy = destroy;195t.refetchEvents = refetchEvents;196t.reportEvents = reportEvents;197t.reportEventChange = reportEventChange;198t.rerenderEvents = rerenderEvents;199t.changeView = changeView;200t.select = select;201t.unselect = unselect;202t.prev = prev;203t.next = next;204t.prevYear = prevYear;205t.nextYear = nextYear;206t.today = today;207t.gotoDate = gotoDate;208t.incrementDate = incrementDate;209t.formatDate = function(format, date) { return formatDate(format, date, options) };210t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };211t.getDate = getDate;212t.getView = getView;213t.option = option;214t.trigger = trigger;215216217// imports218EventManager.call(t, options, eventSources);219var isFetchNeeded = t.isFetchNeeded;220var fetchEvents = t.fetchEvents;221222223// locals224var _element = element[0];225var header;226var headerElement;227var content;228var tm; // for making theme classes229var currentView;230var elementOuterWidth;231var suggestedViewHeight;232var resizeUID = 0;233var ignoreWindowResize = 0;234var date = new Date();235var events = [];236var _dragElement;237238239240/* Main Rendering241-----------------------------------------------------------------------------*/242243244setYMD(date, options.year, options.month, options.date);245246247function render(inc) {248if (!content) {249initialRender();250}251else if (elementVisible()) {252// mainly for the public API253calcSize();254_renderView(inc);255}256}257258259function initialRender() {260tm = options.theme ? 'ui' : 'fc';261element.addClass('fc');262if (options.isRTL) {263element.addClass('fc-rtl');264}265else {266element.addClass('fc-ltr');267}268if (options.theme) {269element.addClass('ui-widget');270}271272content = $("<div class='fc-content' style='position:relative'/>")273.prependTo(element);274275header = new Header(t, options);276headerElement = header.render();277if (headerElement) {278element.prepend(headerElement);279}280281changeView(options.defaultView);282283if (options.handleWindowResize) {284$(window).resize(windowResize);285}286287// needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize288if (!bodyVisible()) {289lateRender();290}291}292293294// called when we know the calendar couldn't be rendered when it was initialized,295// but we think it's ready now296function lateRender() {297setTimeout(function() { // IE7 needs this so dimensions are calculated correctly298if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once299renderView();300}301},0);302}303304305function destroy() {306307if (currentView) {308trigger('viewDestroy', currentView, currentView, currentView.element);309currentView.triggerEventDestroy();310}311312$(window).unbind('resize', windowResize);313314header.destroy();315content.remove();316element.removeClass('fc fc-rtl ui-widget');317}318319320function elementVisible() {321return element.is(':visible');322}323324325function bodyVisible() {326return $('body').is(':visible');327}328329330331/* View Rendering332-----------------------------------------------------------------------------*/333334335function changeView(newViewName) {336if (!currentView || newViewName != currentView.name) {337_changeView(newViewName);338}339}340341342function _changeView(newViewName) {343ignoreWindowResize++;344345if (currentView) {346trigger('viewDestroy', currentView, currentView, currentView.element);347unselect();348currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event349freezeContentHeight();350currentView.element.remove();351header.deactivateButton(currentView.name);352}353354header.activateButton(newViewName);355356currentView = new fcViews[newViewName](357$("<div class='fc-view fc-view-" + newViewName + "' style='position:relative'/>")358.appendTo(content),359t // the calendar object360);361362renderView();363unfreezeContentHeight();364365ignoreWindowResize--;366}367368369function renderView(inc) {370if (371!currentView.start || // never rendered before372inc || date < currentView.start || date >= currentView.end // or new date range373) {374if (elementVisible()) {375_renderView(inc);376}377}378}379380381function _renderView(inc) { // assumes elementVisible382ignoreWindowResize++;383384if (currentView.start) { // already been rendered?385trigger('viewDestroy', currentView, currentView, currentView.element);386unselect();387clearEvents();388}389390freezeContentHeight();391currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else392setSize();393unfreezeContentHeight();394(currentView.afterRender || noop)();395396updateTitle();397updateTodayButton();398399trigger('viewRender', currentView, currentView, currentView.element);400currentView.trigger('viewDisplay', _element); // deprecated401402ignoreWindowResize--;403404getAndRenderEvents();405}406407408409/* Resizing410-----------------------------------------------------------------------------*/411412413function updateSize() {414if (elementVisible()) {415unselect();416clearEvents();417calcSize();418setSize();419renderEvents();420}421}422423424function calcSize() { // assumes elementVisible425if (options.contentHeight) {426suggestedViewHeight = options.contentHeight;427}428else if (options.height) {429suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);430}431else {432suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));433}434}435436437function setSize() { // assumes elementVisible438439if (suggestedViewHeight === undefined) {440calcSize(); // for first time441// NOTE: we don't want to recalculate on every renderView because442// it could result in oscillating heights due to scrollbars.443}444445ignoreWindowResize++;446currentView.setHeight(suggestedViewHeight);447currentView.setWidth(content.width());448ignoreWindowResize--;449450elementOuterWidth = element.outerWidth();451}452453454function windowResize() {455if (!ignoreWindowResize) {456if (currentView.start) { // view has already been rendered457var uid = ++resizeUID;458setTimeout(function() { // add a delay459if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {460if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {461ignoreWindowResize++; // in case the windowResize callback changes the height462updateSize();463currentView.trigger('windowResize', _element);464ignoreWindowResize--;465}466}467}, 200);468}else{469// calendar must have been initialized in a 0x0 iframe that has just been resized470lateRender();471}472}473}474475476477/* Event Fetching/Rendering478-----------------------------------------------------------------------------*/479// TODO: going forward, most of this stuff should be directly handled by the view480481482function refetchEvents() { // can be called as an API method483clearEvents();484fetchAndRenderEvents();485}486487488function rerenderEvents(modifiedEventID) { // can be called as an API method489clearEvents();490renderEvents(modifiedEventID);491}492493494function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack495if (elementVisible()) {496currentView.setEventData(events); // for View.js, TODO: unify with renderEvents497currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements498currentView.trigger('eventAfterAllRender');499}500}501502503function clearEvents() {504currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event505currentView.clearEvents(); // actually remove the DOM elements506currentView.clearEventData(); // for View.js, TODO: unify with clearEvents507}508509510function getAndRenderEvents() {511if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {512fetchAndRenderEvents();513}514else {515renderEvents();516}517}518519520function fetchAndRenderEvents() {521fetchEvents(currentView.visStart, currentView.visEnd);522// ... will call reportEvents523// ... which will call renderEvents524}525526527// called when event data arrives528function reportEvents(_events) {529events = _events;530renderEvents();531}532533534// called when a single event's data has been changed535function reportEventChange(eventID) {536rerenderEvents(eventID);537}538539540541/* Header Updating542-----------------------------------------------------------------------------*/543544545function updateTitle() {546header.updateTitle(currentView.title);547}548549550function updateTodayButton() {551var today = new Date();552if (today >= currentView.start && today < currentView.end) {553header.disableButton('today');554}555else {556header.enableButton('today');557}558}559560561562/* Selection563-----------------------------------------------------------------------------*/564565566function select(start, end, allDay) {567currentView.select(start, end, allDay===undefined ? true : allDay);568}569570571function unselect() { // safe to be called before renderView572if (currentView) {573currentView.unselect();574}575}576577578579/* Date580-----------------------------------------------------------------------------*/581582583function prev() {584renderView(-1);585}586587588function next() {589renderView(1);590}591592593function prevYear() {594addYears(date, -1);595renderView();596}597598599function nextYear() {600addYears(date, 1);601renderView();602}603604605function today() {606date = new Date();607renderView();608}609610611function gotoDate(year, month, dateOfMonth) {612if (year instanceof Date) {613date = cloneDate(year); // provided 1 argument, a Date614}else{615setYMD(date, year, month, dateOfMonth);616}617renderView();618}619620621function incrementDate(years, months, days) {622if (years !== undefined) {623addYears(date, years);624}625if (months !== undefined) {626addMonths(date, months);627}628if (days !== undefined) {629addDays(date, days);630}631renderView();632}633634635function getDate() {636return cloneDate(date);637}638639640641/* Height "Freezing"642-----------------------------------------------------------------------------*/643644645function freezeContentHeight() {646content.css({647width: '100%',648height: content.height(),649overflow: 'hidden'650});651}652653654function unfreezeContentHeight() {655content.css({656width: '',657height: '',658overflow: ''659});660}661662663664/* Misc665-----------------------------------------------------------------------------*/666667668function getView() {669return currentView;670}671672673function option(name, value) {674if (value === undefined) {675return options[name];676}677if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {678options[name] = value;679updateSize();680}681}682683684function trigger(name, thisObj) {685if (options[name]) {686return options[name].apply(687thisObj || _element,688Array.prototype.slice.call(arguments, 2)689);690}691}692693694695/* External Dragging696------------------------------------------------------------------------*/697698if (options.droppable) {699$(document)700.bind('dragstart', function(ev, ui) {701var _e = ev.target;702var e = $(_e);703if (!e.parents('.fc').length) { // not already inside a calendar704var accept = options.dropAccept;705if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {706_dragElement = _e;707currentView.dragStart(_dragElement, ev, ui);708}709}710})711.bind('dragstop', function(ev, ui) {712if (_dragElement) {713currentView.dragStop(_dragElement, ev, ui);714_dragElement = null;715}716});717}718719720}721722;;723724function Header(calendar, options) {725var t = this;726727728// exports729t.render = render;730t.destroy = destroy;731t.updateTitle = updateTitle;732t.activateButton = activateButton;733t.deactivateButton = deactivateButton;734t.disableButton = disableButton;735t.enableButton = enableButton;736737738// locals739var element = $([]);740var tm;741742743744function render() {745tm = options.theme ? 'ui' : 'fc';746var sections = options.header;747if (sections) {748element = $("<table class='fc-header' style='width:100%'/>")749.append(750$("<tr/>")751.append(renderSection('left'))752.append(renderSection('center'))753.append(renderSection('right'))754);755return element;756}757}758759760function destroy() {761element.remove();762}763764765function renderSection(position) {766var e = $("<td class='fc-header-" + position + "'/>");767var buttonStr = options.header[position];768if (buttonStr) {769$.each(buttonStr.split(' '), function(i) {770if (i > 0) {771e.append("<span class='fc-header-space'/>");772}773var prevButton;774$.each(this.split(','), function(j, buttonName) {775if (buttonName == 'title') {776e.append("<span class='fc-header-title'><h2> </h2></span>");777if (prevButton) {778prevButton.addClass(tm + '-corner-right');779}780prevButton = null;781}else{782var buttonClick;783if (calendar[buttonName]) {784buttonClick = calendar[buttonName]; // calendar method785}786else if (fcViews[buttonName]) {787buttonClick = function() {788button.removeClass(tm + '-state-hover'); // forget why789calendar.changeView(buttonName);790};791}792if (buttonClick) {793var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?794var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?795var button = $(796"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +797(icon ?798"<span class='fc-icon-wrap'>" +799"<span class='ui-icon ui-icon-" + icon + "'/>" +800"</span>" :801text802) +803"</span>"804)805.click(function() {806if (!button.hasClass(tm + '-state-disabled')) {807buttonClick();808}809})810.mousedown(function() {811button812.not('.' + tm + '-state-active')813.not('.' + tm + '-state-disabled')814.addClass(tm + '-state-down');815})816.mouseup(function() {817button.removeClass(tm + '-state-down');818})819.hover(820function() {821button822.not('.' + tm + '-state-active')823.not('.' + tm + '-state-disabled')824.addClass(tm + '-state-hover');825},826function() {827button828.removeClass(tm + '-state-hover')829.removeClass(tm + '-state-down');830}831)832.appendTo(e);833disableTextSelection(button);834if (!prevButton) {835button.addClass(tm + '-corner-left');836}837prevButton = button;838}839}840});841if (prevButton) {842prevButton.addClass(tm + '-corner-right');843}844});845}846return e;847}848849850function updateTitle(html) {851element.find('h2')852.html(html);853}854855856function activateButton(buttonName) {857element.find('span.fc-button-' + buttonName)858.addClass(tm + '-state-active');859}860861862function deactivateButton(buttonName) {863element.find('span.fc-button-' + buttonName)864.removeClass(tm + '-state-active');865}866867868function disableButton(buttonName) {869element.find('span.fc-button-' + buttonName)870.addClass(tm + '-state-disabled');871}872873874function enableButton(buttonName) {875element.find('span.fc-button-' + buttonName)876.removeClass(tm + '-state-disabled');877}878879880}881882;;883884fc.sourceNormalizers = [];885fc.sourceFetchers = [];886887var ajaxDefaults = {888dataType: 'json',889cache: false890};891892var eventGUID = 1;893894895function EventManager(options, _sources) {896var t = this;897898899// exports900t.isFetchNeeded = isFetchNeeded;901t.fetchEvents = fetchEvents;902t.addEventSource = addEventSource;903t.removeEventSource = removeEventSource;904t.updateEvent = updateEvent;905t.renderEvent = renderEvent;906t.removeEvents = removeEvents;907t.clientEvents = clientEvents;908t.normalizeEvent = normalizeEvent;909910911// imports912var trigger = t.trigger;913var getView = t.getView;914var reportEvents = t.reportEvents;915916917// locals918var stickySource = { events: [] };919var sources = [ stickySource ];920var rangeStart, rangeEnd;921var currentFetchID = 0;922var pendingSourceCnt = 0;923var loadingLevel = 0;924var cache = [];925926927for (var i=0; i<_sources.length; i++) {928_addEventSource(_sources[i]);929}930931932933/* Fetching934-----------------------------------------------------------------------------*/935936937function isFetchNeeded(start, end) {938return !rangeStart || start < rangeStart || end > rangeEnd;939}940941942function fetchEvents(start, end) {943rangeStart = start;944rangeEnd = end;945cache = [];946var fetchID = ++currentFetchID;947var len = sources.length;948pendingSourceCnt = len;949for (var i=0; i<len; i++) {950fetchEventSource(sources[i], fetchID);951}952}953954955function fetchEventSource(source, fetchID) {956_fetchEventSource(source, function(events) {957if (fetchID == currentFetchID) {958if (events) {959960if (options.eventDataTransform) {961events = $.map(events, options.eventDataTransform);962}963if (source.eventDataTransform) {964events = $.map(events, source.eventDataTransform);965}966// TODO: this technique is not ideal for static array event sources.967// For arrays, we'll want to process all events right in the beginning, then never again.968969for (var i=0; i<events.length; i++) {970events[i].source = source;971normalizeEvent(events[i]);972}973cache = cache.concat(events);974}975pendingSourceCnt--;976if (!pendingSourceCnt) {977reportEvents(cache);978}979}980});981}982983984function _fetchEventSource(source, callback) {985var i;986var fetchers = fc.sourceFetchers;987var res;988for (i=0; i<fetchers.length; i++) {989res = fetchers[i](source, rangeStart, rangeEnd, callback);990if (res === true) {991// the fetcher is in charge. made its own async request992return;993}994else if (typeof res == 'object') {995// the fetcher returned a new source. process it996_fetchEventSource(res, callback);997return;998}999}1000var events = source.events;1001if (events) {1002if ($.isFunction(events)) {1003pushLoading();1004events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {1005callback(events);1006popLoading();1007});1008}1009else if ($.isArray(events)) {1010callback(events);1011}1012else {1013callback();1014}1015}else{1016var url = source.url;1017if (url) {1018var success = source.success;1019var error = source.error;1020var complete = source.complete;10211022// retrieve any outbound GET/POST $.ajax data from the options1023var customData;1024if ($.isFunction(source.data)) {1025// supplied as a function that returns a key/value object1026customData = source.data();1027}1028else {1029// supplied as a straight key/value object1030customData = source.data;1031}10321033// use a copy of the custom data so we can modify the parameters1034// and not affect the passed-in object.1035var data = $.extend({}, customData || {});10361037var startParam = firstDefined(source.startParam, options.startParam);1038var endParam = firstDefined(source.endParam, options.endParam);1039if (startParam) {1040data[startParam] = Math.round(+rangeStart / 1000);1041}1042if (endParam) {1043data[endParam] = Math.round(+rangeEnd / 1000);1044}10451046pushLoading();1047$.ajax($.extend({}, ajaxDefaults, source, {1048data: data,1049success: function(events) {1050events = events || [];1051var res = applyAll(success, this, arguments);1052if ($.isArray(res)) {1053events = res;1054}1055callback(events);1056},1057error: function() {1058applyAll(error, this, arguments);1059callback();1060},1061complete: function() {1062applyAll(complete, this, arguments);1063popLoading();1064}1065}));1066}else{1067callback();1068}1069}1070}1071107210731074/* Sources1075-----------------------------------------------------------------------------*/107610771078function addEventSource(source) {1079source = _addEventSource(source);1080if (source) {1081pendingSourceCnt++;1082fetchEventSource(source, currentFetchID); // will eventually call reportEvents1083}1084}108510861087function _addEventSource(source) {1088if ($.isFunction(source) || $.isArray(source)) {1089source = { events: source };1090}1091else if (typeof source == 'string') {1092source = { url: source };1093}1094if (typeof source == 'object') {1095normalizeSource(source);1096sources.push(source);1097return source;1098}1099}110011011102function removeEventSource(source) {1103sources = $.grep(sources, function(src) {1104return !isSourcesEqual(src, source);1105});1106// remove all client events from that source1107cache = $.grep(cache, function(e) {1108return !isSourcesEqual(e.source, source);1109});1110reportEvents(cache);1111}1112111311141115/* Manipulation1116-----------------------------------------------------------------------------*/111711181119function updateEvent(event) { // update an existing event1120var i, len = cache.length, e,1121defaultEventEnd = getView().defaultEventEnd, // getView???1122startDelta = event.start - event._start,1123endDelta = event.end ?1124(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end1125: 0; // was null and event was just resized1126for (i=0; i<len; i++) {1127e = cache[i];1128if (e._id == event._id && e != event) {1129e.start = new Date(+e.start + startDelta);1130if (event.end) {1131if (e.end) {1132e.end = new Date(+e.end + endDelta);1133}else{1134e.end = new Date(+defaultEventEnd(e) + endDelta);1135}1136}else{1137e.end = null;1138}1139e.title = event.title;1140e.url = event.url;1141e.allDay = event.allDay;1142e.className = event.className;1143e.editable = event.editable;1144e.color = event.color;1145e.backgroundColor = event.backgroundColor;1146e.borderColor = event.borderColor;1147e.textColor = event.textColor;1148normalizeEvent(e);1149}1150}1151normalizeEvent(event);1152reportEvents(cache);1153}115411551156function renderEvent(event, stick) {1157normalizeEvent(event);1158if (!event.source) {1159if (stick) {1160stickySource.events.push(event);1161event.source = stickySource;1162}1163cache.push(event);1164}1165reportEvents(cache);1166}116711681169function removeEvents(filter) {1170if (!filter) { // remove all1171cache = [];1172// clear all array sources1173for (var i=0; i<sources.length; i++) {1174if ($.isArray(sources[i].events)) {1175sources[i].events = [];1176}1177}1178}else{1179if (!$.isFunction(filter)) { // an event ID1180var id = filter + '';1181filter = function(e) {1182return e._id == id;1183};1184}1185cache = $.grep(cache, filter, true);1186// remove events from array sources1187for (var i=0; i<sources.length; i++) {1188if ($.isArray(sources[i].events)) {1189sources[i].events = $.grep(sources[i].events, filter, true);1190}1191}1192}1193reportEvents(cache);1194}119511961197function clientEvents(filter) {1198if ($.isFunction(filter)) {1199return $.grep(cache, filter);1200}1201else if (filter) { // an event ID1202filter += '';1203return $.grep(cache, function(e) {1204return e._id == filter;1205});1206}1207return cache; // else, return all1208}1209121012111212/* Loading State1213-----------------------------------------------------------------------------*/121412151216function pushLoading() {1217if (!loadingLevel++) {1218trigger('loading', null, true, getView());1219}1220}122112221223function popLoading() {1224if (!--loadingLevel) {1225trigger('loading', null, false, getView());1226}1227}1228122912301231/* Event Normalization1232-----------------------------------------------------------------------------*/123312341235function normalizeEvent(event) {1236var source = event.source || {};1237var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);1238event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');1239if (event.date) {1240if (!event.start) {1241event.start = event.date;1242}1243delete event.date;1244}1245event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));1246event.end = parseDate(event.end, ignoreTimezone);1247if (event.end && event.end <= event.start) {1248event.end = null;1249}1250event._end = event.end ? cloneDate(event.end) : null;1251if (event.allDay === undefined) {1252event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);1253}1254if (event.className) {1255if (typeof event.className == 'string') {1256event.className = event.className.split(/\s+/);1257}1258}else{1259event.className = [];1260}1261// TODO: if there is no start date, return false to indicate an invalid event1262}1263126412651266/* Utils1267------------------------------------------------------------------------------*/126812691270function normalizeSource(source) {1271if (source.className) {1272// TODO: repeat code, same code for event classNames1273if (typeof source.className == 'string') {1274source.className = source.className.split(/\s+/);1275}1276}else{1277source.className = [];1278}1279var normalizers = fc.sourceNormalizers;1280for (var i=0; i<normalizers.length; i++) {1281normalizers[i](source);1282}1283}128412851286function isSourcesEqual(source1, source2) {1287return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);1288}128912901291function getSourcePrimitive(source) {1292return ((typeof source == 'object') ? (source.events || source.url) : '') || source;1293}129412951296}12971298;;129913001301fc.addDays = addDays;1302fc.cloneDate = cloneDate;1303fc.parseDate = parseDate;1304fc.parseISO8601 = parseISO8601;1305fc.parseTime = parseTime;1306fc.formatDate = formatDate;1307fc.formatDates = formatDates;1308130913101311/* Date Math1312-----------------------------------------------------------------------------*/13131314var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],1315DAY_MS = 86400000,1316HOUR_MS = 3600000,1317MINUTE_MS = 60000;131813191320function addYears(d, n, keepTime) {1321d.setFullYear(d.getFullYear() + n);1322if (!keepTime) {1323clearTime(d);1324}1325return d;1326}132713281329function addMonths(d, n, keepTime) { // prevents day overflow/underflow1330if (+d) { // prevent infinite looping on invalid dates1331var m = d.getMonth() + n,1332check = cloneDate(d);1333check.setDate(1);1334check.setMonth(m);1335d.setMonth(m);1336if (!keepTime) {1337clearTime(d);1338}1339while (d.getMonth() != check.getMonth()) {1340d.setDate(d.getDate() + (d < check ? 1 : -1));1341}1342}1343return d;1344}134513461347function addDays(d, n, keepTime) { // deals with daylight savings1348if (+d) {1349var dd = d.getDate() + n,1350check = cloneDate(d);1351check.setHours(9); // set to middle of day1352check.setDate(dd);1353d.setDate(dd);1354if (!keepTime) {1355clearTime(d);1356}1357fixDate(d, check);1358}1359return d;1360}136113621363function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes1364if (+d) { // prevent infinite looping on invalid dates1365while (d.getDate() != check.getDate()) {1366d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);1367}1368}1369}137013711372function addMinutes(d, n) {1373d.setMinutes(d.getMinutes() + n);1374return d;1375}137613771378function clearTime(d) {1379d.setHours(0);1380d.setMinutes(0);1381d.setSeconds(0);1382d.setMilliseconds(0);1383return d;1384}138513861387function cloneDate(d, dontKeepTime) {1388if (dontKeepTime) {1389return clearTime(new Date(+d));1390}1391return new Date(+d);1392}139313941395function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=11396var i=0, d;1397do {1398d = new Date(1970, i++, 1);1399} while (d.getHours()); // != 01400return d;1401}140214031404function dayDiff(d1, d2) { // d1 - d21405return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);1406}140714081409function setYMD(date, y, m, d) {1410if (y !== undefined && y != date.getFullYear()) {1411date.setDate(1);1412date.setMonth(0);1413date.setFullYear(y);1414}1415if (m !== undefined && m != date.getMonth()) {1416date.setDate(1);1417date.setMonth(m);1418}1419if (d !== undefined) {1420date.setDate(d);1421}1422}1423142414251426/* Date Parsing1427-----------------------------------------------------------------------------*/142814291430function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true1431if (typeof s == 'object') { // already a Date object1432return s;1433}1434if (typeof s == 'number') { // a UNIX timestamp1435return new Date(s * 1000);1436}1437if (typeof s == 'string') {1438if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp1439return new Date(parseFloat(s) * 1000);1440}1441if (ignoreTimezone === undefined) {1442ignoreTimezone = true;1443}1444return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);1445}1446// TODO: never return invalid dates (like from new Date(<string>)), return null instead1447return null;1448}144914501451function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false1452// derived from http://delete.me.uk/2005/03/iso8601.html1453// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html1454var 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}))?))?)?)?)?$/);1455if (!m) {1456return null;1457}1458var date = new Date(m[1], 0, 1);1459if (ignoreTimezone || !m[13]) {1460var check = new Date(m[1], 0, 1, 9, 0);1461if (m[3]) {1462date.setMonth(m[3] - 1);1463check.setMonth(m[3] - 1);1464}1465if (m[5]) {1466date.setDate(m[5]);1467check.setDate(m[5]);1468}1469fixDate(date, check);1470if (m[7]) {1471date.setHours(m[7]);1472}1473if (m[8]) {1474date.setMinutes(m[8]);1475}1476if (m[10]) {1477date.setSeconds(m[10]);1478}1479if (m[12]) {1480date.setMilliseconds(Number("0." + m[12]) * 1000);1481}1482fixDate(date, check);1483}else{1484date.setUTCFullYear(1485m[1],1486m[3] ? m[3] - 1 : 0,1487m[5] || 11488);1489date.setUTCHours(1490m[7] || 0,1491m[8] || 0,1492m[10] || 0,1493m[12] ? Number("0." + m[12]) * 1000 : 01494);1495if (m[14]) {1496var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);1497offset *= m[15] == '-' ? 1 : -1;1498date = new Date(+date + (offset * 60 * 1000));1499}1500}1501return date;1502}150315041505function parseTime(s) { // returns minutes since start of day1506if (typeof s == 'number') { // an hour1507return s * 60;1508}1509if (typeof s == 'object') { // a Date object1510return s.getHours() * 60 + s.getMinutes();1511}1512var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);1513if (m) {1514var h = parseInt(m[1], 10);1515if (m[3]) {1516h %= 12;1517if (m[3].toLowerCase().charAt(0) == 'p') {1518h += 12;1519}1520}1521return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);1522}1523}1524152515261527/* Date Formatting1528-----------------------------------------------------------------------------*/1529// TODO: use same function formatDate(date, [date2], format, [options])153015311532function formatDate(date, format, options) {1533return formatDates(date, null, format, options);1534}153515361537function formatDates(date1, date2, format, options) {1538options = options || defaults;1539var date = date1,1540otherDate = date2,1541i, len = format.length, c,1542i2, formatter,1543res = '';1544for (i=0; i<len; i++) {1545c = format.charAt(i);1546if (c == "'") {1547for (i2=i+1; i2<len; i2++) {1548if (format.charAt(i2) == "'") {1549if (date) {1550if (i2 == i+1) {1551res += "'";1552}else{1553res += format.substring(i+1, i2);1554}1555i = i2;1556}1557break;1558}1559}1560}1561else if (c == '(') {1562for (i2=i+1; i2<len; i2++) {1563if (format.charAt(i2) == ')') {1564var subres = formatDate(date, format.substring(i+1, i2), options);1565if (parseInt(subres.replace(/\D/, ''), 10)) {1566res += subres;1567}1568i = i2;1569break;1570}1571}1572}1573else if (c == '[') {1574for (i2=i+1; i2<len; i2++) {1575if (format.charAt(i2) == ']') {1576var subformat = format.substring(i+1, i2);1577var subres = formatDate(date, subformat, options);1578if (subres != formatDate(otherDate, subformat, options)) {1579res += subres;1580}1581i = i2;1582break;1583}1584}1585}1586else if (c == '{') {1587date = date2;1588otherDate = date1;1589}1590else if (c == '}') {1591date = date1;1592otherDate = date2;1593}1594else {1595for (i2=len; i2>i; i2--) {1596if (formatter = dateFormatters[format.substring(i, i2)]) {1597if (date) {1598res += formatter(date, options);1599}1600i = i2 - 1;1601break;1602}1603}1604if (i2 == i) {1605if (date) {1606res += c;1607}1608}1609}1610}1611return res;1612};161316141615var dateFormatters = {1616s : function(d) { return d.getSeconds() },1617ss : function(d) { return zeroPad(d.getSeconds()) },1618m : function(d) { return d.getMinutes() },1619mm : function(d) { return zeroPad(d.getMinutes()) },1620h : function(d) { return d.getHours() % 12 || 12 },1621hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },1622H : function(d) { return d.getHours() },1623HH : function(d) { return zeroPad(d.getHours()) },1624d : function(d) { return d.getDate() },1625dd : function(d) { return zeroPad(d.getDate()) },1626ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },1627dddd: function(d,o) { return o.dayNames[d.getDay()] },1628M : function(d) { return d.getMonth() + 1 },1629MM : function(d) { return zeroPad(d.getMonth() + 1) },1630MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },1631MMMM: function(d,o) { return o.monthNames[d.getMonth()] },1632yy : function(d) { return (d.getFullYear()+'').substring(2) },1633yyyy: function(d) { return d.getFullYear() },1634t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },1635tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },1636T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },1637TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },1638u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },1639S : function(d) {1640var date = d.getDate();1641if (date > 10 && date < 20) {1642return 'th';1643}1644return ['st', 'nd', 'rd'][date%10-1] || 'th';1645},1646w : function(d, o) { // local1647return o.weekNumberCalculation(d);1648},1649W : function(d) { // ISO1650return iso8601Week(d);1651}1652};1653fc.dateFormatters = dateFormatters;165416551656/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js)1657*1658* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.1659* `date` - the date to get the week for1660* `number` - the number of the week within the year that contains this date1661*/1662function iso8601Week(date) {1663var time;1664var checkDate = new Date(date.getTime());16651666// Find Thursday of this week starting on Monday1667checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));16681669time = checkDate.getTime();1670checkDate.setMonth(0); // Compare with Jan 11671checkDate.setDate(1);1672return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;1673}167416751676;;16771678fc.applyAll = applyAll;167916801681/* Event Date Math1682-----------------------------------------------------------------------------*/168316841685function exclEndDay(event) {1686if (event.end) {1687return _exclEndDay(event.end, event.allDay);1688}else{1689return addDays(cloneDate(event.start), 1);1690}1691}169216931694function _exclEndDay(end, allDay) {1695end = cloneDate(end);1696return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);1697// why don't we check for seconds/ms too?1698}1699170017011702/* Event Element Binding1703-----------------------------------------------------------------------------*/170417051706function lazySegBind(container, segs, bindHandlers) {1707container.unbind('mouseover').mouseover(function(ev) {1708var parent=ev.target, e,1709i, seg;1710while (parent != this) {1711e = parent;1712parent = parent.parentNode;1713}1714if ((i = e._fci) !== undefined) {1715e._fci = undefined;1716seg = segs[i];1717bindHandlers(seg.event, seg.element, seg);1718$(ev.target).trigger(ev);1719}1720ev.stopPropagation();1721});1722}1723172417251726/* Element Dimensions1727-----------------------------------------------------------------------------*/172817291730function setOuterWidth(element, width, includeMargins) {1731for (var i=0, e; i<element.length; i++) {1732e = $(element[i]);1733e.width(Math.max(0, width - hsides(e, includeMargins)));1734}1735}173617371738function setOuterHeight(element, height, includeMargins) {1739for (var i=0, e; i<element.length; i++) {1740e = $(element[i]);1741e.height(Math.max(0, height - vsides(e, includeMargins)));1742}1743}174417451746function hsides(element, includeMargins) {1747return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);1748}174917501751function hpadding(element) {1752return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +1753(parseFloat($.css(element[0], 'paddingRight', true)) || 0);1754}175517561757function hmargins(element) {1758return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +1759(parseFloat($.css(element[0], 'marginRight', true)) || 0);1760}176117621763function hborders(element) {1764return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +1765(parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);1766}176717681769function vsides(element, includeMargins) {1770return vpadding(element) + vborders(element) + (includeMargins ? vmargins(element) : 0);1771}177217731774function vpadding(element) {1775return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +1776(parseFloat($.css(element[0], 'paddingBottom', true)) || 0);1777}177817791780function vmargins(element) {1781return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +1782(parseFloat($.css(element[0], 'marginBottom', true)) || 0);1783}178417851786function vborders(element) {1787return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +1788(parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);1789}1790179117921793/* Misc Utils1794-----------------------------------------------------------------------------*/179517961797//TODO: arraySlice1798//TODO: isFunction, grep ?179918001801function noop() { }180218031804function dateCompare(a, b) {1805return a - b;1806}180718081809function arrayMax(a) {1810return Math.max.apply(Math, a);1811}181218131814function zeroPad(n) {1815return (n < 10 ? '0' : '') + n;1816}181718181819function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object1820if (obj[name] !== undefined) {1821return obj[name];1822}1823var parts = name.split(/(?=[A-Z])/),1824i=parts.length-1, res;1825for (; i>=0; i--) {1826res = obj[parts[i].toLowerCase()];1827if (res !== undefined) {1828return res;1829}1830}1831return obj[''];1832}183318341835function htmlEscape(s) {1836return s.replace(/&/g, '&')1837.replace(/</g, '<')1838.replace(/>/g, '>')1839.replace(/'/g, ''')1840.replace(/"/g, '"')1841.replace(/\n/g, '<br />');1842}184318441845function disableTextSelection(element) {1846element1847.attr('unselectable', 'on')1848.css('MozUserSelect', 'none')1849.bind('selectstart.ui', function() { return false; });1850}185118521853/*1854function enableTextSelection(element) {1855element1856.attr('unselectable', 'off')1857.css('MozUserSelect', '')1858.unbind('selectstart.ui');1859}1860*/186118621863function markFirstLast(e) {1864e.children()1865.removeClass('fc-first fc-last')1866.filter(':first-child')1867.addClass('fc-first')1868.end()1869.filter(':last-child')1870.addClass('fc-last');1871}187218731874function setDayID(cell, date) {1875cell.each(function(i, _cell) {1876_cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);1877// TODO: make a way that doesn't rely on order of classes1878});1879}188018811882function getSkinCss(event, opt) {1883var source = event.source || {};1884var eventColor = event.color;1885var sourceColor = source.color;1886var optionColor = opt('eventColor');1887var backgroundColor =1888event.backgroundColor ||1889eventColor ||1890source.backgroundColor ||1891sourceColor ||1892opt('eventBackgroundColor') ||1893optionColor;1894var borderColor =1895event.borderColor ||1896eventColor ||1897source.borderColor ||1898sourceColor ||1899opt('eventBorderColor') ||1900optionColor;1901var textColor =1902event.textColor ||1903source.textColor ||1904opt('eventTextColor');1905var statements = [];1906if (backgroundColor) {1907statements.push('background-color:' + backgroundColor);1908}1909if (borderColor) {1910statements.push('border-color:' + borderColor);1911}1912if (textColor) {1913statements.push('color:' + textColor);1914}1915return statements.join(';');1916}191719181919function applyAll(functions, thisObj, args) {1920if ($.isFunction(functions)) {1921functions = [ functions ];1922}1923if (functions) {1924var i;1925var ret;1926for (i=0; i<functions.length; i++) {1927ret = functions[i].apply(thisObj, args) || ret;1928}1929return ret;1930}1931}193219331934function firstDefined() {1935for (var i=0; i<arguments.length; i++) {1936if (arguments[i] !== undefined) {1937return arguments[i];1938}1939}1940}194119421943;;19441945fcViews.month = MonthView;19461947function MonthView(element, calendar) {1948var t = this;194919501951// exports1952t.render = render;195319541955// imports1956BasicView.call(t, element, calendar, 'month');1957var opt = t.opt;1958var renderBasic = t.renderBasic;1959var skipHiddenDays = t.skipHiddenDays;1960var getCellsPerWeek = t.getCellsPerWeek;1961var formatDate = calendar.formatDate;196219631964function render(date, delta) {19651966if (delta) {1967addMonths(date, delta);1968date.setDate(1);1969}19701971var firstDay = opt('firstDay');19721973var start = cloneDate(date, true);1974start.setDate(1);19751976var end = addMonths(cloneDate(start), 1);19771978var visStart = cloneDate(start);1979addDays(visStart, -((visStart.getDay() - firstDay + 7) % 7));1980skipHiddenDays(visStart);19811982var visEnd = cloneDate(end);1983addDays(visEnd, (7 - visEnd.getDay() + firstDay) % 7);1984skipHiddenDays(visEnd, -1, true);19851986var colCnt = getCellsPerWeek();1987var rowCnt = Math.round(dayDiff(visEnd, visStart) / 7); // should be no need for Math.round19881989if (opt('weekMode') == 'fixed') {1990addDays(visEnd, (6 - rowCnt) * 7); // add weeks to make up for it1991rowCnt = 6;1992}19931994t.title = formatDate(start, opt('titleFormat'));19951996t.start = start;1997t.end = end;1998t.visStart = visStart;1999t.visEnd = visEnd;20002001renderBasic(rowCnt, colCnt, true);2002}200320042005}20062007;;20082009fcViews.basicWeek = BasicWeekView;20102011function BasicWeekView(element, calendar) {2012var t = this;201320142015// exports2016t.render = render;201720182019// imports2020BasicView.call(t, element, calendar, 'basicWeek');2021var opt = t.opt;2022var renderBasic = t.renderBasic;2023var skipHiddenDays = t.skipHiddenDays;2024var getCellsPerWeek = t.getCellsPerWeek;2025var formatDates = calendar.formatDates;202620272028function render(date, delta) {20292030if (delta) {2031addDays(date, delta * 7);2032}20332034var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));2035var end = addDays(cloneDate(start), 7);20362037var visStart = cloneDate(start);2038skipHiddenDays(visStart);20392040var visEnd = cloneDate(end);2041skipHiddenDays(visEnd, -1, true);20422043var colCnt = getCellsPerWeek();20442045t.start = start;2046t.end = end;2047t.visStart = visStart;2048t.visEnd = visEnd;20492050t.title = formatDates(2051visStart,2052addDays(cloneDate(visEnd), -1),2053opt('titleFormat')2054);20552056renderBasic(1, colCnt, false);2057}205820592060}20612062;;20632064fcViews.basicDay = BasicDayView;206520662067function BasicDayView(element, calendar) {2068var t = this;206920702071// exports2072t.render = render;207320742075// imports2076BasicView.call(t, element, calendar, 'basicDay');2077var opt = t.opt;2078var renderBasic = t.renderBasic;2079var skipHiddenDays = t.skipHiddenDays;2080var formatDate = calendar.formatDate;208120822083function render(date, delta) {20842085if (delta) {2086addDays(date, delta);2087}2088skipHiddenDays(date, delta < 0 ? -1 : 1);20892090var start = cloneDate(date, true);2091var end = addDays(cloneDate(start), 1);20922093t.title = formatDate(date, opt('titleFormat'));20942095t.start = t.visStart = start;2096t.end = t.visEnd = end;20972098renderBasic(1, 1, false);2099}210021012102}21032104;;21052106setDefaults({2107weekMode: 'fixed'2108});210921102111function BasicView(element, calendar, viewName) {2112var t = this;211321142115// exports2116t.renderBasic = renderBasic;2117t.setHeight = setHeight;2118t.setWidth = setWidth;2119t.renderDayOverlay = renderDayOverlay;2120t.defaultSelectionEnd = defaultSelectionEnd;2121t.renderSelection = renderSelection;2122t.clearSelection = clearSelection;2123t.reportDayClick = reportDayClick; // for selection (kinda hacky)2124t.dragStart = dragStart;2125t.dragStop = dragStop;2126t.defaultEventEnd = defaultEventEnd;2127t.getHoverListener = function() { return hoverListener };2128t.colLeft = colLeft;2129t.colRight = colRight;2130t.colContentLeft = colContentLeft;2131t.colContentRight = colContentRight;2132t.getIsCellAllDay = function() { return true };2133t.allDayRow = allDayRow;2134t.getRowCnt = function() { return rowCnt };2135t.getColCnt = function() { return colCnt };2136t.getColWidth = function() { return colWidth };2137t.getDaySegmentContainer = function() { return daySegmentContainer };213821392140// imports2141View.call(t, element, calendar, viewName);2142OverlayManager.call(t);2143SelectionManager.call(t);2144BasicEventRenderer.call(t);2145var opt = t.opt;2146var trigger = t.trigger;2147var renderOverlay = t.renderOverlay;2148var clearOverlays = t.clearOverlays;2149var daySelectionMousedown = t.daySelectionMousedown;2150var cellToDate = t.cellToDate;2151var dateToCell = t.dateToCell;2152var rangeToSegments = t.rangeToSegments;2153var formatDate = calendar.formatDate;215421552156// locals21572158var table;2159var head;2160var headCells;2161var body;2162var bodyRows;2163var bodyCells;2164var bodyFirstCells;2165var firstRowCellInners;2166var firstRowCellContentInners;2167var daySegmentContainer;21682169var viewWidth;2170var viewHeight;2171var colWidth;2172var weekNumberWidth;21732174var rowCnt, colCnt;2175var showNumbers;2176var coordinateGrid;2177var hoverListener;2178var colPositions;2179var colContentPositions;21802181var tm;2182var colFormat;2183var showWeekNumbers;2184var weekNumberTitle;2185var weekNumberFormat;2186218721882189/* Rendering2190------------------------------------------------------------*/219121922193disableTextSelection(element.addClass('fc-grid'));219421952196function renderBasic(_rowCnt, _colCnt, _showNumbers) {2197rowCnt = _rowCnt;2198colCnt = _colCnt;2199showNumbers = _showNumbers;2200updateOptions();22012202if (!body) {2203buildEventContainer();2204}22052206buildTable();2207}220822092210function updateOptions() {2211tm = opt('theme') ? 'ui' : 'fc';2212colFormat = opt('columnFormat');22132214// week # options. (TODO: bad, logic also in other views)2215showWeekNumbers = opt('weekNumbers');2216weekNumberTitle = opt('weekNumberTitle');2217if (opt('weekNumberCalculation') != 'iso') {2218weekNumberFormat = "w";2219}2220else {2221weekNumberFormat = "W";2222}2223}222422252226function buildEventContainer() {2227daySegmentContainer =2228$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")2229.appendTo(element);2230}223122322233function buildTable() {2234var html = buildTableHTML();22352236if (table) {2237table.remove();2238}2239table = $(html).appendTo(element);22402241head = table.find('thead');2242headCells = head.find('.fc-day-header');2243body = table.find('tbody');2244bodyRows = body.find('tr');2245bodyCells = body.find('.fc-day');2246bodyFirstCells = bodyRows.find('td:first-child');22472248firstRowCellInners = bodyRows.eq(0).find('.fc-day > div');2249firstRowCellContentInners = bodyRows.eq(0).find('.fc-day-content > div');22502251markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's2252markFirstLast(bodyRows); // marks first+last td's2253bodyRows.eq(0).addClass('fc-first');2254bodyRows.filter(':last').addClass('fc-last');22552256bodyCells.each(function(i, _cell) {2257var date = cellToDate(2258Math.floor(i / colCnt),2259i % colCnt2260);2261trigger('dayRender', t, date, $(_cell));2262});22632264dayBind(bodyCells);2265}2266226722682269/* HTML Building2270-----------------------------------------------------------*/227122722273function buildTableHTML() {2274var html =2275"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +2276buildHeadHTML() +2277buildBodyHTML() +2278"</table>";22792280return html;2281}228222832284function buildHeadHTML() {2285var headerClass = tm + "-widget-header";2286var html = '';2287var col;2288var date;22892290html += "<thead><tr>";22912292if (showWeekNumbers) {2293html +=2294"<th class='fc-week-number " + headerClass + "'>" +2295htmlEscape(weekNumberTitle) +2296"</th>";2297}22982299for (col=0; col<colCnt; col++) {2300date = cellToDate(0, col);2301html +=2302"<th class='fc-day-header fc-" + dayIDs[date.getDay()] + " " + headerClass + "'>" +2303htmlEscape(formatDate(date, colFormat)) +2304"</th>";2305}23062307html += "</tr></thead>";23082309return html;2310}231123122313function buildBodyHTML() {2314var contentClass = tm + "-widget-content";2315var html = '';2316var row;2317var col;2318var date;23192320html += "<tbody>";23212322for (row=0; row<rowCnt; row++) {23232324html += "<tr class='fc-week'>";23252326if (showWeekNumbers) {2327date = cellToDate(row, 0);2328html +=2329"<td class='fc-week-number " + contentClass + "'>" +2330"<div>" +2331htmlEscape(formatDate(date, weekNumberFormat)) +2332"</div>" +2333"</td>";2334}23352336for (col=0; col<colCnt; col++) {2337date = cellToDate(row, col);2338html += buildCellHTML(date);2339}23402341html += "</tr>";2342}23432344html += "</tbody>";23452346return html;2347}234823492350function buildCellHTML(date) {2351var contentClass = tm + "-widget-content";2352var month = t.start.getMonth();2353var today = clearTime(new Date());2354var html = '';2355var classNames = [2356'fc-day',2357'fc-' + dayIDs[date.getDay()],2358contentClass2359];23602361if (date.getMonth() != month) {2362classNames.push('fc-other-month');2363}2364if (+date == +today) {2365classNames.push(2366'fc-today',2367tm + '-state-highlight'2368);2369}2370else if (date < today) {2371classNames.push('fc-past');2372}2373else {2374classNames.push('fc-future');2375}23762377html +=2378"<td" +2379" class='" + classNames.join(' ') + "'" +2380" data-date='" + formatDate(date, 'yyyy-MM-dd') + "'" +2381">" +2382"<div>";23832384if (showNumbers) {2385html += "<div class='fc-day-number'>" + date.getDate() + "</div>";2386}23872388html +=2389"<div class='fc-day-content'>" +2390"<div style='position:relative'> </div>" +2391"</div>" +2392"</div>" +2393"</td>";23942395return html;2396}2397239823992400/* Dimensions2401-----------------------------------------------------------*/240224032404function setHeight(height) {2405viewHeight = height;24062407var bodyHeight = viewHeight - head.height();2408var rowHeight;2409var rowHeightLast;2410var cell;24112412if (opt('weekMode') == 'variable') {2413rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));2414}else{2415rowHeight = Math.floor(bodyHeight / rowCnt);2416rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);2417}24182419bodyFirstCells.each(function(i, _cell) {2420if (i < rowCnt) {2421cell = $(_cell);2422cell.find('> div').css(2423'min-height',2424(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)2425);2426}2427});24282429}243024312432function setWidth(width) {2433viewWidth = width;2434colPositions.clear();2435colContentPositions.clear();24362437weekNumberWidth = 0;2438if (showWeekNumbers) {2439weekNumberWidth = head.find('th.fc-week-number').outerWidth();2440}24412442colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt);2443setOuterWidth(headCells.slice(0, -1), colWidth);2444}2445244624472448/* Day clicking and binding2449-----------------------------------------------------------*/245024512452function dayBind(days) {2453days.click(dayClick)2454.mousedown(daySelectionMousedown);2455}245624572458function dayClick(ev) {2459if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick2460var date = parseISO8601($(this).data('date'));2461trigger('dayClick', this, date, true, ev);2462}2463}2464246524662467/* Semi-transparent Overlay Helpers2468------------------------------------------------------*/2469// TODO: should be consolidated with AgendaView's methods247024712472function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive24732474if (refreshCoordinateGrid) {2475coordinateGrid.build();2476}24772478var segments = rangeToSegments(overlayStart, overlayEnd);24792480for (var i=0; i<segments.length; i++) {2481var segment = segments[i];2482dayBind(2483renderCellOverlay(2484segment.row,2485segment.leftCol,2486segment.row,2487segment.rightCol2488)2489);2490}2491}249224932494function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive2495var rect = coordinateGrid.rect(row0, col0, row1, col1, element);2496return renderOverlay(rect, element);2497}2498249925002501/* Selection2502-----------------------------------------------------------------------*/250325042505function defaultSelectionEnd(startDate, allDay) {2506return cloneDate(startDate);2507}250825092510function renderSelection(startDate, endDate, allDay) {2511renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???2512}251325142515function clearSelection() {2516clearOverlays();2517}251825192520function reportDayClick(date, allDay, ev) {2521var cell = dateToCell(date);2522var _element = bodyCells[cell.row*colCnt + cell.col];2523trigger('dayClick', _element, date, allDay, ev);2524}2525252625272528/* External Dragging2529-----------------------------------------------------------------------*/253025312532function dragStart(_dragElement, ev, ui) {2533hoverListener.start(function(cell) {2534clearOverlays();2535if (cell) {2536renderCellOverlay(cell.row, cell.col, cell.row, cell.col);2537}2538}, ev);2539}254025412542function dragStop(_dragElement, ev, ui) {2543var cell = hoverListener.stop();2544clearOverlays();2545if (cell) {2546var d = cellToDate(cell);2547trigger('drop', _dragElement, d, true, ev, ui);2548}2549}2550255125522553/* Utilities2554--------------------------------------------------------*/255525562557function defaultEventEnd(event) {2558return cloneDate(event.start);2559}256025612562coordinateGrid = new CoordinateGrid(function(rows, cols) {2563var e, n, p;2564headCells.each(function(i, _e) {2565e = $(_e);2566n = e.offset().left;2567if (i) {2568p[1] = n;2569}2570p = [n];2571cols[i] = p;2572});2573p[1] = n + e.outerWidth();2574bodyRows.each(function(i, _e) {2575if (i < rowCnt) {2576e = $(_e);2577n = e.offset().top;2578if (i) {2579p[1] = n;2580}2581p = [n];2582rows[i] = p;2583}2584});2585p[1] = n + e.outerHeight();2586});258725882589hoverListener = new HoverListener(coordinateGrid);25902591colPositions = new HorizontalPositionCache(function(col) {2592return firstRowCellInners.eq(col);2593});25942595colContentPositions = new HorizontalPositionCache(function(col) {2596return firstRowCellContentInners.eq(col);2597});259825992600function colLeft(col) {2601return colPositions.left(col);2602}260326042605function colRight(col) {2606return colPositions.right(col);2607}260826092610function colContentLeft(col) {2611return colContentPositions.left(col);2612}261326142615function colContentRight(col) {2616return colContentPositions.right(col);2617}261826192620function allDayRow(i) {2621return bodyRows.eq(i);2622}26232624}26252626;;26272628function BasicEventRenderer() {2629var t = this;263026312632// exports2633t.renderEvents = renderEvents;2634t.clearEvents = clearEvents;263526362637// imports2638DayEventRenderer.call(t);263926402641function renderEvents(events, modifiedEventId) {2642t.renderDayEvents(events, modifiedEventId);2643}264426452646function clearEvents() {2647t.getDaySegmentContainer().empty();2648}264926502651// TODO: have this class (and AgendaEventRenderer) be responsible for creating the event container div26522653}26542655;;26562657fcViews.agendaWeek = AgendaWeekView;26582659function AgendaWeekView(element, calendar) {2660var t = this;266126622663// exports2664t.render = render;266526662667// imports2668AgendaView.call(t, element, calendar, 'agendaWeek');2669var opt = t.opt;2670var renderAgenda = t.renderAgenda;2671var skipHiddenDays = t.skipHiddenDays;2672var getCellsPerWeek = t.getCellsPerWeek;2673var formatDates = calendar.formatDates;267426752676function render(date, delta) {26772678if (delta) {2679addDays(date, delta * 7);2680}26812682var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));2683var end = addDays(cloneDate(start), 7);26842685var visStart = cloneDate(start);2686skipHiddenDays(visStart);26872688var visEnd = cloneDate(end);2689skipHiddenDays(visEnd, -1, true);26902691var colCnt = getCellsPerWeek();26922693t.title = formatDates(2694visStart,2695addDays(cloneDate(visEnd), -1),2696opt('titleFormat')2697);26982699t.start = start;2700t.end = end;2701t.visStart = visStart;2702t.visEnd = visEnd;27032704renderAgenda(colCnt);2705}27062707}27082709;;27102711fcViews.agendaDay = AgendaDayView;271227132714function AgendaDayView(element, calendar) {2715var t = this;271627172718// exports2719t.render = render;272027212722// imports2723AgendaView.call(t, element, calendar, 'agendaDay');2724var opt = t.opt;2725var renderAgenda = t.renderAgenda;2726var skipHiddenDays = t.skipHiddenDays;2727var formatDate = calendar.formatDate;272827292730function render(date, delta) {27312732if (delta) {2733addDays(date, delta);2734}2735skipHiddenDays(date, delta < 0 ? -1 : 1);27362737var start = cloneDate(date, true);2738var end = addDays(cloneDate(start), 1);27392740t.title = formatDate(date, opt('titleFormat'));27412742t.start = t.visStart = start;2743t.end = t.visEnd = end;27442745renderAgenda(1);2746}274727482749}27502751;;27522753setDefaults({2754allDaySlot: true,2755allDayText: 'all-day',2756firstHour: 6,2757slotMinutes: 30,2758defaultEventMinutes: 120,2759axisFormat: 'h(:mm)tt',2760timeFormat: {2761agenda: 'h:mm{ - h:mm}'2762},2763dragOpacity: {2764agenda: .52765},2766minTime: 0,2767maxTime: 24,2768slotEventOverlap: true2769});277027712772// TODO: make it work in quirks mode (event corners, all-day height)2773// TODO: test liquid width, especially in IE6277427752776function AgendaView(element, calendar, viewName) {2777var t = this;277827792780// exports2781t.renderAgenda = renderAgenda;2782t.setWidth = setWidth;2783t.setHeight = setHeight;2784t.afterRender = afterRender;2785t.defaultEventEnd = defaultEventEnd;2786t.timePosition = timePosition;2787t.getIsCellAllDay = getIsCellAllDay;2788t.allDayRow = getAllDayRow;2789t.getCoordinateGrid = function() { return coordinateGrid }; // specifically for AgendaEventRenderer2790t.getHoverListener = function() { return hoverListener };2791t.colLeft = colLeft;2792t.colRight = colRight;2793t.colContentLeft = colContentLeft;2794t.colContentRight = colContentRight;2795t.getDaySegmentContainer = function() { return daySegmentContainer };2796t.getSlotSegmentContainer = function() { return slotSegmentContainer };2797t.getMinMinute = function() { return minMinute };2798t.getMaxMinute = function() { return maxMinute };2799t.getSlotContainer = function() { return slotContainer };2800t.getRowCnt = function() { return 1 };2801t.getColCnt = function() { return colCnt };2802t.getColWidth = function() { return colWidth };2803t.getSnapHeight = function() { return snapHeight };2804t.getSnapMinutes = function() { return snapMinutes };2805t.defaultSelectionEnd = defaultSelectionEnd;2806t.renderDayOverlay = renderDayOverlay;2807t.renderSelection = renderSelection;2808t.clearSelection = clearSelection;2809t.reportDayClick = reportDayClick; // selection mousedown hack2810t.dragStart = dragStart;2811t.dragStop = dragStop;281228132814// imports2815View.call(t, element, calendar, viewName);2816OverlayManager.call(t);2817SelectionManager.call(t);2818AgendaEventRenderer.call(t);2819var opt = t.opt;2820var trigger = t.trigger;2821var renderOverlay = t.renderOverlay;2822var clearOverlays = t.clearOverlays;2823var reportSelection = t.reportSelection;2824var unselect = t.unselect;2825var daySelectionMousedown = t.daySelectionMousedown;2826var slotSegHtml = t.slotSegHtml;2827var cellToDate = t.cellToDate;2828var dateToCell = t.dateToCell;2829var rangeToSegments = t.rangeToSegments;2830var formatDate = calendar.formatDate;283128322833// locals28342835var dayTable;2836var dayHead;2837var dayHeadCells;2838var dayBody;2839var dayBodyCells;2840var dayBodyCellInners;2841var dayBodyCellContentInners;2842var dayBodyFirstCell;2843var dayBodyFirstCellStretcher;2844var slotLayer;2845var daySegmentContainer;2846var allDayTable;2847var allDayRow;2848var slotScroller;2849var slotContainer;2850var slotSegmentContainer;2851var slotTable;2852var selectionHelper;28532854var viewWidth;2855var viewHeight;2856var axisWidth;2857var colWidth;2858var gutterWidth;2859var slotHeight; // TODO: what if slotHeight changes? (see issue 650)28602861var snapMinutes;2862var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)2863var snapHeight; // holds the pixel hight of a "selection" slot28642865var colCnt;2866var slotCnt;2867var coordinateGrid;2868var hoverListener;2869var colPositions;2870var colContentPositions;2871var slotTopCache = {};28722873var tm;2874var rtl;2875var minMinute, maxMinute;2876var colFormat;2877var showWeekNumbers;2878var weekNumberTitle;2879var weekNumberFormat;2880288128822883/* Rendering2884-----------------------------------------------------------------------------*/288528862887disableTextSelection(element.addClass('fc-agenda'));288828892890function renderAgenda(c) {2891colCnt = c;2892updateOptions();28932894if (!dayTable) { // first time rendering?2895buildSkeleton(); // builds day table, slot area, events containers2896}2897else {2898buildDayTable(); // rebuilds day table2899}2900}290129022903function updateOptions() {29042905tm = opt('theme') ? 'ui' : 'fc';2906rtl = opt('isRTL')2907minMinute = parseTime(opt('minTime'));2908maxMinute = parseTime(opt('maxTime'));2909colFormat = opt('columnFormat');29102911// week # options. (TODO: bad, logic also in other views)2912showWeekNumbers = opt('weekNumbers');2913weekNumberTitle = opt('weekNumberTitle');2914if (opt('weekNumberCalculation') != 'iso') {2915weekNumberFormat = "w";2916}2917else {2918weekNumberFormat = "W";2919}29202921snapMinutes = opt('snapMinutes') || opt('slotMinutes');2922}2923292429252926/* Build DOM2927-----------------------------------------------------------------------*/292829292930function buildSkeleton() {2931var headerClass = tm + "-widget-header";2932var contentClass = tm + "-widget-content";2933var s;2934var d;2935var i;2936var maxd;2937var minutes;2938var slotNormal = opt('slotMinutes') % 15 == 0;29392940buildDayTable();29412942slotLayer =2943$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")2944.appendTo(element);29452946if (opt('allDaySlot')) {29472948daySegmentContainer =2949$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")2950.appendTo(slotLayer);29512952s =2953"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +2954"<tr>" +2955"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +2956"<td>" +2957"<div class='fc-day-content'><div style='position:relative'/></div>" +2958"</td>" +2959"<th class='" + headerClass + " fc-agenda-gutter'> </th>" +2960"</tr>" +2961"</table>";2962allDayTable = $(s).appendTo(slotLayer);2963allDayRow = allDayTable.find('tr');29642965dayBind(allDayRow.find('td'));29662967slotLayer.append(2968"<div class='fc-agenda-divider " + headerClass + "'>" +2969"<div class='fc-agenda-divider-inner'/>" +2970"</div>"2971);29722973}else{29742975daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()29762977}29782979slotScroller =2980$("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")2981.appendTo(slotLayer);29822983slotContainer =2984$("<div style='position:relative;width:100%;overflow:hidden'/>")2985.appendTo(slotScroller);29862987slotSegmentContainer =2988$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")2989.appendTo(slotContainer);29902991s =2992"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +2993"<tbody>";2994d = zeroDate();2995maxd = addMinutes(cloneDate(d), maxMinute);2996addMinutes(d, minMinute);2997slotCnt = 0;2998for (i=0; d < maxd; i++) {2999minutes = d.getMinutes();3000s +=3001"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +3002"<th class='fc-agenda-axis " + headerClass + "'>" +3003((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') +3004"</th>" +3005"<td class='" + contentClass + "'>" +3006"<div style='position:relative'> </div>" +3007"</td>" +3008"</tr>";3009addMinutes(d, opt('slotMinutes'));3010slotCnt++;3011}3012s +=3013"</tbody>" +3014"</table>";3015slotTable = $(s).appendTo(slotContainer);30163017slotBind(slotTable.find('td'));3018}3019302030213022/* Build Day Table3023-----------------------------------------------------------------------*/302430253026function buildDayTable() {3027var html = buildDayTableHTML();30283029if (dayTable) {3030dayTable.remove();3031}3032dayTable = $(html).appendTo(element);30333034dayHead = dayTable.find('thead');3035dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter3036dayBody = dayTable.find('tbody');3037dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter3038dayBodyCellInners = dayBodyCells.find('> div');3039dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');30403041dayBodyFirstCell = dayBodyCells.eq(0);3042dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);30433044markFirstLast(dayHead.add(dayHead.find('tr')));3045markFirstLast(dayBody.add(dayBody.find('tr')));30463047// TODO: now that we rebuild the cells every time, we should call dayRender3048}304930503051function buildDayTableHTML() {3052var html =3053"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +3054buildDayTableHeadHTML() +3055buildDayTableBodyHTML() +3056"</table>";30573058return html;3059}306030613062function buildDayTableHeadHTML() {3063var headerClass = tm + "-widget-header";3064var date;3065var html = '';3066var weekText;3067var col;30683069html +=3070"<thead>" +3071"<tr>";30723073if (showWeekNumbers) {3074date = cellToDate(0, 0);3075weekText = formatDate(date, weekNumberFormat);3076if (rtl) {3077weekText += weekNumberTitle;3078}3079else {3080weekText = weekNumberTitle + weekText;3081}3082html +=3083"<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +3084htmlEscape(weekText) +3085"</th>";3086}3087else {3088html += "<th class='fc-agenda-axis " + headerClass + "'> </th>";3089}30903091for (col=0; col<colCnt; col++) {3092date = cellToDate(0, col);3093html +=3094"<th class='fc-" + dayIDs[date.getDay()] + " fc-col" + col + ' ' + headerClass + "'>" +3095htmlEscape(formatDate(date, colFormat)) +3096"</th>";3097}30983099html +=3100"<th class='fc-agenda-gutter " + headerClass + "'> </th>" +3101"</tr>" +3102"</thead>";31033104return html;3105}310631073108function buildDayTableBodyHTML() {3109var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called3110var contentClass = tm + "-widget-content";3111var date;3112var today = clearTime(new Date());3113var col;3114var cellsHTML;3115var cellHTML;3116var classNames;3117var html = '';31183119html +=3120"<tbody>" +3121"<tr>" +3122"<th class='fc-agenda-axis " + headerClass + "'> </th>";31233124cellsHTML = '';31253126for (col=0; col<colCnt; col++) {31273128date = cellToDate(0, col);31293130classNames = [3131'fc-col' + col,3132'fc-' + dayIDs[date.getDay()],3133contentClass3134];3135if (+date == +today) {3136classNames.push(3137tm + '-state-highlight',3138'fc-today'3139);3140}3141else if (date < today) {3142classNames.push('fc-past');3143}3144else {3145classNames.push('fc-future');3146}31473148cellHTML =3149"<td class='" + classNames.join(' ') + "'>" +3150"<div>" +3151"<div class='fc-day-content'>" +3152"<div style='position:relative'> </div>" +3153"</div>" +3154"</div>" +3155"</td>";31563157cellsHTML += cellHTML;3158}31593160html += cellsHTML;3161html +=3162"<td class='fc-agenda-gutter " + contentClass + "'> </td>" +3163"</tr>" +3164"</tbody>";31653166return html;3167}316831693170// TODO: data-date on the cells3171317231733174/* Dimensions3175-----------------------------------------------------------------------*/317631773178function setHeight(height) {3179if (height === undefined) {3180height = viewHeight;3181}3182viewHeight = height;3183slotTopCache = {};31843185var headHeight = dayBody.position().top;3186var allDayHeight = slotScroller.position().top; // including divider3187var bodyHeight = Math.min( // total body height, including borders3188height - headHeight, // when scrollbars3189slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border3190);31913192dayBodyFirstCellStretcher3193.height(bodyHeight - vsides(dayBodyFirstCell));31943195slotLayer.css('top', headHeight);31963197slotScroller.height(bodyHeight - allDayHeight - 1);31983199// the stylesheet guarantees that the first row has no border.3200// this allows .height() to work well cross-browser.3201slotHeight = slotTable.find('tr:first').height() + 1; // +1 for bottom border32023203snapRatio = opt('slotMinutes') / snapMinutes;3204snapHeight = slotHeight / snapRatio;3205}320632073208function setWidth(width) {3209viewWidth = width;3210colPositions.clear();3211colContentPositions.clear();32123213var axisFirstCells = dayHead.find('th:first');3214if (allDayTable) {3215axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));3216}3217axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));32183219axisWidth = 0;3220setOuterWidth(3221axisFirstCells3222.width('')3223.each(function(i, _cell) {3224axisWidth = Math.max(axisWidth, $(_cell).outerWidth());3225}),3226axisWidth3227);32283229var gutterCells = dayTable.find('.fc-agenda-gutter');3230if (allDayTable) {3231gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));3232}32333234var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)32353236gutterWidth = slotScroller.width() - slotTableWidth;3237if (gutterWidth) {3238setOuterWidth(gutterCells, gutterWidth);3239gutterCells3240.show()3241.prev()3242.removeClass('fc-last');3243}else{3244gutterCells3245.hide()3246.prev()3247.addClass('fc-last');3248}32493250colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);3251setOuterWidth(dayHeadCells.slice(0, -1), colWidth);3252}3253325432553256/* Scrolling3257-----------------------------------------------------------------------*/325832593260function resetScroll() {3261var d0 = zeroDate();3262var scrollDate = cloneDate(d0);3263scrollDate.setHours(opt('firstHour'));3264var top = timePosition(d0, scrollDate) + 1; // +1 for the border3265function scroll() {3266slotScroller.scrollTop(top);3267}3268scroll();3269setTimeout(scroll, 0); // overrides any previous scroll state made by the browser3270}327132723273function afterRender() { // after the view has been freshly rendered and sized3274resetScroll();3275}3276327732783279/* Slot/Day clicking and binding3280-----------------------------------------------------------------------*/328132823283function dayBind(cells) {3284cells.click(slotClick)3285.mousedown(daySelectionMousedown);3286}328732883289function slotBind(cells) {3290cells.click(slotClick)3291.mousedown(slotSelectionMousedown);3292}329332943295function slotClick(ev) {3296if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick3297var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));3298var date = cellToDate(0, col);3299var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data3300if (rowMatch) {3301var mins = parseInt(rowMatch[1]) * opt('slotMinutes');3302var hours = Math.floor(mins/60);3303date.setHours(hours);3304date.setMinutes(mins%60 + minMinute);3305trigger('dayClick', dayBodyCells[col], date, false, ev);3306}else{3307trigger('dayClick', dayBodyCells[col], date, true, ev);3308}3309}3310}3311331233133314/* Semi-transparent Overlay Helpers3315-----------------------------------------------------*/3316// TODO: should be consolidated with BasicView's methods331733183319function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive33203321if (refreshCoordinateGrid) {3322coordinateGrid.build();3323}33243325var segments = rangeToSegments(overlayStart, overlayEnd);33263327for (var i=0; i<segments.length; i++) {3328var segment = segments[i];3329dayBind(3330renderCellOverlay(3331segment.row,3332segment.leftCol,3333segment.row,3334segment.rightCol3335)3336);3337}3338}333933403341function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?3342var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);3343return renderOverlay(rect, slotLayer);3344}334533463347function renderSlotOverlay(overlayStart, overlayEnd) {3348for (var i=0; i<colCnt; i++) {3349var dayStart = cellToDate(0, i);3350var dayEnd = addDays(cloneDate(dayStart), 1);3351var stretchStart = new Date(Math.max(dayStart, overlayStart));3352var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));3353if (stretchStart < stretchEnd) {3354var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // only use it for horizontal coords3355var top = timePosition(dayStart, stretchStart);3356var bottom = timePosition(dayStart, stretchEnd);3357rect.top = top;3358rect.height = bottom - top;3359slotBind(3360renderOverlay(rect, slotContainer)3361);3362}3363}3364}3365336633673368/* Coordinate Utilities3369-----------------------------------------------------------------------------*/337033713372coordinateGrid = new CoordinateGrid(function(rows, cols) {3373var e, n, p;3374dayHeadCells.each(function(i, _e) {3375e = $(_e);3376n = e.offset().left;3377if (i) {3378p[1] = n;3379}3380p = [n];3381cols[i] = p;3382});3383p[1] = n + e.outerWidth();3384if (opt('allDaySlot')) {3385e = allDayRow;3386n = e.offset().top;3387rows[0] = [n, n+e.outerHeight()];3388}3389var slotTableTop = slotContainer.offset().top;3390var slotScrollerTop = slotScroller.offset().top;3391var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();3392function constrain(n) {3393return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));3394}3395for (var i=0; i<slotCnt*snapRatio; i++) { // adapt slot count to increased/decreased selection slot count3396rows.push([3397constrain(slotTableTop + snapHeight*i),3398constrain(slotTableTop + snapHeight*(i+1))3399]);3400}3401});340234033404hoverListener = new HoverListener(coordinateGrid);34053406colPositions = new HorizontalPositionCache(function(col) {3407return dayBodyCellInners.eq(col);3408});34093410colContentPositions = new HorizontalPositionCache(function(col) {3411return dayBodyCellContentInners.eq(col);3412});341334143415function colLeft(col) {3416return colPositions.left(col);3417}341834193420function colContentLeft(col) {3421return colContentPositions.left(col);3422}342334243425function colRight(col) {3426return colPositions.right(col);3427}342834293430function colContentRight(col) {3431return colContentPositions.right(col);3432}343334343435function getIsCellAllDay(cell) {3436return opt('allDaySlot') && !cell.row;3437}343834393440function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system3441var d = cellToDate(0, cell.col);3442var slotIndex = cell.row;3443if (opt('allDaySlot')) {3444slotIndex--;3445}3446if (slotIndex >= 0) {3447addMinutes(d, minMinute + slotIndex * snapMinutes);3448}3449return d;3450}345134523453// get the Y coordinate of the given time on the given day (both Date objects)3454function timePosition(day, time) { // both date objects. day holds 00:00 of current day3455day = cloneDate(day, true);3456if (time < addMinutes(cloneDate(day), minMinute)) {3457return 0;3458}3459if (time >= addMinutes(cloneDate(day), maxMinute)) {3460return slotTable.height();3461}3462var slotMinutes = opt('slotMinutes'),3463minutes = time.getHours()*60 + time.getMinutes() - minMinute,3464slotI = Math.floor(minutes / slotMinutes),3465slotTop = slotTopCache[slotI];3466if (slotTop === undefined) {3467slotTop = slotTopCache[slotI] =3468slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop;3469// .eq() is faster than ":eq()" selector3470// [0].offsetTop is faster than .position().top (do we really need this optimization?)3471// a better optimization would be to cache all these divs3472}3473return Math.max(0, Math.round(3474slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)3475));3476}347734783479function getAllDayRow(index) {3480return allDayRow;3481}348234833484function defaultEventEnd(event) {3485var start = cloneDate(event.start);3486if (event.allDay) {3487return start;3488}3489return addMinutes(start, opt('defaultEventMinutes'));3490}3491349234933494/* Selection3495---------------------------------------------------------------------------------*/349634973498function defaultSelectionEnd(startDate, allDay) {3499if (allDay) {3500return cloneDate(startDate);3501}3502return addMinutes(cloneDate(startDate), opt('slotMinutes'));3503}350435053506function renderSelection(startDate, endDate, allDay) { // only for all-day3507if (allDay) {3508if (opt('allDaySlot')) {3509renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);3510}3511}else{3512renderSlotSelection(startDate, endDate);3513}3514}351535163517function renderSlotSelection(startDate, endDate) {3518var helperOption = opt('selectHelper');3519coordinateGrid.build();3520if (helperOption) {3521var col = dateToCell(startDate).col;3522if (col >= 0 && col < colCnt) { // only works when times are on same day3523var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords3524var top = timePosition(startDate, startDate);3525var bottom = timePosition(startDate, endDate);3526if (bottom > top) { // protect against selections that are entirely before or after visible range3527rect.top = top;3528rect.height = bottom - top;3529rect.left += 2;3530rect.width -= 5;3531if ($.isFunction(helperOption)) {3532var helperRes = helperOption(startDate, endDate);3533if (helperRes) {3534rect.position = 'absolute';3535selectionHelper = $(helperRes)3536.css(rect)3537.appendTo(slotContainer);3538}3539}else{3540rect.isStart = true; // conside rect a "seg" now3541rect.isEnd = true; //3542selectionHelper = $(slotSegHtml(3543{3544title: '',3545start: startDate,3546end: endDate,3547className: ['fc-select-helper'],3548editable: false3549},3550rect3551));3552selectionHelper.css('opacity', opt('dragOpacity'));3553}3554if (selectionHelper) {3555slotBind(selectionHelper);3556slotContainer.append(selectionHelper);3557setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended3558setOuterHeight(selectionHelper, rect.height, true);3559}3560}3561}3562}else{3563renderSlotOverlay(startDate, endDate);3564}3565}356635673568function clearSelection() {3569clearOverlays();3570if (selectionHelper) {3571selectionHelper.remove();3572selectionHelper = null;3573}3574}357535763577function slotSelectionMousedown(ev) {3578if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button3579unselect(ev);3580var dates;3581hoverListener.start(function(cell, origCell) {3582clearSelection();3583if (cell && cell.col == origCell.col && !getIsCellAllDay(cell)) {3584var d1 = realCellToDate(origCell);3585var d2 = realCellToDate(cell);3586dates = [3587d1,3588addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes3589d2,3590addMinutes(cloneDate(d2), snapMinutes)3591].sort(dateCompare);3592renderSlotSelection(dates[0], dates[3]);3593}else{3594dates = null;3595}3596}, ev);3597$(document).one('mouseup', function(ev) {3598hoverListener.stop();3599if (dates) {3600if (+dates[0] == +dates[1]) {3601reportDayClick(dates[0], false, ev);3602}3603reportSelection(dates[0], dates[3], false, ev);3604}3605});3606}3607}360836093610function reportDayClick(date, allDay, ev) {3611trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev);3612}3613361436153616/* External Dragging3617--------------------------------------------------------------------------------*/361836193620function dragStart(_dragElement, ev, ui) {3621hoverListener.start(function(cell) {3622clearOverlays();3623if (cell) {3624if (getIsCellAllDay(cell)) {3625renderCellOverlay(cell.row, cell.col, cell.row, cell.col);3626}else{3627var d1 = realCellToDate(cell);3628var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));3629renderSlotOverlay(d1, d2);3630}3631}3632}, ev);3633}363436353636function dragStop(_dragElement, ev, ui) {3637var cell = hoverListener.stop();3638clearOverlays();3639if (cell) {3640trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui);3641}3642}364336443645}36463647;;36483649function AgendaEventRenderer() {3650var t = this;365136523653// exports3654t.renderEvents = renderEvents;3655t.clearEvents = clearEvents;3656t.slotSegHtml = slotSegHtml;365736583659// imports3660DayEventRenderer.call(t);3661var opt = t.opt;3662var trigger = t.trigger;3663var isEventDraggable = t.isEventDraggable;3664var isEventResizable = t.isEventResizable;3665var eventEnd = t.eventEnd;3666var eventElementHandlers = t.eventElementHandlers;3667var setHeight = t.setHeight;3668var getDaySegmentContainer = t.getDaySegmentContainer;3669var getSlotSegmentContainer = t.getSlotSegmentContainer;3670var getHoverListener = t.getHoverListener;3671var getMaxMinute = t.getMaxMinute;3672var getMinMinute = t.getMinMinute;3673var timePosition = t.timePosition;3674var getIsCellAllDay = t.getIsCellAllDay;3675var colContentLeft = t.colContentLeft;3676var colContentRight = t.colContentRight;3677var cellToDate = t.cellToDate;3678var getColCnt = t.getColCnt;3679var getColWidth = t.getColWidth;3680var getSnapHeight = t.getSnapHeight;3681var getSnapMinutes = t.getSnapMinutes;3682var getSlotContainer = t.getSlotContainer;3683var reportEventElement = t.reportEventElement;3684var showEvents = t.showEvents;3685var hideEvents = t.hideEvents;3686var eventDrop = t.eventDrop;3687var eventResize = t.eventResize;3688var renderDayOverlay = t.renderDayOverlay;3689var clearOverlays = t.clearOverlays;3690var renderDayEvents = t.renderDayEvents;3691var calendar = t.calendar;3692var formatDate = calendar.formatDate;3693var formatDates = calendar.formatDates;369436953696// overrides3697t.draggableDayEvent = draggableDayEvent;3698369937003701/* Rendering3702----------------------------------------------------------------------------*/370337043705function renderEvents(events, modifiedEventId) {3706var i, len=events.length,3707dayEvents=[],3708slotEvents=[];3709for (i=0; i<len; i++) {3710if (events[i].allDay) {3711dayEvents.push(events[i]);3712}else{3713slotEvents.push(events[i]);3714}3715}37163717if (opt('allDaySlot')) {3718renderDayEvents(dayEvents, modifiedEventId);3719setHeight(); // no params means set to viewHeight3720}37213722renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);3723}372437253726function clearEvents() {3727getDaySegmentContainer().empty();3728getSlotSegmentContainer().empty();3729}373037313732function compileSlotSegs(events) {3733var colCnt = getColCnt(),3734minMinute = getMinMinute(),3735maxMinute = getMaxMinute(),3736d,3737visEventEnds = $.map(events, slotEventEnd),3738i,3739j, seg,3740colSegs,3741segs = [];37423743for (i=0; i<colCnt; i++) {37443745d = cellToDate(0, i);3746addMinutes(d, minMinute);37473748colSegs = sliceSegs(3749events,3750visEventEnds,3751d,3752addMinutes(cloneDate(d), maxMinute-minMinute)3753);37543755colSegs = placeSlotSegs(colSegs); // returns a new order37563757for (j=0; j<colSegs.length; j++) {3758seg = colSegs[j];3759seg.col = i;3760segs.push(seg);3761}3762}37633764return segs;3765}376637673768function sliceSegs(events, visEventEnds, start, end) {3769var segs = [],3770i, len=events.length, event,3771eventStart, eventEnd,3772segStart, segEnd,3773isStart, isEnd;3774for (i=0; i<len; i++) {3775event = events[i];3776eventStart = event.start;3777eventEnd = visEventEnds[i];3778if (eventEnd > start && eventStart < end) {3779if (eventStart < start) {3780segStart = cloneDate(start);3781isStart = false;3782}else{3783segStart = eventStart;3784isStart = true;3785}3786if (eventEnd > end) {3787segEnd = cloneDate(end);3788isEnd = false;3789}else{3790segEnd = eventEnd;3791isEnd = true;3792}3793segs.push({3794event: event,3795start: segStart,3796end: segEnd,3797isStart: isStart,3798isEnd: isEnd3799});3800}3801}3802return segs.sort(compareSlotSegs);3803}380438053806function slotEventEnd(event) {3807if (event.end) {3808return cloneDate(event.end);3809}else{3810return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));3811}3812}381338143815// renders events in the 'time slots' at the bottom3816// TODO: when we refactor this, when user returns `false` eventRender, don't have empty space3817// TODO: refactor will include using pixels to detect collisions instead of dates (handy for seg cmp)38183819function renderSlotSegs(segs, modifiedEventId) {38203821var i, segCnt=segs.length, seg,3822event,3823top,3824bottom,3825columnLeft,3826columnRight,3827columnWidth,3828width,3829left,3830right,3831html = '',3832eventElements,3833eventElement,3834triggerRes,3835titleElement,3836height,3837slotSegmentContainer = getSlotSegmentContainer(),3838isRTL = opt('isRTL');38393840// calculate position/dimensions, create html3841for (i=0; i<segCnt; i++) {3842seg = segs[i];3843event = seg.event;3844top = timePosition(seg.start, seg.start);3845bottom = timePosition(seg.start, seg.end);3846columnLeft = colContentLeft(seg.col);3847columnRight = colContentRight(seg.col);3848columnWidth = columnRight - columnLeft;38493850// shave off space on right near scrollbars (2.5%)3851// TODO: move this to CSS somehow3852columnRight -= columnWidth * .025;3853columnWidth = columnRight - columnLeft;38543855width = columnWidth * (seg.forwardCoord - seg.backwardCoord);38563857if (opt('slotEventOverlap')) {3858// double the width while making sure resize handle is visible3859// (assumed to be 20px wide)3860width = Math.max(3861(width - (20/2)) * 2,3862width // narrow columns will want to make the segment smaller than3863// the natural width. don't allow it3864);3865}38663867if (isRTL) {3868right = columnRight - seg.backwardCoord * columnWidth;3869left = right - width;3870}3871else {3872left = columnLeft + seg.backwardCoord * columnWidth;3873right = left + width;3874}38753876// make sure horizontal coordinates are in bounds3877left = Math.max(left, columnLeft);3878right = Math.min(right, columnRight);3879width = right - left;38803881seg.top = top;3882seg.left = left;3883seg.outerWidth = width;3884seg.outerHeight = bottom - top;3885html += slotSegHtml(event, seg);3886}38873888slotSegmentContainer[0].innerHTML = html; // faster than html()3889eventElements = slotSegmentContainer.children();38903891// retrieve elements, run through eventRender callback, bind event handlers3892for (i=0; i<segCnt; i++) {3893seg = segs[i];3894event = seg.event;3895eventElement = $(eventElements[i]); // faster than eq()3896triggerRes = trigger('eventRender', event, event, eventElement);3897if (triggerRes === false) {3898eventElement.remove();3899}else{3900if (triggerRes && triggerRes !== true) {3901eventElement.remove();3902eventElement = $(triggerRes)3903.css({3904position: 'absolute',3905top: seg.top,3906left: seg.left3907})3908.appendTo(slotSegmentContainer);3909}3910seg.element = eventElement;3911if (event._id === modifiedEventId) {3912bindSlotSeg(event, eventElement, seg);3913}else{3914eventElement[0]._fci = i; // for lazySegBind3915}3916reportEventElement(event, eventElement);3917}3918}39193920lazySegBind(slotSegmentContainer, segs, bindSlotSeg);39213922// record event sides and title positions3923for (i=0; i<segCnt; i++) {3924seg = segs[i];3925if (eventElement = seg.element) {3926seg.vsides = vsides(eventElement, true);3927seg.hsides = hsides(eventElement, true);3928titleElement = eventElement.find('.fc-event-title');3929if (titleElement.length) {3930seg.contentTop = titleElement[0].offsetTop;3931}3932}3933}39343935// set all positions/dimensions at once3936for (i=0; i<segCnt; i++) {3937seg = segs[i];3938if (eventElement = seg.element) {3939eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';3940height = Math.max(0, seg.outerHeight - seg.vsides);3941eventElement[0].style.height = height + 'px';3942event = seg.event;3943if (seg.contentTop !== undefined && height - seg.contentTop < 10) {3944// not enough room for title, put it in the time (TODO: maybe make both display:inline instead)3945eventElement.find('div.fc-event-time')3946.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);3947eventElement.find('div.fc-event-title')3948.remove();3949}3950trigger('eventAfterRender', event, event, eventElement);3951}3952}39533954}395539563957function slotSegHtml(event, seg) {3958var html = "<";3959var url = event.url;3960var skinCss = getSkinCss(event, opt);3961var classes = ['fc-event', 'fc-event-vert'];3962if (isEventDraggable(event)) {3963classes.push('fc-event-draggable');3964}3965if (seg.isStart) {3966classes.push('fc-event-start');3967}3968if (seg.isEnd) {3969classes.push('fc-event-end');3970}3971classes = classes.concat(event.className);3972if (event.source) {3973classes = classes.concat(event.source.className || []);3974}3975if (url) {3976html += "a href='" + htmlEscape(event.url) + "'";3977}else{3978html += "div";3979}3980html +=3981" class='" + classes.join(' ') + "'" +3982" style=" +3983"'" +3984"position:absolute;" +3985"top:" + seg.top + "px;" +3986"left:" + seg.left + "px;" +3987skinCss +3988"'" +3989">" +3990"<div class='fc-event-inner'>" +3991"<div class='fc-event-time'>" +3992htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +3993"</div>" +3994"<div class='fc-event-title'>" +3995htmlEscape(event.title || '') +3996"</div>" +3997"</div>" +3998"<div class='fc-event-bg'></div>";3999if (seg.isEnd && isEventResizable(event)) {4000html +=4001"<div class='ui-resizable-handle ui-resizable-s'>=</div>";4002}4003html +=4004"</" + (url ? "a" : "div") + ">";4005return html;4006}400740084009function bindSlotSeg(event, eventElement, seg) {4010var timeElement = eventElement.find('div.fc-event-time');4011if (isEventDraggable(event)) {4012draggableSlotEvent(event, eventElement, timeElement);4013}4014if (seg.isEnd && isEventResizable(event)) {4015resizableSlotEvent(event, eventElement, timeElement);4016}4017eventElementHandlers(event, eventElement);4018}4019402040214022/* Dragging4023-----------------------------------------------------------------------------------*/402440254026// when event starts out FULL-DAY4027// overrides DayEventRenderer's version because it needs to account for dragging elements4028// to and from the slot area.40294030function draggableDayEvent(event, eventElement, seg) {4031var isStart = seg.isStart;4032var origWidth;4033var revert;4034var allDay = true;4035var dayDelta;4036var hoverListener = getHoverListener();4037var colWidth = getColWidth();4038var snapHeight = getSnapHeight();4039var snapMinutes = getSnapMinutes();4040var minMinute = getMinMinute();4041eventElement.draggable({4042opacity: opt('dragOpacity', 'month'), // use whatever the month view was using4043revertDuration: opt('dragRevertDuration'),4044start: function(ev, ui) {4045trigger('eventDragStart', eventElement, event, ev, ui);4046hideEvents(event, eventElement);4047origWidth = eventElement.width();4048hoverListener.start(function(cell, origCell) {4049clearOverlays();4050if (cell) {4051revert = false;4052var origDate = cellToDate(0, origCell.col);4053var date = cellToDate(0, cell.col);4054dayDelta = dayDiff(date, origDate);4055if (!cell.row) {4056// on full-days4057renderDayOverlay(4058addDays(cloneDate(event.start), dayDelta),4059addDays(exclEndDay(event), dayDelta)4060);4061resetElement();4062}else{4063// mouse is over bottom slots4064if (isStart) {4065if (allDay) {4066// convert event to temporary slot-event4067eventElement.width(colWidth - 10); // don't use entire width4068setOuterHeight(4069eventElement,4070snapHeight * Math.round(4071(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) /4072snapMinutes4073)4074);4075eventElement.draggable('option', 'grid', [colWidth, 1]);4076allDay = false;4077}4078}else{4079revert = true;4080}4081}4082revert = revert || (allDay && !dayDelta);4083}else{4084resetElement();4085revert = true;4086}4087eventElement.draggable('option', 'revert', revert);4088}, ev, 'drag');4089},4090stop: function(ev, ui) {4091hoverListener.stop();4092clearOverlays();4093trigger('eventDragStop', eventElement, event, ev, ui);4094if (revert) {4095// hasn't moved or is out of bounds (draggable has already reverted)4096resetElement();4097eventElement.css('filter', ''); // clear IE opacity side-effects4098showEvents(event, eventElement);4099}else{4100// changed!4101var minuteDelta = 0;4102if (!allDay) {4103minuteDelta = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight)4104* snapMinutes4105+ minMinute4106- (event.start.getHours() * 60 + event.start.getMinutes());4107}4108eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);4109}4110}4111});4112function resetElement() {4113if (!allDay) {4114eventElement4115.width(origWidth)4116.height('')4117.draggable('option', 'grid', null);4118allDay = true;4119}4120}4121}412241234124// when event starts out IN TIMESLOTS41254126function draggableSlotEvent(event, eventElement, timeElement) {4127var coordinateGrid = t.getCoordinateGrid();4128var colCnt = getColCnt();4129var colWidth = getColWidth();4130var snapHeight = getSnapHeight();4131var snapMinutes = getSnapMinutes();41324133// states4134var origPosition; // original position of the element, not the mouse4135var origCell;4136var isInBounds, prevIsInBounds;4137var isAllDay, prevIsAllDay;4138var colDelta, prevColDelta;4139var dayDelta; // derived from colDelta4140var minuteDelta, prevMinuteDelta;41414142eventElement.draggable({4143scroll: false,4144grid: [ colWidth, snapHeight ],4145axis: colCnt==1 ? 'y' : false,4146opacity: opt('dragOpacity'),4147revertDuration: opt('dragRevertDuration'),4148start: function(ev, ui) {41494150trigger('eventDragStart', eventElement, event, ev, ui);4151hideEvents(event, eventElement);41524153coordinateGrid.build();41544155// initialize states4156origPosition = eventElement.position();4157origCell = coordinateGrid.cell(ev.pageX, ev.pageY);4158isInBounds = prevIsInBounds = true;4159isAllDay = prevIsAllDay = getIsCellAllDay(origCell);4160colDelta = prevColDelta = 0;4161dayDelta = 0;4162minuteDelta = prevMinuteDelta = 0;41634164},4165drag: function(ev, ui) {41664167// NOTE: this `cell` value is only useful for determining in-bounds and all-day.4168// Bad for anything else due to the discrepancy between the mouse position and the4169// element position while snapping. (problem revealed in PR #55)4170//4171// PS- the problem exists for draggableDayEvent() when dragging an all-day event to a slot event.4172// We should overhaul the dragging system and stop relying on jQuery UI.4173var cell = coordinateGrid.cell(ev.pageX, ev.pageY);41744175// update states4176isInBounds = !!cell;4177if (isInBounds) {4178isAllDay = getIsCellAllDay(cell);41794180// calculate column delta4181colDelta = Math.round((ui.position.left - origPosition.left) / colWidth);4182if (colDelta != prevColDelta) {4183// calculate the day delta based off of the original clicked column and the column delta4184var origDate = cellToDate(0, origCell.col);4185var col = origCell.col + colDelta;4186col = Math.max(0, col);4187col = Math.min(colCnt-1, col);4188var date = cellToDate(0, col);4189dayDelta = dayDiff(date, origDate);4190}41914192// calculate minute delta (only if over slots)4193if (!isAllDay) {4194minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes;4195}4196}41974198// any state changes?4199if (4200isInBounds != prevIsInBounds ||4201isAllDay != prevIsAllDay ||4202colDelta != prevColDelta ||4203minuteDelta != prevMinuteDelta4204) {42054206updateUI();42074208// update previous states for next time4209prevIsInBounds = isInBounds;4210prevIsAllDay = isAllDay;4211prevColDelta = colDelta;4212prevMinuteDelta = minuteDelta;4213}42144215// if out-of-bounds, revert when done, and vice versa.4216eventElement.draggable('option', 'revert', !isInBounds);42174218},4219stop: function(ev, ui) {42204221clearOverlays();4222trigger('eventDragStop', eventElement, event, ev, ui);42234224if (isInBounds && (isAllDay || dayDelta || minuteDelta)) { // changed!4225eventDrop(this, event, dayDelta, isAllDay ? 0 : minuteDelta, isAllDay, ev, ui);4226}4227else { // either no change or out-of-bounds (draggable has already reverted)42284229// reset states for next time, and for updateUI()4230isInBounds = true;4231isAllDay = false;4232colDelta = 0;4233dayDelta = 0;4234minuteDelta = 0;42354236updateUI();4237eventElement.css('filter', ''); // clear IE opacity side-effects42384239// sometimes fast drags make event revert to wrong position, so reset.4240// also, if we dragged the element out of the area because of snapping,4241// but the *mouse* is still in bounds, we need to reset the position.4242eventElement.css(origPosition);42434244showEvents(event, eventElement);4245}4246}4247});42484249function updateUI() {4250clearOverlays();4251if (isInBounds) {4252if (isAllDay) {4253timeElement.hide();4254eventElement.draggable('option', 'grid', null); // disable grid snapping4255renderDayOverlay(4256addDays(cloneDate(event.start), dayDelta),4257addDays(exclEndDay(event), dayDelta)4258);4259}4260else {4261updateTimeText(minuteDelta);4262timeElement.css('display', ''); // show() was causing display=inline4263eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping4264}4265}4266}42674268function updateTimeText(minuteDelta) {4269var newStart = addMinutes(cloneDate(event.start), minuteDelta);4270var newEnd;4271if (event.end) {4272newEnd = addMinutes(cloneDate(event.end), minuteDelta);4273}4274timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));4275}42764277}4278427942804281/* Resizing4282--------------------------------------------------------------------------------------*/428342844285function resizableSlotEvent(event, eventElement, timeElement) {4286var snapDelta, prevSnapDelta;4287var snapHeight = getSnapHeight();4288var snapMinutes = getSnapMinutes();4289eventElement.resizable({4290handles: {4291s: '.ui-resizable-handle'4292},4293grid: snapHeight,4294start: function(ev, ui) {4295snapDelta = prevSnapDelta = 0;4296hideEvents(event, eventElement);4297trigger('eventResizeStart', this, event, ev, ui);4298},4299resize: function(ev, ui) {4300// don't rely on ui.size.height, doesn't take grid into account4301snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight);4302if (snapDelta != prevSnapDelta) {4303timeElement.text(4304formatDates(4305event.start,4306(!snapDelta && !event.end) ? null : // no change, so don't display time range4307addMinutes(eventEnd(event), snapMinutes*snapDelta),4308opt('timeFormat')4309)4310);4311prevSnapDelta = snapDelta;4312}4313},4314stop: function(ev, ui) {4315trigger('eventResizeStop', this, event, ev, ui);4316if (snapDelta) {4317eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui);4318}else{4319showEvents(event, eventElement);4320// BUG: if event was really short, need to put title back in span4321}4322}4323});4324}432543264327}4328432943304331/* Agenda Event Segment Utilities4332-----------------------------------------------------------------------------*/433343344335// Sets the seg.backwardCoord and seg.forwardCoord on each segment and returns a new4336// list in the order they should be placed into the DOM (an implicit z-index).4337function placeSlotSegs(segs) {4338var levels = buildSlotSegLevels(segs);4339var level0 = levels[0];4340var i;43414342computeForwardSlotSegs(levels);43434344if (level0) {43454346for (i=0; i<level0.length; i++) {4347computeSlotSegPressures(level0[i]);4348}43494350for (i=0; i<level0.length; i++) {4351computeSlotSegCoords(level0[i], 0, 0);4352}4353}43544355return flattenSlotSegLevels(levels);4356}435743584359// Builds an array of segments "levels". The first level will be the leftmost tier of segments4360// if the calendar is left-to-right, or the rightmost if the calendar is right-to-left.4361function buildSlotSegLevels(segs) {4362var levels = [];4363var i, seg;4364var j;43654366for (i=0; i<segs.length; i++) {4367seg = segs[i];43684369// go through all the levels and stop on the first level where there are no collisions4370for (j=0; j<levels.length; j++) {4371if (!computeSlotSegCollisions(seg, levels[j]).length) {4372break;4373}4374}43754376(levels[j] || (levels[j] = [])).push(seg);4377}43784379return levels;4380}438143824383// For every segment, figure out the other segments that are in subsequent4384// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs4385function computeForwardSlotSegs(levels) {4386var i, level;4387var j, seg;4388var k;43894390for (i=0; i<levels.length; i++) {4391level = levels[i];43924393for (j=0; j<level.length; j++) {4394seg = level[j];43954396seg.forwardSegs = [];4397for (k=i+1; k<levels.length; k++) {4398computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);4399}4400}4401}4402}440344044405// Figure out which path forward (via seg.forwardSegs) results in the longest path until4406// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure4407function computeSlotSegPressures(seg) {4408var forwardSegs = seg.forwardSegs;4409var forwardPressure = 0;4410var i, forwardSeg;44114412if (seg.forwardPressure === undefined) { // not already computed44134414for (i=0; i<forwardSegs.length; i++) {4415forwardSeg = forwardSegs[i];44164417// figure out the child's maximum forward path4418computeSlotSegPressures(forwardSeg);44194420// either use the existing maximum, or use the child's forward pressure4421// plus one (for the forwardSeg itself)4422forwardPressure = Math.max(4423forwardPressure,44241 + forwardSeg.forwardPressure4425);4426}44274428seg.forwardPressure = forwardPressure;4429}4430}443144324433// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range4434// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and4435// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.4436//4437// The segment might be part of a "series", which means consecutive segments with the same pressure4438// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of4439// segments behind this one in the current series, and `seriesBackwardCoord` is the starting4440// coordinate of the first segment in the series.4441function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {4442var forwardSegs = seg.forwardSegs;4443var i;44444445if (seg.forwardCoord === undefined) { // not already computed44464447if (!forwardSegs.length) {44484449// if there are no forward segments, this segment should butt up against the edge4450seg.forwardCoord = 1;4451}4452else {44534454// sort highest pressure first4455forwardSegs.sort(compareForwardSlotSegs);44564457// this segment's forwardCoord will be calculated from the backwardCoord of the4458// highest-pressure forward segment.4459computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);4460seg.forwardCoord = forwardSegs[0].backwardCoord;4461}44624463// calculate the backwardCoord from the forwardCoord. consider the series4464seg.backwardCoord = seg.forwardCoord -4465(seg.forwardCoord - seriesBackwardCoord) / // available width for series4466(seriesBackwardPressure + 1); // # of segments in the series44674468// use this segment's coordinates to computed the coordinates of the less-pressurized4469// forward segments4470for (i=0; i<forwardSegs.length; i++) {4471computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);4472}4473}4474}447544764477// Outputs a flat array of segments, from lowest to highest level4478function flattenSlotSegLevels(levels) {4479var segs = [];4480var i, level;4481var j;44824483for (i=0; i<levels.length; i++) {4484level = levels[i];44854486for (j=0; j<level.length; j++) {4487segs.push(level[j]);4488}4489}44904491return segs;4492}449344944495// Find all the segments in `otherSegs` that vertically collide with `seg`.4496// Append into an optionally-supplied `results` array and return.4497function computeSlotSegCollisions(seg, otherSegs, results) {4498results = results || [];44994500for (var i=0; i<otherSegs.length; i++) {4501if (isSlotSegCollision(seg, otherSegs[i])) {4502results.push(otherSegs[i]);4503}4504}45054506return results;4507}450845094510// Do these segments occupy the same vertical space?4511function isSlotSegCollision(seg1, seg2) {4512return seg1.end > seg2.start && seg1.start < seg2.end;4513}451445154516// A cmp function for determining which forward segment to rely on more when computing coordinates.4517function compareForwardSlotSegs(seg1, seg2) {4518// put higher-pressure first4519return seg2.forwardPressure - seg1.forwardPressure ||4520// put segments that are closer to initial edge first (and favor ones with no coords yet)4521(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||4522// do normal sorting...4523compareSlotSegs(seg1, seg2);4524}452545264527// A cmp function for determining which segment should be closer to the initial edge4528// (the left edge on a left-to-right calendar).4529function compareSlotSegs(seg1, seg2) {4530return seg1.start - seg2.start || // earlier start time goes first4531(seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first4532(seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title4533}453445354536;;453745384539function View(element, calendar, viewName) {4540var t = this;454145424543// exports4544t.element = element;4545t.calendar = calendar;4546t.name = viewName;4547t.opt = opt;4548t.trigger = trigger;4549t.isEventDraggable = isEventDraggable;4550t.isEventResizable = isEventResizable;4551t.setEventData = setEventData;4552t.clearEventData = clearEventData;4553t.eventEnd = eventEnd;4554t.reportEventElement = reportEventElement;4555t.triggerEventDestroy = triggerEventDestroy;4556t.eventElementHandlers = eventElementHandlers;4557t.showEvents = showEvents;4558t.hideEvents = hideEvents;4559t.eventDrop = eventDrop;4560t.eventResize = eventResize;4561// t.title4562// t.start, t.end4563// t.visStart, t.visEnd456445654566// imports4567var defaultEventEnd = t.defaultEventEnd;4568var normalizeEvent = calendar.normalizeEvent; // in EventManager4569var reportEventChange = calendar.reportEventChange;457045714572// locals4573var eventsByID = {}; // eventID mapped to array of events (there can be multiple b/c of repeating events)4574var eventElementsByID = {}; // eventID mapped to array of jQuery elements4575var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system4576var options = calendar.options;4577457845794580function opt(name, viewNameOverride) {4581var v = options[name];4582if ($.isPlainObject(v)) {4583return smartProperty(v, viewNameOverride || viewName);4584}4585return v;4586}458745884589function trigger(name, thisObj) {4590return calendar.trigger.apply(4591calendar,4592[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])4593);4594}4595459645974598/* Event Editable Boolean Calculations4599------------------------------------------------------------------------------*/460046014602function isEventDraggable(event) {4603var source = event.source || {};4604return firstDefined(4605event.startEditable,4606source.startEditable,4607opt('eventStartEditable'),4608event.editable,4609source.editable,4610opt('editable')4611)4612&& !opt('disableDragging'); // deprecated4613}461446154616function isEventResizable(event) { // but also need to make sure the seg.isEnd == true4617var source = event.source || {};4618return firstDefined(4619event.durationEditable,4620source.durationEditable,4621opt('eventDurationEditable'),4622event.editable,4623source.editable,4624opt('editable')4625)4626&& !opt('disableResizing'); // deprecated4627}4628462946304631/* Event Data4632------------------------------------------------------------------------------*/463346344635function setEventData(events) { // events are already normalized at this point4636eventsByID = {};4637var i, len=events.length, event;4638for (i=0; i<len; i++) {4639event = events[i];4640if (eventsByID[event._id]) {4641eventsByID[event._id].push(event);4642}else{4643eventsByID[event._id] = [event];4644}4645}4646}464746484649function clearEventData() {4650eventsByID = {};4651eventElementsByID = {};4652eventElementCouples = [];4653}465446554656// returns a Date object for an event's end4657function eventEnd(event) {4658return event.end ? cloneDate(event.end) : defaultEventEnd(event);4659}4660466146624663/* Event Elements4664------------------------------------------------------------------------------*/466546664667// report when view creates an element for an event4668function reportEventElement(event, element) {4669eventElementCouples.push({ event: event, element: element });4670if (eventElementsByID[event._id]) {4671eventElementsByID[event._id].push(element);4672}else{4673eventElementsByID[event._id] = [element];4674}4675}467646774678function triggerEventDestroy() {4679$.each(eventElementCouples, function(i, couple) {4680t.trigger('eventDestroy', couple.event, couple.event, couple.element);4681});4682}468346844685// attaches eventClick, eventMouseover, eventMouseout4686function eventElementHandlers(event, eventElement) {4687eventElement4688.click(function(ev) {4689if (!eventElement.hasClass('ui-draggable-dragging') &&4690!eventElement.hasClass('ui-resizable-resizing')) {4691return trigger('eventClick', this, event, ev);4692}4693})4694.hover(4695function(ev) {4696trigger('eventMouseover', this, event, ev);4697},4698function(ev) {4699trigger('eventMouseout', this, event, ev);4700}4701);4702// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)4703// TODO: same for resizing4704}470547064707function showEvents(event, exceptElement) {4708eachEventElement(event, exceptElement, 'show');4709}471047114712function hideEvents(event, exceptElement) {4713eachEventElement(event, exceptElement, 'hide');4714}471547164717function eachEventElement(event, exceptElement, funcName) {4718// NOTE: there may be multiple events per ID (repeating events)4719// and multiple segments per event4720var elements = eventElementsByID[event._id],4721i, len = elements.length;4722for (i=0; i<len; i++) {4723if (!exceptElement || elements[i][0] != exceptElement[0]) {4724elements[i][funcName]();4725}4726}4727}4728472947304731/* Event Modification Reporting4732---------------------------------------------------------------------------------*/473347344735function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {4736var oldAllDay = event.allDay;4737var eventId = event._id;4738moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);4739trigger(4740'eventDrop',4741e,4742event,4743dayDelta,4744minuteDelta,4745allDay,4746function() {4747// TODO: investigate cases where this inverse technique might not work4748moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);4749reportEventChange(eventId);4750},4751ev,4752ui4753);4754reportEventChange(eventId);4755}475647574758function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {4759var eventId = event._id;4760elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);4761trigger(4762'eventResize',4763e,4764event,4765dayDelta,4766minuteDelta,4767function() {4768// TODO: investigate cases where this inverse technique might not work4769elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);4770reportEventChange(eventId);4771},4772ev,4773ui4774);4775reportEventChange(eventId);4776}4777477847794780/* Event Modification Math4781---------------------------------------------------------------------------------*/478247834784function moveEvents(events, dayDelta, minuteDelta, allDay) {4785minuteDelta = minuteDelta || 0;4786for (var e, len=events.length, i=0; i<len; i++) {4787e = events[i];4788if (allDay !== undefined) {4789e.allDay = allDay;4790}4791addMinutes(addDays(e.start, dayDelta, true), minuteDelta);4792if (e.end) {4793e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);4794}4795normalizeEvent(e, options);4796}4797}479847994800function elongateEvents(events, dayDelta, minuteDelta) {4801minuteDelta = minuteDelta || 0;4802for (var e, len=events.length, i=0; i<len; i++) {4803e = events[i];4804e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);4805normalizeEvent(e, options);4806}4807}4808480948104811// ====================================================================================================4812// Utilities for day "cells"4813// ====================================================================================================4814// The "basic" views are completely made up of day cells.4815// The "agenda" views have day cells at the top "all day" slot.4816// This was the obvious common place to put these utilities, but they should be abstracted out into4817// a more meaningful class (like DayEventRenderer).4818// ====================================================================================================481948204821// For determining how a given "cell" translates into a "date":4822//4823// 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).4824// Keep in mind that column indices are inverted with isRTL. This is taken into account.4825//4826// 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).4827//4828// 3. Convert the "day offset" into a "date" (a JavaScript Date object).4829//4830// The reverse transformation happens when transforming a date into a cell.483148324833// exports4834t.isHiddenDay = isHiddenDay;4835t.skipHiddenDays = skipHiddenDays;4836t.getCellsPerWeek = getCellsPerWeek;4837t.dateToCell = dateToCell;4838t.dateToDayOffset = dateToDayOffset;4839t.dayOffsetToCellOffset = dayOffsetToCellOffset;4840t.cellOffsetToCell = cellOffsetToCell;4841t.cellToDate = cellToDate;4842t.cellToCellOffset = cellToCellOffset;4843t.cellOffsetToDayOffset = cellOffsetToDayOffset;4844t.dayOffsetToDate = dayOffsetToDate;4845t.rangeToSegments = rangeToSegments;484648474848// internals4849var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden4850var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)4851var cellsPerWeek;4852var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week4853var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week4854var isRTL = opt('isRTL');485548564857// initialize important internal variables4858(function() {48594860if (opt('weekends') === false) {4861hiddenDays.push(0, 6); // 0=sunday, 6=saturday4862}48634864// Loop through a hypothetical week and determine which4865// days-of-week are hidden. Record in both hashes (one is the reverse of the other).4866for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {4867dayToCellMap[dayIndex] = cellIndex;4868isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;4869if (!isHiddenDayHash[dayIndex]) {4870cellToDayMap[cellIndex] = dayIndex;4871cellIndex++;4872}4873}48744875cellsPerWeek = cellIndex;4876if (!cellsPerWeek) {4877throw 'invalid hiddenDays'; // all days were hidden? bad.4878}48794880})();488148824883// Is the current day hidden?4884// `day` is a day-of-week index (0-6), or a Date object4885function isHiddenDay(day) {4886if (typeof day == 'object') {4887day = day.getDay();4888}4889return isHiddenDayHash[day];4890}489148924893function getCellsPerWeek() {4894return cellsPerWeek;4895}489648974898// Keep incrementing the current day until it is no longer a hidden day.4899// If the initial value of `date` is not a hidden day, don't do anything.4900// Pass `isExclusive` as `true` if you are dealing with an end date.4901// `inc` defaults to `1` (increment one day forward each time)4902function skipHiddenDays(date, inc, isExclusive) {4903inc = inc || 1;4904while (4905isHiddenDayHash[ ( date.getDay() + (isExclusive ? inc : 0) + 7 ) % 7 ]4906) {4907addDays(date, inc);4908}4909}491049114912//4913// TRANSFORMATIONS: cell -> cell offset -> day offset -> date4914//49154916// cell -> date (combines all transformations)4917// Possible arguments:4918// - row, col4919// - { row:#, col: # }4920function cellToDate() {4921var cellOffset = cellToCellOffset.apply(null, arguments);4922var dayOffset = cellOffsetToDayOffset(cellOffset);4923var date = dayOffsetToDate(dayOffset);4924return date;4925}49264927// cell -> cell offset4928// Possible arguments:4929// - row, col4930// - { row:#, col:# }4931function cellToCellOffset(row, col) {4932var colCnt = t.getColCnt();49334934// rtl variables. wish we could pre-populate these. but where?4935var dis = isRTL ? -1 : 1;4936var dit = isRTL ? colCnt - 1 : 0;49374938if (typeof row == 'object') {4939col = row.col;4940row = row.row;4941}4942var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)49434944return cellOffset;4945}49464947// cell offset -> day offset4948function cellOffsetToDayOffset(cellOffset) {4949var day0 = t.visStart.getDay(); // first date's day of week4950cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week4951return Math.floor(cellOffset / cellsPerWeek) * 7 // # of days from full weeks4952+ cellToDayMap[ // # of days from partial last week4953(cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets4954]4955- day0; // adjustment for beginning-of-week normalization4956}49574958// day offset -> date (JavaScript Date object)4959function dayOffsetToDate(dayOffset) {4960var date = cloneDate(t.visStart);4961addDays(date, dayOffset);4962return date;4963}496449654966//4967// TRANSFORMATIONS: date -> day offset -> cell offset -> cell4968//49694970// date -> cell (combines all transformations)4971function dateToCell(date) {4972var dayOffset = dateToDayOffset(date);4973var cellOffset = dayOffsetToCellOffset(dayOffset);4974var cell = cellOffsetToCell(cellOffset);4975return cell;4976}49774978// date -> day offset4979function dateToDayOffset(date) {4980return dayDiff(date, t.visStart);4981}49824983// day offset -> cell offset4984function dayOffsetToCellOffset(dayOffset) {4985var day0 = t.visStart.getDay(); // first date's day of week4986dayOffset += day0; // normalize dayOffset to beginning-of-week4987return Math.floor(dayOffset / 7) * cellsPerWeek // # of cells from full weeks4988+ dayToCellMap[ // # of cells from partial last week4989(dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets4990]4991- dayToCellMap[day0]; // adjustment for beginning-of-week normalization4992}49934994// cell offset -> cell (object with row & col keys)4995function cellOffsetToCell(cellOffset) {4996var colCnt = t.getColCnt();49974998// rtl variables. wish we could pre-populate these. but where?4999var dis = isRTL ? -1 : 1;5000var dit = isRTL ? colCnt - 1 : 0;50015002var row = Math.floor(cellOffset / colCnt);5003var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)5004return {5005row: row,5006col: col5007};5008}500950105011//5012// Converts a date range into an array of segment objects.5013// "Segments" are horizontal stretches of time, sliced up by row.5014// A segment object has the following properties:5015// - row5016// - cols5017// - isStart5018// - isEnd5019//5020function rangeToSegments(startDate, endDate) {5021var rowCnt = t.getRowCnt();5022var colCnt = t.getColCnt();5023var segments = []; // array of segments to return50245025// day offset for given date range5026var rangeDayOffsetStart = dateToDayOffset(startDate);5027var rangeDayOffsetEnd = dateToDayOffset(endDate); // exclusive50285029// first and last cell offset for the given date range5030// "last" implies inclusivity5031var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);5032var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;50335034// loop through all the rows in the view5035for (var row=0; row<rowCnt; row++) {50365037// first and last cell offset for the row5038var rowCellOffsetFirst = row * colCnt;5039var rowCellOffsetLast = rowCellOffsetFirst + colCnt - 1;50405041// get the segment's cell offsets by constraining the range's cell offsets to the bounds of the row5042var segmentCellOffsetFirst = Math.max(rangeCellOffsetFirst, rowCellOffsetFirst);5043var segmentCellOffsetLast = Math.min(rangeCellOffsetLast, rowCellOffsetLast);50445045// make sure segment's offsets are valid and in view5046if (segmentCellOffsetFirst <= segmentCellOffsetLast) {50475048// translate to cells5049var segmentCellFirst = cellOffsetToCell(segmentCellOffsetFirst);5050var segmentCellLast = cellOffsetToCell(segmentCellOffsetLast);50515052// view might be RTL, so order by leftmost column5053var cols = [ segmentCellFirst.col, segmentCellLast.col ].sort();50545055// Determine if segment's first/last cell is the beginning/end of the date range.5056// We need to compare "day offset" because "cell offsets" are often ambiguous and5057// can translate to multiple days, and an edge case reveals itself when we the5058// range's first cell is hidden (we don't want isStart to be true).5059var isStart = cellOffsetToDayOffset(segmentCellOffsetFirst) == rangeDayOffsetStart;5060var isEnd = cellOffsetToDayOffset(segmentCellOffsetLast) + 1 == rangeDayOffsetEnd; // +1 for comparing exclusively50615062segments.push({5063row: row,5064leftCol: cols[0],5065rightCol: cols[1],5066isStart: isStart,5067isEnd: isEnd5068});5069}5070}50715072return segments;5073}507450755076}50775078;;50795080function DayEventRenderer() {5081var t = this;508250835084// exports5085t.renderDayEvents = renderDayEvents;5086t.draggableDayEvent = draggableDayEvent; // made public so that subclasses can override5087t.resizableDayEvent = resizableDayEvent; // "508850895090// imports5091var opt = t.opt;5092var trigger = t.trigger;5093var isEventDraggable = t.isEventDraggable;5094var isEventResizable = t.isEventResizable;5095var eventEnd = t.eventEnd;5096var reportEventElement = t.reportEventElement;5097var eventElementHandlers = t.eventElementHandlers;5098var showEvents = t.showEvents;5099var hideEvents = t.hideEvents;5100var eventDrop = t.eventDrop;5101var eventResize = t.eventResize;5102var getRowCnt = t.getRowCnt;5103var getColCnt = t.getColCnt;5104var getColWidth = t.getColWidth;5105var allDayRow = t.allDayRow; // TODO: rename5106var colLeft = t.colLeft;5107var colRight = t.colRight;5108var colContentLeft = t.colContentLeft;5109var colContentRight = t.colContentRight;5110var dateToCell = t.dateToCell;5111var getDaySegmentContainer = t.getDaySegmentContainer;5112var formatDates = t.calendar.formatDates;5113var renderDayOverlay = t.renderDayOverlay;5114var clearOverlays = t.clearOverlays;5115var clearSelection = t.clearSelection;5116var getHoverListener = t.getHoverListener;5117var rangeToSegments = t.rangeToSegments;5118var cellToDate = t.cellToDate;5119var cellToCellOffset = t.cellToCellOffset;5120var cellOffsetToDayOffset = t.cellOffsetToDayOffset;5121var dateToDayOffset = t.dateToDayOffset;5122var dayOffsetToCellOffset = t.dayOffsetToCellOffset;512351245125// Render `events` onto the calendar, attach mouse event handlers, and call the `eventAfterRender` callback for each.5126// Mouse event will be lazily applied, except if the event has an ID of `modifiedEventId`.5127// Can only be called when the event container is empty (because it wipes out all innerHTML).5128function renderDayEvents(events, modifiedEventId) {51295130// do the actual rendering. Receive the intermediate "segment" data structures.5131var segments = _renderDayEvents(5132events,5133false, // don't append event elements5134true // set the heights of the rows5135);51365137// report the elements to the View, for general drag/resize utilities5138segmentElementEach(segments, function(segment, element) {5139reportEventElement(segment.event, element);5140});51415142// attach mouse handlers5143attachHandlers(segments, modifiedEventId);51445145// call `eventAfterRender` callback for each event5146segmentElementEach(segments, function(segment, element) {5147trigger('eventAfterRender', segment.event, segment.event, element);5148});5149}515051515152// Render an event on the calendar, but don't report them anywhere, and don't attach mouse handlers.5153// Append this event element to the event container, which might already be populated with events.5154// If an event's segment will have row equal to `adjustRow`, then explicitly set its top coordinate to `adjustTop`.5155// This hack is used to maintain continuity when user is manually resizing an event.5156// Returns an array of DOM elements for the event.5157function renderTempDayEvent(event, adjustRow, adjustTop) {51585159// actually render the event. `true` for appending element to container.5160// Recieve the intermediate "segment" data structures.5161var segments = _renderDayEvents(5162[ event ],5163true, // append event elements5164false // don't set the heights of the rows5165);51665167var elements = [];51685169// Adjust certain elements' top coordinates5170segmentElementEach(segments, function(segment, element) {5171if (segment.row === adjustRow) {5172element.css('top', adjustTop);5173}5174elements.push(element[0]); // accumulate DOM nodes5175});51765177return elements;5178}517951805181// Render events onto the calendar. Only responsible for the VISUAL aspect.5182// Not responsible for attaching handlers or calling callbacks.5183// Set `doAppend` to `true` for rendering elements without clearing the existing container.5184// Set `doRowHeights` to allow setting the height of each row, to compensate for vertical event overflow.5185function _renderDayEvents(events, doAppend, doRowHeights) {51865187// where the DOM nodes will eventually end up5188var finalContainer = getDaySegmentContainer();51895190// the container where the initial HTML will be rendered.5191// If `doAppend`==true, uses a temporary container.5192var renderContainer = doAppend ? $("<div/>") : finalContainer;51935194var segments = buildSegments(events);5195var html;5196var elements;51975198// calculate the desired `left` and `width` properties on each segment object5199calculateHorizontals(segments);52005201// build the HTML string. relies on `left` property5202html = buildHTML(segments);52035204// render the HTML. innerHTML is considerably faster than jQuery's .html()5205renderContainer[0].innerHTML = html;52065207// retrieve the individual elements5208elements = renderContainer.children();52095210// if we were appending, and thus using a temporary container,5211// re-attach elements to the real container.5212if (doAppend) {5213finalContainer.append(elements);5214}52155216// assigns each element to `segment.event`, after filtering them through user callbacks5217resolveElements(segments, elements);52185219// Calculate the left and right padding+margin for each element.5220// We need this for setting each element's desired outer width, because of the W3C box model.5221// It's important we do this in a separate pass from acually setting the width on the DOM elements5222// because alternating reading/writing dimensions causes reflow for every iteration.5223segmentElementEach(segments, function(segment, element) {5224segment.hsides = hsides(element, true); // include margins = `true`5225});52265227// Set the width of each element5228segmentElementEach(segments, function(segment, element) {5229element.width(5230Math.max(0, segment.outerWidth - segment.hsides)5231);5232});52335234// Grab each element's outerHeight (setVerticals uses this).5235// To get an accurate reading, it's important to have each element's width explicitly set already.5236segmentElementEach(segments, function(segment, element) {5237segment.outerHeight = element.outerHeight(true); // include margins = `true`5238});52395240// Set the top coordinate on each element (requires segment.outerHeight)5241setVerticals(segments, doRowHeights);52425243return segments;5244}524552465247// Generate an array of "segments" for all events.5248function buildSegments(events) {5249var segments = [];5250for (var i=0; i<events.length; i++) {5251var eventSegments = buildSegmentsForEvent(events[i]);5252segments.push.apply(segments, eventSegments); // append an array to an array5253}5254return segments;5255}525652575258// Generate an array of segments for a single event.5259// A "segment" is the same data structure that View.rangeToSegments produces,5260// with the addition of the `event` property being set to reference the original event.5261function buildSegmentsForEvent(event) {5262var startDate = event.start;5263var endDate = exclEndDay(event);5264var segments = rangeToSegments(startDate, endDate);5265for (var i=0; i<segments.length; i++) {5266segments[i].event = event;5267}5268return segments;5269}527052715272// Sets the `left` and `outerWidth` property of each segment.5273// These values are the desired dimensions for the eventual DOM elements.5274function calculateHorizontals(segments) {5275var isRTL = opt('isRTL');5276for (var i=0; i<segments.length; i++) {5277var segment = segments[i];52785279// Determine functions used for calulating the elements left/right coordinates,5280// depending on whether the view is RTL or not.5281// NOTE:5282// colLeft/colRight returns the coordinate butting up the edge of the cell.5283// colContentLeft/colContentRight is indented a little bit from the edge.5284var leftFunc = (isRTL ? segment.isEnd : segment.isStart) ? colContentLeft : colLeft;5285var rightFunc = (isRTL ? segment.isStart : segment.isEnd) ? colContentRight : colRight;52865287var left = leftFunc(segment.leftCol);5288var right = rightFunc(segment.rightCol);5289segment.left = left;5290segment.outerWidth = right - left;5291}5292}529352945295// Build a concatenated HTML string for an array of segments5296function buildHTML(segments) {5297var html = '';5298for (var i=0; i<segments.length; i++) {5299html += buildHTMLForSegment(segments[i]);5300}5301return html;5302}530353045305// Build an HTML string for a single segment.5306// Relies on the following properties:5307// - `segment.event` (from `buildSegmentsForEvent`)5308// - `segment.left` (from `calculateHorizontals`)5309function buildHTMLForSegment(segment) {5310var html = '';5311var isRTL = opt('isRTL');5312var event = segment.event;5313var url = event.url;53145315// generate the list of CSS classNames5316var classNames = [ 'fc-event', 'fc-event-hori' ];5317if (isEventDraggable(event)) {5318classNames.push('fc-event-draggable');5319}5320if (segment.isStart) {5321classNames.push('fc-event-start');5322}5323if (segment.isEnd) {5324classNames.push('fc-event-end');5325}5326// use the event's configured classNames5327// guaranteed to be an array via `normalizeEvent`5328classNames = classNames.concat(event.className);5329if (event.source) {5330// use the event's source's classNames, if specified5331classNames = classNames.concat(event.source.className || []);5332}53335334// generate a semicolon delimited CSS string for any of the "skin" properties5335// of the event object (`backgroundColor`, `borderColor` and such)5336var skinCss = getSkinCss(event, opt);53375338if (url) {5339html += "<a href='" + htmlEscape(url) + "'";5340}else{5341html += "<div";5342}5343html +=5344" class='" + classNames.join(' ') + "'" +5345" style=" +5346"'" +5347"position:absolute;" +5348"left:" + segment.left + "px;" +5349skinCss +5350"'" +5351">" +5352"<div class='fc-event-inner'>";5353if (!event.allDay && segment.isStart) {5354html +=5355"<span class='fc-event-time'>" +5356htmlEscape(5357formatDates(event.start, event.end, opt('timeFormat'))5358) +5359"</span>";5360}5361html +=5362"<span class='fc-event-title'>" +5363htmlEscape(event.title || '') +5364"</span>" +5365"</div>";5366if (segment.isEnd && isEventResizable(event)) {5367html +=5368"<div class='ui-resizable-handle ui-resizable-" + (isRTL ? 'w' : 'e') + "'>" +5369" " + // makes hit area a lot better for IE6/75370"</div>";5371}5372html += "</" + (url ? "a" : "div") + ">";53735374// TODO:5375// When these elements are initially rendered, they will be briefly visibile on the screen,5376// even though their widths/heights are not set.5377// SOLUTION: initially set them as visibility:hidden ?53785379return html;5380}538153825383// Associate each segment (an object) with an element (a jQuery object),5384// by setting each `segment.element`.5385// Run each element through the `eventRender` filter, which allows developers to5386// modify an existing element, supply a new one, or cancel rendering.5387function resolveElements(segments, elements) {5388for (var i=0; i<segments.length; i++) {5389var segment = segments[i];5390var event = segment.event;5391var element = elements.eq(i);53925393// call the trigger with the original element5394var triggerRes = trigger('eventRender', event, event, element);53955396if (triggerRes === false) {5397// if `false`, remove the event from the DOM and don't assign it to `segment.event`5398element.remove();5399}5400else {5401if (triggerRes && triggerRes !== true) {5402// the trigger returned a new element, but not `true` (which means keep the existing element)54035404// re-assign the important CSS dimension properties that were already assigned in `buildHTMLForSegment`5405triggerRes = $(triggerRes)5406.css({5407position: 'absolute',5408left: segment.left5409});54105411element.replaceWith(triggerRes);5412element = triggerRes;5413}54145415segment.element = element;5416}5417}5418}5419542054215422/* Top-coordinate Methods5423-------------------------------------------------------------------------------------------------*/542454255426// Sets the "top" CSS property for each element.5427// If `doRowHeights` is `true`, also sets each row's first cell to an explicit height,5428// so that if elements vertically overflow, the cell expands vertically to compensate.5429function setVerticals(segments, doRowHeights) {5430var rowContentHeights = calculateVerticals(segments); // also sets segment.top5431var rowContentElements = getRowContentElements(); // returns 1 inner div per row5432var rowContentTops = [];54335434// Set each row's height by setting height of first inner div5435if (doRowHeights) {5436for (var i=0; i<rowContentElements.length; i++) {5437rowContentElements[i].height(rowContentHeights[i]);5438}5439}54405441// Get each row's top, relative to the views's origin.5442// Important to do this after setting each row's height.5443for (var i=0; i<rowContentElements.length; i++) {5444rowContentTops.push(5445rowContentElements[i].position().top5446);5447}54485449// Set each segment element's CSS "top" property.5450// Each segment object has a "top" property, which is relative to the row's top, but...5451segmentElementEach(segments, function(segment, element) {5452element.css(5453'top',5454rowContentTops[segment.row] + segment.top // ...now, relative to views's origin5455);5456});5457}545854595460// Calculate the "top" coordinate for each segment, relative to the "top" of the row.5461// Also, return an array that contains the "content" height for each row5462// (the height displaced by the vertically stacked events in the row).5463// Requires segments to have their `outerHeight` property already set.5464function calculateVerticals(segments) {5465var rowCnt = getRowCnt();5466var colCnt = getColCnt();5467var rowContentHeights = []; // content height for each row5468var segmentRows = buildSegmentRows(segments); // an array of segment arrays, one for each row54695470for (var rowI=0; rowI<rowCnt; rowI++) {5471var segmentRow = segmentRows[rowI];54725473// an array of running total heights for each column.5474// initialize with all zeros.5475var colHeights = [];5476for (var colI=0; colI<colCnt; colI++) {5477colHeights.push(0);5478}54795480// loop through every segment5481for (var segmentI=0; segmentI<segmentRow.length; segmentI++) {5482var segment = segmentRow[segmentI];54835484// find the segment's top coordinate by looking at the max height5485// of all the columns the segment will be in.5486segment.top = arrayMax(5487colHeights.slice(5488segment.leftCol,5489segment.rightCol + 1 // make exclusive for slice5490)5491);54925493// adjust the columns to account for the segment's height5494for (var colI=segment.leftCol; colI<=segment.rightCol; colI++) {5495colHeights[colI] = segment.top + segment.outerHeight;5496}5497}54985499// the tallest column in the row should be the "content height"5500rowContentHeights.push(arrayMax(colHeights));5501}55025503return rowContentHeights;5504}550555065507// Build an array of segment arrays, each representing the segments that will5508// be in a row of the grid, sorted by which event should be closest to the top.5509function buildSegmentRows(segments) {5510var rowCnt = getRowCnt();5511var segmentRows = [];5512var segmentI;5513var segment;5514var rowI;55155516// group segments by row5517for (segmentI=0; segmentI<segments.length; segmentI++) {5518segment = segments[segmentI];5519rowI = segment.row;5520if (segment.element) { // was rendered?5521if (segmentRows[rowI]) {5522// already other segments. append to array5523segmentRows[rowI].push(segment);5524}5525else {5526// first segment in row. create new array5527segmentRows[rowI] = [ segment ];5528}5529}5530}55315532// sort each row5533for (rowI=0; rowI<rowCnt; rowI++) {5534segmentRows[rowI] = sortSegmentRow(5535segmentRows[rowI] || [] // guarantee an array, even if no segments5536);5537}55385539return segmentRows;5540}554155425543// Sort an array of segments according to which segment should appear closest to the top5544function sortSegmentRow(segments) {5545var sortedSegments = [];55465547// build the subrow array5548var subrows = buildSegmentSubrows(segments);55495550// flatten it5551for (var i=0; i<subrows.length; i++) {5552sortedSegments.push.apply(sortedSegments, subrows[i]); // append an array to an array5553}55545555return sortedSegments;5556}555755585559// Take an array of segments, which are all assumed to be in the same row,5560// and sort into subrows.5561function buildSegmentSubrows(segments) {55625563// Give preference to elements with certain criteria, so they have5564// a chance to be closer to the top.5565segments.sort(compareDaySegments);55665567var subrows = [];5568for (var i=0; i<segments.length; i++) {5569var segment = segments[i];55705571// loop through subrows, starting with the topmost, until the segment5572// doesn't collide with other segments.5573for (var j=0; j<subrows.length; j++) {5574if (!isDaySegmentCollision(segment, subrows[j])) {5575break;5576}5577}5578// `j` now holds the desired subrow index5579if (subrows[j]) {5580subrows[j].push(segment);5581}5582else {5583subrows[j] = [ segment ];5584}5585}55865587return subrows;5588}558955905591// Return an array of jQuery objects for the placeholder content containers of each row.5592// The content containers don't actually contain anything, but their dimensions should match5593// the events that are overlaid on top.5594function getRowContentElements() {5595var i;5596var rowCnt = getRowCnt();5597var rowDivs = [];5598for (i=0; i<rowCnt; i++) {5599rowDivs[i] = allDayRow(i)5600.find('div.fc-day-content > div');5601}5602return rowDivs;5603}5604560556065607/* Mouse Handlers5608---------------------------------------------------------------------------------------------------*/5609// TODO: better documentation!561056115612function attachHandlers(segments, modifiedEventId) {5613var segmentContainer = getDaySegmentContainer();56145615segmentElementEach(segments, function(segment, element, i) {5616var event = segment.event;5617if (event._id === modifiedEventId) {5618bindDaySeg(event, element, segment);5619}else{5620element[0]._fci = i; // for lazySegBind5621}5622});56235624lazySegBind(segmentContainer, segments, bindDaySeg);5625}562656275628function bindDaySeg(event, eventElement, segment) {56295630if (isEventDraggable(event)) {5631t.draggableDayEvent(event, eventElement, segment); // use `t` so subclasses can override5632}56335634if (5635segment.isEnd && // only allow resizing on the final segment for an event5636isEventResizable(event)5637) {5638t.resizableDayEvent(event, eventElement, segment); // use `t` so subclasses can override5639}56405641// attach all other handlers.5642// needs to be after, because resizableDayEvent might stopImmediatePropagation on click5643eventElementHandlers(event, eventElement);5644}564556465647function draggableDayEvent(event, eventElement) {5648var hoverListener = getHoverListener();5649var dayDelta;5650eventElement.draggable({5651delay: 50,5652opacity: opt('dragOpacity'),5653revertDuration: opt('dragRevertDuration'),5654start: function(ev, ui) {5655trigger('eventDragStart', eventElement, event, ev, ui);5656hideEvents(event, eventElement);5657hoverListener.start(function(cell, origCell, rowDelta, colDelta) {5658eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);5659clearOverlays();5660if (cell) {5661var origDate = cellToDate(origCell);5662var date = cellToDate(cell);5663dayDelta = dayDiff(date, origDate);5664renderDayOverlay(5665addDays(cloneDate(event.start), dayDelta),5666addDays(exclEndDay(event), dayDelta)5667);5668}else{5669dayDelta = 0;5670}5671}, ev, 'drag');5672},5673stop: function(ev, ui) {5674hoverListener.stop();5675clearOverlays();5676trigger('eventDragStop', eventElement, event, ev, ui);5677if (dayDelta) {5678eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);5679}else{5680eventElement.css('filter', ''); // clear IE opacity side-effects5681showEvents(event, eventElement);5682}5683}5684});5685}568656875688function resizableDayEvent(event, element, segment) {5689var isRTL = opt('isRTL');5690var direction = isRTL ? 'w' : 'e';5691var handle = element.find('.ui-resizable-' + direction); // TODO: stop using this class because we aren't using jqui for this5692var isResizing = false;56935694// TODO: look into using jquery-ui mouse widget for this stuff5695disableTextSelection(element); // prevent native <a> selection for IE5696element5697.mousedown(function(ev) { // prevent native <a> selection for others5698ev.preventDefault();5699})5700.click(function(ev) {5701if (isResizing) {5702ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)5703ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called5704// (eventElementHandlers needs to be bound after resizableDayEvent)5705}5706});57075708handle.mousedown(function(ev) {5709if (ev.which != 1) {5710return; // needs to be left mouse button5711}5712isResizing = true;5713var hoverListener = getHoverListener();5714var rowCnt = getRowCnt();5715var colCnt = getColCnt();5716var elementTop = element.css('top');5717var dayDelta;5718var helpers;5719var eventCopy = $.extend({}, event);5720var minCellOffset = dayOffsetToCellOffset( dateToDayOffset(event.start) );5721clearSelection();5722$('body')5723.css('cursor', direction + '-resize')5724.one('mouseup', mouseup);5725trigger('eventResizeStart', this, event, ev);5726hoverListener.start(function(cell, origCell) {5727if (cell) {57285729var origCellOffset = cellToCellOffset(origCell);5730var cellOffset = cellToCellOffset(cell);57315732// don't let resizing move earlier than start date cell5733cellOffset = Math.max(cellOffset, minCellOffset);57345735dayDelta =5736cellOffsetToDayOffset(cellOffset) -5737cellOffsetToDayOffset(origCellOffset);57385739if (dayDelta) {5740eventCopy.end = addDays(eventEnd(event), dayDelta, true);5741var oldHelpers = helpers;57425743helpers = renderTempDayEvent(eventCopy, segment.row, elementTop);5744helpers = $(helpers); // turn array into a jQuery object57455746helpers.find('*').css('cursor', direction + '-resize');5747if (oldHelpers) {5748oldHelpers.remove();5749}57505751hideEvents(event);5752}5753else {5754if (helpers) {5755showEvents(event);5756helpers.remove();5757helpers = null;5758}5759}5760clearOverlays();5761renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start()5762event.start,5763addDays( exclEndDay(event), dayDelta )5764// TODO: instead of calling renderDayOverlay() with dates,5765// call _renderDayOverlay (or whatever) with cell offsets.5766);5767}5768}, ev);57695770function mouseup(ev) {5771trigger('eventResizeStop', this, event, ev);5772$('body').css('cursor', '');5773hoverListener.stop();5774clearOverlays();5775if (dayDelta) {5776eventResize(this, event, dayDelta, 0, ev);5777// event redraw will clear helpers5778}5779// otherwise, the drag handler already restored the old events57805781setTimeout(function() { // make this happen after the element's click event5782isResizing = false;5783},0);5784}5785});5786}578757885789}5790579157925793/* Generalized Segment Utilities5794-------------------------------------------------------------------------------------------------*/579557965797function isDaySegmentCollision(segment, otherSegments) {5798for (var i=0; i<otherSegments.length; i++) {5799var otherSegment = otherSegments[i];5800if (5801otherSegment.leftCol <= segment.rightCol &&5802otherSegment.rightCol >= segment.leftCol5803) {5804return true;5805}5806}5807return false;5808}580958105811function segmentElementEach(segments, callback) { // TODO: use in AgendaView?5812for (var i=0; i<segments.length; i++) {5813var segment = segments[i];5814var element = segment.element;5815if (element) {5816callback(segment, element, i);5817}5818}5819}582058215822// A cmp function for determining which segments should appear higher up5823function compareDaySegments(a, b) {5824return (b.rightCol - b.leftCol) - (a.rightCol - a.leftCol) || // put wider events first5825b.event.allDay - a.event.allDay || // if tie, put all-day events first (booleans cast to 0/1)5826a.event.start - b.event.start || // if a tie, sort by event start date5827(a.event.title || '').localeCompare(b.event.title) // if a tie, sort by event title5828}582958305831;;58325833//BUG: unselect needs to be triggered when events are dragged+dropped58345835function SelectionManager() {5836var t = this;583758385839// exports5840t.select = select;5841t.unselect = unselect;5842t.reportSelection = reportSelection;5843t.daySelectionMousedown = daySelectionMousedown;584458455846// imports5847var opt = t.opt;5848var trigger = t.trigger;5849var defaultSelectionEnd = t.defaultSelectionEnd;5850var renderSelection = t.renderSelection;5851var clearSelection = t.clearSelection;585258535854// locals5855var selected = false;5856585758585859// unselectAuto5860if (opt('selectable') && opt('unselectAuto')) {5861$(document).mousedown(function(ev) {5862var ignore = opt('unselectCancel');5863if (ignore) {5864if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match5865return;5866}5867}5868unselect(ev);5869});5870}587158725873function select(startDate, endDate, allDay) {5874unselect();5875if (!endDate) {5876endDate = defaultSelectionEnd(startDate, allDay);5877}5878renderSelection(startDate, endDate, allDay);5879reportSelection(startDate, endDate, allDay);5880}588158825883function unselect(ev) {5884if (selected) {5885selected = false;5886clearSelection();5887trigger('unselect', null, ev);5888}5889}589058915892function reportSelection(startDate, endDate, allDay, ev) {5893selected = true;5894trigger('select', null, startDate, endDate, allDay, ev);5895}589658975898function daySelectionMousedown(ev) { // not really a generic manager method, oh well5899var cellToDate = t.cellToDate;5900var getIsCellAllDay = t.getIsCellAllDay;5901var hoverListener = t.getHoverListener();5902var reportDayClick = t.reportDayClick; // this is hacky and sort of weird5903if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button5904unselect(ev);5905var _mousedownElement = this;5906var dates;5907hoverListener.start(function(cell, origCell) { // TODO: maybe put cellToDate/getIsCellAllDay info in cell5908clearSelection();5909if (cell && getIsCellAllDay(cell)) {5910dates = [ cellToDate(origCell), cellToDate(cell) ].sort(dateCompare);5911renderSelection(dates[0], dates[1], true);5912}else{5913dates = null;5914}5915}, ev);5916$(document).one('mouseup', function(ev) {5917hoverListener.stop();5918if (dates) {5919if (+dates[0] == +dates[1]) {5920reportDayClick(dates[0], true, ev);5921}5922reportSelection(dates[0], dates[1], true, ev);5923}5924});5925}5926}592759285929}59305931;;59325933function OverlayManager() {5934var t = this;593559365937// exports5938t.renderOverlay = renderOverlay;5939t.clearOverlays = clearOverlays;594059415942// locals5943var usedOverlays = [];5944var unusedOverlays = [];594559465947function renderOverlay(rect, parent) {5948var e = unusedOverlays.shift();5949if (!e) {5950e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");5951}5952if (e[0].parentNode != parent[0]) {5953e.appendTo(parent);5954}5955usedOverlays.push(e.css(rect).show());5956return e;5957}595859595960function clearOverlays() {5961var e;5962while (e = usedOverlays.shift()) {5963unusedOverlays.push(e.hide().unbind());5964}5965}596659675968}59695970;;59715972function CoordinateGrid(buildFunc) {59735974var t = this;5975var rows;5976var cols;597759785979t.build = function() {5980rows = [];5981cols = [];5982buildFunc(rows, cols);5983};598459855986t.cell = function(x, y) {5987var rowCnt = rows.length;5988var colCnt = cols.length;5989var i, r=-1, c=-1;5990for (i=0; i<rowCnt; i++) {5991if (y >= rows[i][0] && y < rows[i][1]) {5992r = i;5993break;5994}5995}5996for (i=0; i<colCnt; i++) {5997if (x >= cols[i][0] && x < cols[i][1]) {5998c = i;5999break;6000}6001}6002return (r>=0 && c>=0) ? { row:r, col:c } : null;6003};600460056006t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive6007var origin = originElement.offset();6008return {6009top: rows[row0][0] - origin.top,6010left: cols[col0][0] - origin.left,6011width: cols[col1][1] - cols[col0][0],6012height: rows[row1][1] - rows[row0][0]6013};6014};60156016}60176018;;60196020function HoverListener(coordinateGrid) {602160226023var t = this;6024var bindType;6025var change;6026var firstCell;6027var cell;602860296030t.start = function(_change, ev, _bindType) {6031change = _change;6032firstCell = cell = null;6033coordinateGrid.build();6034mouse(ev);6035bindType = _bindType || 'mousemove';6036$(document).bind(bindType, mouse);6037};603860396040function mouse(ev) {6041_fixUIEvent(ev); // see below6042var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);6043if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {6044if (newCell) {6045if (!firstCell) {6046firstCell = newCell;6047}6048change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);6049}else{6050change(newCell, firstCell);6051}6052cell = newCell;6053}6054}605560566057t.stop = function() {6058$(document).unbind(bindType, mouse);6059return cell;6060};606160626063}6064606560666067// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)6068// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem6069// but keep this in here for 1.8.16 users6070// and maybe remove it down the line60716072function _fixUIEvent(event) { // for issue 11686073if (event.pageX === undefined) {6074event.pageX = event.originalEvent.pageX;6075event.pageY = event.originalEvent.pageY;6076}6077}6078;;60796080function HorizontalPositionCache(getElement) {60816082var t = this,6083elements = {},6084lefts = {},6085rights = {};60866087function e(i) {6088return elements[i] = elements[i] || getElement(i);6089}60906091t.left = function(i) {6092return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];6093};60946095t.right = function(i) {6096return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];6097};60986099t.clear = function() {6100elements = {};6101lefts = {};6102rights = {};6103};61046105}61066107;;61086109})(jQuery);61106111