[cvs] [Wiki] created: Project/HordeView

Chuck Hagenbuch chuck at horde.org
Wed May 28 01:55:03 UTC 2008


chuck  Tue, 27 May 2008 21:55:03 -0400

Created page: http://wiki.horde.org/Project/HordeView

[[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 at 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 at zend.com>
To: fw-general at 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 List))


More information about the cvs mailing list