FullCalendar and CakePHP part 3 – Adding Events

DUCK SWOOP

Creative Commons License photo credit: Vidyo

In the previous part of this series we saw how to link fullCalendar via CakePHP to a MySQL events table. This is nice, but it’s only really useful if all the Create/Update and Delete actions on your events happen somewhere else.

Obviously – you’ll want a way to manipulate events directly from the calendar control on the screen.

FullCalendar has excellent support for event manipulation. In this post we’ll see one way to integrate this with CakePHP, to create a full-fledged CRUD calendar in your application.

A calendar control is quite confusing when interactive applications are concerned. This is because the term ‘event’ has two meanings in this context. One type of events makes up the calendar’s data, while the other type is fired when the user clicks an area on the calendar control.

FullCalendar calls both of these types ‘events’ – which makes the (extensive and impressive) documentation a bit confusing at times. This just means you need to keep focus when reading it.

FullCalendar exposes a few event handles for you. I am going to deal with the following:

  • dayClick: function(date, allDay, jsEvent, view) This is the one used for adding events, which we cover in this post
  • eventClick: function(calEvent, jsEvent, view)
  • eventDrop: function(event,dayDelta,minuteDelta,allDay,revertFunc)
  • eventResize: function(event,dayDelta,minuteDelta,revertFunc)

The way this works is quite simple. When one of these events happen, fullCalendar calls the event handler you attach to that event. The event handler receives the parameters in the list above, and all you need to do is write the code to parse and handle the data. (For full details of these parameters – see fullCalendar’s documentation).

The dayClick handle is called when a day is clicked in the calendar. The data that interests us in our event handler is the date object and the allDay flag. We can use this data to initiate an add operation in our events_controller.

A nice way to implement this is by presenting the user with a simple form to add the event details. This form can be displayed in various ways – you can use lightbox, thickbox, fancybox – any box, really. In this example I’ll just use a simple <div> element. It is normally hidden, and is shown only when the add (and later edit) operation is called:

Inside the fullCalendar() init method, add this:

dayClick: function(date, allDay, jsEvent, view) {
    $("#eventdata").show();
    $("#eventdata").load("<?php echo Dispatcher::baseUrl();?>/events/add/"+allDay+"/"+$.fullCalendar.formatDate( date, "dd/MM/yyyy/HH/mm"));
},
What’s happening here?

Simple, really. We define a function for the dayClick event. This function is called by fullCalendar whenever the user clicks an empty slot on the calendar.

The first thing this function does is call jQuery‘s show() on the eventdata <div> element:

Using the eventdata div is simple. Style and position it with CSS, and use the following code to define and hide it automatically:

<div id="eventdata"></div>

<!-- hide the eventdata div when the page loads -->
<script type="text/javascript">
$(document).ready(function(){
    $("#eventdata").hide();
    ...
}
</script>

After show()-ing the eventdata div, our event handler loads its content using an ajax request to the add() method on our events_controller.
The request parameters are the value of allDay, along with the date/time slot clicked on the calendar. We use fullCalendar’s formatDate utility to split the date object into a dd/MM/yyyy/HH/mm fromat. This results in passing a total of 6 parameters to the add method.

Clicking on the slot for 09:00 at March 26, 2010 will submit the following ajax request:

/events/add/false/26/03/2010/09/00

The add() method in the events_controller receives this request and responds with a form that lets the user create the event.
You can let the user enter the event name, and maybe change the event’s start and end date/time. The sample below is very simplistic, and only asks the user for the event’s title. It defaults to a 1-hour long event, starting at the timeslot the user originally clicked:

function add($allday=null,$day=null,$month=null,$year=null,$hour=null,$min=null) {
    if (empty($this->data)) {
        //Set default duration: 1hr and format to a leading zero.
        $hourPlus=intval($hour)+1;
        if (strlen($hourPlus)==1) {
            $hourPlus = '0'.$hourPlus;
        }

        //Create a time string to display in view. The time string
        //is either  "Fri 26 / Mar, 09 : 00 — 10 : 00" or
        //"All day event: (Fri 26 / Mar)"
        if ($allday=='true') {
            $event['Event']['allday'] = 1;
            $displayTime = 'All day event: ('
                . date('D',strtotime($day.'/'.$month.'/'.$year)).' '.
                $day.' / '. date("M", mktime(0, 0, 0, $month, 10)).')';
        } else {
            $event['Event']['allday'] = 0;
            $displayTime = date('D',strtotime($day.'/'.$month.'/'.$year)).' '
                .$day.' / '.date("M", mktime(0, 0, 0, $month, 10)).
                ', '.$hour.' : '.$min.' &mdash; '.$hourPlus.' : '.$min;
        }
        $this->set("displayTime",$displayTime);

        //Populate the event fields for the add form
        $event['Event']['title'] = 'Event description';
        $event['Event']['start'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$min.':00';
        $event['Event']['end'] = $year.'-'.$month.'-'.$day.' '.$hourPlus.':'.$min.':00';
        $this->set('event',$event);

        //Do not use a view template.
        $this->layout="empty";
    } else {
        //Create and save the new event in the table.
        //Event type is set to editable - because this is a user event.
        $this->Event->create();
        $this->data['Event']['title'] = Sanitize::paranoid($this->data["Event"]["title"], array('!','\'','?','_','.',' ','-'));
        $this->data['Event']['editable']='1';
        $this->Event->save($this->data);
        $this->redirect(array('controller' => "events", 'action' => "index");
    }
}

The method’s functionality is simple: in the spirit of CakePHP – if $this->data is empty (i.e. this is the initial ajax load request, not the form submit) – the method sets up an Event object and passes it to the view.
If, on the other hand, $this->data contains information – the method takes it to be the result of a form submit, and saves the newly created event in the database.

Note that the method only sanitizes the event title it receives from the user and not the other fields. This is because the other fields are either too small to let a malicious user script (size=1) or are never re-displayed to the user. So, even if someone spoofs your form – no harm will come your way (of course, if you want to feel safe – you are welcome to sanitize the rest of the fields).

All that’s left now is create the view for the add() method. This is a very simple view (remember – it is used only to populate the eventdata div):

<!-- app/views/events/add.ctp -->
<?php 
    echo $form->create('Event', array('target'=> '_parent'));
    echo $form->input('title' , array('label' => 'Event title'));
    echo '<br/>At: ' . $displayTime;
    echo $form->input('start', array('type'=>'hidden','value'=>$event['Event']['start']));
    echo $form->input('end', array('type'=>'hidden','value'=>$event['Event']['end']));
    echo $form->input('allday', array('type'=>'hidden','value'=>$event['Event']['allday']));
    echo  $form->end(array('label'=>'Save' ,'name' => 'save')); 
?>

If you’d like to be nicer to your users – you can add the following at the end of add.ctp:

<input type="button" value="Cancel" onclick="back();">
<script type="text/javascript">
    function back() {
        window.location.href ="/events/index";
    }
</script>

That’s it, really. When the user submits the form, a HTTP POST request is fired to the add() method on the events_controller.
When the method receives the POST, $this->data is not empty, and therefore the method creates and saves the new event in the table.
When you return to the calendar view (the last line in the add() method redirects to /events/index) – the new event will appear there, since it is now loaded from the json event source we created in the previous post.

The fullCalendar and CakePHP Series:

  1. Part 1: Set up
  2. Part 2: Creating an event source
  3. Part 3: Adding events
  4. Part 4: Editing events
  5. Part 5: Dragging and resizing

13 thoughts on “FullCalendar and CakePHP part 3 – Adding Events

  1. Liceth Vargas

    Good Afternoon:

    I want to know how to generate recurring events with cakephp 1.3.

    Thanks

  2. Duck Ranger Post author

    @Liceth – this is not a Cakephp question. It’s a PHP question.
    If you want a recurring event, you need to create a loop that adds additional events to the Events table,
    and increments the date of the event every pass of the loop.

  3. Liceth Vargas

    Good Afternoon:
    what happens is that it generate the repetitive events, when printed, and performed the operation, per when I select the user and the office does not care, events generated, for example if you select August 1, 2011, August 8 out of 2011, but the number of times that generated in the loop. Here I leave the piece of code that reliza this operation.

    for($i=0;$iEvent->create();
    $this->Event->save($this->data);
    $var= $this->data['Event']['start']=date(‘Y-m-d’,strtotime($this->Event->data['Event']['start'].’+ 7 days’));
    echo ”;
    print_r($var);

    }

    Thank you very much for your help.

  4. Ted

    Hi duck ranger. Thanks to you I have fullcalendar running in my CakePHP 2.0 app with drag and resize. Ican’t for the life of me get the add to work :(

    It seems this line is not working:
    $(“#eventdata”).load(“/events/add/”+allDay+”/”+$.fullCalendar.formatDate( date, “dd/MM/yyyy/HH/mm”));
    },

    if I change it to $(‘#eventdata’).load(“/events/add/”+allDay+”/”+$.fullCalendar.formatDate( date, “dd/MM/yyyy/HH/mm”));

    it fires but of course I get “Fatal error: Call to a member function create() on a non-object ” because the Eventcontroller is bypassed.

    any ideas?

  5. Ted

    oops I meant this line is not working:

    $(“#eventdata”).load(“/events/add/”+allDay+”/”+$.fullCalendar.formatDate( date, “dd/MM/yyyy/HH/mm”));

  6. Ted

    $(“#eventdata”).load(“php start php echo Dispatcher::baseUrl(); php end /events/add/”+allDay+”/”+$.fullCalendar.formatDate( date, “dd/MM/yyyy/HH/mm”));

  7. Duck Ranger Post author

    @Ted – I am not sure what you mean. It seems like all lines are exactly the same (except for the < ?php ..?> on the last one.
    Note that Dispatcher::baseUrl() doesn’t work in Cake 2.0.

    Can you have a look with firebug console and let me know what you get there?

  8. milina

    The problem was I haven’t import the “fullcalendar.js”.
    Since i’m new to the programming, can you tell me the how to debug console?

  9. Lomguiton

    Hi there,
    First, thanks Duck Ranger for all your work here! It’s so helpfull !
    But i’m wondoring, you said Dispatcher::baseUrl() doesn’t work in Cake 2.0 anymore, so which fonction we should use now?

    I have problems when i have to code a line with PHP function and javascript value!

    Thanks,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>