Author: blabno
Date: 2011-11-22 01:22:20 -0500 (Tue, 22 Nov 2011)
New Revision: 22967
Modified:
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/Bean.java
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/ScheduleTask.java
sandbox/trunk/ui/schedule/demo/src/main/webapp/pages/schedule.xhtml
sandbox/trunk/ui/schedule/ui/pom.xml
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractSchedule.java
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractScheduleItem.java
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/renderkit/ScheduleRendererBase.java
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.css
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.js
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/gcal.js
Log:
Upgraded fullcalendar plugin to 1.5.2 and introduced new attributes to scheduleItem:
color,backgroundColor,borderColor,textColor.
Modified: sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/Bean.java
===================================================================
---
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/Bean.java 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/Bean.java 2011-11-22
06:22:20 UTC (rev 22967)
@@ -123,6 +123,7 @@
public Bean() {
Calendar instance = Calendar.getInstance();
instance.setTime(getInitialDate());
+ final String[] colors = new String[]{"#00ff00", "#ff0000",
"#0000ff"};
Random random = new Random();
for (int i = -30; i < 60; i++) {
instance.set(Calendar.HOUR, minTime + random.nextInt(maxTime - minTime));
@@ -131,7 +132,8 @@
Map<String, Object> data = new HashMap<String, Object>();
data.put("category", "category-" + (i % 3));
int taskId = taskIdSequence++;
- allTasks.add(new ScheduleTask("" + taskId, "Title " +
taskId, instance.getTime(), instance.getTime(), data));
+
+ allTasks.add(new ScheduleTask("" + taskId, "Title " +
taskId, instance.getTime(), instance.getTime(), data, colors[random.nextInt(3)]));
}
}
Modified:
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/ScheduleTask.java
===================================================================
---
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/ScheduleTask.java 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/demo/src/main/java/org/richfaces/schedule/ScheduleTask.java 2011-11-22
06:22:20 UTC (rev 22967)
@@ -17,6 +17,7 @@
private Boolean editable;
private String url;
private String details;
+ private String color;
public ScheduleTask() {
}
@@ -36,12 +37,13 @@
this.allDay = allDay;
}
- public ScheduleTask(String id, String title, Date start, Date end, Map<String,
Object> data) {
+ public ScheduleTask(String id, String title, Date start, Date end, Map<String,
Object> data, String color) {
this.id = id;
this.title = title;
this.startDate = start;
this.endDate = end;
this.data = data;
+ this.color = color;
}
public String getId() {
@@ -138,4 +140,12 @@
public void setDetails(String details) {
this.details = details;
}
+
+ public String getColor() {
+ return color;
+ }
+
+ public void setColor(String color) {
+ this.color = color;
+ }
}
Modified: sandbox/trunk/ui/schedule/demo/src/main/webapp/pages/schedule.xhtml
===================================================================
--- sandbox/trunk/ui/schedule/demo/src/main/webapp/pages/schedule.xhtml 2011-11-22
06:11:32 UTC (rev 22966)
+++ sandbox/trunk/ui/schedule/demo/src/main/webapp/pages/schedule.xhtml 2011-11-22
06:22:20 UTC (rev 22967)
@@ -1,7 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
-
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:schedule="http://richfaces.org/sandbox/schedule">
+<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
+
xmlns:f="http://java.sun.com/jsf/core"
+
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
+
xmlns:schedule="http://richfaces.org/sandbox/schedule">
<head>
<title></title>
</head>
@@ -9,32 +11,52 @@
<!-- mode should passed to this template as param -->
<ui:composition>
<h:form id="f">
- <schedule:schedule id="schedule" widgetVar="schedule"
switchType="#{mode}" value="#{myBean.lazyDataModel}"
var="event" weekMode="#{myBean.weekMode}"
- height="#{myBean.height}"
date="#{myBean.initialDate}" firstDay="#{myBean.firstDay}"
showWeekends="#{myBean.showWeekends}"
-
- allDayText="#{myBean.allDayText}"
allDayByDefault="#{myBean.allDayByDefault}"
allDaySlot="#{myBean.allDaySlot}"
- aspectRatio="#{myBean.aspectRatio}"
axisFormat="#{myBean.axisFormat}"
contentHeight="#{myBean.contentHeight}"
+ <schedule:schedule id="schedule" widgetVar="schedule"
switchType="#{mode}" value="#{myBean.lazyDataModel}"
+ var="event" weekMode="#{myBean.weekMode}"
+ height="#{myBean.height}"
date="#{myBean.initialDate}" firstDay="#{myBean.firstDay}"
+ showWeekends="#{myBean.showWeekends}"
+ eventColor="#00ff00"
eventBorderColor="#ffff00" eventBackgroundColor="#ff00ff"
+ eventTextColor="#ff55ff"
+ allDayText="#{myBean.allDayText}"
allDayByDefault="#{myBean.allDayByDefault}"
+ allDaySlot="#{myBean.allDaySlot}"
+ aspectRatio="#{myBean.aspectRatio}"
axisFormat="#{myBean.axisFormat}"
+ contentHeight="#{myBean.contentHeight}"
defaultEventMinutes="#{myBean.defaultEventMinutes}"
dragOpacity="#{myBean.dragOpacity}"
- dragRevertDuration="#{myBean.dragRevertDuration}"
editable="#{myBean.editable}" selectable="#{myBean.selectable}"
- selectHelper="#{myBean.selectHelper}"
unselectAuto="#{myBean.unselectAuto}"
unselectCancel="#{myBean.unselectCancel}"
- firstHour="#{myBean.firstHour}"
headerCenter="#{myBean.headerCenter}"
headerLeft="#{myBean.headerLeft}"
- headerRight="#{myBean.headerRight}"
isRTL="#{myBean.isRTL}" maxTime="#{myBean.maxTime}"
minTime="#{myBean.minTime}"
+ dragRevertDuration="#{myBean.dragRevertDuration}"
editable="#{myBean.editable}"
+ selectable="#{myBean.selectable}"
+ selectHelper="#{myBean.selectHelper}"
unselectAuto="#{myBean.unselectAuto}"
+ unselectCancel="#{myBean.unselectCancel}"
+ firstHour="#{myBean.firstHour}"
headerCenter="#{myBean.headerCenter}"
+ headerLeft="#{myBean.headerLeft}"
+ headerRight="#{myBean.headerRight}"
isRTL="#{myBean.isRTL}" maxTime="#{myBean.maxTime}"
+ minTime="#{myBean.minTime}"
slotMinutes="#{myBean.slotMinutes}"
view="#{myBean.view}"
- columnFormat="#{myBean.columnFormat}"
titleFormat="#{myBean.titleFormat}" timeFormat="#{myBean.timeFormat}"
+ columnFormat="#{myBean.columnFormat}"
titleFormat="#{myBean.titleFormat}"
+ timeFormat="#{myBean.timeFormat}"
- itemResizeListener="#{myBean.taskResized}"
itemMoveListener="#{myBean.taskMoved}"
itemSelectListener="#{myBean.taskSelected}"
- viewChangeListener="#{myBean.viewChanged}"
dateRangeChangeListener="#{myBean.dateRangeChanged}"
-
dateRangeSelectListener="#{myBean.dateRangeSelected}"
dateSelectListener="#{myBean.dateSelected}"
+ itemResizeListener="#{myBean.taskResized}"
itemMoveListener="#{myBean.taskMoved}"
+ itemSelectListener="#{myBean.taskSelected}"
+ viewChangeListener="#{myBean.viewChanged}"
+
dateRangeChangeListener="#{myBean.dateRangeChanged}"
+
dateRangeSelectListener="#{myBean.dateRangeSelected}"
+ dateSelectListener="#{myBean.dateSelected}"
ondaterangeselect="#{rich:component('schedule')}.unselect()">
- <schedule:scheduleItem eventId="#{event.id}"
startDate="#{event.startDate}" title="#{event.title}"
endDate="#{event.endDate}"
- allDay="#{event.allDay}"
styleClass="#{event.id == 1 ? 'first' : null}"
data="#{event.data}"/>
- <schedule:scheduleMonthView weekMode="#{myBean.weekMode}"
titleFormat="MM yy" timeFormat="h:m" columnFormat="dddd"
dragOpacity=".1"/>
- <schedule:scheduleAgendaDayView titleFormat="d MMM yy"
timeFormat="hh:m" columnFormat="ddd" dragOpacity=".3"/>
- <schedule:scheduleBasicDayView titleFormat="dd MMM yy"
timeFormat="hh:mm" columnFormat="aaa ddd"
dragOpacity=".5"/>
- <schedule:scheduleAgendaWeekView titleFormat="dd MMM yy"
timeFormat="hh:mm" columnFormat="aaa ddd"
dragOpacity=".7"/>
- <schedule:scheduleBasicWeekView titleFormat="bw dd MMM yy"
timeFormat="bw hh:mm" columnFormat="bw ddd"
dragOpacity=".9"/>
+ <schedule:scheduleItem eventId="#{event.id}"
startDate="#{event.startDate}" title="#{event.title}"
+ endDate="#{event.endDate}"
+ allDay="#{event.allDay}"
styleClass="#{event.id == 1 ? 'first' : null}"
+ data="#{event.data}"
color="#{event.color}"/>
+ <schedule:scheduleMonthView weekMode="#{myBean.weekMode}"
titleFormat="MM yy" timeFormat="h:m"
+ columnFormat="dddd"
dragOpacity=".1"/>
+ <schedule:scheduleAgendaDayView titleFormat="d MMM yy"
timeFormat="hh:m" columnFormat="ddd"
+ dragOpacity=".3"/>
+ <schedule:scheduleBasicDayView titleFormat="dd MMM yy"
timeFormat="hh:mm" columnFormat="aaa ddd"
+ dragOpacity=".5"/>
+ <schedule:scheduleAgendaWeekView titleFormat="dd MMM yy"
timeFormat="hh:mm" columnFormat="aaa ddd"
+ dragOpacity=".7"/>
+ <schedule:scheduleBasicWeekView titleFormat="bw dd MMM yy"
timeFormat="bw hh:mm" columnFormat="bw ddd"
+ dragOpacity=".9"/>
<!--
TODO uncomment this
<schedule:itemSelectedListener
binding="#{myBean.additionalListener}"/>
Modified: sandbox/trunk/ui/schedule/ui/pom.xml
===================================================================
--- sandbox/trunk/ui/schedule/ui/pom.xml 2011-11-22 06:11:32 UTC (rev 22966)
+++ sandbox/trunk/ui/schedule/ui/pom.xml 2011-11-22 06:22:20 UTC (rev 22967)
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
+<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
+
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -33,6 +34,7 @@
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>provided</scope>
+ <version>1.2</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
Modified:
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractSchedule.java
===================================================================
---
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractSchedule.java 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractSchedule.java 2011-11-22
06:22:20 UTC (rev 22967)
@@ -597,9 +597,9 @@
};
((ExtendedDataModel) dataModel).walk(getFacesContext(), visitor, new
DateRange(startDate, endDate), null);
}
- ELContext elContext = getFacesContext().getELContext();
+ ELContext elContext = (ELContext) getFacesContext().getELContext();
ValueExpression valueExpression =
getFacesContext().getApplication().getExpressionFactory()
- .createValueExpression(getFacesContext().getELContext(), "#{" +
getVar() + "}", Object.class);
+ .createValueExpression(elContext, "#{" + getVar() + "}",
Object.class);
List<Map<String, Object>> data = new ArrayList<Map<String,
Object>>();
for (int i = 0; i < dataModel.getRowCount(); i++) {
dataModel.setRowIndex(i);
@@ -633,6 +633,18 @@
if (item.getData() != null) {
firstDataElement.put("data", item.getData());
}
+ if (item.getColor() != null) {
+ firstDataElement.put("color", item.getColor());
+ }
+ if (item.getBackgroundColor() != null) {
+ firstDataElement.put("backgroundColor",
item.getBackgroundColor());
+ }
+ if (item.getBorderColor() != null) {
+ firstDataElement.put("borderColor",
item.getBorderColor());
+ }
+ if (item.getTextColor() != null) {
+ firstDataElement.put("textColor",
item.getTextColor());
+ }
data.add(firstDataElement);
}
}
@@ -715,6 +727,18 @@
@Attribute(defaultValue = "" + DEFAULT_UNSELECT_AUTO)
public abstract Boolean isUnselectAuto();
+ @Attribute
+ public abstract String getEventColor();
+
+ @Attribute
+ public abstract String getEventBackgroundColor();
+
+ @Attribute
+ public abstract String getEventBorderColor();
+
+ @Attribute
+ public abstract String getEventTextColor();
+
public void setDataModel(DataModel model) {
this.model = model;
}
Modified:
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractScheduleItem.java
===================================================================
---
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractScheduleItem.java 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/component/AbstractScheduleItem.java 2011-11-22
06:22:20 UTC (rev 22967)
@@ -66,4 +66,17 @@
@Attribute
public abstract Object getData();
+
+ @Attribute
+ public abstract String getColor();
+
+ @Attribute
+ public abstract String getBackgroundColor();
+
+ @Attribute
+ public abstract String getBorderColor();
+
+ @Attribute
+ public abstract String getTextColor();
+
}
Modified:
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/renderkit/ScheduleRendererBase.java
===================================================================
---
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/renderkit/ScheduleRendererBase.java 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/ui/src/main/java/org/richfaces/renderkit/ScheduleRendererBase.java 2011-11-22
06:22:20 UTC (rev 22967)
@@ -102,6 +102,10 @@
*/
static {
Map<String, Object> defaults = new HashMap<String, Object>();
+ defaults.put("eventColor", "");
+ defaults.put("eventBackgroundColor", "");
+ defaults.put("eventBorderColor", "");
+ defaults.put("eventTextColor", "");
defaults.put("styleClass", "");
defaults.put("defaultView", AbstractSchedule.DEFAULT_VIEW);
defaults.put("firstDay", AbstractSchedule.DEFAULT_FIRST_DAY - 1);
@@ -332,6 +336,10 @@
addOptionIfSetAndNotDefault("disableDragging",
schedule.isDisableDragging(), options);
addOptionIfSetAndNotDefault("disableResizing",
schedule.isDisableResizing(), options);
addOptionIfSetAndNotDefault("dragRevertDuration",
schedule.getDragRevertDuration(), options);
+ addOptionIfSetAndNotDefault("eventColor", schedule.getEventColor(),
options);
+ addOptionIfSetAndNotDefault("eventBackgroundColor",
schedule.getEventBackgroundColor(), options);
+ addOptionIfSetAndNotDefault("eventBorderColor",
schedule.getEventBorderColor(), options);
+ addOptionIfSetAndNotDefault("eventTextColor",
schedule.getEventTextColor(), options);
addOptionHash("dragOpacity", schedule, options);
addOptionHash("titleFormat", schedule, options);
addOptionHash("timeFormat", schedule, options);
Modified:
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.css
===================================================================
---
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.css 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.css 2011-11-22
06:22:20 UTC (rev 22967)
@@ -1,21 +1,14 @@
/*
- * FullCalendar v1.4.5 Stylesheet
+ * FullCalendar v1.5.2 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.
+ * Copyright (c) 2011 Adam Shaw
+ * Dual licensed under the MIT and GPL licenses, located in
+ * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
- * Date: Sun Feb 21 20:30:11 2010 -0800
+ * Date: Sun Aug 21 22:06:09 2011 -0700
*
- * TODO adjust class names to richfaces naming conventions
*/
-.fc,
-.fc .fc-header,
-.fc .fc-content {
- font-size: 1em;
-}
-
.fc {
direction: ltr;
text-align: left;
@@ -26,7 +19,13 @@
border-spacing: 0;
}
-.fc td, .fc th {
+html .fc,
+.fc table {
+ font-size: 1em;
+}
+
+.fc td,
+.fc th {
padding: 0;
vertical-align: top;
}
@@ -34,266 +33,274 @@
/* Header
------------------------------------------------------------------------*/
-table.fc-header {
- width: 100%;
+.fc-header td {
+ white-space: nowrap;
}
.fc-header-left {
width: 25%;
+ text-align: left;
}
-.fc-header-left table {
- float: left;
-}
-
.fc-header-center {
- width: 50%;
text-align: center;
}
-.fc-header-center table {
- margin: 0 auto;
-}
-
.fc-header-right {
width: 25%;
+ text-align: right;
}
-.fc-header-right table {
- float: right;
+.fc-header-title {
+ display: inline-block;
+ vertical-align: top;
}
-.fc-header-title {
+.fc-header-title h2 {
margin-top: 0;
white-space: nowrap;
}
-.fc-header-space {
+.fc .fc-header-space {
padding-left: 10px;
}
-/* right-to-left */
-
-.fc-rtl .fc-header-title {
- direction: rtl;
+.fc-header .fc-button {
+ margin-bottom: 1em;
+ vertical-align: top;
}
-/* Buttons
-------------------------------------------------------------------------*/
+/* buttons edges butting together */
-.fc-header .fc-state-default,
-.fc-header .ui-state-default {
- margin-bottom: 1em;
- cursor: pointer;
+.fc-header .fc-button {
+ margin-right: -1px;
}
-.fc-header .fc-state-default {
- border-width: 1px 0;
- padding: 0 1px;
+.fc-header .fc-corner-right {
+ margin-right: 1px; /* back to normal */
}
-.fc-header .fc-state-default,
-.fc-header .fc-state-default a {
- border-style: solid;
+.fc-header .ui-corner-right {
+ margin-right: 0; /* back to normal */
}
-.fc-header .fc-state-default a {
- display: block;
- border-width: 0 1px;
- margin: 0 -1px;
- width: 100%;
- text-decoration: none;
+/* button layering (for border precedence) */
+
+.fc-header .fc-state-hover,
+.fc-header .ui-state-hover {
+ z-index: 2;
}
-.fc-header .fc-state-default span {
- display: block;
- border-style: solid;
- border-width: 1px 0 1px 1px;
- padding: 3px 5px;
+.fc-header .fc-state-down {
+ z-index: 3;
}
-.fc-header .ui-state-default {
- padding: 4px 6px;
+.fc-header .fc-state-active,
+.fc-header .ui-state-active {
+ z-index: 4;
}
-.fc-header .fc-state-default span,
-.fc-header .ui-state-default span {
- white-space: nowrap;
+/* Content
+------------------------------------------------------------------------*/
+
+.fc-content {
+ clear: both;
}
-/* for adjacent buttons */
+.fc-view {
+ width: 100%; /* needed for view switching (when view is absolute) */
+ overflow: hidden;
+}
-.fc-header .fc-no-right {
- padding-right: 0;
+/* Cell Styles
+------------------------------------------------------------------------*/
+
+.fc-widget-header, /* <th>, usually */
+.fc-widget-content {
+ /* <td>, usually */
+ border: 1px solid #ccc;
}
-.fc-header .fc-no-right a {
- margin-right: 0;
- border-right: 0;
+.fc-state-highlight {
+ /* <td> today cell */
+ /* TODO: add .fc-today to <th> */
+ background: #ffc;
}
-.fc-header .ui-no-right {
- border-right: 0;
+.fc-cell-overlay {
+ /* semi-transparent rectangle while dragging */
+ background: #9cf;
+ opacity: .2;
+ filter: alpha(opacity = 20); /* for IE */
}
-/* for fake rounded corners */
+/* Buttons
+------------------------------------------------------------------------*/
-.fc-header .fc-corner-left {
- margin-left: 1px;
- padding-left: 0;
+.fc-button {
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
}
-.fc-header .fc-corner-right {
- margin-right: 1px;
- padding-right: 0;
+.fc-state-default {
+ /* non-theme */
+ border-style: solid;
+ border-width: 1px 0;
}
-/* DEFAULT button COLORS */
+.fc-button-inner {
+ position: relative;
+ float: left;
+ overflow: hidden;
+}
-.fc-header .fc-state-default,
-.fc-header .fc-state-default a {
- border-color: #777; /* outer border */
- color: #333;
+.fc-state-default .fc-button-inner {
+ /* non-theme */
+ border-style: solid;
+ border-width: 0 1px;
}
-.fc-header .fc-state-default span {
- border-color: #fff #fff #d1d1d1; /* inner border */
- background: #e8e8e8;
+.fc-button-content {
+ position: relative;
+ float: left;
+ height: 1.9em;
+ line-height: 1.9em;
+ padding: 0 .6em;
+ white-space: nowrap;
}
-/* PRESSED button COLORS (down and active) */
+/* icon (for jquery ui) */
-.fc-header .fc-state-active a {
- color: #fff;
+.fc-button-content .fc-icon-wrap {
+ position: relative;
+ float: left;
+ top: 50%;
}
-.fc-header .fc-state-down span,
-.fc-header .fc-state-active span {
- background: #888;
- border-color: #808080 #808080 #909090; /* inner border */
+.fc-button-content .ui-icon {
+ position: relative;
+ float: left;
+ margin-top: -50%;
+ *margin-top: 0;
+ *top: -50%;
}
-/* DISABLED button COLORS */
+/* gloss effect */
-.fc-header .fc-state-disabled a {
- color: #999;
+.fc-state-default .fc-button-effect {
+ position: absolute;
+ top: 50%;
+ left: 0;
}
-.fc-header .fc-state-disabled,
-.fc-header .fc-state-disabled a {
- border-color: #ccc; /* outer border */
+.fc-state-default .fc-button-effect span {
+ position: absolute;
+ top: -100px;
+ left: 0;
+ width: 500px;
+ height: 100px;
+ border-width: 100px 0 0 1px;
+ border-style: solid;
+ border-color: #fff;
+ background: #444;
+ opacity: .09;
+ filter: alpha(opacity = 9);
}
-.fc-header .fc-state-disabled span {
- border-color: #fff #fff #f0f0f0; /* inner border */
- background: #f0f0f0;
-}
+/* button states (determines colors) */
-/* Content Area & Global Cell Styles
-------------------------------------------------------------------------*/
-
-.fc-widget-content {
- border: 1px solid #ccc; /* outer border color */
+.fc-state-default,
+.fc-state-default .fc-button-inner {
+ border-style: solid;
+ border-color: #ccc #bbb #aaa;
+ background: #F3F3F3;
+ color: #000;
}
-.fc-content {
- clear: both;
+.fc-state-hover,
+.fc-state-hover .fc-button-inner {
+ border-color: #999;
}
-.fc-content .fc-state-default {
- border-style: solid;
- border-color: #ccc; /* inner border color */
+.fc-state-down,
+.fc-state-down .fc-button-inner {
+ border-color: #555;
+ background: #777;
}
-.fc-content .fc-state-highlight {
-/* today */
- background: #ffc;
+.fc-state-active,
+.fc-state-active .fc-button-inner {
+ border-color: #555;
+ background: #777;
+ color: #fff;
}
-.fc-content .fc-not-today {
- background: none;
+.fc-state-disabled,
+.fc-state-disabled .fc-button-inner {
+ color: #999;
+ border-color: #ddd;
}
-.fc-cell-overlay {
-/* semi-transparent rectangle while dragging */
- background: #9cf;
- opacity: .2;
- filter: alpha(opacity = 20); /* for IE */
+.fc-state-disabled {
+ cursor: default;
}
-.fc-view {
-/* prevents dragging outside of widget */
- width: 100%;
- overflow: hidden;
+.fc-state-disabled .fc-button-effect {
+ display: none;
}
/* Global Event Styles
------------------------------------------------------------------------*/
-.fc-event,
-.fc-agenda .fc-event-time,
-.fc-event a {
+.fc-event {
border-style: solid;
- border-color: #36c; /* default BORDER color (probably the same as background-color)
*/
- background-color: #36c; /* default BACKGROUND color */
- color: #fff; /* default TEXT color */
+ border-width: 0;
+ font-size: .85em;
+ cursor: default;
}
-/* Use the 'className' CalEvent property and the following
- * example CSS to change event color on a per-event basis:
- *
- * .myclass,
- * .fc-agenda .myclass .fc-event-time,
- * .myclass a {
- * background-color: black;
- * border-color: black;
- * color: red;
- * }
- */
-
-.fc-event {
- text-align: left;
+a.fc-event,
+.fc-event-draggable {
+ cursor: pointer;
}
-.fc-event a {
- overflow: hidden;
- font-size: .85em;
+a.fc-event {
text-decoration: none;
- cursor: pointer;
}
-.fc-event-editable {
- cursor: pointer;
+.fc-rtl .fc-event {
+ text-align: right;
}
-.fc-event-time,
-.fc-event-title {
- padding: 0 1px;
+.fc-event-skin {
+ border-color: #36c; /* default BORDER color */
+ background-color: #36c; /* default BACKGROUND color */
+ color: #fff; /* default TEXT color */
}
-/* for fake rounded corners */
-
-.fc-event a {
- display: block;
+.fc-event-inner {
position: relative;
width: 100%;
height: 100%;
+ border-style: solid;
+ border-width: 0;
+ overflow: hidden;
}
-/* right-to-left */
-
-.fc-rtl .fc-event a {
- text-align: right;
+.fc-event-time,
+.fc-event-title {
+ padding: 0 1px;
}
-/* resizable */
-
.fc .ui-resizable-handle {
+ /*** TODO: don't use ui-resizable anymore, change class ***/
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 */
+ overflow: hidden; /* hacky spaces (IE6/7) */
+ font-size: 300%; /* */
+ line-height: 50%; /* */
}
/* Horizontal Events
@@ -304,30 +311,6 @@
margin-bottom: 1px;
}
-.fc-event-hori a {
- border-width: 0;
-}
-
-/* for fake rounded corners */
-
-.fc-content .fc-corner-left {
- margin-left: 1px;
-}
-
-.fc-content .fc-corner-left a {
- margin-left: -1px;
- border-left-width: 1px;
-}
-
-.fc-content .fc-corner-right {
- margin-right: 1px;
-}
-
-.fc-content .fc-corner-right a {
- margin-right: -1px;
- border-right-width: 1px;
-}
-
/* resizable */
.fc-event-hori .ui-resizable-e {
@@ -350,27 +333,96 @@
_padding-bottom: 14px; /* IE6 had 0 height */
}
-/* Month View, Basic Week View, Basic Day View
-------------------------------------------------------------------------*/
+/* Fake Rounded Corners (for buttons and events)
+------------------------------------------------------------*/
-.fc-grid table {
- width: 100%;
+.fc-corner-left {
+ margin-left: 1px;
}
-.fc .fc-grid th {
- border-width: 0 0 0 1px;
- text-align: center;
+.fc-corner-left .fc-button-inner,
+.fc-corner-left .fc-event-inner {
+ margin-left: -1px;
}
-.fc .fc-grid td {
+.fc-corner-right {
+ margin-right: 1px;
+}
+
+.fc-corner-right .fc-button-inner,
+.fc-corner-right .fc-event-inner {
+ margin-right: -1px;
+}
+
+.fc-corner-top {
+ margin-top: 1px;
+}
+
+.fc-corner-top .fc-event-inner {
+ margin-top: -1px;
+}
+
+.fc-corner-bottom {
+ margin-bottom: 1px;
+}
+
+.fc-corner-bottom .fc-event-inner {
+ margin-bottom: -1px;
+}
+
+/* Fake Rounded Corners SPECIFICALLY FOR EVENTS
+-----------------------------------------------------------------*/
+
+.fc-corner-left .fc-event-inner {
+ border-left-width: 1px;
+}
+
+.fc-corner-right .fc-event-inner {
+ border-right-width: 1px;
+}
+
+.fc-corner-top .fc-event-inner {
+ border-top-width: 1px;
+}
+
+.fc-corner-bottom .fc-event-inner {
+ border-bottom-width: 1px;
+}
+
+/* Reusable Separate-border Table
+------------------------------------------------------------*/
+
+table.fc-border-separate {
+ border-collapse: separate;
+}
+
+.fc-border-separate th,
+.fc-border-separate td {
border-width: 1px 0 0 1px;
}
-.fc-grid th.fc-leftmost,
-.fc-grid td.fc-leftmost {
- border-left: 0;
+.fc-border-separate th.fc-last,
+.fc-border-separate td.fc-last {
+ border-right-width: 1px;
}
+.fc-border-separate tr.fc-last th,
+.fc-border-separate tr.fc-last td {
+ border-bottom-width: 1px;
+}
+
+.fc-border-separate tbody tr.fc-first td,
+.fc-border-separate tbody tr.fc-first th {
+ border-top-width: 0;
+}
+
+/* Month View, Basic Week View, Basic Day View
+------------------------------------------------------------------------*/
+
+.fc-grid th {
+ text-align: center;
+}
+
.fc-grid .fc-day-number {
float: right;
padding: 0 2px;
@@ -386,7 +438,7 @@
.fc-grid .fc-day-content {
clear: both;
- padding: 2px 2px 0; /* distance between events and day edges */
+ padding: 2px 2px 1px; /* distance between events and day edges */
}
/* event styles */
@@ -397,10 +449,6 @@
/* right-to-left */
-.fc-rtl .fc-grid {
- direction: rtl;
-}
-
.fc-rtl .fc-grid .fc-day-number {
float: left;
}
@@ -412,131 +460,110 @@
/* Agenda Week View, Agenda Day View
------------------------------------------------------------------------*/
-.fc .fc-agenda th,
-.fc .fc-agenda td {
- border-width: 1px 0 0 1px;
+.fc-agenda table {
+ border-collapse: separate;
}
-.fc .fc-agenda .fc-leftmost {
- border-left: 0;
-}
-
-.fc-agenda tr.fc-first th,
-.fc-agenda tr.fc-first td {
- border-top: 0;
-}
-
-.fc-agenda-head tr.fc-last th {
- border-bottom-width: 1px;
-}
-
-.fc .fc-agenda-head td,
-.fc .fc-agenda-body td {
- background: none;
-}
-
-.fc-agenda-head th {
+.fc-agenda-days th {
text-align: center;
}
-/* the time axis running down the left side */
-
-.fc-agenda .fc-axis {
+.fc-agenda .fc-agenda-axis {
width: 50px;
padding: 0 4px;
vertical-align: middle;
+ text-align: right;
white-space: nowrap;
- text-align: right;
font-weight: normal;
}
-/* all-day event cells at top */
+.fc-agenda .fc-day-content {
+ padding: 2px 2px 1px;
+}
-.fc-agenda-head tr.fc-all-day th {
- height: 35px;
+/* make axis border take precedence */
+
+.fc-agenda-days .fc-agenda-axis {
+ border-right-width: 1px;
}
-.fc-agenda-head td {
- padding-bottom: 10px;
+.fc-agenda-days .fc-col0 {
+ border-left-width: 0;
}
-.fc .fc-divider div {
- font-size: 1px; /* for IE6/7 */
- height: 2px;
+/* all-day area */
+
+.fc-agenda-allday th {
+ border-width: 0 1px;
}
-.fc .fc-divider .fc-state-default {
- background: #eee; /* color for divider between all-day and time-slot events */
+.fc-agenda-allday .fc-day-content {
+ min-height: 34px; /* TODO: doesnt work well in quirksmode */
+ _height: 34px;
}
-/* body styles */
+/* divider (between all-day and slots) */
-.fc .fc-agenda-body td div {
- height: 20px; /* slot height */
+.fc-agenda-divider-inner {
+ height: 2px;
+ overflow: hidden;
}
-.fc .fc-agenda-body tr.fc-minor th,
-.fc .fc-agenda-body tr.fc-minor td {
- border-top-style: dotted;
+.fc-widget-header .fc-agenda-divider-inner {
+ background: #eee;
}
-.fc-agenda .fc-day-content {
- padding: 2px 2px 0; /* distance between events and day edges */
+/* slot rows */
+
+.fc-agenda-slots th {
+ border-width: 1px 1px 0;
}
-/* Vertical Events
-------------------------------------------------------------------------*/
-
-.fc-event-vert {
- border-width: 0 1px;
+.fc-agenda-slots td {
+ border-width: 1px 0 0;
+ background: none;
}
-.fc-event-vert a {
- border-width: 0;
+.fc-agenda-slots td div {
+ height: 20px;
}
-/* for fake rounded corners */
-
-.fc-content .fc-corner-top {
- margin-top: 1px;
+.fc-agenda-slots tr.fc-slot0 th,
+.fc-agenda-slots tr.fc-slot0 td {
+ border-top-width: 0;
}
-.fc-content .fc-corner-top a {
- margin-top: -1px;
- border-top-width: 1px;
+.fc-agenda-slots tr.fc-minor th,
+.fc-agenda-slots tr.fc-minor td {
+ border-top-style: dotted;
}
-.fc-content .fc-corner-bottom {
- margin-bottom: 1px;
+.fc-agenda-slots tr.fc-minor th.ui-widget-header {
+ *border-top-style: solid; /* doesn't work with background in IE6/7 */
}
-.fc-content .fc-corner-bottom a {
- margin-bottom: -1px;
- border-bottom-width: 1px;
+/* Vertical Events
+------------------------------------------------------------------------*/
+
+.fc-event-vert {
+ border-width: 0 1px;
}
-/* event content */
-
-.fc-event-vert span {
- display: block;
+.fc-event-vert .fc-event-head,
+.fc-event-vert .fc-event-content {
position: relative;
z-index: 2;
+ width: 100%;
+ overflow: hidden;
}
-.fc-event-vert span.fc-event-time {
+.fc-event-vert .fc-event-time {
white-space: nowrap;
- _white-space: normal;
- overflow: hidden;
- border: 0;
font-size: 10px;
}
-.fc-event-vert span.fc-event-title {
- line-height: 13px;
-}
-
-.fc-event-vert span.fc-event-bg {
-/* makes the event lighter w/ a semi-transparent overlay */
+.fc-event-vert .fc-event-bg {
+ /* makes the event lighter w/ a semi-transparent overlay */
position: absolute;
z-index: 1;
top: 0;
@@ -545,20 +572,31 @@
height: 100%;
background: #fff;
opacity: .3;
- filter: alpha(opacity = 30); /* for IE */
+ filter: alpha(opacity = 30);
}
+.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */
+.fc-select-helper .fc-event-bg {
+ display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work
*/
+}
+
/* resizable */
.fc-event-vert .ui-resizable-s {
bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */
width: 100% !important;
height: 8px !important;
+ overflow: hidden !important;
line-height: 8px !important;
font-size: 11px !important;
font-family: monospace;
text-align: center;
cursor: s-resize;
}
+
+.fc-agenda .ui-resizable-resizing {
+ /* TODO: better selector */
+ _overflow: hidden;
+}
Modified:
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.js
===================================================================
---
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.js 2011-11-22
06:11:32 UTC (rev 22966)
+++
sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/fullcalendar.js 2011-11-22
06:22:20 UTC (rev 22967)
@@ -1,31 +1,23 @@
/**
* @preserve
- * FullCalendar v1.4.6
+ * FullCalendar v1.5.2
*
http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
- * For event drag & drop, required jQuery UI draggable.
+ * For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
- * 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
+ * Copyright (c) 2011 Adam Shaw
+ * Dual licensed under the MIT and GPL licenses, located in
+ * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
- * Date: Mon May 31 10:18:29 2010 -0700
+ * Date: Sun Aug 21 22:06:09 2011 -0700
*
*/
-(function($) {
+(function($, undefined) {
- var fc = $.fullCalendar = {};
- var views = fc.views = {};
-
-
- /* Defaults
- -----------------------------------------------------------------------------*/
-
var defaults = {
// display
@@ -44,6 +36,7 @@
//disableResizing: false,
allDayDefault: true,
+ ignoreTimezone: true,
// event ajax
lazyFetching: true,
@@ -73,10 +66,10 @@
dayNames:
['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
dayNamesShort:
['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
buttonText: {
- prev: ' ◄ ',
- next: ' ► ',
- prevYear: ' << ',
- nextYear: ' >> ',
+ prev: ' ◄ ',
+ next: ' ► ',
+ prevYear: ' << ',
+ nextYear: ' >> ',
today: 'today',
month: 'month',
week: 'week',
@@ -91,11 +84,13 @@
},
//selectable: false,
- unselectAuto: true
+ unselectAuto: true,
+ dropAccept: '*'
+
};
- // right-to-left defaults
+// right-to-left defaults
var rtlDefaults = {
header: {
left: 'next,prev today',
@@ -103,10 +98,10 @@
right: 'title'
},
buttonText: {
- prev: ' ► ',
- next: ' ◄ ',
- prevYear: ' >> ',
- nextYear: ' << '
+ prev: ' ► ',
+ next: ' ◄ ',
+ prevYear: ' >> ',
+ nextYear: ' << '
},
buttonIcons: {
prev: 'circle-triangle-e',
@@ -114,31 +109,28 @@
}
};
- // function for adding/overriding defaults
- var setDefaults = fc.setDefaults = function(d) {
- $.extend(true, defaults, d);
- };
+ var fc = $.fullCalendar = { version: "1.5.2" };
+ var fcViews = fc.views = {};
- /* .fullCalendar jQuery function
- -----------------------------------------------------------------------------*/
$.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);
+ var res;
this.each(function() {
- var data = $.data(this, 'fullCalendar');
- if (data) {
- var meth = data[options];
- if (meth) {
- var r = meth.apply(this, args);
- if (res === undefined) {
- res = r;
- }
+ var calendar = $.data(this, 'fullCalendar');
+ if (calendar && $.isFunction(calendar[options])) {
+ var r = calendar[options].apply(calendar, args);
+ if (res === undefined) {
+ res = r;
}
+ if (options == 'destroy') {
+ $.removeData(this, 'fullCalendar');
+ }
}
});
if (res !== undefined) {
@@ -147,7 +139,8 @@
return this;
}
- // pluck the 'events' and 'eventSources' options
+
+ // would like to have this logic in EventManager, but needs to happen before
options are recursively extended
var eventSources = options.eventSources || [];
delete options.eventSources;
if (options.events) {
@@ -155,1319 +148,2279 @@
delete options.events;
}
- // first event source reserved for 'sticky' events
- eventSources.unshift([]);
- // initialize options
options = $.extend(true, {},
defaults,
(options.isRTL || options.isRTL === undefined && defaults.isRTL)
? rtlDefaults : {},
options
- );
- var tm = options.theme ? 'ui' : 'fc'; // for making theme
classes
+ );
- this.each(function() {
+ this.each(function(i, _element) {
+ var element = $(_element);
+ var calendar = new Calendar(element, options, eventSources);
+ element.data('fullCalendar', calendar); // TODO: look into memory
leak implications
+ calendar.render();
+ });
- /* Instance Initialization
-
-----------------------------------------------------------------------------*/
+ return this;
- // element
- var _element = this,
- element = $(_element).addClass('fc'),
- elementOuterWidth,
- content = $("<div class='fc-content " + tm +
"-widget-content'
style='position:relative'/>").prependTo(_element),
- suggestedViewHeight,
- resizeUID = 0,
- ignoreWindowResize = 0,
- date = new Date(),
- viewName, // the current view name (TODO: look into getting rid of)
- view, // the current view
- viewInstances = {},
- absoluteViewElement;
+ };
+// function for adding/overriding defaults
+ function setDefaults(d) {
+ $.extend(true, defaults, d);
+ }
+
+
+ function Calendar(element, options, eventSources) {
+ var t = this;
+
+
+ // exports
+ t.options = options;
+ t.render = render;
+ t.destroy = destroy;
+ t.refetchEvents = refetchEvents;
+ t.reportEvents = reportEvents;
+ t.reportEventChange = reportEventChange;
+ t.rerenderEvents = rerenderEvents;
+ t.changeView = changeView;
+ t.select = select;
+ t.unselect = unselect;
+ t.prev = prev;
+ t.next = next;
+ t.prevYear = prevYear;
+ t.nextYear = nextYear;
+ t.today = today;
+ t.gotoDate = gotoDate;
+ t.incrementDate = incrementDate;
+ t.formatDate = function(format, date) {
+ return formatDate(format, date, options)
+ };
+ t.formatDates = function(format, date1, date2) {
+ return formatDates(format, date1, date2, options)
+ };
+ t.getDate = getDate;
+ t.getView = getView;
+ t.option = option;
+ t.trigger = trigger;
+
+
+ // imports
+ EventManager.call(t, options, eventSources);
+ var isFetchNeeded = t.isFetchNeeded;
+ var fetchEvents = t.fetchEvents;
+
+
+ // locals
+ var _element = element[0];
+ var header;
+ var headerElement;
+ var content;
+ var tm; // for making theme classes
+ var currentView;
+ var viewInstances = {};
+ var elementOuterWidth;
+ var suggestedViewHeight;
+ var absoluteViewElement;
+ var resizeUID = 0;
+ var ignoreWindowResize = 0;
+ var date = new Date();
+ var events = [];
+ var _dragElement;
+
+
+ /* Main Rendering
+ -----------------------------------------------------------------------------*/
+
+
+ setYMD(date, options.year, options.month, options.date);
+
+
+ function render(inc) {
+ if (!content) {
+ initialRender();
+ } else {
+ calcSize();
+ markSizesDirty();
+ markEventsDirty();
+ renderView(inc);
+ }
+ }
+
+
+ function initialRender() {
+ tm = options.theme ? 'ui' : 'fc';
+ element.addClass('fc');
if (options.isRTL) {
element.addClass('fc-rtl');
}
if (options.theme) {
element.addClass('ui-widget');
}
-
- if (options.year !== undefined && options.year != date.getFullYear())
{
- date.setDate(1);
- date.setMonth(0);
- date.setFullYear(options.year);
+ content = $("<div class='fc-content'
style='position:relative'/>")
+ .prependTo(element);
+ header = new Header(t, options);
+ headerElement = header.render();
+ if (headerElement) {
+ element.prepend(headerElement);
}
- if (options.month !== undefined && options.month != date.getMonth())
{
- date.setDate(1);
- date.setMonth(options.month);
+ changeView(options.defaultView);
+ $(window).resize(windowResize);
+ // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a
windowResize
+ if (!bodyVisible()) {
+ lateRender();
}
- if (options.date !== undefined) {
- date.setDate(options.date);
- }
+ }
- /* View Rendering
-
-----------------------------------------------------------------------------*/
+ // called when we know the calendar couldn't be rendered when it was
initialized,
+ // but we think it's ready now
+ function lateRender() {
+ setTimeout(function() { // IE7 needs this so dimensions are calculated
correctly
+ if (!currentView.start && bodyVisible()) { // !currentView.start
makes sure this never happens more than once
+ renderView();
+ }
+ }, 0);
+ }
- function changeView(v) {
- if (v != viewName) {
- ignoreWindowResize++; // because setMinHeight might change the height
before render (and subsequently setSize) is reached
- viewUnselect();
+ function destroy() {
+ $(window).unbind('resize', windowResize);
+ header.destroy();
+ content.remove();
+ element.removeClass('fc fc-rtl ui-widget');
+ }
- var oldView = view,
- newViewElement;
- if (oldView) {
- if (oldView.eventsChanged) {
- eventsDirty();
- oldView.eventDirty = oldView.eventsChanged = false;
- }
- if (oldView.beforeHide) {
- oldView.beforeHide(); // called before changing min-height.
if called after, scroll state is reset (in Opera)
- }
- setMinHeight(content, content.height());
- oldView.element.hide();
- } else {
- setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or
else view dimensions miscalculated
- }
- content.css('overflow', 'hidden');
+ function elementVisible() {
+ return _element.offsetWidth !== 0;
+ }
- if (viewInstances[v]) {
- (view = viewInstances[v]).element.show();
- } else {
- view = viewInstances[v] = fc.views[v](
- newViewElement = absoluteViewElement =
- $("<div class='fc-view fc-view-"
+ v + "' style='position:absolute'/>")
- .appendTo(content),
- options
- );
- }
- 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');
- }
+ function bodyVisible() {
+ return $('body')[0].offsetWidth !== 0;
+ }
- view.name = viewName = v;
- render(); // after height has been set, will make
absoluteViewElement's position=relative, then set to null
- content.css('overflow', '');
- if (oldView) {
- setMinHeight(content, 1);
- }
- if (!newViewElement && view.afterShow) {
- view.afterShow(); // called after setting min-height/overflow, so
in final scroll state (for Opera)
- }
- ignoreWindowResize--;
- }
- }
+ /* View Rendering
+ -----------------------------------------------------------------------------*/
+ // TODO: improve view switching (still weird transition in IE, and FF has
whiteout problem)
- function render(inc) {
- if (elementVisible()) {
- ignoreWindowResize++; // because view.renderEvents might temporarily
change the height before setSize is reached
+ function changeView(newViewName) {
+ if (!currentView || newViewName != currentView.name) {
+ ignoreWindowResize++; // because setMinHeight might change the height
before render (and subsequently setSize) is reached
- viewUnselect();
+ unselect();
- if (suggestedViewHeight === undefined) {
- calcSize();
- }
+ var oldView = currentView;
+ var newViewElement;
- if (!view.start || inc || date < view.start || date >=
view.end) {
- view.render(date, inc || 0); // responsible for clearing events
- setSize(true);
- if (!eventStart || !options.lazyFetching || view.visStart <
eventStart || view.visEnd > eventEnd) {
- fetchAndRenderEvents();
- } else {
- view.renderEvents(events); // don't refetch
- }
- }
- else if (view.sizeDirty || view.eventsDirty || !options.lazyFetching)
{
- view.clearEvents();
- if (view.sizeDirty) {
- setSize();
- }
- if (options.lazyFetching) {
- view.renderEvents(events); // don't refetch
- } else {
- fetchAndRenderEvents();
- }
- }
- elementOuterWidth = element.outerWidth();
- view.sizeDirty = false;
- view.eventsDirty = false;
+ if (oldView) {
+ (oldView.beforeHide || noop)(); // called before changing min-height.
if called after, scroll state is reset (in Opera)
+ setMinHeight(content, content.height());
+ oldView.element.hide();
+ } else {
+ setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else
view dimensions miscalculated
+ }
+ content.css('overflow', 'hidden');
- if (header) {
- // update title text
- header.find('h2.fc-header-title').html(view.title);
- // 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');
- } else {
- header.find('div.fc-button-today').removeClass(tm +
'-state-disabled');
- }
- }
+ currentView = viewInstances[newViewName];
+ if (currentView) {
+ currentView.element.show();
+ } else {
+ currentView = viewInstances[newViewName] = new fcViews[newViewName](
+ newViewElement = absoluteViewElement =
+ $("<div class='fc-view fc-view-" +
newViewName + "' style='position:absolute'/>")
+ .appendTo(content),
+ t // the calendar object
+ );
+ }
- ignoreWindowResize--;
- view.trigger('viewDisplay', _element);
+ if (oldView) {
+ header.deactivateButton(oldView.name);
}
- }
+ header.activateButton(newViewName);
+ renderView(); // after height has been set, will make
absoluteViewElement's position=relative, then set to null
- function elementVisible() {
- return _element.offsetWidth !== 0;
- }
+ content.css('overflow', '');
+ if (oldView) {
+ setMinHeight(content, 1);
+ }
- function bodyVisible() {
- return $('body')[0].offsetWidth !== 0;
- }
+ if (!newViewElement) {
+ (currentView.afterShow || noop)(); // called after setting
min-height/overflow, so in final scroll state (for Opera)
+ }
- function viewUnselect() {
- if (view) {
- view.unselect();
- }
+ ignoreWindowResize--;
}
+ }
- // called when any event objects have been added/removed/changed, rerenders
- function eventsChanged() {
- eventsDirty();
- if (elementVisible()) {
- view.clearEvents();
- view.renderEvents(events);
- view.eventsDirty = false;
- }
- }
+ function renderView(inc) {
+ if (elementVisible()) {
+ ignoreWindowResize++; // because renderEvents might temporarily change
the height before setSize is reached
- // marks other views' events as dirty
- function eventsDirty() {
- $.each(viewInstances, function() {
- this.eventsDirty = true;
- });
- }
+ unselect();
- // called when we know the element size has changed
- function sizeChanged() {
- sizesDirty();
- if (elementVisible()) {
+ if (suggestedViewHeight === undefined) {
calcSize();
+ }
+
+ var forceEventRender = false;
+ if (!currentView.start || inc || date < currentView.start || date
>= currentView.end) {
+ // view must render an entire new date range (and refetch/render
events)
+ currentView.render(date, inc || 0); // responsible for clearing
events
+ setSize(true);
+ forceEventRender = true;
+ }
+ else if (currentView.sizeDirty) {
+ // view must resize (and rerender events)
+ currentView.clearEvents();
setSize();
- viewUnselect();
- view.rerenderEvents();
- view.sizeDirty = false;
+ forceEventRender = true;
}
+ else if (currentView.eventsDirty) {
+ currentView.clearEvents();
+ forceEventRender = true;
+ }
+ currentView.sizeDirty = false;
+ currentView.eventsDirty = false;
+ updateEvents(forceEventRender);
+
+ elementOuterWidth = element.outerWidth();
+
+ header.updateTitle(currentView.title);
+ var today = new Date();
+ if (today >= currentView.start && today < currentView.end)
{
+ header.disableButton('today');
+ } else {
+ header.enableButton('today');
+ }
+
+ ignoreWindowResize--;
+ currentView.trigger('viewDisplay', _element);
}
+ }
- // marks other views' sizes as dirty
- function sizesDirty() {
- $.each(viewInstances, function() {
- this.sizeDirty = true;
- });
+
+ /* Resizing
+ -----------------------------------------------------------------------------*/
+
+
+ function updateSize() {
+ markSizesDirty();
+ if (elementVisible()) {
+ calcSize();
+ setSize();
+ unselect();
+ currentView.clearEvents();
+ currentView.renderEvents(events);
+ currentView.sizeDirty = false;
}
+ }
- /* Event Sources and Fetching
-
-----------------------------------------------------------------------------*/
+ function markSizesDirty() {
+ $.each(viewInstances, function(i, inst) {
+ inst.sizeDirty = true;
+ });
+ }
- var events = [],
- eventStart, eventEnd;
- // Fetch from ALL sources. Clear 'events' array and populate
- function fetchEvents(callback) {
- events = [];
- eventStart = cloneDate(view.visStart);
- eventEnd = cloneDate(view.visEnd);
- var queued = eventSources.length,
- sourceDone = function() {
- if (!--queued) {
- if (callback) {
- callback(events);
- }
- }
- }, i = 0;
- for (; i < eventSources.length; i++) {
- fetchEventSource(eventSources[i], sourceDone);
- }
+ function calcSize() {
+ if (options.contentHeight) {
+ suggestedViewHeight = options.contentHeight;
}
+ else if (options.height) {
+ suggestedViewHeight = options.height - (headerElement ?
headerElement.height() : 0) - vsides(content);
+ }
+ else {
+ suggestedViewHeight = Math.round(content.width() /
Math.max(options.aspectRatio, .5));
+ }
+ }
- // Fetch from a particular source. Append to the 'events' array
- function fetchEventSource(src, callback) {
- var prevViewName = view.name,
- prevDate = cloneDate(date),
- reportEvents = function(a) {
- if (prevViewName == view.name && +prevDate == +date
&& // protects from fast switching
- $.inArray(src, eventSources) != -1) { //
makes sure source hasn't been removed
- for (var i = 0; i < a.length; i++) {
- normalizeEvent(a[i], options);
- a[i].source = src;
- }
- events = events.concat(a);
- if (callback) {
- callback(a);
- }
+
+ function setSize(dateChanged) { // todo: dateChanged?
+ ignoreWindowResize++;
+ currentView.setHeight(suggestedViewHeight, dateChanged);
+ if (absoluteViewElement) {
+ absoluteViewElement.css('position', 'relative');
+ absoluteViewElement = null;
+ }
+ currentView.setWidth(content.width(), dateChanged);
+ ignoreWindowResize--;
+ }
+
+
+ function windowResize() {
+ if (!ignoreWindowResize) {
+ if (currentView.start) { // view has already been rendered
+ var uid = ++resizeUID;
+ setTimeout(function() { // add a delay
+ if (uid == resizeUID && !ignoreWindowResize &&
elementVisible()) {
+ if (elementOuterWidth != (elementOuterWidth =
element.outerWidth())) {
+ ignoreWindowResize++; // in case the windowResize
callback changes the height
+ updateSize();
+ currentView.trigger('windowResize', _element);
+ ignoreWindowResize--;
}
- },
- reportEventsAndPop = function(a) {
- reportEvents(a);
- popLoading();
- };
- if (typeof src == 'string') {
- var params = {};
- params[options.startParam] = Math.round(eventStart.getTime() /
1000);
- params[options.endParam] = Math.round(eventEnd.getTime() / 1000);
- if (options.cacheParam) {
- params[options.cacheParam] = (new Date()).getTime(); // TODO:
deprecate cacheParam
- }
- pushLoading();
- $.ajax({
- url: src,
- dataType: 'json',
- data: params,
- cache: options.cacheParam || false, // don't let jquery
prevent caching if cacheParam is being used
- success: reportEventsAndPop
- });
+ }
+ }, 200);
+ } else {
+ // calendar must have been initialized in a 0x0 iframe that has just
been resized
+ lateRender();
}
- else if ($.isFunction(src)) {
- pushLoading();
- src(cloneDate(eventStart), cloneDate(eventEnd), reportEventsAndPop);
- }
- else {
- reportEvents(src); // src is an array
- }
}
+ }
- // for convenience
- function fetchAndRenderEvents() {
- fetchEvents(function(events) {
- view.renderEvents(events); // maintain `this` in view
- });
+ /* Event Fetching/Rendering
+ -----------------------------------------------------------------------------*/
+
+
+ // fetches events if necessary, rerenders events if necessary (or if forced)
+ function updateEvents(forceRender) {
+ if (!options.lazyFetching || isFetchNeeded(currentView.visStart,
currentView.visEnd)) {
+ refetchEvents();
}
+ else if (forceRender) {
+ rerenderEvents();
+ }
+ }
- /* Loading State
-
-----------------------------------------------------------------------------*/
+ function refetchEvents() {
+ fetchEvents(currentView.visStart, currentView.visEnd); // will call
reportEvents
+ }
- var loadingLevel = 0;
- function pushLoading() {
- if (!loadingLevel++) {
- view.trigger('loading', _element, true);
- }
+ // called when event data arrives
+ function reportEvents(_events) {
+ events = _events;
+ rerenderEvents();
+ }
+
+
+ // called when a single event's data has been changed
+ function reportEventChange(eventID) {
+ rerenderEvents(eventID);
+ }
+
+
+ // attempts to rerenderEvents
+ function rerenderEvents(modifiedEventID) {
+ markEventsDirty();
+ if (elementVisible()) {
+ currentView.clearEvents();
+ currentView.renderEvents(events, modifiedEventID);
+ currentView.eventsDirty = false;
}
+ }
- function popLoading() {
- if (!--loadingLevel) {
- view.trigger('loading', _element, false);
- }
+
+ function markEventsDirty() {
+ $.each(viewInstances, function(i, inst) {
+ inst.eventsDirty = true;
+ });
+ }
+
+
+ /* Selection
+ -----------------------------------------------------------------------------*/
+
+
+ function select(start, end, allDay) {
+ currentView.select(start, end, allDay === undefined ? true : allDay);
+ }
+
+
+ function unselect() { // safe to be called before renderView
+ if (currentView) {
+ currentView.unselect();
}
+ }
- /* Public Methods
-
-----------------------------------------------------------------------------*/
+ /* Date
+ -----------------------------------------------------------------------------*/
- var publicMethods = {
- render: function() {
- calcSize();
- sizesDirty();
- eventsDirty();
- render();
- },
+ function prev() {
+ renderView(-1);
+ }
- changeView: changeView,
- getView: function() {
- return view;
- },
+ function next() {
+ renderView(1);
+ }
- getDate: function() {
- return date;
- },
- option: function(name, value) {
- if (value === undefined) {
- return options[name];
- }
- if (name == 'height' || name == 'contentHeight' ||
name == 'aspectRatio') {
- options[name] = value;
- sizeChanged();
- }
- },
+ function prevYear() {
+ addYears(date, -1);
+ renderView();
+ }
- destroy: function() {
- $(window).unbind('resize', windowResize);
- if (header) {
- header.remove();
- }
- content.remove();
- $.removeData(_element, 'fullCalendar');
- },
- //
- // Navigation
- //
+ function nextYear() {
+ addYears(date, 1);
+ renderView();
+ }
- prev: function() {
- render(-1);
- },
- next: function() {
- render(1);
- },
+ function today() {
+ date = new Date();
+ renderView();
+ }
- prevYear: function() {
- addYears(date, -1);
- render();
- },
- nextYear: function() {
- addYears(date, 1);
- render();
- },
+ function gotoDate(year, month, dateOfMonth) {
+ if (year instanceof Date) {
+ date = cloneDate(year); // provided 1 argument, a Date
+ } else {
+ setYMD(date, year, month, dateOfMonth);
+ }
+ renderView();
+ }
- today: function() {
- date = new Date();
- render();
- },
- gotoDate: function(year, month, dateNum) {
- if (typeof year == 'object') {
- date = cloneDate(year); // provided 1 argument, a Date
- } else {
- if (year !== undefined) {
- date.setFullYear(year);
- }
- if (month !== undefined) {
- date.setMonth(month);
- }
- if (dateNum !== undefined) {
- date.setDate(dateNum);
- }
- }
- render();
- },
+ function incrementDate(years, months, days) {
+ if (years !== undefined) {
+ addYears(date, years);
+ }
+ if (months !== undefined) {
+ addMonths(date, months);
+ }
+ if (days !== undefined) {
+ addDays(date, days);
+ }
+ renderView();
+ }
- incrementDate: function(years, months, days) {
- if (years !== undefined) {
- addYears(date, years);
- }
- if (months !== undefined) {
- addMonths(date, months);
- }
- if (days !== undefined) {
- addDays(date, days);
- }
- render();
- },
- //
- // Event Manipulation
- //
+ function getDate() {
+ return cloneDate(date);
+ }
- updateEvent: function(event) { // update an existing event
- var i, len = events.length, e,
- startDelta = event.start - event._start,
- endDelta = event.end ?
- (event.end - (event._end ||
view.defaultEventEnd(event))) // event._end would be null if event.end
- : 0;
// was null and event was just resized
- for (i = 0; i < len; i++) {
- e = events[i];
- if (e._id == event._id && e != event) {
- e.start = new Date(+e.start + startDelta);
- if (event.end) {
- if (e.end) {
- e.end = new Date(+e.end + endDelta);
- } else {
- e.end = new Date(+view.defaultEventEnd(e) +
endDelta);
- }
- } else {
- e.end = null;
- }
- e.title = event.title;
- e.url = event.url;
- e.allDay = event.allDay;
- e.className = event.className;
- e.editable = event.editable;
- normalizeEvent(e, options);
- }
- }
- normalizeEvent(event, options);
- eventsChanged();
- },
- renderEvent: function(event, stick) { // render a new event
- normalizeEvent(event, options);
- if (!event.source) {
- if (stick) {
- (event.source = eventSources[0]).push(event);
- }
- events.push(event);
- }
- eventsChanged();
- },
+ /* Misc
+ -----------------------------------------------------------------------------*/
- removeEvents: function(filter) {
- if (!filter) { // remove all
- events = [];
- // clear all array sources
- for (var i = 0; i < eventSources.length; i++) {
- if (typeof eventSources[i] == 'object') {
- eventSources[i] = [];
- }
- }
- } else {
- if (!$.isFunction(filter)) { // an event ID
- var id = filter + '';
- filter = function(e) {
- return e._id == id;
- };
- }
- events = $.grep(events, filter, true);
- // remove events from array sources
- for (var i = 0; i < eventSources.length; i++) {
- if (typeof eventSources[i] == 'object') {
- eventSources[i] = $.grep(eventSources[i], filter, true);
- }
- }
- }
- eventsChanged();
- },
- clientEvents: function(filter) {
- if ($.isFunction(filter)) {
- return $.grep(events, filter);
- }
- else if (filter) { // an event ID
- filter += '';
- return $.grep(events, function(e) {
- return e._id == filter;
- });
- }
- return events; // else, return all
- },
+ function getView() {
+ return currentView;
+ }
- rerenderEvents: eventsChanged, // TODO: think of renaming eventsChanged
- //
- // Event Source
- //
+ function option(name, value) {
+ if (value === undefined) {
+ return options[name];
+ }
+ if (name == 'height' || name == 'contentHeight' || name ==
'aspectRatio') {
+ options[name] = value;
+ updateSize();
+ }
+ }
- addEventSource: function(source) {
- eventSources.push(source);
- fetchEventSource(source, eventsChanged);
- },
- removeEventSource: function(source) {
- eventSources = $.grep(eventSources, function(src) {
- return src != source;
+ function trigger(name, thisObj) {
+ if (options[name]) {
+ return options[name].apply(
+ thisObj || _element,
+ Array.prototype.slice.call(arguments, 2)
+ );
+ }
+ }
+
+
+ /* External Dragging
+ ------------------------------------------------------------------------*/
+
+ if (options.droppable) {
+ $(document)
+ .bind('dragstart', function(ev, ui) {
+ var _e = ev.target;
+ var e = $(_e);
+ if (!e.parents('.fc').length) { // not already inside a
calendar
+ var accept = options.dropAccept;
+ if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept))
{
+ _dragElement = _e;
+ currentView.dragStart(_dragElement, ev, ui);
+ }
+ }
+ })
+ .bind('dragstop', function(ev, ui) {
+ if (_dragElement) {
+ currentView.dragStop(_dragElement, ev, ui);
+ _dragElement = null;
+ }
});
- // remove all client events from that source
- events = $.grep(events, function(e) {
- return e.source != source;
- });
- eventsChanged();
- },
+ }
- refetchEvents: function() {
- fetchEvents(eventsChanged);
- },
- //
- // selection
- //
+ }
- select: function(start, end, allDay) {
- view.select(start, end, allDay === undefined ? true : allDay);
- },
+ function Header(calendar, options) {
+ var t = this;
- unselect: function() {
- view.unselect();
- }
- };
+ // exports
+ t.render = render;
+ t.destroy = destroy;
+ t.updateTitle = updateTitle;
+ t.activateButton = activateButton;
+ t.deactivateButton = deactivateButton;
+ t.disableButton = disableButton;
+ t.enableButton = enableButton;
- $.data(this, 'fullCalendar', publicMethods); // TODO: look into
memory leak implications
+ // locals
+ var element = $([]);
+ var tm;
- /* Header
-
-----------------------------------------------------------------------------*/
- var header,
- sections = options.header;
+ function render() {
+ tm = options.theme ? 'ui' : 'fc';
+ var sections = options.header;
if (sections) {
- header = $("<table class='fc-header'/>")
- .append($("<tr/>")
- .append($("<td
class='fc-header-left'/>").append(buildSection(sections.left)))
- .append($("<td
class='fc-header-center'/>").append(buildSection(sections.center)))
- .append($("<td
class='fc-header-right'/>").append(buildSection(sections.right))))
- .prependTo(element);
+ element = $("<table class='fc-header'
style='width:100%'/>")
+ .append(
+ $("<tr/>")
+ .append(renderSection('left'))
+ .append(renderSection('center'))
+ .append(renderSection('right'))
+ );
+ return element;
}
- function buildSection(buttonStr) {
- if (buttonStr) {
- var tr = $("<tr/>");
- $.each(buttonStr.split(' '), function(i) {
- if (i > 0) {
- tr.append("<td><span
class='fc-header-space'/></td>");
- }
- var prevButton;
- $.each(this.split(','), function(j, buttonName) {
- if (buttonName == 'title') {
- tr.append("<td><h2
class='fc-header-title'> </h2></td>");
- if (prevButton) {
- prevButton.addClass(tm + '-corner-right');
- }
- prevButton = null;
- } else {
- var buttonClick;
- if (publicMethods[buttonName]) {
- buttonClick = publicMethods[buttonName];
- }
- else if (views[buttonName]) {
- buttonClick = function() {
- button.removeClass(tm + '-state-hover');
- changeView(buttonName);
- };
- }
- if (buttonClick) {
- if (prevButton) {
- prevButton.addClass(tm + '-no-right');
- }
- var button,
- icon = options.theme ?
smartProperty(options.buttonIcons, buttonName) : null,
- text = smartProperty(options.buttonText,
buttonName);
- if (icon) {
- button = $("<div
class='fc-button-" + buttonName + " ui-state-default'>" +
- "<a><span
class='ui-icon ui-icon-" + icon + "'/></a></div>");
- }
- else if (text) {
- button = $("<div
class='fc-button-" + buttonName + " " + tm +
"-state-default'>" +
- "<a><span>" + text
+ "</span></a></div>");
- }
- if (button) {
- button
- .click(function() {
- if (!button.hasClass(tm +
'-state-disabled')) {
- buttonClick();
+ }
+
+
+ function destroy() {
+ element.remove();
+ }
+
+
+ function renderSection(position) {
+ var e = $("<td class='fc-header-" + position +
"'/>");
+ var buttonStr = options.header[position];
+ if (buttonStr) {
+ $.each(buttonStr.split(' '), function(i) {
+ if (i > 0) {
+ e.append("<span
class='fc-header-space'/>");
+ }
+ var prevButton;
+ $.each(this.split(','), function(j, buttonName) {
+ if (buttonName == 'title') {
+ e.append("<span
class='fc-header-title'><h2> </h2></span>");
+ if (prevButton) {
+ prevButton.addClass(tm + '-corner-right');
+ }
+ prevButton = null;
+ } else {
+ var buttonClick;
+ if (calendar[buttonName]) {
+ buttonClick = calendar[buttonName]; // calendar method
+ }
+ else if (fcViews[buttonName]) {
+ buttonClick = function() {
+ button.removeClass(tm + '-state-hover'); //
forget why
+ calendar.changeView(buttonName);
+ };
+ }
+ if (buttonClick) {
+ var icon = options.theme ?
smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty
here?
+ var text = smartProperty(options.buttonText, buttonName);
// why are we using smartProperty here?
+ var button = $(
+ "<span class='fc-button
fc-button-" + buttonName + " " + tm + "-state-default'>"
+
+ "<span
class='fc-button-inner'>" +
+ "<span
class='fc-button-content'>" +
+ (icon ?
+ "<span
class='fc-icon-wrap'>" +
+ "<span
class='ui-icon ui-icon-" + icon + "'/>" +
+ "</span>"
:
+ text
+ ) +
+ "</span>" +
+ "<span
class='fc-button-effect'><span></span></span>" +
+ "</span>" +
+ "</span>"
+ );
+ if (button) {
+ button
+ .click(function() {
+ if (!button.hasClass(tm +
'-state-disabled')) {
+ buttonClick();
+ }
+ })
+ .mousedown(function() {
+ button
+ .not('.' + tm +
'-state-active')
+ .not('.' + tm +
'-state-disabled')
+ .addClass(tm +
'-state-down');
+ })
+ .mouseup(function() {
+ button.removeClass(tm +
'-state-down');
+ })
+ .hover(
+ function() {
+ button
+ .not('.' + tm +
'-state-active')
+ .not('.' + tm +
'-state-disabled')
+ .addClass(tm +
'-state-hover');
+ },
+ function() {
+ button
+ .removeClass(tm +
'-state-hover')
+ .removeClass(tm +
'-state-down');
}
- })
- .mousedown(function() {
- button
- .not('.' + tm +
'-state-active')
- .not('.' + tm +
'-state-disabled')
- .addClass(tm +
'-state-down');
- })
- .mouseup(function() {
- button.removeClass(tm +
'-state-down');
- })
- .hover(
- function() {
- button
- .not('.' + tm +
'-state-active')
- .not('.' + tm +
'-state-disabled')
- .addClass(tm +
'-state-hover');
- },
- function() {
- button
- .removeClass(tm +
'-state-hover')
- .removeClass(tm +
'-state-down');
- }
- )
-
.appendTo($("<td/>").appendTo(tr));
- if (prevButton) {
- prevButton.addClass(tm +
'-no-right');
- } else {
- button.addClass(tm +
'-corner-left');
- }
- prevButton = button;
+ )
+ .appendTo(e);
+ if (!prevButton) {
+ button.addClass(tm + '-corner-left');
}
+ prevButton = button;
}
}
- });
- if (prevButton) {
- prevButton.addClass(tm + '-corner-right');
}
});
- return $("<table/>").append(tr);
- }
+ if (prevButton) {
+ prevButton.addClass(tm + '-corner-right');
+ }
+ });
}
+ return e;
+ }
- /* Resizing
-
-----------------------------------------------------------------------------*/
+ function updateTitle(html) {
+ element.find('h2')
+ .html(html);
+ }
- function calcSize() {
- if (options.contentHeight) {
- suggestedViewHeight = options.contentHeight;
+ function activateButton(buttonName) {
+ element.find('span.fc-button-' + buttonName)
+ .addClass(tm + '-state-active');
+ }
+
+
+ function deactivateButton(buttonName) {
+ element.find('span.fc-button-' + buttonName)
+ .removeClass(tm + '-state-active');
+ }
+
+
+ function disableButton(buttonName) {
+ element.find('span.fc-button-' + buttonName)
+ .addClass(tm + '-state-disabled');
+ }
+
+
+ function enableButton(buttonName) {
+ element.find('span.fc-button-' + buttonName)
+ .removeClass(tm + '-state-disabled');
+ }
+
+
+ }
+
+ fc.sourceNormalizers = [];
+ fc.sourceFetchers = [];
+
+ var ajaxDefaults = {
+ dataType: 'json',
+ cache: false
+ };
+
+ var eventGUID = 1;
+
+
+ function EventManager(options, _sources) {
+ var t = this;
+
+
+ // exports
+ t.isFetchNeeded = isFetchNeeded;
+ t.fetchEvents = fetchEvents;
+ t.addEventSource = addEventSource;
+ t.removeEventSource = removeEventSource;
+ t.updateEvent = updateEvent;
+ t.renderEvent = renderEvent;
+ t.removeEvents = removeEvents;
+ t.clientEvents = clientEvents;
+ t.normalizeEvent = normalizeEvent;
+
+
+ // imports
+ var trigger = t.trigger;
+ var getView = t.getView;
+ var reportEvents = t.reportEvents;
+
+
+ // locals
+ var stickySource = { events: [] };
+ var sources = [ stickySource ];
+ var rangeStart, rangeEnd;
+ var currentFetchID = 0;
+ var pendingSourceCnt = 0;
+ var loadingLevel = 0;
+ var cache = [];
+
+
+ for (var i = 0; i < _sources.length; i++) {
+ _addEventSource(_sources[i]);
+ }
+
+
+ /* Fetching
+ -----------------------------------------------------------------------------*/
+
+
+ function isFetchNeeded(start, end) {
+ return !rangeStart || start < rangeStart || end > rangeEnd;
+ }
+
+
+ function fetchEvents(start, end) {
+ rangeStart = start;
+ rangeEnd = end;
+ cache = [];
+ var fetchID = ++currentFetchID;
+ var len = sources.length;
+ pendingSourceCnt = len;
+ for (var i = 0; i < len; i++) {
+ fetchEventSource(sources[i], fetchID);
+ }
+ }
+
+
+ function fetchEventSource(source, fetchID) {
+ _fetchEventSource(source, function(events) {
+ if (fetchID == currentFetchID) {
+ if (events) {
+ for (var i = 0; i < events.length; i++) {
+ events[i].source = source;
+ normalizeEvent(events[i]);
+ }
+ cache = cache.concat(events);
+ }
+ pendingSourceCnt--;
+ if (!pendingSourceCnt) {
+ reportEvents(cache);
+ }
}
- else if (options.height) {
- suggestedViewHeight = options.height - (header ? header.height() : 0)
- vsides(content[0]);
+ });
+ }
+
+
+ function _fetchEventSource(source, callback) {
+ var i;
+ var fetchers = fc.sourceFetchers;
+ var res;
+ for (i = 0; i < fetchers.length; i++) {
+ res = fetchers[i](source, rangeStart, rangeEnd, callback);
+ if (res === true) {
+ // the fetcher is in charge. made its own async request
+ return;
}
+ else if (typeof res == 'object') {
+ // the fetcher returned a new source. process it
+ _fetchEventSource(res, callback);
+ return;
+ }
+ }
+ var events = source.events;
+ if (events) {
+ if ($.isFunction(events)) {
+ pushLoading();
+ events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events)
{
+ callback(events);
+ popLoading();
+ });
+ }
+ else if ($.isArray(events)) {
+ callback(events);
+ }
else {
- suggestedViewHeight = Math.round(content.width() /
Math.max(options.aspectRatio, .5));
+ callback();
}
+ } else {
+ var url = source.url;
+ if (url) {
+ var success = source.success;
+ var error = source.error;
+ var complete = source.complete;
+ var data = $.extend({}, source.data || {});
+ var startParam = firstDefined(source.startParam,
options.startParam);
+ var endParam = firstDefined(source.endParam, options.endParam);
+ if (startParam) {
+ data[startParam] = Math.round(+rangeStart / 1000);
+ }
+ if (endParam) {
+ data[endParam] = Math.round(+rangeEnd / 1000);
+ }
+ pushLoading();
+ $.ajax($.extend({}, ajaxDefaults, source, {
+ data: data,
+ success: function(events) {
+ events = events || [];
+ var res = applyAll(success, this, arguments);
+ if ($.isArray(res)) {
+ events = res;
+ }
+ callback(events);
+ },
+ error: function() {
+ applyAll(error, this, arguments);
+ callback();
+ },
+ complete: function() {
+ applyAll(complete, this, arguments);
+ popLoading();
+ }
+ }));
+ } else {
+ callback();
+ }
}
+ }
- function setSize(dateChanged) {
- ignoreWindowResize++;
- view.setHeight(suggestedViewHeight, dateChanged);
- if (absoluteViewElement) {
- absoluteViewElement.css('position', 'relative');
- absoluteViewElement = null;
- }
- view.setWidth(content.width(), dateChanged);
- ignoreWindowResize--;
+ /* Sources
+ -----------------------------------------------------------------------------*/
+
+
+ function addEventSource(source) {
+ source = _addEventSource(source);
+ if (source) {
+ pendingSourceCnt++;
+ fetchEventSource(source, currentFetchID); // will eventually call
reportEvents
}
+ }
- function windowResize() {
- if (!ignoreWindowResize) {
- if (view.start) { // view has already been rendered
- var uid = ++resizeUID;
- setTimeout(function() { // add a delay
- if (uid == resizeUID && !ignoreWindowResize
&& elementVisible()) {
- if (elementOuterWidth != (elementOuterWidth =
element.outerWidth())) {
- ignoreWindowResize++; // in case the windowResize
callback changes the height
- sizeChanged();
- view.trigger('windowResize', _element);
- ignoreWindowResize--;
- }
- }
- }, 200);
+ function _addEventSource(source) {
+ if ($.isFunction(source) || $.isArray(source)) {
+ source = { events: source };
+ }
+ else if (typeof source == 'string') {
+ source = { url: source };
+ }
+ if (typeof source == 'object') {
+ normalizeSource(source);
+ sources.push(source);
+ return source;
+ }
+ }
+
+
+ function removeEventSource(source) {
+ sources = $.grep(sources, function(src) {
+ return !isSourcesEqual(src, source);
+ });
+ // remove all client events from that source
+ cache = $.grep(cache, function(e) {
+ return !isSourcesEqual(e.source, source);
+ });
+ reportEvents(cache);
+ }
+
+
+ /* Manipulation
+ -----------------------------------------------------------------------------*/
+
+
+ function updateEvent(event) { // update an existing event
+ var i, len = cache.length, e,
+ defaultEventEnd = getView().defaultEventEnd, // getView???
+ startDelta = event.start - event._start,
+ endDelta = event.end ?
+ (event.end - (event._end || defaultEventEnd(event))) //
event._end would be null if event.end
+ : 0; //
was null and event was just resized
+ for (i = 0; i < len; i++) {
+ e = cache[i];
+ if (e._id == event._id && e != event) {
+ e.start = new Date(+e.start + startDelta);
+ if (event.end) {
+ if (e.end) {
+ e.end = new Date(+e.end + endDelta);
+ } else {
+ e.end = new Date(+defaultEventEnd(e) + endDelta);
+ }
} else {
- // calendar must have been initialized in a 0x0 iframe that has
just been resized
- lateRender();
+ e.end = null;
}
+ e.title = event.title;
+ e.url = event.url;
+ e.allDay = event.allDay;
+ e.className = event.className;
+ e.editable = event.editable;
+ e.color = event.color;
+ e.backgroudColor = event.backgroudColor;
+ e.borderColor = event.borderColor;
+ e.textColor = event.textColor;
+ normalizeEvent(e);
}
}
+ normalizeEvent(event);
+ reportEvents(cache);
+ }
- $(window).resize(windowResize);
+ function renderEvent(event, stick) {
+ normalizeEvent(event);
+ if (!event.source) {
+ if (stick) {
+ stickySource.events.push(event);
+ event.source = stickySource;
+ }
+ cache.push(event);
+ }
+ reportEvents(cache);
+ }
- // let's begin...
- changeView(options.defaultView);
+ function removeEvents(filter) {
+ if (!filter) { // remove all
+ cache = [];
+ // clear all array sources
+ for (var i = 0; i < sources.length; i++) {
+ if ($.isArray(sources[i].events)) {
+ sources[i].events = [];
+ }
+ }
+ } else {
+ if (!$.isFunction(filter)) { // an event ID
+ var id = filter + '';
+ filter = function(e) {
+ return e._id == id;
+ };
+ }
+ cache = $.grep(cache, filter, true);
+ // remove events from array sources
+ for (var i = 0; i < sources.length; i++) {
+ if ($.isArray(sources[i].events)) {
+ sources[i].events = $.grep(sources[i].events, filter, true);
+ }
+ }
+ }
+ reportEvents(cache);
+ }
- // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a
windowResize
- if (!bodyVisible()) {
- lateRender();
+
+ function clientEvents(filter) {
+ if ($.isFunction(filter)) {
+ return $.grep(cache, filter);
}
+ else if (filter) { // an event ID
+ filter += '';
+ return $.grep(cache, function(e) {
+ return e._id == filter;
+ });
+ }
+ return cache; // else, return all
+ }
- // called when we know the calendar couldn't be rendered when it was
initialized,
- // but we think it's ready now
- function lateRender() {
- setTimeout(function() { // IE7 needs this so dimensions are calculated
correctly
- if (!view.start && bodyVisible()) { // !view.start makes sure
this never happens more than once
- render();
- }
- }, 0);
+ /* Loading State
+ -----------------------------------------------------------------------------*/
+
+
+ function pushLoading() {
+ if (!loadingLevel++) {
+ trigger('loading', null, true);
}
+ }
- });
+ function popLoading() {
+ if (!--loadingLevel) {
+ trigger('loading', null, false);
+ }
+ }
- return this;
- };
+ /* Event Normalization
+ -----------------------------------------------------------------------------*/
- /* Important Event Utilities
- -----------------------------------------------------------------------------*/
+ function normalizeEvent(event) {
+ var source = event.source || {};
+ var ignoreTimezone = firstDefined(source.ignoreTimezone,
options.ignoreTimezone);
+ event._id = event._id || (event.id === undefined ? '_fc' +
eventGUID++ : event.id + '');
+ if (event.date) {
+ if (!event.start) {
+ event.start = event.date;
+ }
+ delete event.date;
+ }
+ event._start = cloneDate(event.start = parseDate(event.start,
ignoreTimezone));
+ event.end = parseDate(event.end, ignoreTimezone);
+ if (event.end && event.end <= event.start) {
+ event.end = null;
+ }
+ event._end = event.end ? cloneDate(event.end) : null;
+ if (event.allDay === undefined) {
+ event.allDay = firstDefined(source.allDayDefault,
options.allDayDefault);
+ }
+ if (event.className) {
+ if (typeof event.className == 'string') {
+ event.className = event.className.split(/\s+/);
+ }
+ } else {
+ event.className = [];
+ }
+ // TODO: if there is no start date, return false to indicate an invalid
event
+ }
- var fakeID = 0;
- function normalizeEvent(event, options) {
- event._id = event._id || (event.id === undefined ? '_fc' + fakeID++ :
event.id + '');
- if (event.date) {
- if (!event.start) {
- event.start = event.date;
+ /* Utils
+
------------------------------------------------------------------------------*/
+
+
+ function normalizeSource(source) {
+ if (source.className) {
+ // TODO: repeat code, same code for event classNames
+ if (typeof source.className == 'string') {
+ source.className = source.className.split(/\s+/);
+ }
+ } else {
+ source.className = [];
}
- delete event.date;
+ var normalizers = fc.sourceNormalizers;
+ for (var i = 0; i < normalizers.length; i++) {
+ normalizers[i](source);
+ }
}
- event._start = cloneDate(event.start = parseDate(event.start));
- event.end = parseDate(event.end);
- if (event.end && event.end <= event.start) {
- event.end = null;
+
+
+ function isSourcesEqual(source1, source2) {
+ return source1 && source2 && getSourcePrimitive(source1) ==
getSourcePrimitive(source2);
}
- event._end = event.end ? cloneDate(event.end) : null;
- if (event.allDay === undefined) {
- event.allDay = options.allDayDefault;
+
+
+ function getSourcePrimitive(source) {
+ return ((typeof source == 'object') ? (source.events || source.url) :
'') || source;
}
- if (event.className) {
- if (typeof event.className == 'string') {
- event.className = event.className.split(/\s+/);
- }
- } else {
- event.className = [];
- }
+
+
}
- // TODO: if there is no start date, return false to indicate an invalid event
+ fc.addDays = addDays;
+ fc.cloneDate = cloneDate;
+ fc.parseDate = parseDate;
+ fc.parseISO8601 = parseISO8601;
+ fc.parseTime = parseTime;
+ fc.formatDate = formatDate;
+ fc.formatDates = formatDates;
- /* Grid-based Views: month, basicWeek, basicDay
+
+ /* Date Math
-----------------------------------------------------------------------------*/
- setDefaults({
- weekMode: 'fixed'
- });
+ var dayIDs = ['sun', 'mon', 'tue', 'wed',
'thu', 'fri', 'sat'],
+ DAY_MS = 86400000,
+ HOUR_MS = 3600000,
+ MINUTE_MS = 60000;
- views.month = function(element, options) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addMonths(date, delta);
- date.setDate(1);
- }
- // start/end
- var start = this.start = cloneDate(date, true);
- start.setDate(1);
- this.end = addMonths(cloneDate(start), 1);
- // visStart/visEnd
- var visStart = this.visStart = cloneDate(start),
- visEnd = this.visEnd = cloneDate(this.end),
- nwe = options.weekends ? 0 : 1;
- if (nwe) {
- skipWeekend(visStart);
- skipWeekend(visEnd, -1, true);
- }
- addDays(visStart, -((visStart.getDay() - Math.max(options.firstDay, nwe)
+ 7) % 7));
- addDays(visEnd, (7 - visEnd.getDay() + Math.max(options.firstDay, nwe)) %
7);
- // row count
- var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
- if (options.weekMode == 'fixed') {
- addDays(visEnd, (6 - rowCnt) * 7);
- rowCnt = 6;
- }
- // title
- this.title = formatDate(
- start,
- this.option('titleFormat'),
- options
- );
- // render
- this.renderGrid(
- rowCnt, options.weekends ? 7 : 5,
- this.option('columnFormat'),
- true
- );
+
+ function addYears(d, n, keepTime) {
+ d.setFullYear(d.getFullYear() + n);
+ if (!keepTime) {
+ clearTime(d);
+ }
+ return d;
+ }
+
+
+ function addMonths(d, n, keepTime) { // prevents day overflow/underflow
+ if (+d) { // prevent infinite looping on invalid dates
+ var m = d.getMonth() + n,
+ check = cloneDate(d);
+ check.setDate(1);
+ check.setMonth(m);
+ d.setMonth(m);
+ if (!keepTime) {
+ clearTime(d);
}
- });
- };
+ while (d.getMonth() != check.getMonth()) {
+ d.setDate(d.getDate() + (d < check ? 1 : -1));
+ }
+ }
+ return d;
+ }
- views.basicWeek = function(element, options) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta * 7);
- }
- var visStart = this.visStart = cloneDate(
- this.start = addDays(cloneDate(date), -((date.getDay() -
options.firstDay + 7) % 7))
- ),
- visEnd = this.visEnd = cloneDate(
- this.end = addDays(cloneDate(visStart), 7)
- );
- if (!options.weekends) {
- skipWeekend(visStart);
- skipWeekend(visEnd, -1, true);
- }
- this.title = formatDates(
- visStart,
- addDays(cloneDate(visEnd), -1),
- this.option('titleFormat'),
- options
- );
- this.renderGrid(
- 1, options.weekends ? 7 : 5,
- this.option('columnFormat'),
- false
- );
+
+ function addDays(d, n, keepTime) { // deals with daylight savings
+ if (+d) {
+ var dd = d.getDate() + n,
+ check = cloneDate(d);
+ check.setHours(9); // set to middle of day
+ check.setDate(dd);
+ d.setDate(dd);
+ if (!keepTime) {
+ clearTime(d);
}
- });
- };
+ fixDate(d, check);
+ }
+ return d;
+ }
- views.basicDay = function(element, options) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta);
- if (!options.weekends) {
- skipWeekend(date, delta < 0 ? -1 : 1);
- }
- }
- 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.renderGrid(
- 1, 1,
- this.option('columnFormat'),
- false
- );
+
+ function fixDate(d, check) { // force d to be on check's YMD, for daylight
savings purposes
+ if (+d) { // prevent infinite looping on invalid dates
+ while (d.getDate() != check.getDate()) {
+ d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
}
- });
- };
+ }
+ }
- // rendering bugs
+ function addMinutes(d, n) {
+ d.setMinutes(d.getMinutes() + n);
+ return d;
+ }
- var tdHeightBug;
+ function clearTime(d) {
+ d.setHours(0);
+ d.setMinutes(0);
+ d.setSeconds(0);
+ d.setMilliseconds(0);
+ return d;
+ }
- function Grid(element, options, methods) {
- var tm, firstDay,
- nwe, // no weekends (int)
- rtl, dis, dit, // day index sign / translate
- viewWidth, viewHeight,
- rowCnt, colCnt,
- colWidth,
- thead, tbody,
- cachedEvents = [],
- segmentContainer,
- dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
- return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,
nwe) + colCnt) % colCnt) + ') div div');
- }),
- selectionManager,
- selectionMatrix,
- // ...
+ function cloneDate(d, dontKeepTime) {
+ if (dontKeepTime) {
+ return clearTime(new Date(+d));
+ }
+ return new Date(+d);
+ }
- // initialize superclass
- view = $.extend(this, viewMethods, methods, {
- renderGrid: renderGrid,
- renderEvents: renderEvents,
- rerenderEvents: rerenderEvents,
- clearEvents: clearEvents,
- setHeight: setHeight,
- setWidth: setWidth,
- defaultEventEnd: function(event) { // calculates an end if event
doesnt have one, mostly for resizing
- return cloneDate(event.start);
- }
- });
- view.init(element, options);
+ function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
+ var i = 0, d;
+ do {
+ d = new Date(1970, i++, 1);
+ } while (d.getHours()); // != 0
+ return d;
+ }
- /* Grid Rendering
- -----------------------------------------------------------------------------*/
+ function skipWeekend(date, inc, excl) {
+ inc = inc || 1;
+ while (!date.getDay() || (excl && date.getDay() == 1 || !excl &&
date.getDay() == 6)) {
+ addDays(date, inc);
+ }
+ return date;
+ }
- disableTextSelection(element.addClass('fc-grid'));
+ function dayDiff(d1, d2) { // d1 - d2
+ return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
+ }
- function renderGrid(r, c, colFormat, showNumbers) {
- rowCnt = r;
- colCnt = c;
+ function setYMD(date, y, m, d) {
+ if (y !== undefined && y != date.getFullYear()) {
+ date.setDate(1);
+ date.setMonth(0);
+ date.setFullYear(y);
+ }
+ if (m !== undefined && m != date.getMonth()) {
+ date.setDate(1);
+ date.setMonth(m);
+ }
+ if (d !== undefined) {
+ date.setDate(d);
+ }
+ }
- // update option-derived variables
- tm = options.theme ? 'ui' : 'fc';
- nwe = options.weekends ? 0 : 1;
- firstDay = options.firstDay;
- if (rtl = options.isRTL) {
- dis = -1;
- dit = colCnt - 1;
- } else {
- dis = 1;
- dit = 0;
+
+ /* Date Parsing
+ -----------------------------------------------------------------------------*/
+
+
+ function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
+ 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+(\.\d+)?$/)) { // a UNIX timestamp
+ return new Date(parseFloat(s) * 1000);
}
+ if (ignoreTimezone === undefined) {
+ ignoreTimezone = true;
+ }
+ return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
+ }
+ // TODO: never return invalid dates (like from new Date(<string>)), return
null instead
+ return null;
+ }
- var month = view.start.getMonth(),
- today = clearTime(new Date()),
- s, i, j, d = cloneDate(view.visStart);
- if (!tbody) { // first time, build all cells from scratch
+ function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
+ // derived from
http://delete.me.uk/2005/03/iso8601.html
+ // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
+ var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T
]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
+ if (!m) {
+ return null;
+ }
+ var date = new Date(m[1], 0, 1);
+ if (ignoreTimezone || !m[13]) {
+ var check = new Date(m[1], 0, 1, 9, 0);
+ if (m[3]) {
+ date.setMonth(m[3] - 1);
+ check.setMonth(m[3] - 1);
+ }
+ if (m[5]) {
+ date.setDate(m[5]);
+ check.setDate(m[5]);
+ }
+ fixDate(date, check);
+ if (m[7]) {
+ date.setHours(m[7]);
+ }
+ if (m[8]) {
+ date.setMinutes(m[8]);
+ }
+ if (m[10]) {
+ date.setSeconds(m[10]);
+ }
+ if (m[12]) {
+ date.setMilliseconds(Number("0." + m[12]) * 1000);
+ }
+ fixDate(date, check);
+ } else {
+ date.setUTCFullYear(
+ m[1],
+ m[3] ? m[3] - 1 : 0,
+ m[5] || 1
+ );
+ date.setUTCHours(
+ m[7] || 0,
+ m[8] || 0,
+ m[10] || 0,
+ m[12] ? Number("0." + m[12]) * 1000 : 0
+ );
+ if (m[14]) {
+ var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
+ offset *= m[15] == '-' ? 1 : -1;
+ date = new Date(+date + (offset * 60 * 1000));
+ }
+ }
+ return date;
+ }
- var table = $("<table/>").appendTo(element);
- s = "<thead><tr>";
- for (i = 0; i < colCnt; i++) {
- s += "<th class='fc-" +
- dayIDs[d.getDay()] + ' ' + // needs to be first
- tm + '-state-default' +
- (i == dit ? ' fc-leftmost' : '') +
- "'>" + formatDate(d, colFormat, options) +
"</th>";
- addDays(d, 1);
- if (nwe) {
- skipWeekend(d);
- }
+ function parseTime(s) { // returns minutes since start of day
+ if (typeof s == 'number') { // an hour
+ return s * 60;
+ }
+ if (typeof s == 'object') { // a Date object
+ return s.getHours() * 60 + s.getMinutes();
+ }
+ var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
+ if (m) {
+ var h = parseInt(m[1], 10);
+ if (m[3]) {
+ h %= 12;
+ if (m[3].toLowerCase().charAt(0) == 'p') {
+ h += 12;
}
- thead = $(s + "</tr></thead>").appendTo(table);
+ }
+ return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
+ }
+ }
- s = "<tbody>";
- d = cloneDate(view.visStart);
- for (i = 0; i < rowCnt; i++) {
- s += "<tr class='fc-week" + i +
"'>";
- for (j = 0; j < colCnt; j++) {
- s += "<td class='fc-" +
- dayIDs[d.getDay()] + ' ' + // needs to be first
- tm + '-state-default fc-day' + (i * colCnt + j)
+
- (j == dit ? ' fc-leftmost' : '') +
- (rowCnt > 1 && d.getMonth() != month ? '
fc-other-month' : '') +
- (+d == +today ?
- ' fc-today ' + tm +
'-state-highlight' :
- ' fc-not-today') + "'>"
+
- (showNumbers ? "<div
class='fc-day-number'>" + d.getDate() + "</div>" :
'') +
- "<div class='fc-day-content'><div
style='position:relative'> </div></div></td>";
- addDays(d, 1);
- if (nwe) {
- skipWeekend(d);
- }
- }
- s += "</tr>";
- }
- tbody = $(s + "</tbody>").appendTo(table);
- dayBind(tbody.find('td'));
- segmentContainer = $("<div
style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(element);
+ /* Date Formatting
+ -----------------------------------------------------------------------------*/
+// TODO: use same function formatDate(date, [date2], format, [options])
- } else { // NOT first time, reuse as many cells as possible
- clearEvents();
+ function formatDate(date, format, options) {
+ return formatDates(date, null, format, options);
+ }
- 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 < rowCnt; i++) {
- s += "<tr class='fc-week" + i +
"'>";
- for (j = 0; j < colCnt; j++) {
- s += "<td class='fc-" +
- dayIDs[d.getDay()] + ' ' + // needs to be
first
- tm + '-state-default fc-new fc-day' + (i *
colCnt + j) +
- (j == dit ? ' fc-leftmost' : '') +
"'>" +
- (showNumbers ? "<div
class='fc-day-number'></div>" : '') +
- "<div
class='fc-day-content'><div
style='position:relative'> </div></div>" +
- "</td>";
- addDays(d, 1);
- if (nwe) {
- skipWeekend(d);
+
+ function formatDates(date1, date2, format, options) {
+ options = options || defaults;
+ var date = date1,
+ otherDate = date2,
+ i, len = format.length, c,
+ i2, formatter,
+ res = '';
+ for (i = 0; i < len; i++) {
+ c = format.charAt(i);
+ if (c == "'") {
+ for (i2 = i + 1; i2 < len; i2++) {
+ if (format.charAt(i2) == "'") {
+ if (date) {
+ if (i2 == i + 1) {
+ res += "'";
+ } else {
+ res += format.substring(i + 1, i2);
}
+ i = i2;
}
- s += "</tr>";
+ break;
}
- tbody.append(s);
}
- dayBind(tbody.find('td.fc-new').removeClass('fc-new'));
-
- // 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');
+ }
+ else if (c == '(') {
+ for (i2 = i + 1; i2 < len; i2++) {
+ if (format.charAt(i2) == ')') {
+ var subres = formatDate(date, format.substring(i + 1, i2),
options);
+ if (parseInt(subres.replace(/\D/, ''), 10)) {
+ res += subres;
}
+ i = i2;
+ break;
}
- 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');
+ }
+ }
+ else if (c == '[') {
+ for (i2 = i + 1; i2 < len; i2++) {
+ if (format.charAt(i2) == ']') {
+ var subformat = format.substring(i + 1, i2);
+ var subres = formatDate(date, subformat, options);
+ if (subres != formatDate(otherDate, subformat, options)) {
+ res += subres;
+ }
+ i = i2;
+ break;
}
- td.find('div.fc-day-number').text(d.getDate());
- addDays(d, 1);
- if (nwe) {
- skipWeekend(d);
+ }
+ }
+ else if (c == '{') {
+ date = date2;
+ otherDate = date1;
+ }
+ else if (c == '}') {
+ date = date1;
+ otherDate = date2;
+ }
+ else {
+ for (i2 = len; i2 > i; 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;
+ }
- 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);
- if (nwe) {
- skipWeekend(d);
- }
- });
- // 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);
- if (nwe) {
- skipWeekend(d);
- }
- });
+ 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';
+ }
+ };
- }
- }
+ fc.applyAll = applyAll;
+
+ /* Event Date Math
+ -----------------------------------------------------------------------------*/
+
+
+ function exclEndDay(event) {
+ if (event.end) {
+ return _exclEndDay(event.end, event.allDay);
+ } else {
+ return addDays(cloneDate(event.start), 1);
}
+ }
- function setHeight(height) {
- viewHeight = height;
- var leftTDs = tbody.find('tr td:first-child'),
- tbodyHeight = viewHeight - thead.height(),
- 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);
+ function _exclEndDay(end, allDay) {
+ end = cloneDate(end);
+ return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) :
clearTime(end);
+ }
+
+
+ function segCmp(a, b) {
+ return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
+ }
+
+
+ function segsCollide(seg1, seg2) {
+ return seg1.end > seg2.start && seg1.start < seg2.end;
+ }
+
+
+ /* Event Sorting
+ -----------------------------------------------------------------------------*/
+
+
+// event rendering utilities
+ function sliceSegs(events, visEventEnds, start, end) {
+ var segs = [],
+ i, len = events.length, event,
+ eventStart, eventEnd,
+ segStart, segEnd,
+ isStart, isEnd;
+ for (i = 0; i < len; i++) {
+ event = events[i];
+ eventStart = event.start;
+ eventEnd = visEventEnds[i];
+ if (eventEnd > start && eventStart < end) {
+ if (eventStart < start) {
+ segStart = cloneDate(start);
+ isStart = false;
+ } else {
+ segStart = eventStart;
+ isStart = true;
+ }
+ if (eventEnd > end) {
+ segEnd = cloneDate(end);
+ isEnd = false;
+ } else {
+ segEnd = eventEnd;
+ isEnd = true;
+ }
+ segs.push({
+ event: event,
+ start: segStart,
+ end: segEnd,
+ isStart: isStart,
+ isEnd: isEnd,
+ msLength: segEnd - segStart
+ });
}
- if (tdHeightBug === undefined) {
- // bug in firefox where cell height includes padding
- var tr = tbody.find('tr:first'),
- td = tr.find('td:first');
- td.height(rowHeight1);
- tdHeightBug = rowHeight1 != td.height();
+ }
+ return segs.sort(segCmp);
+ }
+
+
+// event rendering calculation utilities
+ function stackSegs(segs) {
+ var levels = [],
+ i, len = segs.length, seg,
+ j, collide, k;
+ for (i = 0; i < len; i++) {
+ seg = segs[i];
+ j = 0; // the level index where seg should belong
+ while (true) {
+ collide = false;
+ if (levels[j]) {
+ for (k = 0; k < levels[j].length; k++) {
+ if (segsCollide(levels[j][k], seg)) {
+ collide = true;
+ break;
+ }
+ }
+ }
+ if (collide) {
+ j++;
+ } else {
+ break;
+ }
}
- if (tdHeightBug) {
- leftTDs.slice(0, -1).height(rowHeight1);
- leftTDs.slice(-1).height(rowHeight2);
+ if (levels[j]) {
+ levels[j].push(seg);
} else {
- setOuterHeight(leftTDs.slice(0, -1), rowHeight1);
- setOuterHeight(leftTDs.slice(-1), rowHeight2);
+ levels[j] = [seg];
}
}
+ return levels;
+ }
- function setWidth(width) {
- viewWidth = width;
- dayContentPositions.clear();
- setOuterWidth(
- thead.find('th').slice(0, -1),
- colWidth = Math.floor(viewWidth / colCnt)
- );
+ /* Event Element Binding
+ -----------------------------------------------------------------------------*/
+
+
+ function lazySegBind(container, segs, bindHandlers) {
+ container.unbind('mouseover').mouseover(function(ev) {
+ var parent = ev.target, e,
+ i, seg;
+ while (parent != this) {
+ e = parent;
+ parent = parent.parentNode;
+ }
+ if ((i = e._fci) !== undefined) {
+ e._fci = undefined;
+ seg = segs[i];
+ bindHandlers(seg.event, seg.element, seg);
+ $(ev.target).trigger(ev);
+ }
+ ev.stopPropagation();
+ });
+ }
+
+
+ /* Element Dimensions
+ -----------------------------------------------------------------------------*/
+
+
+ function setOuterWidth(element, width, includeMargins) {
+ for (var i = 0, e; i < element.length; i++) {
+ e = $(element[i]);
+ e.width(Math.max(0, width - hsides(e, includeMargins)));
}
+ }
- /* Event Rendering
- -----------------------------------------------------------------------------*/
+ function setOuterHeight(element, height, includeMargins) {
+ for (var i = 0, e; i < element.length; i++) {
+ e = $(element[i]);
+ e.height(Math.max(0, height - vsides(e, includeMargins)));
+ }
+ }
- function renderEvents(events) {
- view.reportEvents(cachedEvents = events);
- renderSegs(compileSegs(events));
+// TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
+
+
+ function hsides(element, includeMargins) {
+ return hpadding(element) + hborders(element) + (includeMargins ?
hmargins(element) : 0);
+ }
+
+
+ function hpadding(element) {
+ return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
+ (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
+ }
+
+
+ function hmargins(element) {
+ return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
+ (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
+ }
+
+
+ function hborders(element) {
+ return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0)
+
+ (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) ||
0);
+ }
+
+
+ function vsides(element, includeMargins) {
+ return vpadding(element) + vborders(element) + (includeMargins ?
vmargins(element) : 0);
+ }
+
+
+ function vpadding(element) {
+ return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
+ (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
+ }
+
+
+ function vmargins(element) {
+ return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
+ (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
+ }
+
+
+ function vborders(element) {
+ return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
+ (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) ||
0);
+ }
+
+
+ function setMinHeight(element, height) {
+ height = (typeof height == 'number' ? height + 'px' : height);
+ element.each(function(i, _element) {
+ _element.style.cssText += ';min-height:' + height +
';_height:' + height;
+ // why can't we just use .css() ? i forget
+ });
+ }
+
+
+ /* Misc Utils
+ -----------------------------------------------------------------------------*/
+
+
+//TODO: arraySlice
+//TODO: isFunction, grep ?
+
+
+ function noop() {
+ }
+
+
+ function cmp(a, b) {
+ return a - b;
+ }
+
+
+ function arrayMax(a) {
+ return Math.max.apply(Math, a);
+ }
+
+
+ function zeroPad(n) {
+ return (n < 10 ? '0' : '') + n;
+ }
+
+
+ function smartProperty(obj, name) { // get a camel-cased/namespaced property of an
object
+ if (obj[name] !== undefined) {
+ return obj[name];
}
+ var parts = name.split(/(?=[A-Z])/),
+ i = parts.length - 1, res;
+ for (; i >= 0; i--) {
+ res = obj[parts[i].toLowerCase()];
+ if (res !== undefined) {
+ return res;
+ }
+ }
+ return obj[''];
+ }
- function rerenderEvents(modifiedEventId) {
- clearEvents();
- renderSegs(compileSegs(cachedEvents), modifiedEventId);
+ function htmlEscape(s) {
+ return s.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/'/g, ''')
+ .replace(/"/g, '"')
+ .replace(/\n/g, '<br />');
+ }
+
+
+ function cssKey(_element) {
+ return _element.id + '/' + _element.className + '/' +
_element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
+ }
+
+
+ function disableTextSelection(element) {
+ element
+ .attr('unselectable', 'on')
+ .css('MozUserSelect', 'none')
+ .bind('selectstart.ui', function() {
+ return false;
+ });
+ }
+
+
+ /*
+ function enableTextSelection(element) {
+ element
+ .attr('unselectable', 'off')
+ .css('MozUserSelect', '')
+ .unbind('selectstart.ui');
+ }
+ */
+
+
+ function markFirstLast(e) {
+ e.children()
+ .removeClass('fc-first fc-last')
+ .filter(':first-child')
+ .addClass('fc-first')
+ .end()
+ .filter(':last-child')
+ .addClass('fc-last');
+ }
+
+
+ function setDayID(cell, date) {
+ cell.each(function(i, _cell) {
+ _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' +
dayIDs[date.getDay()]);
+ // TODO: make a way that doesn't rely on order of classes
+ });
+ }
+
+
+ function getSkinCss(event, opt) {
+ var source = event.source || {};
+ var eventColor = event.color;
+ var sourceColor = source.color;
+ var optionColor = opt('eventColor');
+ var backgroundColor =
+ event.backgroundColor ||
+ eventColor ||
+ source.backgroundColor ||
+ sourceColor ||
+ opt('eventBackgroundColor') ||
+ optionColor;
+ var borderColor =
+ event.borderColor ||
+ eventColor ||
+ source.borderColor ||
+ sourceColor ||
+ opt('eventBorderColor') ||
+ optionColor;
+ var textColor =
+ event.textColor ||
+ source.textColor ||
+ opt('eventTextColor');
+ var statements = [];
+ if (backgroundColor) {
+ statements.push('background-color:' + backgroundColor);
}
+ if (borderColor) {
+ statements.push('border-color:' + borderColor);
+ }
+ if (textColor) {
+ statements.push('color:' + textColor);
+ }
+ return statements.join(';');
+ }
- function clearEvents() {
- view._clearEvents(); // only clears the hashes
- segmentContainer.empty();
+ function applyAll(functions, thisObj, args) {
+ if ($.isFunction(functions)) {
+ functions = [ functions ];
}
+ if (functions) {
+ var i;
+ var ret;
+ for (i = 0; i < functions.length; i++) {
+ ret = functions[i].apply(thisObj, args) || ret;
+ }
+ return ret;
+ }
+ }
- function compileSegs(events) {
- var d1 = cloneDate(view.visStart),
- d2 = addDays(cloneDate(d1), colCnt),
- visEventsEnds = $.map(events, exclEndDay),
- i, row,
- j, level,
- k, seg,
- segs = [];
- for (i = 0; i < rowCnt; i++) {
- row = stackSegs(view.sliceSegs(events, visEventsEnds, d1, d2));
- for (j = 0; j < row.length; j++) {
- level = row[j];
- for (k = 0; k < level.length; k++) {
- seg = level[k];
- seg.row = i;
- seg.level = j;
- segs.push(seg);
- }
- }
- addDays(d1, 7);
- addDays(d2, 7);
+ function firstDefined() {
+ for (var i = 0; i < arguments.length; i++) {
+ if (arguments[i] !== undefined) {
+ return arguments[i];
}
- return segs;
}
+ }
- function renderSegs(segs, modifiedEventId) {
- _renderDaySegs(
- segs,
- rowCnt,
- view,
- 0,
- viewWidth,
- function(i) {
- return tbody.find('tr:eq(' + i + ')')
- },
- dayContentPositions.left,
- dayContentPositions.right,
- segmentContainer,
- bindSegHandlers,
- modifiedEventId
- );
+ fcViews.month = MonthView;
+
+ function MonthView(element, calendar) {
+ var t = this;
+
+
+ // exports
+ t.render = render;
+
+
+ // imports
+ BasicView.call(t, element, calendar, 'month');
+ var opt = t.opt;
+ var renderBasic = t.renderBasic;
+ var formatDate = calendar.formatDate;
+
+
+ function render(date, delta) {
+ if (delta) {
+ addMonths(date, delta);
+ date.setDate(1);
+ }
+ var start = cloneDate(date, true);
+ start.setDate(1);
+ var end = addMonths(cloneDate(start), 1);
+ var visStart = cloneDate(start);
+ var visEnd = cloneDate(end);
+ var firstDay = opt('firstDay');
+ var nwe = opt('weekends') ? 0 : 1;
+ if (nwe) {
+ skipWeekend(visStart);
+ skipWeekend(visEnd, -1, true);
+ }
+ addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
+ addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
+ var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
+ if (opt('weekMode') == 'fixed') {
+ addDays(visEnd, (6 - rowCnt) * 7);
+ rowCnt = 6;
+ }
+ t.title = formatDate(start, opt('titleFormat'));
+ t.start = start;
+ t.end = end;
+ t.visStart = visStart;
+ t.visEnd = visEnd;
+ renderBasic(6, rowCnt, nwe ? 5 : 7, true);
}
- function bindSegHandlers(event, eventElement, seg) {
- view.eventElementHandlers(event, eventElement);
- if (event.editable || event.editable === undefined &&
options.editable) {
- draggableEvent(event, eventElement);
- if (seg.isEnd) {
- view.resizableDayEvent(event, eventElement, colWidth);
- }
+ }
+
+ fcViews.basicWeek = BasicWeekView;
+
+ function BasicWeekView(element, calendar) {
+ var t = this;
+
+
+ // exports
+ t.render = render;
+
+
+ // imports
+ BasicView.call(t, element, calendar, 'basicWeek');
+ var opt = t.opt;
+ var renderBasic = t.renderBasic;
+ var formatDates = calendar.formatDates;
+
+
+ function render(date, delta) {
+ if (delta) {
+ addDays(date, delta * 7);
}
+ var start = addDays(cloneDate(date), -((date.getDay() -
opt('firstDay') + 7) % 7));
+ var end = addDays(cloneDate(start), 7);
+ var visStart = cloneDate(start);
+ var visEnd = cloneDate(end);
+ var weekends = opt('weekends');
+ if (!weekends) {
+ skipWeekend(visStart);
+ skipWeekend(visEnd, -1, true);
+ }
+ t.title = formatDates(
+ visStart,
+ addDays(cloneDate(visEnd), -1),
+ opt('titleFormat')
+ );
+ t.start = start;
+ t.end = end;
+ t.visStart = visStart;
+ t.visEnd = visEnd;
+ renderBasic(1, 1, weekends ? 7 : 5, false);
}
- /* Event Dragging
- -----------------------------------------------------------------------------*/
+ }
+ fcViews.basicDay = BasicDayView;
- function draggableEvent(event, eventElement) {
- if (!options.disableDragging && eventElement.draggable) {
- var matrix,
- dayDelta = 0;
- eventElement.draggable({
- zIndex: 9,
- delay: 50,
- opacity: view.option('dragOpacity'),
- revertDuration: options.dragRevertDuration,
- start: function(ev, ui) {
- view.hideEvents(event, eventElement);
- view.trigger('eventDragStart', eventElement, event, ev,
ui);
- matrix = buildDayMatrix(function(cell) {
- eventElement.draggable('option', 'revert',
!cell || !cell.rowDelta && !cell.colDelta);
- clearOverlays();
- if (cell) {
- dayDelta = cell.rowDelta * 7 + cell.colDelta * dis;
- renderDayOverlays(
- matrix,
- addDays(cloneDate(event.start), dayDelta),
- addDays(exclEndDay(event), dayDelta)
- );
- } else {
- dayDelta = 0;
- }
- });
- matrix.mouse(ev);
- },
- drag: function(ev) {
- matrix.mouse(ev);
- },
- stop: function(ev, ui) {
- clearOverlays();
- view.trigger('eventDragStop', eventElement, event, ev,
ui);
- if (dayDelta) {
- eventElement.find('a').removeAttr('href'); //
prevents safari from visiting the link
- view.eventDrop(this, event, dayDelta, 0, event.allDay, ev,
ui);
- } else {
- if ($.browser.msie) {
- eventElement.css('filter', ''); // clear
IE opacity side-effects
- }
- view.showEvents(event, eventElement);
- }
- }
- });
+//TODO: when calendar's date starts out on a weekend, shouldn't happen
+
+
+ function BasicDayView(element, calendar) {
+ var t = this;
+
+
+ // exports
+ t.render = render;
+
+
+ // imports
+ BasicView.call(t, element, calendar, 'basicDay');
+ var opt = t.opt;
+ var renderBasic = t.renderBasic;
+ var formatDate = calendar.formatDate;
+
+
+ function render(date, delta) {
+ if (delta) {
+ addDays(date, delta);
+ if (!opt('weekends')) {
+ skipWeekend(date, delta < 0 ? -1 : 1);
+ }
}
+ t.title = formatDate(date, opt('titleFormat'));
+ t.start = t.visStart = cloneDate(date, true);
+ t.end = t.visEnd = addDays(cloneDate(t.start), 1);
+ renderBasic(1, 1, 1, false);
}
- /* Day clicking and binding
- ---------------------------------------------------------*/
+ }
- function dayBind(days) {
- days.click(dayClick)
- .mousedown(selectionMousedown);
+ setDefaults({
+ weekMode: 'fixed'
+ });
+
+
+ function BasicView(element, calendar, viewName) {
+ var t = this;
+
+
+ // exports
+ t.renderBasic = renderBasic;
+ t.setHeight = setHeight;
+ t.setWidth = setWidth;
+ t.renderDayOverlay = renderDayOverlay;
+ t.defaultSelectionEnd = defaultSelectionEnd;
+ t.renderSelection = renderSelection;
+ t.clearSelection = clearSelection;
+ t.reportDayClick = reportDayClick; // for selection (kinda hacky)
+ t.dragStart = dragStart;
+ t.dragStop = dragStop;
+ t.defaultEventEnd = defaultEventEnd;
+ t.getHoverListener = function() {
+ return hoverListener
+ };
+ t.colContentLeft = colContentLeft;
+ t.colContentRight = colContentRight;
+ t.dayOfWeekCol = dayOfWeekCol;
+ t.dateCell = dateCell;
+ t.cellDate = cellDate;
+ t.cellIsAllDay = function() {
+ return true
+ };
+ t.allDayRow = allDayRow;
+ t.allDayBounds = allDayBounds;
+ t.getRowCnt = function() {
+ return rowCnt
+ };
+ t.getColCnt = function() {
+ return colCnt
+ };
+ t.getColWidth = function() {
+ return colWidth
+ };
+ t.getDaySegmentContainer = function() {
+ return daySegmentContainer
+ };
+
+
+ // imports
+ View.call(t, element, calendar, viewName);
+ OverlayManager.call(t);
+ SelectionManager.call(t);
+ BasicEventRenderer.call(t);
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var clearEvents = t.clearEvents;
+ var renderOverlay = t.renderOverlay;
+ var clearOverlays = t.clearOverlays;
+ var daySelectionMousedown = t.daySelectionMousedown;
+ var formatDate = calendar.formatDate;
+
+
+ // locals
+
+ var head;
+ var headCells;
+ var body;
+ var bodyRows;
+ var bodyCells;
+ var bodyFirstCells;
+ var bodyCellTopInners;
+ var daySegmentContainer;
+
+ var viewWidth;
+ var viewHeight;
+ var colWidth;
+
+ var rowCnt, colCnt;
+ var coordinateGrid;
+ var hoverListener;
+ var colContentPositions;
+
+ var rtl, dis, dit;
+ var firstDay;
+ var nwe;
+ var tm;
+ var colFormat;
+
+
+ /* Rendering
+ ------------------------------------------------------------*/
+
+
+ disableTextSelection(element.addClass('fc-grid'));
+
+
+ function renderBasic(maxr, r, c, showNumbers) {
+ rowCnt = r;
+ colCnt = c;
+ updateOptions();
+ var firstTime = !body;
+ if (firstTime) {
+ buildSkeleton(maxr, showNumbers);
+ } else {
+ clearEvents();
+ }
+ updateCells(firstTime);
}
- function dayClick(ev) {
- if (!view.option('selectable')) { // SelectionManager will worry
about dayClick
- var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
- date = addDays(
- cloneDate(view.visStart),
- Math.floor(n / colCnt) * 7 + n % colCnt
- );
- // TODO: what about weekends in middle of week?
- view.trigger('dayClick', this, date, true, ev);
+
+ function updateOptions() {
+ rtl = opt('isRTL');
+ if (rtl) {
+ dis = -1;
+ dit = colCnt - 1;
+ } else {
+ dis = 1;
+ dit = 0;
}
+ firstDay = opt('firstDay');
+ nwe = opt('weekends') ? 0 : 1;
+ tm = opt('theme') ? 'ui' : 'fc';
+ colFormat = opt('columnFormat');
}
- /* Selecting
- --------------------------------------------------------*/
+ function buildSkeleton(maxRowCnt, showNumbers) {
+ var s;
+ var headerClass = tm + "-widget-header";
+ var contentClass = tm + "-widget-content";
+ var i, j;
+ var table;
- selectionManager = new SelectionManager(
- view,
- unselect,
- function(startDate, endDate, allDay) {
- renderDayOverlays(
- selectionMatrix,
- startDate,
- addDays(cloneDate(endDate), 1)
- );
- },
- clearOverlays
- );
+ s =
+ "<table class='fc-border-separate'
style='width:100%' cellspacing='0'>" +
+ "<thead>" +
+ "<tr>";
+ for (i = 0; i < colCnt; i++) {
+ s +=
+ "<th class='fc- " + headerClass +
"'/>"; // need fc- for setDayID
+ }
+ s +=
+ "</tr>" +
+ "</thead>" +
+ "<tbody>";
+ for (i = 0; i < maxRowCnt; i++) {
+ s +=
+ "<tr class='fc-week" + i +
"'>";
+ for (j = 0; j < colCnt; j++) {
+ s +=
+ "<td class='fc- " + contentClass + "
fc-day" + (i * colCnt + j) + "'>" + // need fc- for setDayID
+ "<div>" +
+ (showNumbers ?
+ "<div
class='fc-day-number'/>" :
+ ''
+ ) +
+ "<div
class='fc-day-content'>" +
+ "<div
style='position:relative'> </div>" +
+ "</div>" +
+ "</div>" +
+ "</td>";
+ }
+ s +=
+ "</tr>";
+ }
+ s +=
+ "</tbody>" +
+ "</table>";
+ table = $(s).appendTo(element);
- function selectionMousedown(ev) {
- if (view.option('selectable')) {
- selectionMatrix = buildDayMatrix(function(cell) {
- if (cell) {
- var d = cellDate(cell.row, cell.col);
- selectionManager.drag(d, d, true);
+ head = table.find('thead');
+ headCells = head.find('th');
+ body = table.find('tbody');
+ bodyRows = body.find('tr');
+ bodyCells = body.find('td');
+ bodyFirstCells = bodyCells.filter(':first-child');
+ bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
+
+ markFirstLast(head.add(head.find('tr'))); // marks first+last
tr/th's
+ markFirstLast(bodyRows); // marks first+last td's
+ bodyRows.eq(0).addClass('fc-first'); // fc-last is done in
updateCells
+
+ dayBind(bodyCells);
+
+ daySegmentContainer =
+ $("<div
style='position:absolute;z-index:8;top:0;left:0'/>")
+ .appendTo(element);
+ }
+
+
+ function updateCells(firstTime) {
+ var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks
need updating?
+ var month = t.start.getMonth();
+ var today = clearTime(new Date());
+ var cell;
+ var date;
+ var row;
+
+ if (dowDirty) {
+ headCells.each(function(i, _cell) {
+ cell = $(_cell);
+ date = indexDate(i);
+ cell.html(formatDate(date, colFormat));
+ setDayID(cell, date);
+ });
+ }
+
+ bodyCells.each(function(i, _cell) {
+ cell = $(_cell);
+ date = indexDate(i);
+ if (date.getMonth() == month) {
+ cell.removeClass('fc-other-month');
+ } else {
+ cell.addClass('fc-other-month');
+ }
+ if (+date == +today) {
+ cell.addClass(tm + '-state-highlight fc-today');
+ } else {
+ cell.removeClass(tm + '-state-highlight fc-today');
+ }
+ cell.find('div.fc-day-number').text(date.getDate());
+ if (dowDirty) {
+ setDayID(cell, date);
+ }
+ });
+
+ bodyRows.each(function(i, _row) {
+ row = $(_row);
+ if (i < rowCnt) {
+ row.show();
+ if (i == rowCnt - 1) {
+ row.addClass('fc-last');
} else {
- selectionManager.drag();
+ row.removeClass('fc-last');
}
- });
- documentDragHelp(
- function(ev) {
- selectionMatrix.mouse(ev);
- },
- function(ev) {
- selectionManager.dragStop(ev);
- }
- );
- selectionManager.dragStart(ev);
- selectionMatrix.mouse(ev);
- return false; // prevent auto-unselect and text selection
- }
+ } else {
+ row.hide();
+ }
+ });
}
- documentUnselectAuto(view, unselect);
- view.select = function(start, end, allDay) {
- if (!end) {
- end = cloneDate(start);
+ function setHeight(height) {
+ viewHeight = height;
+
+ var bodyHeight = viewHeight - head.height();
+ var rowHeight;
+ var rowHeightLast;
+ var cell;
+
+ if (opt('weekMode') == 'variable') {
+ rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt == 1 ? 2 :
6));
+ } else {
+ rowHeight = Math.floor(bodyHeight / rowCnt);
+ rowHeightLast = bodyHeight - rowHeight * (rowCnt - 1);
}
- selectionMatrix = buildDayMatrix();
- selectionManager.select(start, end, allDay);
- };
- function unselect() {
- selectionManager.unselect();
+ bodyFirstCells.each(function(i, _cell) {
+ if (i < rowCnt) {
+ cell = $(_cell);
+ setMinHeight(
+ cell.find('> div'),
+ (i == rowCnt - 1 ? rowHeightLast : rowHeight) - vsides(cell)
+ );
+ }
+ });
+
}
- view.unselect = unselect;
+ function setWidth(width) {
+ viewWidth = width;
+ colContentPositions.clear();
+ colWidth = Math.floor(viewWidth / colCnt);
+ setOuterWidth(headCells.slice(0, -1), colWidth);
+ }
+
+ /* Day clicking and binding
+ -----------------------------------------------------------*/
+
+
+ function dayBind(days) {
+ days.click(dayClick)
+ .mousedown(daySelectionMousedown);
+ }
+
+
+ function dayClick(ev) {
+ if (!opt('selectable')) { // if selectable, SelectionManager will
worry about dayClick
+ var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO:
maybe use .data
+ var date = indexDate(index);
+ trigger('dayClick', this, date, true, ev);
+ }
+ }
+
+
/* Semi-transparent Overlay Helpers
------------------------------------------------------*/
- function renderDayOverlays(matrix, overlayStart, overlayEnd) { // overlayEnd is
exclusive
- var rowStart = cloneDate(view.visStart);
+
+ function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { //
overlayEnd is exclusive
+ if (refreshCoordinateGrid) {
+ coordinateGrid.build();
+ }
+ var rowStart = cloneDate(t.visStart);
var rowEnd = addDays(cloneDate(rowStart), colCnt);
for (var i = 0; i < rowCnt; i++) {
var stretchStart = new Date(Math.max(rowStart, overlayStart));
@@ -1481,202 +2434,389 @@
colStart = dayDiff(stretchStart, rowStart);
colEnd = dayDiff(stretchEnd, rowStart);
}
- var rect = matrix.rect(i, colStart, i + 1, colEnd, element);
dayBind(
- view.renderOverlay(rect, element)
- );
+ renderCellOverlay(i, colStart, i, colEnd - 1)
+ );
}
addDays(rowStart, 7);
addDays(rowEnd, 7);
}
}
- function clearOverlays() {
- view.clearOverlays();
+
+ function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
+ var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
+ return renderOverlay(rect, element);
}
- /* Utils
- ---------------------------------------------------*/
+ /* Selection
+ -----------------------------------------------------------------------*/
- function buildDayMatrix(changeCallback) {
- var tds = tbody.find('tr:first td');
- if (rtl) {
- tds = $(tds.get().reverse());
- }
- return new HoverMatrix(tbody.find('tr'), tds, changeCallback);
+ function defaultSelectionEnd(startDate, allDay) {
+ return cloneDate(startDate);
}
- function cellDate(r, c) { // convert r,c to date
- return addDays(cloneDate(view.visStart), r * 7 + c * dis + dit);
- // TODO: what about weekends in middle of week?
+ function renderSelection(startDate, endDate, allDay) {
+ renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild
every time???
}
- }
+ function clearSelection() {
+ clearOverlays();
+ }
- function _renderDaySegs(segs, rowCnt, view, minLeft, maxLeft, getRow, dayContentLeft,
dayContentRight, segmentContainer, bindSegHandlers, modifiedEventId) {
+ function reportDayClick(date, allDay, ev) {
+ var cell = dateCell(date);
+ var _element = bodyCells[cell.row * colCnt + cell.col];
+ trigger('dayClick', _element, date, allDay, ev);
+ }
- var options = view.options,
- rtl = options.isRTL,
- i, segCnt = segs.length, seg,
- event,
- className,
- left, right,
- html = '',
- eventElements,
- eventElement,
- triggerRes,
- hsideCache = {},
- vmarginCache = {},
- key, val,
- rowI, top, levelI, levelHeight,
- rowDivs = [],
- rowDivTops = [];
- // calculate desired position/dimensions, create html
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- event = seg.event;
- className = 'fc-event fc-event-hori ';
- if (rtl) {
- if (seg.isStart) {
- className += 'fc-corner-right ';
+ /* External Dragging
+ -----------------------------------------------------------------------*/
+
+
+ function dragStart(_dragElement, ev, ui) {
+ hoverListener.start(function(cell) {
+ clearOverlays();
+ if (cell) {
+ renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
}
- if (seg.isEnd) {
- className += 'fc-corner-left ';
- }
- left = seg.isEnd ? dayContentLeft(seg.end.getDay() - 1) : minLeft;
- right = seg.isStart ? dayContentRight(seg.start.getDay()) : maxLeft;
- } else {
- if (seg.isStart) {
- className += 'fc-corner-left ';
- }
- if (seg.isEnd) {
- className += 'fc-corner-right ';
- }
- left = seg.isStart ? dayContentLeft(seg.start.getDay()) : minLeft;
- right = seg.isEnd ? dayContentRight(seg.end.getDay() - 1) : maxLeft;
+ }, ev);
+ }
+
+
+ function dragStop(_dragElement, ev, ui) {
+ var cell = hoverListener.stop();
+ clearOverlays();
+ if (cell) {
+ var d = cellDate(cell);
+ trigger('drop', _dragElement, d, true, ev, ui);
}
- html +=
- "<div class='" + className +
event.className.join(' ') + "'
style='position:absolute;z-index:8;left:" + left + "px'>" +
- "<a" + (event.url ? " href='" +
htmlEscape(event.url) + "'" : '') + ">" +
- (!event.allDay && seg.isStart ?
- "<span
class='fc-event-time'>" +
- htmlEscape(formatDates(event.start,
event.end, view.option('timeFormat'), options)) +
- "</span>"
- : '') +
- "<span class='fc-event-title'>" +
htmlEscape(event.title) + "</span>" +
- "</a>" +
- ((event.editable || event.editable === undefined &&
options.editable) && !options.disableResizing && $.fn.resizable ?
- "<div class='ui-resizable-handle
ui-resizable-" + (rtl ? 'w' : 'e') +
"'></div>"
- : '') +
- "</div>";
- seg.left = left;
- seg.outerWidth = right - left;
}
- segmentContainer[0].innerHTML = html; // faster than html()
- eventElements = segmentContainer.children();
- // retrieve elements, run through eventRender callback, bind handlers
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- eventElement = $(eventElements[i]); // faster than eq()
- event = seg.event;
- triggerRes = view.trigger('eventRender', event, event,
eventElement);
- if (triggerRes === false) {
- eventElement.remove();
- } else {
- if (triggerRes && triggerRes !== true) {
- eventElement.remove();
- eventElement = $(triggerRes)
- .css({
- position: 'absolute',
- left: seg.left
- })
- .appendTo(segmentContainer);
+
+ /* Utilities
+ --------------------------------------------------------*/
+
+
+ function defaultEventEnd(event) {
+ return cloneDate(event.start);
+ }
+
+
+ coordinateGrid = new CoordinateGrid(function(rows, cols) {
+ var e, n, p;
+ headCells.each(function(i, _e) {
+ e = $(_e);
+ n = e.offset().left;
+ if (i) {
+ p[1] = n;
}
- seg.element = eventElement;
- if (event._id === modifiedEventId) {
- bindSegHandlers(event, eventElement, seg);
- } else {
- eventElement[0]._fci = i; // for lazySegBind
+ p = [n];
+ cols[i] = p;
+ });
+ p[1] = n + e.outerWidth();
+ bodyRows.each(function(i, _e) {
+ if (i < rowCnt) {
+ e = $(_e);
+ n = e.offset().top;
+ if (i) {
+ p[1] = n;
+ }
+ p = [n];
+ rows[i] = p;
}
- view.reportEventElement(event, eventElement);
- }
+ });
+ p[1] = n + e.outerHeight();
+ });
+
+
+ hoverListener = new HoverListener(coordinateGrid);
+
+
+ colContentPositions = new HorizontalPositionCache(function(col) {
+ return bodyCellTopInners.eq(col);
+ });
+
+
+ function colContentLeft(col) {
+ return colContentPositions.left(col);
}
- lazySegBind(segmentContainer, segs, bindSegHandlers);
- // record event horizontal sides
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- if (eventElement = seg.element) {
- val = hsideCache[key = seg.key = cssKey(eventElement[0])];
- seg.hsides = val === undefined ? (hsideCache[key] =
hsides(eventElement[0], true)) : val;
- }
+ function colContentRight(col) {
+ return colContentPositions.right(col);
}
- // set event widths
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- if (eventElement = seg.element) {
- eventElement[0].style.width = seg.outerWidth - seg.hsides +
'px';
- }
+
+ function dateCell(date) {
+ return {
+ row: Math.floor(dayDiff(date, t.visStart) / 7),
+ col: dayOfWeekCol(date.getDay())
+ };
}
- // record event heights
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- if (eventElement = seg.element) {
- val = vmarginCache[key = seg.key];
- seg.outerHeight = eventElement[0].offsetHeight + (
- val === undefined ? (vmarginCache[key] =
vmargins(eventElement[0])) : val
- );
- }
+
+ function cellDate(cell) {
+ return _cellDate(cell.row, cell.col);
}
- // set row heights, calculate event tops (in relation to row top)
- for (i = 0,rowI = 0; rowI < rowCnt; rowI++) {
- top = levelI = levelHeight = 0;
- while (i < segCnt && (seg = segs[i]).row == rowI) {
- if (seg.level != levelI) {
- top += levelHeight;
- levelHeight = 0;
- levelI++;
+
+ function _cellDate(row, col) {
+ return addDays(cloneDate(t.visStart), row * 7 + col * dis + dit);
+ // what about weekends in middle of week?
+ }
+
+
+ function indexDate(index) {
+ return _cellDate(Math.floor(index / colCnt), index % colCnt);
+ }
+
+
+ function dayOfWeekCol(dayOfWeek) {
+ return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis +
dit;
+ }
+
+
+ function allDayRow(i) {
+ return bodyRows.eq(i);
+ }
+
+
+ function allDayBounds(i) {
+ return {
+ left: 0,
+ right: viewWidth
+ };
+ }
+
+
+ }
+
+ function BasicEventRenderer() {
+ var t = this;
+
+
+ // exports
+ t.renderEvents = renderEvents;
+ t.compileDaySegs = compileSegs; // for DayEventRenderer
+ t.clearEvents = clearEvents;
+ t.bindDaySeg = bindDaySeg;
+
+
+ // imports
+ DayEventRenderer.call(t);
+ var opt = t.opt;
+ var trigger = t.trigger;
+ //var setOverflowHidden = t.setOverflowHidden;
+ var isEventDraggable = t.isEventDraggable;
+ var isEventResizable = t.isEventResizable;
+ var reportEvents = t.reportEvents;
+ var reportEventClear = t.reportEventClear;
+ var eventElementHandlers = t.eventElementHandlers;
+ var showEvents = t.showEvents;
+ var hideEvents = t.hideEvents;
+ var eventDrop = t.eventDrop;
+ var getDaySegmentContainer = t.getDaySegmentContainer;
+ var getHoverListener = t.getHoverListener;
+ var renderDayOverlay = t.renderDayOverlay;
+ var clearOverlays = t.clearOverlays;
+ var getRowCnt = t.getRowCnt;
+ var getColCnt = t.getColCnt;
+ var renderDaySegs = t.renderDaySegs;
+ var resizableDayEvent = t.resizableDayEvent;
+
+
+ /* Rendering
+ --------------------------------------------------------------------*/
+
+
+ function renderEvents(events, modifiedEventId) {
+ reportEvents(events);
+ renderDaySegs(compileSegs(events), modifiedEventId);
+ }
+
+
+ function clearEvents() {
+ reportEventClear();
+ getDaySegmentContainer().empty();
+ }
+
+
+ function compileSegs(events) {
+ var rowCnt = getRowCnt(),
+ colCnt = getColCnt(),
+ d1 = cloneDate(t.visStart),
+ d2 = addDays(cloneDate(d1), colCnt),
+ visEventsEnds = $.map(events, exclEndDay),
+ i, row,
+ j, level,
+ k, seg,
+ segs = [];
+ for (i = 0; i < rowCnt; i++) {
+ row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
+ for (j = 0; j < row.length; j++) {
+ level = row[j];
+ for (k = 0; k < level.length; k++) {
+ seg = level[k];
+ seg.row = i;
+ seg.level = j; // not needed anymore
+ segs.push(seg);
+ }
}
- levelHeight = Math.max(levelHeight, seg.outerHeight || 0);
- seg.top = top;
- i++;
+ addDays(d1, 7);
+ addDays(d2, 7);
}
- rowDivs[rowI] = getRow(rowI).find('td:first div.fc-day-content >
div')// optimal selector?
- .height(top + levelHeight);
+ return segs;
}
- // calculate row tops
- for (rowI = 0; rowI < rowCnt; rowI++) {
- rowDivTops[rowI] = rowDivs[rowI][0].offsetTop;
+
+ function bindDaySeg(event, eventElement, seg) {
+ if (isEventDraggable(event)) {
+ draggableDayEvent(event, eventElement);
+ }
+ if (seg.isEnd && isEventResizable(event)) {
+ resizableDayEvent(event, eventElement, seg);
+ }
+ eventElementHandlers(event, eventElement);
+ // needs to be after, because resizableDayEvent might
stopImmediatePropagation on click
}
- // set event tops
- for (i = 0; i < segCnt; i++) {
- seg = segs[i];
- if (eventElement = seg.element) {
- eventElement[0].style.top = rowDivTops[seg.row] + seg.top +
'px';
- event = seg.event;
- view.trigger('eventAfterRender', event, event, eventElement);
+
+ /* Dragging
+ ----------------------------------------------------------------------------*/
+
+
+ function draggableDayEvent(event, eventElement) {
+ var hoverListener = getHoverListener();
+ var dayDelta;
+ eventElement.draggable({
+ zIndex: 9,
+ delay: 50,
+ opacity: opt('dragOpacity'),
+ revertDuration: opt('dragRevertDuration'),
+ start: function(ev, ui) {
+ trigger('eventDragStart', eventElement, event, ev, ui);
+ hideEvents(event, eventElement);
+ hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
+ eventElement.draggable('option', 'revert', !cell
|| !rowDelta && !colDelta);
+ clearOverlays();
+ if (cell) {
+ //setOverflowHidden(true);
+ dayDelta = rowDelta * 7 + colDelta * (opt('isRTL') ?
-1 : 1);
+ renderDayOverlay(
+ addDays(cloneDate(event.start), dayDelta),
+ addDays(exclEndDay(event), dayDelta)
+ );
+ } else {
+ //setOverflowHidden(false);
+ dayDelta = 0;
+ }
+ }, ev, 'drag');
+ },
+ stop: function(ev, ui) {
+ hoverListener.stop();
+ clearOverlays();
+ trigger('eventDragStop', eventElement, event, ev, ui);
+ if (dayDelta) {
+ eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
+ } else {
+ eventElement.css('filter', ''); // clear IE
opacity side-effects
+ showEvents(event, eventElement);
+ }
+ //setOverflowHidden(false);
+ }
+ });
+ }
+
+
+ }
+
+ fcViews.agendaWeek = AgendaWeekView;
+
+ function AgendaWeekView(element, calendar) {
+ var t = this;
+
+
+ // exports
+ t.render = render;
+
+
+ // imports
+ AgendaView.call(t, element, calendar, 'agendaWeek');
+ var opt = t.opt;
+ var renderAgenda = t.renderAgenda;
+ var formatDates = calendar.formatDates;
+
+
+ function render(date, delta) {
+ if (delta) {
+ addDays(date, delta * 7);
}
+ var start = addDays(cloneDate(date), -((date.getDay() -
opt('firstDay') + 7) % 7));
+ var end = addDays(cloneDate(start), 7);
+ var visStart = cloneDate(start);
+ var visEnd = cloneDate(end);
+ var weekends = opt('weekends');
+ if (!weekends) {
+ skipWeekend(visStart);
+ skipWeekend(visEnd, -1, true);
+ }
+ t.title = formatDates(
+ visStart,
+ addDays(cloneDate(visEnd), -1),
+ opt('titleFormat')
+ );
+ t.start = start;
+ t.end = end;
+ t.visStart = visStart;
+ t.visEnd = visEnd;
+ renderAgenda(weekends ? 7 : 5);
}
+
}
+ fcViews.agendaDay = AgendaDayView;
- /* Agenda Views: agendaWeek/agendaDay
- -----------------------------------------------------------------------------*/
+ function AgendaDayView(element, calendar) {
+ var t = this;
+
+ // exports
+ t.render = render;
+
+
+ // imports
+ AgendaView.call(t, element, calendar, 'agendaDay');
+ var opt = t.opt;
+ var renderAgenda = t.renderAgenda;
+ var formatDate = calendar.formatDate;
+
+
+ function render(date, delta) {
+ if (delta) {
+ addDays(date, delta);
+ if (!opt('weekends')) {
+ skipWeekend(date, delta < 0 ? -1 : 1);
+ }
+ }
+ var start = cloneDate(date, true);
+ var end = addDays(cloneDate(start), 1);
+ t.title = formatDate(date, opt('titleFormat'));
+ t.start = t.visStart = start;
+ t.end = t.visEnd = end;
+ renderAgenda(1);
+ }
+
+
+ }
+
setDefaults({
allDaySlot: true,
allDayText: 'all-day',
@@ -1694,284 +2834,354 @@
maxTime: 24
});
- views.agendaWeek = function(element, options) {
- return new Agenda(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta * 7);
- }
- var visStart = this.visStart = cloneDate(
- this.start = addDays(cloneDate(date), -((date.getDay() -
options.firstDay + 7) % 7))
- ),
- visEnd = this.visEnd = cloneDate(
- this.end = addDays(cloneDate(visStart), 7)
- );
- if (!options.weekends) {
- skipWeekend(visStart);
- skipWeekend(visEnd, -1, true);
- }
- this.title = formatDates(
- visStart,
- addDays(cloneDate(visEnd), -1),
- this.option('titleFormat'),
- options
- );
- this.renderAgenda(
- options.weekends ? 7 : 5,
- this.option('columnFormat')
- );
- }
- });
- };
- views.agendaDay = function(element, options) {
- return new Agenda(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta);
- if (!options.weekends) {
- skipWeekend(date, delta < 0 ? -1 : 1);
- }
- }
- 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,
- this.option('columnFormat')
- );
- }
- });
- };
+// TODO: make it work in quirks mode (event corners, all-day height)
+// TODO: test liquid width, especially in IE6
- function Agenda(element, options, methods) {
- var head, body, bodyContent, bodyTable, bg,
- colCnt,
- slotCnt = 0, // spanning all the way across
- axisWidth, colWidth, slotHeight,
- viewWidth, viewHeight,
- savedScrollTop,
- cachedEvents = [],
- daySegmentContainer,
- slotSegmentContainer,
- tm, firstDay,
- nwe, // no weekends (int)
- rtl, dis, dit, // day index sign / translate
- minMinute, maxMinute,
- colContentPositions = new HorizontalPositionCache(function(col) {
- return bg.find('td:eq(' + col + ') div div');
- }),
- slotTopCache = {},
- daySelectionManager,
- slotSelectionManager,
- selectionHelper,
- selectionMatrix,
- // ...
+ function AgendaView(element, calendar, viewName) {
+ var t = this;
- view = $.extend(this, viewMethods, methods, {
- renderAgenda: renderAgenda,
- renderEvents: renderEvents,
- rerenderEvents: rerenderEvents,
- clearEvents: clearEvents,
- setHeight: setHeight,
- setWidth: setWidth,
- beforeHide: function() {
- savedScrollTop = body.scrollTop();
- },
- afterShow: function() {
- body.scrollTop(savedScrollTop);
- },
- defaultEventEnd: function(event) {
- var start = cloneDate(event.start);
- if (event.allDay) {
- return start;
- }
- return addMinutes(start, options.defaultEventMinutes);
- }
- });
- view.init(element, options);
+ // exports
+ t.renderAgenda = renderAgenda;
+ t.setWidth = setWidth;
+ t.setHeight = setHeight;
+ t.beforeHide = beforeHide;
+ t.afterShow = afterShow;
+ t.defaultEventEnd = defaultEventEnd;
+ t.timePosition = timePosition;
+ t.dayOfWeekCol = dayOfWeekCol;
+ t.dateCell = dateCell;
+ t.cellDate = cellDate;
+ t.cellIsAllDay = cellIsAllDay;
+ t.allDayRow = getAllDayRow;
+ t.allDayBounds = allDayBounds;
+ t.getHoverListener = function() {
+ return hoverListener
+ };
+ t.colContentLeft = colContentLeft;
+ t.colContentRight = colContentRight;
+ t.getDaySegmentContainer = function() {
+ return daySegmentContainer
+ };
+ t.getSlotSegmentContainer = function() {
+ return slotSegmentContainer
+ };
+ t.getMinMinute = function() {
+ return minMinute
+ };
+ t.getMaxMinute = function() {
+ return maxMinute
+ };
+ t.getBodyContent = function() {
+ return slotContent
+ }; // !!??
+ t.getRowCnt = function() {
+ return 1
+ };
+ t.getColCnt = function() {
+ return colCnt
+ };
+ t.getColWidth = function() {
+ return colWidth
+ };
+ t.getSlotHeight = function() {
+ return slotHeight
+ };
+ t.defaultSelectionEnd = defaultSelectionEnd;
+ t.renderDayOverlay = renderDayOverlay;
+ t.renderSelection = renderSelection;
+ t.clearSelection = clearSelection;
+ t.reportDayClick = reportDayClick; // selection mousedown hack
+ t.dragStart = dragStart;
+ t.dragStop = dragStop;
- /* Time-slot rendering
+
+ // imports
+ View.call(t, element, calendar, viewName);
+ OverlayManager.call(t);
+ SelectionManager.call(t);
+ AgendaEventRenderer.call(t);
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var clearEvents = t.clearEvents;
+ var renderOverlay = t.renderOverlay;
+ var clearOverlays = t.clearOverlays;
+ var reportSelection = t.reportSelection;
+ var unselect = t.unselect;
+ var daySelectionMousedown = t.daySelectionMousedown;
+ var slotSegHtml = t.slotSegHtml;
+ var formatDate = calendar.formatDate;
+
+
+ // locals
+
+ var dayTable;
+ var dayHead;
+ var dayHeadCells;
+ var dayBody;
+ var dayBodyCells;
+ var dayBodyCellInners;
+ var dayBodyFirstCell;
+ var dayBodyFirstCellStretcher;
+ var slotLayer;
+ var daySegmentContainer;
+ var allDayTable;
+ var allDayRow;
+ var slotScroller;
+ var slotContent;
+ var slotSegmentContainer;
+ var slotTable;
+ var slotTableFirstInner;
+ var axisFirstCells;
+ var gutterCells;
+ var selectionHelper;
+
+ var viewWidth;
+ var viewHeight;
+ var axisWidth;
+ var colWidth;
+ var gutterWidth;
+ var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
+ var savedScrollTop;
+
+ var colCnt;
+ var slotCnt;
+ var coordinateGrid;
+ var hoverListener;
+ var colContentPositions;
+ var slotTopCache = {};
+
+ var tm;
+ var firstDay;
+ var nwe; // no weekends (int)
+ var rtl, dis, dit; // day index sign / translate
+ var minMinute, maxMinute;
+ var colFormat;
+
+
+ /* Rendering
-----------------------------------------------------------------------------*/
disableTextSelection(element.addClass('fc-agenda'));
- function renderAgenda(c, colFormat) {
-
+ function renderAgenda(c) {
colCnt = c;
+ updateOptions();
+ if (!dayTable) {
+ buildSkeleton();
+ } else {
+ clearEvents();
+ }
+ updateCells();
+ }
- // update option-derived variables
- tm = options.theme ? 'ui' : 'fc';
- nwe = options.weekends ? 0 : 1;
- firstDay = options.firstDay;
- if (rtl = options.isRTL) {
+
+ function updateOptions() {
+ tm = opt('theme') ? 'ui' : 'fc';
+ nwe = opt('weekends') ? 0 : 1;
+ firstDay = opt('firstDay');
+ if (rtl = opt('isRTL')) {
dis = -1;
dit = colCnt - 1;
} else {
dis = 1;
dit = 0;
}
- minMinute = parseTime(options.minTime);
- maxMinute = parseTime(options.maxTime);
+ minMinute = parseTime(opt('minTime'));
+ maxMinute = parseTime(opt('maxTime'));
+ colFormat = opt('columnFormat');
+ }
- var d0 = rtl ? addDays(cloneDate(view.visEnd), -1) :
cloneDate(view.visStart),
- d = cloneDate(d0),
- today = clearTime(new Date());
- if (!head) { // first time rendering, build from scratch
+ function buildSkeleton() {
+ var headerClass = tm + "-widget-header";
+ var contentClass = tm + "-widget-content";
+ var s;
+ var i;
+ var d;
+ var maxd;
+ var minutes;
+ var slotNormal = opt('slotMinutes') % 15 == 0;
- var i,
- minutes,
- slotNormal = options.slotMinutes % 15 == 0, //...
+ s =
+ "<table style='width:100%' class='fc-agenda-days
fc-border-separate' cellspacing='0'>" +
+ "<thead>" +
+ "<tr>" +
+ "<th class='fc-agenda-axis " + headerClass +
"'> </th>";
+ for (i = 0; i < colCnt; i++) {
+ s +=
+ "<th class='fc- fc-col" + i + ' ' +
headerClass + "'/>"; // fc- needed for setDayID
+ }
+ s +=
+ "<th class='fc-agenda-gutter " + headerClass +
"'> </th>" +
+ "</tr>" +
+ "</thead>" +
+ "<tbody>" +
+ "<tr>" +
+ "<th class='fc-agenda-axis " + headerClass +
"'> </th>";
+ for (i = 0; i < colCnt; i++) {
+ s +=
+ "<td class='fc- fc-col" + i + ' ' +
contentClass + "'>" + // fc- needed for setDayID
+ "<div>" +
+ "<div class='fc-day-content'>" +
+ "<div
style='position:relative'> </div>" +
+ "</div>" +
+ "</div>" +
+ "</td>";
+ }
+ s +=
+ "<td class='fc-agenda-gutter " + contentClass +
"'> </td>" +
+ "</tr>" +
+ "</tbody>" +
+ "</table>";
+ dayTable = $(s).appendTo(element);
+ dayHead = dayTable.find('thead');
+ dayHeadCells = dayHead.find('th').slice(1, -1);
+ dayBody = dayTable.find('tbody');
+ dayBodyCells = dayBody.find('td').slice(0, -1);
+ dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
+ dayBodyFirstCell = dayBodyCells.eq(0);
+ dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
- // head
- s = "<div class='fc-agenda-head'
style='position:relative;z-index:4'>" +
- "<table style='width:100%'>" +
- "<tr class='fc-first" +
(options.allDaySlot ? '' : ' fc-last') + "'>" +
- "<th class='fc-leftmost " +
- tm +
"-state-default'> </th>";
- for (i = 0; i < colCnt; i++) {
- s += "<th class='fc-" +
- dayIDs[d.getDay()] + ' ' + // needs to be first
- tm + '-state-default' +
- "'>" + formatDate(d, colFormat, options) +
"</th>";
- addDays(d, dis);
- if (nwe) {
- skipWeekend(d, dis);
- }
- }
- s += "<th class='" + tm +
"-state-default'> </th></tr>";
- if (options.allDaySlot) {
- s += "<tr class='fc-all-day'>" +
- "<th class='fc-axis fc-leftmost " + tm +
"-state-default'>" + options.allDayText + "</th>" +
- "<td colspan='" + colCnt + "'
class='" + tm + "-state-default'>" +
- "<div class='fc-day-content'><div
style='position:relative'> </div></div></td>"
+
- "<th class='" + tm +
"-state-default'> </th>" +
- "</tr><tr class='fc-divider
fc-last'><th colspan='" + (colCnt + 2) + "' class='"
+
- tm + "-state-default
fc-leftmost'><div/></th></tr>";
- }
- s += "</table></div>";
- head = $(s).appendTo(element);
- dayBind(head.find('td'));
+ markFirstLast(dayHead.add(dayHead.find('tr')));
+ markFirstLast(dayBody.add(dayBody.find('tr')));
- // all-day event container
- daySegmentContainer = $("<div
style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(head);
+ axisFirstCells = dayHead.find('th:first');
+ gutterCells = dayTable.find('.fc-agenda-gutter');
- // body
- d = zeroDate();
- var maxd = addMinutes(cloneDate(d), maxMinute);
- addMinutes(d, minMinute);
- s = "<table>";
- for (i = 0; d < maxd; i++) {
- minutes = d.getMinutes();
- s += "<tr class='" +
- (!i ? 'fc-first' : (!minutes ? '' :
'fc-minor')) +
- "'><th class='fc-axis fc-leftmost " +
tm + "-state-default'>" +
- ((!slotNormal || !minutes) ? formatDate(d,
options.axisFormat) : ' ') +
- "</th><td class='fc-slot" + i + '
' +
- tm + "-state-default'><div
style='position:relative'> </div></td></tr>";
- addMinutes(d, options.slotMinutes);
- slotCnt++;
- }
- s += "</table>";
- body = $("<div class='fc-agenda-body'
style='position:relative;z-index:2;overflow:auto'/>")
- .append(bodyContent = $("<div
style='position:relative;overflow:hidden'/>")
- .append(bodyTable = $(s)))
- .appendTo(element);
- slotBind(body.find('td'));
+ slotLayer =
+ $("<div
style='position:absolute;z-index:2;left:0;width:100%'/>")
+ .appendTo(element);
- // slot event container
- slotSegmentContainer = $("<div
style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(bodyContent);
+ if (opt('allDaySlot')) {
- // background stripes
- d = cloneDate(d0);
- s = "<div class='fc-agenda-bg'
style='position:absolute;z-index:1'>" +
- "<table style='width:100%;height:100%'><tr
class='fc-first'>";
- for (i = 0; i < colCnt; i++) {
- s += "<td class='fc-" +
- dayIDs[d.getDay()] + ' ' + // needs to be first
- tm + '-state-default ' +
- (!i ? 'fc-leftmost ' : '') +
- (+d == +today ? tm + '-state-highlight fc-today' :
'fc-not-today') +
- "'><div
class='fc-day-content'><div> </div></div></td>";
- addDays(d, dis);
- if (nwe) {
- skipWeekend(d, dis);
- }
- }
- s += "</tr></table></div>";
- bg = $(s).appendTo(element);
+ daySegmentContainer =
+ $("<div
style='position:absolute;z-index:8;top:0;left:0'/>")
+ .appendTo(slotLayer);
- } else { // skeleton already built, just modify it
+ s =
+ "<table style='width:100%'
class='fc-agenda-allday' cellspacing='0'>" +
+ "<tr>" +
+ "<th class='" + headerClass + "
fc-agenda-axis'>" + opt('allDayText') + "</th>" +
+ "<td>" +
+ "<div class='fc-day-content'><div
style='position:relative'/></div>" +
+ "</td>" +
+ "<th class='" + headerClass + "
fc-agenda-gutter'> </th>" +
+ "</tr>" +
+ "</table>";
+ allDayTable = $(s).appendTo(slotLayer);
+ allDayRow = allDayTable.find('tr');
- clearEvents();
+ dayBind(allDayRow.find('td'));
- // redo column header text and class
- head.find('tr:first th').slice(1, -1).each(function() {
- $(this).text(formatDate(d, colFormat, options));
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-'
+ dayIDs[d.getDay()]);
- addDays(d, dis);
- if (nwe) {
- skipWeekend(d, dis);
- }
- });
+ axisFirstCells =
axisFirstCells.add(allDayTable.find('th:first'));
+ gutterCells =
gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
- // change classes of background stripes
- d = cloneDate(d0);
- bg.find('td').each(function() {
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-'
+ dayIDs[d.getDay()]);
- if (+d == +today) {
- $(this)
- .removeClass('fc-not-today')
- .addClass('fc-today')
- .addClass(tm + '-state-highlight');
- } else {
- $(this)
- .addClass('fc-not-today')
- .removeClass('fc-today')
- .removeClass(tm + '-state-highlight');
- }
- addDays(d, dis);
- if (nwe) {
- skipWeekend(d, dis);
- }
- });
+ slotLayer.append(
+ "<div class='fc-agenda-divider " + headerClass +
"'>" +
+ "<div
class='fc-agenda-divider-inner'/>" +
+ "</div>"
+ );
+ } else {
+
+ daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
+
}
+ slotScroller =
+ $("<div
style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
+ .appendTo(slotLayer);
+
+ slotContent =
+ $("<div
style='position:relative;width:100%;overflow:hidden'/>")
+ .appendTo(slotScroller);
+
+ slotSegmentContainer =
+ $("<div
style='position:absolute;z-index:8;top:0;left:0'/>")
+ .appendTo(slotContent);
+
+ s =
+ "<table class='fc-agenda-slots'
style='width:100%' cellspacing='0'>" +
+ "<tbody>";
+ d = zeroDate();
+ maxd = addMinutes(cloneDate(d), maxMinute);
+ addMinutes(d, minMinute);
+ slotCnt = 0;
+ for (i = 0; d < maxd; i++) {
+ minutes = d.getMinutes();
+ s +=
+ "<tr class='fc-slot" + i + ' ' +
(!minutes ? '' : 'fc-minor') + "'>" +
+ "<th class='fc-agenda-axis " +
headerClass + "'>" +
+ ((!slotNormal || !minutes) ? formatDate(d,
opt('axisFormat')) : ' ') +
+ "</th>" +
+ "<td class='" + contentClass +
"'>" +
+ "<div
style='position:relative'> </div>" +
+ "</td>" +
+ "</tr>";
+ addMinutes(d, opt('slotMinutes'));
+ slotCnt++;
+ }
+ s +=
+ "</tbody>" +
+ "</table>";
+ slotTable = $(s).appendTo(slotContent);
+ slotTableFirstInner = slotTable.find('div:first');
+
+ slotBind(slotTable.find('td'));
+
+ axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
}
- function resetScroll() {
- var d0 = zeroDate(),
- scrollDate = cloneDate(d0);
- scrollDate.setHours(options.firstHour);
- var top = timePosition(d0, scrollDate) + 1, // +1 for the border
- scroll = function() {
- body.scrollTop(top);
- };
- scroll();
- setTimeout(scroll, 0); // overrides any previous scroll state made by the
browser
+ function updateCells() {
+ var i;
+ var headCell;
+ var bodyCell;
+ var date;
+ var today = clearTime(new Date());
+ for (i = 0; i < colCnt; i++) {
+ date = colDate(i);
+ headCell = dayHeadCells.eq(i);
+ headCell.html(formatDate(date, colFormat));
+ bodyCell = dayBodyCells.eq(i);
+ if (+date == +today) {
+ bodyCell.addClass(tm + '-state-highlight fc-today');
+ } else {
+ bodyCell.removeClass(tm + '-state-highlight fc-today');
+ }
+ setDayID(headCell.add(bodyCell), date);
+ }
}
function setHeight(height, dateChanged) {
+ if (height === undefined) {
+ height = viewHeight;
+ }
viewHeight = height;
slotTopCache = {};
- body.height(height - head.height());
+ var headHeight = dayBody.position().top;
+ var allDayHeight = slotScroller.position().top; // including divider
+ var bodyHeight = Math.min(// total body height, including borders
+ height - headHeight, // when scrollbars
+ slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for
bottom border
+ );
- slotHeight = body.find('tr:first div').height() + 1;
+ dayBodyFirstCellStretcher
+ .height(bodyHeight - vsides(dayBodyFirstCell));
- bg.css({
- top: head.find('tr').height(),
- height: height
- });
+ slotLayer.css('top', headHeight);
+ slotScroller.height(bodyHeight - allDayHeight - 1);
+
+ slotHeight = slotTableFirstInner.height() + 1; // +1 for border
+
if (dateChanged) {
resetScroll();
}
@@ -1982,78 +3192,497 @@
viewWidth = width;
colContentPositions.clear();
- body.width(width);
- bodyTable.width('');
-
- var topTDs = head.find('tr:first th'),
- stripeTDs = bg.find('td'),
- clientWidth = body[0].clientWidth;
-
- bodyTable.width(clientWidth);
-
- // time-axis width
axisWidth = 0;
setOuterWidth(
- head.find('tr:lt(2) th:first').add(body.find('tr:first
th'))
+ axisFirstCells
.width('')
- .each(function() {
- axisWidth = Math.max(axisWidth, $(this).outerWidth());
- }),
+ .each(function(i, _cell) {
+ axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
+ }),
axisWidth
- );
+ );
- // column width
- colWidth = Math.floor((clientWidth - axisWidth) / colCnt);
- setOuterWidth(stripeTDs.slice(0, -1), colWidth);
- setOuterWidth(topTDs.slice(1, -2), colWidth);
- setOuterWidth(topTDs.slice(-2, -1), clientWidth - axisWidth - colWidth *
(colCnt - 1));
+ var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after
axisWidth (for IE7)
+ //slotTable.width(slotTableWidth);
- bg.css({
- left: axisWidth,
- width: clientWidth - axisWidth
- });
+ gutterWidth = slotScroller.width() - slotTableWidth;
+ if (gutterWidth) {
+ setOuterWidth(gutterCells, gutterWidth);
+ gutterCells
+ .show()
+ .prev()
+ .removeClass('fc-last');
+ } else {
+ gutterCells
+ .hide()
+ .prev()
+ .addClass('fc-last');
+ }
+
+ colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
+ setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
}
+ function resetScroll() {
+ var d0 = zeroDate();
+ var scrollDate = cloneDate(d0);
+ scrollDate.setHours(opt('firstHour'));
+ var top = timePosition(d0, scrollDate) + 1; // +1 for the border
+ function scroll() {
+ slotScroller.scrollTop(top);
+ }
+
+ scroll();
+ setTimeout(scroll, 0); // overrides any previous scroll state made by the
browser
+ }
+
+
+ function beforeHide() {
+ savedScrollTop = slotScroller.scrollTop();
+ }
+
+
+ function afterShow() {
+ slotScroller.scrollTop(savedScrollTop);
+ }
+
+
/* Slot/Day clicking and binding
-----------------------------------------------------------------------*/
- function dayBind(tds) {
- tds.click(slotClick)
+ function dayBind(cells) {
+ cells.click(slotClick)
.mousedown(daySelectionMousedown);
}
- function slotBind(tds) {
- tds.click(slotClick)
+ function slotBind(cells) {
+ cells.click(slotClick)
.mousedown(slotSelectionMousedown);
}
function slotClick(ev) {
- if (!view.option('selectable')) { // SelectionManager will worry
about dayClick
- var col = Math.floor((ev.pageX - bg.offset().left) / colWidth),
- date = addDays(cloneDate(view.visStart), dit + dis * col),
- rowMatch = this.className.match(/fc-slot(\d+)/);
+ if (!opt('selectable')) { // if selectable, SelectionManager will
worry about dayClick
+ var col = Math.min(colCnt - 1, Math.floor((ev.pageX -
dayTable.offset().left - axisWidth) / colWidth));
+ var date = colDate(col);
+ var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO:
maybe use data
if (rowMatch) {
- var mins = parseInt(rowMatch[1]) * options.slotMinutes,
- hours = Math.floor(mins / 60);
+ var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
+ var hours = Math.floor(mins / 60);
date.setHours(hours);
date.setMinutes(mins % 60 + minMinute);
- view.trigger('dayClick', this, date, false, ev);
+ trigger('dayClick', dayBodyCells[col], date, false, ev);
} else {
- view.trigger('dayClick', this, date, true, ev);
+ trigger('dayClick', dayBodyCells[col], date, true, ev);
}
}
}
- /* Event Rendering
+ /* Semi-transparent Overlay Helpers
+ -----------------------------------------------------*/
+
+
+ function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate
is exclusive
+ if (refreshCoordinateGrid) {
+ coordinateGrid.build();
+ }
+ var visStart = cloneDate(t.visStart);
+ var startCol, endCol;
+ if (rtl) {
+ startCol = dayDiff(endDate, visStart) * dis + dit + 1;
+ endCol = dayDiff(startDate, visStart) * dis + dit + 1;
+ } else {
+ startCol = dayDiff(startDate, visStart);
+ endCol = dayDiff(endDate, visStart);
+ }
+ startCol = Math.max(0, startCol);
+ endCol = Math.min(colCnt, endCol);
+ if (startCol < endCol) {
+ dayBind(
+ renderCellOverlay(0, startCol, 0, endCol - 1)
+ );
+ }
+ }
+
+
+ function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
+ var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
+ return renderOverlay(rect, slotLayer);
+ }
+
+
+ function renderSlotOverlay(overlayStart, overlayEnd) {
+ var dayStart = cloneDate(t.visStart);
+ var dayEnd = addDays(cloneDate(dayStart), 1);
+ for (var i = 0; i < colCnt; i++) {
+ var stretchStart = new Date(Math.max(dayStart, overlayStart));
+ var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
+ if (stretchStart < stretchEnd) {
+ var col = i * dis + dit;
+ var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only
use it for horizontal coords
+ var top = timePosition(dayStart, stretchStart);
+ var bottom = timePosition(dayStart, stretchEnd);
+ rect.top = top;
+ rect.height = bottom - top;
+ slotBind(
+ renderOverlay(rect, slotContent)
+ );
+ }
+ addDays(dayStart, 1);
+ addDays(dayEnd, 1);
+ }
+ }
+
+
+ /* Coordinate Utilities
-----------------------------------------------------------------------------*/
+
+ coordinateGrid = new CoordinateGrid(function(rows, cols) {
+ var e, n, p;
+ dayHeadCells.each(function(i, _e) {
+ e = $(_e);
+ n = e.offset().left;
+ if (i) {
+ p[1] = n;
+ }
+ p = [n];
+ cols[i] = p;
+ });
+ p[1] = n + e.outerWidth();
+ if (opt('allDaySlot')) {
+ e = allDayRow;
+ n = e.offset().top;
+ rows[0] = [n, n + e.outerHeight()];
+ }
+ var slotTableTop = slotContent.offset().top;
+ var slotScrollerTop = slotScroller.offset().top;
+ var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
+
+ function constrain(n) {
+ return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
+ }
+
+ for (var i = 0; i < slotCnt; i++) {
+ rows.push([
+ constrain(slotTableTop + slotHeight * i),
+ constrain(slotTableTop + slotHeight * (i + 1))
+ ]);
+ }
+ });
+
+
+ hoverListener = new HoverListener(coordinateGrid);
+
+
+ colContentPositions = new HorizontalPositionCache(function(col) {
+ return dayBodyCellInners.eq(col);
+ });
+
+
+ function colContentLeft(col) {
+ return colContentPositions.left(col);
+ }
+
+
+ function colContentRight(col) {
+ return colContentPositions.right(col);
+ }
+
+
+ function dateCell(date) { // "cell" terminology is now confusing
+ return {
+ row: Math.floor(dayDiff(date, t.visStart) / 7),
+ col: dayOfWeekCol(date.getDay())
+ };
+ }
+
+
+ function cellDate(cell) {
+ var d = colDate(cell.col);
+ var slotIndex = cell.row;
+ if (opt('allDaySlot')) {
+ slotIndex--;
+ }
+ if (slotIndex >= 0) {
+ addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
+ }
+ return d;
+ }
+
+
+ function colDate(col) { // returns dates with 00:00:00
+ return addDays(cloneDate(t.visStart), col * dis + dit);
+ }
+
+
+ function cellIsAllDay(cell) {
+ return opt('allDaySlot') && !cell.row;
+ }
+
+
+ function dayOfWeekCol(dayOfWeek) {
+ return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis +
dit;
+ }
+
+
+ // get the Y coordinate of the given time on the given day (both Date objects)
+ function timePosition(day, time) { // both date objects. day holds 00:00 of
current day
+ day = cloneDate(day, true);
+ if (time < addMinutes(cloneDate(day), minMinute)) {
+ return 0;
+ }
+ if (time >= addMinutes(cloneDate(day), maxMinute)) {
+ return slotTable.height();
+ }
+ var slotMinutes = opt('slotMinutes'),
+ minutes = time.getHours() * 60 + time.getMinutes() - minMinute,
+ slotI = Math.floor(minutes / slotMinutes),
+ slotTop = slotTopCache[slotI];
+ if (slotTop === undefined) {
+ slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI +
') td div')[0].offsetTop; //.position().top; // need this optimization???
+ }
+ return Math.max(0, Math.round(
+ slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
+ ));
+ }
+
+
+ function allDayBounds() {
+ return {
+ left: axisWidth,
+ right: viewWidth - gutterWidth
+ }
+ }
+
+
+ function getAllDayRow(index) {
+ return allDayRow;
+ }
+
+
+ function defaultEventEnd(event) {
+ var start = cloneDate(event.start);
+ if (event.allDay) {
+ return start;
+ }
+ return addMinutes(start, opt('defaultEventMinutes'));
+ }
+
+
+ /* Selection
+
---------------------------------------------------------------------------------*/
+
+
+ function defaultSelectionEnd(startDate, allDay) {
+ if (allDay) {
+ return cloneDate(startDate);
+ }
+ return addMinutes(cloneDate(startDate), opt('slotMinutes'));
+ }
+
+
+ function renderSelection(startDate, endDate, allDay) { // only for all-day
+ if (allDay) {
+ if (opt('allDaySlot')) {
+ renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
+ }
+ } else {
+ renderSlotSelection(startDate, endDate);
+ }
+ }
+
+
+ function renderSlotSelection(startDate, endDate) {
+ var helperOption = opt('selectHelper');
+ coordinateGrid.build();
+ if (helperOption) {
+ var col = dayDiff(startDate, t.visStart) * dis + dit;
+ if (col >= 0 && col < colCnt) { // only works when times
are on same day
+ var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only
for horizontal coords
+ var top = timePosition(startDate, startDate);
+ var bottom = timePosition(startDate, endDate);
+ if (bottom > top) { // protect against selections that are
entirely before or after visible range
+ rect.top = top;
+ rect.height = bottom - top;
+ rect.left += 2;
+ rect.width -= 5;
+ if ($.isFunction(helperOption)) {
+ var helperRes = helperOption(startDate, endDate);
+ if (helperRes) {
+ rect.position = 'absolute';
+ rect.zIndex = 8;
+ selectionHelper = $(helperRes)
+ .css(rect)
+ .appendTo(slotContent);
+ }
+ } else {
+ rect.isStart = true; // conside rect a "seg" now
+ rect.isEnd = true; //
+ selectionHelper = $(slotSegHtml(
+ {
+ title: '',
+ start: startDate,
+ end: endDate,
+ className: ['fc-select-helper'],
+ editable: false
+ },
+ rect
+ ));
+ selectionHelper.css('opacity',
opt('dragOpacity'));
+ }
+ if (selectionHelper) {
+ slotBind(selectionHelper);
+ slotContent.append(selectionHelper);
+ setOuterWidth(selectionHelper, rect.width, true); // needs to
be after appended
+ setOuterHeight(selectionHelper, rect.height, true);
+ }
+ }
+ }
+ } else {
+ renderSlotOverlay(startDate, endDate);
+ }
+ }
+
+
+ function clearSelection() {
+ clearOverlays();
+ if (selectionHelper) {
+ selectionHelper.remove();
+ selectionHelper = null;
+ }
+ }
+
+
+ function slotSelectionMousedown(ev) {
+ if (ev.which == 1 && opt('selectable')) { // ev.which==1
means left mouse button
+ unselect(ev);
+ var dates;
+ hoverListener.start(function(cell, origCell) {
+ clearSelection();
+ if (cell && cell.col == origCell.col &&
!cellIsAllDay(cell)) {
+ var d1 = cellDate(origCell);
+ var d2 = cellDate(cell);
+ dates = [
+ d1,
+ addMinutes(cloneDate(d1), opt('slotMinutes')),
+ d2,
+ addMinutes(cloneDate(d2), opt('slotMinutes'))
+ ].sort(cmp);
+ renderSlotSelection(dates[0], dates[3]);
+ } else {
+ dates = null;
+ }
+ }, ev);
+ $(document).one('mouseup', function(ev) {
+ hoverListener.stop();
+ if (dates) {
+ if (+dates[0] == +dates[1]) {
+ reportDayClick(dates[0], false, ev);
+ }
+ reportSelection(dates[0], dates[3], false, ev);
+ }
+ });
+ }
+ }
+
+
+ function reportDayClick(date, allDay, ev) {
+ trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date,
allDay, ev);
+ }
+
+
+ /* External Dragging
+
--------------------------------------------------------------------------------*/
+
+
+ function dragStart(_dragElement, ev, ui) {
+ hoverListener.start(function(cell) {
+ clearOverlays();
+ if (cell) {
+ if (cellIsAllDay(cell)) {
+ renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
+ } else {
+ var d1 = cellDate(cell);
+ var d2 = addMinutes(cloneDate(d1),
opt('defaultEventMinutes'));
+ renderSlotOverlay(d1, d2);
+ }
+ }
+ }, ev);
+ }
+
+
+ function dragStop(_dragElement, ev, ui) {
+ var cell = hoverListener.stop();
+ clearOverlays();
+ if (cell) {
+ trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell),
ev, ui);
+ }
+ }
+
+
+ }
+
+ function AgendaEventRenderer() {
+ var t = this;
+
+
+ // exports
+ t.renderEvents = renderEvents;
+ t.compileDaySegs = compileDaySegs; // for DayEventRenderer
+ t.clearEvents = clearEvents;
+ t.slotSegHtml = slotSegHtml;
+ t.bindDaySeg = bindDaySeg;
+
+
+ // imports
+ DayEventRenderer.call(t);
+ var opt = t.opt;
+ var trigger = t.trigger;
+ //var setOverflowHidden = t.setOverflowHidden;
+ var isEventDraggable = t.isEventDraggable;
+ var isEventResizable = t.isEventResizable;
+ var eventEnd = t.eventEnd;
+ var reportEvents = t.reportEvents;
+ var reportEventClear = t.reportEventClear;
+ var eventElementHandlers = t.eventElementHandlers;
+ var setHeight = t.setHeight;
+ var getDaySegmentContainer = t.getDaySegmentContainer;
+ var getSlotSegmentContainer = t.getSlotSegmentContainer;
+ var getHoverListener = t.getHoverListener;
+ var getMaxMinute = t.getMaxMinute;
+ var getMinMinute = t.getMinMinute;
+ var timePosition = t.timePosition;
+ var colContentLeft = t.colContentLeft;
+ var colContentRight = t.colContentRight;
+ var renderDaySegs = t.renderDaySegs;
+ var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding
architecture
+ var getColCnt = t.getColCnt;
+ var getColWidth = t.getColWidth;
+ var getSlotHeight = t.getSlotHeight;
+ var getBodyContent = t.getBodyContent;
+ var reportEventElement = t.reportEventElement;
+ var showEvents = t.showEvents;
+ var hideEvents = t.hideEvents;
+ var eventDrop = t.eventDrop;
+ var eventResize = t.eventResize;
+ var renderDayOverlay = t.renderDayOverlay;
+ var clearOverlays = t.clearOverlays;
+ var calendar = t.calendar;
+ var formatDate = calendar.formatDate;
+ var formatDates = calendar.formatDates;
+
+
+ /* Rendering
+ ----------------------------------------------------------------------------*/
+
+
function renderEvents(events, modifiedEventId) {
- view.reportEvents(cachedEvents = events);
+ reportEvents(events);
var i, len = events.length,
dayEvents = [],
slotEvents = [];
@@ -2064,26 +3693,23 @@
slotEvents.push(events[i]);
}
}
- renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
+ if (opt('allDaySlot')) {
+ renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
+ setHeight(); // no params means set to viewHeight
+ }
renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
}
- function rerenderEvents(modifiedEventId) {
- clearEvents();
- renderEvents(cachedEvents, modifiedEventId);
- }
-
-
function clearEvents() {
- view._clearEvents(); // only clears the hashes
- daySegmentContainer.empty();
- slotSegmentContainer.empty();
+ reportEventClear();
+ getDaySegmentContainer().empty();
+ getSlotSegmentContainer().empty();
}
function compileDaySegs(events) {
- var levels = stackSegs(view.sliceSegs(events, $.map(events, exclEndDay),
view.visStart, view.visEnd)),
+ var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay),
t.visStart, t.visEnd)),
i, levelCnt = levels.length, level,
j, seg,
segs = [];
@@ -2092,7 +3718,7 @@
for (j = 0; j < level.length; j++) {
seg = level[j];
seg.row = 0;
- seg.level = i;
+ seg.level = i; // not needed anymore
segs.push(seg);
}
}
@@ -2101,14 +3727,17 @@
function compileSlotSegs(events) {
- var d = addMinutes(cloneDate(view.visStart), minMinute),
+ var colCnt = getColCnt(),
+ minMinute = getMinMinute(),
+ maxMinute = getMaxMinute(),
+ d = addMinutes(cloneDate(t.visStart), minMinute),
visEventEnds = $.map(events, slotEventEnd),
i, col,
j, level,
k, seg,
segs = [];
for (i = 0; i < colCnt; i++) {
- col = stackSegs(view.sliceSegs(events, visEventEnds, d,
addMinutes(cloneDate(d), maxMinute - minMinute)));
+ col = stackSegs(sliceSegs(events, visEventEnds, d,
addMinutes(cloneDate(d), maxMinute - minMinute)));
countForwardSegs(col);
for (j = 0; j < col.length; j++) {
level = col[j];
@@ -2125,30 +3754,11 @@
}
- // renders 'all-day' events at the top
-
- function renderDaySegs(segs, modifiedEventId) {
- if (options.allDaySlot) {
- _renderDaySegs(
- segs,
- 1,
- view,
- axisWidth,
- viewWidth,
- function() {
- return head.find('tr.fc-all-day');
- },
- function(dayOfWeek) {
- return axisWidth +
colContentPositions.left(dayOfWeekCol(dayOfWeek));
- },
- function(dayOfWeek) {
- return axisWidth +
colContentPositions.right(dayOfWeekCol(dayOfWeek));
- },
- daySegmentContainer,
- daySegBind,
- modifiedEventId
- );
- setHeight(viewHeight); // might have pushed the body down, so resize
+ function slotEventEnd(event) {
+ if (event.end) {
+ return cloneDate(event.end);
+ } else {
+ return addMinutes(cloneDate(event.start),
opt('defaultEventMinutes'));
}
}
@@ -2159,7 +3769,7 @@
var i, segCnt = segs.length, seg,
event,
- className,
+ classes,
top, bottom,
colI, levelI, forward,
leftmost,
@@ -2173,27 +3783,31 @@
vsideCache = {},
hsideCache = {},
key, val,
- titleSpan,
- height;
+ contentElement,
+ height,
+ slotSegmentContainer = getSlotSegmentContainer(),
+ rtl, dis, dit,
+ colCnt = getColCnt();
+ if (rtl = opt('isRTL')) {
+ dis = -1;
+ dit = colCnt - 1;
+ } else {
+ dis = 1;
+ dit = 0;
+ }
+
// calculate position/dimensions, create html
for (i = 0; i < segCnt; i++) {
seg = segs[i];
event = seg.event;
- className = 'fc-event fc-event-vert ';
- if (seg.isStart) {
- className += 'fc-corner-top ';
- }
- if (seg.isEnd) {
- className += 'fc-corner-bottom ';
- }
top = timePosition(seg.start, seg.start);
bottom = timePosition(seg.start, seg.end);
colI = seg.col;
levelI = seg.level;
forward = seg.forward || 0;
- leftmost = axisWidth + colContentPositions.left(colI * dis + dit);
- availWidth = axisWidth + colContentPositions.right(colI * dis + dit) -
leftmost;
+ leftmost = colContentLeft(colI * dis + dit);
+ availWidth = colContentRight(colI * dis + dit) - leftmost;
availWidth = Math.min(availWidth - 6, availWidth * .95); // TODO: move
this to CSS
if (levelI) {
// indented and thin
@@ -2214,7 +3828,7 @@
seg.left = left;
seg.outerWidth = outerWidth;
seg.outerHeight = bottom - top;
- html += slotSegHtml(event, seg, className);
+ html += slotSegHtml(event, seg);
}
slotSegmentContainer[0].innerHTML = html; // faster than html()
eventElements = slotSegmentContainer.children();
@@ -2224,7 +3838,7 @@
seg = segs[i];
event = seg.event;
eventElement = $(eventElements[i]); // faster than eq()
- triggerRes = view.trigger('eventRender', event, event,
eventElement);
+ triggerRes = trigger('eventRender', event, event, eventElement);
if (triggerRes === false) {
eventElement.remove();
} else {
@@ -2232,35 +3846,35 @@
eventElement.remove();
eventElement = $(triggerRes)
.css({
- position: 'absolute',
- top: seg.top,
- left: seg.left
- })
+ position: 'absolute',
+ top: seg.top,
+ left: seg.left
+ })
.appendTo(slotSegmentContainer);
}
seg.element = eventElement;
if (event._id === modifiedEventId) {
- slotSegBind(event, eventElement, seg);
+ bindSlotSeg(event, eventElement, seg);
} else {
eventElement[0]._fci = i; // for lazySegBind
}
- view.reportEventElement(event, eventElement);
+ reportEventElement(event, eventElement);
}
}
- lazySegBind(slotSegmentContainer, segs, slotSegBind);
+ lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
// record event sides and title positions
for (i = 0; i < segCnt; i++) {
seg = segs[i];
if (eventElement = seg.element) {
val = vsideCache[key = seg.key = cssKey(eventElement[0])];
- seg.vsides = val === undefined ? (vsideCache[key] =
vsides(eventElement[0], true)) : val;
+ seg.vsides = val === undefined ? (vsideCache[key] =
vsides(eventElement, true)) : val;
val = hsideCache[key];
- seg.hsides = val === undefined ? (hsideCache[key] =
hsides(eventElement[0], true)) : val;
- titleSpan = eventElement.find('span.fc-event-title');
- if (titleSpan.length) {
- seg.titleTop = titleSpan[0].offsetTop;
+ seg.hsides = val === undefined ? (hsideCache[key] =
hsides(eventElement, true)) : val;
+ contentElement = eventElement.find('div.fc-event-content');
+ if (contentElement.length) {
+ seg.contentTop = contentElement[0].offsetTop;
}
}
}
@@ -2269,152 +3883,196 @@
for (i = 0; i < segCnt; i++) {
seg = segs[i];
if (eventElement = seg.element) {
- eventElement[0].style.width = seg.outerWidth - seg.hsides +
'px';
- eventElement[0].style.height = (height = seg.outerHeight -
seg.vsides) + 'px';
+ eventElement[0].style.width = Math.max(0, seg.outerWidth -
seg.hsides) + 'px';
+ height = Math.max(0, seg.outerHeight - seg.vsides);
+ eventElement[0].style.height = height + 'px';
event = seg.event;
- if (seg.titleTop !== undefined && height - seg.titleTop <
10) {
+ if (seg.contentTop !== undefined && height - seg.contentTop
< 10) {
// not enough room for title, put it in the time header
- eventElement.find('span.fc-event-time')
- .text(formatDate(event.start,
view.option('timeFormat')) + ' - ' + event.title);
- eventElement.find('span.fc-event-title')
+ eventElement.find('div.fc-event-time')
+ .text(formatDate(event.start, opt('timeFormat'))
+ ' - ' + event.title);
+ eventElement.find('div.fc-event-title')
.remove();
}
- view.trigger('eventAfterRender', event, event,
eventElement);
+ trigger('eventAfterRender', event, event, eventElement);
}
}
}
- function slotSegHtml(event, seg, className) {
- return "<div class='" + className +
event.className.join(' ') + "'
style='position:absolute;z-index:8;top:" + seg.top + "px;left:" +
seg.left + "px'>" +
- "<a" + (event.url ? " href='" +
htmlEscape(event.url) + "'" : '') + ">" +
- "<span class='fc-event-bg'></span>" +
- "<span class='fc-event-time'>" +
htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'))) +
"</span>" +
- "<span class='fc-event-title'>" +
htmlEscape(event.title) + "</span>" +
- "</a>" +
- ((event.editable || event.editable === undefined &&
options.editable) && !options.disableResizing && $.fn.resizable ?
- "<div class='ui-resizable-handle
ui-resizable-s'>=</div>"
- : '') +
- "</div>";
+
+ function slotSegHtml(event, seg) {
+ var html = "<";
+ var url = event.url;
+ var skinCss = getSkinCss(event, opt);
+ var skinCssAttr = (skinCss ? " style='" + skinCss +
"'" : '');
+ var classes = ['fc-event', 'fc-event-skin',
'fc-event-vert'];
+ if (isEventDraggable(event)) {
+ classes.push('fc-event-draggable');
+ }
+ if (seg.isStart) {
+ classes.push('fc-corner-top');
+ }
+ if (seg.isEnd) {
+ classes.push('fc-corner-bottom');
+ }
+ classes = classes.concat(event.className);
+ if (event.source) {
+ classes = classes.concat(event.source.className || []);
+ }
+ if (url) {
+ html += "a href='" + htmlEscape(event.url) +
"'";
+ } else {
+ html += "div";
+ }
+ html +=
+ " class='" + classes.join(' ') +
"'" +
+ " style='position:absolute;z-index:8;top:" +
seg.top + "px;left:" + seg.left + "px;" + skinCss + "'"
+
+ ">" +
+ "<div class='fc-event-inner
fc-event-skin'" + skinCssAttr + ">" +
+ "<div class='fc-event-head
fc-event-skin'" + skinCssAttr + ">" +
+ "<div class='fc-event-time'>" +
+ htmlEscape(formatDates(event.start, event.end,
opt('timeFormat'))) +
+ "</div>" +
+ "</div>" +
+ "<div class='fc-event-content'>" +
+ "<div class='fc-event-title'>" +
+ htmlEscape(event.title) +
+ "</div>" +
+ "</div>" +
+ "<div
class='fc-event-bg'></div>" +
+ "</div>"; // close inner
+ if (seg.isEnd && isEventResizable(event)) {
+ html +=
+ "<div class='ui-resizable-handle
ui-resizable-s'>=</div>";
+ }
+ html +=
+ "</" + (url ? "a" : "div") +
">";
+ return html;
}
- function daySegBind(event, eventElement, seg) {
- view.eventElementHandlers(event, eventElement);
- if (event.editable || event.editable === undefined &&
options.editable) {
+ function bindDaySeg(event, eventElement, seg) {
+ if (isEventDraggable(event)) {
draggableDayEvent(event, eventElement, seg.isStart);
- if (seg.isEnd) {
- view.resizableDayEvent(event, eventElement, colWidth);
- }
}
+ if (seg.isEnd && isEventResizable(event)) {
+ resizableDayEvent(event, eventElement, seg);
+ }
+ eventElementHandlers(event, eventElement);
+ // needs to be after, because resizableDayEvent might
stopImmediatePropagation on click
}
- function slotSegBind(event, eventElement, seg) {
- view.eventElementHandlers(event, eventElement);
- if (event.editable || event.editable === undefined &&
options.editable) {
- var timeElement = eventElement.find('span.fc-event-time');
+ function bindSlotSeg(event, eventElement, seg) {
+ var timeElement = eventElement.find('div.fc-event-time');
+ if (isEventDraggable(event)) {
draggableSlotEvent(event, eventElement, timeElement);
- if (seg.isEnd) {
- resizableSlotEvent(event, eventElement, timeElement);
- }
}
+ if (seg.isEnd && isEventResizable(event)) {
+ resizableSlotEvent(event, eventElement, timeElement);
+ }
+ eventElementHandlers(event, eventElement);
}
- /* Event Dragging
- -----------------------------------------------------------------------------*/
+ /* Dragging
+
-----------------------------------------------------------------------------------*/
// when event starts out FULL-DAY
function draggableDayEvent(event, eventElement, isStart) {
- if (!options.disableDragging && eventElement.draggable) {
- var origPosition, origWidth,
- resetElement,
- allDay = true,
- matrix;
- eventElement.draggable({
- zIndex: 9,
- opacity: view.option('dragOpacity', 'month'), // use
whatever the month view was using
- revertDuration: options.dragRevertDuration,
- start: function(ev, ui) {
- view.hideEvents(event, eventElement);
- view.trigger('eventDragStart', eventElement, event, ev,
ui);
- origPosition = eventElement.position();
- origWidth = eventElement.width();
- resetElement = function() {
- if (!allDay) {
- eventElement
- .width(origWidth)
- .height('')
- .draggable('option', 'grid',
null);
- allDay = true;
- }
- };
- matrix = buildDayMatrix(function(cell) {
- eventElement.draggable('option', 'revert',
!cell || !cell.rowDelta && !cell.colDelta);
- view.clearOverlays();
- if (cell) {
- if (!cell.row) {
- // on full-days
- renderDayOverlay(
- matrix,
- addDays(cloneDate(event.start),
cell.colDelta),
- addDays(exclEndDay(event), cell.colDelta)
- );
- resetElement();
- } else {
- // mouse is over bottom slots
- if (isStart && allDay) {
+ var origWidth;
+ var revert;
+ var allDay = true;
+ var dayDelta;
+ var dis = opt('isRTL') ? -1 : 1;
+ var hoverListener = getHoverListener();
+ var colWidth = getColWidth();
+ var slotHeight = getSlotHeight();
+ var minMinute = getMinMinute();
+ eventElement.draggable({
+ zIndex: 9,
+ opacity: opt('dragOpacity', 'month'), // use whatever the
month view was using
+ revertDuration: opt('dragRevertDuration'),
+ start: function(ev, ui) {
+ trigger('eventDragStart', eventElement, event, ev, ui);
+ hideEvents(event, eventElement);
+ origWidth = eventElement.width();
+ hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
+ clearOverlays();
+ if (cell) {
+ //setOverflowHidden(true);
+ revert = false;
+ dayDelta = colDelta * dis;
+ if (!cell.row) {
+ // on full-days
+ renderDayOverlay(
+ addDays(cloneDate(event.start), dayDelta),
+ addDays(exclEndDay(event), dayDelta)
+ );
+ resetElement();
+ } else {
+ // mouse is over bottom slots
+ if (isStart) {
+ if (allDay) {
// convert event to temporary slot-event
+ eventElement.width(colWidth - 10); // don't
use entire width
setOuterHeight(
- eventElement.width(colWidth - 10), //
don't use entire width
+ eventElement,
slotHeight * Math.round(
- (event.end ? ((event.end -
event.start) / MINUTE_MS) : options.defaultEventMinutes)
- / options.slotMinutes)
- );
+ (event.end ? ((event.end -
event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
+ /
opt('slotMinutes')
+ )
+ );
eventElement.draggable('option',
'grid', [colWidth, 1]);
allDay = false;
}
+ } else {
+ revert = true;
}
}
- }, true);
- matrix.mouse(ev);
- },
- drag: function(ev, ui) {
- matrix.mouse(ev);
- },
- stop: function(ev, ui) {
- view.trigger('eventDragStop', eventElement, event, ev,
ui);
- view.clearOverlays();
- var cell = matrix.cell;
- var 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)
+ revert = revert || (allDay && !dayDelta);
+ } else {
resetElement();
- if ($.browser.msie) {
- eventElement.css('filter', ''); // clear
IE opacity side-effects
- }
- view.showEvents(event, eventElement);
- } else {
- eventElement.find('a').removeAttr('href'); //
prevents safari from visiting the link
- view.eventDrop(
- this, event, dayDelta,
- allDay ? 0 : // minute delta
- Math.round((eventElement.offset().top -
bodyContent.offset().top) / slotHeight)
- * options.slotMinutes
- + minMinute
- - (event.start.getHours() * 60 +
event.start.getMinutes()),
- allDay, ev, ui
- );
+ //setOverflowHidden(false);
+ revert = true;
}
+ eventElement.draggable('option', 'revert',
revert);
+ }, ev, 'drag');
+ },
+ stop: function(ev, ui) {
+ hoverListener.stop();
+ clearOverlays();
+ trigger('eventDragStop', eventElement, event, ev, ui);
+ if (revert) {
+ // hasn't moved or is out of bounds (draggable has already
reverted)
+ resetElement();
+ eventElement.css('filter', ''); // clear IE
opacity side-effects
+ showEvents(event, eventElement);
+ } else {
+ // changed!
+ var minuteDelta = 0;
+ if (!allDay) {
+ minuteDelta = Math.round((eventElement.offset().top -
getBodyContent().offset().top) / slotHeight)
+ * opt('slotMinutes')
+ + minMinute
+ - (event.start.getHours() * 60 +
event.start.getMinutes());
+ }
+ eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
}
- });
+ //setOverflowHidden(false);
+ }
+ });
+ function resetElement() {
+ if (!allDay) {
+ eventElement
+ .width(origWidth)
+ .height('')
+ .draggable('option', 'grid', null);
+ allDay = true;
+ }
}
}
@@ -2422,440 +4080,148 @@
// when event starts out IN TIMESLOTS
function draggableSlotEvent(event, eventElement, timeElement) {
- if (!options.disableDragging && eventElement.draggable) {
- var origPosition,
- resetElement,
- prevSlotDelta, slotDelta,
- allDay = false,
- matrix;
- eventElement.draggable({
- zIndex: 9,
- scroll: false,
- grid: [colWidth, slotHeight],
- axis: colCnt == 1 ? 'y' : false,
- opacity: view.option('dragOpacity'),
- revertDuration: options.dragRevertDuration,
- start: function(ev, ui) {
- view.hideEvents(event, eventElement);
- view.trigger('eventDragStart', eventElement, event, 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) {
- timeElement.css('display', ''); // show()
was causing display=inline
- eventElement.draggable('option', 'grid',
[colWidth, slotHeight]);
- allDay = false;
- }
- };
- prevSlotDelta = 0;
- matrix = buildDayMatrix(function(cell) {
- eventElement.draggable('option', 'revert',
!cell);
- view.clearOverlays();
- if (cell) {
- if (!cell.row && options.allDaySlot) { // over
full days
- if (!allDay) {
- // convert to temporary all-day event
- allDay = true;
- timeElement.hide();
- eventElement.draggable('option',
'grid', null);
- }
- renderDayOverlay(
- matrix,
- addDays(cloneDate(event.start),
cell.colDelta),
- addDays(exclEndDay(event), cell.colDelta)
- );
- } else { // on slots
- resetElement();
+ var origPosition;
+ var allDay = false;
+ var dayDelta;
+ var minuteDelta;
+ var prevMinuteDelta;
+ var dis = opt('isRTL') ? -1 : 1;
+ var hoverListener = getHoverListener();
+ var colCnt = getColCnt();
+ var colWidth = getColWidth();
+ var slotHeight = getSlotHeight();
+ eventElement.draggable({
+ zIndex: 9,
+ scroll: false,
+ grid: [colWidth, slotHeight],
+ axis: colCnt == 1 ? 'y' : false,
+ opacity: opt('dragOpacity'),
+ revertDuration: opt('dragRevertDuration'),
+ start: function(ev, ui) {
+ trigger('eventDragStart', eventElement, event, ev, ui);
+ hideEvents(event, eventElement);
+ origPosition = eventElement.position();
+ minuteDelta = prevMinuteDelta = 0;
+ hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
+ eventElement.draggable('option', 'revert',
!cell);
+ clearOverlays();
+ if (cell) {
+ dayDelta = colDelta * dis;
+ if (opt('allDaySlot') && !cell.row) {
+ // over full days
+ if (!allDay) {
+ // convert to temporary all-day event
+ allDay = true;
+ timeElement.hide();
+ eventElement.draggable('option',
'grid', null);
}
+ renderDayOverlay(
+ addDays(cloneDate(event.start), dayDelta),
+ addDays(exclEndDay(event), dayDelta)
+ );
+ } else {
+ // on slots
+ resetElement();
}
- }, true);
- matrix.mouse(ev);
- },
- drag: function(ev, ui) {
- slotDelta = Math.round((ui.position.top - origPosition.top) /
slotHeight);
- if (slotDelta != prevSlotDelta) {
- if (!allDay) {
- // update time header
- var minuteDelta = slotDelta * options.slotMinutes,
- newStart = addMinutes(cloneDate(event.start),
minuteDelta),
- newEnd;
- if (event.end) {
- newEnd = addMinutes(cloneDate(event.end),
minuteDelta);
- }
- timeElement.text(formatDates(newStart, newEnd,
view.option('timeFormat')));
- }
- prevSlotDelta = slotDelta;
}
- matrix.mouse(ev);
- },
- stop: function(ev, ui) {
- view.clearOverlays();
- view.trigger('eventDragStop', eventElement, event, ev,
ui);
- 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 {
- view.eventDrop(
- this, event, dayDelta,
- allDay ? 0 : slotDelta * options.slotMinutes, //
minute delta
- allDay, ev, ui
- );
+ }, ev, 'drag');
+ },
+ drag: function(ev, ui) {
+ minuteDelta = Math.round((ui.position.top - origPosition.top) /
slotHeight) * opt('slotMinutes');
+ if (minuteDelta != prevMinuteDelta) {
+ if (!allDay) {
+ updateTimeText(minuteDelta);
}
+ prevMinuteDelta = minuteDelta;
}
- });
- }
- }
-
-
- /* Event Resizing
- -----------------------------------------------------------------------------*/
-
- // for TIMESLOT events
-
- function resizableSlotEvent(event, eventElement, timeElement) {
- if (!options.disableResizing && eventElement.resizable) {
- var slotDelta, prevSlotDelta;
- eventElement.resizable({
- handles: {
- s: 'div.ui-resizable-s'
- },
- grid: slotHeight,
- start: function(ev, ui) {
- slotDelta = prevSlotDelta = 0;
- view.hideEvents(event, eventElement);
- if ($.browser.msie && $.browser.version == '6.0')
{
- eventElement.css('overflow', 'hidden');
- }
- eventElement.css('z-index', 9);
- view.trigger('eventResizeStart', this, event, ev, ui);
- },
- resize: function(ev, ui) {
- // don't rely on ui.size.height, doesn't take grid into
account
- slotDelta = Math.round((Math.max(slotHeight,
eventElement.height()) - ui.originalSize.height) / slotHeight);
- if (slotDelta != prevSlotDelta) {
- 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) {
- view.trigger('eventResizeStop', this, event, ev, ui);
- 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
- }
- }
- });
- }
- }
-
-
- /* Selecting
- -----------------------------------------------------------------------------*/
-
- daySelectionManager = new SelectionManager(
- view,
- unselect,
- function(startDate, endDate, allDay) {
- renderDayOverlay(
- selectionMatrix,
- startDate,
- addDays(cloneDate(endDate), 1)
- );
},
- clearSelection
- );
-
- function daySelectionMousedown(ev) {
- if (view.option('selectable')) {
- selectionMatrix = buildDayMatrix(function(cell) {
- if (cell) {
- var d = dayColDate(cell.col);
- daySelectionManager.drag(d, d, true);
+ stop: function(ev, ui) {
+ var cell = hoverListener.stop();
+ clearOverlays();
+ trigger('eventDragStop', eventElement, event, ev, ui);
+ if (cell && (dayDelta || minuteDelta || allDay)) {
+ // changed!
+ eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta,
allDay, ev, ui);
} else {
- daySelectionManager.drag();
+ // either no change or out-of-bounds (draggable has already
reverted)
+ resetElement();
+ eventElement.css('filter', ''); // clear IE
opacity side-effects
+ eventElement.css(origPosition); // sometimes fast drags make
event revert to wrong position
+ updateTimeText(0);
+ showEvents(event, eventElement);
}
- });
- documentDragHelp(
- function(ev) {
- selectionMatrix.mouse(ev);
- },
- function(ev) {
- daySelectionManager.dragStop(ev);
- }
- );
- daySelectionManager.dragStart(ev);
- selectionMatrix.mouse(ev);
- return false; // prevent auto-unselect and text selection
- }
- }
-
- slotSelectionManager = new SelectionManager(
- view,
- unselect,
- renderSlotSelection,
- clearSelection
- );
-
- function slotSelectionMousedown(ev) {
- if (view.option('selectable')) {
- selectionMatrix = buildSlotMatrix(function(cell) {
- if (cell) {
- var d = slotCellDate(cell.row, cell.origCol);
- slotSelectionManager.drag(d, addMinutes(cloneDate(d),
options.slotMinutes), false);
- } else {
- slotSelectionManager.drag();
- }
- });
- documentDragHelp(
- function(ev) {
- selectionMatrix.mouse(ev);
- },
- function(ev) {
- slotSelectionManager.dragStop(ev);
- }
- );
- slotSelectionManager.dragStart(ev);
- selectionMatrix.mouse(ev);
- return false; // prevent auto-unselect and text selection
- }
- }
-
- documentUnselectAuto(view, unselect);
-
- this.select = function(start, end, allDay) {
- if (allDay) {
- if (options.allDaySlot) {
- if (!end) {
- end = cloneDate(start);
- }
- selectionMatrix = buildDayMatrix();
- daySelectionManager.select(start, end, allDay);
}
- } else {
- if (!end) {
- end = addMinutes(cloneDate(start), options.slotMinutes);
+ });
+ function updateTimeText(minuteDelta) {
+ var newStart = addMinutes(cloneDate(event.start), minuteDelta);
+ var newEnd;
+ if (event.end) {
+ newEnd = addMinutes(cloneDate(event.end), minuteDelta);
}
- selectionMatrix = buildSlotMatrix();
- slotSelectionManager.select(start, end, allDay);
+ timeElement.text(formatDates(newStart, newEnd,
opt('timeFormat')));
}
- };
- function unselect() {
- slotSelectionManager.unselect();
- daySelectionManager.unselect();
- }
-
- this.unselect = unselect;
-
-
- /* Selecting drawing utils
- -----------------------------------------------------------------------------*/
-
- function renderSlotSelection(startDate, endDate) {
- var helperOption = view.option('selectHelper');
- if (helperOption) {
- var col = dayDiff(startDate, view.visStart);
- if (col >= 0 && col < colCnt) { // only works when times
are on same day
- var rect = selectionMatrix.rect(0, col * dis + dit, 1, col * dis +
dit + 1, bodyContent); // only for horizontal coords
- var top = timePosition(startDate, startDate);
- var bottom = timePosition(startDate, endDate);
- if (bottom > top) { // protect against selections that are
entirely before or after visible range
- rect.top = top;
- rect.height = bottom - top;
- rect.left += 2;
- rect.width -= 5;
- if ($.isFunction(helperOption)) {
- var helperRes = helperOption(startDate, endDate);
- if (helperRes) {
- rect.position = 'absolute';
- rect.zIndex = 8;
- selectionHelper = $(helperRes)
- .css(rect)
- .appendTo(bodyContent);
- }
- } else {
- selectionHelper = $(slotSegHtml(
- {
- title: '',
- start: startDate,
- end: endDate,
- className: [],
- editable: false
- },
- rect,
- 'fc-event fc-event-vert fc-corner-top
fc-corner-bottom '
- ));
- if ($.browser.msie) {
- selectionHelper.find('span.fc-event-bg').hide();
// nested opacities mess up in IE, just hide
- }
- selectionHelper.css('opacity',
view.option('dragOpacity'));
- }
- if (selectionHelper) {
- slotBind(selectionHelper);
- bodyContent.append(selectionHelper);
- setOuterWidth(selectionHelper, rect.width, true); // needs to
be after appended
- setOuterHeight(selectionHelper, rect.height, true);
- }
- }
+ function resetElement() {
+ // convert back to original slot-event
+ if (allDay) {
+ timeElement.css('display', ''); // show() was causing
display=inline
+ eventElement.draggable('option', 'grid', [colWidth,
slotHeight]);
+ allDay = false;
}
- } else {
- renderSlotOverlay(selectionMatrix, startDate, endDate);
}
}
- function clearSelection() {
- clearOverlays();
- if (selectionHelper) {
- selectionHelper.remove();
- selectionHelper = null;
- }
- }
+ /* Resizing
+
--------------------------------------------------------------------------------------*/
- /* Semi-transparent Overlay Helpers
- -----------------------------------------------------*/
- function renderDayOverlay(matrix, startDate, endDate) {
- var startCol, endCol;
- if (rtl) {
- startCol = dayDiff(endDate, view.visStart) * dis + dit + 1;
- endCol = dayDiff(startDate, view.visStart) * dis + dit + 1;
- } else {
- startCol = dayDiff(startDate, view.visStart);
- endCol = dayDiff(endDate, view.visStart);
- }
- startCol = Math.max(0, startCol);
- endCol = Math.min(colCnt, endCol);
- if (startCol < endCol) {
- var rect = matrix.rect(0, startCol, 1, endCol, head);
- dayBind(
- view.renderOverlay(rect, head)
+ function resizableSlotEvent(event, eventElement, timeElement) {
+ var slotDelta, prevSlotDelta;
+ var slotHeight = getSlotHeight();
+ eventElement.resizable({
+ handles: {
+ s: 'div.ui-resizable-s'
+ },
+ grid: slotHeight,
+ start: function(ev, ui) {
+ slotDelta = prevSlotDelta = 0;
+ hideEvents(event, eventElement);
+ eventElement.css('z-index', 9);
+ trigger('eventResizeStart', this, event, ev, ui);
+ },
+ resize: function(ev, ui) {
+ // don't rely on ui.size.height, doesn't take grid into
account
+ slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) -
ui.originalSize.height) / slotHeight);
+ if (slotDelta != prevSlotDelta) {
+ timeElement.text(
+ formatDates(
+ event.start,
+ (!slotDelta && !event.end) ? null : // no
change, so don't display time range
+ addMinutes(eventEnd(event),
opt('slotMinutes') * slotDelta),
+ opt('timeFormat')
+ )
);
- }
- }
-
- function renderSlotOverlay(matrix, overlayStart, overlayEnd) {
- var dayStart = cloneDate(view.visStart);
- var dayEnd = addDays(cloneDate(dayStart), 1);
- for (var i = 0; i < colCnt; i++) {
- var stretchStart = new Date(Math.max(dayStart, overlayStart));
- var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
- if (stretchStart < stretchEnd) {
- var rect = matrix.rect(0, i * dis + dit, 1, i * dis + dit + 1,
bodyContent); // only use it for horizontal coords
- var top = timePosition(dayStart, stretchStart);
- var bottom = timePosition(dayStart, stretchEnd);
- rect.top = top;
- rect.height = bottom - top;
- slotBind(
- view.renderOverlay(rect, bodyContent)
- );
+ prevSlotDelta = slotDelta;
+ }
+ },
+ stop: function(ev, ui) {
+ trigger('eventResizeStop', this, event, ev, ui);
+ if (slotDelta) {
+ eventResize(this, event, 0, opt('slotMinutes') *
slotDelta, ev, ui);
+ } else {
+ eventElement.css('z-index', 8);
+ showEvents(event, eventElement);
+ // BUG: if event was really short, need to put title back in
span
+ }
}
- addDays(dayStart, 1);
- addDays(dayEnd, 1);
- }
+ });
}
- function clearOverlays() {
- view.clearOverlays();
- }
-
- /* Coordinate Utilities
- -----------------------------------------------------------------------------*/
-
- // get the Y coordinate of the given time on the given day (both Date objects)
- function timePosition(day, time) { // both date objects. day holds 00:00 of
current day
- day = cloneDate(day, true);
- if (time < addMinutes(cloneDate(day), minMinute)) {
- return 0;
- }
- if (time >= addMinutes(cloneDate(day), maxMinute)) {
- return bodyContent.height();
- }
- var slotMinutes = options.slotMinutes,
- minutes = time.getHours() * 60 + time.getMinutes() - minMinute,
- slotI = Math.floor(minutes / slotMinutes),
- slotTop = slotTopCache[slotI];
- if (slotTop === undefined) {
- slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI +
') td div')[0].offsetTop;
- }
- return Math.max(0, Math.round(
- slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
- ));
- }
-
- function buildDayMatrix(changeCallback, includeSlotArea) {
- var rowElements = options.allDaySlot ? head.find('td') : $([]);
- if (includeSlotArea) {
- rowElements = rowElements.add(body);
- }
- return new HoverMatrix(rowElements, bg.find('td'), changeCallback);
- }
-
- function buildSlotMatrix(changeCallback) {
- return new HoverMatrix(bodyTable.find('td'), bg.find('td'),
changeCallback);
- }
-
-
- /* Date Utilities
- ----------------------------------------------------*/
-
- function slotEventEnd(event) {
- if (event.end) {
- return cloneDate(event.end);
- } else {
- return addMinutes(cloneDate(event.start), options.defaultEventMinutes);
- }
- }
-
- function dayOfWeekCol(dayOfWeek) {
- return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis +
dit;
- }
-
-
- // generating dates from cell row & columns
-
- function dayColDate(col) {
- return addDays(cloneDate(view.visStart), col * dis + dit);
- }
-
- function slotCellDate(row, col) {
- var d = dayColDate(col);
- addMinutes(d, minMinute + row * options.slotMinutes);
- return d;
- }
-
-
}
- // count the number of colliding, higher-level segments (for event squishing)
-
function countForwardSegs(levels) {
var i, j, k, level, segForward, segBack;
for (i = levels.length - 1; i > 0; i--) {
@@ -2873,67 +4239,93 @@
}
- /* Methods & Utilities for All Views
- -----------------------------------------------------------------------------*/
+ function View(element, calendar, viewName) {
+ var t = this;
- var viewMethods = {
- /*
- * Objects inheriting these methods must implement the following
properties/methods:
- * - title
- * - start
- * - end
- * - visStart
- * - visEnd
- * - defaultEventEnd(event)
- * - render(events)
- * - rerenderEvents()
- *
- *
- * z-index reservations:
- * 3 - day-overlay
- * 8 - events
- * 9 - dragging/resizing events
- *
- */
+ // exports
+ t.element = element;
+ t.calendar = calendar;
+ t.name = viewName;
+ t.opt = opt;
+ t.trigger = trigger;
+ //t.setOverflowHidden = setOverflowHidden;
+ t.isEventDraggable = isEventDraggable;
+ t.isEventResizable = isEventResizable;
+ t.reportEvents = reportEvents;
+ t.eventEnd = eventEnd;
+ t.reportEventElement = reportEventElement;
+ t.reportEventClear = reportEventClear;
+ t.eventElementHandlers = eventElementHandlers;
+ t.showEvents = showEvents;
+ t.hideEvents = hideEvents;
+ t.eventDrop = eventDrop;
+ t.eventResize = eventResize;
+ // t.title
+ // t.start, t.end
+ // t.visStart, t.visEnd
+ // imports
+ var defaultEventEnd = t.defaultEventEnd;
+ var normalizeEvent = calendar.normalizeEvent; // in EventManager
+ var reportEventChange = calendar.reportEventChange;
- init: function(element, options) {
- this.element = element;
- this.options = options;
- this.eventsByID = {};
- this.eventElements = [];
- this.eventElementsByID = {};
- this.usedOverlays = [];
- this.unusedOverlays = [];
- },
+ // locals
+ var eventsByID = {};
+ var eventElements = [];
+ var eventElementsByID = {};
+ var options = calendar.options;
- // 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]));
+ function opt(name, viewNameOverride) {
+ var v = options[name];
+ if (typeof v == 'object') {
+ return smartProperty(v, viewNameOverride || viewName);
}
- },
+ return v;
+ }
+ function trigger(name, thisObj) {
+ return calendar.trigger.apply(
+ calendar,
+ [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2),
[t])
+ );
+ }
- // returns a Date object for an event's end
- eventEnd: function(event) {
- return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); //
TODO: make sure always using copies
- },
+ /*
+ function setOverflowHidden(bool) {
+ element.css('overflow', bool ? 'hidden' : '');
+ }
+ */
+ function isEventDraggable(event) {
+ return isEventEditable(event) && !opt('disableDragging');
+ }
+
+ function isEventResizable(event) { // but also need to make sure the seg.isEnd ==
true
+ return isEventEditable(event) && !opt('disableResizing');
+ }
+
+
+ function isEventEditable(event) {
+ return firstDefined(event.editable, (event.source || {}).editable,
opt('editable'));
+ }
+
+
+ /* Event Data
+
------------------------------------------------------------------------------*/
+
+
// 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 = {};
+ function reportEvents(events) { // events are already normalized at this point
+ eventsByID = {};
+ var i, len = events.length, event;
for (i = 0; i < len; i++) {
event = events[i];
if (eventsByID[event._id]) {
@@ -2942,85 +4334,132 @@
eventsByID[event._id] = [event];
}
}
- },
+ }
+ // returns a Date object for an event's end
+ function eventEnd(event) {
+ return event.end ? cloneDate(event.end) : defaultEventEnd(event);
+ }
+
+ /* Event Elements
+
------------------------------------------------------------------------------*/
+
+
// report when view creates an element for an event
-
- reportEventElement: function(event, element) {
- this.eventElements.push(element);
- var eventElementsByID = this.eventElementsByID;
+ function reportEventElement(event, element) {
+ eventElements.push(element);
if (eventElementsByID[event._id]) {
eventElementsByID[event._id].push(element);
} else {
eventElementsByID[event._id] = [element];
}
- },
+ }
+ function reportEventClear() {
+ eventElements = [];
+ eventElementsByID = {};
+ }
- // event element manipulation
- _clearEvents: function() { // only resets hashes
- this.eventElements = [];
- this.eventElementsByID = {};
- },
+ // attaches eventClick, eventMouseover, eventMouseout
+ function eventElementHandlers(event, eventElement) {
+ eventElement
+ .click(function(ev) {
+ if (!eventElement.hasClass('ui-draggable-dragging') &&
+ !eventElement.hasClass('ui-resizable-resizing')) {
+ return trigger('eventClick', this, event, ev);
+ }
+ })
+ .hover(
+ function(ev) {
+ trigger('eventMouseover', this, event, ev);
+ },
+ function(ev) {
+ trigger('eventMouseout', this, event, ev);
+ }
+ );
+ // TODO: don't fire eventMouseover/eventMouseout *while* dragging is
occuring (on subject element)
+ // TODO: same for resizing
+ }
- showEvents: function(event, exceptElement) {
- this._eee(event, exceptElement, 'show');
- },
- hideEvents: function(event, exceptElement) {
- this._eee(event, exceptElement, 'hide');
- },
+ function showEvents(event, exceptElement) {
+ eachEventElement(event, exceptElement, 'show');
+ }
- _eee: function(event, exceptElement, funcName) { // event-element-each
- var elements = this.eventElementsByID[event._id],
+
+ function hideEvents(event, exceptElement) {
+ eachEventElement(event, exceptElement, 'hide');
+ }
+
+
+ function eachEventElement(event, exceptElement, funcName) {
+ var elements = eventElementsByID[event._id],
i, len = elements.length;
for (i = 0; i < len; i++) {
- if (elements[i][0] != exceptElement[0]) { // AHAHAHAHAHAHAHAH
+ if (!exceptElement || elements[i][0] != exceptElement[0]) {
elements[i][funcName]();
}
}
- },
+ }
+ /* Event Modification Reporting
+
---------------------------------------------------------------------------------*/
- // event modification reporting
- eventDrop: function(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
- var view = this,
- oldAllDay = event.allDay,
- eventId = event._id;
- view.moveEvents(view.eventsByID[eventId], dayDelta, minuteDelta, allDay);
- view.trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay,
function() { // TODO: change docs
- // TODO: investigate cases where this inverse technique might not work
- view.moveEvents(view.eventsByID[eventId], -dayDelta, -minuteDelta,
oldAllDay);
- view.rerenderEvents();
- }, ev, ui);
- view.eventsChanged = true;
- view.rerenderEvents(eventId);
- },
+ function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
+ var oldAllDay = event.allDay;
+ var eventId = event._id;
+ moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
+ trigger(
+ 'eventDrop',
+ e,
+ event,
+ dayDelta,
+ minuteDelta,
+ allDay,
+ function() {
+ // TODO: investigate cases where this inverse technique might not
work
+ moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta,
oldAllDay);
+ reportEventChange(eventId);
+ },
+ ev,
+ ui
+ );
+ reportEventChange(eventId);
+ }
- eventResize: function(e, event, dayDelta, minuteDelta, ev, ui) {
- var view = this,
- eventId = event._id;
- view.elongateEvents(view.eventsByID[eventId], dayDelta, minuteDelta);
- view.trigger('eventResize', e, event, dayDelta, minuteDelta,
function() {
- // TODO: investigate cases where this inverse technique might not work
- view.elongateEvents(view.eventsByID[eventId], -dayDelta, -minuteDelta);
- view.rerenderEvents();
- }, ev, ui);
- view.eventsChanged = true;
- view.rerenderEvents(eventId);
- },
+ function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
+ var eventId = event._id;
+ elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
+ trigger(
+ 'eventResize',
+ e,
+ event,
+ dayDelta,
+ minuteDelta,
+ function() {
+ // TODO: investigate cases where this inverse technique might not
work
+ elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
+ reportEventChange(eventId);
+ },
+ ev,
+ ui
+ );
+ reportEventChange(eventId);
+ }
- // event modification
+ /* Event Modification Math
+
---------------------------------------------------------------------------------*/
- moveEvents: function(events, dayDelta, minuteDelta, allDay) {
+
+ function moveEvents(events, dayDelta, minuteDelta, allDay) {
minuteDelta = minuteDelta || 0;
for (var e, len = events.length, i = 0; i < len; i++) {
e = events[i];
@@ -3031,856 +4470,728 @@
if (e.end) {
e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
}
- normalizeEvent(e, this.options);
+ normalizeEvent(e, options);
}
- },
+ }
- elongateEvents: function(events, dayDelta, minuteDelta) {
+
+ function elongateEvents(events, dayDelta, minuteDelta) {
minuteDelta = minuteDelta || 0;
for (var e, len = events.length, i = 0; i < len; i++) {
e = events[i];
- e.end = addMinutes(addDays(this.eventEnd(e), dayDelta, true),
minuteDelta);
- normalizeEvent(e, this.options);
+ e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
+ normalizeEvent(e, options);
}
- },
+ }
+ }
- // semi-transparent overlay (while dragging or selecting)
+ function DayEventRenderer() {
+ var t = this;
- renderOverlay: function(rect, parent) {
- var e = this.unusedOverlays.shift();
- if (!e) {
- e = $("<div class='fc-cell-overlay'
style='position:absolute;z-index:3'/>");
- }
- if (e[0].parentNode != parent[0]) {
- e.appendTo(parent);
- }
- this.usedOverlays.push(e.css(rect).show());
- return e;
- },
- clearOverlays: function() {
- var e;
- while (e = this.usedOverlays.shift()) {
- this.unusedOverlays.push(e.hide().unbind());
- }
- },
+ // exports
+ t.renderDaySegs = renderDaySegs;
+ t.resizableDayEvent = resizableDayEvent;
+ // imports
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var isEventDraggable = t.isEventDraggable;
+ var isEventResizable = t.isEventResizable;
+ var eventEnd = t.eventEnd;
+ var reportEventElement = t.reportEventElement;
+ var showEvents = t.showEvents;
+ var hideEvents = t.hideEvents;
+ var eventResize = t.eventResize;
+ var getRowCnt = t.getRowCnt;
+ var getColCnt = t.getColCnt;
+ var getColWidth = t.getColWidth;
+ var allDayRow = t.allDayRow;
+ var allDayBounds = t.allDayBounds;
+ var colContentLeft = t.colContentLeft;
+ var colContentRight = t.colContentRight;
+ var dayOfWeekCol = t.dayOfWeekCol;
+ var dateCell = t.dateCell;
+ var compileDaySegs = t.compileDaySegs;
+ var getDaySegmentContainer = t.getDaySegmentContainer;
+ var bindDaySeg = t.bindDaySeg; //TODO: streamline this
+ var formatDates = t.calendar.formatDates;
+ var renderDayOverlay = t.renderDayOverlay;
+ var clearOverlays = t.clearOverlays;
+ var clearSelection = t.clearSelection;
- // common horizontal event resizing
+ /* Rendering
+ -----------------------------------------------------------------------------*/
- resizableDayEvent: function(event, eventElement, colWidth) {
- var view = this;
- if (!view.options.disableResizing && eventElement.resizable) {
- eventElement.resizable({
- handles: view.options.isRTL ? {w:'div.ui-resizable-w'} :
{e:'div.ui-resizable-e'},
- grid: colWidth,
- minWidth: colWidth / 2, // need this or else IE throws errors when
too small
- containment: view.element.parent().parent(), // the main element...
- // ... a fix. wouldn't allow extending to last column in agenda
views (jq ui bug?)
- start: function(ev, ui) {
- eventElement.css('z-index', 9);
- 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.eventResize(this, event, dayDelta, 0, ev, ui);
- } else {
- eventElement.css('z-index', 8);
- view.showEvents(event, eventElement);
- }
+
+ function renderDaySegs(segs, modifiedEventId) {
+ var segmentContainer = getDaySegmentContainer();
+ var rowDivs;
+ var rowCnt = getRowCnt();
+ var colCnt = getColCnt();
+ var i = 0;
+ var rowI;
+ var levelI;
+ var colHeights;
+ var j;
+ var segCnt = segs.length;
+ var seg;
+ var top;
+ var k;
+ segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
+ daySegElementResolve(segs, segmentContainer.children());
+ daySegElementReport(segs);
+ daySegHandlers(segs, segmentContainer, modifiedEventId);
+ daySegCalcHSides(segs);
+ daySegSetWidths(segs);
+ daySegCalcHeights(segs);
+ rowDivs = getRowDivs();
+ // set row heights, calculate event tops (in relation to row top)
+ for (rowI = 0; rowI < rowCnt; rowI++) {
+ levelI = 0;
+ colHeights = [];
+ for (j = 0; j < colCnt; j++) {
+ colHeights[j] = 0;
+ }
+ while (i < segCnt && (seg = segs[i]).row == rowI) {
+ // loop through segs in a row
+ top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
+ seg.top = top;
+ top += seg.outerHeight;
+ for (k = seg.startCol; k < seg.endCol; k++) {
+ colHeights[k] = top;
}
- });
+ i++;
+ }
+ rowDivs[rowI].height(arrayMax(colHeights));
}
- },
+ daySegSetTops(segs, getRowTops(rowDivs));
+ }
+ function renderTempDaySegs(segs, adjustRow, adjustTop) {
+ var tempContainer = $("<div/>");
+ var elements;
+ var segmentContainer = getDaySegmentContainer();
+ var i;
+ var segCnt = segs.length;
+ var element;
+ tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
+ elements = tempContainer.children();
+ segmentContainer.append(elements);
+ daySegElementResolve(segs, elements);
+ daySegCalcHSides(segs);
+ daySegSetWidths(segs);
+ daySegCalcHeights(segs);
+ daySegSetTops(segs, getRowTops(getRowDivs()));
+ elements = [];
+ for (i = 0; i < segCnt; i++) {
+ element = segs[i].element;
+ if (element) {
+ if (segs[i].row === adjustRow) {
+ element.css('top', adjustTop);
+ }
+ elements.push(element[0]);
+ }
+ }
+ return $(elements);
+ }
- // attaches eventClick, eventMouseover, eventMouseout
- eventElementHandlers: function(event, eventElement) {
- var view = this;
- eventElement
- .click(function(ev) {
- if (!eventElement.hasClass('ui-draggable-dragging') &&
- !eventElement.hasClass('ui-resizable-resizing')) {
- return view.trigger('eventClick', this, event, ev);
+ function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
+ var rtl = opt('isRTL');
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var event;
+ var url;
+ var classes;
+ var bounds = allDayBounds();
+ var minLeft = bounds.left;
+ var maxLeft = bounds.right;
+ var leftCol;
+ var rightCol;
+ var left;
+ var right;
+ var skinCss;
+ var html = '';
+ // calculate desired position/dimensions, create html
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ event = seg.event;
+ classes = ['fc-event', 'fc-event-skin',
'fc-event-hori'];
+ if (isEventDraggable(event)) {
+ classes.push('fc-event-draggable');
}
- })
- .hover(
- function(ev) {
- view.trigger('eventMouseover', this, event, ev);
- },
- function(ev) {
- view.trigger('eventMouseout', this, event, ev);
+ if (rtl) {
+ if (seg.isStart) {
+ classes.push('fc-corner-right');
}
- );
- },
+ if (seg.isEnd) {
+ classes.push('fc-corner-left');
+ }
+ leftCol = dayOfWeekCol(seg.end.getDay() - 1);
+ rightCol = dayOfWeekCol(seg.start.getDay());
+ left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
+ right = seg.isStart ? colContentRight(rightCol) : maxLeft;
+ } else {
+ if (seg.isStart) {
+ classes.push('fc-corner-left');
+ }
+ if (seg.isEnd) {
+ classes.push('fc-corner-right');
+ }
+ leftCol = dayOfWeekCol(seg.start.getDay());
+ rightCol = dayOfWeekCol(seg.end.getDay() - 1);
+ left = seg.isStart ? colContentLeft(leftCol) : minLeft;
+ right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
+ }
+ classes = classes.concat(event.className);
+ if (event.source) {
+ classes = classes.concat(event.source.className || []);
+ }
+ url = event.url;
+ skinCss = getSkinCss(event, opt);
+ if (url) {
+ html += "<a href='" + htmlEscape(url) +
"'";
+ } else {
+ html += "<div";
+ }
+ html +=
+ " class='" + classes.join(' ') +
"'" +
+ " style='position:absolute;z-index:8;left:"
+ left + "px;" + skinCss + "'" +
+ ">" +
+ "<div" +
+ " class='fc-event-inner fc-event-skin'"
+
+ (skinCss ? " style='" + skinCss +
"'" : '') +
+ ">";
+ if (!event.allDay && seg.isStart) {
+ html +=
+ "<span class='fc-event-time'>" +
+ htmlEscape(formatDates(event.start, event.end,
opt('timeFormat'))) +
+ "</span>";
+ }
+ html +=
+ "<span class='fc-event-title'>" +
htmlEscape(event.title) + "</span>" +
+ "</div>";
+ if (seg.isEnd && isEventResizable(event)) {
+ html +=
+ "<div class='ui-resizable-handle
ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
+ " " + // makes
hit area a lot better for IE6/7
+ "</div>";
+ }
+ html +=
+ "</" + (url ? "a" : "div" ) +
">";
+ seg.left = left;
+ seg.outerWidth = right - left;
+ seg.startCol = leftCol;
+ seg.endCol = rightCol + 1; // needs to be exclusive
+ }
+ return html;
+ }
+ function daySegElementResolve(segs, elements) { // sets seg.element
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var event;
+ var element;
+ var triggerRes;
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ event = seg.event;
+ element = $(elements[i]); // faster than .eq()
+ triggerRes = trigger('eventRender', event, event, element);
+ if (triggerRes === false) {
+ element.remove();
+ } else {
+ if (triggerRes && triggerRes !== true) {
+ triggerRes = $(triggerRes)
+ .css({
+ position: 'absolute',
+ left: seg.left
+ });
+ element.replaceWith(triggerRes);
+ element = triggerRes;
+ }
+ seg.element = element;
+ }
+ }
+ }
- // get a property from the 'options' object, using smart view naming
- option: function(name, viewName) {
- var v = this.options[name];
- if (typeof v == 'object') {
- return smartProperty(v, viewName || this.name);
+ function daySegElementReport(segs) {
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ element = seg.element;
+ if (element) {
+ reportEventElement(seg.event, element);
+ }
}
- return v;
- },
+ }
-
- // event rendering utilities
-
- sliceSegs: function(events, visEventEnds, start, end) {
- var segs = [],
- i, len = events.length, event,
- eventStart, eventEnd,
- segStart, segEnd,
- isStart, isEnd;
- for (i = 0; i < len; i++) {
- event = events[i];
- eventStart = event.start;
- eventEnd = visEventEnds[i];
- if (eventEnd > start && eventStart < end) {
- if (eventStart < start) {
- segStart = cloneDate(start);
- isStart = false;
+ function daySegHandlers(segs, segmentContainer, modifiedEventId) {
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ var event;
+ // retrieve elements, run through eventRender callback, bind handlers
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ element = seg.element;
+ if (element) {
+ event = seg.event;
+ if (event._id === modifiedEventId) {
+ bindDaySeg(event, element, seg);
} else {
- segStart = eventStart;
- isStart = true;
+ element[0]._fci = i; // for lazySegBind
}
- if (eventEnd > end) {
- segEnd = cloneDate(end);
- isEnd = false;
- } else {
- segEnd = eventEnd;
- isEnd = true;
- }
- segs.push({
- event: event,
- start: segStart,
- end: segEnd,
- isStart: isStart,
- isEnd: isEnd,
- msLength: segEnd - segStart
- });
}
}
- return segs.sort(segCmp);
+ lazySegBind(segmentContainer, segs, bindDaySeg);
}
- };
+ function daySegCalcHSides(segs) { // also sets seg.key
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ var key, val;
+ var hsideCache = {};
+ // record event horizontal sides
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ element = seg.element;
+ if (element) {
+ key = seg.key = cssKey(element[0]);
+ val = hsideCache[key];
+ if (val === undefined) {
+ val = hsideCache[key] = hsides(element, true);
+ }
+ seg.hsides = val;
+ }
+ }
+ }
- function lazySegBind(container, segs, bindHandlers) {
- container.unbind('mouseover').mouseover(function(ev) {
- var parent = ev.target, e,
- i, seg;
- while (parent != this) {
- e = parent;
- parent = parent.parentNode;
- }
- if ((i = e._fci) !== undefined) {
- e._fci = undefined;
+ function daySegSetWidths(segs) {
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ for (i = 0; i < segCnt; i++) {
seg = segs[i];
- bindHandlers(seg.event, seg.element, seg);
- $(ev.target).trigger(ev);
+ element = seg.element;
+ if (element) {
+ element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) +
'px';
+ }
}
- ev.stopPropagation();
- });
- }
+ }
- // event rendering calculation utilities
-
- function stackSegs(segs) {
- var levels = [],
- i, len = segs.length, seg,
- j, collide, k;
- for (i = 0; i < len; i++) {
- seg = segs[i];
- j = 0; // the level index where seg should belong
- while (true) {
- collide = false;
- if (levels[j]) {
- for (k = 0; k < levels[j].length; k++) {
- if (segsCollide(levels[j][k], seg)) {
- collide = true;
- break;
- }
+ function daySegCalcHeights(segs) {
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ var key, val;
+ var vmarginCache = {};
+ // record event heights
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ element = seg.element;
+ if (element) {
+ key = seg.key; // created in daySegCalcHSides
+ val = vmarginCache[key];
+ if (val === undefined) {
+ val = vmarginCache[key] = vmargins(element);
}
+ seg.outerHeight = element[0].offsetHeight + val;
}
- if (collide) {
- j++;
- } else {
- break;
- }
}
- if (levels[j]) {
- levels[j].push(seg);
- } else {
- levels[j] = [seg];
+ }
+
+
+ function getRowDivs() {
+ var i;
+ var rowCnt = getRowCnt();
+ var rowDivs = [];
+ for (i = 0; i < rowCnt; i++) {
+ rowDivs[i] = allDayRow(i)
+ .find('td:first div.fc-day-content > div'); // optimal
selector?
}
+ return rowDivs;
}
- return levels;
- }
- function segCmp(a, b) {
- return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
- }
- function segsCollide(seg1, seg2) {
- return seg1.end > seg2.start && seg1.start < seg2.end;
- }
+ function getRowTops(rowDivs) {
+ var i;
+ var rowCnt = rowDivs.length;
+ var tops = [];
+ for (i = 0; i < rowCnt; i++) {
+ tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element
needs position:relative if in a table cell!!!!
+ }
+ return tops;
+ }
- function SelectionManager(view, initFunc, displayFunc, clearFunc) {
+ function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ var event;
+ for (i = 0; i < segCnt; i++) {
+ seg = segs[i];
+ element = seg.element;
+ if (element) {
+ element[0].style.top = rowTops[seg.row] + (seg.top || 0) +
'px';
+ event = seg.event;
+ trigger('eventAfterRender', event, event, element);
+ }
+ }
+ }
- var t = this;
- var selected = false;
- var initialElement;
- var initialRange;
- var start;
- var end;
- var allDay;
+ /* Resizing
+
-----------------------------------------------------------------------------------*/
- t.dragStart = function(ev) {
- initFunc();
- start = end = undefined;
- initialRange = undefined;
- initialElement = ev.currentTarget;
- };
+ function resizableDayEvent(event, element, seg) {
+ var rtl = opt('isRTL');
+ var direction = rtl ? 'w' : 'e';
+ var handle = element.find('div.ui-resizable-' + direction);
+ var isResizing = false;
- t.drag = function(currentStart, currentEnd, currentAllDay) {
- if (currentStart) {
- var range = [currentStart, currentEnd];
- if (!initialRange) {
- initialRange = range;
+ // TODO: look into using jquery-ui mouse widget for this stuff
+ disableTextSelection(element); // prevent native <a> selection for IE
+ element
+ .mousedown(function(ev) { // prevent native <a> selection for
others
+ ev.preventDefault();
+ })
+ .click(function(ev) {
+ if (isResizing) {
+ ev.preventDefault(); // prevent link from being visited (only
method that worked in IE6)
+ ev.stopImmediatePropagation(); // prevent fullcalendar
eventClick handler from being called
+ // (eventElementHandlers needs to be bound after
resizableDayEvent)
+ }
+ });
+
+ handle.mousedown(function(ev) {
+ if (ev.which != 1) {
+ return; // needs to be left mouse button
}
- var dates = initialRange.concat(range).sort(cmp);
- start = dates[0];
- end = dates[3];
- allDay = currentAllDay;
- clearFunc();
- displayFunc(cloneDate(start), cloneDate(end), allDay);
- } else {
- // called with no arguments
- start = end = undefined;
- clearFunc();
- }
- };
+ isResizing = true;
+ var hoverListener = t.getHoverListener();
+ var rowCnt = getRowCnt();
+ var colCnt = getColCnt();
+ var dis = rtl ? -1 : 1;
+ var dit = rtl ? colCnt - 1 : 0;
+ var elementTop = element.css('top');
+ var dayDelta;
+ var helpers;
+ var eventCopy = $.extend({}, event);
+ var minCell = dateCell(event.start);
+ clearSelection();
+ $('body')
+ .css('cursor', direction + '-resize')
+ .one('mouseup', mouseup);
+ trigger('eventResizeStart', this, event, ev);
+ hoverListener.start(function(cell, origCell) {
+ if (cell) {
+ var r = Math.max(minCell.row, cell.row);
+ var c = cell.col;
+ if (rowCnt == 1) {
+ r = 0; // hack for all-day area in agenda views
+ }
+ if (r == minCell.row) {
+ if (rtl) {
+ c = Math.min(minCell.col, c);
+ } else {
+ c = Math.max(minCell.col, c);
+ }
+ }
+ dayDelta = (r * 7 + c * dis + dit) - (origCell.row * 7 +
origCell.col * dis + dit);
+ var newEnd = addDays(eventEnd(event), dayDelta, true);
+ if (dayDelta) {
+ eventCopy.end = newEnd;
+ var oldHelpers = helpers;
+ helpers = renderTempDaySegs(compileDaySegs([eventCopy]),
seg.row, elementTop);
+ helpers.find('*').css('cursor', direction +
'-resize');
+ if (oldHelpers) {
+ oldHelpers.remove();
+ }
+ hideEvents(event);
+ } else {
+ if (helpers) {
+ showEvents(event);
+ helpers.remove();
+ helpers = null;
+ }
+ }
+ clearOverlays();
+ renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); //
coordinate grid already rebuild at hoverListener.start
+ }
+ }, ev);
+ function mouseup(ev) {
+ trigger('eventResizeStop', this, event, ev);
+ $('body').css('cursor', '');
+ hoverListener.stop();
+ clearOverlays();
+ if (dayDelta) {
+ eventResize(this, event, dayDelta, 0, ev);
+ // event redraw will clear helpers
+ }
+ // otherwise, the drag handler already restored the old events
- t.dragStop = function(ev) {
- if (start) {
- if (+initialRange[0] == +start && +initialRange[1] == +end) {
- view.trigger('dayClick', initialElement, start, allDay, ev);
+ setTimeout(function() { // make this happen after the element's
click event
+ isResizing = false;
+ }, 0);
}
- _select();
- }
- };
+ });
+ }
- t.select = function(newStart, newEnd, newAllDay) {
- initFunc();
- start = newStart;
- end = newEnd;
- allDay = newAllDay;
- displayFunc(cloneDate(start), cloneDate(end), allDay);
- _select();
- };
+ }
- function _select() { // just set the selected flag, and trigger
- selected = true;
- view.trigger('select', view, start, end, allDay);
- }
+//BUG: unselect needs to be triggered when events are dragged+dropped
+ function SelectionManager() {
+ var t = this;
- function unselect() {
- if (selected) {
- selected = false;
- start = end = undefined;
- clearFunc();
- view.trigger('unselect', view);
- }
- }
+ // exports
+ t.select = select;
t.unselect = unselect;
+ t.reportSelection = reportSelection;
+ t.daySelectionMousedown = daySelectionMousedown;
- }
+ // imports
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var defaultSelectionEnd = t.defaultSelectionEnd;
+ var renderSelection = t.renderSelection;
+ var clearSelection = t.clearSelection;
- function documentDragHelp(mousemove, mouseup) {
- function _mouseup(ev) {
- mouseup(ev);
- $(document)
- .unbind('mousemove', mousemove)
- .unbind('mouseup', _mouseup);
- }
- $(document)
- .mousemove(mousemove)
- .mouseup(_mouseup);
- }
+ // locals
+ var selected = false;
- function documentUnselectAuto(view, unselectFunc) {
- if (view.option('selectable') &&
view.option('unselectAuto')) {
+ // unselectAuto
+ if (opt('selectable') && opt('unselectAuto')) {
$(document).mousedown(function(ev) {
- var ignore = view.option('unselectCancel');
+ var ignore = opt('unselectCancel');
if (ignore) {
if ($(ev.target).parents(ignore).length) { // could be optimized to
stop after first match
return;
}
}
- unselectFunc();
+ unselect(ev);
});
}
- }
- /* Date Math
- -----------------------------------------------------------------------------*/
-
- var DAY_MS = 86400000,
- HOUR_MS = 3600000,
- MINUTE_MS = 60000;
-
- function addYears(d, n, keepTime) {
- d.setFullYear(d.getFullYear() + n);
- if (!keepTime) {
- clearTime(d);
- }
- return d;
- }
-
- function addMonths(d, n, keepTime) { // prevents day overflow/underflow
- if (+d) { // prevent infinite looping on invalid dates
- var m = d.getMonth() + n,
- check = cloneDate(d);
- check.setDate(1);
- check.setMonth(m);
- d.setMonth(m);
- if (!keepTime) {
- clearTime(d);
+ function select(startDate, endDate, allDay) {
+ unselect();
+ if (!endDate) {
+ endDate = defaultSelectionEnd(startDate, allDay);
}
- while (d.getMonth() != check.getMonth()) {
- d.setDate(d.getDate() + (d < check ? 1 : -1));
- }
+ renderSelection(startDate, endDate, allDay);
+ reportSelection(startDate, endDate, allDay);
}
- return d;
- }
- function addDays(d, n, keepTime) { // deals with daylight savings
- if (+d) {
- var dd = d.getDate() + n,
- check = cloneDate(d);
- check.setHours(9); // set to middle of day
- check.setDate(dd);
- d.setDate(dd);
- if (!keepTime) {
- clearTime(d);
- }
- fixDate(d, check);
- }
- return d;
- }
- fc.addDays = addDays;
-
- function fixDate(d, check) { // force d to be on check's YMD, for daylight
savings purposes
- if (+d) { // prevent infinite looping on invalid dates
- while (d.getDate() != check.getDate()) {
- d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
+ function unselect(ev) {
+ if (selected) {
+ selected = false;
+ clearSelection();
+ trigger('unselect', null, ev);
}
}
- }
- 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));
+ function reportSelection(startDate, endDate, allDay, ev) {
+ selected = true;
+ trigger('select', null, startDate, endDate, allDay, ev);
}
- return new Date(+d);
- }
- fc.cloneDate = cloneDate;
- function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
- var i = 0, d;
- do {
- d = new Date(1970, i++, 1);
- } while (d.getHours()); // != 0
- return d;
- }
-
- function skipWeekend(date, inc, excl) {
- inc = inc || 1;
- while (!date.getDay() || (excl && date.getDay() == 1 || !excl &&
date.getDay() == 6)) {
- addDays(date, inc);
- }
- return date;
- }
-
- function dayDiff(d1, d2) { // d1 - d2
- return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
- }
-
-
- /* 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) || (s ? new Date(s) : null);
- }
- // TODO: never return invalid dates (like from new Date(<string>)), return
null instead
- return null;
- };
-
- var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) {
- // derived from
http://delete.me.uk/2005/03/iso8601.html
- // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
- var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T
]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/);
- if (!m) {
- return null;
- }
- var date = new Date(m[1], 0, 1),
- check = new Date(m[1], 0, 1, 9, 0),
- offset = 0;
- if (m[3]) {
- date.setMonth(m[3] - 1);
- check.setMonth(m[3] - 1);
- }
- if (m[5]) {
- date.setDate(m[5]);
- check.setDate(m[5]);
- }
- fixDate(date, check);
- if (m[7]) {
- date.setHours(m[7]);
- }
- if (m[8]) {
- date.setMinutes(m[8]);
- }
- if (m[10]) {
- date.setSeconds(m[10]);
- }
- if (m[12]) {
- date.setMilliseconds(Number("0." + m[12]) * 1000);
- }
- fixDate(date, check);
- if (!ignoreTimezone) {
- if (m[14]) {
- offset = Number(m[16]) * 60 + Number(m[17]);
- offset *= m[15] == '-' ? 1 : -1;
- }
- offset -= date.getTimezoneOffset();
- }
- return new Date(+date + (offset * 60 * 1000));
- };
-
- var parseTime = fc.parseTime = function(s) { // returns minutes since start of day
- if (typeof s == 'number') { // an hour
- return s * 60;
- }
- if (typeof s == 'object') { // a Date object
- return s.getHours() * 60 + s.getMinutes();
- }
- var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
- if (m) {
- var h = parseInt(m[1]);
- if (m[3]) {
- h %= 12;
- if (m[3].toLowerCase().charAt(0) == 'p') {
- h += 12;
- }
- }
- return h * 60 + (m[2] ? parseInt(m[2]) : 0);
- }
- };
-
-
- /* 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; i < len; i++) {
- c = format.charAt(i);
- if (c == "'") {
- for (i2 = i + 1; i2 < len; i2++) {
- if (format.charAt(i2) == "'") {
- if (date) {
- if (i2 == i + 1) {
- res += "'";
- } else {
- res += format.substring(i + 1, i2);
- }
- i = i2;
- }
- break;
+ function daySelectionMousedown(ev) { // not really a generic manager method, oh
well
+ var cellDate = t.cellDate;
+ var cellIsAllDay = t.cellIsAllDay;
+ var hoverListener = t.getHoverListener();
+ var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
+ if (ev.which == 1 && opt('selectable')) { // which==1 means
left mouse button
+ unselect(ev);
+ var _mousedownElement = this;
+ var dates;
+ hoverListener.start(function(cell, origCell) { // TODO: maybe put
cellDate/cellIsAllDay info in cell
+ clearSelection();
+ if (cell && cellIsAllDay(cell)) {
+ dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
+ renderSelection(dates[0], dates[1], true);
+ } else {
+ dates = null;
}
- }
- }
- else if (c == '(') {
- for (i2 = i + 1; i2 < len; i2++) {
- if (format.charAt(i2) == ')') {
- var subres = formatDate(date, format.substring(i + 1, i2),
options);
- if (parseInt(subres.replace(/\D/, ''))) {
- res += subres;
+ }, ev);
+ $(document).one('mouseup', function(ev) {
+ hoverListener.stop();
+ if (dates) {
+ if (+dates[0] == +dates[1]) {
+ reportDayClick(dates[0], true, ev);
}
- i = i2;
- break;
+ reportSelection(dates[0], dates[1], true, ev);
}
- }
+ });
}
- else if (c == '[') {
- for (i2 = i + 1; i2 < len; i2++) {
- if (format.charAt(i2) == ']') {
- var subformat = format.substring(i + 1, i2);
- var subres = formatDate(date, subformat, options);
- if (subres != formatDate(otherDate, subformat, options)) {
- res += subres;
- }
- i = i2;
- break;
- }
- }
- }
- else if (c == '{') {
- date = date2;
- otherDate = date1;
- }
- else if (c == '}') {
- date = date1;
- otherDate = date2;
- }
- else {
- for (i2 = len; i2 > i; 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(i, _element) {
- _element.style.width = width - hsides(_element, includeMargins) +
'px';
- });
}
- function setOuterHeight(element, height, includeMargins) {
- element.each(function(i, _element) {
- _element.style.height = height - vsides(_element, includeMargins) +
'px';
- });
- }
+ function OverlayManager() {
+ var t = this;
- function hsides(_element, includeMargins) {
- return (parseFloat(jQuery.curCSS(_element, 'paddingLeft', true)) || 0) +
- (parseFloat(jQuery.curCSS(_element, 'paddingRight', true)) || 0)
+
- (parseFloat(jQuery.curCSS(_element, 'borderLeftWidth', true)) ||
0) +
- (parseFloat(jQuery.curCSS(_element, 'borderRightWidth', true)) ||
0) +
- (includeMargins ? hmargins(_element) : 0);
- }
+ // exports
+ t.renderOverlay = renderOverlay;
+ t.clearOverlays = clearOverlays;
- function hmargins(_element) {
- return (parseFloat(jQuery.curCSS(_element, 'marginLeft', true)) || 0) +
- (parseFloat(jQuery.curCSS(_element, 'marginRight', true)) || 0);
- }
- function vsides(_element, includeMargins) {
- return (parseFloat(jQuery.curCSS(_element, 'paddingTop', true)) || 0) +
- (parseFloat(jQuery.curCSS(_element, 'paddingBottom', true)) || 0)
+
- (parseFloat(jQuery.curCSS(_element, 'borderTopWidth', true)) ||
0) +
- (parseFloat(jQuery.curCSS(_element, 'borderBottomWidth', true))
|| 0) +
- (includeMargins ? vmargins(_element) : 0);
- }
+ // locals
+ var usedOverlays = [];
+ var unusedOverlays = [];
- function vmargins(_element) {
- return (parseFloat(jQuery.curCSS(_element, 'marginTop', true)) || 0) +
- (parseFloat(jQuery.curCSS(_element, 'marginBottom', true)) ||
0);
- }
+ function renderOverlay(rect, parent) {
+ var e = unusedOverlays.shift();
+ if (!e) {
+ e = $("<div class='fc-cell-overlay'
style='position:absolute;z-index:3'/>");
+ }
+ if (e[0].parentNode != parent[0]) {
+ e.appendTo(parent);
+ }
+ usedOverlays.push(e.css(rect).show());
+ return e;
+ }
- function setMinHeight(element, h) {
- h = typeof h == 'number' ? h + 'px' : h;
- element[0].style.cssText += ';min-height:' + h + ';_height:' +
h;
- }
+ function clearOverlays() {
+ var e;
+ while (e = usedOverlays.shift()) {
+ unusedOverlays.push(e.hide().unbind());
+ }
+ }
- /* Position Calculation
- -----------------------------------------------------------------------------*/
- // nasty bugs in opera 9.25
- // position()'s top returning incorrectly with TR/TD or elements within TD
- var topBug;
-
- function topCorrect(tr) { // tr/th/td or anything else
- if (topBug !== false) {
- var cell;
- if (tr.is('th,td')) {
- tr = (cell = tr).parent();
- }
- if (topBug === undefined && tr.is('tr')) {
- topBug = tr.position().top != tr.children().position().top;
- }
- if (topBug) {
- return tr.parent().position().top + (cell ? tr.position().top -
cell.position().top : 0);
- }
- }
- return 0;
}
+ function CoordinateGrid(buildFunc) {
- /* Hover Matrix
- -----------------------------------------------------------------------------*/
+ var t = this;
+ var rows;
+ var cols;
- function HoverMatrix(rowElements, colElements, changeCallback) {
- var t = this,
- tops = [], lefts = [],
- origRow, origCol,
- currRow, currCol,
- e;
+ t.build = function() {
+ rows = [];
+ cols = [];
+ buildFunc(rows, cols);
+ };
- $.each(rowElements, function(i, _e) {
- e = $(_e);
- tops.push(e.offset().top + topCorrect(e));
- });
- tops.push(tops[tops.length - 1] + e.outerHeight());
- $.each(colElements, function(i, _e) {
- e = $(_e);
- lefts.push(e.offset().left);
- });
- lefts.push(lefts[lefts.length - 1] + e.outerWidth());
-
- t.mouse = function(ev) {
- var x = ev.pageX;
- var y = ev.pageY;
- var r, c;
- for (r = 0; r < tops.length && y >= tops[r]; r++) {
+ t.cell = function(x, y) {
+ var rowCnt = rows.length;
+ var colCnt = cols.length;
+ var i, r = -1, c = -1;
+ for (i = 0; i < rowCnt; i++) {
+ if (y >= rows[i][0] && y < rows[i][1]) {
+ r = i;
+ break;
+ }
}
- for (c = 0; c < lefts.length && x >= 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) {
- t.cell = null;
- } else {
- if (origRow === undefined) {
- origRow = r;
- origCol = c;
- }
- t.cell = {
- row: r,
- col: c,
- top: tops[r],
- left: lefts[c],
- width: lefts[c + 1] - lefts[c],
- height: tops[r + 1] - tops[r],
- origRow: origRow,
- origCol: origCol,
- isOrig: r == origRow && c == origCol,
- rowDelta: r - origRow,
- colDelta: c - origCol
- };
+ for (i = 0; i < colCnt; i++) {
+ if (x >= cols[i][0] && x < cols[i][1]) {
+ c = i;
+ break;
}
- changeCallback(t.cell);
}
+ return (r >= 0 && c >= 0) ? { row:r, col:c } : null;
};
- t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 are
exclusive
+
+ t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is
inclusive
var origin = originElement.offset();
return {
- top: tops[row0] - origin.top,
- left: lefts[col0] - origin.left,
- width: lefts[col1] - lefts[col0],
- height: tops[row1] - tops[row0]
+ top: rows[row0][0] - origin.top,
+ left: cols[col0][0] - origin.left,
+ width: cols[col1][1] - cols[col0][0],
+ height: rows[row1][1] - rows[row0][0]
};
};
}
+ function HoverListener(coordinateGrid) {
- /* Misc Utils
- -----------------------------------------------------------------------------*/
- var undefined,
- dayIDs = ['sun', 'mon', 'tue', 'wed',
'thu', 'fri', 'sat'];
+ var t = this;
+ var bindType;
+ var change;
+ var firstCell;
+ var cell;
- function zeroPad(n) {
- return (n < 10 ? '0' : '') + n;
- }
- function smartProperty(obj, name) { // get a camel-cased/namespaced property of an
object
- if (obj[name] !== undefined) {
- return obj[name];
- }
- var parts = name.split(/(?=[A-Z])/),
- i = parts.length - 1, res;
- for (; i >= 0; i--) {
- res = obj[parts[i].toLowerCase()];
- if (res !== undefined) {
- return res;
+ t.start = function(_change, ev, _bindType) {
+ change = _change;
+ firstCell = cell = null;
+ coordinateGrid.build();
+ mouse(ev);
+ bindType = _bindType || 'mousemove';
+ $(document).bind(bindType, mouse);
+ };
+
+
+ function mouse(ev) {
+ var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
+ if (!newCell != !cell || newCell && (newCell.row != cell.row ||
newCell.col != cell.col)) {
+ if (newCell) {
+ if (!firstCell) {
+ firstCell = newCell;
+ }
+ change(newCell, firstCell, newCell.row - firstCell.row, newCell.col -
firstCell.col);
+ } else {
+ change(newCell, firstCell);
+ }
+ cell = newCell;
}
}
- return obj[''];
- }
- function htmlEscape(s) {
- return s
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/'/g, ''')
- .replace(/"/g, '"');
+
+ t.stop = function() {
+ $(document).unbind(bindType, mouse);
+ return cell;
+ };
+
+
}
-
function HorizontalPositionCache(getElement) {
var t = this,
@@ -3908,48 +5219,4 @@
}
-
- function cssKey(_element) {
- return _element.id + '/' + _element.className + '/' +
_element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
- }
-
-
- function cmp(a, b) {
- return a - b;
- }
-
-
- function exclEndDay(event) {
- if (event.end) {
- return _exclEndDay(event.end, event.allDay);
- } else {
- return addDays(cloneDate(event.start), 1);
- }
- }
-
- function _exclEndDay(end, allDay) {
- end = cloneDate(end);
- return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : end;
- }
-
-
- function disableTextSelection(element) {
- element
- .attr('unselectable', 'on')
- .css('MozUserSelect', 'none')
- .bind('selectstart.ui', function() {
- return false;
- });
- }
-
- /*
- function enableTextSelection(element) {
- element
- .attr('unselectable', 'off')
- .css('MozUserSelect', '')
- .unbind('selectstart.ui');
- }
- */
-
-
})(jQuery);
\ No newline at end of file
Modified: sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/gcal.js
===================================================================
--- sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/gcal.js 2011-11-22
06:11:32 UTC (rev 22966)
+++ sandbox/trunk/ui/schedule/ui/src/main/resources/META-INF/resources/gcal.js 2011-11-22
06:22:20 UTC (rev 22967)
@@ -1,52 +1,82 @@
/*
- * FullCalendar v1.4.5 Google Calendar Extension
+ * FullCalendar v1.5.2 Google Calendar Plugin
*
- * 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
+ * Copyright (c) 2011 Adam Shaw
+ * Dual licensed under the MIT and GPL licenses, located in
+ * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
- * Date: Sun Feb 21 20:30:11 2010 -0800
+ * Date: Sun Aug 21 22:06:09 2011 -0700
*
*/
(function($) {
- $.fullCalendar.gcalFeed = function(feedUrl, options) {
- feedUrl = feedUrl.replace(/\/basic$/, '/full');
- options = options || {};
+ var fc = $.fullCalendar;
+ var formatDate = fc.formatDate;
+ var parseISO8601 = fc.parseISO8601;
+ var addDays = fc.addDays;
+ var applyAll = fc.applyAll;
- return function(start, end, callback) {
- var params = {
- 'start-min': $.fullCalendar.formatDate(start, 'u'),
- 'start-max': $.fullCalendar.formatDate(end, 'u'),
- 'singleevents': true,
- 'max-results': 9999
- };
- var ctz = options.currentTimezone;
- if (ctz) {
- params.ctz = ctz = ctz.replace(' ', '_');
+
+ fc.sourceNormalizers.push(function(sourceOptions) {
+ if (sourceOptions.dataType == 'gcal' || sourceOptions.dataType ===
undefined && (sourceOptions.url || '')
+ .match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
+ sourceOptions.dataType = 'gcal';
+ if (sourceOptions.editable === undefined) {
+ sourceOptions.editable = false;
}
- $.getJSON(feedUrl + "?alt=json-in-script&callback=?", params,
function(data) {
+ }
+ });
+
+
+ fc.sourceFetchers.push(function(sourceOptions, start, end) {
+ if (sourceOptions.dataType == 'gcal') {
+ return transformOptions(sourceOptions, start, end);
+ }
+ });
+
+
+ function transformOptions(sourceOptions, start, end) {
+
+ var success = sourceOptions.success;
+ var data = $.extend({}, sourceOptions.data || {}, {
+ 'start-min': formatDate(start, 'u'),
+ 'start-max': formatDate(end, 'u'),
+ 'singleevents': true,
+ 'max-results': 9999
+ });
+
+ var ctz = sourceOptions.currentTimezone;
+ if (ctz) {
+ data.ctz = ctz = ctz.replace(' ', '_');
+ }
+
+ return $.extend({}, sourceOptions, {
+ url: sourceOptions.url.replace(/\/basic$/, '/full') +
'?alt=json-in-script&callback=?',
+ dataType: 'jsonp',
+ data: data,
+ startParam: false,
+ endParam: false,
+ success: function(data) {
var events = [];
if (data.feed.entry) {
$.each(data.feed.entry, function(i, entry) {
- var startStr = entry['gd$when'][0]['startTime'],
- start = $.fullCalendar.parseISO8601(startStr, true),
- end =
$.fullCalendar.parseISO8601(entry['gd$when'][0]['endTime'], true),
- allDay = startStr.indexOf('T') == -1,
- url;
- $.each(entry.link, function() {
- if (this.type == 'text/html') {
- url = this.href;
+ var startStr = entry['gd$when'][0]['startTime'];
+ var start = parseISO8601(startStr, true);
+ var end =
parseISO8601(entry['gd$when'][0]['endTime'], true);
+ var allDay = startStr.indexOf('T') == -1;
+ var url;
+ $.each(entry.link, function(i, link) {
+ if (link.type == 'text/html') {
+ url = link.href;
if (ctz) {
url += (url.indexOf('?') == -1 ? '?'
: '&') + 'ctz=' + ctz;
}
}
});
if (allDay) {
- $.fullCalendar.addDays(end, -1); // make inclusive
+ addDays(end, -1); // make inclusive
}
events.push({
id: entry['gCal$uid']['value'],
@@ -56,16 +86,26 @@
end: end,
allDay: allDay,
location:
entry['gd$where'][0]['valueString'],
- description: entry['content']['$t'],
- className: options.className,
- editable: options.editable || false
+ description: entry['content']['$t']
});
});
}
- callback(events);
- });
- }
+ var args = [events].concat(Array.prototype.slice.call(arguments, 1));
+ var res = applyAll(success, this, args);
+ if ($.isArray(res)) {
+ return res;
+ }
+ return events;
+ }
+ });
}
+
+// legacy
+ fc.gcalFeed = function(url, sourceOptions) {
+ return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
+ };
+
+
})(jQuery);