Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117883749
fullcalendar.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
137 KB
Referenced Files
None
Subscribers
None
fullcalendar.js
View Options
/**
* @preserve
* FullCalendar v1.5.1
* http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
* For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
* Copyright (c) 2011 Adam Shaw
* Dual licensed under the MIT and GPL licenses, located in
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
* Date: Sat Apr 9 14:09:51 2011 -0700
*
*/
(
function
(
$
,
undefined
)
{
var
defaults
=
{
// display
defaultView
:
'month'
,
aspectRatio
:
1.35
,
header
:
{
left
:
'title'
,
center
:
''
,
right
:
'today prev,next'
},
weekends
:
true
,
// editing
//editable: false,
//disableDragging: false,
//disableResizing: false,
allDayDefault
:
true
,
ignoreTimezone
:
true
,
// event ajax
lazyFetching
:
true
,
startParam
:
'start'
,
endParam
:
'end'
,
// time formats
titleFormat
:
{
month
:
'MMMM yyyy'
,
week
:
"MMM d[ yyyy]{ '—'[ MMM] d yyyy}"
,
day
:
'dddd, MMM d, yyyy'
,
list
:
'MMM d, yyyy'
,
table
:
'MMM d, yyyy'
},
columnFormat
:
{
month
:
'ddd'
,
week
:
'ddd M/d'
,
day
:
'dddd M/d'
,
list
:
'dddd, MMM d, yyyy'
,
table
:
'dddd, MMM d, yyyy'
},
timeFormat
:
{
// for event elements
''
:
'h(:mm)t'
// default
},
// locale
isRTL
:
false
,
firstDay
:
0
,
monthNames
:
[
'January'
,
'February'
,
'March'
,
'April'
,
'May'
,
'June'
,
'July'
,
'August'
,
'September'
,
'October'
,
'November'
,
'December'
],
monthNamesShort
:
[
'Jan'
,
'Feb'
,
'Mar'
,
'Apr'
,
'May'
,
'Jun'
,
'Jul'
,
'Aug'
,
'Sep'
,
'Oct'
,
'Nov'
,
'Dec'
],
dayNames
:
[
'Sunday'
,
'Monday'
,
'Tuesday'
,
'Wednesday'
,
'Thursday'
,
'Friday'
,
'Saturday'
],
dayNamesShort
:
[
'Sun'
,
'Mon'
,
'Tue'
,
'Wed'
,
'Thu'
,
'Fri'
,
'Sat'
],
buttonText
:
{
prev
:
' ◄ '
,
next
:
' ► '
,
prevYear
:
' << '
,
nextYear
:
' >> '
,
today
:
'today'
,
month
:
'month'
,
week
:
'week'
,
day
:
'day'
,
list
:
'list'
,
table
:
'table'
},
listTexts
:
{
from
:
'from'
,
until
:
'until'
,
past
:
'Past events'
,
today
:
'Today'
,
tomorrow
:
'Tomorrow'
,
thisWeek
:
'This week'
,
nextWeek
:
'Next week'
,
thisMonth
:
'This month'
,
nextMonth
:
'Next month'
,
future
:
'Future events'
,
week
:
'W'
},
// list options
listSections
:
'month'
,
// false|'day'|'week'|'month'|'smart'
// jquery-ui theming
theme
:
false
,
buttonIcons
:
{
prev
:
'circle-triangle-w'
,
next
:
'circle-triangle-e'
},
//selectable: false,
unselectAuto
:
true
,
dropAccept
:
'*'
};
// right-to-left defaults
var
rtlDefaults
=
{
header
:
{
left
:
'next,prev today'
,
center
:
''
,
right
:
'title'
},
buttonText
:
{
prev
:
' ► '
,
next
:
' ◄ '
,
prevYear
:
' >> '
,
nextYear
:
' << '
},
buttonIcons
:
{
prev
:
'circle-triangle-e'
,
next
:
'circle-triangle-w'
}
};
var
fc
=
$
.
fullCalendar
=
{
version
:
"1.5.1"
};
var
fcViews
=
fc
.
views
=
{};
$
.
fn
.
fullCalendar
=
function
(
options
)
{
// method calling
if
(
typeof
options
==
'string'
)
{
var
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
);
var
res
;
this
.
each
(
function
()
{
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
)
{
return
res
;
}
return
this
;
}
// 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
)
{
eventSources
.
push
(
options
.
events
);
delete
options
.
events
;
}
options
=
$
.
extend
(
true
,
{},
defaults
,
(
options
.
isRTL
||
options
.
isRTL
===
undefined
&&
defaults
.
isRTL
)
?
rtlDefaults
:
{},
options
);
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
();
});
return
this
;
};
// 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'
);
}
content
=
$
(
"<div class='fc-content' style='position:relative'/>"
)
.
prependTo
(
element
);
header
=
new
Header
(
t
,
options
);
headerElement
=
header
.
render
();
if
(
headerElement
)
{
element
.
prepend
(
headerElement
);
}
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
();
}
}
// 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
destroy
()
{
$
(
window
).
unbind
(
'resize'
,
windowResize
);
header
.
destroy
();
content
.
remove
();
element
.
removeClass
(
'fc fc-rtl ui-widget'
);
}
function
elementVisible
()
{
return
_element
.
offsetWidth
!==
0
;
}
function
bodyVisible
()
{
return
$
(
'body'
)[
0
].
offsetWidth
!==
0
;
}
/* View Rendering
-----------------------------------------------------------------------------*/
// TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
function
changeView
(
newViewName
)
{
if
(
!
currentView
||
newViewName
!=
currentView
.
name
)
{
ignoreWindowResize
++
;
// because setMinHeight might change the height before render (and subsequently setSize) is reached
unselect
();
var
oldView
=
currentView
;
var
newViewElement
;
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'
);
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
);
}
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
content
.
css
(
'overflow'
,
''
);
if
(
oldView
)
{
setMinHeight
(
content
,
1
);
}
if
(
!
newViewElement
)
{
(
currentView
.
afterShow
||
noop
)();
// called after setting min-height/overflow, so in final scroll state (for Opera)
}
ignoreWindowResize
--
;
}
}
function
renderView
(
inc
)
{
if
(
elementVisible
())
{
ignoreWindowResize
++
;
// because renderEvents might temporarily change the height before setSize is reached
unselect
();
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
();
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
);
}
}
/* Resizing
-----------------------------------------------------------------------------*/
function
updateSize
()
{
markSizesDirty
();
if
(
elementVisible
())
{
calcSize
();
setSize
();
unselect
();
currentView
.
clearEvents
();
currentView
.
renderEvents
(
events
);
currentView
.
sizeDirty
=
false
;
}
}
function
markSizesDirty
()
{
$
.
each
(
viewInstances
,
function
(
i
,
inst
)
{
inst
.
sizeDirty
=
true
;
});
}
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
));
}
}
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
--
;
}
}
},
200
);
}
else
{
// calendar must have been initialized in a 0x0 iframe that has just been resized
lateRender
();
}
}
}
/* 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
();
}
}
function
refetchEvents
(
source
)
{
fetchEvents
(
currentView
.
visStart
,
currentView
.
visEnd
,
source
);
// will call reportEvents
}
// 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
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
();
}
}
/* Date
-----------------------------------------------------------------------------*/
function
prev
()
{
renderView
(
-
1
);
}
function
next
()
{
renderView
(
1
);
}
function
prevYear
()
{
addYears
(
date
,
-
1
);
renderView
();
}
function
nextYear
()
{
addYears
(
date
,
1
);
renderView
();
}
function
today
()
{
date
=
new
Date
();
renderView
();
}
function
gotoDate
(
year
,
month
,
dateOfMonth
)
{
if
(
year
instanceof
Date
)
{
date
=
cloneDate
(
year
);
// provided 1 argument, a Date
}
else
{
setYMD
(
date
,
year
,
month
,
dateOfMonth
);
}
renderView
();
}
function
incrementDate
(
years
,
months
,
days
)
{
if
(
years
!==
undefined
)
{
addYears
(
date
,
years
);
}
if
(
months
!==
undefined
)
{
addMonths
(
date
,
months
);
}
if
(
days
!==
undefined
)
{
addDays
(
date
,
days
);
}
renderView
();
}
function
getDate
()
{
return
cloneDate
(
date
);
}
/* Misc
-----------------------------------------------------------------------------*/
function
getView
()
{
return
currentView
;
}
function
option
(
name
,
value
)
{
if
(
value
===
undefined
)
{
return
options
[
name
];
}
options
[
name
]
=
value
;
if
(
name
==
'height'
||
name
==
'contentHeight'
||
name
==
'aspectRatio'
)
{
updateSize
();
}
}
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
;
}
});
}
}
function
Header
(
calendar
,
options
)
{
var
t
=
this
;
// exports
t
.
render
=
render
;
t
.
destroy
=
destroy
;
t
.
updateTitle
=
updateTitle
;
t
.
activateButton
=
activateButton
;
t
.
deactivateButton
=
deactivateButton
;
t
.
disableButton
=
disableButton
;
t
.
enableButton
=
enableButton
;
// locals
var
element
=
$
([]);
var
tm
;
function
render
()
{
tm
=
options
.
theme
?
'ui'
:
'fc'
;
var
sections
=
options
.
header
;
if
(
sections
)
{
element
=
$
(
"<table class='fc-header' style='width:100%'/>"
)
.
append
(
$
(
"<tr/>"
)
.
append
(
renderSection
(
'left'
))
.
append
(
renderSection
(
'center'
))
.
append
(
renderSection
(
'right'
))
);
return
element
;
}
}
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'
);
}
)
.
appendTo
(
e
);
if
(
!
prevButton
)
{
button
.
addClass
(
tm
+
'-corner-left'
);
}
prevButton
=
button
;
}
}
}
});
if
(
prevButton
)
{
prevButton
.
addClass
(
tm
+
'-corner-right'
);
}
});
}
return
e
;
}
function
updateTitle
(
html
)
{
element
.
find
(
'h2'
)
.
html
(
html
);
}
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
,
src
)
{
rangeStart
=
start
;
rangeEnd
=
end
;
cache
=
typeof
src
!=
'undefined'
?
$
.
grep
(
cache
,
function
(
e
)
{
return
!
isSourcesEqual
(
e
.
source
,
src
);
})
:
[];
var
fetchID
=
++
currentFetchID
;
var
len
=
sources
.
length
;
pendingSourceCnt
=
typeof
src
==
'undefined'
?
len
:
1
;
for
(
var
i
=
0
;
i
<
len
;
i
++
)
{
if
(
typeof
src
==
'undefined'
||
isSourcesEqual
(
sources
[
i
],
src
))
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
);
}
}
});
}
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
{
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
();
}
}
}
/* Sources
-----------------------------------------------------------------------------*/
function
addEventSource
(
source
)
{
source
=
_addEventSource
(
source
);
if
(
source
)
{
pendingSourceCnt
++
;
fetchEventSource
(
source
,
currentFetchID
);
// will eventually call reportEvents
}
}
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
{
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
);
}
function
renderEvent
(
event
,
stick
)
{
normalizeEvent
(
event
);
if
(
!
event
.
source
)
{
if
(
stick
)
{
stickySource
.
events
.
push
(
event
);
event
.
source
=
stickySource
;
}
cache
.
push
(
event
);
}
reportEvents
(
cache
);
}
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
);
}
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
}
/* Loading State
-----------------------------------------------------------------------------*/
function
pushLoading
()
{
if
(
!
loadingLevel
++
)
{
trigger
(
'loading'
,
null
,
true
);
}
}
function
popLoading
()
{
if
(
!--
loadingLevel
)
{
trigger
(
'loading'
,
null
,
false
);
}
}
/* Event Normalization
-----------------------------------------------------------------------------*/
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
}
/* 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
=
[];
}
var
normalizers
=
fc
.
sourceNormalizers
;
for
(
var
i
=
0
;
i
<
normalizers
.
length
;
i
++
)
{
normalizers
[
i
](
source
);
}
}
function
isSourcesEqual
(
source1
,
source2
)
{
return
source1
&&
source2
&&
getSourcePrimitive
(
source1
)
==
getSourcePrimitive
(
source2
);
}
function
getSourcePrimitive
(
source
)
{
return
((
typeof
source
==
'object'
)
?
(
source
.
events
||
source
.
url
)
:
''
)
||
source
;
}
}
fc
.
addDays
=
addDays
;
fc
.
cloneDate
=
cloneDate
;
fc
.
parseDate
=
parseDate
;
fc
.
parseISO8601
=
parseISO8601
;
fc
.
parseTime
=
parseTime
;
fc
.
formatDate
=
formatDate
;
fc
.
formatDates
=
formatDates
;
/* Date Math
-----------------------------------------------------------------------------*/
var
dayIDs
=
[
'sun'
,
'mon'
,
'tue'
,
'wed'
,
'thu'
,
'fri'
,
'sat'
],
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
);
}
while
(
d
.
getMonth
()
!=
check
.
getMonth
())
{
d
.
setDate
(
d
.
getDate
()
+
(
d
<
check
?
1
:
-
1
));
}
}
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
;
}
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
addMinutes
(
d
,
n
)
{
d
.
setMinutes
(
d
.
getMinutes
()
+
n
);
return
d
;
}
function
clearTime
(
d
)
{
d
.
setHours
(
0
);
d
.
setMinutes
(
0
);
d
.
setSeconds
(
0
);
d
.
setMilliseconds
(
0
);
return
d
;
}
function
cloneDate
(
d
,
dontKeepTime
)
{
if
(
dontKeepTime
)
{
return
clearTime
(
new
Date
(
+
d
));
}
return
new
Date
(
+
d
);
}
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
);
}
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
);
}
}
/* 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
;
}
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
[
14
])
{
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
);
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
;
}
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
;
}
}
return
h
*
60
+
(
m
[
2
]
?
parseInt
(
m
[
2
],
10
)
:
0
);
}
}
/* Date Formatting
-----------------------------------------------------------------------------*/
// TODO: use same function formatDate(date, [date2], format, [options])
function
formatDate
(
date
,
format
,
options
)
{
return
formatDates
(
date
,
null
,
format
,
options
);
}
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
;
}
break
;
}
}
}
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
;
}
}
}
else
if
(
c
==
'['
)
{
for
(
i2
=
i
+
1
;
i2
<
len
;
i2
++
)
{
if
(
format
.
charAt
(
i2
)
==
']'
)
{
var
subformat
=
format
.
substring
(
i
+
1
,
i2
);
var
subres
=
formatDate
(
otherDate
,
subformat
,
options
);
if
(
subres
!=
formatDate
(
date
,
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'
;
},
W
:
function
(
d
)
{
return
iso8601Week
(
d
);
}
};
// Determine the week of the year based on the ISO 8601 definition.
// copied from jquery UI Datepicker
var
iso8601Week
=
function
(
date
)
{
var
checkDate
=
cloneDate
(
date
);
// Find Thursday of this week starting on Monday
checkDate
.
setDate
(
checkDate
.
getDate
()
+
4
-
(
checkDate
.
getDay
()
||
7
));
var
time
=
checkDate
.
getTime
();
checkDate
.
setMonth
(
0
);
// Compare with Jan 1
checkDate
.
setDate
(
1
);
return
Math
.
floor
(
Math
.
round
((
time
-
checkDate
)
/
86400000
)
/
7
)
+
1
;
};
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
_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
});
}
}
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
(
levels
[
j
])
{
levels
[
j
].
push
(
seg
);
}
else
{
levels
[
j
]
=
[
seg
];
}
}
return
levels
;
}
/* 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
)));
}
}
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
)));
}
}
// 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
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
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
firstDefined
()
{
for
(
var
i
=
0
;
i
<
arguments
.
length
;
i
++
)
{
if
(
arguments
[
i
]
!==
undefined
)
{
return
arguments
[
i
];
}
}
}
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
);
}
}
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
);
}
}
fcViews
.
basicDay
=
BasicDayView
;
//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
);
}
}
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
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'
);
}
function
buildSkeleton
(
maxRowCnt
,
showNumbers
)
{
var
s
;
var
headerClass
=
tm
+
"-widget-header"
;
var
contentClass
=
tm
+
"-widget-content"
;
var
i
,
j
;
var
table
;
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
);
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
{
row
.
removeClass
(
'fc-last'
);
}
}
else
{
row
.
hide
();
}
});
}
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
);
}
bodyFirstCells
.
each
(
function
(
i
,
_cell
)
{
if
(
i
<
rowCnt
)
{
cell
=
$
(
_cell
);
setMinHeight
(
cell
.
find
(
'> div'
),
(
i
==
rowCnt
-
1
?
rowHeightLast
:
rowHeight
)
-
vsides
(
cell
)
);
}
});
}
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
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
));
var
stretchEnd
=
new
Date
(
Math
.
min
(
rowEnd
,
overlayEnd
));
if
(
stretchStart
<
stretchEnd
)
{
var
colStart
,
colEnd
;
if
(
rtl
)
{
colStart
=
dayDiff
(
stretchEnd
,
rowStart
)
*
dis
+
dit
+
1
;
colEnd
=
dayDiff
(
stretchStart
,
rowStart
)
*
dis
+
dit
+
1
;
}
else
{
colStart
=
dayDiff
(
stretchStart
,
rowStart
);
colEnd
=
dayDiff
(
stretchEnd
,
rowStart
);
}
dayBind
(
renderCellOverlay
(
i
,
colStart
,
i
,
colEnd
-
1
)
);
}
addDays
(
rowStart
,
7
);
addDays
(
rowEnd
,
7
);
}
}
function
renderCellOverlay
(
row0
,
col0
,
row1
,
col1
)
{
// row1,col1 is inclusive
var
rect
=
coordinateGrid
.
rect
(
row0
,
col0
,
row1
,
col1
,
element
);
return
renderOverlay
(
rect
,
element
);
}
/* Selection
-----------------------------------------------------------------------*/
function
defaultSelectionEnd
(
startDate
,
allDay
)
{
return
cloneDate
(
startDate
);
}
function
renderSelection
(
startDate
,
endDate
,
allDay
)
{
renderDayOverlay
(
startDate
,
addDays
(
cloneDate
(
endDate
),
1
),
true
);
// rebuild every time???
}
function
clearSelection
()
{
clearOverlays
();
}
function
reportDayClick
(
date
,
allDay
,
ev
)
{
var
cell
=
dateCell
(
date
);
var
_element
=
bodyCells
[
cell
.
row
*
colCnt
+
cell
.
col
];
trigger
(
'dayClick'
,
_element
,
date
,
allDay
,
ev
);
}
/* External Dragging
-----------------------------------------------------------------------*/
function
dragStart
(
_dragElement
,
ev
,
ui
)
{
hoverListener
.
start
(
function
(
cell
)
{
clearOverlays
();
if
(
cell
)
{
renderCellOverlay
(
cell
.
row
,
cell
.
col
,
cell
.
row
,
cell
.
col
);
}
},
ev
);
}
function
dragStop
(
_dragElement
,
ev
,
ui
)
{
var
cell
=
hoverListener
.
stop
();
clearOverlays
();
if
(
cell
)
{
var
d
=
cellDate
(
cell
);
trigger
(
'drop'
,
_dragElement
,
d
,
true
,
ev
,
ui
);
}
}
/* 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
;
}
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
;
}
});
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
);
}
function
colContentRight
(
col
)
{
return
colContentPositions
.
right
(
col
);
}
function
dateCell
(
date
)
{
return
{
row
:
Math
.
floor
(
dayDiff
(
date
,
t
.
visStart
)
/
7
),
col
:
dayOfWeekCol
(
date
.
getDay
())
};
}
function
cellDate
(
cell
)
{
return
_cellDate
(
cell
.
row
,
cell
.
col
);
}
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
);
}
}
addDays
(
d1
,
7
);
addDays
(
d2
,
7
);
}
return
segs
;
}
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
}
/* 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
;
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'
,
firstHour
:
6
,
slotMinutes
:
30
,
defaultEventMinutes
:
120
,
axisFormat
:
'h(:mm)tt'
,
timeFormat
:
{
agenda
:
'h:mm{ - h:mm}'
},
dragOpacity
:
{
agenda
:
.5
},
minTime
:
0
,
maxTime
:
24
});
// TODO: make it work in quirks mode (event corners, all-day height)
// TODO: test liquid width, especially in IE6
function
AgendaView
(
element
,
calendar
,
viewName
)
{
var
t
=
this
;
// 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
;
// 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
)
{
colCnt
=
c
;
updateOptions
();
if
(
!
dayTable
)
{
buildSkeleton
();
}
else
{
clearEvents
();
}
updateCells
();
}
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
(
opt
(
'minTime'
));
maxMinute
=
parseTime
(
opt
(
'maxTime'
));
colFormat
=
opt
(
'columnFormat'
);
}
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
;
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'
);
markFirstLast
(
dayHead
.
add
(
dayHead
.
find
(
'tr'
)));
markFirstLast
(
dayBody
.
add
(
dayBody
.
find
(
'tr'
)));
axisFirstCells
=
dayHead
.
find
(
'th:first'
);
gutterCells
=
dayTable
.
find
(
'.fc-agenda-gutter'
);
slotLayer
=
$
(
"<div style='position:absolute;z-index:2;left:0;width:100%'/>"
)
.
appendTo
(
element
);
if
(
opt
(
'allDaySlot'
))
{
daySegmentContainer
=
$
(
"<div style='position:absolute;z-index:8;top:0;left:0'/>"
)
.
appendTo
(
slotLayer
);
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'
);
dayBind
(
allDayRow
.
find
(
'td'
));
axisFirstCells
=
axisFirstCells
.
add
(
allDayTable
.
find
(
'th:first'
));
gutterCells
=
gutterCells
.
add
(
allDayTable
.
find
(
'th.fc-agenda-gutter'
));
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
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
=
{};
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
);
dayBodyFirstCellStretcher
.
height
(
bodyHeight
-
vsides
(
dayBodyFirstCell
));
slotLayer
.
css
(
'top'
,
headHeight
);
slotScroller
.
height
(
bodyHeight
-
allDayHeight
-
1
);
slotHeight
=
slotTableFirstInner
.
height
()
+
1
;
// +1 for border
if
(
dateChanged
)
{
resetScroll
();
}
}
function
setWidth
(
width
)
{
viewWidth
=
width
;
colContentPositions
.
clear
();
axisWidth
=
0
;
setOuterWidth
(
axisFirstCells
.
width
(
''
)
.
each
(
function
(
i
,
_cell
)
{
axisWidth
=
Math
.
max
(
axisWidth
,
$
(
_cell
).
outerWidth
());
}),
axisWidth
);
var
slotTableWidth
=
slotScroller
[
0
].
clientWidth
;
// needs to be done after axisWidth (for IE7)
//slotTable.width(slotTableWidth);
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
(
cells
)
{
cells
.
click
(
slotClick
)
.
mousedown
(
daySelectionMousedown
);
}
function
slotBind
(
cells
)
{
cells
.
click
(
slotClick
)
.
mousedown
(
slotSelectionMousedown
);
}
function
slotClick
(
ev
)
{
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
])
*
opt
(
'slotMinutes'
);
var
hours
=
Math
.
floor
(
mins
/
60
);
date
.
setHours
(
hours
);
date
.
setMinutes
(
mins
%
60
+
minMinute
);
trigger
(
'dayClick'
,
dayBodyCells
[
col
],
date
,
false
,
ev
);
}
else
{
trigger
(
'dayClick'
,
dayBodyCells
[
col
],
date
,
true
,
ev
);
}
}
}
/* 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
)
{
reportEvents
(
events
);
var
i
,
len
=
events
.
length
,
dayEvents
=
[],
slotEvents
=
[];
for
(
i
=
0
;
i
<
len
;
i
++
)
{
if
(
events
[
i
].
allDay
)
{
dayEvents
.
push
(
events
[
i
]);
}
else
{
slotEvents
.
push
(
events
[
i
]);
}
}
if
(
opt
(
'allDaySlot'
))
{
renderDaySegs
(
compileDaySegs
(
dayEvents
),
modifiedEventId
);
setHeight
();
// no params means set to viewHeight
}
renderSlotSegs
(
compileSlotSegs
(
slotEvents
),
modifiedEventId
);
}
function
clearEvents
()
{
reportEventClear
();
getDaySegmentContainer
().
empty
();
getSlotSegmentContainer
().
empty
();
}
function
compileDaySegs
(
events
)
{
var
levels
=
stackSegs
(
sliceSegs
(
events
,
$
.
map
(
events
,
exclEndDay
),
t
.
visStart
,
t
.
visEnd
)),
i
,
levelCnt
=
levels
.
length
,
level
,
j
,
seg
,
segs
=
[];
for
(
i
=
0
;
i
<
levelCnt
;
i
++
)
{
level
=
levels
[
i
];
for
(
j
=
0
;
j
<
level
.
length
;
j
++
)
{
seg
=
level
[
j
];
seg
.
row
=
0
;
seg
.
level
=
i
;
// not needed anymore
segs
.
push
(
seg
);
}
}
return
segs
;
}
function
compileSlotSegs
(
events
)
{
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
(
sliceSegs
(
events
,
visEventEnds
,
d
,
addMinutes
(
cloneDate
(
d
),
maxMinute
-
minMinute
)));
countForwardSegs
(
col
);
for
(
j
=
0
;
j
<
col
.
length
;
j
++
)
{
level
=
col
[
j
];
for
(
k
=
0
;
k
<
level
.
length
;
k
++
)
{
seg
=
level
[
k
];
seg
.
col
=
i
;
seg
.
level
=
j
;
segs
.
push
(
seg
);
}
}
addDays
(
d
,
1
,
true
);
}
return
segs
;
}
function
slotEventEnd
(
event
)
{
if
(
event
.
end
)
{
return
cloneDate
(
event
.
end
);
}
else
{
return
addMinutes
(
cloneDate
(
event
.
start
),
opt
(
'defaultEventMinutes'
));
}
}
// renders events in the 'time slots' at the bottom
function
renderSlotSegs
(
segs
,
modifiedEventId
)
{
var
i
,
segCnt
=
segs
.
length
,
seg
,
event
,
classes
,
top
,
bottom
,
colI
,
levelI
,
forward
,
leftmost
,
availWidth
,
outerWidth
,
left
,
html
=
''
,
eventElements
,
eventElement
,
triggerRes
,
vsideCache
=
{},
hsideCache
=
{},
key
,
val
,
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
;
top
=
timePosition
(
seg
.
start
,
seg
.
start
);
bottom
=
timePosition
(
seg
.
start
,
seg
.
end
);
colI
=
seg
.
col
;
levelI
=
seg
.
level
;
forward
=
seg
.
forward
||
0
;
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
outerWidth
=
availWidth
/
(
levelI
+
forward
+
1
);
}
else
{
if
(
forward
)
{
// moderately wide, aligned left still
outerWidth
=
((
availWidth
/
(
forward
+
1
))
-
(
12
/
2
))
*
2
;
// 12 is the predicted width of resizer =
}
else
{
// can be entire width, aligned left
outerWidth
=
availWidth
;
}
}
left
=
leftmost
+
// leftmost possible
(
availWidth
/
(
levelI
+
forward
+
1
)
*
levelI
)
// indentation
*
dis
+
(
rtl
?
availWidth
-
outerWidth
:
0
);
// rtl
seg
.
top
=
top
;
seg
.
left
=
left
;
seg
.
outerWidth
=
outerWidth
;
seg
.
outerHeight
=
bottom
-
top
;
html
+=
slotSegHtml
(
event
,
seg
);
}
slotSegmentContainer
[
0
].
innerHTML
=
html
;
// faster than html()
eventElements
=
slotSegmentContainer
.
children
();
// retrieve elements, run through eventRender callback, bind event handlers
for
(
i
=
0
;
i
<
segCnt
;
i
++
)
{
seg
=
segs
[
i
];
event
=
seg
.
event
;
eventElement
=
$
(
eventElements
[
i
]);
// faster than eq()
triggerRes
=
trigger
(
'eventRender'
,
event
,
event
,
eventElement
);
if
(
triggerRes
===
false
)
{
eventElement
.
remove
();
}
else
{
if
(
triggerRes
&&
triggerRes
!==
true
)
{
eventElement
.
remove
();
eventElement
=
$
(
triggerRes
)
.
css
({
position
:
'absolute'
,
top
:
seg
.
top
,
left
:
seg
.
left
})
.
appendTo
(
slotSegmentContainer
);
}
seg
.
element
=
eventElement
;
if
(
event
.
_id
===
modifiedEventId
)
{
bindSlotSeg
(
event
,
eventElement
,
seg
);
}
else
{
eventElement
[
0
].
_fci
=
i
;
// for lazySegBind
}
reportEventElement
(
event
,
eventElement
);
}
}
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
,
true
))
:
val
;
val
=
hsideCache
[
key
];
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
;
}
}
}
// set all positions/dimensions at once
for
(
i
=
0
;
i
<
segCnt
;
i
++
)
{
seg
=
segs
[
i
];
if
(
eventElement
=
seg
.
element
)
{
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
.
contentTop
!==
undefined
&&
height
-
seg
.
contentTop
<
10
)
{
// not enough room for title, put it in the time header
eventElement
.
find
(
'div.fc-event-time'
)
.
text
(
formatDate
(
event
.
start
,
opt
(
'timeFormat'
))
+
' - '
+
event
.
title
);
eventElement
.
find
(
'div.fc-event-title'
)
.
remove
();
}
trigger
(
'eventAfterRender'
,
event
,
event
,
eventElement
);
}
}
}
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
bindDaySeg
(
event
,
eventElement
,
seg
)
{
if
(
isEventDraggable
(
event
))
{
draggableDayEvent
(
event
,
eventElement
,
seg
.
isStart
);
}
if
(
seg
.
isEnd
&&
isEventResizable
(
event
))
{
resizableDayEvent
(
event
,
eventElement
,
seg
);
}
eventElementHandlers
(
event
,
eventElement
);
// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
}
function
bindSlotSeg
(
event
,
eventElement
,
seg
)
{
var
timeElement
=
eventElement
.
find
(
'div.fc-event-time'
);
if
(
isEventDraggable
(
event
))
{
draggableSlotEvent
(
event
,
eventElement
,
timeElement
);
}
if
(
seg
.
isEnd
&&
isEventResizable
(
event
))
{
resizableSlotEvent
(
event
,
eventElement
,
timeElement
);
}
eventElementHandlers
(
event
,
eventElement
);
}
/* Dragging
-----------------------------------------------------------------------------------*/
// when event starts out FULL-DAY
function
draggableDayEvent
(
event
,
eventElement
,
isStart
)
{
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
,
slotHeight
*
Math
.
round
(
(
event
.
end
?
((
event
.
end
-
event
.
start
)
/
MINUTE_MS
)
:
opt
(
'defaultEventMinutes'
))
/
opt
(
'slotMinutes'
)
)
);
eventElement
.
draggable
(
'option'
,
'grid'
,
[
colWidth
,
1
]);
allDay
=
false
;
}
}
else
{
revert
=
true
;
}
}
revert
=
revert
||
(
allDay
&&
!
dayDelta
);
}
else
{
resetElement
();
//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
;
}
}
}
// when event starts out IN TIMESLOTS
function
draggableSlotEvent
(
event
,
eventElement
,
timeElement
)
{
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
();
}
}
},
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
;
}
},
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
{
// 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
);
}
}
});
function
updateTimeText
(
minuteDelta
)
{
var
newStart
=
addMinutes
(
cloneDate
(
event
.
start
),
minuteDelta
);
var
newEnd
;
if
(
event
.
end
)
{
newEnd
=
addMinutes
(
cloneDate
(
event
.
end
),
minuteDelta
);
}
timeElement
.
text
(
formatDates
(
newStart
,
newEnd
,
opt
(
'timeFormat'
)));
}
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
;
}
}
}
/* Resizing
--------------------------------------------------------------------------------------*/
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'
)
)
);
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
}
}
});
}
}
function
countForwardSegs
(
levels
)
{
var
i
,
j
,
k
,
level
,
segForward
,
segBack
;
for
(
i
=
levels
.
length
-
1
;
i
>
0
;
i
--
)
{
level
=
levels
[
i
];
for
(
j
=
0
;
j
<
level
.
length
;
j
++
)
{
segForward
=
level
[
j
];
for
(
k
=
0
;
k
<
levels
[
i
-
1
].
length
;
k
++
)
{
segBack
=
levels
[
i
-
1
][
k
];
if
(
segsCollide
(
segForward
,
segBack
))
{
segBack
.
forward
=
Math
.
max
(
segBack
.
forward
||
0
,
(
segForward
.
forward
||
0
)
+
1
);
}
}
}
}
}
function
View
(
element
,
calendar
,
viewName
)
{
var
t
=
this
;
// 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
;
// locals
var
eventsByID
=
{};
var
eventElements
=
[];
var
eventElementsByID
=
{};
var
options
=
calendar
.
options
;
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
])
);
}
/*
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
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
])
{
eventsByID
[
event
.
_id
].
push
(
event
);
}
else
{
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
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
=
{};
}
// 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
}
function
showEvents
(
event
,
exceptElement
)
{
eachEventElement
(
event
,
exceptElement
,
'show'
);
}
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
(
!
exceptElement
||
elements
[
i
][
0
]
!=
exceptElement
[
0
])
{
elements
[
i
][
funcName
]();
}
}
}
/* Event Modification Reporting
---------------------------------------------------------------------------------*/
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
);
}
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 Math
---------------------------------------------------------------------------------*/
function
moveEvents
(
events
,
dayDelta
,
minuteDelta
,
allDay
)
{
minuteDelta
=
minuteDelta
||
0
;
for
(
var
e
,
len
=
events
.
length
,
i
=
0
;
i
<
len
;
i
++
)
{
e
=
events
[
i
];
if
(
allDay
!==
undefined
)
{
e
.
allDay
=
allDay
;
}
addMinutes
(
addDays
(
e
.
start
,
dayDelta
,
true
),
minuteDelta
);
if
(
e
.
end
)
{
e
.
end
=
addMinutes
(
addDays
(
e
.
end
,
dayDelta
,
true
),
minuteDelta
);
}
normalizeEvent
(
e
,
options
);
}
}
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
(
eventEnd
(
e
),
dayDelta
,
true
),
minuteDelta
);
normalizeEvent
(
e
,
options
);
}
}
}
function
DayEventRenderer
()
{
var
t
=
this
;
// 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
;
/* Rendering
-----------------------------------------------------------------------------*/
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
);
}
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'
);
}
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
;
}
}
}
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
);
}
}
}
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
{
element
[
0
].
_fci
=
i
;
// for lazySegBind
}
}
}
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
daySegSetWidths
(
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
)
{
element
[
0
].
style
.
width
=
Math
.
max
(
0
,
seg
.
outerWidth
-
seg
.
hsides
)
+
'px'
;
}
}
}
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
;
}
}
}
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
;
}
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
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
);
}
}
}
/* Resizing
-----------------------------------------------------------------------------------*/
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
;
// 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
}
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
setTimeout
(
function
()
{
// make this happen after the element's click event
isResizing
=
false
;
},
0
);
}
});
}
}
//BUG: unselect needs to be triggered when events are dragged+dropped
function
SelectionManager
()
{
var
t
=
this
;
// 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
;
// locals
var
selected
=
false
;
// unselectAuto
if
(
opt
(
'selectable'
)
&&
opt
(
'unselectAuto'
))
{
$
(
document
).
mousedown
(
function
(
ev
)
{
var
ignore
=
opt
(
'unselectCancel'
);
if
(
ignore
)
{
if
(
$
(
ev
.
target
).
parents
(
ignore
).
length
)
{
// could be optimized to stop after first match
return
;
}
}
unselect
(
ev
);
});
}
function
select
(
startDate
,
endDate
,
allDay
)
{
unselect
();
if
(
!
endDate
)
{
endDate
=
defaultSelectionEnd
(
startDate
,
allDay
);
}
renderSelection
(
startDate
,
endDate
,
allDay
);
reportSelection
(
startDate
,
endDate
,
allDay
);
}
function
unselect
(
ev
)
{
if
(
selected
)
{
selected
=
false
;
clearSelection
();
trigger
(
'unselect'
,
null
,
ev
);
}
}
function
reportSelection
(
startDate
,
endDate
,
allDay
,
ev
)
{
selected
=
true
;
trigger
(
'select'
,
null
,
startDate
,
endDate
,
allDay
,
ev
);
}
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
;
}
},
ev
);
$
(
document
).
one
(
'mouseup'
,
function
(
ev
)
{
hoverListener
.
stop
();
if
(
dates
)
{
if
(
+
dates
[
0
]
==
+
dates
[
1
])
{
reportDayClick
(
dates
[
0
],
true
,
ev
);
}
reportSelection
(
dates
[
0
],
dates
[
1
],
true
,
ev
);
}
});
}
}
}
function
OverlayManager
()
{
var
t
=
this
;
// exports
t
.
renderOverlay
=
renderOverlay
;
t
.
clearOverlays
=
clearOverlays
;
// locals
var
usedOverlays
=
[];
var
unusedOverlays
=
[];
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
clearOverlays
()
{
var
e
;
while
(
e
=
usedOverlays
.
shift
())
{
unusedOverlays
.
push
(
e
.
hide
().
unbind
());
}
}
}
function
CoordinateGrid
(
buildFunc
)
{
var
t
=
this
;
var
rows
;
var
cols
;
t
.
build
=
function
()
{
rows
=
[];
cols
=
[];
buildFunc
(
rows
,
cols
);
};
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
(
i
=
0
;
i
<
colCnt
;
i
++
)
{
if
(
x
>=
cols
[
i
][
0
]
&&
x
<
cols
[
i
][
1
])
{
c
=
i
;
break
;
}
}
return
(
r
>=
0
&&
c
>=
0
)
?
{
row
:
r
,
col
:
c
}
:
null
;
};
t
.
rect
=
function
(
row0
,
col0
,
row1
,
col1
,
originElement
)
{
// row1,col1 is inclusive
var
origin
=
originElement
.
offset
();
return
{
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
)
{
var
t
=
this
;
var
bindType
;
var
change
;
var
firstCell
;
var
cell
;
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
;
}
}
t
.
stop
=
function
()
{
$
(
document
).
unbind
(
bindType
,
mouse
);
return
cell
;
};
}
function
HorizontalPositionCache
(
getElement
)
{
var
t
=
this
,
elements
=
{},
lefts
=
{},
rights
=
{};
function
e
(
i
)
{
return
elements
[
i
]
=
elements
[
i
]
||
getElement
(
i
);
}
t
.
left
=
function
(
i
)
{
return
lefts
[
i
]
=
lefts
[
i
]
===
undefined
?
e
(
i
).
position
().
left
:
lefts
[
i
];
};
t
.
right
=
function
(
i
)
{
return
rights
[
i
]
=
rights
[
i
]
===
undefined
?
t
.
left
(
i
)
+
e
(
i
).
width
()
:
rights
[
i
];
};
t
.
clear
=
function
()
{
elements
=
{};
lefts
=
{};
rights
=
{};
};
}
/* Additional view: list (by bruederli@kolabsys.com)
---------------------------------------------------------------------------------*/
function
ListEventRenderer
()
{
var
t
=
this
;
// exports
t
.
renderEvents
=
renderEvents
;
t
.
renderEventTime
=
renderEventTime
;
t
.
compileDaySegs
=
compileSegs
;
// for DayEventRenderer
t
.
clearEvents
=
clearEvents
;
t
.
lazySegBind
=
lazySegBind
;
t
.
sortCmp
=
sortCmp
;
// imports
DayEventRenderer
.
call
(
t
);
var
opt
=
t
.
opt
;
var
trigger
=
t
.
trigger
;
var
reportEvents
=
t
.
reportEvents
;
var
reportEventClear
=
t
.
reportEventClear
;
var
reportEventElement
=
t
.
reportEventElement
;
var
eventElementHandlers
=
t
.
eventElementHandlers
;
var
showEvents
=
t
.
showEvents
;
var
hideEvents
=
t
.
hideEvents
;
var
getListContainer
=
t
.
getDaySegmentContainer
;
var
calendar
=
t
.
calendar
;
var
formatDate
=
calendar
.
formatDate
;
var
formatDates
=
calendar
.
formatDates
;
/* Rendering
--------------------------------------------------------------------*/
function
clearEvents
()
{
reportEventClear
();
getListContainer
().
empty
();
}
function
renderEvents
(
events
,
modifiedEventId
)
{
events
.
sort
(
sortCmp
);
reportEvents
(
events
);
renderSegs
(
compileSegs
(
events
),
modifiedEventId
);
}
function
compileSegs
(
events
)
{
var
segs
=
[];
var
colFormat
=
opt
(
'titleFormat'
,
'day'
);
var
firstDay
=
opt
(
'firstDay'
);
var
segmode
=
opt
(
'listSections'
);
var
event
,
i
,
dd
,
wd
,
md
,
seg
,
segHash
,
curSegHash
,
segDate
,
curSeg
=
-
1
;
var
today
=
clearTime
(
new
Date
());
var
weekstart
=
addDays
(
cloneDate
(
today
),
-
((
today
.
getDay
()
-
firstDay
+
7
)
%
7
));
for
(
i
=
0
;
i
<
events
.
length
;
i
++
)
{
event
=
events
[
i
];
// skip events < t.start
if
(
event
.
end
<
t
.
start
)
continue
;
// define sections of this event
// create smart sections such as today, tomorrow, this week, next week, next month, ect.
segDate
=
cloneDate
(
event
.
start
<
t
.
start
&&
event
.
end
>
t
.
start
?
t
.
start
:
event
.
start
,
true
);
dd
=
dayDiff
(
segDate
,
today
);
wd
=
Math
.
floor
(
dayDiff
(
segDate
,
weekstart
)
/
7
);
md
=
segDate
.
getMonth
()
+
((
segDate
.
getYear
()
-
today
.
getYear
())
*
12
)
-
today
.
getMonth
();
// build section title
if
(
segmode
==
'smart'
)
{
if
(
dd
<
0
)
{
segHash
=
opt
(
'listTexts'
,
'past'
);
}
else
if
(
dd
==
0
)
{
segHash
=
opt
(
'listTexts'
,
'today'
);
}
else
if
(
dd
==
1
)
{
segHash
=
opt
(
'listTexts'
,
'tomorrow'
);
}
else
if
(
wd
==
0
)
{
segHash
=
opt
(
'listTexts'
,
'thisWeek'
);
}
else
if
(
wd
==
1
)
{
segHash
=
opt
(
'listTexts'
,
'nextWeek'
);
}
else
if
(
md
==
0
)
{
segHash
=
opt
(
'listTexts'
,
'thisMonth'
);
}
else
if
(
md
==
1
)
{
segHash
=
opt
(
'listTexts'
,
'nextMonth'
);
}
}
else
if
(
segmode
==
'month'
)
{
segHash
=
formatDate
(
segDate
,
'MMMM yyyy'
);
}
else
if
(
segmode
==
'week'
)
{
segHash
=
opt
(
'listTexts'
,
'week'
)
+
formatDate
(
segDate
,
' W'
);
}
else
if
(
segmode
==
'day'
)
{
segHash
=
formatDate
(
segDate
,
colFormat
);
}
else
{
segHash
=
''
;
}
// start new segment
if
(
segHash
!=
curSegHash
)
{
segs
[
++
curSeg
]
=
{
events
:
[],
start
:
segDate
,
title
:
segHash
,
daydiff
:
dd
,
weekdiff
:
wd
,
monthdiff
:
md
};
curSegHash
=
segHash
;
}
segs
[
curSeg
].
events
.
push
(
event
);
}
return
segs
;
}
function
sortCmp
(
a
,
b
)
{
return
(
a
.
start
.
getTime
()
-
b
.
start
.
getTime
())
+
(
a
.
end
.
getTime
()
-
b
.
end
.
getTime
());
}
function
renderSegs
(
segs
,
modifiedEventId
)
{
var
tm
=
opt
(
'theme'
)
?
'ui'
:
'fc'
;
var
headerClass
=
tm
+
"-widget-header"
;
var
contentClass
=
tm
+
"-widget-content"
;
var
i
,
j
,
seg
,
event
,
times
,
s
,
skinCss
,
skinCssAttr
,
classes
,
segHeader
,
segContainer
,
eventElements
;
for
(
j
=
0
;
j
<
segs
.
length
;
j
++
)
{
seg
=
segs
[
j
];
if
(
seg
.
title
)
segHeader
=
$
(
'<div class="fc-list-header '
+
headerClass
+
'">'
+
htmlEscape
(
seg
.
title
)
+
'</div>'
).
appendTo
(
getListContainer
());
segContainer
=
$
(
'<div>'
).
addClass
(
'fc-list-section '
+
contentClass
).
appendTo
(
getListContainer
());
s
=
''
;
for
(
i
=
0
;
i
<
seg
.
events
.
length
;
i
++
)
{
event
=
seg
.
events
[
i
];
times
=
renderEventTime
(
event
,
seg
);
skinCss
=
getSkinCss
(
event
,
opt
);
skinCssAttr
=
(
skinCss
?
" style='"
+
skinCss
+
"'"
:
''
);
classes
=
[
'fc-event'
,
'fc-event-skin'
,
'fc-event-vert'
,
'fc-corner-top'
,
'fc-corner-bottom'
].
concat
(
event
.
className
);
if
(
event
.
source
&&
event
.
source
.
className
)
{
classes
=
classes
.
concat
(
event
.
source
.
className
);
}
s
+=
"<div class='"
+
classes
.
join
(
' '
)
+
"'"
+
skinCssAttr
+
">"
+
"<div class='fc-event-inner fc-event-skin'"
+
skinCssAttr
+
">"
+
"<div class='fc-event-head fc-event-skin'"
+
skinCssAttr
+
">"
+
"<div class='fc-event-time'>"
+
(
times
[
0
]
?
'<span class="fc-col-date">'
+
times
[
0
]
+
'</span> '
:
''
)
+
(
times
[
1
]
?
'<span class="fc-col-time">'
+
times
[
1
]
+
'</span>'
:
''
)
+
"</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
"</div>"
;
// close outer
}
segContainer
[
0
].
innerHTML
=
s
;
eventElements
=
segContainer
.
children
();
// retrieve elements, run through eventRender callback, bind event handlers
for
(
i
=
0
;
i
<
seg
.
events
.
length
;
i
++
)
{
event
=
seg
.
events
[
i
];
eventElement
=
$
(
eventElements
[
i
]);
// faster than eq()
triggerRes
=
trigger
(
'eventRender'
,
event
,
event
,
eventElement
);
if
(
triggerRes
===
false
)
{
eventElement
.
remove
();
}
else
{
if
(
triggerRes
&&
triggerRes
!==
true
)
{
eventElement
.
remove
();
eventElement
=
$
(
triggerRes
).
appendTo
(
segContainer
);
}
if
(
event
.
_id
===
modifiedEventId
)
{
eventElementHandlers
(
event
,
eventElement
,
seg
);
}
else
{
eventElement
[
0
].
_fci
=
i
;
// for lazySegBind
}
reportEventElement
(
event
,
eventElement
);
}
}
lazySegBind
(
segContainer
,
seg
,
eventElementHandlers
);
}
markFirstLast
(
getListContainer
());
}
// event time/date range to display
function
renderEventTime
(
event
,
seg
)
{
var
timeFormat
=
opt
(
'timeFormat'
);
var
dateFormat
=
opt
(
'columnFormat'
);
var
segmode
=
opt
(
'listSections'
);
var
duration
=
event
.
end
.
getTime
()
-
event
.
start
.
getTime
();
var
datestr
=
''
,
timestr
=
''
;
if
(
segmode
==
'smart'
)
{
if
(
event
.
start
<
seg
.
start
)
{
datestr
=
opt
(
'listTexts'
,
'until'
)
+
' '
+
formatDate
(
event
.
end
,
(
event
.
allDay
||
event
.
end
.
getDate
()
!=
seg
.
start
.
getDate
())
?
dateFormat
:
timeFormat
);
}
else
if
(
duration
>
DAY_MS
)
{
datestr
=
formatDates
(
event
.
start
,
event
.
end
,
dateFormat
+
'[ - '
+
dateFormat
+
']'
);
}
else
if
(
seg
.
daydiff
==
0
)
{
datestr
=
opt
(
'listTexts'
,
'today'
);
}
else
if
(
seg
.
daydiff
==
1
)
{
datestr
=
opt
(
'listTexts'
,
'tomorrow'
);
}
else
if
(
seg
.
weekdiff
==
0
||
seg
.
weekdiff
==
1
)
{
datestr
=
formatDate
(
event
.
start
,
'dddd'
);
}
else
if
(
seg
.
daydiff
>
1
||
seg
.
daydiff
<
0
)
{
datestr
=
formatDate
(
event
.
start
,
dateFormat
);
}
}
else
if
(
segmode
!=
'day'
)
{
datestr
=
formatDates
(
event
.
start
,
event
.
end
,
dateFormat
+
(
duration
>
DAY_MS
?
'[ - '
+
dateFormat
+
']'
:
''
));
}
if
(
!
datestr
&&
event
.
allDay
)
{
timestr
=
opt
(
'allDayText'
);
}
else
if
((
duration
<
DAY_MS
||
!
datestr
)
&&
!
event
.
allDay
)
{
timestr
=
formatDates
(
event
.
start
,
event
.
end
,
timeFormat
);
}
return
[
datestr
,
timestr
];
}
function
lazySegBind
(
container
,
seg
,
bindHandlers
)
{
container
.
unbind
(
'mouseover'
).
mouseover
(
function
(
ev
)
{
var
parent
=
ev
.
target
,
e
=
parent
,
i
,
event
;
while
(
parent
!=
this
)
{
e
=
parent
;
parent
=
parent
.
parentNode
;
}
if
((
i
=
e
.
_fci
)
!==
undefined
)
{
e
.
_fci
=
undefined
;
event
=
seg
.
events
[
i
];
bindHandlers
(
event
,
container
.
children
().
eq
(
i
),
seg
);
$
(
ev
.
target
).
trigger
(
ev
);
}
ev
.
stopPropagation
();
});
}
}
fcViews
.
list
=
ListView
;
function
ListView
(
element
,
calendar
)
{
var
t
=
this
;
// exports
t
.
render
=
render
;
t
.
select
=
dummy
;
t
.
unselect
=
dummy
;
t
.
getDaySegmentContainer
=
function
(){
return
body
;
};
// imports
View
.
call
(
t
,
element
,
calendar
,
'list'
);
ListEventRenderer
.
call
(
t
);
var
opt
=
t
.
opt
;
var
trigger
=
t
.
trigger
;
var
clearEvents
=
t
.
clearEvents
;
var
reportEventClear
=
t
.
reportEventClear
;
var
formatDates
=
calendar
.
formatDates
;
var
formatDate
=
calendar
.
formatDate
;
// overrides
t
.
setWidth
=
setWidth
;
t
.
setHeight
=
setHeight
;
// locals
var
body
;
var
firstDay
;
var
nwe
;
var
tm
;
var
colFormat
;
function
render
(
date
,
delta
)
{
if
(
delta
)
{
addDays
(
date
,
delta
);
if
(
!
opt
(
'weekends'
))
{
skipWeekend
(
date
,
delta
<
0
?
-
1
:
1
);
}
}
t
.
title
=
opt
(
'listTexts'
,
'from'
)
+
' '
+
formatDate
(
date
,
opt
(
'titleFormat'
));
t
.
start
=
t
.
visStart
=
cloneDate
(
date
,
true
);
t
.
end
=
addDays
(
cloneDate
(
t
.
start
),
1
);
t
.
visEnd
=
addMonths
(
cloneDate
(
t
.
start
),
1
);
// show events one month ahead. Enough?
updateOptions
();
if
(
!
body
)
{
buildSkeleton
();
}
else
{
clearEvents
();
}
}
function
updateOptions
()
{
firstDay
=
opt
(
'firstDay'
);
nwe
=
opt
(
'weekends'
)
?
0
:
1
;
tm
=
opt
(
'theme'
)
?
'ui'
:
'fc'
;
colFormat
=
opt
(
'columnFormat'
,
'day'
);
}
function
buildSkeleton
()
{
body
=
$
(
'<div>'
).
addClass
(
'fc-list-content'
).
appendTo
(
element
);
}
function
setHeight
(
height
,
dateChanged
)
{
body
.
css
(
'height'
,
(
height
-
1
)
+
'px'
).
css
(
'overflow'
,
'auto'
);
}
function
setWidth
(
width
)
{
// nothing to be done here
}
function
dummy
()
{
// Stub.
}
}
/* Additional view: table (by bruederli@kolabsys.com)
---------------------------------------------------------------------------------*/
function
TableEventRenderer
()
{
var
t
=
this
;
// imports
ListEventRenderer
.
call
(
t
);
var
opt
=
t
.
opt
;
var
sortCmp
=
t
.
sortCmp
;
var
trigger
=
t
.
trigger
;
var
compileSegs
=
t
.
compileDaySegs
;
var
reportEvents
=
t
.
reportEvents
;
var
reportEventClear
=
t
.
reportEventClear
;
var
reportEventElement
=
t
.
reportEventElement
;
var
eventElementHandlers
=
t
.
eventElementHandlers
;
var
renderEventTime
=
t
.
renderEventTime
;
var
showEvents
=
t
.
showEvents
;
var
hideEvents
=
t
.
hideEvents
;
var
getListContainer
=
t
.
getDaySegmentContainer
;
var
lazySegBind
=
t
.
lazySegBind
;
var
calendar
=
t
.
calendar
;
var
formatDate
=
calendar
.
formatDate
;
var
formatDates
=
calendar
.
formatDates
;
// exports
t
.
renderEvents
=
renderEvents
;
t
.
clearEvents
=
clearEvents
;
/* Rendering
--------------------------------------------------------------------*/
function
clearEvents
()
{
reportEventClear
();
getListContainer
().
children
(
'tbody'
).
remove
();
}
function
renderEvents
(
events
,
modifiedEventId
)
{
events
.
sort
(
sortCmp
);
reportEvents
(
events
);
renderSegs
(
compileSegs
(
events
),
modifiedEventId
);
getListContainer
().
removeClass
(
'fc-list-smart fc-list-day fc-list-month fc-list-week'
).
addClass
(
'fc-list-'
+
opt
(
'listSections'
));
}
function
renderSegs
(
segs
,
modifiedEventId
)
{
var
tm
=
opt
(
'theme'
)
?
'ui'
:
'fc'
;
var
table
=
getListContainer
();
var
headerClass
=
tm
+
"-widget-header"
;
var
contentClass
=
tm
+
"-widget-content"
;
var
i
,
j
,
seg
,
event
,
times
,
s
,
skinCss
,
skinCssAttr
,
skinClasses
,
rowClasses
,
segHeader
,
segContainer
,
eventElements
;
for
(
j
=
0
;
j
<
segs
.
length
;
j
++
)
{
seg
=
segs
[
j
];
if
(
seg
.
title
)
segHeader
=
$
(
'<tbody class="fc-list-header"><tr><td class="fc-list-header '
+
headerClass
+
'" colspan="5">'
+
htmlEscape
(
seg
.
title
)
+
'</td></tr></tbody>'
).
appendTo
(
table
);
segContainer
=
$
(
'<tbody>'
).
addClass
(
'fc-list-section '
+
contentClass
).
appendTo
(
table
);
s
=
''
;
for
(
i
=
0
;
i
<
seg
.
events
.
length
;
i
++
)
{
event
=
seg
.
events
[
i
];
times
=
renderEventTime
(
event
,
seg
);
skinCss
=
getSkinCss
(
event
,
opt
);
skinCssAttr
=
(
skinCss
?
" style='"
+
skinCss
+
"'"
:
''
);
skinClasses
=
[
'fc-event-skin'
,
'fc-corner-left'
,
'fc-corner-right'
,
'fc-corner-top'
,
'fc-corner-bottom'
].
concat
(
event
.
className
);
if
(
event
.
source
&&
event
.
source
.
className
)
{
skinClasses
=
skinClasses
.
concat
(
event
.
source
.
className
);
}
rowClasses
=
[
'fc-event'
,
'fc-event-row'
,
'fc-'
+
dayIDs
[
event
.
start
.
getDay
()]];
if
(
seg
.
daydiff
==
0
)
{
rowClasses
.
push
(
'fc-today'
);
}
s
+=
"<tr class='"
+
rowClasses
.
join
(
' '
)
+
"'>"
+
"<td class='fc-event-handle'>"
+
"<div class='"
+
skinClasses
.
join
(
' '
)
+
"'"
+
skinCssAttr
+
">"
+
"<span class='fc-event-inner'></span>"
+
"</div></td>"
+
"<td class='fc-event-date' colspan='"
+
(
times
[
1
]
?
1
:
2
)
+
"'>"
+
htmlEscape
(
times
[
0
])
+
"</td>"
+
(
times
[
1
]
?
"<td class='fc-event-time'>"
+
htmlEscape
(
times
[
1
])
+
"</td>"
:
""
)
+
"<td class='fc-event-title'>"
+
htmlEscape
(
event
.
title
)
+
"</td>"
+
"<td class='fc-event-location'>"
+
htmlEscape
(
event
.
location
)
+
"</td>"
+
"</tr>"
;
}
segContainer
[
0
].
innerHTML
=
s
;
eventElements
=
segContainer
.
children
();
// retrieve elements, run through eventRender callback, bind event handlers
for
(
i
=
0
;
i
<
seg
.
events
.
length
;
i
++
)
{
event
=
seg
.
events
[
i
];
eventElement
=
$
(
eventElements
[
i
]);
// faster than eq()
triggerRes
=
trigger
(
'eventRender'
,
event
,
event
,
eventElement
);
if
(
triggerRes
===
false
)
{
eventElement
.
remove
();
}
else
{
if
(
triggerRes
&&
triggerRes
!==
true
)
{
eventElement
.
remove
();
eventElement
=
$
(
triggerRes
).
appendTo
(
segContainer
);
}
if
(
event
.
_id
===
modifiedEventId
)
{
eventElementHandlers
(
event
,
eventElement
,
seg
);
}
else
{
eventElement
[
0
].
_fci
=
i
;
// for lazySegBind
}
reportEventElement
(
event
,
eventElement
);
}
}
lazySegBind
(
segContainer
,
seg
,
eventElementHandlers
);
markFirstLast
(
segContainer
);
}
//markFirstLast(table);
}
}
fcViews
.
table
=
TableView
;
function
TableView
(
element
,
calendar
)
{
var
t
=
this
;
// exports
t
.
render
=
render
;
t
.
select
=
dummy
;
t
.
unselect
=
dummy
;
t
.
getDaySegmentContainer
=
function
(){
return
table
;
};
// imports
View
.
call
(
t
,
element
,
calendar
,
'table'
);
TableEventRenderer
.
call
(
t
);
var
opt
=
t
.
opt
;
var
trigger
=
t
.
trigger
;
var
clearEvents
=
t
.
clearEvents
;
var
reportEventClear
=
t
.
reportEventClear
;
var
formatDates
=
calendar
.
formatDates
;
var
formatDate
=
calendar
.
formatDate
;
// overrides
t
.
setWidth
=
setWidth
;
t
.
setHeight
=
setHeight
;
// locals
var
div
;
var
table
;
var
firstDay
;
var
nwe
;
var
tm
;
var
colFormat
;
function
render
(
date
,
delta
)
{
if
(
delta
)
{
addDays
(
date
,
delta
);
if
(
!
opt
(
'weekends'
))
{
skipWeekend
(
date
,
delta
<
0
?
-
1
:
1
);
}
}
t
.
title
=
opt
(
'listTexts'
,
'from'
)
+
' '
+
formatDate
(
date
,
opt
(
'titleFormat'
));
t
.
start
=
t
.
visStart
=
cloneDate
(
date
,
true
);
t
.
end
=
addDays
(
cloneDate
(
t
.
start
),
1
);
t
.
visEnd
=
addMonths
(
cloneDate
(
t
.
start
),
1
);
// show events one month ahead. Enough?
updateOptions
();
if
(
!
table
)
{
buildSkeleton
();
}
else
{
clearEvents
();
}
}
function
updateOptions
()
{
firstDay
=
opt
(
'firstDay'
);
nwe
=
opt
(
'weekends'
)
?
0
:
1
;
tm
=
opt
(
'theme'
)
?
'ui'
:
'fc'
;
colFormat
=
opt
(
'columnFormat'
);
}
function
buildSkeleton
()
{
var
s
=
"<table class='fc-border-separate' style='width:100%' cellspacing='0'>"
+
"<colgroup>"
+
"<col class='fc-event-handle' />"
+
"<col class='fc-event-date' />"
+
"<col class='fc-event-time' />"
+
"<col class='fc-event-title' />"
+
"<col class='fc-event-location' />"
+
"</colgroup>"
+
"</table>"
;
div
=
$
(
'<div>'
).
addClass
(
'fc-list-content'
).
appendTo
(
element
);
table
=
$
(
s
).
appendTo
(
div
);
}
function
setHeight
(
height
,
dateChanged
)
{
div
.
css
(
'height'
,
(
height
-
1
)
+
'px'
).
css
(
'overflow'
,
'auto'
);
}
function
setWidth
(
width
)
{
// nothing to be done here
}
function
dummy
()
{
// Stub.
}
}
})(
jQuery
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Apr 6, 1:09 AM (2 d, 19 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
2c/de/045db21a7477d4c9d378521096f8
Default Alt Text
fullcalendar.js (137 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline