Full Calendar Example with Client Side Edits

FullCalendar Example with Client-Side Event Updates

Hey everyone,

I’ve been mucking around with FullCalendar recently and decided to share one of the prototypes I’ve ended up with. It basically lets the user change the events without having to do a postback. A user simply has to click the event, type in the changes and hit update.

I’ve posted the code below, however there’s also a zip which is a little easier to manage. Note that you’ll probably want to clean it up a little if you plan to use it in production. The following libraries and plugins are also used:
jQuery
jQuery UI
jQuery FullCalendar
jQuery miniColors

What it Looks Like:

Full Calendar Example with Client Side Edits

Full Calendar Example with Client Side Edits

How to Use It:

It’s all pretty straight forward, but just in case any one runs into issues there are two parts to this example. First, you generate an event template. You can then drag and drop this template onto the calendar as many times as you want.

The second part allows you to edit existing events without posting back to the server. To do this, simply click an event and then make the necessary adjustments using the top panel on the right. Once you’re done, just press update event.

The example uses the standard title property, but also includes a few others: descriptions, price, available. You can change/remove these to suit your needs, just remember to pull them out of the JavaScript as well.

Zip:

Full Calendar Zip

Basic Example Page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<html>
	<head>
 
		<!-- Stylesheets etc -->
		<link rel="stylesheet" type="text/css" href="css/jquery-ui-1.9.2.custom.css" />
		<link rel="stylesheet" type="text/css" href="css/fullcalendar.css" />
		<link rel="stylesheet" type="text/css" href="css/view_calendar.css" />
		<link rel="stylesheet" type="text/css" href="css/jquery.miniColors.css" />
	</head>
	<body>
 
		<!-- Content Wrapper -->
		<div id="content_wrapper">
 
			<!-- Calendar div -->
			<div id="calendar">
			</div>
 
			<!-- Event generation -->
			<div id="event_generation_wrapper">
				<div class='left'>
					<div class='text'>Background:</div><br /> 	
					<div class='text'>Border:</div><br />
					<div class='text'>Text:</div><br />	
				</div>
				<div class='right'>
					<input id="txt_background_color" type='hidden' class='color_picker' value='#2795C3' /><br />
					<input id="txt_border_color" type='hidden' class='color_picker' value='#6AB3D3' /><br />
					<input id="txt_text_color" type='hidden' class='color_picker' value='#ffffff' /><br />
				</div>
				<input id='txt_title' type='text' value='Title' /><br />
				<textarea id='txt_description'>Description</textarea><br />
				<input id='txt_price' type='text' value='5.00' /><br />
				<input id='txt_available' type='text' value='5' /><br />
				<input id="btn_gen_event" type="button" value="New Template" class='btn' />
				<input id="btn_update_event" type="button" value="Update Event" class='btn'/>
				<input id="txt_current_event" type="hidden" value="" />
			</div>
 
			<!-- Booking types list -->
			<div id='external_events'>
				<div id='external_event_template' class='external-event ui-draggable'>One Hour</div>
			</div>
		</div>
 
		<!-- Include scripts at bottom to aid dom loading and prevent hangs -->
		<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'></script>
		<script type='text/javascript' src='js/jquery-ui-1.9.2.custom.min.js'></script>
		<script type='text/javascript' src='js/fullcalendar.js'></script>
		<script type='text/javascript' src='js/view_calendar.js'></script>
		<script type='text/javascript' src='js/jquery.miniColors.js'></script>
	</body>
</html>

Basic JavaScript:

/* Wait for DOM to load etc */
$(document).ready(function(){
 
	//Initialisations
	initialise_calendar();
	initialise_color_pickers();
	initialise_buttons();
	initialise_event_generation();
	initialise_update_event();
});
 
 
/* Initialise buttons */
function initialise_buttons(){
 
	$('.btn').button();
}
 
 
/* Binds and initialises event generation functionality */
function initialise_event_generation(){
 
	//Bind event
	$('#btn_gen_event').bind('click', function(){
 
		//Retrieve template event
		var template_event = $('#external_event_template').clone();
		var background_color = $('#txt_background_color').val();
		var border_color = $('#txt_border_color').val();
		var text_color = $('#txt_text_color').val();
		var title = $('#txt_title').val();
		var description = $('#txt_description').val();
		var price = $('#txt_price').val(); 
		var available = $('#txt_available').val();
 
		//Edit id
		$(template_event).attr('id', get_uni_id());
 
		//Add template data attributes
		$(template_event).attr('data-background', background_color);
		$(template_event).attr('data-border', border_color);
		$(template_event).attr('data-text', text_color);
		$(template_event).attr('data-title', title);
		$(template_event).attr('data-description', description);
		$(template_event).attr('data-price', price);
		$(template_event).attr('data-available', available);
 
		//Style external event
		$(template_event).css('background-color', background_color);
		$(template_event).css('border-color', border_color);
		$(template_event).css('color', text_color);
 
		//Set text of external event
		$(template_event).text(title);
 
		//Append to external events container
		$('#external_events').append(template_event);
 
		//Initialise external event
		initialise_external_event('#' + $(template_event).attr('id'));
 
		//Show
		$(template_event).fadeIn(2000);
	});
}
 
 
/* Initialise external events */
function initialise_external_event(selector){
 
	//Initialise booking types
	$(selector).each(function(){
 
		//Make draggable
		$(this).draggable({
			revert: true,
			revertDuration: 0,
			zIndex: 999,
			cursorAt: {
				left: 10,
				top: 1
			}
		});
 
		//Create event object
		var event_object = {
			title: $.trim($(this).text())
		};
 
		//Store event in dom to be accessed later
		$(this).data('eventObject', event_object);
	});
}
 
 
/* Initialise color pickers */
function initialise_color_pickers(){
 
	//Initialise color pickers
	$('.color_picker').miniColors({
		'trigger': 'show',
		'opacity': 'none'
	});
}
 
 
/* Initialises calendar */
function initialise_calendar(){
 
	//Initialise calendar
	$('#calendar').fullCalendar({
		theme: true,
		firstDay: 1,
		header: {
			left: 'today prev,next',
			center: 'title',
			right: 'month,agendaWeek,agendaDay'
		},
		defaultView: 'agendaWeek',
		minTime: '6:00am',
		maxTime: '6:00pm',
		allDaySlot: false,
		columnFormat: {
			month: 'ddd',
			week: 'ddd dd/MM',
			day: 'dddd M/d'
		},
		eventSources: [
			{
				url: 'calendar_events.json',
				editable: false
			}
		],
		droppable: true,
		drop: function(date, all_day){
			external_event_dropped(date, all_day, this);
		},
		eventClick: function(cal_event, js_event, view){
			calendar_event_clicked(cal_event, js_event, view);
		},
		editable: true
	});	
 
	//Initialise external events
	initialise_external_event('.external-event');
}
 
 
/* Handle an external event that has been dropped on the calendar */
function external_event_dropped(date, all_day, external_event){
 
	//Create vars
	var event_object;
	var copied_event_object;
	var duration = 60;
	var cost;
 
	//Retrive dropped elemetns stored event object
	event_object = $(external_event).data('eventObject');
 
	//Copy so that multiple events don't reference same object
	copied_event_object = $.extend({}, event_object);
 
	//Assign reported start and end dates
	copied_event_object.start = date;
	copied_event_object.end = new Date(date.getTime() + duration * 60000);
	copied_event_object.allDay = all_day;
 
	//Assign colors etc
	copied_event_object.backgroundColor = $(external_event).data('background');
	copied_event_object.textColor = $(external_event).data('text');
	copied_event_object.borderColor = $(external_event).data('border');
 
	//Assign text, price, etc
	copied_event_object.id = get_uni_id();
	copied_event_object.title = $(external_event).data('title');
	copied_event_object.description = $(external_event).data('description');
	copied_event_object.price = $(external_event).data('price');
	copied_event_object.available = $(external_event).data('available');
 
	//Render event on calendar
	$('#calendar').fullCalendar('renderEvent', copied_event_object, true);
}
 
 
/* Initialise event clicks */
function calendar_event_clicked(cal_event, js_event, view){
 
	//Set generation values
	set_event_generation_values(cal_event.id, cal_event.backgroundColor, cal_event.borderColor, cal_event.textColor, cal_event.title, cal_event.description, cal_event.price, cal_event.available);
}
 
 
/* Set event generation values */
function set_event_generation_values(event_id, bg_color, border_color, text_color, title, description, price, available){
 
	//Set values
	$('#txt_background_color').miniColors('value', bg_color);
	$('#txt_border_color').miniColors('value', border_color);
	$('#txt_text_color').miniColors('value', text_color);
	$('#txt_title').val(title);
	$('#txt_description').val(description);
	$('#txt_price').val(price);
	$('#txt_available').val(available);
	$('#txt_current_event').val(event_id);
}
 
 
/* Generate unique id */
function get_uni_id(){
 
	//Generate unique id
	return new Date().getTime() + Math.floor(Math.random()) * 500;
}
 
 
/* Initialise update event button */
function initialise_update_event(){
	var test = $('#calendar').fullCalendar( 'clientEvents');
	//Bind event
	$('#btn_update_event').bind('click', function(){
 
		//Create vars
		var current_event_id = $('#txt_current_event').val();
 
		//Check if value found
		if(current_event_id){
 
			//Retrieve current event
			var current_event = $('#calendar').fullCalendar('clientEvents', current_event_id);
 
			//Check if found
			if(current_event && current_event.length == 1){
 
				//Retrieve current event from array
				current_event = current_event[0];
 
				//Set values
				current_event.backgroundColor = $('#txt_background_color').val();
				current_event.textColor = $('#txt_text_color').val();
				current_event.borderColor = $('#txt_border_color').val();
				current_event.title = $('#txt_title').val();
				current_event.description = $('#txt_description').val();
				current_event.price = $('#txt_price').val();
				current_event.available = $('#txt_available').val();
 
				//Update event
				$('#calendar').fullCalendar('updateEvent', current_event);
			}
		}
	});
}

Stylesheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
body{
	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
	font-size: 12px;
}
 
#content_wrapper{
	width: 820px;
	margin-left: auto;
	margin-right: auto;
}
 
#calendar{
	width: 650px;
	float: left;
}
 
.btn{
	font-size: 85%
	margin-top: 10px;
}
 
#event_generation_wrapper{
	float: left;
	width: 120px;
	background-color: #DDD;
	margin-left: 20px;
	margin-top: 40px;
	border: 1px solid #4297D7;
	background-color: #FCFDFD;	
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
	padding: 10px 5px 10px 5px;
}
 
#event_generation_wrapper .left{
	float: left;
	width: 70px;
}
 
#event_generation_wrapper .right{
	float: left;
	max-width: 25px;
	margin-left: 10px;
}
 
#event_generation_wrapper input{
	width: 112px;
}
 
#event_generation_wrapper textarea{
	width: 110px;	
	height: 50px;
}
 
#event_generation_wrapper .miniColors-triggerWrap{	
	margin-bottom: 5px;
}
 
#event_generation_wrapper .text{
	padding-top: 1px;	
	margin-bottom: 5px;
	line-height: 10px;
}
 
 
#external_events{
	float: left;
	width: 130px;
	background-color: #DDD;
	margin-left: 20px;
	margin-top: 40px;
	border: 1px solid #4297D7;
	background-color: #FCFDFD;	
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
}
 
#external_events .external-event{
	width: 100px;
	margin: 5px;
	font-family: Verdana, Arial, sans-serif;
	border: 1px solid #36C;
	padding: 3px;
	text-align: center;
	background-color: #36C;
	color: white;
	cursor: pointer;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
}
 
#calendar .ui-widget-header{
	font-weight: normal;
	padding: 3px 3px 3px 3px;
}
 
#calendar .fc-header-title{
	font-weight: normal;
}
 
#external_event_template{
	display: none;
}

Basic List of Events (JSON):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[
    "0",
    {
        "allDay": "",
        "title": "Test event",
        "id": "821",
        "start": "2012-11-23 14:00:00",
        "end": "2012-11-23 15:00:00"        
    },
    "1",
    {
        "allDay": "",
        "title": "Test event 2",
        "id": "822",        
        "start": "2012-11-23 9:00:00",
        "end": "2012-11-23 10:00:00"
    },
    "2",
    {
        "allDay": "",
        "title": "Test event 3",
        "id": "823",        
        "start": "2012-11-24 8:00:00",
        "end": "2012-11-24 6:00:00"
    },
    "3",
    {
        "allDay": "",
        "title": "Test event 4",
        "id": "824",        
        "start": "2012-11-27 6:00:00",
        "end": "2012-11-27 7:00:00"
    }
]

Let me know if you have any issues or something to add!

Cheers,
Chris

38 thoughts on “FullCalendar Example with Client-Side Event Updates

  1. Raül

    How to do that the events persist? I mean, I create new events, they are shown but when I go out and back (refresh page, go out anb back to the site, etc) they are disappeared.

    Reply
    1. Chris Owens Post author

      Hey Raul,

      To store the events you’ll probably want to wire it up to a backend of some sort or look into HTML5 storage depending on what you’re after. Alternatively, FullCalendar does support Google Calendar. I haven’t tried it but if it’s probably a lot quicker to setup if you don’t need a fully fledged app. Check out this link for more info: http://arshaw.com/fullcalendar/docs/google_calendar/.

      Reply
  2. Kala Kala

    Nice ! How to remove the event just dropped from the event list (i want to be able to drop an event only once). Can we reverse an event to the events list ?

    Thanks you and sorry for my english

    Reply
    1. Chris Owens Post author

      Hey Kala,

      No worries. If I’m understanding you right, all you’d need to do is an a line to the external_event_dropped event. Just remove the matching template from the id. You might be better off redoing the event list if you’re going to do much with it though, this is really just a mock-up. If I were to do it all again, I’d probably take a look at AngularJS – you could do some really nice stuff wiring this up as a directive.

      Let me know if you need any more info.

      Cheers,
      Chris

      Reply
  3. James Klein

    I’m using FullCalendar out of the box following their documentation. I’m able to pass json to the view with my Event’s controller with no problem but I can’t seem to do an ajax post request as a PUT to update the event after moving it around the calendar.
    I’m getting this message
    > PUT http://localhost:3001/events/2 406 (Not Acceptable)

    and the code that I’ve dropped into my calendar.js is as follows
    editable: true,
    eventDrop: function(event,dayDelta,minuteDelta,allDay,revertFunc) {
    $.ajax({
    type:”PUT”,
    url: “/events/”+event.id
    });
    }
    I know I’m not passing any data right now but I just want to get the post working.
    Thanks in advance and any help is appreciated.

    Reply
    1. Chris Owens Post author

      Hey James,

      I’m going to need some more info to be able to help, this is probably server side.

      Is it just put that’s affected or is post being rejected as well? What stack etc?

      If you can you provide a link or fiddle I’ll take a quick look? Might also be worth trying to set to set the content type.

      Cheers,
      Chris

      Reply
      1. James Klein

        Hey Chris,
        I meant to reply to this awhile ago. Not sure what the fix was back then but here is my updated code incase it helps anyone else. Notice the “.json” which may have been the problem.

        eventDrop: function(event,dayDelta,minuteDelta,allDay,revertFunc) {
        console.log(event.id);
        $.ajax({
        url: “/events/” + event.id +”.json”,
        type: ‘PUT’,
        data: { days_moved: dayDelta },
        success: function(result) {
        },
        error: function() {
        console.log(“Failed to update event”);
        }
        });
        },

        Cheers,
        James

        Reply
  4. Karteek Kommana

    Hi Chris,
    It was nice work for fullCalendar. I appreciate your work. I wonder is it possible to save the events in Database and show it agian when ever the web page is loaded? I want to have a event calendar in my project where user can able to create event and see it when ever they visit the page again.
    Thanks
    Karteek

    Reply
  5. Lugisani Muthige

    There seems to a bug on the calendar interface, when i navigate through dates there is a blue portion that comes up from the buttons to the date title, its like one that’s meant for editing the date i’m not sure… and when i add events on the monthly view they don’t appear on the week and day view.

    Reply
  6. Gary

    Chris, just want to say thanks, this is an excellent foundation and starting point, thanks for the framework to get up and running with understanding of how to implement and tool the calendar.

    Reply
  7. Gary

    Chris, one more thing, wondering if you’ve encountered this in your work with this but is there an easy way to incorporate the repeat event, say, I want to set an event that I am on call every 3rd week of every month? thanks again

    Reply
  8. Gary

    Chris, you are differently the master and a very kewl person to keep this up and provide us with your knowledge. a million thanks, because without it, I know I would spend countless hours getting to this point. thank you very much!!

    Reply
  9. Peter Kaye

    Thanks for this post which has opened my eyes to Full Calendar features I’d not been aware of.

    I’d like to render the event id in the HTML as an HTML ID or similar so that I can pick it up in an Microsoft Access application. For example

    Peter Jones

    where 99023 is the MySQL ID for the event. Is this possible and if so could you point me in the right direction ? Thanks again.

    Reply
    1. Chris Owens Post author

      Hey Peter,

      Sorry, I’m not too sure what you mean? Do you want to have it appear in the event so that users can can see it, or just have it as a data attribute on the element?

      Cheers,
      Chris

      Reply
      1. Peter Kaye

        Thanks Chris.
        No I don’t want the id to appear on the calendar, just in the HTML so that my VBA screen scrape can read it and find the correct calendar event record.

        Reply
  10. Peter Kaye

    I should have realised that HTML gets stripped from WP comments ! The missing code relates to the Full Calendar Div which wraps the event and has a class of fc-event-inner.

    Reply
  11. Harsh Jhaveri

    Hello, I am using Full Calendar in my pages, but there is some issues as if i am selecting any records then that day selected but if i am click out side of it, then it will be unselect, even if i m click on page scroll then also it unselect my selection.
    IS there any way where we can default selected particular day which we clicked.

    Please advice

    thanks

    Reply
  12. Don Martin

    I am interested in retieving events from a file and tried you eventsource with calendar_events.json. I was not able to get it to work. So I downloaded and ran your zip package. Only the first event is displayed. Has the json spec change in the last year?

    …Don

    Reply
  13. Cesar

    Hi Chris , I have a doubt , was reviewing your contribution and I feel sensational, I have just one question , and I take several days looking for information about it and I can not take this, I need you to keep an event that remains assigned a color but not I have idea how to pass this parameter by Jason respect for me to load data into the full calendar from a MySQL DB can be done ?

    Thank You

    atte

    Cesar

    Reply
  14. Jesùs

    Hi Chris,

    You definitely saved one poor soul today from getting insane :)
    Just one remark:
    your function get_uni_id() returns the UNIX timestamp in milliseconds,
    because Math.floor(Math.random()) always equals 0,
    but hey, I guess that return value is unique enough.

    Cheers,
    Jesús

    Reply
  15. Jalaal Charaf

    Hi Chris,

    Really a useful bit of coding. Nice work!

    I cannot get it to work with the newer version of fullcalendar though. Have run into many errors. Did anyone do any updates on this code?

    Thanks,

    Jaime

    Reply
    1. Chris Owens Post author

      Hey Jamie,

      Thanks for the reply hey, unfortunately this was all done on the original fullcalendar. I’m not too sure what’s changed with the updates. I’d tags a look but I’m overseas for a few weeks.

      Please let me know if there’s anything that needs to be added to the party and I’m update it.

      Cheers,
      Chris

      Reply

Leave a Reply