Two step view with CodeIgniter

2 Responses August 5th, 2008 | Ekerete.

All the web development frameworks I have worked with have a full fledged layout system out of the box except CodeIgniter. A good number of CI tutorials encourage including common header and footer files to build the final page but I don’t like this approach for the following reasons:

  • It’s more difficult to swap layouts compared to using a single complete layout file.
  • You need to have a knowledge (admittedly small) of the server-side language.
  • A single complete layout file can be viewed in a WYSIWYG editor.

The layout system in the other frameworks I have worked with all use the two step view pattern and I have adapted CI to work the same way.

What is a two step view?

The idea behind using common header and footer files is to get a consistent look for your site or application and to make maintenance as painless as possible by providing a single place to make changes that affect the look and feel of the entire site or app.

Quoting Martin Fowler, the Two Step View pattern deals with this problem by splitting the transformation into two stages. The first transforms the model data into a logical presentation without any specific formatting; the second converts that logical presentation with the actual formatting needed. This way you can make a global change by altering the second stage, or you can support multiple output looks and feels with one second stage each.

How it works

Rather than extend the CodeIgniter controller class directly, the controller classes extend a custom base controller. This controller then extends the CI controller classes. The CI convention is to call this base controller class MY_Controller (default) and to place the class file in the libraries folder.

The base controller loads some default data and also has a render function which actually implements the two step view. I used the default Rails, Cake and Zend conventions of creating a folder named after the controller class and creating template files named after the actions. The render function does the first stage of the two step view pattern by checking if a file named after the action exists in folder named after a class. If it does, it loads it into a variable and then the second stage of the two step view pattern is implemented where this variable in addition to any other view data is injected into the layout file. So far, it’s worked great for me.

Some code, please

  • The layout file (application/views/layouts/main.tpl.php)
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title><?php echo $title; ?></title>
     
    <?php echo $css; ?>
     
    </head>
     
    <body>
        <div id="page"> 
     
            <div id="header">
                <h1><?php echo $title; ?></h1>
                <?php $this->load->view('partials/menu.tpl.php'); ?>
            </div>
     
            <div id="content">
                <h2><?php echo $heading; ?></h2>
                <?php echo $content; ?>            
            </div>
     
            <div id="footer">
                <p>&copy; 2008 AVNet Labs.</p>
            </div>
     
        </div>
    </body>
    </html>
  • The Base Controller (application/libraries/MY_Controller.php)
    <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
     
    class MY_Controller extends Controller {
     
    	protected $data = array();
    	protected $class_name;
    	protected $action;
    	protected $param;
     
    	public function __construct() {  
                    parent::__construct();
    		$this->load_defaults();
            }
     
    	protected function load_defaults() {
    		$this->data['heading'] = 'Page Heading';
    		$this->data['content'] = '';
    		$this->data['css'] = '';
    		$this->data['title'] = 'Page Title';
     
    		$this->class_name = strtolower(get_class($this));	
    		$this->action = $this->uri->segment(2, 'index');
     
    	}
     
    	protected function render($template='main') {
     
    		if (file_exists(APPPATH . 'views/' . $this->class_name . '/' . $this->action . '.tpl.php')) {
    			$this->data['content'] .= $this->load->view($this->class_name . '/' . $this->action . '.tpl.php', $this->data, true);
    		}
     
    		$this->load->view("layouts/$template.tpl.php", $this->data);
    	}
     
    	protected function add_css($filename) {
    		$this->data['css'] .= $this->load->view("partials/css.tpl.php", array('filename' => $filename), true);
    	}	
     
    }
    ?>
  • The Welcome Controller (application/controllers/welcome.php)
    <?php
     
    class Welcome extends MY_Controller {
     
    	function __construct() {
    		parent::__construct();
    		$this->add_css('main');	
    	}
     
    	function index() {
    		$this->data['heading'] = 'Home Page';
    		$this->render();
    	}
     
    	function edit() {
    		$this->data['heading'] = 'Edit Page';
    		$this->render();
    	}
    }
  • The Menu Partial Template (application/views/partials/menu.tpl.php)
    <ul>
     <li><a href="<?php echo site_url(); ?>">Home</a></li>
     <li><a href="<?php echo site_url('welcome/edit'); ?>">Edit</a></li>
    </ul>
  • The CSS Partial Template (application/views/partials/css.tpl.php)
    <link href="<?php echo base_url() . 'css/' . $filename . '.css'; ?>" rel="stylesheet" type="text/css" />
  • The Homepage Template (application/views/welcome/index.tpl.php)
    <p>Home page stuff goes here</p>
  • The Edit Page Template (application/views/welcome/edit.tpl.php)
    <p>Edit page stuff goes here</p>

2 Responses to “Two step view with CodeIgniter”

  1. Khoa Says:

    Hi Ekerete, I got this problem with CI framework about where some logical checking should be performed, which also results from the limitation of CI template system. Please let me explain (a bit long, sorry):

    I retrieve a list of items from database, each item has a category. I want to display all of them on the list (mix them all, sorted by create date). However, each of category has its own layout, for example, category A has image on the left, category B has image on the right, category C has an additional field to be shown, and so on.

    So, where should the checking “if (category == ‘A’)…else if (category == ‘B’)…else…” be included taking best MVC practice, easy maintenance and logic/presentation separation factor into account?

    1. Inside the model immidiately after retrieving data and before passing it to the controller?

    2. Inside the controller just before sending the list to view (which means I have to render all the category-tempalte at this point and pass all of them as variables)?

    3. Or inside the view? This means “raw” data will be sent to the view and the conditional statement will be written here. But this seems to “violate” the MVC model, and especially the CI template system does not allow us to use if statements inside views.

    Your article seems to address just this problem, however sorry for my php knowledge, I still cant get how to do this using your 2 step view process. Do you mind to explain a bit more about this? And if you can do this in my context, it will be very appreciated.

    Many thanks Ekerete.

    Khoa

  2. Ekerete Says:

    @Khoa,
    You will need to write a custom helper to do this cleanly.
    http://codeigniter.com/user_guide/general/helpers.html.

    This is not strictly MVC but an implementation of the View Helper Pattern.

    In your case, you would pass the array (or each item from the foreach loop - depending on your code) to the helper function and load different partial templates (or use conditionals in one partial template) depending on what category is being displayed.

    Hope that helps.

Leave a Reply