12

enter image description here

How to add a custom button to sales order view in magento2, since some of the events was remove in-favor of plugins.

  • Removed some events (plugins must be used instead):
    • adminhtml_widget_container_html_before (use in magento 1.x)
    • admin_session_user_logout
    • model_config_data_save_before
    • ...

See Magento2 Change Log

18

The cleanest solution I've seen so far is to use a plugin targeting 'beforeSetLayout'

This can target the exact block, saving the check for the current request, and also avoids the plugin being on 'getOrderId' which in my case could not be used as I needed to call getOrderId in my plugin method.

So this in di.xml

   <type name="Magento\Sales\Block\Adminhtml\Order\View">
    <plugin name="addMyButton" type="My\Module\Plugin\Block\Adminhtml\Order\View"/>
   </type>

And then this in the file My\Module\Plugin\Block\Adminhtml\Order\View.php

public function beforeSetLayout(\Magento\Sales\Block\Adminhtml\Order\View $view)
{
    $message ='Are you sure you want to do this?';
    $url = '/mymodule/controller/action/id/' . $view->getOrderId();


    $view->addButton(
        'order_myaction',
        [
            'label' => __('My Action'),
            'class' => 'myclass',
            'onclick' => "confirmSetLocation('{$message}', '{$url}')"
        ]
    );


}
|improve this answer|||||
17

After trying many different ways, this is the only solution I could find that seem to work without affecting other modules. I would love to see other solutions.

Option 1

Create a plugin in Company/Module/etc/adminhtml/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Backend\Block\Widget\Button\Toolbar">
        <plugin name="MagePal_TestBed::pluginBefore" type="MagePal\TestBed\Plugin\PluginBefore" />
    </type>
</config>

Then in Plugin/PluginBefore.php

namespace MagePal\TestBed\Plugin;

class PluginBefore
{
    public function beforePushButtons(
        \Magento\Backend\Block\Widget\Button\Toolbar\Interceptor $subject,
        \Magento\Framework\View\Element\AbstractBlock $context,
        \Magento\Backend\Block\Widget\Button\ButtonList $buttonList
    ) {

        $this->_request = $context->getRequest();
        if($this->_request->getFullActionName() == 'sales_order_view'){
              $buttonList->add(
                'mybutton',
                ['label' => __('My Button'), 'onclick' => 'setLocation(window.location.href)', 'class' => 'reset'],
                -1
            );
        }

    }
}

Option 2

Create a plugin in Company/Module/etc/adminhtml/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="\Magento\Sales\Block\Adminhtml\Order\View">
        <plugin name="MagePal_TestBed::pluginBeforeView" type="MagePal\TestBed\Plugin\PluginBeforeView" />
    </type>
</config>

Then in Plugin/PluginBeforeView.php

namespace MagePal\TestBed\Plugin;

class PluginBeforeView
{

    public function beforeGetOrderId(\Magento\Sales\Block\Adminhtml\Order\View $subject){
        $subject->addButton(
                'mybutton',
                ['label' => __('My Buttion'), 'onclick' => 'setLocation(window.location.href)', 'class' => 'reset'],
                -1
            );

        return null;
    }

}

See Full Source code

|improve this answer|||||
  • @r-s I've tried 2nd option, and it causes an error - Warning: call_user_func_array() expects parameter 2 to be array, object given in D:\new\OpenServer\domains\graffiticaps-m2.loc\vendor\magento\framework\Interception\Interceptor.php on line 144, since __callPlugin() method adds what beforeGetOrderId() method returns to arguments of getOrderId() method. \vendor\magento\framework\Interception\Interceptor.php [line 124] - $arguments = $beforeResult;. So I think there must be returned smth else, but not object, meaning $subject – Kate Suykovskaya Mar 6 '16 at 17:13
  • 1
    I just test on Magento 2.0.2... Take a look at my update for option #2 .... See github.com/magepal/stackexchange/tree/develop/91071 – Renon Stewart Mar 6 '16 at 21:17
  • Is there any way to call ajax on click of this button? – nuwaus Oct 3 '17 at 9:48
  • @nuwaus ... you could change the 'onclick' to 'onclick="processAjax()"" then add you ajax function there or some other on click jquery binding – Renon Stewart Oct 3 '17 at 14:23
  • here is a similar issue. magento.stackexchange.com/questions/251458/… – Ajwad Syed Nov 27 '18 at 13:09
9

Create DI file: app/code/YourVendor/YourModule/etc/di.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <virtualType name="SalesOrderViewWidgetContext" type="\Magento\Backend\Block\Widget\Context">
        <arguments>
            <argument name="buttonList" xsi:type="object">YourVendor\YourModule\Block\Adminhtml\Order\View\ButtonList
            </argument>
        </arguments>
    </virtualType>
    <type name="Magento\Sales\Block\Adminhtml\Order\View">
        <arguments>
            <argument name="context" xsi:type="object">SalesOrderViewWidgetContext</argument>
        </arguments>
    </type>
</config>

What we do here is:

  1. Set custom context argument into the Order\View block. This context is defined as a virtual type.
  2. Define virtual type for a widget context. We set custom buttonList argument with our own button list class.

Implement your button list class:

<?php
namespace YourVendor\YourModule\Block\Adminhtml\Order\View;

class ButtonList extends \Magento\Backend\Block\Widget\Button\ButtonList
{
   public function __construct(\Magento\Backend\Block\Widget\Button\ItemFactory $itemFactory)
   {
       parent::__construct($itemFactory);
       $this->add('mybutton', [
           'label' => __('My button label')
       ]);
   }
}
|improve this answer|||||
  • 1
    Thank you for this solution ! I think this is the best and the most elegant. – eInyzant Apr 26 '18 at 4:24
  • This looked nice, elegant and easy to understand but unfortunately it does not work. In Magento 2.3.4 when clicking in a order, it throws an error Exception occurred during order load – Gianni Di Falco Feb 17 at 9:40
3

This is one of the best solutions I have seen so far without using plugins

MagePal/CustomButton/view/adminhtml/layout/sales_order_view.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="sales_order_edit">
            <block class="MagePal\CustomButton\Block\Adminhtml\Order\View\Buttons" name="custom_buttons">
                <action method="addButtons"/>
            </block>
        </referenceBlock>
    </body>
</page>

MagePal/CustomButton/Block/Adminhtml/Order/View/Buttons.php

namespace MagePal\CustomButton\Block\Adminhtml\Order\View;

class Buttons extends \Magento\Sales\Block\Adminhtml\Order\View
{    
    public function __construct(
        \Magento\Backend\Block\Widget\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Sales\Model\Config $salesConfig,
        \Magento\Sales\Helper\Reorder $reorderHelper,
        array $data = []
    ) {
        parent::__construct($context, $registry, $salesConfig, $reorderHelper, $data);
    }

    public function addButtons()
    {
        $parentBlock = $this->getParentBlock();

        if(!$parentBlock instanceof \Magento\Backend\Block\Template || !$parentBlock->getOrderId()) {
            return;
        }

        $buttonUrl = $this->_urlBuilder->getUrl(
            'adminhtml/custombutton/new',
            ['order_id' => $parentBlock->getOrderId()]
        );

        $this->getToolbar()->addChild(
              'create_custom_button',
              \Magento\Backend\Block\Widget\Button::class,
              ['label' => __('Custom Button'), 'onclick' => 'setLocation(\'' . $buttonUrl . '\')']
            );
        }
        return $this;
    }

}
|improve this answer|||||
  • There is mistake in adminhtml_sales_order_view.xml should be sales_order_view.xml – Zaheerabbas Dec 27 '19 at 10:20
  • There is no need in public function __construct – Serhii Koval Feb 28 at 11:44
2

Create di.xml following location

app/code/Learning/RewriteSales/etc/di.xml

Content should be

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Backend\Block\Widget\Context">
        <plugin name="add_custom_button_sales_veiw" type="Learning\RewriteSales\Plugin\Widget\Context" sortOrder="1"/>
    </type>
</config>

Create Context.php following loaction

app/code/Learning/RewriteSales/Plugin/Widget/Context.php

Content should be

namespace Learning\RewriteSales\Plugin\Widget;


class Context
{
    public function afterGetButtonList(
        \Magento\Backend\Block\Widget\Context $subject,
        $buttonList
    )
    {
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $request = $objectManager->get('Magento\Framework\App\Action\Context')->getRequest();
        if($request->getFullActionName() == 'sales_order_view'){
            $buttonList->add(
                'custom_button',
                [
                    'label' => __('Custom Button'),
                    'onclick' => 'setLocation(\'' . $this->getCustomUrl() . '\')',
                    'class' => 'ship'
                ]
            );
        }

        return $buttonList;
    }

    public function getCustomUrl()
    {
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $urlManager = $objectManager->get('Magento\Framework\Url');
        return $urlManager->getUrl('sales/*/custom');
    }
}

Clear Magento cache and run update command

php bin/magento setup:upgrade
|improve this answer|||||
  • Correct me if i'm wrong, but from all my testing so far preference type is the equivalent of rewrite in magento 1. Therefore only one module can take advantage of it – Renon Stewart Nov 22 '15 at 16:26
  • yes. But you can't create plugin for protected function. – Sohel Rana Nov 22 '15 at 16:37
  • Just update my answer using plugin – Sohel Rana Nov 22 '15 at 18:41
  • 1
    Instead of loading the objectManager you could have done $subject->getRequest()->getFullActionName() – Renon Stewart Nov 22 '15 at 19:08
  • add this before afterGetButtonList function....... protected $urlBuider; public function __construct(\Magento\Framework\UrlInterface $urlBuilder) { $this->urlBuilder = $urlBuilder; } Then in getCustomUrl() function add this line only..... return $this->urlBuilder->getUrl('modulename/controllername/methodname',array('parameter'=>parameter_value)); – KA9 Jul 31 '17 at 12:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.