About Magento2 backend form button "Save", "SaveAndContinue"

All we need is an easy explanation of the problem, so here it is.

To create a backend form via ui_component, I define following in the config file to display buttons save and saveAndContinue Button

<item name="buttons" xsi:type="array">
    <item name="save" xsi:type="string">namespace\module\Block\Adminhtml\Edit\SaveButton</item>
    <item name="save_and_continue" xsi:type="string">namespace\module\Block\Adminhtml\Edit\SaveAndContinueButton</item>
</item>

Respectively, two files SaveButton.php and SaveAndContinueButton.php are created and both implement ButtonProviderInterface

As I know, button is mainly rendered from a getButtonData function. See SaveAndContinueButton.php

public function getButtonData()
{
    $TodoItemId = $this->getTodoItemId();
    $data = [];
    if ($TodoItemId) {
        $data = [
            'label' => __('Save and Continue Edit'),
            'class' => 'save',
            'data_attribute' => [
                'mage-init' => [
                    'button' => ['event' => 'saveAndContinueEdit'],
                ],
            ],
            'sort_order' => 80,
        ];
    }
    return $data;
}

The data_attribute is where that I don’t understand. How does it know which file to handle save request?

If we check the SaveButton.php, We saw

$data = [
    'label' => __('Save TodoItem'),
    'class' => 'save primary',
    'data_attribute' => [
        'mage-init' => ['button' => ['event' => 'save']],
        'form-role' => 'save',
    ],
    'sort_order' => 90,
];

I know in the ui_component config file, there is

<item name="submit_url" xsi:type="url" path="path/to/save"/>

Both action successfully execute same Save.php file and it makes sense. What confuse me much is data_attribute and how does SaveAndContinueButton pass parameter “back” so that it knows to stay at the same page instead of go to grid (normally grid is entry point of a form, aka edit page).

If we take another look at the deleteButton, it’s another landscape

$data = [
    'label' => __('Delete'),
    'class' => 'delete',
    'on_click' => 'deleteConfirm(\'' . __(
        'Are you sure you want to do this?'
    ) . '\', \'' . $this->getDeleteUrl() . '\')',

    'sort_order' => 20,
];

It directly executes the onClick JavaScript event. Any idea/suggestion will be appreciated. thanks

One more question: what’s the different of data_attribute and on_click? or advantage one over another one?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I don’t have a full explanation, but I have an idea.
All elements rendered with mage-init will/should be handled by some javascript code.
You are linking these buttons to a form, and the form is handled by this js file lib/web/mage/backend/form.js where a jquery ui widget is created.

These are the options for the widget

options: {
    handlersData: {
        save: {},
        saveAndContinueEdit: {
            action: {
                args: {
                    back: 'edit'
                }
            }
        },
        preview: {
            target: '_blank'
        }
    }
},

you can see a saveAndContinueEdit in there somewhere inside handlersData.
Looking for the usages of handlersData you end up in _beforeSubmit where some magic happens (I don’t really understand everything in there), and at one point _processData is called.
Moving to _processData you will see something like this

if (attrName === 'action') {
    data[attrName] = this._getActionUrl(attrValue);
}

this means that the action of the form is changed based on the button pressed.

the _getActionUrl function looks like this

_getActionUrl: function(data) {
    if ($.type(data) === 'object') {
        return this._buildURL(this.oldAttributes.action, data.args);
    } else {
        return $.type(data) === 'string' ? data : this.oldAttributes.action;
    }
},

you can see in there data.args involved. The same variable in the widget options for saveAndContinueEdit.

Conclusion: When you set the role saveAndContinueEdit toa submit button, the action of the form is changed via js and back/edit is added to the url.

on_click is transformed to the onclick event and simply called.
I honestly have no idea why there are 2 ways of doing this. Maybe the delete actions didn’t get refactored yet.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply