This isn't going to be a real deep post - more of a "Cool, take a look" type thing. A while back I found the FullCalendar jQuery plugin. As you can probably guess, it's a plugin that provides a full calendar view for dates. I finally made myself take some time to play with it and I'm pretty darn impressed. Some of the features include:
- Ability to edit events (move them around to reschedule)
- Obviously the ability to load events via JSON, but even cooler, the ability to integrate with Google Calendar
- Multiple views (from calendar to week to day)
- Lots of customization
I ran into a few issues while working with the code, but nothing too terrible. Here's a simple example with a few customizations:
<html>
<head>
<link rel="stylesheet" text="text/css" href="fullcalendar-1.5.1/fullcalendar/fullcalendar.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="fullcalendar-1.5.1/fullcalendar/fullcalendar.js"></script>
<script>
$(document).ready(function() {
$("#cal").fullCalendar({
aspectRatio: 2,
buttonText: {
today:"Go to Today"
},
events:'events.cfc?method=getevents'
});
});
</script>
</head>
<body>
<div id="cal"></div>
</body>
</html>
As you can see, there isn't much going on here. I've got a div that will store my calendar and then I simply point to it with the plugin. All of the options I have there (and I'll explain them in a second) are optional.
So what did I customize and why? Oddly the height of he calendar, by default, was always a bit too tall for the screen in Chrome. Setting an aspectRatio of 2 seemed to work well to keep the calendar on screen. I don't think I full understand yet how best to use it, but in playing around it worked ok.
Another minor customization was to the today button. By default the calendar will include a button to return to you the current month and current day. That button's default label was "today". The lowercase bugged me. Luckily you can see how easy it was to customize. (I could easily remove it as well.)
Finally it would be nice to include some events, right? I pointed to a CFC called events. The plugin will pass a start/end range to your back end service and then you simply return the data. It requires an array of events and their docs clarify what each event object should contain. Here's the CFC I used:
component {
url.returnformat="json";
remote function getEvents(any start, any end) {
var realStart = epochTimeToDate(start);
var realEnd = epochTimeToDate(end);
writelog(file="application", text="Asked for #realstart# to #realend#, orig start=#start#");
var q = new com.adobe.coldfusion.query();
q.setDatasource("blogdev");
q.setSQL("select id, title, posted from tblblogentries where posted >= :start and posted <= :end");
q.addParam(name="start",value=realstart,cfsqltype="cf_sql_date");
q.addParam(name="end",value=realend,cfsqltype="cf_sql_date");
var dbresults = q.execute().getResult();
writelog(file="application", text="Result: #dbresults.recordCount#");
var results = [];
for(var i=1; i<= dbresults.recordCount; i++) {
arrayAppend(results, {
"id"=dbresults.id[i],
"title"=dbresults.title[i],
"start"=getEpochTime(dbresults.posted[i]),
"url"="http://www.raymondcamden.com/index.cfm?mode=entry&entry="&dbresults.id[i]
});
}
return results;
}
//Credit Chris Mellon - http://www.cflib.org/udf/EpochTimeToDate
private function epochTimeToDate(epoch) {
return DateAdd("s", epoch, "January 1 1970 00:00:00");
}
//Credit Chris Mellon - http://www.cflib.org/udf/GetEpochTime
private function getEpochTime() {
var datetime = 0;
if (ArrayLen(Arguments) is 0) {
datetime = Now();
}
else {
if (IsDate(Arguments[1])) {
datetime = Arguments[1];
} else {
return NULL;
}
}
return DateDiff("s", "January 1 1970 00:00", datetime);
}
}
The plugin sends a date range to you in UNIX time stamps - which are the number of seconds since epoch. Luckily there's a nice UDF at CFLib to make that easy to work with. I noticed that the ranges included a start time of 6AM. When I did my query I wanted to drop that so I used a cf_sql_date instead of timestamp queryparam. I'll be coming back to this.
Once I've got my data, it was a simple matter to convert it to an array of structs. And that's it. Check out the demo:
The old demo has been removed. You can download it here: https://static.raymondcamden.com/enclosures/july162011B.zip
So - the one thing I'm kinda stuck on is the whole time issue. Remember above where I said the plugin would send a time of 6AM along with it's date range? I can't get rid of that. So if I have something at 5AM on the first day of the range it won't show up. Normally the first day is the previous month. On the demo now that is May 29th. But in January, 2012, the first day is the real first day of the month. Even though I return an event for that (in my local testing environment), the plugin wanted to render it for the previous day. It's got to be some timezone issue I don't fully understand.
That's it for now. Overall - it seems like a darn cool little plugin. Fellow ColdFusion-er Jim Leether is also using it. You can see his example here. Anyone else?
Edit on February 13, 2013: James Moberg noted below that my demo stopped working with the latest jQuery due to curCSS being removed from jQuery. Most likely the latest edition of this jQuery plugin fixes that. In order to make my demo work, I went from using the latest 1.* branch of jQuery to 1.7.1 specifically. I'd urge readers to see if the latest build of the plugin is corrected, and if not, to file a bug with the owner.
Archived Comments
+1 for FullCalendar! We used it on the Middlesex Hospital website <a href="http://middlesexhospital.or...">http://middlesexhospital.or... it is a fantastic jQuery plugin. The best calendar plugin I have seen.
I guess I didn't need the anchor tag. Link again: http://middlesexhospital.or...
@Sean, sweet job on the calender w/filtering!
Very nice Ray!
I wonder if this can be integrated with ben nadels calender? have you seen it?
@Steve Thanks. We also used it for an administration area. With FullCalendar's drag and drop support, we make it really easy for users to copy previously used calendar events. They search for an event, then drag and drop it onto a date in the calendar. This fires off an ajax request which copies the event, changes some dates and such and saves the new record. FullCalendar has some great event hooks that you can use to create some really cool interfaces.
@Sean, what a perfect use case for the drag+drop. not to mention the use of jQuery UI for even details, etc. well done.
Jody - my understanding is that _any_ database could be used. All you need to do is write a wrapper. I mean look at my demo - it wasn't even "events" but blog entries. I just had to spit the data out in the right way.
Ditto Steve - Sean that's a nice calendar!
not a JS expert but isn't it's epoch offset the same as java, in ms since 1-1-1970? if that's the case you can use dateTime.getTime() to get the epoch offset in cf & dateTime.setTime(epochOffset) to change back to a cf datetime.
flash player's behavior in changing incoming datetimes to client tz used to rub me the wrong way but i eventually went w/the flow & passed epoch offsets back & forth. never fallen into tz hell for a flex app since ;-)
Ray,
I haven't done any testing (I should but am busy today), but your timezone issue might be related to one of these lines in your cfc:
return DateAdd("s", epoch, "January 1 1970 00:00:00");
return DateDiff("s", "January 1 1970 00:00", datetime);
You aren't putting a timezone offset in either call. So its defaulting to GMT time (which is 5 hours off CST right now). That could be the issue. I recently ran into something similar in my code, so it caught my eye.
Sorry I couldn't test, but see if adding (-05) like this helps:
return DateAdd("s", epoch, "January 1 1970 00:00:00-05");
How well does their solution work with multi-day events? I always seem to have an issue with cross-month, multiday events in these calendar systems. It definitely looks good though!
@CJ: Check the demo - they examples of multiday events.
@Mark: Hmmm. To be clear - when my db script ran for Jan 2012, it did get one result. Because I ignored times, it found my test. But when I returned it - it showed up in Dec 2011. So your saying my 'to epoch' code needs to be omdded.
Thanks Ray. Great post.
Couldn't get the calendar to display the events for the life of me, until I realized debugging was on!
Heh, that's burned more than one person. Really hoping Adobe adds cfsetting to script-based CFCs in the next update.
We are using this same calendar and JQuery UI for drag and drop to add new events (emails, tweets, fb posts). When a new "event" is dropped onto the calendar, we open a modal w/ a form for that particular event type. We're also showing tooltips for each event since real estate on each date is limited (app requires a login, but here's a screenshot: http://emailer.emfluence.co....
One thing I'd like to see added is the ability to easily color-code the calendar days based on date (e.g. highlight this week or gray out past dates). Anyone had any success with that?
Nice script for calender.
Wow . . . nice calendar! Bookmarking this entry for future reference . . .
Yes, I am using FullCalendar in conjunction with CFEXCHANGE.
Basically a read only view of an Exchange calendar for users to see when events are scheduled ... am yet to extend this so users can download the iCal for an event.
Would be nice to integrate it as a view on blog posts - i.e. a blog post would equate to an 'event'. Maybe better used on a blog aggregator?
Glen - did you try my demo? It's exactly that.
Doh! Maybe if I read the whole post I would see that you are using blog posts.
I'll get my coat.
Trust - I've done it plenty of times. Don't feel bad.
This is fantastic! I would like to have a query on the page and display static events with this. Any idea of how I would create an array of events on the same page without passing this back in a JSON format?
The docs talk about this. Just check their examples of static data. Let me know if it doesn't make sense. I assume your data is still dynamic - you just don't want to load it via JSON?
Thanks Ray, I'll have a look at the docs. And yes, I am trying to load dynamic data without the JSON call.
It's definitely possible then. If you have any issues getting CF to output it right, let me know.
That is one sweet momma calendar hehe. Later this year I have to rework over a 10yr old calendar/events app that I built originally in 2000 and this may be the solution. As soon as I figure out how to get the drag and drop to work with our strict business rules, this will be a absolutely winner!!
I'm going to be spending my evening looking into this plugin instead of farming :)
Thanks again!
Great post! I changed the code to use
http://www.cflib.org/udf/Ep...
and
http://www.cflib.org/udf/Ge...
Ray, This post couldn't have come at a better time: I have used your code for a new project that needed a free-flowing, blog type calendar and one in regular calendar format. It is currently at its development site here: http://yourtowntv.rhapsodys...
Glad to help. FYI,your link leads to an error.
Thanks for the heads up -- it has been fixed now. The new link is: http://www.yourtowntv.com/c...
Did anyone have problems with the event displaying in the wrong month? I got the calendar to take my dates, and they look right in the source code, but the calendar puts them exactly one month ahead of the actual date. to the day.
Can you share the code? A link is better. If you can't, use Pastebin and share that link.
hey ray, gr8 work can u just try the same with user friendly cfcomponent tag,
that will be great if u do
thanks
Sorry - what? You mean a tag based component?
yes, tad Based Component
Well, it is just a query and a loop. I know the script syntax is a bit new for folks, but if you take it line by line, it should be easy to rewrite into tags.
I guess this is CF9 code cos I'm getting errors in CF8 with that CFScript arrayappend block. Tried initially to rewrite in tag code:
<cfset arrayAppend(results,{"id"=id,"title"=title,"start"=start,"url"=url})>
But, its dishing up an error: Missing argument name.When using named parameters to a function, every parameter must have a name....
Change it to:
<cfset s = structNew()>
<cfset s.id = id>
and so on
<cfset arrayAPpend(results, s)>
Thanks Ray,
Got the query returning data, apparently correctly according to Firebug. But, the event isn't displaying in the calendar. http://scrapbookcentral.01d.... Any idea why that might be?
Further to above, I think the issue is with my JSON keys returning in uppercase. Running your demo, I see that yours are lowercase.
Sorted with the following:
<cfset s = structnew()>
<cfset s["id"] = id>
<cfset s["url"] = url>
<cfset s["title"] = title>
<cfset s["start"] = start>
<cfset arrayappend(results, s)
Glad you got it.
It was thanks to this post http://www.coldfusionjedi.c... :-)
I just wanted to say thanks. I used this code and tied it with my current DB that has events with a start and an end date already entered which I just adjusted the query to accept it.
I have been beating my head on how to exactly set the colors for each even other than allowing someone when setting the events to specify the color. I was thinking possibly checking the title and if it contained a specific word to then specify the color with an if then. Such as: if title.indexOf("something") then color="red" I am sure its rather easy but I seem to be rather stuck.
Did you try the support options over at the plugin?
http://arshaw.com/fullcalen...
Not saying to go away (grin), but it sounds like something you may get better luck with there.
I will try there..thanks Ray...I didn't see the stackoverflow link until just now. Plus I'm lazy....
We all are. ;)
Just to help out any future users looking for this answer.
The FullCalendar Eventsources allows you to specify different sources for events...so in essence you can call a function that pulls event group 1 assign it a color and then specify another source and call the function that pulls in group 2 and assign it a color.
$('#calendar').fullCalendar({
eventSources: [
// your event source
{
url: '/myfeed.php',
type: 'POST',
data: {
custom_param1: 'something',
custom_param2: 'somethingelse'
}
error: function() {
alert('there was an error while fetching events!');
},
color: 'yellow', // a non-ajax option
textColor: 'black' // a non-ajax option
}
// any other sources...
]
});
Interesting - I would have assumed it could have allowed you to specify a key in your event data instead.
Silly copy and paste....it has PHP ...anyways that's the general idea.
Well my original thought was to place an if then looking for a specific word in the title of the event and changing the color based on that, but with the EventSources it may give me a bit more flexibility.
I also considered allowing admins who add events into the database to assign specific colors to individual items, but I will address it with the users and see what they prefer.
I prefer hard-coding the colors because some people just don't have a keen eye for color :)
Upon clicking on each event how can I launch the dialog ui popup and load an external there.
Also the events are different type. That said based on the type the loaded page will be different
Erik, I'd recommend reading the docs on the site. There should be a way to handle that event.
Hi Ray.
The examples that are given on the site only cover non conditional setup.
And do you know any sites that are using sophisticated fullcalendar plugin.
Not sure what you mean by non-conditional setup. The docs (I just checked) cover event clicks. Therefore you can handle a click on an event. If your event contains a type value, then you would be able to get that in the event handler. Not trying to be unhelpful here, but I do think the event click documentation is where you should check.
I just looked - and in the Event Object doc, they state you can include non-standard fields. So simply use that to include your 'type' attribute.
As for real examples, Im not seeing that on the site, but you could possibly reach out to the author of the plugin.
Your fullCalendar demo doesn't work. The latest jQuery library you are using is not compatible with the old fullCalendar scripts since fullCalendar used a now-deprecated curCSS() function.
Thanks James. So... I'd either ask folks to use an older version of jQuery or ping the author of fullCalenadar for an update.
No problem. I wanted to make sure that you were aware. I had recently upgraded the fullCalendar library and found your article while searching for some information regarding adding a US Holiday Google Calendar. I wanted to quickly view your demo to see what you had done, but couldn't because it was broken due to the updated core library. jQuery & fullCalendar are current/relevant technologies, your demo ranked high in the search engine and I didn't know if you were updating sample code when your site core libraries were updated.
Tomorrow I'll see about updating this to use a specific jQuery version. Both in the demo and in the blog post.
Demo fixed and note added to end of entry. Thanks James!
Demo is broken again.
Weird, I fixed it in test2.html. I must have forgotten to change the link. Will do so now. In the meantime, just change test.html to test2.html.
another useful post Ray...
I've converted the script to tags, but even though the results are identical, the client is not recognizing it. Anything obvious I missed?
<cffunction name="getEvents" access="remote">
<cfargument name="site_id" type="any" required="false" />
<cfquery name="getallevents" >
Select event_id, event_name, event_start_datetime
from event
where site_id = <cfqueryparam value="#arguments.site_id#">
order by event_start_datetime
</cfquery>
<cfset url.returnformat="json">
<cfset results = []>
<cfloop query = "getallevents">
<cfset s = structnew()>
<cfset s["id"] = event_id>
<cfset s["title"] = event_name>
<cfset s["start"] = getEpochTime(event_start_datetime)>
<cfset s["url"] = event_id>
<cfset arrayappend(results, s)>
</cfloop>
<cfreturn results />
</cffunction>
nevermind, I found the error on the client side. The tag based function posted above works well.
I've added an array of colors and fixed the data conversion. This is all in a cfc and working great.
<cfcomponent>
<cffunction name="getEvents" access="remote">
<cfargument name="site_id" type="any" required="false" />
<cfquery name="getallevents" >
Select event_id, event_name, event_start_datetime, event_end_datetime, category_id
from event
where site_id = <cfqueryparam value="#arguments.site_id#">
order by event_start_datetime
</cfquery>
<cfquery name="getallcolors" dbtype="query">
Select distinct category_id
from getallevents
</cfquery>
<cfset colorlist="red,green,blue,yellow,black,brown,orange,grey,aqua,darkred,darkgreen,darkblue,darkgrey,purple">
<cfset colorpos=1>
<cfset colors = []>
<cfloop query = "getallcolors">
<cfset thecolor='#listgetat(colorlist,colorpos)#'>
<cfif colorpos eq listlen(colorlist)>
<cfset colorpos=0>
</cfif>
<cfset colorpos=colorpos+1>
<cfset colors[#category_id#] = thecolor>
</cfloop>
<cfset url.returnformat="json">
<cfset results = []>
<cfloop query = "getallevents">
<cfset eventurl ="eventdetails.cfm?id=" & "#event_id#">
<cfset eventcolor ="#colors[category_id]#">
<cfset s = structnew()>
<cfset s["id"] = event_id>
<cfset s["title"] = event_name>
<cfset s["start"] = getEpochTime(event_start_datetime)>
<cfset s["end"] = getEpochTime(event_end_datetime)>
<cfset s["url"] = eventurl>
<cfset s["color"] = eventcolor>
<cfset s["allDay"] = false>
<cfset arrayappend(results, s)>
</cfloop>
<cfreturn results>
</cffunction>
<cffunction access="private" name="getEpochTime" returntype="date">
<cfargument name="thedatetime" type="date"/>
<cfif (ArrayLen(Arguments) is 0)>
<cfset thedatetime = Now() />
<cfelseif IsDate(Arguments[1])>
<cfset thedatetime=Arguments[1] />
<cfelse>
return NULL;
</cfif>
<cfreturn DateDiff("s", DateConvert("utc2Local", "January 1 1970 00:00"), thedatetime) />
</cffunction>
</cfcomponent>
you can short circuit that "epoch" time (using seconds, that's actually unix time) by simply doing a theDateTime.getTime() to get back java epoch in ms (see my comment from 2011).
Hi Ray,
I see a number of people are running into problems seeing their results show up in their calendar. I am one of them. I then remembered the issues I had seeing my grid when working with the jqgrid when robust debugging was turned on. So, I turned off robust debugging in cfadmin and I magically saw the results in my Calendar.
I then turned off showdebugoutput and that had the same result without turning off robust debugging in cfadmin.
Please advise your followers who are struggling to see results to add:
<cfsetting showdebugoutput="false"> right under <cfcomponent>.
Their component should look like this:
<cfcomponent>
<cfsetting showdebugoutput="false">
I was able to see results without using the epochTimeToDate function.
I hope this helps someone.
thanks,
Bud
Thanks for the code Ray - is there anywhere that you know of that gets this working with the drag and drop and having that update the database - I found something on Google ages ago but can't for the life of me find it again!!
Cheers
Jan
Sorry no, I haven't played with this (plugin) in a while. (Well, that's a lie - I used it for a HarpJS demo a bit ago.)