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.
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; } } ?> |
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); } } ?> |
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 ), ); } ?> |
1 2 3 4 5 6 7 8 9 |
<h2><?php __('Events');?></h2> <?php echo $calendar->calendar($year, $month, $data, $base_url); ?> |
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; |
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;} |
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).
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)