History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: ZF-3282
Type: Bug Bug
Status: Resolved Resolved
Resolution: Won't Fix
Priority: Major Major
Assignee: Matthew Weier O'Phinney
Reporter: Bart Dens
Votes: 3
Watchers: 5
Operations

If you were logged in you would be able to see more operations.
Google issue summary
Zend Framework

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

Created: 17/May/08 01:41 AM   Updated: 20/Aug/08 11:13 AM
Component/s: Zend_View, Zend_Layout
Affects Version/s: 1.5.0
Fix Version/s: None

Time Tracking:
Not Specified

Tags:
Participants: Andries Seutens, Bart Dens, Jeremy Brown, Matthew Weier O'Phinney and Mike Coakley


 Description  « Hide
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



 All   Comments   Work Log   Change History   FishEye   Crucible      Sort Order: Ascending order - Click to sort in descending order
Bart Dens - 17/May/08 01:56 AM
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...)


Andries Seutens - 19/May/08 01:19 AM
added code markup

Mike Coakley - 20/May/08 12:19 PM
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


Jeremy Brown - 20/Aug/08 10:53 AM
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.

Matthew Weier O'Phinney - 20/Aug/08 11:13 AM
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.