CakePHP Calendar Helper

By | January 7, 2009

Recently I began my first CakePHP project; for those of you who aren’t aware, CakePHP is a popular model-view-controller framework for creating web applications in PHP.

In my project, I had the need for a calendar in a few of my views, and after looking around a bit, I found a nice and simple calendar helper.  The helper works fine and dandy, but doesnt quite do all of the things I needed; namely, what was missing was a weekly calendar view that displays an hourly schedule one week at a time.

The main differences between my helper and the original is the addition of the “week” function and the renaming of the “calendar” function to “month”.

I’ve also changed the code a bit to make the $data parameter that’s passed to both the month and week functions an optional associative array that also allows you to set an onClick and class type for each cell in addition to the content. This makes it much easier to incorporate AJAX or just plain javascript with the calendar. For example:

$weekData=array();
$weekData[10][12] = array('content'=>'Lunch time',
'onClick'=>'alert(\'mmm.. lunch\');',
'class'=>'lunch');

If you passed $weekData to the week function, the cell on the 10th day (if it exists) at 12pm would have “Lunch time” in the cell, and when you click it will alert ‘mm.. lunch’.

Also added are cell id’s; in the monthly calendar, each cell has the id “cell-DD” where DD is the day of the month. In the weekly calendar, it is “cell-DD-HH’ where HH is the hour. This makes it easier to dynamically change cells or determine which one is selected with javascript.

UPDATED: Download Source Here

< ?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 $month_list = array('january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december');
	var $day_list = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');
 
	var $helpers = array('Html', 'Form');
 
	/**
	 * Perpares a list of GET params that are tacked on to next/prev. links.
	 * @retunr string - urlencoded GET params.
	 */
	function getParams()
	{
		$params=array();
		foreach ($this->params['url'] as $key=>$val)
			if ($key != 'url')
				$params[]=urlencode($key).'='.urlencode($val);
		return (count($params)>0?'?'.join('&',$params):'');
	}
 
	/**
	* 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 month($year = '', $month = '', $data = '', $base_url ='')
	{
		$str = '';
		$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) == $this->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 = $this->month_list[$next_month -1];
		}
 
		if($prev_month == 0) {
			$prev_month = 'december';
			$prev_year = intval($year) - 1;
		} else {
			$prev_month = $this->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 .= '

‘; $str .= ”; $str .= ”; $str .= ”; for($i = 0; $i < 7;$i++) { $str .= ”; $str .= ”; $str .= ”; while($day < = $days_in_month) { $str .= ”; for($i = 0; $i < 7; $i ++) { $cell = ‘ ‘; $onClick=”; $class = ”; $style =”; if($i > 4) { $class = ‘ class=”cell-weekend” ‘; } if($day == $today) { $class = ‘ class=”cell-today” ‘; } if(isset($data[$day])) { if (is_array($data[$day])) { if (isset($data[$day][‘onClick’])) { $onClick = ‘ onClick=”‘.$data[$day][‘onClick’].'”‘; $style= ‘ style=”cursor:pointer;”‘; } if (isset($data[$day][‘content’])) $cell = $data[$day][‘content’]; if (isset($data[$day][‘class’])) $class = ‘ class=”‘.$data[$day][‘class’].'”‘; } else $cell = $data[$day]; } if(($first_day_in_month == $this->day_list[$i] || $day > 1) && ($day < = $days_in_month)) { $str .= ‘<td ‘ . $class .$style.$onClick.’ id=”cell-‘.$day.'”>’ . $day . ” . $cell . ”; $day++; } else { $str .= ‘<td ‘ . $class . ‘> ‘; } } $str .= ”; } $str .= ”; $str .= ‘

‘; $str .= $this->Html->link(__(‘prev’, true), $base_url.’/’ . $prev_year . ‘/’ . $prev_month.$this->getParams()); $str .= ‘ ‘ . ucfirst($month) . ‘ ‘ . $year . ‘ ‘; $str .= $this->Html->link(__(‘next’, true), $base_url.’/’ . $next_year . ‘/’ . $next_month.$this->getParams()); $str .= ‘
‘ . $this->day_list[$i] . ”; } $str .= ‘
';
 
		return $str;
	}
 
	/**
	* Generates a Calendar for the week specified by the day, month and year params and populates it with the content of the data array
	*
	* @param $year string
	* @param $month string
	* @param $day string
	* @param $data array[day][hour]
	* @param $base_url
	* @return string - HTML code to display calendar in view
	*
	*/
 
	function week($year = '', $month = '', $day = '', $data = '', $base_url ='', $min_hour = 8, $max_hour=24)
	{
		$str = '';
 
		$today = 0;
 
		if($year == '' || $month == '') { // just use current yeear &amp; month
			$year = date('Y');
			$month = date('F');
			$day = date('d');
			$month_num = date('m');
		}
 
		$flag = 0;
 
		for($i = 0; $i &lt; 12; $i++) {
			if(strtolower($month) == $this-&gt;month_list[$i]) {
				if(intval($year) != 0) {
					$flag = 1;
					$month_num = $i + 1;
					break;
				}
			}
		}
 
		if ($flag == 1)
		{
			$days_in_month = date("t", mktime(0, 0, 0, $month_num, 1, $year));		
			if ($day &lt; = 0 || $day &gt; $days_in_month) 
				$flag = 0;
		}
 
		if($flag == 0) {
			$year = date('Y');
			$month = date('F');
			$month_num = date('m');
			$day = date('d');
			$days_in_month = date("t", mktime(0, 0, 0, $month_num, 1, $year));		
		}
 
		$next_year = $year;
		$prev_year = $year;
 
		$next_month = intval($month_num);
		$prev_month = intval($month_num);
 
		$next_week = intval($day) + 7;
		$prev_week = intval($day) - 7;
 
		if ($next_week &gt; $days_in_month)
		{
			$next_week = $next_week - $days_in_month;
			$next_month++;
		}
 
		if ($prev_week &lt; = 0) 
		{
			$prev_month--;
			$prev_week = date('t', mktime(0,0,0, $prev_month,$year)) + $prev_week;
		}
 
		$next_month_num = null;
		if($next_month == 13) {
			$next_month_num = 1;
			$next_month = 'january';
			$next_year = intval($year) + 1;
		} else {
			$next_month_num = $next_month;
			$next_month = $this-&gt;month_list[$next_month -1];
		}
 
		$prev_month_num = null;
		if($prev_month == 0) {
			$prev_month_num = 12;
			$prev_month = 'december';
			$prev_year = intval($year) - 1;
		} else {
			$prev_month_num = $prev_month;
			$prev_month = $this-&gt;month_list[$prev_month - 1];
		}
 
		if($year == date('Y') &amp;&amp; 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');
		}
 
		//count back day until its monday
		while ( date('D', mktime(0,0,0, $month_num, $day, $year)) != 'Mon')
			$day--;	
 
		$title = '';
		if ($day+6&gt;$days_in_month)
		{
			if ($next_month == 'january')
				$title = ucfirst($month).' '.$year.' / '.ucfirst($next_month).' '. ($year+1);
			else
				$title = ucfirst($month).'/'.ucfirst($next_month).' '.$year;
 
		} else 
			$title = ucfirst($month).' '.$year;
 
		$str .= '

‘; $str .= ”; $str .= ”; $str .= ”; for($i = 0; $i < 7;$i++) { $offset = 0; if ($day+$i > $days_in_month) $offset = $days_in_month; else if ($day+$i < 1) $offset = – date(‘t’,mktime(1,1,1,$prev_month_num,1,$prev_year)); $str .= ”; $str .= ”; $str .= ”; for($hour=$min_hour;$hour < $max_hour;$hour++) { $str .= ”; for($i = 0; $i < 7; $i ++) { $offset = 0; if ($day+$i > $days_in_month) $offset = $days_in_month; else if ($day+$i < 1) $offset = – date(‘t’,mktime(1,1,1,$prev_month_num,1,$prev_year)); $cell = ”; $onClick=”; $style =”; $class = ”; if($i > 4) { $class = ‘ class=”cell-weekend” ‘; } if(($day+$i) == $today && $month_num == date(‘m’) && $year == date(‘Y’)) { $class = ‘ class=”cell-today” ‘; } if(isset($data[$day+$i-$offset][$hour])) { if (is_array($data[$day+$i-$offset][$hour])) { if (isset($data[$day+$i-$offset][$hour][‘onClick’])) { $onClick = ‘ onClick=”‘.$data[$day+$i-$offset][$hour][‘onClick’].'”‘; $style= ‘ style=”cursor:pointer;”‘; } if (isset($data[$day+$i-$offset][$hour][‘content’])) $cell = $data[$day+$i-$offset][$hour][‘content’]; if (isset($data[$day+$i-$offset][$hour][‘class’])) $class = ‘ class=”‘.$data[$day+$i-$offset][$hour][‘class’].'”‘; } else $cell = $data[$day+$i-$offset][$hour]; } $str .= ‘<td ‘.$class.$onClick.$style.’ id=”cell-‘.($day+$i-$offset).’-‘.$hour.'”>’ . $hour.’:00′ . ” . $cell . ”; } $str .= ”; } $str .= ”; $str .= ‘

‘; $str .= $this->Html->link(__(‘prev’, true), $base_url.’/’ . $prev_year . ‘/’ . $prev_month.’/’.$prev_week.$this->getParams()); $str .= ‘ ‘ . $title . ‘ ‘; $str .= $this->Html->link(__(‘next’, true), $base_url.’/’ . $next_year . ‘/’ . $next_month.’/’.$next_week.$this->getParams()); $str .= ‘
‘ . $this->day_list[$i] . ‘
‘.($day+$i-$offset).”; } $str .= ‘
';
 
		return $str;
	}	
}	
 
?&gt;

14 thoughts on “CakePHP Calendar Helper

  1. Peter

    Hi!
    Thank’s a lot but it’s really difficult to copy/past your helper…
    =)

    Reply
  2. cypher Post author

    Peter,

    I’ve updated the source above (WordPress likes to encode html entities, thus messing up the code. There is also a link above the source that lets you download the raw code.

    Thanks and good luck!

    Reply
  3. aaarrrggh

    Hi,

    Looks good so far, but I’m pretty sure (correct me if I’m wrong) that we also need an updated controller to be able to use this properly? The current controller does not send the $data array in the correct format.

    I’ve got this up and running on my own system at the moment, but I’m reworking it to work for multiple users in a system (by that I mean that each user has access to their own individual calendar, as well as having a global calendar available to all users).

    I’m going to rewrite the controller myself.

    If possible, would you be able to upload your own updated controller? I’d find it useful to look at, and I’m sure others would too.

    If for some reason an updated controller is not actually necessary, my apologies!

    Reply
  4. Sebastian

    Hi!

    I want to know if I can use the original files (from the original project) and edit only the CalendarHelper with yours?!

    Excuse my english, please.

    Thanks,
    Sebastian

    Reply
  5. cypher Post author

    aaarrrggh: I do have a very simple component that I’ve written, but all that it does is sanitize a requested date before passing it to the helper. To get the information into the necessary array format, just loop through your event types (or whatever you want to put on the calendar), and then insert it into the array according to the date/time of the event.

    I suppose if you wanted, you could write a component that has an “insert” event type that does this for you, but I figured that it was simple enough that I could just leave it in the controller.

    Reply
  6. cypher Post author

    Sebastian: Yes, that is exactly the purpose of my code. It essentially takes the original and just updates it to add the week formatted calendar.

    So to use it as I’ve described, you would download the original (linked above) and then replace just the helper class with mine.

    Reply
  7. aaarrrggh

    Hi Cypher.

    I’ve got it working now. I’ve actually got this working so that both the original monthly calendar and the daily break down (using your helper) can work on the same view page. It seems quite nice.

    One issue I do have though is that it would be nice to be able to display more than one event per hour on the daily view. As I understand it this is not currently possible. I’ve been working on something else for the last few days, but I intend to go back through this code and modify it to enable multiple events to take place each hour, possibly breaking down hours with more than one event into 10 or 20 minute blocks, or something like that.

    Is there a way of creating more than one event per hour currently, or will this need a bit of re-writing?

    Thanks for this helper by the way 🙂

    Reply
  8. cypher Post author

    aaarrrggh: I thought about multiple events when I was writing this, but what I decided was that it is simple enough to do multiple events by just listing them all in the same ‘content’ field that is sent to the helper.

    You just have to be careful to maintain the separation between controller and view correctly. The way I’ve done this is in my projects is to change the data array from the controller slightly for your events so that you send an array of events for each day.

    For example: $data[$day][$hour] = array(‘event1′,’event2’,’event3’…);

    And then in your view, you can loop through the events and build the content string with the array of events (your events also don’t have to be strings any more, you could make them models and build your event view from them).

    $calendar[$day][$hour][‘content’] =”;
    foreach ($data[$day][$hour] as $event)
    $calendar[$day][$hour][‘content’].='<div class=”event”>’.$event[‘Event’][‘name’].'</div>’;

    This also gives you a bit more flexibility on how it looks and behaves, because you can put in links, javascript, ajax, whatever…

    Mark

    Reply
  9. mjohnny

    Hi, thanks a lot. I tried it this morning and it did exactly what I had in mind.

    One problem though, on the Month Calendar, when I click on the prev link, it gives me the correct URL but the calendar will still display the current month.

    For example, today’s April. When I click on prev, it will display the calendar for March, when I click prev again, it will go back to April. I also tried moving my pc clock to June and still when I get to March and press the prev link, it will display June. No problems with browsing succeeding months.

    mJohnny

    Reply
  10. mjohnny

    my apologies.

    I just copied the controller found in the original helpers link. I browsed through it and found a typo on the February month. it’s now working perfectly.

    mJohnny

    Reply
  11. albert

    Hello,

    Hi, thank you for this helper. I tried it today and got the week calendar to display but the prev and next function does not work. The right url for the new week and previous week are ok but it still shows the same week in stead of going backward of forward one week. Could some share the working app directory?

    Any help provided is appreciated.

    Albert

    Reply
  12. albert

    Hello,

    Got the links to work. I need to pass the day variable to the week function in the calander helper.

    Thank you of this nice helper.

    Albert

    Reply
  13. cypher Post author

    Hi Albert,

    I’m glad that you were able to figure it out. I’m on vacation currently, hence my delay in response.

    Thanks for your interest and I hope the helper is useful.

    Mark

    Reply
  14. Akif

    Nice helper, it works great.

    I had problems getting it working properly at first. Seems that you need to pass the month as a word instead of a number. So ‘april’ will work, ‘4’ won’t.

    Keep up the good work!

    Reply

Leave a Reply

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