11

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

16

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

  • @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 Taqvi Nov 27 '18 at 13:09
16

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}')"
        ]
    );


}
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')
       ]);
   }
}
  • 1
    Thank you for this solution ! I think this is the best and the most elegant. – eInyzant Apr 26 '18 at 4:24
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
  • 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
2

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

MagePal/CustomButton/view/adminhtml/layout/adminhtml_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;
    }

}

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.