diff --git a/src/agenda.js b/src/agenda.js index f7bef63..d2689b8 100755 --- a/src/agenda.js +++ b/src/agenda.js @@ -1,13 +1,24 @@ +// todo: scrolling +// todo: check all other options +// cleanup CSS +// optimize moveEvent/resizeEvent, to return revert function + + /* Agenda Views: agendaWeek/agendaDay -----------------------------------------------------------------------------*/ setDefaults({ + allDayHeader: true, slotMinutes: 30, defaultEventMinutes: 120, - agendaTimeFormat: 'g:i{ - g:i}', // todo: merge into object w/ timeFormat axisFormat: 'htt', - agendaDragOpacity: .5 // maybe merge into object + timeFormat: { + agenda: 'h:mm{ - h:mm}' + }, + dragOpacity: { + agenda: .5 + } }); views.agendaWeek = function(element, options) { @@ -19,10 +30,10 @@ views.agendaWeek = function(element, options) { 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'), + this.option('titleFormat'), options ); - this.renderAgenda(7, strProp(options.columnFormat, 'week'), fetchEvents); + this.renderAgenda(7, this.option('columnFormat'), fetchEvents); } }); }; @@ -33,10 +44,10 @@ views.agendaDay = function(element, options) { if (delta) { addDays(date, delta); } - this.title = formatDate(date, strProp(options.titleFormat, 'day'), options); + this.title = formatDate(date, this.option('titleFormat'), options); this.start = this.visStart = cloneDate(date, true); this.end = this.visEnd = addDays(cloneDate(this.start), 1); - this.renderAgenda(1, strProp(options.columnFormat, 'day'), fetchEvents); + this.renderAgenda(1, this.option('columnFormat'), fetchEvents); } }); }; @@ -45,8 +56,8 @@ function Agenda(element, options, methods) { var head, body, bodyContent, bodyTable, bg, colCnt, - timeWidth, colWidth, rowHeight, - cachedSlotSegs, cachedDaySegs, + timeWidth, colWidth, rowHeight, // todo: timeWidth -> axisWidth, rowHeight->slotHeight ? + cachedDaySegs, cachedSlotSegs, tm, firstDay, rtl, dis, dit, // day index sign / translate // ... @@ -57,10 +68,26 @@ function Agenda(element, options, methods) { rerenderEvents: rerenderEvents, updateSize: updateSize, defaultEventEnd: function(event) { - return addMinutes(cloneDate(event.start), options.defaultEventMinutes); + var start = cloneDate(event.start); + if (event.allDay) { + return start; + } + return addMinutes(start, options.defaultEventMinutes); }, visEventEnd: function(event) { - return addMinutes(cloneDate(event.start), options.defaultEventMinutes); + if (event.allDay) { + 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); + } + } + if (event.end) { + return cloneDate(event.end); + }else{ + return addMinutes(cloneDate(event.start), options.defaultEventMinutes); + } } }); view.init(element, options); @@ -76,7 +103,7 @@ function Agenda(element, options, methods) { element.disableSelection(); } - function renderAgenda(c, colFormat, fetchEvents) { // TODO: get z-indexes sorted out + function renderAgenda(c, colFormat, fetchEvents) { colCnt = c; // update option-derived variables @@ -101,9 +128,9 @@ function Agenda(element, options, methods) { slotNormal = options.slotMinutes % 15 == 0, //... // head - s = "
" + + s = "
" + "" + - "" + + "" + ""; for (i=0; i" + formatDate(d, colFormat, options) + ""; addDays(d, dis); } - s+= "" + - "" + - "" + - "" + - "" + - "
  
all day" + - "
 
 
"; + s+= " "; + if (options.allDayHeader) { + s+= "" + + "all day" + + "" + + "
 
" + + " " + + "
"; + } + s+= "
"; head = $(s).appendTo(element); head.find('td').click(slotClick); @@ -134,7 +164,7 @@ function Agenda(element, options, methods) { "'>" + ((!slotNormal || minutes==0) ? formatDate(d, options.axisFormat) : ' ') + "
 
"; + tm + "-state-default'>
 
"; addMinutes(d, options.slotMinutes); } s += ""; @@ -214,7 +244,7 @@ function Agenda(element, options, methods) { // time-axis width timeWidth = 0; setOuterWidth( - head.find('th.fc-axis').add(body.find('th.fc-axis:first')) + head.find('tr:lt(2) th:first').add(body.find('tr:first th')) .width('') .each(function() { timeWidth = Math.max(timeWidth, $(this).outerWidth()); @@ -256,77 +286,146 @@ function Agenda(element, options, methods) { - - - - - - - /********************************** event rendering *********************************/ + /* Event Rendering + -----------------------------------------------------------------------------*/ function renderEvents(events) { - return; + view.reportEvents(events); - var i, len=events.length, event, - fakeID=0, nextDay, - slotEvents=[], dayEvents=[]; - + var i, len=events.length, + dayEvents=[], + slotEvents=[]; for (i=0; i view.start) { - if (event.hasTime) { - event._end = event.end || addMinutes(cloneDate(event.start), options.defaultEventMinutes); - slotEvents.push(event); - }else{ - event._end = addDays(cloneDate(event.end || event.start), 1); - dayEvents.push(event); - } + slotEvents.push(events[i]); } } - cachedEvents = events; - cachedSlotSegs = compileSlotSegs(slotEvents, view.start, view.end); - cachedDaySegs = levelizeSegs(sliceSegs(dayEvents, view.start, view.end)); - - renderSlotSegs(cachedSlotSegs); - renderDaySegs(cachedDaySegs); - + renderDaySegs(cachedDaySegs = stackSegs(view.sliceSegs(dayEvents, view.visStart, view.visEnd))); + renderSlotSegs(cachedSlotSegs = compileSlotSegs(slotEvents)); } function rerenderEvents(skipCompile) { - return; - clearEvents(); + view.clearEvents(); if (skipCompile) { - renderSlotSegs(cachedSlotSegs); renderDaySegs(cachedDaySegs); + renderSlotSegs(cachedSlotSegs); }else{ - renderEvents(cachedEvents); + renderEvents(view.cachedEvents); } } + function compileSlotSegs(events) { + var d1 = cloneDate(view.visStart), + d2 = addDays(cloneDate(d1), 1), + levels, + segCols = [], + i=0; + for (; i") + .append(anchorElement = $("") + .append($("") + .text(event.title))) + .css({ + position: 'absolute', + top: top, + left: left, + zIndex: 8 + }) + .appendTo(head); + setOuterWidth(eventElement, right-left, true); + if (seg.isEnd) { + view.resizableDayEvent(event, eventElement, colWidth); + } + draggableDayEvent(event, eventElement, seg.isStart); + view.reportEventElement(event, eventElement); + levelHeight = Math.max(levelHeight, eventElement.outerHeight(true)); + } + top += levelHeight; + rowHeight += levelHeight; + } + tdInner.height(rowHeight); + updateSize(); // tdInner might have pushed the body down, so resize + } + } + + + // renders events in the 'time slots' at the bottom function renderSlotSegs(segCols) { var colI, colLen=segCols.length, col, levelI, level, segI, seg, - event, start, end, + forward, + event, top, bottom, - tdInner, left, width, + tdInner, + width, left, eventElement, anchorElement, timeElement, titleElement; for (colI=0; colI") .append(anchorElement = $("") .append(titleElement = $("") .text(event.title))) .css({ position: 'absolute', - zIndex: 1000, + zIndex: 8, top: top, left: left - }); + }) + .appendTo(bodyContent); if (event.url) { anchorElement.attr('href', event.url); } @@ -371,7 +473,7 @@ function Agenda(element, options, methods) { // add the time header anchorElement .prepend(timeElement = $("") - .text(formatDates(event.start, event.end, options.agendaEventTimeFormat))) + .text(formatDates(event.start, event.end, view.option('timeFormat')))) }else{ timeElement = null; } @@ -379,420 +481,312 @@ function Agenda(element, options, methods) { eventElement.addClass('fc-corner-bottom'); resizableSlotEvent(event, eventElement, timeElement); } - eventElement.appendTo(panel); setOuterWidth(eventElement, width, true); setOuterHeight(eventElement, bottom-top, true); if (timeElement && eventElement.height() - titleElement.position().top < 10) { - // event title doesn't have enough room, but next to the time - timeElement.text(formatDate(event.start, options.agendaEventTimeFormat) + ' - ' + event.title); + // event title doesn't have enough room, put next to the time + timeElement.text(formatDate(event.start, view.option('timeFormat')) + ' - ' + event.title); titleElement.remove(); } draggableSlotEvent(event, eventElement, timeElement); - reportEventElement(event, eventElement); + view.reportEventElement(event, eventElement); } } } } - - - // renders 'all-day' events at the top - - function renderDaySegs(segRow) { - var td = head.find('td'); - var tdInner = td.find('div div'); - var top = tdInner.position().top, - rowHeight = 0, - i, len=segRow.length, level, - levelHeight, - j, seg, - event, left, right, - eventElement, anchorElement; - for (i=0; i") - .append(anchorElement = $("") - .append($("") - .text(event.title))) - .css({ - position: 'absolute', - top: top, - left: timeWidth + left - }); - if (seg.isStart) { - eventElement.addClass('fc-corner-left'); - } - if (seg.isEnd) { - eventElement.addClass('fc-corner-right'); - } - if (event.url) { - anchorElement.attr('href', event.url); - } - eventElement.appendTo(head); - setOuterWidth(eventElement, right-left, true); - draggableDayEvent(event, eventElement); - //resizableDayEvent(event, eventElement); - reportEventElement(event, eventElement); - levelHeight = Math.max(levelHeight, eventElement.outerHeight(true)); - } - top += levelHeight; - rowHeight += levelHeight; - } - tdInner.height(rowHeight); - //bg.height(element.height()); // tdInner might have pushed the body down, so resize - //updateSize(); - } + - /******************************************* draggable *****************************************/ + /* Event Dragging + -----------------------------------------------------------------------------*/ - // when event starts out IN TIMESLOTS - function draggableSlotEvent(event, eventElement, timeElement) { - var origPosition, origMarginTop, - prevSlotDelta, slotDelta, + // when event starts out FULL-DAY + + function draggableDayEvent(event, eventElement, isStart) { + var origPosition, origWidth, + resetElement, + allDay=true, matrix; eventElement.draggable({ - zIndex: 1001, - scroll: false, - grid: [dayWidth, slotHeight], - axis: dayCnt==1 ? 'y' : false, - cancel: '.ui-resizable-handle', - opacity: .5, - start: function(ev, ui) { - if ($.browser.msie) { - eventElement.find('span.fc-event-bg').hide(); - } + zIndex: 9, + opacity: view.option('month'), // use whatever the month view was using + start: function(ev) { origPosition = eventElement.position(); - origMarginTop = parseInt(eventElement.css('margin-top')) || 0; - prevSlotDelta = 0; - matrix = new HoverMatrix(function(cell) { - if (event.hasTime) { - // event is an original slot-event - if (cell && cell.row == 0) { - // but needs to convert to temporary full-day-event - var topDiff = panel.offset().top - head.offset().top; - eventElement.css('margin-top', origMarginTop + topDiff) - .appendTo(head); - // TODO: bug in IE8 w/ above technique, draggable ends immediately - event.hasTime = false; - if (timeElement) { - timeElement.hide(); - } - eventElement.draggable('option', 'grid', null); - } - }else{ - // event is a temporary full-day-event - if (cell && cell.row == 1) { - // but needs to convert to original slot-event - eventElement.css('margin-top', origMarginTop) - .appendTo(panel); - event.hasTime = true; - if (timeElement) { - timeElement.css('display', ''); // show() was causing display=inline - } - eventElement.draggable('option', 'grid', [dayWidth, slotHeight]); - } + origWidth = eventElement.width(); + resetElement = function() { + if (!allDay) { + eventElement + .width(origWidth) + .height('') + .draggable('option', 'grid', null); + allDay = true; } - if (cell && cell.row == 0) { - showDayOverlay(cell); - }else{ - hideDayOverlay(); + }; + matrix = new HoverMatrix(function(cell) { + eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta); + if (cell) { + if (!cell.row) { // on full-days + resetElement(); + view.showOverlay(cell); + }else{ // mouse is over bottom slots + if (isStart && allDay) { + // convert event to temporary slot-event + setOuterHeight( + eventElement.width(colWidth - 10), // don't use entire width + rowHeight * Math.round( + (event.end ? ((event.end - event.start)/MINUTE_MS) : options.defaultEventMinutes) + /options.slotMinutes) + ); + eventElement.draggable('option', 'grid', [colWidth, 1]); + allDay = false; + } + view.hideOverlay(); + } + }else{ // mouse is outside of everything + view.hideOverlay(); } }); + view.hideEvents(event, eventElement); matrix.row(head.find('td')); bg.find('td').each(function() { matrix.col(this); }); matrix.row(body); - matrix.start(); - hideSimilarEvents(event, eventElement); + matrix.mouse(ev.pageX, ev.pageY); }, drag: function(ev, ui) { - slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight); + matrix.mouse(ev.pageX, ev.pageY); + }, + stop: function(ev, ui) { + view.hideOverlay(); + var cell = matrix.cell, + dayDelta = dis * ( + allDay ? // can't trust cell.colDelta when using slot grid + (cell ? cell.colDelta : 0) : + Math.floor((ui.position.left - origPosition.left) / colWidth) + ); + if (!cell || !dayDelta && !cell.rowDelta) { + // over nothing (has reverted) + resetElement(); + view.showEvents(event, eventElement); + }else{ + view.eventDrop( + this, event, dayDelta, + allDay ? 0 : // minute delta + Math.round((eventElement.offset().top - bodyContent.offset().top) / rowHeight) + * options.slotMinutes + - (event.start.getHours() * 60 + event.start.getMinutes()), + allDay, ev, ui + ); + } + } + }); + } + + + + // when event starts out IN TIMESLOTS + + function draggableSlotEvent(event, eventElement, timeElement) { + var origPosition, + resetElement, + prevSlotDelta, slotDelta, + allDay=false, + matrix; + eventElement.draggable({ + zIndex: 9, + scroll: false, + grid: [colWidth, rowHeight], + axis: colCnt==1 ? 'y' : false, + opacity: view.option('dragOpacity'), + start: function(ev, ui) { + if ($.browser.msie) { + eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide + } + origPosition = eventElement.position(); + resetElement = function() { + // convert back to original slot-event + if (allDay) { + if (timeElement) { + timeElement.css('display', ''); // show() was causing display=inline + } + eventElement.draggable('option', 'grid', [colWidth, rowHeight]); + allDay = false; + } + }; + prevSlotDelta = 0; + matrix = new HoverMatrix(function(cell) { + eventElement.draggable('option', 'revert', !cell); + if (cell) { + if (!cell.row && options.allDayHeader) { // over full days + if (!allDay) { + // convert to temporary all-day event + allDay = true; + if (timeElement) { + timeElement.hide(); + } + eventElement.draggable('option', 'grid', null); + } + view.showOverlay(cell); + }else{ // on slots + resetElement(); + view.hideOverlay(); + } + }else{ + view.hideOverlay(); + } + }); + if (options.allDayHeader) { + matrix.row(head.find('td')); + } + bg.find('td').each(function() { + matrix.col(this); + }); + matrix.row(body); + matrix.mouse(ev.pageX, ev.pageY); + view.hideEvents(event, eventElement); + }, + drag: function(ev, ui) { + slotDelta = Math.round((ui.position.top - origPosition.top) / rowHeight); if (slotDelta != prevSlotDelta) { - if (timeElement && event.hasTime) { + if (timeElement && !allDay) { // update time header - var newStart = addMinutes(cloneDate(event.start), slotDelta * options.slotMinutes), + var minuteDelta = slotDelta*options.slotMinutes, + newStart = addMinutes(cloneDate(event.start), minuteDelta), newEnd; if (event.end) { - newEnd = addMinutes(cloneDate(event.end), slotDelta * options.slotMinutes); + newEnd = addMinutes(cloneDate(event.end), minuteDelta); } - timeElement.text(formatDates(newStart, newEnd, options.agendaEventTimeFormat)); + timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat'))); } prevSlotDelta = slotDelta; } matrix.mouse(ev.pageX, ev.pageY); }, stop: function(ev, ui) { - if (event.hasTime) { - if (matrix.cell) { - // over slots - var dayDelta = Math.round((ui.position.left - origPosition.left) / dayWidth); - reportEventMove(event, dayDelta, true, slotDelta * options.slotMinutes); + view.hideOverlay(); + var cell = matrix.cell, + dayDelta = dis * ( + allDay ? // can't trust cell.colDelta when using slot grid + (cell ? cell.colDelta : 0) : + Math.floor((ui.position.left - origPosition.left) / colWidth) + ); + if (!cell || !slotDelta && !dayDelta) { + resetElement(); + if ($.browser.msie) { + eventElement + .css('filter', '') // clear IE opacity side-effects + .find('span.fc-event-bg').css('display', ''); // .show() made display=inline } + eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position + view.showEvents(event, eventElement); }else{ - // over full-days - if (!matrix.cell) { - // was being dragged over full-days, but finished over nothing, reset - event.hasTime = true; - }else{ - event.end = null; - reportEventMove(event, matrix.cell.colDelta); - } + view.eventDrop( + this, event, dayDelta, + allDay ? 0 : slotDelta * options.slotMinutes, // minute delta + allDay, ev, ui + ); } - hideDayOverlay(); - rerenderEvents(); } }); } - // when event starts out FULL-DAY - - function draggableDayEvent(event, eventElement) { - var origWidth, matrix; - eventElement.draggable({ - zIndex: 1001, - start: function() { - origWidth = eventElement.width(); - matrix = new HoverMatrix(function(cell) { - if (!cell) { - // mouse is outside of everything - hideDayOverlay(); - }else{ - if (cell.row == 0) { - // on full-days - if (event.hasTime) { - // and needs to be original full-day event - eventElement - .width(origWidth) - .height('') - .draggable('option', 'grid', null); - event.hasTime = false; - } - showDayOverlay(cell); - }else{ - // mouse is over bottom slots - if (!event.hasTime) { - // convert event to temporary slot-event - //if (+cloneDate(event.start, true) == +cloneDate(event._end, true)) { - // only change styles if a 1-day event - eventElement - .width(dayWidth - 10) // don't use entire width - .height(slotHeight * Math.round(options.defaultEventMinutes/options.slotMinutes) - 2); - //} - eventElement.draggable('option', 'grid', [dayWidth, 1]); - event.hasTime = true; - } - hideDayOverlay(); - } - } - }); - matrix.row(head.find('td')); - bg.find('td').each(function() { - matrix.col(this); - }); - matrix.row(body); - matrix.start(); - hideSimilarEvents(event, eventElement); - }, - drag: function(ev, ui) { - matrix.mouse(ev.pageX, ev.pageY); - }, - stop: function() { - var cell = matrix.cell; - if (!cell) { - // over nothing - if (event.hasTime) { - // event was on the slots before going out, convert back - event.hasTime = false; - } - }else{ - if (!event.hasTime) { - // event has been dropped on a full-day - reportEventMove(event, cell.colDelta); - }else{ - // event has been dropped on the slots - var slots = Math.floor((eventElement.offset().top - panel.offset().top) / slotHeight); - event.end = null; - reportEventMove(event, cell.colDelta, false, slots * options.slotMinutes); - } - } - hideDayOverlay(); - rerenderEvents(); - } - }); - } + /* Event Resizing + -----------------------------------------------------------------------------*/ - /************************************* resizable **************************************/ - + // for TIMESLOT events function resizableSlotEvent(event, eventElement, timeElement) { - var prevSlotDelta, slotDelta, newEnd; + var slotDelta, prevSlotDelta; eventElement .resizable({ handles: 's', - grid: [0, slotHeight], + grid: rowHeight, start: function() { - prevSlotDelta = 0; - hideSimilarEvents(event, eventElement); + slotDelta = prevSlotDelta = 0; + view.hideEvents(event, eventElement); if ($.browser.msie && $.browser.version == '6.0') { eventElement.css('overflow', 'hidden'); } + eventElement.css('z-index', 9); }, resize: function(ev, ui) { - slotDelta = Math.round((Math.max(slotHeight, ui.size.height) - ui.originalSize.height) / slotHeight); + // don't rely on ui.size.height, doesn't take grid into account + slotDelta = Math.round((Math.max(rowHeight, eventElement.height()) - ui.originalSize.height) / rowHeight); if (slotDelta != prevSlotDelta) { - newEnd = addMinutes(cloneDate(event._end), options.slotMinutes * slotDelta); if (timeElement) { - timeElement.text(formatDates(event.start, newEnd, options.agendaEventTimeFormat)); + timeElement.text( + formatDates( + event.start, + (!slotDelta && !event.end) ? null : // no change, so don't display time range + addMinutes(view.eventEnd(event), options.slotMinutes*slotDelta), + view.option('timeFormat') + ) + ); } prevSlotDelta = slotDelta; } }, stop: function(ev, ui) { - reportEventResize(event, 0, true, options.slotMinutes * slotDelta); - rerenderEvents(); + if (slotDelta) { + view.eventResize(this, event, 0, options.slotMinutes*slotDelta, ev, ui); + }else{ + eventElement.css('z-index', 8); + view.showEvents(event, eventElement); + // BUG: if event was really short, need to put title back in span + } } }) .find('div.ui-resizable-s').text('='); } - function resizableDayEvent(event, eventElement) { - eventElement.resizable({ - handles: 'e', - grid: [dayWidth, 0], - start: function() { - hideSimilarEvents(event, eventElement); - }, - stop: function(ev, ui) { - var dayDelta = Math.round((Math.max(dayWidth, ui.size.width) - ui.originalSize.width) / dayWidth); - reportEventResize(event, dayDelta); - rerenderEvents(); - } - }); - } + // ALL-DAY event resizing w/ 'view' methods... - /**************************************** misc **************************************/ - // get the Y coordinate of the given time on the given day + /* Misc + -----------------------------------------------------------------------------*/ - function timeCoord(day, time) { - var nextDay = addDays(cloneDate(day), 1); - if (time < nextDay) { - var slotMinutes = options.slotMinutes; - var minutes = time.getHours()*60 + time.getMinutes(); - var slotI = Math.floor(minutes / slotMinutes); - var td = body.find('tr:eq(' + slotI + ') td'); - return Math.round(td.position().top + slotHeight * ((minutes % slotMinutes) / slotMinutes)); - }else{ - return panel.height(); + // get the Y coordinate of the given time on the given day (both Date objects) + + function timePosition(day, time) { + if (time > day && time.getDay() != day.getDay()) { + return bodyContent.height(); } + var slotMinutes = options.slotMinutes, + minutes = time.getHours()*60 + time.getMinutes(), + slotI = Math.floor(minutes / slotMinutes), + innerDiv = body.find('tr:eq(' + slotI + ') td div'); + return Math.max(0, Math.round(innerDiv.position().top - 1 + rowHeight * ((minutes % slotMinutes) / slotMinutes))); } } -function compileSlotSegs(events, start, end) { +// count the number of colliding, higher-level segments (for event squishing) - // slice by day - var segCols = [], - d1 = cloneDate(start), - d2 = addDays(cloneDate(start), 1); - for (; d10; levelI--) { - level = segLevels[levelI]; - for (segI=0; segI0; i--) { level = levels[i]; for (j=0; j") .append(event.allDay || !seg.isStart ? null : $("") - .html(formatDates(event.start, event.end, options.timeFormat, options))) + .html(formatDates(event.start, event.end, view.option('timeFormat'), options))) .append($("") .text(event.title))); if (event.url) { @@ -430,23 +431,23 @@ function Grid(element, options, methods) { .css({ position: 'absolute', top: top, - left: left1 + (rtlLeftDiff||0), - zIndex: 2 + left: left + (rtlLeftDiff||0), + zIndex: 8 }) .appendTo(element); - setOuterWidth(eventElement, left2-left1, true); + setOuterWidth(eventElement, right-left, true); if (rtl && rtlLeftDiff == undefined) { // bug in IE6 where offsets are miscalculated with direction:rtl - rtlLeftDiff = left1 - eventElement.position().left; + rtlLeftDiff = left - eventElement.position().left; if (rtlLeftDiff) { - eventElement.css('left', left1 + rtlLeftDiff); + eventElement.css('left', left + rtlLeftDiff); } } eventElementHandlers(event, eventElement); if (event.editable || event.editable == undefined && options.editable) { draggableEvent(event, eventElement); if (seg.isEnd) { - resizableEvent(event, eventElement); + view.resizableDayEvent(event, eventElement, colWidth); } } view.reportEventElement(event, eventElement); @@ -479,7 +480,7 @@ function Grid(element, options, methods) { - /* Draggable + /* Event Dragging -----------------------------------------------------------------------------*/ @@ -487,9 +488,9 @@ function Grid(element, options, methods) { if (!options.disableDragging && eventElement.draggable) { var matrix; eventElement.draggable({ - zIndex: 3, + zIndex: 9, delay: 50, - opacity: options.dragOpacity, + opacity: view.option('dragOpacity'), revertDuration: options.dragRevertDuration, start: function(ev, ui) { matrix = new HoverMatrix(function(cell) { @@ -518,20 +519,17 @@ function Grid(element, options, methods) { matrix.mouse(ev.pageX, ev.pageY); }, stop: function(ev, ui) { + if ($.browser.msie) { + eventElement.css('filter', ''); // clear IE opacity side-effects + } 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(); + view.eventDrop(this, event, cell.rowDelta*7+cell.colDelta*dis, 0, event.allDay, ev, ui); } } }); @@ -539,42 +537,7 @@ function Grid(element, options, methods) { } - - /* Resizable - -----------------------------------------------------------------------------*/ - - - function resizableEvent(event, eventElement) { - if (!options.disableResizing && eventElement.resizable) { - eventElement.resizable({ - handles: rtl ? 'w' : 'e', - grid: colWidth, - minWidth: colWidth/2, // need this or else IE throws errors when too small - 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); - } - }); - } - } + // event resizing w/ 'view' methods... }; diff --git a/src/main.js b/src/main.js index 26dc2a8..896d788 100755 --- a/src/main.js +++ b/src/main.js @@ -30,7 +30,6 @@ var defaults = { cacheParam: '_', // time formats - timeFormat: 'h(:mm)t', // for events titleFormat: { month: 'MMMM yyyy', week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}", @@ -41,6 +40,9 @@ var defaults = { week: 'ddd M/d', day: 'dddd M/d' }, + timeFormat: { // for event elements + '': 'h(:mm)t' // default + }, // locale isRTL: false, @@ -166,7 +168,7 @@ $.fn.fullCalendar = function(options) { function changeView(v) { if (v != viewName) { - lockContentSize(); + fixContentSize(); if (view) { if (view.eventsChanged) { eventsDirtyExcept(view); @@ -188,14 +190,14 @@ $.fn.fullCalendar = function(options) { } view.name = viewName = v; render(); - unlockContentSize(); + unfixContentSize(); } } function render(inc) { if (_element.offsetWidth !== 0) { // visible on the screen if (inc || !view.date || +view.date != +date) { // !view.date means it hasn't been rendered yet - ignoreWindowResizes = true; + fixContentSize(); 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) { @@ -204,7 +206,7 @@ $.fn.fullCalendar = function(options) { callback(events); // no refetching } }); - ignoreWindowResizes = false; + unfixContentSize(); view.date = cloneDate(date); if (header) { // enable/disable 'today' button @@ -620,35 +622,47 @@ $.fn.fullCalendar = function(options) { /* Resizing -----------------------------------------------------------------------------*/ - function lockContentSize() { - content.css({ - overflow: 'hidden', - height: Math.round(content.width() / options.aspectRatio) - }); - } - - function unlockContentSize() { - content.css({ - overflow: '', - height: ($.browser.msie && $.browser.version == '6.0') ? 1 : '' - }); - } - var elementWidth, - ignoreWindowResizes = false, + contentSizeFixed = false, resizeCnt = 0; + function fixContentSize() { + if (!contentSizeFixed) { + contentSizeFixed = true; + content.css({ + overflow: 'hidden', + height: Math.round(content.width() / options.aspectRatio) + }); + } + } + + function unfixContentSize() { + if (contentSizeFixed) { + content.css({ + overflow: 'visible', + height: '' + }); + if ($.browser.msie && ($.browser.version=='6.0' || $.browser.version=='7.0')) { + // in IE6/7 the inside of the content div was invisible + // bizarre hack to get this work... need both lines + content[0].clientHeight; + content.hide().show(); + } + contentSizeFixed = false; + } + } + $(window).resize(function() { - if (!ignoreWindowResizes && view.date) { // view.date means the view has been rendered + if (!contentSizeFixed && view.date) { // view.date means the view has been rendered var rcnt = ++resizeCnt; // add a delay setTimeout(function() { - if (rcnt == resizeCnt && !ignoreWindowResizes) { + if (rcnt == resizeCnt && !contentSizeFixed) { var newWidth = element.width(); if (newWidth != elementWidth) { elementWidth = newWidth; - lockContentSize(); + fixContentSize(); view.updateSize(); - unlockContentSize(); + unfixContentSize(); view.rerenderEvents(true); sizesDirtyExcept(view); view.trigger('windowResize', _element); @@ -686,7 +700,7 @@ function normalizeEvent(event, options) { } event._start = cloneDate(event.start = parseDate(event.start)); event.end = parseDate(event.end); - if (event.end && event.end < event.start) { + if (event.end && event.end <= event.start) { event.end = null; } event._end = event.end ? cloneDate(event.end) : null; diff --git a/src/util.js b/src/util.js index c3e6cfc..2e1f8d5 100755 --- a/src/util.js +++ b/src/util.js @@ -3,7 +3,8 @@ -----------------------------------------------------------------------------*/ var DAY_MS = 86400000, - HOUR_MS = 3600000; + HOUR_MS = 3600000, + MINUTE_MS = 60000; function addYears(d, n, keepTime) { d.setFullYear(d.getFullYear() + n); @@ -340,7 +341,3 @@ 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 e28b56d..7b79070 100755 --- a/src/view.js +++ b/src/view.js @@ -1,264 +1,346 @@ - -/* 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(events) - * - rerenderEvents() - * - * - * z-index reservations: - * 1. day-overlay - * 2. events - * 3. dragging/resizing events - * - */ - - - - init: function(element, options) { - this.element = element; - this.options = options; - this.cachedEvents = []; - this.eventsByID = {}; - this.eventElements = []; - this.eventElementsByID = {}; - }, - - - - // triggers an event handler, always append view as last arg - - trigger: function(name, thisObj) { - if (this.options[name]) { - return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this])); - } - }, - - - - // returns a Date object for an event's end - - eventEnd: function(event) { - return event.end || this.defaultEventEnd(event); - }, - - - - // report when view receives new events - - reportEvents: function(events) { // events are already normalized at this point - var i, len=events.length, event, - eventsByID = this.eventsByID = {}, - cachedEvents = this.cachedEvents = []; - for (i=0; i