I'm having a very peculiar problem.
I created a module which has it's own layout xml, and in the correct handle has a
<reference name="product_list">
<action method="setTemplate"><template>rebates/product/list.phtml</template></action>
</reference>
to update to use the correct template.
I also rewrote the module to use my block class Company_Module_Block_List extends Mage_Catalog_Block_Product_List.
I am using Alan Storms commerecebug, so I know the block is being used. Also I looked at the page layout xml using ?showLayout and I see that the setTemplate method is being called.
When I call
public function _construct(){
parent::_construct();
echo $this->getTemplate();
}
in the Block, the correct template shows up, but if I create
protected function _toHtml(){
parent::_toHtml();
echo $this->getTemplate();
}
It reverts to the old template?
I ended up adding a
function _beforeToHtml(){
$this->setTemplate('my_template.phtml');
}
I don't like it, but it works, and the module is not dependent on other layouts working....
Related
I'm trying to pull in a system configuration value to a layout file. I figured the only way to do this was to create a function in my Data.php helper and then use the function in the layout xml. For some reason, it doesn't work.
public function getStoreVar() {
return Mage::getStoreConfig('path/to/var');
}
Then in the xml I have something like
<block type="my/module" template="path/to/template.phtml">
<config_id helper="module/getStoreVar" />
</block>
I have a Mage::log on the getStoreVar function and can see that it isn't even being called. What am I missing?
Just figured out what I was doing wrong. Realized the code I was referencing to do this, addLink, had the helper inside an action tag with the helper on an argument for the action method. Using a method from my block class fixed it for me. For example:
<action method="setStorVar">
<var helper="module/getStoreVar" />
</action>
I have a custom category attribute that i want to add to the body class. As far as I could find out what people do is
Override the CategoryController and add something like $root->addBodyClass($category->getMyAttribute()); But I don't want to override core classes...
In the admin panel they add something like <reference name=”root”><action method=”addBodyClass”><className>caravan-motorhome-lighting</className></action></reference> to each and every category not using the attribute itself but adding the class directly. As I already have an attribute, I surely don't want do clone it and add the class this way.
So what my favourite solution would be is some layout update I can add to the local.xml that says
<reference name=”root”>
<action method=”addBodyClass”>
<className>
get value of my custom attribute here dynamically
</className>
</action>
</reference>
Does anyone have an idea how this could work or another idea that I didn't even think of?
You can use a really cool feature of Magento layout XML to achieve this. You'll need a module to achieve it. Either create a module specifically for this or use a theme module if you have one — this is up to you to decide what you think is best.
I'll show you an example where I'll add a class containing the ID of the category to the body tag:
In my layout XML, I'm going to add via the catalog_category_default handle. This way, I can use Mage::registry('current_category') later to retrieve the current category. So, in your layout XML do something similar to this:
<catalog_category_default>
<reference name="root">
<action method="addBodyClass">
<className helper="mymodule/my_helper/getCategoryClass" />
</action>
</reference>
</catalog_category_default>
This attribute is the important part: helper="mymodule/my_helper/getCategoryClass". This is equivalent to calling Mage::helper('mymodule/my_helper')->getCategoryClass(); in code.
Whatever is returned from that function will be used as the value for the <className> node. You may want to use a different helper that you deem more appropriate, this is up to you to decide.
Carrying on the with the example, here's the function:
public function getCategoryClass() {
return 'category-id-' . Mage::registry('current_category')->getId();
}
You'll want to change the code so that it retrieves the value of your attribute. e.g getMyAttribute() on the category returned by Mage::registry('current_category').
Also, you'll need to ensure that the return is something that is suitable as a CSS class. In this example we don't need to do anything as the ID will always be just number which will be appended to category-id-. If the value of your attribute is not always going to be safe you might want to consider using something like this
I currently have this:
<block type="core/text" name="top.address" as="topAddress">
<action method="addText"><text>PO BOX 1124, Rockdale, Sydney, NSW 2216, Australia</text></action>
</block>
But, when I need to update address, I have to do it manually here in the layout file. I want to pull address from store config ( general/store_information/address ) so, I can update everywhere on the site from one location easily.
I guess it can be done directly on the template like this:
<?php echo Mage::getStoreConfig('general/store_information/address') ?>
But I want to try with layout, is it possible?
Thanks.
My answer might be to oudated, but I faced with such problem just now I found an alternative way to solve it:
In layout you can specify core/text block and set its text via helper. You can use any suitable reference.
<reference name="before_body_end">
<block type="core/text" name="some.config">
<action method="addText">
<text helper="module_name/data/getSomeConfig" />
</action>
</block>
</reference>
Declare getSomeConfig function in the helper:
public function getSomeConfig()
{
return Mage::getStoreConfig('your_config_path');
}
In a such way you can even pass some dynamic data into javascript code.
Short answer - no. There is no functionality for it. That's not to say that it couldn't be done. There is an attribute that you can use on an action tag - ifconfig. It looks to see if a system config flag is set, and if it returns true, then it will proceed with the action. You could override or extend Mage/Core/Model/Layout.php to add that functionality.
There are a number of options to this problem, though.
You can use templates, like you mentioned.
If you are wanting to avoid a template, you can create a block that extends Mage_Core_Block_Text and specify the _toHtml method, with the code that you provided.
The best: I would see creating a generic block in a generic module that is used to pull system config requests and output them as text. You could either have it be a custom action/method, or send along an attribute value, which will end up in the data array for the block, which you could then lookup in _toHtml.
I am trying to change the template(view.phtml) of a block (product.info) for product detail page, to do this, I am observing an event (controller_action_layout_generate_blocks_before), in it after making necessary checks I am trying to change the template of the block (product.info) in following way:
$layout = $observer->getEvent()->getLayout();
$layout->getUpdate()->addUpdate('
<reference name="product.info">
<action method="setTemplate">
<template>customlayout/product/view.phtml</template>
</action>
</reference>');
$layout->getUpdate()->load();
$layout->generateXml();
If I put "<remove name='product.info'/>" , it will be removed but when trying to do the above, its not working.
Edit:
Requirement is to switch the template (product detail) dynamically to the selected one (in CustomModule) against the current product.
As Ben said, I don't know why you're going to put it on the observer but the problem in your case is the sequence of loadLayout.
You can check your loaded layout xml by using:
Mage::log(Mage::getSingleton('core/layout')->getUpdate()->asString());
Pretty sure your <action method="setTemplate"><template>customelayout/product/view.phtml</template> has been overridden by other setTemplate that's the reason your template is not shown.
Mage_Core_Controller_Varien_Action
public function loadLayout($handles=null, $generateBlocks=true, $generateXml=true)
{
// if handles were specified in arguments load them first
if (false!==$handles && ''!==$handles) {
$this->getLayout()->getUpdate()->addHandle($handles ? $handles : 'default');
}
// add default layout handles for this action
$this->addActionLayoutHandles();
$this->loadLayoutUpdates(); //in here: $this->getLayout()->getUpdate()->load();
if (!$generateXml) {
return $this;
}
//event: controller_action_layout_generate_xml_before
$this->generateLayoutXml(); //in here: $this->getLayout()->generateXml();
if (!$generateBlocks) {
return $this;
}
//event: controller_action_layout_generate_blocks_before, your observer is located here
$this->generateLayoutBlocks(); //in here: $this->getLayout()->generateBlocks();
$this->_isLayoutLoaded = true;
return $this;
}
So, you're going to modify the xml using event: controller_action_layout_generate_blocks_before.
It means what you need to do is:
//add the update
$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');
//then generate the xml
$layout->generateXml();
What cause your problem is:
$layout->getUpdate()->load();
was called again after
$layout->getUpdate()->addUpdate('<reference name="product.info"><action method="setTemplate"><template>customelayout/product/view.phtml</template></action></reference>');
Though it is better to use event: controller_action_layout_generate_xml_before. So that you don't need to generate your xml twice.
If you want to change template of a block from an observer, you should
Listen for the controller_action_layout_generate_blocks_after event
Use PHP to manipulate the layout
By listening for the generate after event, you ensure every action method specified via a file based Layout Update XML string will be called first, and your template change will "win".
I recommend using PHP code because the Layout Update XML system is a domain specific language, the intent of which was to provide a limited set of functionality for layout updates without having to write a single line of PHP. If you're already using a PHP observer, it just makes sense to manipulate the layout via PHP.
Code something like this should get you what you want (again, from the after observer method)
$controller = $observer->getAction();
//limit to the product view page
if($controller->getFullActionName() != 'catalog_product_view')
{
return;
}
$layout = $controller->getLayout();
$product_info = $layout->getBlock('product.info');
if(!$product_info)
{
Mage::log('Could not find product.info block');
return;
}
$product_info->setTemplate('customelayout/product/view.phtml');
Why on earth are you doing it this way?
It would be better to use either the local.xml layout file or a layout file declared for a custom module to do this:
<?xml version="1.0" encoding="UTF-8"?>
<layout>
<catalog_product_view>
<reference name="product.info">
<action method="setTemplate">
<tpl>customelayout/product/view.phtml</tpl>
</action>
</reference>
</catalog_product_view>
</layout>
FYI when a block name is <remove/>ed, no block with that name will be instantiated for any rendering scope which includes that remove directive.
Another solution, that is, from my point of view, more in the Magento's Spirit is to declare our own handle.
1. Declare an observer of controller_action_layout_load_before
In your module config.xml, under the node config>frontend>events put this code :
<controller_action_layout_load_before>
<observers>
<stackoverflow_set_handle>
<class>stackoverflow_module/observer</class>
<method>setHandle</method>
</stackoverflow_set_handle>
</observers>
</controller_action_layout_load_before>
2. Define your observer
class Stackoverflow_Module_Model_Observer
{
public function setHandle(Varien_Event_Observer $observer)
{
$fullActionName = $observer->getEvent()->getAction()->getFullActionName();
if (/* Any condition you may want to modify the layout */) {
Mage::app()->getLayout()->getUpdate()->addHandle('MY_HANDLE_' . $fullActionName);
}
}
3. Create a layout xml file
Once done, you have any fullActionName available to use as second level node in your layout update files prefixed by MY_HANDLE_.
Theses instructions will be only triggered if the handle is present, so basicly for any condition you have set in your observer.
<?xml version="1.0"?>
<layout version="0.1.0">
<MY_HANDLE_catalogsearch_result_index>
<reference name="left">
<remove name="catalogsearch.leftnav" />
</reference>
</MY_HANDLE_catalogsearch_result_index>
<MY_HANDLE_catalog_product_view>
<!-- Do anything you want -->
</MY_HANDLE_catalog_product_view>
</layout>
Last words
You can of course test the $fullActionName within your observer to have your handle added more specifically, and you can build a handle not dynamically based on fullActionName.
For information, this is the way Magento manages a lot of layout variations :
STORE_default > Built dynamically with the current store
THEME_frontend_enterprise_enterprise > Built dynamically with the current theme
PRODUCT_TYPE_simple > Built dynamically with the current product type
PRODUCT_16 > Built dynamically with the current product id
customer_logged_out > Only present if customer is logged in
and others...
To view them, you can temporarily put this at the end of your index.php :
var_dump(Mage::app()->getLayout()->getUpdate()->getHandles());
I was going to comment on the fantastic answer by JBreton, but my particular use case which brought me to this thread is slightly different. (Also I'm an SO lurker and do not have adequate reputation to comment yet.)
The accepted answer and other suggestions for modifying the layout in PHP code did not work for me, even after trying to observe various events, so I figured I'd post a steal/support/example answer on JBreton's side. My use case was to REMOVE blocks (core and custom module blocks) from the checkout_cart_index layout programmatically based on certain conditions. The method of using a custom layout handle works for ADDING blocks as well since it simply "activates" a new handle that Magento will process from a standard layout XML file in a theme.
JBreton's method is the BEST from all of the ones that I tried. It makes more sense in the respect of current and future needs. Especially in the case where designers and template builders are not the same people who should be nosing around in the PHP code. Template people know XML and should be well familiar with Magento's layout XML system anyways. So using a custom handle to modify layouts on specific programmatic conditions is the superior method than adding XML through a string in PHP.
AGAIN ... this is not a solution I conjured on my own ... I stole this from JBreton's answer above and am supplying example code which my doppelganger could use in their situation as an additional starting point. Note that not all of my module code is included here (notably the app/modules XML file, model classes, etc).
My module's config file:
app/code/local/Blahblah/GroupCode/etc/config.xml
<config>
... other config XML too ...
<frontend>
<events>
<controller_action_layout_load_before>
<observers>
<blahblah_groupcode_checkout_cart_index>
<type>singleton</type>
<class>Blahblah_Groupcode_Model_Ghost</class>
<method>checkout_cart_prepare</method>
</blahblah_groupcode_checkout_cart_index>
</observers>
</controller_action_layout_load_before>
</events>
</frontend>
</config>
The observer's method in the class:
app/code/local/Blahblah/GroupCode/Model/Observer.php
<?php
public function checkout_cart_prepare(Varien_Event_Observer $observer)
{
// this is the only action this function cares to work on
$fullActionName = 'checkout_cart_index';
... some boring prerequiste code ...
// find out if checkout is permitted
$checkoutPermitted = $this->_ghost_checkoutPermitted();
if(!$checkoutPermitted)
{
// add a custom handle used in our layout update xml file
Mage::app()->getLayout()->getUpdate()->addHandle($fullActionName . '_disable_checkout');
}
return $this;
}
The layout update inclusion in the theme file:
app/design/PACKAGE/THEME/etc/theme.xml
<?xml version="1.0"?>
<theme>
<parent>...</parent>
<layout>
<updates>
<!-- Adding references to updates in separate layout XML files. -->
<blahblah_checkout_cart_index>
<file>blahblah--checkout_cart_index.xml</file>
</blahblah_checkout_cart_index>
... other update references too ...
</updates>
</layout>
</theme>
The layout update definition file:
app/design/PACKAGE/THEME/layout/blahblah--checkout_cart_index.xml
<layouts>
<checkout_cart_index_disable_checkout>
<reference name="content">
<block type="core/template" name="checkout.disabled" as="checkout.disabled" before="-" template="checkout/disabled-message.phtml" />
<remove name="checkout.cart.top_methods" />
<remove name="checkout.cart.methods" />
</reference>
</checkout_cart_index_disable_checkout>
... other layout updates too ...
</layouts>
(Yes, there is other code in my module which watches the checkout process events to ensure that someone doesn't sneak in with a manual URL path. And other checks are in place to truly "disable" the checkout. I'm just showing my example of how to programmatically modify a layout through an observer.)
I am trying to customize a theme using only local.xml whenever possible. I want to add a static block to the header without modifying header.phtml. This code works fine for the content area, but not for the header:
<default>
<reference name="content">
<block type="cms/block" name="how-it-works-button">
<action method="setBlockId"><block_id>how-it-works</block_id></action>
</block>
</reference>
</default>
Anybody know why? I thought that all I would need is to change “content” to “header”, but nothing shows up when I do.
Thanks for your help!
The content block is a special block known as a core/text_list block (PHP class Mage_Core_Block_Text_List). These blocks will automatically render out any child blocks that are added to them.
The header block, on the other hand, is a page/html_header block (PHP class Mage_Page_Block_Html_Header). This block class inherits from Mage_Core_Block_Template, making it a core/template block. Template blocks will only render sub-blocks if their corresponding phtml template requests the block. So, by adding your block to the header, you're only doing half the work you need to. You'll need to create a custom phtml template.
The simplest way to do this (post 1.4.1.1 is to, in your own theme, create a file at
template/page/html/header.phtml
And then at the end of this file add
<?php echo $this->getChildHtml('how-it-works-button'); ?>
Assuming you've added a block to header block via layout xml, this should render your template.
Please Try this
<?php echo $this->getLayout()->createBlock('cms/block')->setBlockId('how-it-works')->toHtml() ?>
And this code in header.phtml
add output="toHtml" in the block tag. I think it is only that.