How to add a button to Magento admin containers

From time to time the developers face with the necessity of adding buttons to Magento admin forms or grids. It is not a problem if a button is added to your custom container. So, our article is aimed to show the ways of adding the buttons to already existing third party or core containers, like an order view page or a product grid.

First of all, there is no difference between adding a button to a form or grid because you will be adding this button to its container class, which extends Mage_Adminhtml_Block_Widget_Container.

Of course, it looks like that “We will rewrite the container!”. And yes, you can rewrite a container and add a button to the _prepareLayout method. We think that adding buttons on “prepare layout” is a great practice for the custom containers. Here is an example from Mage_Adminhtml_Block_Catalog_Product:

/**
 * file location:
 * your_magento_location/app/code/core/Mage/Adminhtml/Block/Catalog/Product.php
 */
 protected function _prepareLayout()
{
    $this->_addButton('add_new', array(
        'label'   => Mage::helper('catalog')->__('Add Product'),
        'onclick' => "setLocation('{$this->getUrl('*/*/new')}')",
        'class'   => 'add'
    ));

    $this->setChild('grid', $this->getLayout()->createBlock('adminhtml/catalog_product_grid', 'product.grid'));
    return parent::_prepareLayout();
}

However, do we really need to rewrite the whole container for adding a button or two? You even can get frustrated if a parent class calls the method removeButton() after you’ve added it.

Well, there are two approaches of adding a button to the container without any rewrites.

The first one is to use an observer. If you check the Mage_Adminhtml_Block_Widget_Container you will find the event created for that (and not only that) purpose:

/**
 * file location:
 * your_magento_location/app/code/core/Mage/Adminhtml/Block/Widget/Container.php
 */
protected function _toHtml()
{
    Mage::dispatchEvent('adminhtml_widget_container_html_before', array('block' => $this));
    return parent::_toHtml();
}

In short, we simply need to catch that event and hang our observer on it. Add this to config.xml of your module:

<!-- ... -->
<global>
	<!-- ... -->
	<events>
		<!-- another events may be here -->
		<adminhtml_widget_container_html_before>
		    <observers>
		        <add_button_test>
		            <class>your_model/observer</class>
		            <method>addButtonTest</method>
		        </add_button_test>
		    </observers>
		</adminhtml_widget_container_html_before>
		<!-- another events may be here -->
	</events>
	<!-- ... -->
</global>
<!-- ... -->

And after that, add a method to your observer (create one if you need):

public function addButtonTest($observer)
{
    $container = $observer->getBlock();
    if(null !== $container && $container->getType() == 'adminhtml/catalog_product') {
        $data = array(
            'label'     => 'My button',
            'class'     => 'some-class',
            'onclick'   => 'setLocation(\' '  . Mage::getUrl('*/*', array('param' => 'value')) . '\')',
        );
        $container->addButton('my_button_identifier', $data);
    }

    return $this;
}

As you can see, this method will add a button to the catalog product grid (as an example):

add_button

We accept that the disadvantage of such method is that your observer will be called on ALL containers rendering. That is why, you should be cautious with what you put inside and always compare if you have caught the right container with the condition like “$container->getType() == ‘adminhtml/catalog_product'”.

Let’s move on – the second way is to add a button via layout. In this case, you need to know the handle of the page you want to add a button to. You can inspect the “body” tag for that purpose. Simply add the following part to your layout (use local.xml if you don’t have it):

<layout version="0.1.0">
	<!-- ... -->
    <adminhtml_catalog_product_index>
        <reference name="products_list">
            <action method="addButton">
                <id>my_button_identifier</id>
                <data>
                    <label>My button</label>
                    <class>some-class</class>
                    <onclick>some_click_action_here</onclick>
                </data>
            </action>
        </reference>
    </adminhtml_catalog_product_index>
    <!-- ... -->
</layout>

So, adding a button via layout looks more gracefully. Even so, the weakness of this method is simple – if we have a need to add an event on click like in the first example is (with setLocation), we will not be able to generate a proper admin URL from the layout. A quick solution here is to generate the URL elsewhere and add it to the button like a JavaScript variable. Modify a layout update from the previous example according to this:

<layout version="0.1.0">
	<!-- ... -->
    <adminhtml_catalog_product_index>
        <reference name="products_list">
            <action method="addButton">
                <id>my_button_identifier</id>
                <data>
                    <label>My button</label>
                    <class>some-class</class>
                    <onclick>setLocation(my_custom_url)</onclick>
                </data>
            </action>
        </reference>
        <reference name="content">
            <block type="adminhtml/template" name="my_button_url" template="my_module/button_url.phtml" />
        </reference>
    </adminhtml_catalog_product_index>
    <!-- ... -->
</layout>

Then, we will need to add a template. Note that we have added my_button_url block as a child of content. The head block should be more obvious, but the admin head block does not render all its children like its “frontend brother”. The my_button_url block template content should look like this:

/**
 * file location:
 * your_magento_location/app/design/adminhtml/default/default/template/my_module/button_url.phtml
 */
<script type="text/javascript">
//<![CDATA[
    var my_custom_url = '<?php echo $this->getUrl('*/*', array('param' => 'value')); ?>';
//]]>
</script>

In conclusion, both methods are fine and have their own advantages and disadvantages.

The addButton() method accepts some additional params: order, level and area, which are set as 0, 0 and ‘header’ by default respectively.

Also, let’s check what data can be transferred as the button params. We will need to look into Mage_Adminhtml_Block_Widget_Button for that purpose:

/**
 * file location:
 * your_magento_location/app/code/core/Mage/Adminhtml/Block/Widget/Button.php
 */
protected function _toHtml()
{
    $html = $this->getBeforeHtml().'<button '
        . ($this->getId()?' id="'.$this->getId() . '"':'')
        . ($this->getElementName()?' name="'.$this->getElementName() . '"':'')
        . ' title="'
        . Mage::helper('core')->quoteEscape($this->getTitle() ? $this->getTitle() : $this->getLabel())
        . '"'
        . ' type="'.$this->getType() . '"'
        . ' class="scalable ' . $this->getClass() . ($this->getDisabled() ? ' disabled' : '') . '"'
        . ' onclick="'.$this->getOnClick().'"'
        . ' style="'.$this->getStyle() .'"'
        . ($this->getValue()?' value="'.$this->getValue() . '"':'')
        . ($this->getDisabled() ? ' disabled="disabled"' : '')
        . '><span><span><span>' .$this->getLabel().'</span></span></span></button>'.$this->getAfterHtml();

    return $html;
}

Sure, it is not perfect, but we can see all the information that can be transferred here. So, take a look at the params as they appear in the code:

  • before_html – HTML markup which will appear before the button itself;
  • element_name – will appear as a “name” HTML attribute value;
  • title – will appear as a “title” HTML attribute value;
  • label – the text on the button, it will be also used as a “title” attribute value if the title is not set;
  • type – will appear as a “type” HTML attribute value;
  • class – will be added to the button HTML element class. Predefined Magento admin classes: add, back, cancel, delete, disabled, no-display, save;
  • disabled – will add a class disabled and an attribute disabled to the button if there is no set as null, false or 0;
  • on_click – will add the on click attribute;
  • style – inline CSS for the button;
  • value – will appear as a “value” HTML attribute;
  • after_html – HTML markup which will appear after the button markup;

We hope that this information will simplify your work with Magento when you will need to add the buttons to Magento admin containers. Thanks for reading our blog.

You may also want to read: