Is there a way we could changes the layout of a Magento page (let's say a product category page) dynamically by using system variable which have been set on our own module? I want to be able to set my category page's default layout via my own module admin config panel. So that I don't have to deal with those confusing XML layout file each time I want to change my default layout for a certain magento page.
I know, on a phtml file, we could simply call our own module's system variable by calling Mage::getStoreConfig('module/scope/...') to use that system variable. but what if we want to use that system variable to change the whole layout which is set on the XML layout file by default.
I don't see any ways to pull that system variable value on the XML Layout file.
But I'm pretty sure there must be a right way to do that. So far, this is the closest clue that I've got
Magento - xml layouts, specify value for ifconfig?
But, still, I couldn't find any direct answer for what I really want to achieve
this is the content of my config.xml
<config>
<modules>
<Prem_Spectra>
<version>0.1.0</version>
</Prem_Spectra>
</modules>
<global>
<models>
<spectra>
<class>Prem_Spectra_Model</class>
</spectra>
</models>
<helpers>
<prem_spectra>
<class>Prem_Spectra_Helper</class>
</prem_spectra>
</helpers>
</global>
</config>
This can be very easily achieved using layout xml and a simple method in your helper. I don't see any requirement for an observer here or anything else overly elaborate.
So, based on your requirements to change all category page layouts from your own modules store config value you will require the following in your layout xml:
<catalog_category_view>
<reference name="root">
<action method="setTemplate">
<template helper="yourmodule/switchTemplate" />
</action>
</reference>
</catalog_category_view>
And the following in your modules default helper:
public function switchTemplate()
{
$template = Mage::getStoreConfig('path_to/yourmodule/config');
return $template;
}
we are talking about the template of the root-element, so 3columns, 2columns, etc? correct?
Implement an observer, listen to the event controller_action_layout_generate_blocks_before and then get the block in the observer and set the template
Mage::app()->getLayout()->getBlock('root')->setTemplate($myFancyTemplatePath);
This should do it.
Other idea, try the event controller_action_layout_load_before, but I think this is too early.
In addition to Fabian's answer:
You could perhaps extend the functionality of the category 'display modes'.
Using the controller_action_layout_load_before event and then retrieve the display mode of the category and create a XML update handle for it.
$category = Mage::registry('current_category');
$handle = 'category_displaymode_' . strtolower($category->getDisplayMode());
$layout = $observer->getEvent()->getLayout();
$layout->getUpdate()->addHandle($handle);
This way you can pre-define all kinds of layouts in your local.xml and easily switch between them by adjusting the 'display mode' dropdown on the category edit page in the admin.
With some tweaking in the admin you can add additional display modes to the dropdown to make more types of custom display mode xml update handles available.
Related
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 want to show all my reviews of all the products on a cms page. Does anybody know ho to do this? I'm using Magento 1.4.2
that all depends on what Module you're using to provide that extension to Magento's features. If you look in the folder of the module (let's say its Cmdcentral/Review)
app/code/community/Cmdcentral/Review/
This is where the module resides (it might be in local too)look in the etc for config.xml There will be a section that looks something like this:
<config>
...
<global>
....
<models>
<review>
<class>Cmdcentral_Review_Model</class>
</review>
<review_mysql4>
<class>Cmdcentral_Review_Model_Mysql4</class>
<entities>
<reviews>table_in_database</reviews>
</entities>
</review_mysql4>
</models>
.....
</global>
...
</config>
This will differ depending on what you've got. what's important is the name of the node inside <entities></entities> in my case it is <reviews></reviews>
You can then take a look at the controllers folder for IndexController.php create a new function that looks like this:
public function showallAction(){
$this->loadLayout();
$this->renderLayout();
}
Now you'll have to create a block for this, create a new block in app/code/community/Cmdcentral/Review/Blocks and call it Showall.php
Your block should look something like this:
<?php class Cmdcentral_Review_Block_Showall extends Mage_Core_Block_Template{
public function getAllReviews(){
return Mage::getModel('review/reviews')->getCollection();
}
}
review is module name and reviews is the entity we saw inside the <entities></entities> node in config.xml.
Next we're off to app/design/frontend/ from here, the file we're looking for is most likely in base/default but may also be in another theme's folder. The file we're looking for will be Modulename.xml so in my case it will be app/design/frontend/base/default/layout/Review.xml
Open your layout file, now simply add this inside the <layout></layout> node:
<review_index_showall>
<reference name="content">
<block type="review/showall" name="showall" template="review/showall.phtml"/>
</reference>
<review_index_showall>
This simply tells Magento that when we load our review/index/showall route and access our showallAction() function in our controller, to add our block inside content.
Now the block also has a template="review/showall.phtml" attribute. Go to app/design/frontend/base/default look for a review directory (or whatever the module is called). If it doesn't exist (which I doubt) create it! Inside this create showall.phtml. So now you should have it looking like app/design/frontend/base/default/review/showall.phtml
Open this file and now you create your page. ~Phew!
Just remember to use $this->getAllReviews() to get your review/reviews collection and then simply do something like this:
$reviews = $this->getAllReviews();
foreach($reviews as $review){
echo $review->getData('column_name');
#or
echo $review->getColumnName();
#does the same thing
}
I hope this helps and I didn't make any mistakes. Remember, at first Magento makes you cry, but when you get used to Magento, it's reduced to intermittent whimpers!
I'd like to add a static box under the price on each product page, but I dont want to overwrite an existing template file (e.g. catalog/product/view.phtml) to render it's child block.
I tried to add a block element via frontend/base/default/layout/local.xml
<layout version="0.1.0">
<default>
<reference name="product.info">
<block type="telllowerpricelink/linkbox" name="telllowerpricelink.linkbox" template="telllowerpricelink/link.phtml" before="product.description" output="toHtml" />
</reference>
</default>
</layout>
Then i built a rudimental module:
app/code/local/MyPackage/TellLowerPriceLink/Block/LinkBox.php
<?php
class MyPackage_TellLowerPriceLink_Block_Link extends Mage_Core_Block_Template
{
}
?>
app/code/local/MyPackage/TellLowerPriceLink/etc/config.xml
<?xml version="1.0"?>
<config>
<modules>
<MyPackage_TellLowerPriceLink>
<version>0.1.0</version>
</MyPackage_TellLowerPriceLink>
</modules>
<global>
<blocks>
<mypackage_telllowerpricelink>
<class>MyPackage_TellLowerPriceLink_Block</class>
</mypackage_telllowerpricelink>
</blocks>
</global>
</config>
And my templatefile:
design/frontend/base/default/template/telllowerpricelink/link.phtml
<?php
echo 'Hello world!';
?>
So my questions are:
1. The main question: Is it possible to add the html-output (btw: I dont see it) at the end of the parent block element without editing its template (a la renderChildHtml)?
2. Is it possible to store my template file in this folder or have I to copy the hole default theme folder into a own theme?
Thanks a lot, I have googled and read a lot but didnt find a satisfying answer.
That's only possible with blocks derived from Mage_Core_Block_Text_List block so in your case it isn't possible but you could add your block to reference content (a container that renders all the children from layout files) and wrap your block into a div with style="display: none;" set and then move it with a javascript to the expected location.
It's possible but instead you could change the default theme in admin under system->configuration->general->design->package
The name that you will use here will be the name of your theme folder (note that this changes the theme for your entire store) so you can create your folder in desing/your_theme_name_from_admin/{layout, template} and put only files that you require to override in there. With this you could copy only the phtml file that you would like to override and change it on the new location while keeping the default copy in tact.
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.)
On previous works, I've overrided the Order grid in the Magento Admin, to display others data. So I've create a module to do so. I wrote the new block and reported the overriding in the config.xml :
<blocks>
<adminhtml>
<rewrite>
<sales_order_grid>
Company_Module_Block_Sales_Order_Grid
</sales_order_grid>
</rewrite>
</adminhtml>
</block>
After that I've created a new module to get and save new data. I want now to display them on the grid. These data come from a custom table and are not already used in the grid. I've also to use a renderer to display them.
I don't want to make these two modules dependent of each other, the new data have to be displayed on the overrided grid as on the default one. So I want (if it's possible) to avoid an other overriding of the first module by the second one.
By searching on the Internet, I found this interesting article : http://www.ecomdev.org/2010/07/27/adding-order-attribute-to-orders-grid-in-magento-1-4-1.html
$resource->addVirtualGridColumn(
'customer_telephone',
'sales/order_address',
array('billing_address_id' => 'entity_id'),
'telephone'
);
This approach is perfect for me : if the first module is disabled, the data of the second one are still displayed on the default grid, and if the second module is disabled, the grid is still overrided.
It could have been perfect, but it seems you can't use a renderer by using the addVirtualGridColumn method.
So my question is : Is there a way to use renderer on virtual column ? And if not, is there still a way to elegantly add a "rendered" column without overriding the grid ?
Hope I've been clear in the explanation of my issue and hope there's a solution. Thanks for your help.
You can use XML:
<add_order_grid_column_handle>
<reference name="sales_order.grid">
<action method="addColumnAfter">
<columnId>customer_telephone</columnId>
<arguments module="" translate="header">
<header>Customer telephone</header>
<index>custom_telephone</index>
<type>tex</type>
<sortable>true</sortable>
<renderer>YOUR_CUSTOM_RENDERER_CLASS</renderer>
</arguments>
<after>....</after>
</action>
</reference>
</add_order_grid_column_handle>