6.0.0-git
2020-01-24

Diff for Project/HordeView between 3 and 4

[[toc]]

+ Horde_View

PHP 5 library targetted for Horde 4 that implements a view pattern.

++ Bugs



++ People

ChuckHagenbuch is the primary author of Horde_View.


++ Description

+++ Notes on views and templates

Some of this may be badly outdated.

http://blog.sig9.net/2008/01/21/template-engines/

I use the Two Step View pattern. My applications render content to the
response object (just the application content, none of the site
skeleton), and then in a dispatchLoopShutdown() plugin I pull the
response content and inject it into a sitewide template, and finally
inject that rendered content back into the response object. I've
provided a sample of such a plugin in the past on either the fw-mvc or
fw-general list.

http://paul-m-jones.com/blog/?p=247
http://framework.zend.com/wiki/display/ZFMLGEN/mail/27145
http://www.ingredients.com.au/nick/2006/06/10/getting-to-know-zend_view/
http://fosterburgess.com/kimsal/?p=237

Horde_View notes:
- http://benramsey.com/archives/zend-framework-view-notes/
- some way to automatically call views
- should I change from "script" to "template"?

To read:
[ZF rev 4332]:
    * Add optional ability to generate notices for undeclared variables in
      templates. Use $view->strictVars(true); to turn on functionality
    * Added Zend_View_Helper_DeclareVars helper. Allows developer to optionally
      declare template variables and default values. When used with
      strictVars(), will prevent notices by declaring unused variables in the
      view object

[ZF rev 4338]: Added getScriptPath($name), getHelperPath($name),
    getFilterPath($name), getHelper($name), getFilter($name), and getFilters()
    methods. Notes:
    * getHelper() and getFilter() will instantiate the filter of $name if not
      already instantiated
    * getHelpers() and getFilters() only return already instantiated helpers and
      filters; i.e., they won't return all helpers and filters available
    * getHelperPath() and getFilterPath() will instantiate the associated helper
      or filter in the process of discovering the path, if not already
      instantiated


View notes:

We're using some different "views" to get a useful layout for each purpose.

We start using:

- portal-layout.php
- clean-layout.php

The first one has the header, footer, top menu, the main content and optionally the left and right menus. The clean layout only has the header and footer.

So, in our scripts we can do the following (extending the Zend_View component):

$view = new TemplateView();
$view->tplMain  = 'articles/view.php';
$view->tplLeft  = 'articles/leftMenu.php';
$view->tplRight = 'articles/contextual.php';
$view->articles = $oArticles->get();
$view->render('portal-layout.php');

The "portal-layout.php" is constructed this way:

$this->render('header.php');
$this->render('topMenu.php');
if(isset($this->tplLeft))
    $this->render($this->tplLeft);
$this->render($this->tplMain);
if(isset($this->tplRight))
    $this->render($this->tplRight);
$this->render('footer.php');



ACLs and views

I'm not quite sure how I should use ACL with Views. I have a menu where I'd like items not to be visible if users role(s) have no access to it. So I was planning on doing array of the menu items and passing that to the view or passing the whole ACL object to view.

Definitely go for the array of menu items. A simple rule to follow is give the view simple data structures only. Otherwise, should your application code switch from Zend_Acl to something else, you would need to change every view involved.



One simple approach that i user is to set up a dir like:
/app/views/public/modules/

I have  view helper RenderModule:
<code>
        /**
         * renders a module:
         * moduleDir_moduleName
         *
         * @param string, module
         */
        public function RenderModule($module){
            $view = Zend_Registry::get('view');
            $config = Zend_Registry::get('config');
            $path = $config->view->publicModules;
            $currModule = str_replace('_','/',$module);
            return $view->render('./' . $path . '/' . $currModule . '.tpl.php');
        }
</code>

so for the show new news module:
dir: /app/views/public/modules/news/newNews.tpl.php

to render it (in the view):
echo $this->RenderModule('news_newNews');



Hi Padraic,

I'm currently playing with a portal-like behaviour. I implemented a number of Controller/View pairs as portlets that can be layouted in any way you want on a webpage.

I was initially not planning to mention this so early but to announce it later in this list when it is more mature, but anyway, this little add-on might be of help for you. There's a little demo on

http://www.rehno.de

which shows a simple and ugly wireframe layout (simple table layout) that are completely code-driven, i.e. you can adjust them dynamically during runtime. There are three demos on this page: a form example which shows how forms can be implemented as portlets and submitted/ evaluated independently of each other. The second example is a link example which shows how intra-portlet navigation can be implemented. The third example is a modification example which shows how one portlet might change the content of another area of the layout.

Please enable the Inplace debug output to get a better feeling about layouting and portlets within the layout cells.

If you like the demos and like to contribute or give feedback, feel free to contact me!

Regards,

Stephan



http://blog.astrumfutura.com/archives/282-Complex-Views-with-the-Zend-Framework-Part-2-View-Helper-Pattern.html



I currently use a programmatic description for the layout, it is stored into so-called containers which are built of frames which in turn might contain further (sub) containers or portlets.

It basically looks like this:

$container = new ContainerModel(ContainerModel::VERTICAL);
$container->addFrame("topmenu", "TopMenu", ContainerModel::MAXSIZE);

$subcontainer = new ContainerModel(ContainerModel::HORIZONTAL, $container);
$subcontainer->addFrame("mainspace", null, ContainerModel::MAXSIZE);
$container->addChild("contentspace", $subcontainer, ContainerModel::MAXSIZE);

As mentioned before, containers might be presented horizontally or vertically. The addFrame() command is passed the frame identifier, the portlet controller name (might be null) and the sizing.

The rendering is done using the container controller:

$controller = new ContainerController($container);
$controller->setRequestResponse($this->_request, $this->_response);

which is passed to the view. In the view.tpl.php file,

echo $this->containerController->render();

is called. Using the php DOM parser, I'm replacing all hyperlinks in the portlets' html output. That's about it :)

The implementation however is far from being complete. For example there is only a limited number of error checks implemented currently, also frame size is only supported in a limited why as I'm using tables for layouting. div sections are probably better.



Date: Thu, 19 Apr 2007 12:48:33 -0500
From: Dale McNeill <dale.mcneill@alchemysystems.com>

I use a dispatchLoopShutdown plugin.  This plugin is able to deal with both AJAX responses as well as a normal HTML response. Here's the code:

class SiteTemplatePlugin extends Zend_Controller_Plugin_Abstract {

    public function dispatchLoopShutdown()
    {
        // assume that we've already determined the request is ajax
        $request  = $this->getRequest();
        $response = $this->getResponse();
        $view     = Zend_Registry::get('view');

        if ($request->getParam('isAjax')) {
            // Ajax request detected
            // Get any variables set in the view
            $vars = get_object_vars($view);

            // Merge with named path segments in response
            $vars = array_merge($vars, $response->getBody(true));

            // Create a header and set the response body to a JSON value
            $response->setHeader('Content-Type', 'text/x-json');
            $response->setBody(Zend_Json::encode($vars));
            return;
        }

        // Otherwise, process as normal
        $view->assign($response->getBody(true));
        $response->setHeader('Content-Type', 'text/html');
        $response->setBody($view->render('site.php'));
    }
}

The site wide template that I use is composed of a header, 3 columns, and a footer.  The header and footer are the same for each page.  I assign the content for each column by appending the response object with a named section.  The named sections eventually become the view variables used in the site template.  Here's an example of a normal HTML response:

class IndexController extends Zend_Controller_Action {

    // Normal HTML response
    public function indexAction()
    {
        $response    = $this->getResponse();
        $view        = Zend_Registry::get('view');
        $view->title = 'My Index Title';
        $response->appendBody($view->render('index_css.php'), 'inline_css');
        $response->appendBody($view->render('index_left.php'), 'left_content');
        $response->appendBody($view->render('index_center.php'), 'center_content');
        $response->appendBody($view->render('index_right.php'), 'right_content');
    }

    // AJAX response
    public function viewAction()
    {
        $request = $this->getRequest();
        $request->setParam("isAjax", true);

        $view = Zend_Registry::get('view');
        $view->status  = 0;
        $view->message = "Problem encounted";
   }

}


Here's the site wide template:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><?php echo $this->escape($this->title); ?></title>
    <script src="/javascript/json.js"></script>
    <style type="text/css">
        <?php echo $this->inline_css; ?>
    </style>
</head>

<body>

    <div id="header">
       <div id="header">This is the header.</div>
    </div>

    <div id="container">

        <div id="center" class="column">
            <?php echo $this->center_content; ?>
        </div>

        <div id="left" class="column">
            <?php echo $this->left_content; ?>
        </div>

        <div id="right" class="column">
            <?php echo $this->right_content; ?>
        </div>

    </div>

    <div id="footer-wrapper">
        <div id="footer">This is the footer.</div>
    </div>
</body>
</html>



Date: Thu, 19 Apr 2007 13:30:27 -0400
From: Matthew Weier O'Phinney <matthew@zend.com>
To: fw-general@lists.zend.com


To implement Two Step View, I typically use a dispatchLoopShutdown() plugin, something like this:

    class Wopnet_Plugins_SiteTemplate extends Zend_Controller_Plugin_Abstract {

        /**
         * View script path
         * @var string
         */
        public $directory;

        /**
         * View script for sitewide template
         * @var string
         */
        public $file;

        /**
         * View object
         * @var Zend_View_Interface
         */
        public $view;

        /**
         * Constructor
         *
         * Get the script path to the sitewide view scripts, as well as the site 
         * template name. If not passed in the constructor, grab from the sitewide
         * configuration, suing the key $config->vars->path->app to determine the
         * script path for site templates, and $config->templates->site->filename
         * to determine the sitewide view script.
         * 
         * @param  string $file 
         * @param  string $directory 
         * @return void
         */
        public function __construct($file = null, $directory = null)
        {
            if (Zend_Registry::isRegistered('view')) {
                $this->view = Zend_Registry::get('view');
            } else {
                $this->view = new Zend_View();
            }

            $config = Zend_Registry::get('siteConfig');
            if ((null !== $directory) && is_dir($directory)) {
                $this->directory = $directory;
            } else {
                if (!isset($config->vars->path->app)) {
                    throw new Exception('No sitewide app path set');
                }
                $this->directory = $config->vars->path->app . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'scripts';
            }

            if (!is_dir($this->directory)) {
                throw new Exception('Sitewide view script path does not exist');
            }

            if (null !== $file) {
                $this->file = (string) $file;
            } else {
                if (!isset($config->templates->site->filename)) {
                    throw new Exception('Sitewide template not set in config');
                }
                $this->file = $config->templates->site->filename;
            }

            if (!file_exists($this->directory . DIRECTORY_SEPARATOR . $this->file)) {
                throw new Exception("Sitewide view script '{$this->directory}/{$this->file}' does not exist");
            }

            $this->view->addScriptPath($this->directory);
        }

        /**
         * Inject generated content into sitewide view script
         *
         * @param Zend_Controller_Request_Abstract $request
         * @return void
         */
        public function dispatchLoopShutdown()
        {
            $front    = Zend_Controller_Front::getInstance();
            $response = $front->getResponse();
            $body     = $response->getBody();

            $this->view->setScriptPath($this->directory);
            $this->view->assign(get_object_vars($response));
            $this->view->content = $body;
            $response->setBody($this->view->render($this->file));
        }
    }

Now, that's all fine and dandy, but it doesn't get the widgets in like you're discussing. For that, I've seen a few ideas. One is a Zend View Helper somebody posted to JIRA (and I think there's also a similar proposal in the wiki). In those, the helper basically does something like this:

    public function widget($action, $controller, $module, array $params = array())
    {
        $front   = Zend_Controller_Front::getInstance();
        $request = clone $front->getRequest();
        $request->setActionName($action)
                ->setControllerName($controller)
                ->setModuleName($module)
                ->setParams($params);
        $response = new Zend_Controller_Response_Http();
        $front->getDispatcher()->dispatch($request, $response);
        return $response->getBody();
    }

(this is rough; I haven't tested this at all). Then, you would call this in your views to grab the necessary components of your page:

    <?= $this->widget('items', 'list', 'blog', array('limit' => 10)) ?>

There's a little overhead, but not much. The con side to this is that your widgets don't get to tie into the dispatch loop, so if you were doing any ACL checks in a preDispatch() plugin, for instance, you'd lose those checks.

Theoretically, you could call $front->dispatch($request, $response), but this would overwrite the current request/response objects, and could lead to some unintended behaviour.


I wanted to post this now 1 week or so but i am very busy and didn't had time to write a
proper email. I don't have enough now :( but still trying to participate.

I already posted some time - in various forums/list - ago this problems raised by working
on big sites (with complex views). And problems are far more complicated, basically the
entire Controller, Action, View side of ZF is not designed for some "real" sites.

The best structure i used before ZF was this way:
1. One database (could be .xml based better) with page entires

2. Each page is having at View side an index, a layout, and multiple zones.

Index is the top structure, each application can have multiple indexes, but most have
just one. Samples: main index, index for a popup window which displays an image, aso.

An index has associated ONE layout which means the same "frame" (index) can on content
side have different structure. Each index has insertion point for layout.

Each layout has associated ONE OR MORE zone files, each zone would display content from a
diff data source. Each layout has insertion points for zones

Online shop sample:
- for an online shop the left side of the main layout can have a zone where on different
pages are displayed different things. But layout is the same, just zones to be bound are
different. Sample: main page having on that zone list of all new products; a product
details page having into zone list of all related products aso.

3. Each zone is having a controller which is responsible for preparing data for that zone

Steps for working with model:
a) The bootstrapper is identifying current page then is query-ing database of pages
requesting info about that page (index, layout, zones, title page, aso, basically any
custom info can be added).

b) All zone controllers are called so the all data is ready

c) Proper index is rendered, and on insertion point on index file is rendered layout for
that page. Again, at insertion points for zone on that layout are rendered proper zones
(specific for each page)

So, this model is far more appropriate for big sites and i worked with this model a lot.
I spent lot of time trying to reach something similar on ZF, and had to rewrite lot of
stuffs and is big mess... I simply think the way is made Controller, Action, View side of
ZF is not appropriate for working on some real sites.

Any developer who tries to jump with ZF in this situations is going to have a nightmare.
Personally i don't know why are needed Controllers and Actions in current format, aso.

I don't wanna sound like criticism, is just my experience working on some real -
production - stuff.


 I?m not happy with the current View implementation.  First I?ll layout my criticism, then suggest what I think may be an answer.  I realize this won?t make it into the next release or anything like that but, I would like to know what people think about it

Basically, what I want is for the View to be more like the Controller.  I want it beefier, meatier, able to stand on its own as part of the MVC.  The MVC needs more V.  As things stand now, the View has all but disappeared into the Controller?s Action helper.  It needs to be brought out and made a powerful component in its own right.

The viewRenderer should be a plug-in for the View, not the other way around.  The viewRenderer should be to the View, what the router is to the Front Controller.  People could then use a default viewRenderer or slot in a custom viewRenderer, just as they now can change routers.

The View should be able to host plug-ins like the Front Controller does.  The plug-ins could have methods like preRender, postRender to allow for such things as selecting skins for the page (preRender) or running the page through tidy (postRender).

The view should have helpers.  In particular, there are two helper classes worth considering.  View_Wrappers and View_Filters.   Wrappers add to, filters take away.  

Suppose you have a row from a database and you only want the 1st, 3rd and 5th . You might write:

$info =  filter(array(?1?,?3?,?5?),  $row);  

A wrapper might follow the decorator pattern and do something like this:

$list = wrap(?list?,$array);  // creates an html list out of an array

Wrappers can be chained together.

 There are lots of things to be puzzled out, but the above is the gist.

Implementation shouldn?t be too hard.  Much of the design for plug-ins and such can come from the Front Controller.   Instead of extending the view class, a new class named Zend_Alternative_View or something could be used.  This way you could use the old View while proto-typing the new Zend_Alternative_View.

So, what do you say?  Could it be ready for Zend 2.0? 


++ Resources




----
Back to the ((Projects|Project((Project|Project List))