22

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

MagePal Extensions
  • 13,911
  • 2
  • 33
  • 52

6 Answers6

30

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 = $view->getUrl('route_id/path').$view->getOrderId();
$view-&gt;addButton(
    'order_myaction',
    [
        'label' =&gt; __('My Action'),
        'class' =&gt; 'myclass',
        'onclick' =&gt; &quot;confirmSetLocation('{$message}', '{$url}')&quot;
    ]
);


}

Zwie
  • 113
  • 3
Chris
  • 1,004
  • 9
  • 9
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

MagePal Extensions
  • 13,911
  • 2
  • 33
  • 52
  • @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 06 '16 at 17:13
  • 1
    I just test on Magento 2.0.2... Take a look at my update for option #2 .... See https://github.com/magepal/stackexchange/tree/develop/91071 – MagePal Extensions Mar 06 '16 at 21:17
  • Is there any way to call ajax on click of this button? – nuwaus Oct 03 '17 at 09:48
  • @nuwaus ... you could change the 'onclick' to 'onclick="processAjax()"" then add you ajax function there or some other on click jquery binding – MagePal Extensions Oct 03 '17 at 14:23
  • here is a similar issue. https://magento.stackexchange.com/questions/251458/m2-how-to-put-button-on-sales-order-information-page – Ajwad Syed Nov 27 '18 at 13:09
  • Using option 2 doesn't allow you to use $this->getOrderId(). Using the plugin as shown in this answer appears to work with it https://magento.stackexchange.com/a/137616/297. – Raj Jan 16 '19 at 12:06
10

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')
       ]);
   }
}
Tjitse
  • 1,300
  • 15
  • 28
dan.kocherga
  • 411
  • 4
  • 7
10

This is one of the best solutions I have seen so far without using plugins. It is also used by the PayPal module.

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" />
        </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 {

protected function _construct() { parent::_construct();

    if(!$this-&gt;getOrderId()) {
        return $this;
    }

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

    $this-&gt;addButton(
        'create_custom_button',
        ['label' =&gt; __('Custom Button'), 'onclick' =&gt; 'setLocation(\'' . $buttonUrl . '\')']
    );

    return $this;
}

}

Rafael Corrêa Gomes
  • 13,309
  • 14
  • 84
  • 171
MagePal Extensions
  • 13,911
  • 2
  • 33
  • 52
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
Sohel Rana
  • 35,846
  • 3
  • 72
  • 90
  • 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 – MagePal Extensions 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() – MagePal Extensions 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
  • Ok, can anyone please tell me, how to get order id in Context.php? – KA9 Jul 31 '17 at 13:50
  • I got order id using this : $order_id = $request->getParam('order_id'); – KA9 Aug 03 '17 at 10:35
  • how can i call it to custom controller in admin? – Ketan Borada Sep 13 '19 at 09:26
0

I tried the below link its working perfect. Add a Custom Button to Admin Sales Order View

chris
  • 341
  • 4
  • 16