From 44526dbe3d3681cf179a7ceb10adacb794f69b27 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Mon, 21 Sep 2009 04:57:20 +0000 Subject: [PATCH] --- changelog.txt | 7 +- docs/locale.txt | 4 +- docs/methods.txt | 6 +- docs/triggered-actions.txt | 10 +- examples/basic.html | 1 - examples/gcal.html | 1 - examples/json.html | 1 - examples/theme.html | 6 +- examples/views.html | 2 +- src/css/agenda.css | 11 + src/css/grid.css | 12 +- src/css/main.css | 164 ++++-- src/gcal.js | 54 +- src/grid.js | 1123 ++++++++++++++++++------------------ src/main.js | 160 ++--- src/misc/foot.txt | 1 + src/misc/head.txt | 8 +- src/util.js | 646 ++++++++++----------- src/view.js | 102 ++-- tests/locale.html | 2 +- tests/options.html | 2 +- tests/sources.html | 2 +- tests/triggers.html | 52 +- 23 files changed, 1255 insertions(+), 1122 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4f15809..e060eda 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,7 +5,8 @@ version 1.3 (9/15/09) - themable by jQuery UI themes - resizable events (require jQuery UI resizable plugin) - rescoped & rewritten CSS, enhanced default look - - reworked options & API to support multiple views / be consistent with jQuery UI datepicker + - cleaner css & rendering techniques for right-to-left + - reworked options & API to support multiple views / be consistent with jQuery UI - refactoring of entire codebase - broken into different JS & CSS files, assembled w/ build scripts - new test suite for new features, uses firebug-lite @@ -37,10 +38,12 @@ version 1.3 (9/15/09) + eventResize x monthDisplay -> viewDisplay x resize -> windowResize + 'eventDrop' params changed, can revert if ajax cuts out - CalEvent Properties x showTime -> allDay x draggable -> editable 'end' is now INCLUSIVE when allDay=true + 'url' now produces a real tag, more native clicking/tab behavior - Methods: + renderEvent x prevMonth -> prev @@ -54,6 +57,8 @@ version 1.3 (9/15/09) 'formatDates' added to support date-ranges - Google Calendar Options: x draggable -> editable + - Bugfixes + - gcal extension fetched 25 results max, now fetches all version 1.2.1 (6/29/09) - bugfixes diff --git a/docs/locale.txt b/docs/locale.txt index 6ab0e7c..45f070d 100755 --- a/docs/locale.txt +++ b/docs/locale.txt @@ -27,8 +27,8 @@ Locale Options Text that will be displayed on buttons of the header. Default:: { - prev: '◄', // left triangle - next: '►', // right triangle + prev: ' ◄ ', // left triangle + next: ' ► ', // right triangle today: 'today', month: 'month', week: 'week', diff --git a/docs/methods.txt b/docs/methods.txt index 61b7de8..fb58f54 100755 --- a/docs/methods.txt +++ b/docs/methods.txt @@ -19,12 +19,12 @@ jQuery object: ``month`` is 0-based, meaning January=0, February=1, etc. -**moveDate** - .fullCalendar('moveDate', *years, [months, [days]]*) +**incrementDate** - .fullCalendar('incrementDate', *years, [months, [days]]*) Moves the calendar forward/backward an arbitrary amount of time. **updateEvent** - .fullCalendar('updateEvent', *calEvent*) - Reports changes about a modified :ref:`CalEvent `. This will cause the event - to be rerendered on the calendar. + Reports changes to a :ref:`CalEvent's ` standard properties. + This will cause the event to be rerendered on the calendar. If there are repeating events on the calendar with the same ID, these events will be changed as well. diff --git a/docs/triggered-actions.txt b/docs/triggered-actions.txt index 87434d5..be5433a 100755 --- a/docs/triggered-actions.txt +++ b/docs/triggered-actions.txt @@ -57,7 +57,7 @@ always available (:ref:`more below `). ``this`` is set to the event's element -**eventDrop**: function(*calEvent, dayDelta, minuteDelta, jsEvent, ui, view*) +**eventDrop**: function(*calEvent, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view*) Triggered when dragging stops and the event has moved to a *different* day. ``dayDelta`` holds the number of days the event was moved forward (a positive number) @@ -69,6 +69,9 @@ always available (:ref:`more below `). ``dayDelta`` and ``minuteDelta`` are elegant for dealing with multi-day and repeating events. If updating a remote database, just add these values to the start and end times of all events with the given ``calEvent.id`` + + ``revertFunc`` is a function that, if called, reverts the event's start/end date to + the values *before* the drag. This is useful if an ajax call should fail. **eventResizeStart**, **eventResizeStop**: function(*calEvent, jsEvent, ui, view*) Triggered before/after an event is resized (but not necessarily changed). @@ -78,7 +81,7 @@ always available (:ref:`more below `). ``this`` is set to the event's element -**eventResize**: function(*calEvent, dayDelta, minuteDelta, jsEvent, ui, view*) +**eventResize**: function(*calEvent, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view*) Triggered when an event is resized and *changed in duration*. ``dayDelta`` holds the number of days the event's end time was moved @@ -86,6 +89,9 @@ always available (:ref:`more below `). ``minuteDelta`` will always be ``0`` and is reserved for a future release of FullCalendar where there will be an agenda view. + + ``revertFunc`` is a function that, if called, reverts the event's start/end date to + the values *before* the drag. This is useful if an ajax call should fail. .. _view-object: diff --git a/examples/basic.html b/examples/basic.html index 8313ec8..f1bdab6 100644 --- a/examples/basic.html +++ b/examples/basic.html @@ -4,7 +4,6 @@ - diff --git a/examples/gcal.html b/examples/gcal.html index a561fd2..89c98bc 100644 --- a/examples/gcal.html +++ b/examples/gcal.html @@ -4,7 +4,6 @@ - diff --git a/examples/json.html b/examples/json.html index 945e178..973e10e 100644 --- a/examples/json.html +++ b/examples/json.html @@ -4,7 +4,6 @@ - diff --git a/examples/theme.html b/examples/theme.html index 72abfb4..cdd8a5f 100755 --- a/examples/theme.html +++ b/examples/theme.html @@ -5,7 +5,6 @@ - @@ -36,6 +35,11 @@ $('#calendar').fullCalendar({ theme: true, editable: true, + header: { + left: 'prev,next today', + center: 'title', + right: 'month,basicWeek,basicDay' + }, events: [ { id: 1, diff --git a/examples/views.html b/examples/views.html index b6818cc..3358cf2 100755 --- a/examples/views.html +++ b/examples/views.html @@ -4,7 +4,6 @@ - @@ -86,6 +85,7 @@ text-align: center; font-size: 14px; font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif; + text-align: left; } #calendar { diff --git a/src/css/agenda.css b/src/css/agenda.css index 39fea8e..dfe8b65 100755 --- a/src/css/agenda.css +++ b/src/css/agenda.css @@ -1,4 +1,15 @@ +.fc-event, +.fc-event a, +.fc-agenda .fc-event-time { + color: #fff; + border-style: solid; + border-color: blue; + background-color: blue; + } + + + /* header styles */ diff --git a/src/css/grid.css b/src/css/grid.css index ed2d5c9..d10a3d0 100755 --- a/src/css/grid.css +++ b/src/css/grid.css @@ -15,8 +15,8 @@ border-width: 1px 0 0 1px; } -.fc-grid th.fc-left, -.fc-grid td.fc-left { +.fc-grid th.fc-leftmost, +.fc-grid td.fc-leftmost { border-left: 0; } @@ -27,7 +27,10 @@ .fc-grid .fc-other-month .fc-day-number { opacity: 0.3; - filter: alpha(opacity=30); + filter: alpha(opacity=30); /* for IE */ + /* opacity with small font can sometimes look too faded + might want to set the 'color' property instead + making day-numbers bold also fixes the problem */ } .fc-grid .fc-day-content { @@ -53,4 +56,5 @@ .fc-rtl .fc-grid .fc-event-time { float: right; - } \ No newline at end of file + } + \ No newline at end of file diff --git a/src/css/main.css b/src/css/main.css index 08115f1..a7e9a72 100755 --- a/src/css/main.css +++ b/src/css/main.css @@ -1,3 +1,12 @@ +/* + * FullCalendar Stylesheet + * + * Feel free to edit this file to customize the look of FullCalendar. + * When upgrading to newer versions, please upgrade this file as well, + * porting over any customizations afterwards. + * + */ + .fc, .fc .fc-header, @@ -62,26 +71,61 @@ table.fc-header { .fc-rtl .fc-header-title { direction: rtl; } - -/* button rounded corners */ + + + +/* Buttons +------------------------------------------------------------------------*/ + +.fc-header .fc-state-default, +.fc-header .ui-state-default { + margin-bottom: 1em; + cursor: pointer; + } .fc-header .fc-state-default { border-width: 1px 0; padding: 0 1px; } +.fc-header .fc-state-default, +.fc-header .fc-state-default a { + border-style: solid; + } + .fc-header .fc-state-default a { display: block; position: relative; - margin: 0 -1px; border-width: 0 1px; + margin: 0 -1px; width: 100%; + text-decoration: none; } .fc-header .fc-state-default span { display: block; + border-style: solid; + border-width: 1px 0 1px 1px; + padding: 3px 5px; } +.fc-header .ui-state-default { + padding: 4px 6px; + } + +/* for adjacent buttons */ + +.fc-header .fc-no-right { + padding-right: 0; + border-right: 0; + } + +.fc-header .ui-no-right { + border-right: 0; + } + +/* for fake rounded corners */ + .fc-header .fc-corner-left { margin-left: 1px; padding-left: 0; @@ -92,73 +136,45 @@ table.fc-header { padding-right: 0; } -.fc-header .fc-no-left { - padding-left: 0; - } - -.fc-header .ui-no-left { - border-left: 0; - } - -/* default button state */ - -.fc-header .fc-state-default, -.fc-header .ui-state-default { - margin-bottom: 10px; - cursor: pointer; - } +/* DEFAULT button COLORS */ .fc-header .fc-state-default, .fc-header .fc-state-default a { - border-style: solid; - border-color: #6E6E6E; + border-color: #777; /* outer border */ color: #333; } .fc-header .fc-state-default span { - border-width: 1px 0 0 1px; - border-style: solid; - border-color: #fff; - background: #F0F0F0; + border-color: #fff #fff #cecece; /* inner border */ + background: #e8e8e8; } -.fc-header .fc-state-default span, -.fc-header .ui-state-default { - padding: 4px 6px; - } - -/* active button state */ +/* PRESSED button COLORS (down and active) */ .fc-header .fc-state-active a { color: #fff; } +.fc-header .fc-state-down span, .fc-header .fc-state-active span { - background: #787878; - border-color: #777; + background: #888; + border-color: #808080 #808080 #909090; /* inner border */ } -/* down button state */ - -.fc-header .fc-state-down span { - background: #787878; - border-color: #777; - } - -/* disabled button state */ - -.fc-header .fc-state-disabled, -.fc-header .fc-state-disabled a { - border-color: #ccc; - } +/* DISABLED button COLORS */ .fc-header .fc-state-disabled a { color: #999; } +.fc-header .fc-state-disabled, +.fc-header .fc-state-disabled a { + border-color: #ccc; /* outer border */ + } + .fc-header .fc-state-disabled span { - border-color: #fff; - background: #F0F0F0; + border-color: #fff #fff #f0f0f0; /* inner border */ + background: #f0f0f0; } @@ -180,15 +196,15 @@ table.fc-header { } .fc-content .fc-state-highlight { /* today */ - background: #FFFFCC; + background: #ffc; } -.fc-content td.fc-not-today { +.fc-content .fc-not-today { background: none; } .fc-cell-overlay { /* semi-transparent rectangle while dragging */ - background: #ADDBFF; + background: #9cf; opacity: .2; filter: alpha(opacity=20); /* for IE */ } @@ -204,40 +220,63 @@ table.fc-header { ------------------------------------------------------------------------*/ .fc-event, -.fc-event a, -.fc-agenda .fc-event-time { +.fc-event a { color: #fff; border-style: solid; - border-color: blue; - background-color: blue; + border-color: #36c; /* DEFAULT EVENT COLOR */ + background-color: #36c; /* */ } + /* Use the 'className' CalEvent property and the following + * example CSS to change event color on a per-event basis: + * + * .my-event-class, + * .my-event-class a { + * border-color: red; + * background-color: red; + * } + */ + .fc-event a { overflow: hidden; - font-size: 11px; + font-size: .85em; text-decoration: none; text-align: left; cursor: pointer; } +.fc-event-editable { + cursor: pointer; + } + .fc-event-time, .fc-event-title { padding: 0 1px; } + +/* for fake rounded corners */ -.fc-event a { /* prep for rounded corners */ +.fc-event a { display: block; position: relative; width: 100%; height: 100%; } +/* right-to-left */ + +.fc-rtl .fc-event a { + text-align: right; + } + /* resizable */ .fc .ui-resizable-handle { display: block; position: absolute; z-index: 99999; + border: 0 !important; /* important overrides pre jquery ui 1.7 styles */ + background: url(data:image/gif;base64,AAAA) !important; /* hover fix for IE */ } @@ -254,6 +293,8 @@ table.fc-header { border-width: 0; } +/* for fake rounded corners */ + .fc-content .fc-corner-left { margin-left: 1px; } @@ -279,22 +320,19 @@ table.fc-header { } .fc-event-hori .ui-resizable-e { - top: 0 !important; - right: -5px !important; + top: 0 !important; /* importants override pre jquery ui 1.7 styles */ + right: -3px !important; width: 7px !important; height: 100% !important; - border: 0 !important; - background: none !important; cursor: e-resize; } .fc-event-hori .ui-resizable-w { top: 0 !important; - left: -5px !important; + left: -3px !important; width: 7px !important; height: 100% !important; - border: 0 !important; - background: none !important; cursor: w-resize; } + \ No newline at end of file diff --git a/src/gcal.js b/src/gcal.js index 0832d8c..66e3e22 100755 --- a/src/gcal.js +++ b/src/gcal.js @@ -1,3 +1,13 @@ +/* + * FullCalendar Google Calendar Extension + * + * Copyright (c) 2009 Adam Shaw + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + (function($) { $.fullCalendar.gcalFeed = function(feedUrl, options) { @@ -11,52 +21,44 @@ 'start-min': $.fullCalendar.formatDate(start, 'u'), 'start-max': $.fullCalendar.formatDate(end, 'u'), 'singleevents': true, - 'max-results': 1000 + 'max-results': 9999 }, function(data) { var events = []; - if (data.feed.entry) + if (data.feed.entry) { $.each(data.feed.entry, function(i, entry) { - var url; - $.each(entry['link'], function(j, link) { - if (link.type == 'text/html') { - url = link.href; + var startStr = entry['gd$when'][0]['startTime'], + start = $.fullCalendar.parseDate(startStr), + end = $.fullCalendar.parseDate(entry['gd$when'][0]['endTime']), + allDay = startStr.indexOf('T') == -1, + classNames = [], + url; + $.each(entry.link, function() { + if (this.type == 'text/html') { + url = this.href; } }); - var startStr = entry['gd$when'][0]['startTime']; - var start = $.fullCalendar.parseDate(startStr); - var end = $.fullCalendar.parseDate(entry['gd$when'][0]['endTime']); - var allDay = startStr.indexOf('T') == -1; - var classNames = []; if (allDay) { - end = new Date(end - 1); // make in inclusive - }else{ - classNames.push('fc-event-nobg'); - } - if (options.className) { - if (typeof options.className == 'string') { - classNames.push(options.className); - }else{ - classNames = classNames.concat(options.className); - } + end = new Date(end - 1); // make inclusive } events.push({ id: entry['gCal$uid']['value'], - url: url, title: entry['title']['$t'], + url: url, start: $.fullCalendar.parseDate(entry['gd$when'][0]['startTime']), end: end, + allDay: allDay, location: entry['gd$where'][0]['valueString'], description: entry['content']['$t'], - allDay: allDay, - className: classNames, + className: options.className, editable: options.editable || false }); }); + } callback(events); }); } - + } -})(jQuery); +})(jQuery); \ No newline at end of file diff --git a/src/grid.js b/src/grid.js index c0f3e1f..4bb9d5e 100755 --- a/src/grid.js +++ b/src/grid.js @@ -1,557 +1,566 @@ - -setDefaults({ - weekMode: 'fixed' -}); - -views.month = function(element, options) { - return new Grid(element, options, { - render: function(date, delta, fetchEvents) { - if (delta) { - addMonths(date, delta); - } - var start = this.start = cloneDate(date, true); - start.setDate(1); - this.title = formatDates( - start, - addDays(cloneDate(this.end = addMonths(cloneDate(start), 1)), -1), - strProp(options.titleFormat, 'month'), - options - ); - addDays(this.visStart = cloneDate(start), -((start.getDay() - options.weekStart + 7) % 7)); - addDays(this.visEnd = cloneDate(this.end), (7 - this.visEnd.getDay() + options.weekStart) % 7); - var rowCnt = Math.round((this.visEnd - this.visStart) / (DAY_MS * 7)); - if (options.weekMode == 'fixed') { - addDays(this.visEnd, (6 - rowCnt) * 7); - rowCnt = 6; - } - this.renderGrid(rowCnt, 7, strProp(options.columnFormat, 'month'), true, fetchEvents); - } - }); -} - -views.basicWeek = function(element, options) { - return new Grid(element, options, { - render: function(date, delta, fetchEvents) { - if (delta) { - addDays(date, delta * 7); - } - this.title = formatDates( - this.start = this.visStart = addDays(cloneDate(date), -((date.getDay() - options.weekStart + 7) % 7)), - addDays(cloneDate(this.end = this.visEnd = addDays(cloneDate(this.start), 7)), -1), - strProp(options.titleFormat, 'week'), - options - ); - this.renderGrid(1, 7, strProp(options.columnFormat, 'week'), false, fetchEvents); - } - }); -}; - -views.basicDay = function(element, options) { - return new Grid(element, options, { - render: function(date, delta, fetchEvents) { - if (delta) { - addDays(date, delta); - } - this.title = formatDate(date, strProp(options.titleFormat, 'day'), options); - this.start = this.visStart = cloneDate(date, true); - this.end = this.visEnd = addDays(cloneDate(this.start), 1); - this.renderGrid(1, 1, strProp(options.columnFormat, 'day'), false, fetchEvents); - } - }); -} - - -// flags for [Opera] rendering bugs - -var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true; - -var tdHeightBug; - -var sniffedEventLeftBug, eventLeftDiff=0; - - -function Grid(element, options, methods) { - - var tm, weekStart, - rtl, dis, dit, // day index sign / translate - rowCnt, colCnt, - colWidth, - thead, tbody, - cachedSegs, //... - - // initialize superclass - view = $.extend(this, viewMethods, methods, { - renderGrid: renderGrid, - renderEvents: renderEvents, - rerenderEvents: rerenderEvents, - updateSize: updateSize, - defaultEventEnd: function(event) { - return cloneDate(event.start); - }, - visEventEnd: function(event) { - if (event.end) { - var end = cloneDate(event.end); - return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end; - }else{ - return addDays(cloneDate(event.start), 1); - } - } - }); - view.init(element, options); - - - - /* Grid Rendering - -----------------------------------------------------------------------------*/ - - - element.addClass('fc-grid').css('position', 'relative'); - if (element.disableSelection) { - element.disableSelection(); - } - - function renderGrid(r, c, colFormat, showNumbers, fetchEvents) { - rowCnt = r; - colCnt = c; - - var month = view.start.getMonth(), - today = clearTime(new Date()), - s, i, j, d = cloneDate(view.visStart); - - // update option-derived variables - tm = options.theme ? 'ui' : 'fc'; - weekStart = options.weekStart; - if (rtl = options.isRTL) { - dis = -1; - dit = colCnt - 1; - }else{ - dis = 1; - dit = 0; - } - - if (!tbody) { // first time, build all cells from scratch - - var table = $("").appendTo(element); - - s = ""; - for (i=0; i" + formatDate(d, colFormat, options) + ""; - addDays(d, 1); - } - thead = $(s + "").appendTo(table); - - s = ""; - d = cloneDate(view.visStart); - for (i=0; i"; - for (j=0; j1 && d.getMonth() != month ? ' fc-other-month' : '') + - (+d == +today ? - ' fc-today '+tm+'-state-highlight' : - ' fc-not-today') + "'>" + - (showNumbers ? "
" + d.getDate() + "
" : '') + - "
 
"; - addDays(d, 1); - } - s += ""; - } - tbody = $(s + "
").appendTo(table); - tbody.find('td').click(dayClick); - - }else{ // NOT first time, reuse as many cells as possible - - view.clearEvents(); - - var prevRowCnt = tbody.find('tr').length; - if (rowCnt < prevRowCnt) { - tbody.find('tr:gt(' + (rowCnt-1) + ')').remove(); // remove extra rows - } - else if (rowCnt > prevRowCnt) { // needs to create new rows... - s = ''; - for (i=prevRowCnt; i"; - for (j=0; j" + - (showNumbers ? "
" : '') + - "
 
" + - ""; - addDays(d, 1); - } - s += ""; - } - tbody.append(s); - } - tbody.find('td.fc-new').removeClass('fc-new').click(dayClick); - - // re-label and re-class existing cells - d = cloneDate(view.visStart); - tbody.find('td').each(function() { - var td = $(this); - if (rowCnt > 1) { - if (d.getMonth() == month) { - td.removeClass('fc-other-month'); - }else{ - td.addClass('fc-other-month'); - } - } - if (+d == +today) { - td.removeClass('fc-not-today') - .addClass('fc-today') - .addClass(tm + '-state-highlight'); - }else{ - td.addClass('fc-not-today') - .removeClass('fc-today') - .removeClass(tm + '-state-highlight'); - } - td.find('div.fc-day-number').text(d.getDate()); - addDays(d, 1); - }); - - if (rowCnt == 1) { // more likely changed (week or day view) - - // redo column header text and class - d = cloneDate(view.visStart); - thead.find('th').each(function() { - $(this).text(formatDate(d, colFormat, options)); - this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]); - addDays(d, 1); - }); - - // redo cell day-of-weeks - d = cloneDate(view.visStart); - tbody.find('td').each(function() { - this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]); - addDays(d, 1); - }); - - } - - } - - updateSize(); - fetchEvents(renderEvents); - - }; - - - function updateSize() { - var width = element.width(); - var height = Math.round(width / options.aspectRatio); - setOuterWidth( - thead.find('th').slice(0, -1), - colWidth = Math.floor(width / colCnt) - ); - var leftTDs = tbody.find('tr td:first-child'); - var tbodyHeight = height - thead.height(); - var rowHeight1, rowHeight2; - if (options.weekMode == 'variable') { - rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt==1 ? 2 : 6)); - }else{ - rowHeight1 = Math.floor(tbodyHeight / rowCnt); - rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1); - } - - if (sniffBugs) { - // nasty bugs in opera 9.25 - // position() returning relative to direct parent - var tr = tbody.find('tr:first'); - var td = tr.find('td:first'); - var trTop = tr.position().top; - var tdTop = td.position().top; - tdTopBug = tdTop < 0; - trTopBug = trTop != tdTop; - tbodyTopBug = tbody.position().top != trTop; - sniffBugs = false; - // - td.height(rowHeight1); - tdHeightBug = rowHeight1 != td.height(); - } - - if (tdHeightBug) { - leftTDs.slice(0, -1).height(rowHeight1); - leftTDs.slice(-1).height(rowHeight2); - }else{ - setOuterHeight(leftTDs.slice(0, -1), rowHeight1); - setOuterHeight(leftTDs.slice(-1), rowHeight2); - } - - //alert(tbodyHeight + ' === ' + tbody.height()); - } - - - - /* Event Rendering - -----------------------------------------------------------------------------*/ - - - function renderEvents(events) { - view.reportEvents(events); - renderSegs(cachedSegs = compileSegs(events)); - } - - - function rerenderEvents(skipCompile) { - //console.log('rerender events'); - view.clearEvents(); - if (skipCompile) { - renderSegs(cachedSegs); - }else{ - renderEvents(view.cachedEvents); - } - } - - - function compileSegs(events) { - var d1 = cloneDate(view.visStart); - var d2 = addDays(cloneDate(d1), colCnt); - var rows = []; - for (var i=0; i") - .append(eventAnchor = $("") - .append(event.allDay ? null : - $("") - .html(formatDates(event.start, event.end, options.timeFormat, options))) - .append($("") - .text(event.title))); - if (event.url) { - eventAnchor.attr('href', event.url); - } - triggerRes = view.trigger('eventRender', event, event, eventElement); - if (triggerRes !== false) { - if (triggerRes && typeof triggerRes != 'boolean') { - eventElement = $(triggerRes); - } - eventElement - .css({ - position: 'absolute', - top: top, - left: left1 + eventLeftDiff, - zIndex: 3 - }) - .appendTo(element); - setOuterWidth(eventElement, left2-left1, true); - if (!sniffedEventLeftBug) { - if (rtl) { - eventLeftDiff = left1 - eventElement.position().left; - if (eventLeftDiff) { - eventElement.css('left', left1 + eventLeftDiff); - } - } - sniffedEventLeftBug = true; - } - eventElementHandlers(event, eventElement); - if (event.editable || typeof event.editable == 'undefined' && options.editable) { - draggableEvent(event, eventElement); - resizableEvent(event, eventElement); - } - view.reportEventElement(event, eventElement); - levelHeight = Math.max(levelHeight, eventElement.outerHeight(true)); - } - } - weekHeight += levelHeight; - top += levelHeight; - } - innerDiv.height(weekHeight); - } - } - - function eventElementHandlers(event, eventElement) { - eventElement - .click(function(ev) { - if (!eventElement.hasClass('ui-draggable-dragging')) { - return view.trigger('eventClick', this, event, ev); - } - }) - .hover( - function(ev) { - view.trigger('eventMouseover', this, event, ev); - }, - function(ev) { - view.trigger('eventMouseover', this, event, ev); - } - ); - } - - - - /* Draggable - -----------------------------------------------------------------------------*/ - - - function draggableEvent(event, eventElement) { - if (!options.disableDragging && eventElement.draggable) { - var matrix; - eventElement.draggable({ - zIndex: 4, - delay: 50, - opacity: options.dragOpacity, - revertDuration: options.dragRevertDuration, - start: function(ev, ui) { - matrix = new HoverMatrix(function(cell) { - eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta); - if (cell) { - view.showOverlay(cell); - }else{ - view.hideOverlay(); - } - }); - tbody.find('tr').each(function() { - matrix.row(this); - }); - var tds = tbody.find('tr:first td'); - if (rtl) { - tds = $(tds.get().reverse()); - } - tds.each(function() { - matrix.col(this); - }); - matrix.start(); - view.hideEvents(event, eventElement); - view.trigger('eventDragStart', eventElement, event, ev, ui); - }, - drag: function(ev) { - matrix.mouse(ev.pageX, ev.pageY); - }, - stop: function(ev, ui) { - view.hideOverlay(); - view.trigger('eventDragStop', eventElement, event, ev, ui); - var cell = matrix.cell; - if (!cell || !cell.rowDelta && !cell.colDelta) { - view.showEvents(event, eventElement); - }else{ - var dayDelta = cell.rowDelta*7 + cell.colDelta*dis; - view.moveEvent(event, dayDelta); - view.trigger('eventDrop', this, event, dayDelta, 0, ev, ui); - rerenderEvents(); - } - } - }); - } - } - - - - /* Resizable - -----------------------------------------------------------------------------*/ - - - function resizableEvent(event, eventElement) { - if (!options.disableResizing && eventElement.resizable) { - eventElement.resizable({ - handles: rtl ? 'w' : 'e', - grid: [colWidth, 0], - containment: element, - start: function(ev, ui) { - eventElement.css('z-index', 4); - view.hideEvents(event, eventElement); - view.trigger('eventResizeStart', this, event, ev, ui); - }, - stop: function(ev, ui) { - view.trigger('eventResizeStop', this, event, ev, ui); - var dayDelta = Math.round((Math.max(colWidth, ui.size.width) - ui.originalSize.width) / colWidth); - if (dayDelta) { - view.resizeEvent(event, dayDelta); - view.trigger('eventResize', this, event, dayDelta, 0, ev, ui); - rerenderEvents(); - }else{ - view.showEvents(event, eventElement); - } - eventElement.css('z-index', 3); - } - }); - } - } - - - - - // - - function dayClick() { - var dayIndex = parseInt(this.className.match(/fc\-day(\d+)/)[1]); - var date = addDays(cloneDate(view.visStart), dayIndex); - view.trigger('dayClick', this, date); - } - - -}; + +/* Grid-based Views: month, basicWeek, basicDay +-----------------------------------------------------------------------------*/ + +setDefaults({ + weekMode: 'fixed' +}); + +views.month = function(element, options) { + return new Grid(element, options, { + render: function(date, delta, fetchEvents) { + if (delta) { + addMonths(date, delta); + } + var start = this.start = cloneDate(date, true); + start.setDate(1); + this.title = formatDates( + start, + addDays(cloneDate(this.end = addMonths(cloneDate(start), 1)), -1), + strProp(options.titleFormat, 'month'), + options + ); + addDays(this.visStart = cloneDate(start), -((start.getDay() - options.firstDay + 7) % 7)); + addDays(this.visEnd = cloneDate(this.end), (7 - this.visEnd.getDay() + options.firstDay) % 7); + var rowCnt = Math.round((this.visEnd - this.visStart) / (DAY_MS * 7)); + if (options.weekMode == 'fixed') { + addDays(this.visEnd, (6 - rowCnt) * 7); + rowCnt = 6; + } + this.renderGrid(rowCnt, 7, strProp(options.columnFormat, 'month'), true, fetchEvents); + } + }); +} + +views.basicWeek = function(element, options) { + return new Grid(element, options, { + render: function(date, delta, fetchEvents) { + if (delta) { + addDays(date, delta * 7); + } + this.title = formatDates( + this.start = this.visStart = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7)), + addDays(cloneDate(this.end = this.visEnd = addDays(cloneDate(this.start), 7)), -1), + strProp(options.titleFormat, 'week'), + options + ); + this.renderGrid(1, 7, strProp(options.columnFormat, 'week'), false, fetchEvents); + } + }); +}; + +views.basicDay = function(element, options) { + return new Grid(element, options, { + render: function(date, delta, fetchEvents) { + if (delta) { + addDays(date, delta); + } + this.title = formatDate(date, strProp(options.titleFormat, 'day'), options); + this.start = this.visStart = cloneDate(date, true); + this.end = this.visEnd = addDays(cloneDate(this.start), 1); + this.renderGrid(1, 1, strProp(options.columnFormat, 'day'), false, fetchEvents); + } + }); +} + + +// rendering bugs + +var tdTopBug, trTopBug, tbodyTopBug, + tdHeightBug, + rtlLeftDiff; + + +function Grid(element, options, methods) { + + var tm, firstDay, + rtl, dis, dit, // day index sign / translate + rowCnt, colCnt, + colWidth, + thead, tbody, + cachedSegs, //... + + // initialize superclass + view = $.extend(this, viewMethods, methods, { + renderGrid: renderGrid, + renderEvents: renderEvents, + rerenderEvents: rerenderEvents, + updateSize: updateSize, + defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing + return cloneDate(event.start); + }, + visEventEnd: function(event) { // returns exclusive 'visible' end, for rendering + if (event.end) { + var end = cloneDate(event.end); + return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end; + }else{ + return addDays(cloneDate(event.start), 1); + } + } + }); + view.init(element, options); + + + + /* Grid Rendering + -----------------------------------------------------------------------------*/ + + + element.addClass('fc-grid').css('position', 'relative'); + if (element.disableSelection) { + element.disableSelection(); + } + + function renderGrid(r, c, colFormat, showNumbers, fetchEvents) { + rowCnt = r; + colCnt = c; + + var month = view.start.getMonth(), + today = clearTime(new Date()), + s, i, j, d = cloneDate(view.visStart); + + // update option-derived variables + tm = options.theme ? 'ui' : 'fc'; + firstDay = options.firstDay; + if (rtl = options.isRTL) { + dis = -1; + dit = colCnt - 1; + }else{ + dis = 1; + dit = 0; + } + + if (!tbody) { // first time, build all cells from scratch + + var table = $("
").appendTo(element); + + s = ""; + for (i=0; i" + formatDate(d, colFormat, options) + ""; + addDays(d, 1); + } + thead = $(s + "").appendTo(table); + + s = ""; + d = cloneDate(view.visStart); + for (i=0; i"; + for (j=0; j1 && d.getMonth() != month ? ' fc-other-month' : '') + + (+d == +today ? + ' fc-today '+tm+'-state-highlight' : + ' fc-not-today') + "'>" + + (showNumbers ? "
" + d.getDate() + "
" : '') + + "
 
"; + addDays(d, 1); + } + s += ""; + } + tbody = $(s + "
").appendTo(table); + tbody.find('td').click(dayClick); + + }else{ // NOT first time, reuse as many cells as possible + + view.clearEvents(); + + var prevRowCnt = tbody.find('tr').length; + if (rowCnt < prevRowCnt) { + tbody.find('tr:gt(' + (rowCnt-1) + ')').remove(); // remove extra rows + } + else if (rowCnt > prevRowCnt) { // needs to create new rows... + s = ''; + for (i=prevRowCnt; i"; + for (j=0; j" + + (showNumbers ? "
" : '') + + "
 
" + + ""; + addDays(d, 1); + } + s += ""; + } + tbody.append(s); + } + tbody.find('td.fc-new').removeClass('fc-new').click(dayClick); + + // re-label and re-class existing cells + d = cloneDate(view.visStart); + tbody.find('td').each(function() { + var td = $(this); + if (rowCnt > 1) { + if (d.getMonth() == month) { + td.removeClass('fc-other-month'); + }else{ + td.addClass('fc-other-month'); + } + } + if (+d == +today) { + td.removeClass('fc-not-today') + .addClass('fc-today') + .addClass(tm + '-state-highlight'); + }else{ + td.addClass('fc-not-today') + .removeClass('fc-today') + .removeClass(tm + '-state-highlight'); + } + td.find('div.fc-day-number').text(d.getDate()); + addDays(d, 1); + }); + + if (rowCnt == 1) { // more changes likely (week or day view) + + // redo column header text and class + d = cloneDate(view.visStart); + thead.find('th').each(function() { + $(this).text(formatDate(d, colFormat, options)); + this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]); + addDays(d, 1); + }); + + // redo cell day-of-weeks + d = cloneDate(view.visStart); + tbody.find('td').each(function() { + this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]); + addDays(d, 1); + }); + + } + + } + + updateSize(); + fetchEvents(renderEvents); + + }; + + + function dayClick() { + var date = addDays( + cloneDate(view.visStart), + parseInt(this.className.match(/fc\-day(\d+)/)[1]) + ); + view.trigger('dayClick', this, date); + } + + + function updateSize() { + var width = element.width(), + height = Math.round(width / options.aspectRatio), + leftTDs = tbody.find('tr td:first-child'), + tbodyHeight = height - thead.height(), + rowHeight1, rowHeight2; + setOuterWidth( + thead.find('th').slice(0, -1), + colWidth = Math.floor(width / colCnt) + ); + if (options.weekMode == 'variable') { + rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt==1 ? 2 : 6)); + }else{ + rowHeight1 = Math.floor(tbodyHeight / rowCnt); + rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1); + } + + if (tdTopBug == undefined) { + // nasty bugs in opera 9.25 + // position() returning relative to direct parent + var tr = tbody.find('tr:first'), + td = tr.find('td:first'), + trTop = tr.position().top, + tdTop = td.position().top; + tdTopBug = tdTop < 0; + trTopBug = trTop != tdTop; + tbodyTopBug = tbody.position().top != trTop; + } + if (tdHeightBug == undefined) { + // bug in firefox where cell height includes padding + td.height(rowHeight1); + tdHeightBug = rowHeight1 != td.height(); + } + + if (tdHeightBug) { + leftTDs.slice(0, -1).height(rowHeight1); + leftTDs.slice(-1).height(rowHeight2); + }else{ + setOuterHeight(leftTDs.slice(0, -1), rowHeight1); + setOuterHeight(leftTDs.slice(-1), rowHeight2); + } + } + + + + /* Event Rendering + -----------------------------------------------------------------------------*/ + + + function renderEvents(events) { + view.reportEvents(events); + renderSegs(cachedSegs = compileSegs(events)); + } + + + function rerenderEvents(skipCompile) { + view.clearEvents(); + if (skipCompile) { + renderSegs(cachedSegs); + }else{ + renderEvents(view.cachedEvents); + } + } + + + function compileSegs(events) { + var d1 = cloneDate(view.visStart); + var d2 = addDays(cloneDate(d1), colCnt); + var rows = []; + for (var i=0; i") + .append(eventAnchor = $("
") + .append(event.allDay || !seg.isStart ? null : + $("") + .html(formatDates(event.start, event.end, options.timeFormat, options))) + .append($("") + .text(event.title))); + if (event.url) { + eventAnchor.attr('href', event.url); + } + triggerRes = view.trigger('eventRender', event, event, eventElement); + if (triggerRes !== false) { + if (triggerRes && typeof triggerRes != 'boolean') { + eventElement = $(triggerRes); + } + eventElement + .css({ + position: 'absolute', + top: top, + left: left1 + (rtlLeftDiff||0), + zIndex: 2 + }) + .appendTo(element); + setOuterWidth(eventElement, left2-left1, true); + if (rtl && rtlLeftDiff == undefined) { + // bug in IE6 where offsets are miscalculated with direction:rtl + rtlLeftDiff = left1 - eventElement.position().left; + if (rtlLeftDiff) { + eventElement.css('left', left1 + rtlLeftDiff); + } + } + eventElementHandlers(event, eventElement); + if (event.editable || event.editable == undefined && options.editable) { + draggableEvent(event, eventElement); + resizableEvent(event, eventElement); + } + view.reportEventElement(event, eventElement); + levelHeight = Math.max(levelHeight, eventElement.outerHeight(true)); + } + } + weekHeight += levelHeight; + top += levelHeight; + } + innerDiv.height(weekHeight); + } + } + + function eventElementHandlers(event, eventElement) { + eventElement + .click(function(ev) { + if (!eventElement.hasClass('ui-draggable-dragging')) { + return view.trigger('eventClick', this, event, ev); + } + }) + .hover( + function(ev) { + view.trigger('eventMouseover', this, event, ev); + }, + function(ev) { + view.trigger('eventMouseout', this, event, ev); + } + ); + } + + + + /* Draggable + -----------------------------------------------------------------------------*/ + + + function draggableEvent(event, eventElement) { + if (!options.disableDragging && eventElement.draggable) { + var matrix; + eventElement.draggable({ + zIndex: 3, + delay: 100, + opacity: options.dragOpacity, + revertDuration: options.dragRevertDuration, + start: function(ev, ui) { + matrix = new HoverMatrix(function(cell) { + eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta); + if (cell) { + view.showOverlay(cell); + }else{ + view.hideOverlay(); + } + }); + tbody.find('tr').each(function() { + matrix.row(this, tbodyTopBug); + }); + var tds = tbody.find('tr:first td'); + if (rtl) { + tds = $(tds.get().reverse()); + } + tds.each(function() { + matrix.col(this); + }); + view.hideEvents(event, eventElement); + view.trigger('eventDragStart', eventElement, event, ev, ui); + matrix.mouse(ev.pageX, ev.pageY); + }, + drag: function(ev) { + matrix.mouse(ev.pageX, ev.pageY); + }, + stop: function(ev, ui) { + view.hideOverlay(); + view.trigger('eventDragStop', eventElement, event, ev, ui); + var cell = matrix.cell; + if (!cell || !cell.rowDelta && !cell.colDelta) { + view.showEvents(event, eventElement); + }else{ + var dayDelta = cell.rowDelta*7 + cell.colDelta*dis; + view.moveEvent(event, dayDelta); + view.trigger('eventDrop', this, event, dayDelta, 0, function() { + view.moveEvent(event, -dayDelta); + rerenderEvents(); + }, ev, ui); + eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link + rerenderEvents(); + } + } + }); + } + } + + + + /* Resizable + -----------------------------------------------------------------------------*/ + + + function resizableEvent(event, eventElement) { + if (!options.disableResizing && eventElement.resizable) { + eventElement.resizable({ + handles: rtl ? 'w' : 'e', + grid: [colWidth, 0], + containment: element, + start: function(ev, ui) { + eventElement.css('z-index', 3); + view.hideEvents(event, eventElement); + view.trigger('eventResizeStart', this, event, ev, ui); + }, + stop: function(ev, ui) { + view.trigger('eventResizeStop', this, event, ev, ui); + // ui.size.width wasn't working with grid correctly, use .width() + var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth); + if (dayDelta) { + view.resizeEvent(event, dayDelta); + view.trigger('eventResize', this, event, dayDelta, 0, function() { + view.resizeEvent(event, -dayDelta); + rerenderEvents(); + }, ev, ui); + rerenderEvents(); + }else{ + view.showEvents(event, eventElement); + } + eventElement.css('z-index', 2); + } + }); + } + } + +}; + diff --git a/src/main.js b/src/main.js index f4cfebb..6768cc7 100755 --- a/src/main.js +++ b/src/main.js @@ -17,6 +17,11 @@ var defaults = { right: 'today prev,next' }, + // editing + //editable: false, + //disableDragging: false, + //disableResizing: false, + // event ajax startParam: 'start', endParam: 'end', @@ -35,16 +40,16 @@ var defaults = { day: 'dddd M/d' }, - // regional + // locale isRTL: false, - weekStart: 0, + firstDay: 0, monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'], buttonText: { - prev: '◄', - next: '►', + prev: ' ◄ ', + next: ' ► ', today: 'today', month: 'month', week: 'week', @@ -57,6 +62,7 @@ var defaults = { prev: 'circle-triangle-w', next: 'circle-triangle-e' } + }; // right-to-left defaults @@ -67,8 +73,8 @@ var rtlDefaults = { right: 'title' }, buttonText: { - prev: '►', - next: '◄' + prev: ' ► ', + next: ' ◄ ' } }; @@ -86,12 +92,15 @@ $.fn.fullCalendar = function(options) { // method calling if (typeof options == 'string') { - var args = Array.prototype.slice.call(arguments, 1), res; + var args = Array.prototype.slice.call(arguments, 1), + res; this.each(function() { var r = $.data(this, 'fullCalendar')[options].apply(this, args); - if (typeof res == 'undefined') res = r; + if (res == undefined) { + res = r; + } }); - if (typeof res != 'undefined') { + if (res != undefined) { return res; } return this; @@ -111,10 +120,10 @@ $.fn.fullCalendar = function(options) { // initialize options options = $.extend(true, {}, defaults, - (options.isRTL || typeof options.isRTL == 'undefined' && defaults.isRTL) ? rtlDefaults : {}, + (options.isRTL || options.isRTL==undefined && defaults.isRTL) ? rtlDefaults : {}, options ); - var tm = options.theme ? 'ui' : 'fc'; + var tm = options.theme ? 'ui' : 'fc'; // for making theme classes this.each(function() { @@ -139,13 +148,13 @@ $.fn.fullCalendar = function(options) { viewName, view, // the current view prevView, viewInstances = {}; - if (options.year) { + if (options.year != undefined) { date.setYear(options.year); } - if (options.month) { + if (options.month != undefined) { date.setMonth(options.month); } - if (options.date) { + if (options.date != undefined) { date.setDate(options.date); } @@ -164,35 +173,40 @@ $.fn.fullCalendar = function(options) { $("
").appendTo(content), options); } - if (prevView) { - prevView.element.hide(); - if (prevView.eventsChanged) { - eventsDirtyExcept(prevView); - prevView.eventsChanged = false; - } + if (prevView && prevView.eventsChanged) { + // if previous view's events have been changed, mark future views' events as dirty + eventsDirtyExcept(prevView); + prevView.eventsChanged = false; } if (header) { + // update 'active' view button header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active'); header.find('div.fc-button-' + v).addClass(tm + '-state-active'); } view.name = viewName = v; render(); + if (prevView) { + // hide the old element AFTER the new has been rendered, preserves scrollbars + prevView.element.hide(); + } } } function render(inc) { - if (inc || !view.date || +view.date != +date) { - ignoreResizes = true; + if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet + ignoreWindowResizes = true; view.render(date, inc || 0, function(callback) { + // dont refetch if new view contains the same events (or a subset) if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) { fetchEvents(callback); }else{ - callback(events); + callback(events); // no refetching } }); - ignoreResizes = false; + ignoreWindowResizes = false; view.date = cloneDate(date); if (header) { + // enable/disable 'today' button var today = new Date(); if (today >= view.start && today < view.end) { header.find('div.fc-button-today').addClass(tm + '-state-disabled'); @@ -202,15 +216,18 @@ $.fn.fullCalendar = function(options) { } } else if (view.eventsDirty) { + // ensure events are rerendered if another view messed with them view.rerenderEvents(); } if (header) { + // update title text header.find('h2.fc-header-title').html(view.title); } view.eventsDirty = false; view.trigger('viewDisplay', _element); } + // marks other views' events as dirty function eventsDirtyExcept(exceptView) { $.each(viewInstances, function() { if (this != exceptView) { @@ -219,6 +236,7 @@ $.fn.fullCalendar = function(options) { }); } + // called when any event objects have been added/removed/changed, rerenders function eventsChanged() { view.clearEvents(); view.renderEvents(events); @@ -253,20 +271,21 @@ $.fn.fullCalendar = function(options) { // Fetch from a particular source. Append to the 'events' array function fetchEventSource(src, callback) { - var prevDate = cloneDate(date), + var prevViewName = view.name, + prevDate = cloneDate(date), reportEvents = function(a) { - if (+date == +prevDate) { + if (prevViewName == view.name && +prevDate == +date) { // protects from fast switching for (var i=0; i"), - prevTitle = false; + var tr = $("
"); $.each(buttonStr.split(' '), function(i) { if (i > 0) { tr.append(""); } + var prevButton; $.each(this.split(','), function(j) { var buttonName = this, buttonNameShort = this.replace(/^(basic|agenda)/, '').toLowerCase(); if (buttonName == 'title') { - tr.find('> :last div').addClass(tm + '-corner-right'); tr.append(""); - prevTitle = true; + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } + prevButton = null; }else{ var buttonClick; if (publicMethods[buttonNameShort]) { @@ -512,6 +535,9 @@ $.fn.fullCalendar = function(options) { buttonClick = function() { switchView(buttonName) }; } if (buttonClick) { + if (prevButton) { + prevButton.addClass(tm + '-no-right'); + } var button, icon = options.theme ? options.buttonIcons[buttonNameShort] : null, text = options.buttonText[buttonNameShort]; @@ -549,17 +575,19 @@ $.fn.fullCalendar = function(options) { switchView(buttonName); }); } - if (j == 0 || prevTitle) { - button.addClass(tm + '-corner-left'); + if (prevButton) { + prevButton.addClass(tm + '-no-right'); }else{ - button.addClass(tm + '-no-left'); + button.addClass(tm + '-corner-left'); } - prevTitle = false; + prevButton = button; } } } }); - tr.find('> :last div').addClass(tm + '-corner-right'); + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } }); return $("

").append(tr); } @@ -571,11 +599,11 @@ $.fn.fullCalendar = function(options) { -----------------------------------------------------------------------------*/ var elementWidth, - ignoreResizes = false, + ignoreWindowResizes = false, resizeCnt = 0; $(window).resize(function() { - if (!ignoreResizes) { + if (!ignoreWindowResizes) { var rcnt = ++resizeCnt; // add a delay setTimeout(function() { if (rcnt == resizeCnt) { @@ -610,7 +638,7 @@ $.fn.fullCalendar = function(options) { var fakeID = 0; function normalizeEvent(event) { - event._id = event._id || (typeof event.id == 'undefined' ? '_fc' + fakeID++ : event.id + ''); + event._id = event._id || (event.id == undefined ? '_fc' + fakeID++ : event.id + ''); if (event.date) { if (!event.start) { event.start = event.date; @@ -620,10 +648,10 @@ function normalizeEvent(event) { event._start = cloneDate(event.start = parseDate(event.start)); event.end = parseDate(event.end); if (event.end && event.end < event.start) { - event.end = cloneDate(event.start); + event.end = null; } event._end = event.end ? cloneDate(event.end) : null; - if (typeof event.allDay == 'undefined') { + if (event.allDay == undefined) { event.allDay = true; } } diff --git a/src/misc/foot.txt b/src/misc/foot.txt index 7f75bb6..492f9b3 100755 --- a/src/misc/foot.txt +++ b/src/misc/foot.txt @@ -1 +1,2 @@ + })(jQuery); \ No newline at end of file diff --git a/src/misc/head.txt b/src/misc/head.txt index 59b2706..2836634 100755 --- a/src/misc/head.txt +++ b/src/misc/head.txt @@ -2,8 +2,9 @@ * FullCalendar * http://arshaw.com/fullcalendar/ * - * use fullcalendar.css for basic styling - * requires jQuery UI core and draggables ONLY if you plan to do drag & drop + * Use fullcalendar.css for basic styling. + * For event drag & drop, required jQuery UI draggable. + * For event resizing, requires jQuery UI resizable. * * Copyright (c) 2009 Adam Shaw * Dual licensed under the MIT and GPL licenses: @@ -14,4 +15,5 @@ * Revision: */ -(function($) { \ No newline at end of file +(function($) { + diff --git a/src/util.js b/src/util.js index f5c9544..31dd740 100755 --- a/src/util.js +++ b/src/util.js @@ -1,321 +1,325 @@ - -/* Date Math ------------------------------------------------------------------------------*/ - -var DAY_MS = 86400000; - -function addYears(d, n, keepTime) { - d.setFullYear(d.getFullYear() + n); - if (keepTime) return d; - return clearTime(d); -} - -function addMonths(d, n, keepTime) { - d.setMonth(d.getMonth() + n); - if (keepTime) return d; - return clearTime(d); -} - -function addDays(d, n, keepTime) { - d.setDate(d.getDate() + n); - if (keepTime) return d; - return clearTime(d); -} - -function addMinutes(d, n) { - d.setMinutes(d.getMinutes() + n); - return d; -} - -function clearTime(d) { - d.setHours(0); - d.setMinutes(0); - d.setSeconds(0); - d.setMilliseconds(0); - return d; -} - -function cloneDate(d, dontKeepTime) { - if (dontKeepTime) { - return clearTime(new Date(+d)); - }else{ - return new Date(+d); - } -} - - - -/* Date Parsing ------------------------------------------------------------------------------*/ - -var parseDate = fc.parseDate = function(s) { - if (typeof s == 'object') - return s; // already a Date object - if (typeof s == 'undefined') - return null; - if (typeof s == 'number') - return new Date(s * 1000); - return parseISO8601(s, true) || - Date.parse(s) || - new Date(parseInt(s) * 1000); -} - -var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) { - // derived from http://delete.me.uk/2005/03/iso8601.html - var regexp = "([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})))?)?)?)?"; - var d = s.match(new RegExp(regexp)); - if (!d) return null; - var offset = 0; - var date = new Date(d[1], 0, 1); - if (d[3]) { date.setMonth(d[3] - 1); } - if (d[5]) { date.setDate(d[5]); } - if (d[7]) { date.setHours(d[7]); } - if (d[8]) { date.setMinutes(d[8]); } - if (d[10]) { date.setSeconds(d[10]); } - if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } - if (!ignoreTimezone) { - if (d[14]) { - offset = (Number(d[16]) * 60) + Number(d[17]); - offset *= ((d[15] == '-') ? 1 : -1); - } - offset -= date.getTimezoneOffset(); - } - return new Date(Number(date) + (offset * 60 * 1000)); -} - - - -/* Date Formatting ------------------------------------------------------------------------------*/ - -var formatDate = fc.formatDate = function(date, format, options) { - return formatDates(date, null, format, options); -} - -var formatDates = fc.formatDates = function(date1, date2, format, options) { - options = options || defaults; - var date = date1, - otherDate = date2, - i, len = format.length, c, - i2, formatter, - res = ''; - for (i=0; ii; i2--) { - if (formatter = dateFormatters[format.substring(i, i2)]) { - if (date) { - res += formatter(date, options); - } - i = i2 - 1; - break; - } - } - if (i2 == i) { - if (date) { - res += c; - } - } - } - } - return res; -} - -var dateFormatters = { - s : function(d) { return d.getSeconds() }, - ss : function(d) { return zeroPad(d.getSeconds()) }, - m : function(d) { return d.getMinutes() }, - mm : function(d) { return zeroPad(d.getMinutes()) }, - h : function(d) { return d.getHours() % 12 || 12 }, - hh : function(d) { return zeroPad(d.getHours() % 12 || 12) }, - H : function(d) { return d.getHours() }, - HH : function(d) { return zeroPad(d.getHours()) }, - d : function(d) { return d.getDate() }, - dd : function(d) { return zeroPad(d.getDate()) }, - ddd : function(d,o) { return o.dayNamesShort[d.getDay()] }, - dddd: function(d,o) { return o.dayNames[d.getDay()] }, - M : function(d) { return d.getMonth() + 1 }, - MM : function(d) { return zeroPad(d.getMonth() + 1) }, - MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] }, - MMMM: function(d,o) { return o.monthNames[d.getMonth()] }, - yy : function(d) { return (d.getFullYear()+'').substring(2) }, - yyyy: function(d) { return d.getFullYear() }, - t : function(d) { return d.getHours() < 12 ? 'a' : 'p' }, - tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' }, - T : function(d) { return d.getHours() < 12 ? 'A' : 'P' }, - TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }, - u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") }, - S : function(d) { - var date = d.getDate(); - if (date > 10 && date < 20) return 'th'; - return ['st', 'nd', 'rd'][date%10-1] || 'th'; - } -}; - - - -/* Element Dimensions ------------------------------------------------------------------------------*/ - -function setOuterWidth(element, width, includeMargins) { - element.each(function() { - var e = $(this); - var w = width - ( - (parseInt(e.css('border-left-width')) || 0) + - (parseInt(e.css('padding-left')) || 0) + - (parseInt(e.css('padding-right')) || 0) + - (parseInt(e.css('border-right-width')) || 0)); - if (includeMargins) { - w -= - (parseInt(e.css('margin-left')) || 0) + - (parseInt(e.css('margin-right')) || 0); - } - e.width(w); - }); -} - -function setOuterHeight(element, height, includeMargins) { - element.each(function() { - var e = $(this); - var h = height - ( - (parseInt(e.css('border-top-width')) || 0) + - (parseInt(e.css('padding-top')) || 0) + - (parseInt(e.css('padding-bottom')) || 0) + - (parseInt(e.css('border-bottom-width')) || 0)); - if (includeMargins) { - h -= - (parseInt(e.css('margin-top')) || 0) + - (parseInt(e.css('margin-bottom')) || 0); - } - e.height(h); - }); -} - - - -/* Hover Matrix ------------------------------------------------------------------------------*/ - -function HoverMatrix(changeCallback) { - - var tops=[], lefts=[], - prevRowE, prevColE, - origRow, origCol, - currRow, currCol; - - // this.cell = null; - - this.row = function(e) { - prevRowE = $(e); - tops.push(prevRowE.offset().top); - }; - - this.col = function(e) { - prevColE = $(e); - lefts.push(prevColE.offset().left); - }; - - this.start = function() { - tops.push(tops[tops.length-1] + prevRowE.outerHeight()); - lefts.push(lefts[lefts.length-1] + prevColE.outerWidth()); - origRow = currRow = currCol = -1; - }; - - this.mouse = function(x, y) { - var r, c; - for (r=0; r=tops[r]; r++) ; - for (c=0; c=lefts[c]; c++) ; - r = r >= tops.length ? -1 : r - 1; - c = c >= lefts.length ? -1 : c - 1; - if (r != currRow || c != currCol) { - currRow = r; - currCol = c; - if (r == -1 || c == -1) { - this.cell = null; - }else{ - if (origRow == -1) { - origRow = r; - origCol = c; - } - this.cell = { - row: r, - col: c, - top: tops[r], - left: lefts[c], - width: lefts[c+1] - lefts[c], - height: tops[r+1] - tops[r], - isOrig: r==origRow && c==origCol, - rowDelta: r-origRow, - colDelta: c-origCol - }; - } - changeCallback(this.cell); - } - }; - -} - - - -/* Misc Utils ------------------------------------------------------------------------------*/ - -var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; - -function zeroPad(n) { - return (n < 10 ? '0' : '') + n; -} - -function strProp(s, prop) { - return typeof s == 'string' ? s : s[prop]; -} + +/* Date Math +-----------------------------------------------------------------------------*/ + +var DAY_MS = 86400000; + +function addYears(d, n, keepTime) { + d.setFullYear(d.getFullYear() + n); + if (keepTime) return d; + return clearTime(d); +} + +function addMonths(d, n, keepTime) { + d.setMonth(d.getMonth() + n); + if (keepTime) return d; + return clearTime(d); +} + +function addDays(d, n, keepTime) { + d.setDate(d.getDate() + n); + if (keepTime) return d; + return clearTime(d); +} + +function addMinutes(d, n) { + d.setMinutes(d.getMinutes() + n); + return d; +} + +function clearTime(d) { + d.setHours(0); + d.setMinutes(0); + d.setSeconds(0); + d.setMilliseconds(0); + return d; +} + +function cloneDate(d, dontKeepTime) { + if (dontKeepTime) { + return clearTime(new Date(+d)); + } + return new Date(+d); +} + + + +/* Date Parsing +-----------------------------------------------------------------------------*/ + +var parseDate = fc.parseDate = function(s) { + if (typeof s == 'object') { // already a Date object + return s; + } + if (typeof s == 'number') { // a UNIX timestamp + return new Date(s * 1000); + } + if (typeof s == 'string') { + if (s.match(/^\d+$/)) { // a UNIX timestamp + return new Date(parseInt(s) * 1000); + } + return parseISO8601(s, true) || Date.parse(s) || null; + } + return null; +} + +var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) { + // derived from http://delete.me.uk/2005/03/iso8601.html + var d = s.match(parseISO8601Regex); + if (!d) return null; + var offset = 0; + var date = new Date(d[1], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { date.setHours(d[7]); } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (!ignoreTimezone) { + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + offset -= date.getTimezoneOffset(); + } + return new Date(Number(date) + (offset * 60 * 1000)); +} + +var parseISO8601Regex = new RegExp( + "([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})))?)?)?)?"); + + + +/* Date Formatting +-----------------------------------------------------------------------------*/ + +var formatDate = fc.formatDate = function(date, format, options) { + return formatDates(date, null, format, options); +} + +var formatDates = fc.formatDates = function(date1, date2, format, options) { + options = options || defaults; + var date = date1, + otherDate = date2, + i, len = format.length, c, + i2, formatter, + res = ''; + for (i=0; ii; i2--) { + if (formatter = dateFormatters[format.substring(i, i2)]) { + if (date) { + res += formatter(date, options); + } + i = i2 - 1; + break; + } + } + if (i2 == i) { + if (date) { + res += c; + } + } + } + } + return res; +} + +var dateFormatters = { + s : function(d) { return d.getSeconds() }, + ss : function(d) { return zeroPad(d.getSeconds()) }, + m : function(d) { return d.getMinutes() }, + mm : function(d) { return zeroPad(d.getMinutes()) }, + h : function(d) { return d.getHours() % 12 || 12 }, + hh : function(d) { return zeroPad(d.getHours() % 12 || 12) }, + H : function(d) { return d.getHours() }, + HH : function(d) { return zeroPad(d.getHours()) }, + d : function(d) { return d.getDate() }, + dd : function(d) { return zeroPad(d.getDate()) }, + ddd : function(d,o) { return o.dayNamesShort[d.getDay()] }, + dddd: function(d,o) { return o.dayNames[d.getDay()] }, + M : function(d) { return d.getMonth() + 1 }, + MM : function(d) { return zeroPad(d.getMonth() + 1) }, + MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] }, + MMMM: function(d,o) { return o.monthNames[d.getMonth()] }, + yy : function(d) { return (d.getFullYear()+'').substring(2) }, + yyyy: function(d) { return d.getFullYear() }, + t : function(d) { return d.getHours() < 12 ? 'a' : 'p' }, + tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' }, + T : function(d) { return d.getHours() < 12 ? 'A' : 'P' }, + TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }, + u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") }, + S : function(d) { + var date = d.getDate(); + if (date > 10 && date < 20) return 'th'; + return ['st', 'nd', 'rd'][date%10-1] || 'th'; + } +}; + + + +/* Element Dimensions +-----------------------------------------------------------------------------*/ + +function setOuterWidth(element, width, includeMargins) { + element.each(function() { + var e = $(this); + var w = width - ( + (parseInt(e.css('border-left-width')) || 0) + + (parseInt(e.css('padding-left')) || 0) + + (parseInt(e.css('padding-right')) || 0) + + (parseInt(e.css('border-right-width')) || 0)); + if (includeMargins) { + w -= + (parseInt(e.css('margin-left')) || 0) + + (parseInt(e.css('margin-right')) || 0); + } + e.width(w); + }); +} + +function setOuterHeight(element, height, includeMargins) { + element.each(function() { + var e = $(this); + var h = height - ( + (parseInt(e.css('border-top-width')) || 0) + + (parseInt(e.css('padding-top')) || 0) + + (parseInt(e.css('padding-bottom')) || 0) + + (parseInt(e.css('border-bottom-width')) || 0)); + if (includeMargins) { + h -= + (parseInt(e.css('margin-top')) || 0) + + (parseInt(e.css('margin-bottom')) || 0); + } + e.height(h); + }); +} + + + +/* Hover Matrix +-----------------------------------------------------------------------------*/ + +function HoverMatrix(changeCallback) { + + var tops=[], lefts=[], + prevRowE, prevColE, + origRow, origCol, + currRow, currCol; + + this.row = function(e, topBug) { + prevRowE = $(e); + tops.push(prevRowE.offset().top + (topBug ? prevRowE.parent().position().top : 0)); + }; + + this.col = function(e) { + prevColE = $(e); + lefts.push(prevColE.offset().left); + }; + + this.mouse = function(x, y) { + if (origRow == undefined) { + tops.push(tops[tops.length-1] + prevRowE.outerHeight()); + lefts.push(lefts[lefts.length-1] + prevColE.outerWidth()); + currRow = currCol = -1; + } + var r, c; + for (r=0; r=tops[r]; r++) ; + for (c=0; c=lefts[c]; c++) ; + r = r >= tops.length ? -1 : r - 1; + c = c >= lefts.length ? -1 : c - 1; + if (r != currRow || c != currCol) { + currRow = r; + currCol = c; + if (r == -1 || c == -1) { + this.cell = null; + }else{ + if (origRow == undefined) { + origRow = r; + origCol = c; + } + this.cell = { + row: r, + col: c, + top: tops[r], + left: lefts[c], + width: lefts[c+1] - lefts[c], + height: tops[r+1] - tops[r], + isOrig: r==origRow && c==origCol, + rowDelta: r-origRow, + colDelta: c-origCol + }; + } + changeCallback(this.cell); + } + }; + +} + + + +/* Misc Utils +-----------------------------------------------------------------------------*/ + +var undefined, + dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + +function zeroPad(n) { + return (n < 10 ? '0' : '') + n; +} + +function strProp(s, prop) { + return typeof s == 'string' ? s : s[prop]; +} + diff --git a/src/view.js b/src/view.js index 641f8d2..63c63e9 100755 --- a/src/view.js +++ b/src/view.js @@ -1,19 +1,30 @@ +/* Methods & Utilities for All Views +-----------------------------------------------------------------------------*/ + var viewMethods = { - // - // Objects inheriting these methods must implement the following properties/methods: - // - title - // - start - // - end - // - visStart - // - visEnd - // - defaultEventEnd(event) - // - visEventEnd(event) - // - // - render - // - rerenderEvents - // + /* + * Objects inheriting these methods must implement the following properties/methods: + * - title + * - start + * - end + * - visStart + * - visEnd + * - defaultEventEnd(event) + * - visEventEnd(event) + * - render(events) + * - rerenderEvents() + * + * + * z-index reservations: + * 1. day-overlay + * 2. events + * 3. dragging/resizing events + * + */ + + init: function(element, options) { this.element = element; @@ -26,7 +37,7 @@ var viewMethods = { - // trigger event handlers, always append view as last arg + // triggers an event handler, always append view as last arg trigger: function(name, thisObj) { if (this.options[name]) { @@ -36,7 +47,7 @@ var viewMethods = { - // + // returns a Date object for an event's end eventEnd: function(event) { return event.end || this.defaultEventEnd(event); @@ -44,14 +55,13 @@ var viewMethods = { - // event/element creation reporting + // report when view receives new events - reportEvents: function(events) { + reportEvents: function(events) { // events are already normalized at this point var i, len=events.length, event, - fakeID = 0, eventsByID = this.eventsByID = {}, cachedEvents = this.cachedEvents = []; - for (i=0; i