Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the right way to do layout updates with CMS pages?

Tags:

layout

magento

I have a featured products module I wrote that puts a custom block on the page with a list of products that match whatever attribute(s) I've defined in the block. Originally I had it working by adding a {{block...}} line to the Content section of the CMS page. This worked fine, but I wasn't getting the pager. So, I fixed that by removing the {{block...}} line from the Content section and adding XML to the Layout Update XML section like so:

<reference name="content">
    <block type="cms/block" name="product_list_top" />
    <block type="vps_featured/list" name="vps_featured_list" template="catalog/product/sale_list.phtml">
        <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml">
            <block type="page/html_pager" name="product_list_toolbar_pager"/>
        </block>
        <action method="setToolbarBlockName"><name>product_list_toolbar</name></action>
        <action method="setAttributeName"><name>my_attribute</name></action>
    </block>
</reference>

This also worked great. I then decided, since there are so many instances of this block, that it would be much cleaner if I added an XML file to the layout folder for my theme and put this code in there. Then, in the Layout Update section, I could simply have this instead:

<reference name="vps_featured_list">
    <action method="setAttributeName"><name>other_attribute</name></action>
</reference>

So, I created a file called vps_featured.xml and added this to it:

<layout version="0.1.0">
    <default>
        <reference name="content">
            <block type="cms/block" name="product_list_top" />
            <block type="vps_featured/list" name="vps_featured_list" template="catalog/product/sale_list.phtml">
                <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml">
                    <block type="page/html_pager" name="product_list_toolbar_pager" />
                </block>
                <action method="setToolbarBlockName"><name>product_list_toolbar</name></action>
            </block>
        </reference>
    </default>
</layout>

This layout update XML file was referenced in the config.xml for my custom featured module. I naively assumed that vps_featured.xml would only get incorporated into the page layout when my VPS Featured block was on the page, which would only happen in these few instances on special CMS pages. Apparently that is not the case. This broke every other page, I guess because it was overriding the default handle.

So this leads me to question #1: When are the layout xml files included in the page layout? Are they used for ALL pages, regardless of whether or not the module referencing them is actually used?

I then decided to try adding a new layout handle that I could reference when I want in my CMS pages. I modified my layout XML file so the main part is within <vps_featured_list> tags rather than the <default> tags. This brought the other pages back to life, but of course, my CMS pages no longer worked because that layout update handle isn't being used on those pages. I tried adding <update handle="vps_featured_list" /> to the Layout Update XML section of the CMS page, but that didn't work (I didn't expect it to).

So this brings me to question #2: What's the correct way to accomplish this?

This doesn't sound like it should be this difficult, but clearly I'm missing something. I've read all I can find on layouts but they are always simple examples, like "add this to EVERY product page". I don't want something added to EVERY cms page...just a few of them. Am I stuck using the Layout Update XML section for the CMS pages in question? I feel like there has to be a cleaner way.

Thanks,

Brian

like image 896
Mageician Avatar asked Apr 26 '11 14:04

Mageician


2 Answers

The node <default/> is something called a "layout handle". Every request into Magent generates a number of these handles. Checkout the Layout tab on the Commerce Bug demo site to get an idea of the sorts of handles that get generated.

Magento merges all layout XML files into one giant tree called the package layout. Then, handles determine which Layout XML Update fragments are used for a particular request. These fragments are combined into the Page Layout. As you intuited, the <default/> handle is always added, which is why you're borking every site in your system.

The bad news is while there's a handle for CMS pages, (<cms_page_view />), it would still be an all or nothing affair. You could add something to all CMS pages, but not specific CMS pages. So yes, the Layout Update XML section of the CMS admin is "the right" way to do what you want.

The good news is there's two approaches you can take that are cleaner than what you've come up with so far. First, you can clean things up a little bit by defining your own handle in your XML file, and then use a special Layout XML command to include that handle.

In your XML, add your Layout Update XML fragment inside a custom handle.

<layout version="0.1.0">  <my_fancy_pants_unique_handle_name_which_doesnt_conflict_with_existing_names>
        <reference name="content">
            <block type="cms/block" name="product_list_top" />
            <block type="vps_featured/list" name="vps_featured_list" template="catalog/product/sale_list.phtml">
                <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml">
                    <block type="page/html_pager" name="product_list_toolbar_pager" />
                </block>
                <action method="setToolbarBlockName"><name>product_list_toolbar</name></action>
            </block>
        </reference>
    </my_fancy_pants_unique_handle_name_which_doesnt_conflict_with_existing_names>
</layout>

Then in the CMS Admin, add the following Layout Update XML for the pages you want

<update handle="my_fancy_pants_unique_handle_name_which_doesnt_conflict_with_existing_names" />

Magento allows Layout Update XML fragments to add additional handles to the request. That's what you're doing above. This cleans things up a bit, and if you wanted to change some of the XML, there's a centralized place for it.

Update: It turns out that that the <update/> command can only be used from the Layout XML files themselves. The code that loads the Layout Update XML for CMS pages isn't the same code that loads and processes updates for the XML files. Magento will literally add anything in the CMS form as a Layout update. It won't processes it for recursive updates. So, adding something like this to local.xml would work

<mock_product_list>
    <reference name="head">
    <label>Mock Product List</label>        
                <action method="addItem"><type>skin_js</type><name>js/sw/wall.js</name></action>
    </reference>    
</mock_product_list>

<cms_page_view>
    <update handle="mock_product_list" />
</cms_page_view>

But you can't do that directly with an update applied via the CMS admin. Live and learn.

All that said, the widget approach is probably still viable, and an alternative to the above would be.

  1. Create a custom module with a custom block class

  2. The block class renders no HTML

  3. The block class contains a single method named something like "addFooToLayout"

Then, in your CMS page, add an update like this

<block type="yourmodule/yourblock" name="unique_name" alias="unique_name">
    <action method="addFooToLayout" />
</block>

and then in your block definition

public addFooToLayout()
{
    $layout = Mage::getSingleton('core/layout');
    $head   = $layout->getBlock('head');
    $head->addItem('skin_js','js/sw/wall.js');
    //etc...
}

Something like that should work and is a little clearer (depending on how much Layout Update XML you want to add). However, I haven't tested it and my credibility's blown on this one so buyer beware.

The second approach is to create a single widget that encompasses what you'd like to add to the page, and then use the Widget Instance admin to add that widget to specific blocks on specific pages. That links should get you started.

Finally, at the risk of being a shill, I've recently self-published a book on Magento Layouts that covers these sorts of issues in depth. It's worth your time if you're interested in understanding how the entire system works.

like image 134
Alan Storm Avatar answered Oct 19 '22 12:10

Alan Storm


Nevertheless this question is quite old already, I found it through google and would like to add some information for those who will read this post in future.

Yesterday when I read this question and answer, and also this and this posts, I became quite sure that it is not possible or quite hard to override/extend the CMS page layout handle only for specific pages. That led me to developing one small extension, which injects any custom layout handle in any specific page. I will update this post with a public link to the extension soon.

But then I discovered that it is actually possible to just create a new template for CMS page and assign any custom layout handle to that template, see this question and answer from Marius for example. Here is the important bit of that code sample:

<global>
    <page>
        <layouts> 
            <lookbook module="page" translate="label">
                <label>Lookbook</label>
                <template>page/1column-lookbook.phtml</template>
                <layout_handle>lookbook</layout_handle>
            </lookbook> 
        </layouts>
    </page>
</global>

It is quite easy to inject any custom layout handle to any page and do whatever you want only with pages which use this template and your custom layout handle. I wish I knew that before, hope this post will save someone's time.

like image 27
Anton Boritskiy Avatar answered Oct 19 '22 13:10

Anton Boritskiy