View Registry

Derek Jones edited this page Jul 5, 2012 · 4 revisions
Clone this wiki locally

I like the idea of loosely coupled modular design. With a well-designed modular system, changes to code can be made without side effects. I like to carry the modular concept right down to the page layout and design. I am not a graphic designer and about the only thing I can draw are flies on a hot day! However, sometimes I am forced to create a project and then ship it off to a designer to have the page layout and design added.

Creating an environment that is easy for the designer to work in and that will still maintain the modular approach can be a challenge. I have found that CI’s templating system can help solve this problem. However, how do you force the designer to work in a modular fashion and still given him all the freedom needed to let his (or her) creative juices flow?

Back in the day, We often wrote our own windowing libraries on top of DOS (does anyone remember DOS). When Windows came on the scene (no they weren’t the first and they didn’t steal the idea from Mac either. They borrowed it from Zerox! Yes, the copy guys were playing with windows and mice not long after Billy wrote his Basic interpreter for the Altair 8800 (That reminds me, I need to replace an 74LS74 on my display panel. Maybe then I can see if I’m programming a ‘1’ or ‘0’ in byte 0x10….) What MS did do, is they did the Windows API right (at least at the time).

I have always liked the way Windows handled the creation of visual object. Basically, you simply define your window class. Register the class, and finally create the window. Ok! So that’s a bit simplistic. However, it really isn’t much more than that… Getting one window inside another is as simple as defining one window as a child of the other. Ok! So I’m taking a bit of a leap here! The fact is however, that creating views and sub-views should be as easy. It should be just as easy as creating a window with child windows in Windows (is that enough windows for ya?) In fact, in Code Igniter it can be even easier! Microsoft used a registry system to store it’s window classes. Once a class was registered, you could make as many windows of that class as you liked by simply calling createWindow() and handing each new instantiation of the class to a new variable.

So how do we make CI views windows friendly? How can we easily create our views and sub-views then display and still give our designers all the freedom they crave?

The simple answer is to create a view registry and allow our final view to be built up from our registered views. I haven’t taken the same route as Billy did but the outcome is similar.

I have noticed that in the C/C++ world extending classes is a natural part of layering functionality on top of existing portion of code. However, in the PHP world I see this done very rarely. Developing a class and then extending it with other classes to build a layered approach helps to subdivide the complexity of the final class. Now you can take this approach too far as well and end up with a layered mess that is difficult to unravel. That said, extending the CI base classes a time or two can really allow you to build amazing functionality on top of Ellis Labs beautiful creation. This is the approach I chose when I wanted to implement my view registry.

I have created a ViewRegistry Controller that extends the native CI Controller to provide a multiple view page rendering system. The system allows you to create a master template for the page layout and then create your sub-views and register them with the system. Once all views are registered, calling showView() will display the compiled view.

I have implemented this using the CI native templating class. You set template tags for your sub-views in the master view, and then supply the tag the sub-view should replace when registering the sub-view. The designer can then move his or her sub-views around by simply moving the template tags.

So if your master template looks like this:


<!Filename: master_view.php
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;My Test Page&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
<div id=header>
{header}
</div>

<div id=left>
{menu}
</div>

<div id=center>
{content}
</div>

<div id=right>
{weather}
<hr />
{news}
<hr />
{adds}
</div>

<div id=footer>
{footer}
</div>
&lt;/body&gt;
&lt;/html&gt;

And your content view looked like this:


<!Filename: content_view.php
<div>
<h2>Welcome to my test page</h2>
<p>This is my test content.</p>
</div>

Using the system is as easy as placing the ViewRegistry Controller in your application/controller folder. Create your new controller and extend the ViewRegistry Controller. Then set your master view template like this:


    $this->setMasterView(‘master_view’, $data);

Once the master view is defined, you will create your sub-views just as you would any other view in CI. However, you wont display your view. Instead you will register the view with the view system like this:


    $this->regView(‘content’, ‘content_view’, $view_data);

When you have all your sub-view created and registered, you can show them all contained in the master view like this:


    $this->showView();

Your controller would look something like this:


class MyTestPage extends ViewRegistry {

    Function MyTestPage()
{
    Parent:ViewRegistry();

    $this->setMasterView(‘master_view’);
}

Function index()
{
    $this->regView(‘content’, ‘content_view’);
}

}

I usually setup all views that will be used on every page (like menus, etc…) in the class constructor. All other class methods then only need to create and register the page dependent portions of the final view.

The ViewRegistry has a few other methods you can call. Look the code over and keep in mind we’re only sorting through a multi-multi dimensional array and you can easily follow the code.

I have found this class very helpful. I hope many of you do as well. Keep in mind that you can embed views inside views inside views… But the order in which you registered them must be from the bottom up. As I move to CI 1.6.x I will be rewriting this to be more object oriented and perhaps building some sort of look-ahead system might allow the view to be registered in any order. However, like CI, I aimed to keep this simple and light.

Enjoy!

Code Follows


&lt;?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* @file: ViewRegistry.php
*
* @desc: The ViewRegistry Controller extends the 
* CI native controller class to add embedded
* view functionality. The class works by allowing
* sub-view to be registered along with their associated
* view template tags and data. Each sub-view is generated
* independently of each other. Once all sub-views are completed
* they are then added to the main view with any undefined 
* template tags left in place. 
*
* Once the master template is processed unused template tags
* can be removed with the 'cleanView()' method if desired.
*
* @dependencies: 
*    Libraries: parser.
*    Helpers: url.
*
* @use:
* 
*  class MyController extends ViewRegistry {
*
*        function MyController()
*        {
*            parent::ViewRegistry();
*            //... Your code here...
*        }
*    }
*
*/

class ViewRegistry extends Controller {

    /**
    * The view registry is a simple associated array
    * formatted as: 
    *
    *    view_registry['view_tag'] = array('name' => view_name,
    *                                      'data' => data_array,
    *                                      'cache' => bool,
    *                                      'code' => view_code );
    */
    var $_view_registry = array();

    var $_master_view = "";

    var $_master_properties = array();

    var $_master_paths = array();

    var $_tag_delimiter = '{*}';

    /**
    * Constructor
    */
    function VeiwRegistry()
    {
        // call parent constructor
        if(get_parent_class($this) === 'Auth')
        {
            parent::Auth();
        }
        else
        {
            parent::Controller();
        }

        $this->load->library('parser');
        $this->load->helper('url');

        // Set default img, css, and js paths
        $master_paths = array('css_uri' => base_url().'css/',
                              'img_uri' => base_url().'images/',
                              'js_uri' => base_url().'js/',
                              'base_url' => base_url());

        $this->setMasterPaths($master_paths);

    }


    /**
     * Set Template tag delimiters
     */
    function setDelimiter($dim='{*}')
    {
        if(is_string($dim))
        {
            $this->_tag_delimiter = $dim;
        }    
    }


    /**
    * Allows for setting of css, js, and img 
    * default paths
    */
    function setMasterPaths($paths=null)
    {
        if(is_array($paths))
        {
            $this->_master_paths = $paths;
            return true;
        }
        return false;
    }


    /**
    * Set the Master Container View
    */
    function setMasterView($view_name=null,$prop=null)
    {
        if(!is_null($view_name))
        {
            $this->_master_view = $view_name;

            if(!is_null($prop))
            {
                $this->_master_properties = $prop;
            }

            return true;
        }

        return false;
    } 


    /**
    * Registers a view with the class
    */
    function regView($view_tag=null, $view_name=null, $data=null, $cache=false)
    {

        if($view_tag != null && $view_name != null)
        {
            if(is_null($data))
            {
                $data = array();
            }

            $this->_view_registry[$view_tag] = array('name' => $view_name,
                                                  'data' => $data,
                                                  'cache' => $cache,
                                                  'code' => '');

            if($cache === true)
            {
                $this->parseView($view_tag);
            }

            return true;
        }

        return false;
    }


    /**
    * Removes a view from the class
    */
    function unregView($view_tag=null)
    {
        // This could be an issue....
        // Some php version may unset the whole array!...
        unset($this->_view_registry[$view_tag]);
        //if so use
        // $this->_view_registry($view_tag] = array();
    }


    /**
    * Prints the view code to the page for review or debug
    */
    function printView($view_tag, $ret=false)
    {
        $view = $this->_view_registry[$view_tag];
        $out = $this->parser->parse($view['name'],$view['data'], true);

        $out = "<code> Code: ".$out."</code>";

        if($ret)
        {
            return $out;
        }
        else
        {
            echo $out;
        }
    }


    /**
    * Parse a view
    */
    function parseView($view_tag=null)
    {
        // has this view been set
        if(array_key_exists($view_tag,$this->_view_registry))
        {
            // Get the view properties
            $view = $this->_view_registry[$view_tag];

            // is this view cached, if not generate the view code
            if(($view['cache'] !== true) || ($this->_view_registry[$view_tag]['code'] == ''))
            {
                $code = $this->parser->parse($view['name'], $view['data'], true);
                $this->_view_registry[$view_tag]['code'] = $code;
                return $code;
            }

            return $this->_view_registry[$view_tag]['code'];    
        }

        return false;
    }


    /**
    * Parse all registered views
    */
    function parseAll()
    {
        $views = array_keys($this->_view_registry);

        foreach($views as $view)
        {
            $this->parseView($view);
        }
    }

    /**
    * NOT EMPLEMENTED
    * Cleans unused template tags from view
    */
    function cleanView($tag=null)
    {        
        return false;    
    }

    /**
    * Displays the view
    *
    * @param bool clean removes unused template tags if true
    *
    */
    function showView($clean=false)
    {
        $data = array();

        // Get a list of all subviews
        $views = array_keys($this->_view_registry);

        // Parse Each Subview
        foreach($views as $view_tag)
        {
            if(array_key_exists('code',$this->_view_registry[$view_tag]))
            {    
                // Skip if the view was previously parsed and cached
                if($this->_view_registry[$view_tag]['cache'] !== true)
                {
                    $this->parseView($view_tag);
                }
                $data[$view_tag] = $this->_view_registry[$view_tag]['code'];
            }
        }


        // Save subviews for use by master view              
        $data = array_merge($data, $this->_master_properties, $this->_master_paths);

        // Parse master view
        $this->_master_properties['master_view'] = $this->parser->parse($this->_master_view, $data, true);


        // Clean Master if requested
        if($clean === true)
        {
            $this->_master_properties['master_view'] = $this->cleanView($this->_master_properties['master_view']);
        }

        // Show Composit view
        echo $this->_master_properties['master_view'];
    }

}
?&gt;