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 164 165 166 | <?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', 'february', '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), array($prev_year, $prev_month)); $str .= '</th><th colspan="5">' . ucfirst($month) . ' ' . $year . '</th><th class="cell-next">'; $str .= $this->Html->link(__(‘next’, true), array($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" '; } $first_day_in_month = strtolower($first_day_in_month); 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', 'february', '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


Hi, I like your calendar, I was trying use, but don’t work :’(
When I write: path/events/calendar , this appears:
Warning (512): Method CalendarHelper::calendar does not exist [CORE\cake\libs\view\helper.php, line 154]
I have this files:
controllers: events_controller.php
Models: event.php
helpers: calendar.php
views: calendar.ctp
Now, I believe that the problem is in calendar.ctp:
calendar($year, $month, $data, $base_url);
?>
I don’t understand why use $calendar as object?
You can help me? What is wrong and how could it right?
Thanks