CakePHP Calendar Helper
About six months ago I wrote a post about a Simple PHP calendar function I had written and how it was also really easy to use as a CakePHP helper.

I didn’t actually write the calendar specifically with Cake in mind, but I was working on a Cake site at the same time and I had a flash of inspiration. I was working on a project that needed a calendar, so I looked through all my old code but all the Calendars I had were all tied up in a terrible mess with bits of logic and SQL queries and layout all rolled into one. I looked on Google and still couldn’t find anything really easy to use - I wanted something I could just drop into place.
I suddenly realised that the way to do it was to stop trying to put any decision making into the calendar at all. The calendar only needs to display the right layout for the month and manage back and forward links. I decided to just dump the data in an array where the index corresponded to the day number (1 to 31) - the idea being that you can put anything in the array - plain text, html, javascript hooks for ajax etc.
It was in part influenced by some work I was doing using the The Yahoo! User Interface Library (YUI)
but I am very dubious about the whole notion of creating embedded calendars solely through Javascript when it could far more easily be done server side. (There is clearly a place for Javascript pop up calendars e.g. date pickers - I’m particularly fond of Marc Grabanski and Keith Woods’ version jQuery UI Datepicker)
At the time I had just moved over to using CakePHP as the main vehicle for my development work and it was clearly a great fit with the MVC setup of CakePHP - the calendar is just a shell that shows whatever you pour into it.
This is the first time I have had to go back and revisit the code, I have fixed the bugs, added some comments and set up a working example. At the moment all the logic is just sitting in the controller - but I am working on a component to tidy everything up and make it nice and portable (watch this space).
Thanks to everybody who commented or emailed me about the first version in September.
Calendar Helper
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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | <?php /** * Calendar Helper for CakePHP * * Copyright 2007-2008 John Elliott * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * * @author John Elliott * @copyright 2008 John Elliott * @link http://www.flipflops.org More Information * @license http://www.opensource.org/licenses/mit-license.php The MIT License * */ class CalendarHelper extends Helper { var $helpers = array('Html', 'Form'); /** * Generates a Calendar for the specified by the month and year params and populates it with the content of the data array * * @param $year string * @param $month string * @param $data array * @param $base_url * @return string - HTML code to display calendar in view * */ function calendar($year = '', $month = '', $data = '', $base_url ='') { $str = ''; $month_list = array('january', 'febuary', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'); $day_list = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'); $day = 1; $today = 0; if($year == '' || $month == '') { // just use current yeear & month $year = date('Y'); $month = date('M'); } $flag = 0; for($i = 0; $i < 12; $i++) { if(strtolower($month) == $month_list[$i]) { if(intval($year) != 0) { $flag = 1; $month_num = $i + 1; break; } } } if($flag == 0) { $year = date('Y'); $month = date('F'); $month_num = date('m'); } $next_year = $year; $prev_year = $year; $next_month = intval($month_num) + 1; $prev_month = intval($month_num) - 1; if($next_month == 13) { $next_month = 'january'; $next_year = intval($year) + 1; } else { $next_month = $month_list[$next_month -1]; } if($prev_month == 0) { $prev_month = 'december'; $prev_year = intval($year) - 1; } else { $prev_month = $month_list[$prev_month - 1]; } if($year == date('Y') && strtolower($month) == strtolower(date('F'))) { // set the flag that shows todays date but only in the current month - not past or future... $today = date('j'); } $days_in_month = date("t", mktime(0, 0, 0, $month_num, 1, $year)); $first_day_in_month = date('D', mktime(0,0,0, $month_num, 1, $year)); $str .= '<table class="calendar">'; $str .= '<thead>'; $str .= '<tr><th class="cell-prev">'; $str .= $this->Html->link(__('prev', true), 'calendar/' . $prev_year . '/' . $prev_month); $str .= '</th><th colspan="5">' . ucfirst($month) . ' ' . $year . '</th><th class="cell-next">'; $str .= $this->Html->link(__('next', true), 'calendar/' . $next_year . '/' . $next_month); $str .= '</th></tr>'; $str .= '<tr>'; for($i = 0; $i < 7;$i++) { $str .= '<th class="cell-header">' . $day_list[$i] . '</th>'; } $str .= '</tr>'; $str .= '</thead>'; $str .= '<tbody>'; while($day <= $days_in_month) { $str .= '<tr>'; for($i = 0; $i < 7; $i ++) { $cell = ' '; if(isset($data[$day])) { $cell = $data[$day]; } $class = ''; if($i > 4) { $class = ' class="cell-weekend" '; } if($day == $today) { $class = ' class="cell-today" '; } if(($first_day_in_month == $day_list[$i] || $day > 1) && ($day <= $days_in_month)) { $str .= '<td ' . $class . '><div class="cell-number">' . $day . '</div><div class="cell-data">' . $cell . '</div></td>'; $day++; } else { $str .= '<td ' . $class . '> </td>'; } } $str .= '</tr>'; } $str .= '</tbody>'; $str .= '</table>'; return $str; } } ?> |
Calendar Controller
The controller is responsible for creating the data array containing the calendar ‘events’ and then passing this on to the view from where the calendar is called.
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 107 | <?php /** * Example controller for the Calendar Helper * * Copyright 2008 John Elliott * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. * * * @author John Elliott * @copyright 2008 John Elliott * @link http://www.flipflops.org More Information * @license http://www.opensource.org/licenses/mit-license.php The MIT License * */ uses('sanitize'); class EventsController extends AppController { var $name = 'Events'; var $helpers = array('Html', 'Form', 'Calendar'); /** * the idea is that the calendar helper itself is purely a shell * the calendar will just display whatever is sent to it * anything you want to do to it is put togthere here in the controller or in a component when I get around to writing it * * @param $year string * @param $month string * **/ function calendar($year = null, $month = null) { $this->Event->recursive = 0; $month_list = array('january', 'febuary', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'); $day_list = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'); $base_url = $this->webroot . 'events/calendar'; // NOT not used in the current helper version but used in the data array $view_base_url = $this->webroot. 'events'; $data = null; if(!$year || !$month) { $year = date('Y'); $month = date('M'); $month_num = date('n'); $item = null; } $flag = 0; for($i = 0; $i < 12; $i++) { // check the month is valid if set if(strtolower($month) == $month_list[$i]) { if(intval($year) != 0) { $flag = 1; $month_num = $i + 1; $month_name = $month_list[$i]; break; } } } if($flag == 0) { // if no date set, then use the default values $year = date('Y'); $month = date('M'); $month_name = date('F'); $month_num = date('m'); } $fields = array('id', 'name', 'DAY(event_date) AS event_day'); $var = $this->Event->findAll('MONTH(Event.event_date) = ' . $month_num . ' AND YEAR(Event.event_date) = ' . $year, $fields, 'Event.event_date ASC'); /** * loop through the returned data and build an array of 'events' that is passes to the view * array key is the day of month * */ foreach($var as $v) { if(isset($v[0]['event_day'])) { $day = $v[0]['event_day']; if(isset($data[$day])) { $data[$day] .= '<br /><a href="' . $view_base_url . '/view/' . $v['Event']['id'] . '">' . $v['Event']['name'] . '</a>'; } else { $data[$day] = '<a href="' . $view_base_url . '/view/' . $v['Event']['id'] . '">' . $v['Event']['name'] . '</a>'; } } } $this->set('year', $year); $this->set('month', $month); $this->set('base_url', $base_url); $this->set('data', $data); } } ?> |
Event Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php class Event extends AppModel { var $name = 'Event'; var $useTable = 'events'; var $validate = array( 'name' => array( 'rule' => array('minLength', 2), 'message' => 'Name must be at least 2 characters long', 'required' => true ), 'notes' => array( 'rule' => array('minLength', 2), 'message' => 'Please add some notes', 'required' => true ), ); } ?> |
Calendar View
1 2 3 4 5 6 7 8 9 | <h2><?php __('Events');?></h2> <?php echo $calendar->calendar($year, $month, $data, $base_url); ?> |
events SQL
1 2 3 4 5 6 7 8 9 10 | CREATE TABLE `events` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `event_date` datetime DEFAULT NULL, `notes` text, `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=latin1; |
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* calendar CSS */ table.calendar {width: auto; border: 1px solid #cccccc; border-collapse: collapse; margin: 0px; padding: 0px; background-color: #ffffff;} table.calendar th {background-color: #eeeeee; text-transform: none; color: #444444; padding: 4px; text-align: center; border: 1px solid #eeeeee;} table.calendar th.cell-prev {text-align: left;} table.calendar th.cell-next {text-align: right;} table.calendar th.cell-header {width: 70px; border-bottom: 1px solid #cccccc;} table.calendar td.cell-today {background-color: #e2e8f6;} /* today in the current month */ table.calendar td.cell-weekend {background-color: #F3F5EB;} table.calendar td {border: 1px solid #cccccc;} table.calendar td div.cell-number {text-align: right; font-size: 8px; color: #444444; display: block;} table.calendar td div {display: block; font-size: 10px; text-align: left;} table.calendar thead th {border: 1px solid #cccccc;} |
To Do List
I’m feeling a little less time poor at the moment and want to do a bit of work on this. If anybody has any comments or suggestions, please let me know. At the moment my plans are as follows (in no particular order).
- add support for internationalisation
- put the checking date checking (and possibly data array construction into a component)
- add built in AJAX support (haven’t decided on jQuery or Prototype yet though)
- look at using Cake 1.2 routes as part of date checking using regular expressions (how easy would it be to combine this with the internationalisation?)
Examples & Downloads
Check out the Working Calendar Helper Example
Download all the files calendar-helper.zip - this is a zipped copy of the app directory, just set up you database, edit /app/core/database.php and slot it into place. (Note Cake 1.2 only)




April 9th, 2008 at
Nice job!
April 10th, 2008 at
Flipflops.org » Blog Archive » CakePHP Calendar Helper…
[…][…]…
April 13th, 2008 at
Nicely done - this will be really great once you’ve added the final things to it. I would have thought Prototype would be the way to go with it. Keep us updated anyway.
I’ve just redesigned our blog, and the sort of approach we both have with designs currently is very similar, it’s quite amusing.
April 13th, 2008 at
I know - its a hard choice I like jquery - but will probably use prototype as it comes out of the box with cake…
April 27th, 2008 at
Hi, I’m relatively new to CakePHP and I’m having a bit of trouble figuring out where all these files should go and what conventions they should follow. I’m using CakePHP 1.2, and I have the files here:
Calendar Helper: /app/views/helpers/calendar.php
Calendar View: /app/views/calendar.php
Event Model: /app/models/event.php
Calendar Controller: /cakebox/app/controllers/events_controller.php
When I go to /events/calendar in my browser I get the following error:
Class ‘Event’ not found in /home/httpd/html/cake/libs/class_registry.php
If you have any advice or insights they would be appreciated. Thanks!
April 28th, 2008 at
Hi William
Calendar Controller: /cakebox/app/controllers/events_controller.php looks a bit weird - the cakebox part of the address - I am guessing though that this is just your development directory and you edited all the other paths for clarity.
When something goes wrong I always firstly check for Typos, then clear the app/tmp/cache/models
Also what debug level have you got set in core/config.php ?
Other than that I’m not sure - but I will have a think about it. I’ll also try and zip up all the files as a working download over the next few days.
Hope this helps.
April 29th, 2008 at
I’ve just zipped up all the files and uploaded them, so it should now be very easy to get it up and running.
April 29th, 2008 at
Very nice.
I wrote a DateHelper and CalendarHelper 2 years ago specifically for cake, which is of course dearly in need of a refactoring, that does a lot of what you plan on adding.
I’m going to dive in this week on it, as I need an international calendar for a project. Feel like merging resources?
Love it, well done!
May 1st, 2008 at
Hi Tommy
Thanks for getting in touch - potentially I am definitely up for joining forces, but I am am really snowed under at the moment and wouldn’t be able to contribute that much at the moment.
I’ve been giving it a lot of thought though recently and have decided that I want in this kind of direction - how does this tie in with your ideas?
I want to extend the helper so you can specify how many calendars you want to show at a time - so you could show say 12 little calendars one for each month or 1 or 4 or whatever - I quite like some of the things you can do with Yahoo YUI, just not the implementation - and I have thought of this very much in terms of a server rather than client side thing.
The main change from that would be the input data array would have to change from a just an array with key 0-31 to a more complex array probably [year][month][day]
I thought it would be good to hive off all the code for checking the validity of a month (from the url) into a component and also functions for generating the data array, so you have a very clean controller.
Then integrating built in AJAX support for scrolling back and forth between calendar months that you can toggle on / off.
I’m still uming and ahhhing about whether to leave the actual content for a given day as just a container where people can put what they want into it….
On the internationalisation front - especially in Cake 1.2 shouldn’t be that hard should it ? (not that I’ve ever had call to do any) - the main thing would just be the elements in month names array…
Cheers
BTW - I think the first thing I’m going to do is add support for multiple calendars.
May 23rd, 2008 at
Very useful Flipflops ! Thanks for sharing your work
I just add the possibility to check if an event is private or public so you can both have a public or private events.
Add a ‘private’ field in your events table : TINYINT (NOT NULL,0)
And in your event controller :
$fields = array(’id’, ‘name’, ‘DAY(event_date) AS event_day’,'private’);
$varPublic = $this->Event->findAll(’MONTH(Event.event_date) = ‘ . $month_num . ‘ AND YEAR(Event.event_date) = ‘ . $year . ‘ AND (private=0)’, $fields, ‘Event.event_date ASC’);
$varPrivate = $this->Event->findAll(’MONTH(Event.event_date) = ‘ . $month_num . ‘ AND YEAR(Event.event_date) = ‘ . $year . ‘ AND (private=1 OR private=0)’, $fields, ‘Event.event_date ASC’);
[…]
Check if a session exist or not :
if (!$this->Session->check(’User’)) {$check = $varPublic;} else {$check = $varPrivate;}
October 9th, 2008 at
Nice stuff bro . Been trying to re-do but hitting walls , op ill shoot it . can u explain why you use the $base_url = $this->webroot. Think its the one giving me hell .
October 12th, 2008 at
Hey ‘FlipFlop’,
I would 1st like to say I really enjoyed your helper! I also wanted to let you knw i’ve converted the calendar to work 100% in Ajax form, you can switch months via ‘Ajax’, you can view an event in a ‘pop-up modal’ and even click edit in there and edit in a different modal. Once i have it on the internet I can share it you. Well thank you very much for such a nice calnendar!
-Ron
October 14th, 2008 at
@Ron Cheers - glad you liked it, are you using any particular framework for the Ajax? One of the (many) reasons I never did this was because I couldn’t decide whether to go jQuery or Prototype…
For the record I’m now firmly in the jQuery camp, I use it almost every day, it makes the little things so much easier. Giod it makes me shudder when I think of the hoops I used to jump through only a couple of years ago… thinking in particular of a car booking system I wrote about 2 1/2 years ago for a car share club - looping through a table with every hour for an entire week and adding event listeners to watch for clicks… hundreds of lines of javascript and all that browser forking code - in jQuery the whole thing would probably be 10 lines.
p.s. any links back to the site greatly appreciated.
October 14th, 2008 at
@mzee
Sorry the $base_url is a kind of orphan from the previous version - in some ways it should have been re factored out but I left in because I thought I might end up needing it so I just left a comment in there.
October 16th, 2008 at
sure . thks bro
November 4th, 2008 at
I installed everything on my application.
Where should I go to get the calendar? if I write myroot/events I get an error. I need to go to myroot/events/calendar/November/2008 to get the page. And even in this case in the end of the page i receive this error:
Notice: Array to string conversion in C:\Programmi\EasyPHP 2.0b1\www\cake\libs\view\helpers\html.php on line 179
And when I click on “add” i get:
Fatal: Confirm you have created the EventsController::Array() in file : app\controllers\events_controller.php
All the file are in the right folders. What’ s the matter?
November 10th, 2008 at
Flipflops,
This looks like a great solution to my problem, but I’m new to cakePHP and I was wondering what you meant when you said “slot it into place” as far as installing the app is concerned.
Thanks,
Brian
November 12th, 2008 at
@Brian - Slot it into place kinda means it shouldn’t be too hard to get working - but you also have to bear in mind that Cake on the whole is a little more involved than something like say Joomla or Wordpress where you litterally can just get a plugin or component and drop it into place.
I would say download the example and get it running as a first step. I just checked and the vesion it uses is 1.2.0.6311 beta - so I would initially try installing it on a test server like that.
November 12th, 2008 at
@Max - I’m guessing that this is because it makes uses of depreceated code - hi try just downloading the example and setting that up - but also download a copy of Cake 1.2.0.6311 beta - as this is the version the example online uses - so it really should ‘just work’ when you have set up a site on your dev machine. Then I would recommend downloading the latest version of Cake and fixing the errors that come up.
However thanks for pointing out the error - I’ll look at this and update the example to run on RC3.
December 30th, 2008 at
Hello John,
I thought I’d mention that I liked your calendar helper, but needed something slightly different, so I modified your helper so that it now can also generate a weekly hour by hour calendar. My version of the helper now has two methods, “month” (your “calendar” method, renamed), and “week”.
The week method generates a weekly view, and you can specify the day, month, and year, and it will display the week that it falls on starting on monday. The week method also takes min_hour and max_hour which lets you specify which hours to display (for most applications, you wont need to show the entire day, for instance, you can set it to display 9:00 - 17:00, the typical workday). The data array is specified like this $data[day][hour] so that you can place an event on any given hour of any given day.
The mod was surprisingly simple, and doesnt really change much else. It even uses the same CSS file with no modifications.
John, please contact me by email if you’d like the source to post on your website.
– Mark Roy