By Robert Popovic
We have covered a lot about Magento’s layout XML in our past 2 articles  . We saw that Magento will read the layout XML files in a specific order and that the
local.xml file is read at the very end of the sequence. This allows us to approach the implementation of new theme designs in a very modular and tidy way.
Most of the time, we would use one of the Magento supplied themes as a starting point for a new theme. The base default theme gives us all the standard theme features and implements the complete Magento layout and phtml files but the theme is almost completely unstyled. We can get a kick start from either the Magento default or modern themes,
or if you installed a third party Magento design.
A minimalist layout folder
So we end up with a setup that we can work with but, often, we don’t want to use all the features, rearrange some of the features we want to keep and, most likely, add some of or own.
We know that we can just copy the selected starting design into a new design package and start modifying all the layout XML and phtml files but what if the original theme is updated with the next release or the theme designer (if using a third party theme) releases an update? To reduce the need to retrofit updates, we can plan our theme modifications better by putting all our layout modifications into our own
local.xml file. As you can see from the example on the right, our layout folder is really minimalist and tidy. This way almost all of the original layout files are kept intact and, the added bonus is that you can see in one file what you have modified as opposed to hunting for your modifications in numerous layout files.
local.xml is really no different to modifying the existing layout files, once you realize that any Magento layout file can target any layout handle. This means that we are really not going to tell you anything new if you are already familiar with modifying layout XML. Nevertheless, for the novice, these tricks may be useful and we hope that the more advanced will be able to take away some ideas too.
local.xml file will have the standard layout root element under which all the other layout handles will be nested:
<?xml version="1.0" encoding="UTF-8"?> <!-- /** * local.xml * * Local layout modifications for our local theme * * @category design * @package my_theme_default * @copyright Copyright (c) 2011 Magebase. */ --> <layout version="0.1.0"> ... </layout>
1. Adding and removing scripts and stylesheets
To include an arbitrary file, you need to decide whether it is going to be included on every page of your site or just on some. This will determine the layout handle you need to specify.
We will work with the
<default> handle to include our example script
my_js.js and style
my_styles.css on every page. We’ll use the standard
addItem action method for adding files and target all this in the
"head" block reference since that’s where the files are included.
<default> <reference name="head"> <action method="addItem"> <type>skin_js</type> <name>js/my_js.js</name> <params/> </action> <action method="addItem"> <type>skin_css</type> <name>css/my_styles.css</name> </action> </reference> </default>
Similarly, to remove styles or scripts, use the
removeItem action method:
<default> <reference name="head"> <action method="removeItem"> <type>skin_css</type> <name>css/brandext/slider.css</name> </action> <action method="removeItem"> <type>js</type> <name>some_ext/jquery-1.4.2.js</name> </action> </reference> </default>
Note that the second
2. Replacing a local jQuery include with one from the Google CDN
In case you are wondering what’s going on here, closer inspection of the original included jQuery file showed that the
addItem action method.
To solve these problems in our replacement, we created a block of type
core/text and added our script tag as a text node. The
core/text block will automatically output the
CDATA content into the head of our page since the default
head.phtml has a call to: $this->getChildHtml().
3. Changing default block parameters
This somewhat cryptic section title refers to changing some default values in Magento template blocks such as the number of columns in the category grid or the number of upsell/crosssell products in their respective blocks.
Let’s start with changing the category grid:
<catalog_category_default> <reference name="product_list"> <action method="setColumnCount"> <count>3</count> </action> </reference> </catalog_category_default> <catalog_category_layered> <reference name="product_list"> <action method="setColumnCount"> <count>3</count> </action> </reference> </catalog_category_layered> <catalogsearch_result_index> <reference name="search_result_list"> <action method="setColumnCount"> <count>3</count> </action> </reference> </catalogsearch_result_index>
Note that we have to find all the pages that show a product grid and change the column count otherwise, they will use the Magento default values.
Changing the upsell grid on the product view, for example, is also very simple:
<catalog_product_view> <reference name="upsell_products"> <action method="setItemLimit"> <type>upsell</type> <limit>3</limit> </action> <action method="setColumnCount"> <columns>3</columns> </action> </reference> </catalog_product_view>
Essentially, we are taking advantage of the fact that local.xml is processed at the end and the values set here will override any values set in other XML layout files.
4. Doing different things for logged in vs. not logged in users
Magento provides us with two interesting layout handles:
<customer_logged_in>. We can be really creative in the way we can use these handles to affect our template depending on whether a customer is logged in or not. This also takes the logic out of the phtml files since we can include different content for the same layout block using this approach.
Let’s take the Magento built in stock alert functionality as an example. The default, if enabled, is to provide customers with a way to be alerted when a product comes back in stock by presenting the customer with a link, which when clicked, will add the logged in customer to a notification database. However, the usability of this feature suffers since when a customer is not logged in, they will be redirected to the standard Log In/Create an Account page. The wording on the notification link doesn’t indicate what a customer can expect when they click on it leading to potential frustration.
So, to improve usability, we want to display slightly different wording depending on whether the customer is already logged in or not. For this, we created a new phtml template block we are going to display instead of the default link. Since Magento will not show a Add to Cart button, we also want to show this block in the space of the button:
<catalog_product_view> <!-- remove the original alert URL block; we will put it back into the add to cart block below --> <reference name="alert.urls"> <action method="unsetChild"> <name>productalert.stock</name> </action> </reference> <!-- create our new block as a child block of the addtocart block --> <reference name="product.info.addtocart"> <!-- Block comes from the original stock alert block in productalert.xml --> <block type="productalert/product_view" name="productalert.stock" as="productalert_stock" template="magebase/productalert.phtml"> <action method="prepareStockAlertData"/> <action method="setHtmlClass"> <value>alert-stock link-stock-alert</value> </action> </block> </reference> </catalog_product_view>
We introduced our own phtml template in
template/magebase/productalert.phtml. It contains:
<p class="<?php echo $this->getHtmlClass() ?>"> <?php if ($this->getSignupDesc()) : ?> <span class="alert-description"><?php echo $this->getSignupDesc() ?></span> <?php endif; ?> <button type="button" title="<?php echo $this->escapeHtml($this->__($this->getSignupLabel())); ?>" class="button btn-alert" onclick="setLocation('<?php echo $this->escapeHtml($this->getSignupUrl()) ?>')"><span><span><?php echo $this->escapeHtml($this->__($this->getSignupText())); ?></span></span></button> </p>
We added some more descriptive elements here in addition to the link (we turned it into a button to make it a clear call to action) so the customer can be clear as to what will happen. So, to make this work for the logged out and logged in states, we just need to add the following to our XML:
<customer_logged_out> <reference name="product.info.addtocart"> <reference name="productalert.stock"> <action method="setSignupLabel" translate="value"> <value>Sign up to get notified when this product is back in stock</value> </action> <action method="setSignupText" translate="value"> <value>Sign Up For Alerts</value> </action> <action method="setSignupDesc" translate="value"> <value>Log in or create an account to be notified when this product is back in stock.</value> </action> </reference> </reference> </customer_logged_out> <customer_logged_in> <reference name="product.info.addtocart"> <reference name="productalert.stock"> <action method="setSignupLabel" translate="value"> <value>Click to get notified when this product is back in stock</value> </action> <action method="setSignupText" translate="value"> <value>Alert Me When In Stock</value> </action> <action method="setSignupDesc" translate="value"> <value>You can sign up to be notified when this product is back in stock.</value> </action> </reference> </reference> </customer_logged_in>
5. Removing, rearranging and replacing template blocks
Often we want to remove some blocks and replace them with our own modified ones as well as rearrange existing blocks.
There are two ways to remove blocks in layout XML:
- by using:
<remove name="" />
- by using:
It is important that we understand what the difference between the two methods is. Essentially, the main difference is that the two methods operate on different contexts.
<remove name="" /> will operate on a global context, after all the layouts are processed which means that it will remove the named layout block completely, regardless of which layout handle added it. By using this approach, you can’t remove a block from one place and then add it in another.
<action method="unsetChild"> operates on a localized context, specifically, in the context where you use it. So, if you want to remove a specific block from a specific layout handle and possibly insert it at another position or layout handle, you need to use this approach.
As you can see, the difference between the two removal methods will give you a clue as to how you can go about rearranging your template layout. Use
remove to get rid of unwanted blocks on a global level:
<default> <!-- remove the language and store switcher and footer links blocks, we won't use them --> <remove name="store_language" /> <remove name="store_switcher"/> <remove name="footer_links" /> </default>
unsetChild to move or rearrange blocks in your layout:
<default> <!-- move the breadcrumb block from the top.bar child block back to the template root (this overrides the modern theme breadcrumb positioning) --> <reference name="top.bar"> <action method="unsetChild"> <name>breadcrumbs</name> </action> </reference> <reference name="root"> <block type="page/html_breadcrumbs" name="breadcrumbs" as="breadcrumbs"/> </reference> </default>
This example will replace the default product tabs block from the modern theme with our own tabs:
<catalog_product_view> <reference name="product.info"> <action method="unsetChild"> <name>info_tabs</name> </action> <block type="catalog/product_view_tabs" name="product.info.tabs" as="info_tabs" template="catalog/product/view/tabs.phtml" > <action method="addTab" translate="title" module="catalog"> <alias>upsell_products</alias> <title>You may also like</title> <block>catalog/product_list_upsell</block> <template>catalog/product/list/upsell.phtml</template> </action> <action method="addTab" translate="title" module="catalog"> <alias>description</alias> <title>Details</title> <block>catalog/product_view_description</block> <template>catalog/product/view/description.phtml</template> </action> <!-- neat trick to include a CMS Static block directly in the tab --> <action method="addTab" translate="title" module="catalog"> <alias>shipping</alias> <title>Shipping Costs</title> <block>cms/block</block> <template>null</template> </action> <!-- define the CMS block ID for the shipping info tab --> <block type="cms/block" name="product.info.tabs.shipping" as="shipping"> <action method="setBlockId"><block_id>tab-product-shipping</block_id></action> </block> </block> </reference> </catalog_product_view>
Here, I also showed a nice little trick to include a static CMS block directly in the product tab all via layout XML only.
We saw how to utilize a
local.xml layout file to manipulate and change the layout of a Magento theme. This powerful approach keeps all your template layout modifications in one single file thus making your modifications easy to find and also ensure an easier upgrade path if and when there is a theme update.
We can change almost all layout aspects of the standard Magento layout however there are some situations when this approach fails. Notably, this manifests itself the minute you want to modify the top.links block. Items in this block are added using the
addLink action method so if you are wondering how to remove some links from the default set, the answer is, you can’t! Unfortunately, the
page/template_links block class doesn’t implement a
'removeLink'action method so the resort is to remove the whole block using the
unsetChild approach and add the links block back with our own links added to it in
Another scenario is if you want to remove some of the navigation links from the My Account navigation. The easiest approach is probably to override the layout XML files that add the unwanted links. That’s why my screen shot of the layout folder at the start of this tutorial has some other layout files in it.
If you have any interesting
local.xml tips and tricks, by all means share them in the comments section.