Zend Framework

headscript and headLink used in a view helper in a layout don't work

Details

  • Type: Bug Bug
  • Status: Resolved Resolved
  • Priority: Major Major
  • Resolution: Won't Fix
  • Affects Version/s: 1.5.0
  • Fix Version/s: None
  • Component/s: Zend_Layout, Zend_View
  • Labels:
    None

Description

Suppose a view helper like this:

class My_View_Helper_Glow
{

    protected $view = null;
    protected $cnt  = 1;
    
    /**
     * Set the view so it can be used in the helper
     *
     * @param Zend_View $view
     */
    public function setView(Zend_View_Interface $view)
    {
        $this->view = $view;
        $this->_attachHeadScripts();
    }

    /**
     * Attaches the needed scripts in the html header
     *
     */
    private function _attachHeadScripts()
    {
        $this->view->headScript()->appendFile('/scripts/swfobject.js');
        $this->view->headScript()->appendFile('/scripts/sIFR/sifr.js');
        $this->view->headLink()->appendStylesheet('/scripts/sIFR/sIFR-screen.css');
    }
    
    public function glow($text, $align = 'center', $case = 'upper', $bgcolor = null, $color = '#c4dce2')
    {
        $cnt = $this->cnt++;
        $id = 'sifr_glow_' . $cnt;
        
        $this->view->inlineScript()->appendScript('
            if(typeof sIFR == "function"){    
                sIFR.replaceElement(
                    named({
                        sSelector   : "span#' . $id . '" 
                        ,sFlashSrc  : "/scripts/fonts/helvetica_blue_glow.swf"
                        ,sColor     : "' . $color . '"
                        ' . ($bgcolor? ',sBgColor:"' . $bgcolor. '"' : ',sWmode:"transparent"') . '
                        ,sCase      : "' . $case . '"
                        ,sFlashVars : "textalign=' . $align . '"
                    })
                );    
            };
        ');
         
        $return = '
            <span class="sifr" id="' . $id . '">' . $text . '</span>
        ';
        
        return $return;
    }
    
}

So we add headScript and headLink items through a View Helper.

Now when this view helper is used in a layout file, than the scripts will not become attached, while they will if used in a controller action view script.

Test scenario (failing)
LAYOUT:

<?= $this->doctype('XHTML1_STRICT') ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
<?= $this->headScript() ?>
<?= $this->headLink() ?>
<?= $this->headStyle() ?>
</head>
<body>
    <?= $this->layout()->content ?>
    <h2> layout content </h2>
    <?= $this->glow('My Account') ?>
    <?= $this->inlineScript() ?>    
</body>
</html>

VIEW SCRIPT:
<h2> action content </h2>

Test scenario (working):
LAYOUT:

<?= $this->doctype('XHTML1_STRICT') ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
<?= $this->headScript() ?>
<?= $this->headLink() ?>
<?= $this->headStyle() ?>
</head>
<body>
    <?= $this->layout()->content ?>
    <h2> layout content </h2>
    <?= $this->glow('My Account') ?>
    <?= $this->inlineScript() ?>    
</body>
</html>

VIEW SCRIPT:

<h2> action content </h2>
<?= $this->glow('My Account') ?>

Neither is it possible to load headscripts or headlinks appended by a view helper called in a partial, while this kinda defeats the purpose of 'on demand loading' of files

Activity

Hide
Bart Dens added a comment -

Funny thing is, this DOES work:
<?= $this->doctype('XHTML1_STRICT') ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?= $this->glow('My Account') ?>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
<?= $this->headScript() ?>
<?= $this->headLink() ?>
<?= $this->headStyle() ?>
</head>
<body>
<?= $this->layout()->content ?>
<h2> layout content </h2>
<?= $this->inlineScript() ?>
</body>
</html>

Meaning if you append script before calling <?= $this->headScript() ?>, they will be appended. Again, this defeats the purpose (as you're not going to write the html contents in your HEAD section off course...)

Show
Bart Dens added a comment - Funny thing is, this DOES work: <?= $this->doctype('XHTML1_STRICT') ?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <?= $this->glow('My Account') ?> <?= $this->headTitle() ?> <?= $this->headMeta() ?> <?= $this->headScript() ?> <?= $this->headLink() ?> <?= $this->headStyle() ?> </head> <body> <?= $this->layout()->content ?> <h2> layout content </h2> <?= $this->inlineScript() ?> </body> </html> Meaning if you append script before calling <?= $this->headScript() ?>, they will be appended. Again, this defeats the purpose (as you're not going to write the html contents in your HEAD section off course...)
Hide
Andries Seutens added a comment -

added code markup

Show
Andries Seutens added a comment - added code markup
Hide
Mike Coakley added a comment -

Bart,

I came across this as well. There really isn't a great way to deal with this directly as the calls to $this->headLink() are actual calls to PHP code which is going to obviously execute right then. Also, it is working as designed, simply the Layout changes the workflow for the overall content rendering. While there are MANY ways to program around this using the current system here is the way I've gotten around this in my code (of course this played into my overall method of building view scripts also):

In your Layout script create something like this:

<?= $this->doctype('XHTML1_STRICT') ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
{HEADSCRIPT}
{HEADLINK}
<?= $this->headStyle() ?>
</head>
<body>
    <?= $this->layout()->content ?>
    <h2> layout content </h2>
    <?= $this->glow('My Account') ?>
    <?= $this->inlineScript() ?>    
</body>
</html>

Then create a filter that looks for the {HEADSCRIPT} and {HEADLINK} tags and calls the appropriate view helpers to render the actual content. Since filters are run after the view is rendered you should be good to go. I will also note you should only register the filters with the view associated with the layout instance. Here is an example of how a filter will look:



class MyApp_Wiki_Filter_HeadLink implements Zend_Filter_Interface
{

protected $_view;

public function setView($view)
{ $this->_view = $view; }

public function filter($value) {
if (preg_match("/{HEADLINK}/i", $value, $match)) { $replaceValue = $this->_view->headLink(); $value = preg_replace("/" . preg_quote($match[0], "/") . "/", $replaceValue, $value); }
return $value;
}
}


Hope that helps.

Thanks,

Mike

Show
Mike Coakley added a comment - Bart, I came across this as well. There really isn't a great way to deal with this directly as the calls to $this->headLink() are actual calls to PHP code which is going to obviously execute right then. Also, it is working as designed, simply the Layout changes the workflow for the overall content rendering. While there are MANY ways to program around this using the current system here is the way I've gotten around this in my code (of course this played into my overall method of building view scripts also): In your Layout script create something like this:
<?= $this->doctype('XHTML1_STRICT') ?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?= $this->headTitle() ?>
<?= $this->headMeta() ?>
{HEADSCRIPT}
{HEADLINK}
<?= $this->headStyle() ?>
</head>
<body>
    <?= $this->layout()->content ?>
    <h2> layout content </h2>
    <?= $this->glow('My Account') ?>
    <?= $this->inlineScript() ?>    
</body>
</html>
Then create a filter that looks for the {HEADSCRIPT} and {HEADLINK} tags and calls the appropriate view helpers to render the actual content. Since filters are run after the view is rendered you should be good to go. I will also note you should only register the filters with the view associated with the layout instance. Here is an example of how a filter will look:

class MyApp_Wiki_Filter_HeadLink implements Zend_Filter_Interface { protected $_view; public function setView($view) { $this->_view = $view; } public function filter($value) { if (preg_match("/{HEADLINK}/i", $value, $match)) { $replaceValue = $this->_view->headLink(); $value = preg_replace("/" . preg_quote($match[0], "/") . "/", $replaceValue, $value); } return $value; } }

Hope that helps. Thanks, Mike
Hide
Jeremy Brown added a comment -

The same thing happens when using the Dojo View Helper ( $this->dojo() ) and/or $this->headScript()>captureStart() and $this>headScript()->captureEnd(). If you want to use Dojo functionality in your layout, this also becomes a problem.

Show
Jeremy Brown added a comment - The same thing happens when using the Dojo View Helper ( $this->dojo() ) and/or $this->headScript()>captureStart() and $this>headScript()->captureEnd(). If you want to use Dojo functionality in your layout, this also becomes a problem.
Hide
Matthew Weier O'Phinney added a comment -

I hate to say it, folks, but this is just the way things work, and it's a matter of concurrency.

When you echo the view helper, it takes the current object state and renders it to a string notation. What you are presenting will not work because you're rendering the view helper, and then expecting that later calls will affect what you've already rendered.

To get what you're looking for, we would need to compile templates, and have one template that is considered a master, and would still need to somehow indicate an order of operations for controls – i.e., you cannot echo a placeholder completely until all other updates are performed.

You can actually update the placeholders within the layout script... so long as you do so prior to rendering the placeholder. So, in Bart's original example, if he had captured the results of $this->glow() before the <head> section, and then rendered the captured content later, it would work:

<? $glow = $this->glow('My Account'); ?>
<?= $this->doctype() ?>
<html>
<head>
...
    <?= $this->headLink() ?>
    <?= $this->headScript() ?>
...
</head>
<body>
...
<?= $glow ?>
...
</body>
</html>

This technique will work for all placeholders that you wish to use directly within your layout scripts.

Show
Matthew Weier O'Phinney added a comment - I hate to say it, folks, but this is just the way things work, and it's a matter of concurrency. When you echo the view helper, it takes the current object state and renders it to a string notation. What you are presenting will not work because you're rendering the view helper, and then expecting that later calls will affect what you've already rendered. To get what you're looking for, we would need to compile templates, and have one template that is considered a master, and would still need to somehow indicate an order of operations for controls – i.e., you cannot echo a placeholder completely until all other updates are performed. You can actually update the placeholders within the layout script... so long as you do so prior to rendering the placeholder. So, in Bart's original example, if he had captured the results of $this->glow() before the <head> section, and then rendered the captured content later, it would work:
<? $glow = $this->glow('My Account'); ?>
<?= $this->doctype() ?>
<html>
<head>
...
    <?= $this->headLink() ?>
    <?= $this->headScript() ?>
...
</head>
<body>
...
<?= $glow ?>
...
</body>
</html>
This technique will work for all placeholders that you wish to use directly within your layout scripts.

People

Vote (3)
Watch (5)

Dates

  • Created:
    Updated:
    Resolved: