5 Useful Tricks For Your Magento local.xml
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.
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 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.
Working with 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.
A general 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
One of the things I find doing quite often is adding and removing JavaScript and CSS includes. For example, some third party extensions install their own CSS with, maybe a few lines of styling. I prefer to move this into the main stylesheet and just not include the original. Similarly, sometimes I don’t want some JavaScript included on all or some pages. Also, if I develop some of my own JS, I need to include it. Clearly, for functionality you develop via a custom module, you may have already created a module specific layout XML file that will handle your custom includes, but if it’s a theme specific JS feature, you may want to be able to include it in your theme’s layout XML.
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 removeItem element targets a JavaScript file that was included by some extension under the Magento js folder rather than the theme folder.
2. Replacing a local jQuery include with one from the Google CDN
We saw how we can easily include/exclude JavaScript files in the previous section. We removed a locally included jQuery library with the goal to replace it with the same library file loaded from the Google CDN. This will hopefully improve our site’s performance a little.
<
default
>
<
reference
name
=
"head"
>
<
block
type
=
"core/text"
name
=
"google.cdn.jquery"
>
<
action
method
=
"setText"
>
<
text
>
<![CDATA[<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script><script type="text/javascript">jQuery.noConflict();</script>]]>
</
text
>
</
action
>
</
block
>
</
reference
>
</
default
>
In case you are wondering what’s going on here, closer inspection of the original included jQuery file showed that the jQuery.noConflict() call was added at the end of the file. This is required in order to avoid jQuery conflicting with the Magento built in Prototype library. Additionally, since the JavaScript file is being included from an external source, we can’t use 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_out> and <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: <action method="unsetChild">
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
>
Use 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.
Conclusion
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 local.xml.
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. For any other questions, feel free to reach me out: http://bit.ly/1YyxOCS
Comments
Post a Comment