PHP Event Calendar - Design advice

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

PHP Event Calendar - Design advice

Post by Luke »

I am building a small Event Calendar for use with a Google Maps application I am working on... I need some advice on how I should go about drawing and populating the calendar. As of right now, all the markup of how to draw the table is hard-coded into the draw() method. Is there even a practical way around this? The biggest question is really how to go about populating this calendar with data received from a database... this is what it looks like right now:

Code: Select all

<?php
class MC2_Calendar{
	protected $week = array(
		'Sunday',
		'Monday',
		'Tuesday',
		'Wednesday',
		'Thursday',
		'Friday',
		'Saturday'
	);
	protected $month;
	public function __construct(MC2_Calendar_Month $month){
		$this->month = $month;
	}
	public function draw(){
	
		$output =  "<table border='1' cellspacing='0' cellpadding='0' class='MC2_Calendar' id='MC2_Calendar'>\n";
		$output .= " <tr>\n  <td colspan='" . count($this->week) . " class='MC2_Calendar_Header'><span class='MC2_Calendar_Title'>" . $this->month->fullName() . "</span> <span class='MC2_Calendar_Year'>" . $this->month->year() . "</span></td>\n </tr>\n <tr>\n";
		foreach($this->week as $day){
			$output .= "  <th class='MC2_Calendar_Heading'>" . $day . "</th>\n";
		}
		$output .= "</tr><tr>\n";
		
		$first_day = date('w', $this->month->date());
		for ($i=0; $i<$first_day; $i++){
			$output .= "<td>&nbsp;</td>\n";
		}
		
		for ($list_day=1; $list_day <= $this->month->totalDays(); $list_day++){
			$output .= "<td><span class='MC2_Calendar_day_number'>" . $list_day . "</span></td>\n";
			if ($first_day == 6){
				$output .= "</tr>\n";
				$output .= "<tr>\n";
				$first_day = -1;
			}
			$first_day++;
			$days_left = ($first_day) ? 7 - $first_day : 0;
			// Print out the week's remaining day's cells
			if($list_day == $this->month->totalDays()){
				for($i = 1; $i <= $days_left; $i++){
					$output .= "<td>&nbsp;</td>\n";
				}
			}
		}
		
		$output .= "</tr>\n";
		$output .= "</table>\n";
		echo $output;
		
	}
}
class MC2_Calendar_Month_Exception extends Exception{
}
class MC2_Calendar_Month{
	/**
	 * Numerical representation of day
	 * @integer
	 */
	//protected $day;
	/**
	 * Numerical representation of month
	 * @integer
	 */
	protected $month;
	/**
	 * Numerical representation of year
	 * @integer
	 */
	protected $year;
	/**
	 * Timestamp to extract date info from
	 * @integer
	 */
	protected $date;
	public function __construct($month, $year){
		//$this->day = (integer) $day;
		$this->month = (integer) $month;
		$this->year = (integer) $year;
		$this->setDate();
	}
	protected function setDate(){
 		$this->date = mktime(0,0,0,$this->month, 1, $this->year);
 	}
	public function totalDays(){
		$days = date('t', $this->date);
		return (integer) $days;
	}
	public function startDay(){
		return (string) date('l', $this->date);
	}
	public function fullName(){
		return (string) date('F', $this->date);
	}
	public function year(){
		return $this->year;
	}
	public function month(){
		return $this->month;
	}
	public function date(){
		return $this->date;
	}
}

$show_month = isset($_GET['month']) ? $_GET['month'] : date('n', time());
$show_year = isset($_GET['year']) ? $_GET['year'] : date('Y', time());

if(($show_month+1) > 12){
	$next_month = 1;
	$next_year = $show_year + 1;
}
else{
	$next_month = $show_month +1;
	$next_year = $show_year;
}

if(($show_month - 1) < 1){
	$prev_month = 12;
	$prev_year = $show_year - 1;
}
else{
	$prev_month = $show_month - 1;
	$prev_year = $show_year;
}

$links = "<a href='?month=" . $prev_month . "&year=" . $prev_year . "'>< Previous</a>&nbsp;&nbsp;&nbsp;&nbsp<a href='?month=" . $next_month . "&year=" . $next_year . "'>Next ></a>";
echo $links;
$month = new MC2_Calendar_Month($show_month, $show_year);
$calendar = new MC2_Calendar($month);
$calendar->draw();
Thanks for looking at it. I really appreciate it!
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

So your caveat is basically how to separate that display logic from the main class?

I tend to create Component View classes (since the calendar isn't going to be a full page I assume).

The idea is that when draw() is called the view class in instantiated and that view component can access some data structure in your main class (perhaps some sort of multi-dimensional array or matrix of some sort in this case?).

You can create the actual template as a file and use output buffering to get its contents once included so that PHP code in it can be executed:

Code: Select all

class Calendar
{
    // .... snip ...
    
    public function draw()
    {
        $view = new Calendar_View();
        $view->setCalendarModel($this);
        echo $view->render();
    }
    
    // ... snip ...
}

class Calendar_View
{
    protected $calendar;
    
    public function setCalendarModel($obj)
    {
        $this->calendar = $obj;
    }

    public function render()
    {
        ob_start();
        include 'an_example_calendar_template.php';
        return ob_get_clean();
    }
}
The template file is easy:

Code: Select all

<table>
<?php foreach ($this->calendar->getMonths() as $m): ?>
    //render something
<?php endforeach ?>
</table>
Of course, with a layout like this you could have all different styles that you can swap easily:

Code: Select all

abstract class Calendar_View
{
    protected $calendar;
    
    public function setCalendarModel(Calendar $obj)
    {
        $this->calendar = $obj;
    }

    abstract public function render();
}

class Calendar
{
    // .... snip ...
    
    public function draw(Calendar_View $view)
    {
        $view->setCalendarModel($this);
        echo $view->render();
    }
    
    // ... snip ...
}

Code: Select all

$calendar = new Calendar();
$calendar->draw(new DetailedCalendarView());

//or
$calendar->draw(new BasicCalendarView());
Have I gone off on a tangent? :P
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

nope... that's EXACTLY what I was looking for... inspiration!! Thanks man! :D
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Thanks d11... this works FANTASTICALLY!

Code: Select all

<?php
class MC2_Calendar{
	public $month;
	public $week = array(
		'Sunday',
		'Monday',
		'Tuesday',
		'Wednesday',
		'Thursday',
		'Friday',
		'Saturday'
	);
	public function __construct(MC2_Calendar_Month $month){
		$this->month = $month;
	}
	public function getGrid(){
	
		$calendarGrid = array();
		$week = 1;
		$first_day = date('w', $this->month->date());
		// Fill in any days before month starts (if month doesn't start on a sunday)
		for ($i=0; $i<$first_day; $i++){
			$calendarGrid[$week][] = "";
		}
		
		// Begin loop count out the days in the month
		$i = $day_of_week = $first_day;
		for($day_of_month = 1; $day_of_month <= $this->month->totalDays(); $day_of_month++){
			if($i % 7 == 0){
				// Start week over
				$day_of_week = 0;
				$week++;
			}
			$calendarGrid[$week][$day_of_week] = $day_of_month;
			$day_of_week++;
			$i++;
		}
		// Fill in the remaining days in the final week with blanks
		$final_week = $calendarGrid[$week];
		$final_week = array_pad($final_week, 7, "");
		$calendarGrid[$week] = $final_week;
		
		return $calendarGrid;
	}
	public function draw(MC2_Calendar_View $view){
		$view->setCalendarModel($this);
		echo $view->render();
	}
	public function capture(MC2_Calendar_View $view){
		$view->setCalendarModel($this);
		return $view->render();
	}
}
class MC2_Calendar_View_Default extends MC2_Calendar_View{
	public function render(){
		ob_start();
		include 'default.tpl.php';
		return ob_get_clean();
	}
}
abstract class MC2_Calendar_View{
	protected $calendar;
	public function setCalendarModel(MC2_Calendar $calendar){
		$this->calendar = $calendar;
	}
	abstract public function render();
}
class MC2_Calendar_Month_Exception extends Exception{
}
class MC2_Calendar_Month{
	/**
	 * Numerical representation of day
	 * @integer
	 */
	//protected $day;
	/**
	 * Numerical representation of month
	 * @integer
	 */
	protected $month;
	/**
	 * Numerical representation of year
	 * @integer
	 */
	protected $year;
	/**
	 * Timestamp to extract date info from
	 * @integer
	 */
	protected $date;
	public function __construct($month, $year){
		//$this->day = (integer) $day;
		$this->month = (integer) $month;
		$this->year = (integer) $year;
		$this->setDate();
	}
	protected function setDate(){
 		$this->date = mktime(0,0,0,$this->month, 1, $this->year);
 	}
	public function totalDays(){
		$days = date('t', $this->date);
		return (integer) $days;
	}
	public function startDay(){
		return (string) date('l', $this->date);
	}
	public function fullName(){
		return (string) date('F', $this->date);
	}
	public function year(){
		return $this->year;
	}
	public function month(){
		return $this->month;
	}
	public function date(){
		return $this->date;
	}
}

$show_month = isset($_GET['month']) ? $_GET['month'] : date('n', time());
$show_year = isset($_GET['year']) ? $_GET['year'] : date('Y', time());

if(($show_month+1) > 12){
	$next_month = 1;
	$next_year = $show_year + 1;
}
else{
	$next_month = $show_month +1;
	$next_year = $show_year;
}

if(($show_month - 1) < 1){
	$prev_month = 12;
	$prev_year = $show_year - 1;
}
else{
	$prev_month = $show_month - 1;
	$prev_year = $show_year;
}

$links = "<a href='?month=" . $prev_month . "&year=" . $prev_year . "'>< Previous</a>&nbsp;&nbsp;&nbsp;&nbsp<a href='?month=" . $next_month . "&year=" . $next_year . "'>Next ></a>";
echo $links;
$month = new MC2_Calendar_Month($show_month, $show_year);
$view = new MC2_Calendar_View_Default;
$calendar = new MC2_Calendar($month);
$calendar->draw($view);
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Glad it worked out for ya :)
Post Reply