--- js/fullcalendar-1.5.2.js 2011-08-21 22:07:18.000000000 +0200 +++ js/fullcalendar.js 2011-11-14 23:44:05.000000000 +0100 @@ -29,6 +29,7 @@ right: 'today prev,next' }, weekends: true, + currentTimeIndicator: false, // editing //editable: false, @@ -47,12 +48,16 @@ titleFormat: { month: 'MMMM yyyy', week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}", - day: 'dddd, MMM d, yyyy' + day: 'dddd, MMM d, yyyy', + list: 'MMM d, yyyy', + table: 'MMM d, yyyy' }, columnFormat: { month: 'ddd', week: 'ddd M/d', - day: 'dddd M/d' + day: 'dddd M/d', + list: 'dddd, MMM d, yyyy', + table: 'dddd, MMM d, yyyy' }, timeFormat: { // for event elements '': 'h(:mm)t' // default @@ -73,8 +78,28 @@ today: 'today', month: 'month', week: 'week', - day: 'day' + day: 'day', + list: 'list', + table: 'table' }, + listTexts: { + until: 'until', + past: 'Past events', + today: 'Today', + tomorrow: 'Tomorrow', + thisWeek: 'This week', + nextWeek: 'Next week', + thisMonth: 'This month', + nextMonth: 'Next month', + future: 'Future events', + week: 'W' + }, + + // list/table options + listSections: 'month', // false|'day'|'week'|'month'|'smart' + listRange: 30, // number of days to be displayed + listPage: 7, // number of days to jump when paging + tableCols: ['handle', 'date', 'time', 'title'], // jquery-ui theming theme: false, @@ -424,6 +449,7 @@ setSize(); unselect(); currentView.clearEvents(); + currentView.trigger('viewRender', currentView); currentView.renderEvents(events); currentView.sizeDirty = false; } @@ -500,8 +526,8 @@ } - function refetchEvents() { - fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents + function refetchEvents(source) { + fetchEvents(currentView.visStart, currentView.visEnd, source); // will call reportEvents } @@ -523,6 +549,7 @@ markEventsDirty(); if (elementVisible()) { currentView.clearEvents(); + currentView.trigger('viewRender', currentView); currentView.renderEvents(events, modifiedEventID); currentView.eventsDirty = false; } @@ -632,6 +659,12 @@ if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { options[name] = value; updateSize(); + } else if (name.indexOf('list') == 0 || name == 'tableCols') { + options[name] = value; + currentView.start = null; // force re-render + } + else if (name == 'maxHeight') { + options[name] = value; } } @@ -897,15 +930,16 @@ } - function fetchEvents(start, end) { + function fetchEvents(start, end, src) { rangeStart = start; rangeEnd = end; - cache = []; + cache = typeof src != 'undefined' ? $.grep(cache, function(e) { return !isSourcesEqual(e.source, src); }) : []; var fetchID = ++currentFetchID; var len = sources.length; - pendingSourceCnt = len; + pendingSourceCnt = typeof src == 'undefined' ? len : 1; for (var i=0; i 1; if (rtl = opt('isRTL')) { dis = -1; @@ -3791,8 +3846,11 @@ outerWidth = availWidth / (levelI + forward + 1); }else{ if (forward) { - // moderately wide, aligned left still - outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer = + if (overlapping) { // moderately wide, aligned left still + outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer = + }else{ + outerWidth = outerWidth = availWidth / (forward + 1); + } }else{ // can be entire width, aligned left outerWidth = availWidth; @@ -3803,7 +3861,7 @@ * dis + (rtl ? availWidth - outerWidth : 0); // rtl seg.top = top; seg.left = left; - seg.outerWidth = outerWidth; + seg.outerWidth = outerWidth - (overlapping ? 0 : 1); seg.outerHeight = bottom - top; html += slotSegHtml(event, seg); } @@ -3953,6 +4011,37 @@ } + // draw a horizontal line across the agenda view indicating the current time (#143) + function setTimeIndicator() + { + var container = getBodyContent(); + var timeline = container.children('.fc-timeline'); + if (timeline.length == 0) { // if timeline isn't there, add it + timeline = $('
').addClass('fc-timeline').appendTo(container); + } + + var cur_time = new Date(); + if (t.visStart < cur_time && t.visEnd > cur_time) { + timeline.show(); + } + else { + timeline.hide(); + return; + } + + var secs = (cur_time.getHours() * 60 * 60) + (cur_time.getMinutes() * 60) + cur_time.getSeconds(); + var percents = secs / 86400; // 24 * 60 * 60 = 86400, # of seconds in a day + + timeline.css('top', Math.floor(container.height() * percents - 1) + 'px'); + + if (t.name == 'agendaWeek') { // week view, don't want the timeline to go the whole way across + var daycol = $('.fc-today', t.element); + var left = daycol.position().left + 1; + var width = daycol.width(); + timeline.css({ left: left + 'px', width: width + 'px' }); + } + } + /* Dragging -----------------------------------------------------------------------------------*/ @@ -4262,7 +4351,7 @@ function opt(name, viewNameOverride) { var v = options[name]; - if (typeof v == 'object') { + if (typeof v == 'object' && !v.length) { return smartProperty(v, viewNameOverride || viewName); } return v; @@ -4528,6 +4617,9 @@ var seg; var top; var k; + var overflows; + var overflowLinks; + var maxHeight = opt('maxHeight'); segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html() daySegElementResolve(segs, segmentContainer.children()); daySegElementReport(segs); @@ -4539,26 +4631,69 @@ // set row heights, calculate event tops (in relation to row top) for (rowI=0; rowI maxHeight) { + seg.overflow = true; + } + else { + seg.top = top; + top += seg.outerHeight; + } for (k=seg.startCol; k 1) { + element = $('').addClass('fc-more-link').html('+'+link.count).appendTo(container); + element[0].style.position = 'absolute'; + element[0].style.left = link.seg.left + 'px'; + element[0].style.top = (link.top + rowDiv[0].offsetTop) + 'px'; + triggerRes = trigger('overflowRender', link, { count:link.count, date:link.date }, element); + if (triggerRes === false) + element.remove(); + } + else { + link.seg.top = link.top; + link.seg.overflow = false; + } + } + } + } + + function renderTempDaySegs(segs, adjustRow, adjustTop) { var tempContainer = $("
"); var elements; @@ -4806,6 +4941,8 @@ } seg.outerHeight = element[0].offsetHeight + val; } + else + seg.outerHeight = 0; } } @@ -4842,11 +4979,13 @@ for (i=0; i t.visEnd) + continue; + + // define sections of this event + // create smart sections such as today, tomorrow, this week, next week, next month, ect. + segDate = cloneDate(event.start < t.start && event.end > t.start ? t.start : event.start, true); + dd = dayDiff(segDate, today); + wd = Math.floor(dayDiff(segDate, weekstart) / 7); + md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth(); + + // build section title + if (segmode == 'smart') { + if (dd < 0) { + segHash = opt('listTexts', 'past'); + } else if (dd == 0) { + segHash = opt('listTexts', 'today'); + } else if (dd == 1) { + segHash = opt('listTexts', 'tomorrow'); + } else if (wd == 0) { + segHash = opt('listTexts', 'thisWeek'); + } else if (wd == 1) { + segHash = opt('listTexts', 'nextWeek'); + } else if (md == 0) { + segHash = opt('listTexts', 'thisMonth'); + } else if (md == 1) { + segHash = opt('listTexts', 'nextMonth'); + } else if (md > 1) { + segHash = opt('listTexts', 'future'); + } + } else if (segmode == 'month') { + segHash = formatDate(segDate, 'MMMM yyyy'); + } else if (segmode == 'week') { + segHash = opt('listTexts', 'week') + formatDate(segDate, ' W'); + } else if (segmode == 'day') { + segHash = formatDate(segDate, colFormat); + } else { + segHash = ''; + } + + // start new segment + if (segHash != curSegHash) { + segs[++curSeg] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md }; + curSegHash = segHash; + } + + segs[curSeg].events.push(event); + } + + return segs; + } + + function sortCmp(a, b) { + var sd = a.start.getTime() - b.start.getTime(); + return sd + (sd ? 0 : a.end.getTime() - b.end.getTime()); + } + + function renderSegs(segs, modifiedEventId) { + var tm = opt('theme') ? 'ui' : 'fc'; + var headerClass = tm + "-widget-header"; + var contentClass = tm + "-widget-content"; + var i, j, seg, event, times, s, skinCss, skinCssAttr, classes, segContainer, eventElements; + + for (j=0; j < segs.length; j++) { + seg = segs[j]; + + if (seg.title) { + $('
' + htmlEscape(seg.title) + '
').appendTo(getListContainer()); + } + segContainer = $('
').addClass('fc-list-section ' + contentClass).appendTo(getListContainer()); + s = ''; + + for (i=0; i < seg.events.length; i++) { + event = seg.events[i]; + times = renderEventTime(event, seg); + skinCss = getSkinCss(event, opt); + skinCssAttr = (skinCss ? " style='" + skinCss + "'" : ''); + classes = ['fc-event', 'fc-event-skin', 'fc-event-vert', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className); + if (event.source && event.source.className) { + classes = classes.concat(event.source.className); + } + + s += + "
" + + "
" + + "
" + + "
" + + (times[0] ? '' + times[0] + ' ' : '') + + (times[1] ? '' + times[1] + '' : '') + + "
" + + "
" + + "
" + + "
" + + htmlEscape(event.title) + + "
" + + "
" + + "
" + + "
" + // close inner + "
"; // close outer + } + + segContainer[0].innerHTML = s; + eventElements = segContainer.children(); + + // retrieve elements, run through eventRender callback, bind event handlers + for (i=0; i < seg.events.length; i++) { + event = seg.events[i]; + eventElement = $(eventElements[i]); // faster than eq() + triggerRes = trigger('eventRender', event, event, eventElement); + if (triggerRes === false) { + eventElement.remove(); + } else { + if (triggerRes && triggerRes !== true) { + eventElement.remove(); + eventElement = $(triggerRes).appendTo(segContainer); + } + if (event._id === modifiedEventId) { + eventElementHandlers(event, eventElement, seg); + } else { + eventElement[0]._fci = i; // for lazySegBind + } + reportEventElement(event, eventElement); + } + } + + lazySegBind(segContainer, seg, eventElementHandlers); + } + + markFirstLast(getListContainer()); + } + + // event time/date range to display + function renderEventTime(event, seg) { + var timeFormat = opt('timeFormat'); + var dateFormat = opt('columnFormat'); + var segmode = opt('listSections'); + var duration = event.end.getTime() - event.start.getTime(); + var datestr = '', timestr = ''; + + if (segmode == 'smart') { + if (event.start < seg.start) { + datestr = opt('listTexts', 'until') + ' ' + formatDate(event.end, (event.allDay || event.end.getDate() != seg.start.getDate()) ? dateFormat : timeFormat); + } else if (duration > DAY_MS) { + datestr = formatDates(event.start, event.end, dateFormat + '{ - ' + dateFormat + '}'); + } else if (seg.daydiff == 0) { + datestr = opt('listTexts', 'today'); + } else if (seg.daydiff == 1) { + datestr = opt('listTexts', 'tomorrow'); + } else if (seg.weekdiff == 0 || seg.weekdiff == 1) { + datestr = formatDate(event.start, 'dddd'); + } else if (seg.daydiff > 1 || seg.daydiff < 0) { + datestr = formatDate(event.start, dateFormat); + } + } else if (segmode != 'day') { + datestr = formatDates(event.start, event.end, dateFormat + (duration > DAY_MS ? '{ - ' + dateFormat + '}' : '')); + } + + if (!datestr && event.allDay) { + timestr = opt('allDayText'); + } else if ((duration < DAY_MS || !datestr) && !event.allDay) { + timestr = formatDates(event.start, event.end, timeFormat); + } + + return [datestr, timestr]; + } + + function lazySegBind(container, seg, bindHandlers) { + container.unbind('mouseover').mouseover(function(ev) { + var parent = ev.target, e = parent, i, event; + while (parent != this) { + e = parent; + parent = parent.parentNode; + } + if ((i = e._fci) !== undefined) { + e._fci = undefined; + event = seg.events[i]; + bindHandlers(event, container.children().eq(i), seg); + $(ev.target).trigger(ev); + } + ev.stopPropagation(); + }); + } + +} + + +fcViews.list = ListView; + + +function ListView(element, calendar) { + var t = this; + + // exports + t.render = render; + t.select = dummy; + t.unselect = dummy; + t.getDaySegmentContainer = function(){ return body; }; + + // imports + View.call(t, element, calendar, 'list'); + ListEventRenderer.call(t); + var opt = t.opt; + var trigger = t.trigger; + var clearEvents = t.clearEvents; + var reportEventClear = t.reportEventClear; + var formatDates = calendar.formatDates; + var formatDate = calendar.formatDate; + + // overrides + t.setWidth = setWidth; + t.setHeight = setHeight; + + // locals + var body; + var firstDay; + var nwe; + var tm; + var colFormat; + + + function render(date, delta) { + if (delta) { + addDays(date, opt('listPage') * delta); + } + t.start = t.visStart = cloneDate(date, true); + t.end = addDays(cloneDate(t.start), opt('listPage')); + t.visEnd = addDays(cloneDate(t.start), opt('listRange')); + addMinutes(t.visEnd, -1); // set end to 23:59 + t.title = formatDates(date, t.visEnd, opt('titleFormat')); + + updateOptions(); + + if (!body) { + buildSkeleton(); + } else { + clearEvents(); + } + } + + + function updateOptions() { + firstDay = opt('firstDay'); + nwe = opt('weekends') ? 0 : 1; + tm = opt('theme') ? 'ui' : 'fc'; + colFormat = opt('columnFormat', 'day'); + } + + + function buildSkeleton() { + body = $('
').addClass('fc-list-content').appendTo(element); + } + + function setHeight(height, dateChanged) { + body.css('height', (height-1)+'px').css('overflow', 'auto'); + } + + function setWidth(width) { + // nothing to be done here + } + + function dummy() { + // Stub. + } + +} + + +/* Additional view: table (by bruederli@kolabsys.com) +---------------------------------------------------------------------------------*/ + +function TableEventRenderer() { + var t = this; + + // imports + ListEventRenderer.call(t); + var opt = t.opt; + var sortCmp = t.sortCmp; + var trigger = t.trigger; + var compileSegs = t.compileDaySegs; + var reportEvents = t.reportEvents; + var reportEventClear = t.reportEventClear; + var reportEventElement = t.reportEventElement; + var eventElementHandlers = t.eventElementHandlers; + var renderEventTime = t.renderEventTime; + var showEvents = t.showEvents; + var hideEvents = t.hideEvents; + var getListContainer = t.getDaySegmentContainer; + var lazySegBind = t.lazySegBind; + var calendar = t.calendar; + var formatDate = calendar.formatDate; + var formatDates = calendar.formatDates; + + // exports + t.renderEvents = renderEvents; + t.clearEvents = clearEvents; + + + /* Rendering + --------------------------------------------------------------------*/ + + function clearEvents() { + reportEventClear(); + getListContainer().children('tbody').remove(); + } + + function renderEvents(events, modifiedEventId) { + events.sort(sortCmp); + reportEvents(events); + renderSegs(compileSegs(events), modifiedEventId); + getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections')); + } + + function renderSegs(segs, modifiedEventId) { + var tm = opt('theme') ? 'ui' : 'fc'; + var table = getListContainer(); + var headerClass = tm + "-widget-header"; + var contentClass = tm + "-widget-content"; + var tableCols = opt('tableCols'); + var timecol = $.inArray('time', tableCols) >= 0; + var i, j, seg, event, times, s, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements; + + for (j=0; j < segs.length; j++) { + seg = segs[j]; + + if (seg.title) { + $('' + htmlEscape(seg.title) + '').appendTo(table); + } + segContainer = $('').addClass('fc-list-section ' + contentClass).appendTo(table); + s = ''; + + for (i=0; i < seg.events.length; i++) { + event = seg.events[i]; + times = renderEventTime(event, seg); + skinCss = getSkinCss(event, opt); + skinCssAttr = (skinCss ? " style='" + skinCss + "'" : ''); + skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className); + if (event.source && event.source.className) { + skinClasses = skinClasses.concat(event.source.className); + } + rowClasses = ['fc-event', 'fc-event-row', 'fc-'+dayIDs[event.start.getDay()]]; + if (seg.daydiff == 0) { + rowClasses.push('fc-today'); + } + + s += ""; + for (var col, c=0; c < tableCols.length; c++) { + col = tableCols[c]; + if (col == 'handle') { + s += "" + + "
" + + "" + + "
"; + } else if (col == 'date') { + s += "" + htmlEscape(times[0]) + ""; + } else if (col == 'time') { + if (times[1]) { + s += "" + htmlEscape(times[1]) + ""; + } + } else { + s += "" + (htmlEscape(event[col]) || ' ') + ""; + } + } + s += ""; + + // IE doesn't like innerHTML on tbody elements so we insert every row individually + if (document.all) { + $(s).appendTo(segContainer); + s = ''; + } + } + + if (!document.all) + segContainer[0].innerHTML = s; + + eventElements = segContainer.children(); + + // retrieve elements, run through eventRender callback, bind event handlers + for (i=0; i < seg.events.length; i++) { + event = seg.events[i]; + eventElement = $(eventElements[i]); // faster than eq() + triggerRes = trigger('eventRender', event, event, eventElement); + if (triggerRes === false) { + eventElement.remove(); + } else { + if (triggerRes && triggerRes !== true) { + eventElement.remove(); + eventElement = $(triggerRes).appendTo(segContainer); + } + if (event._id === modifiedEventId) { + eventElementHandlers(event, eventElement, seg); + } else { + eventElement[0]._fci = i; // for lazySegBind + } + reportEventElement(event, eventElement); + } + } + + lazySegBind(segContainer, seg, eventElementHandlers); + markFirstLast(segContainer); + } + + //markFirstLast(table); + } + +} + + +fcViews.table = TableView; + + +function TableView(element, calendar) { + var t = this; + + // exports + t.render = render; + t.select = dummy; + t.unselect = dummy; + t.getDaySegmentContainer = function(){ return table; }; + + // imports + View.call(t, element, calendar, 'table'); + TableEventRenderer.call(t); + var opt = t.opt; + var trigger = t.trigger; + var clearEvents = t.clearEvents; + var reportEventClear = t.reportEventClear; + var formatDates = calendar.formatDates; + var formatDate = calendar.formatDate; + + // overrides + t.setWidth = setWidth; + t.setHeight = setHeight; + + // locals + var div; + var table; + var firstDay; + var nwe; + var tm; + var colFormat; + + + function render(date, delta) { + if (delta) { + addDays(date, opt('listPage') * delta); + } + t.start = t.visStart = cloneDate(date, true); + t.end = addDays(cloneDate(t.start), opt('listPage')); + t.visEnd = addDays(cloneDate(t.start), opt('listRange')); + addMinutes(t.visEnd, -1); // set end to 23:59 + t.title = (t.visEnd.getTime() - t.visStart.getTime() < DAY_MS) ? formatDate(date, opt('titleFormat')) : formatDates(date, t.visEnd, opt('titleFormat')); + + updateOptions(); + + if (!table) { + buildSkeleton(); + } else { + clearEvents(); + } + } + + + function updateOptions() { + firstDay = opt('firstDay'); + nwe = opt('weekends') ? 0 : 1; + tm = opt('theme') ? 'ui' : 'fc'; + colFormat = opt('columnFormat'); + } + + + function buildSkeleton() { + var tableCols = opt('tableCols'); + var s = + "" + + ""; + for (var c=0; c < tableCols.length; c++) { + s += ""; + } + s += "" + + "
"; + div = $('
').addClass('fc-list-content').appendTo(element); + table = $(s).appendTo(div); + } + + function setHeight(height, dateChanged) { + div.css('height', (height-1)+'px').css('overflow', 'auto'); + } + + function setWidth(width) { + // nothing to be done here + } + + function dummy() { + // Stub. + } + +} + + +})(jQuery); \ No newline at end of file