Magento – uiComponent Form Save Button Not working

magento-2.1ui-formuicomponent

I actualized Magento to version 2.1 and my buttons stopped working. I'm not sure what I've changed. Actual version of files below.

My problem is to send save request to proper action (before it were working by ajax.) to controller action. Now if it even redirect page. I can't get post data on it. (is empty array).

settings_form.xml

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="provider" xsi:type="string">settings_form.correctemail_settings_form_data_source</item>
        <item name="deps" xsi:type="string">settings_form.correctemail_settings_form_data_source</item>
    </item>
    <item name="label" xsi:type="string" translate="true">Sample Form</item>
    <item name="layout" xsi:type="array">
        <item name="type" xsi:type="string">tabs</item>
    </item>
    <item name="buttons" xsi:type="array">
        <item name="save" xsi:type="string">iDesign\Correctemail\Block\Adminhtml\Buttons\Save</item>
        <item name="update" xsi:type="array">
            <item name="name" xsi:type="string">update</item>
            <item name="label" xsi:type="string">Save Settings</item>
            <item name="class" xsi:type="string">primary</item>
            <item name="url" xsi:type="string">*/*/save</item>
        </item>
    </item>
</argument>

<dataSource name="correctemail_settings_form_data_source">
    <argument name="dataProvider" xsi:type="configurableObject">
        <argument name="class" xsi:type="string">iDesign\Correctemail\Model\Settings\DataProvider</argument>
        <argument name="name" xsi:type="string">correctemail_settings_form_data_source</argument>
        <argument name="primaryFieldName" xsi:type="string">settings_id</argument>
        <argument name="requestFieldName" xsi:type="string">settings_id</argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
            <item name="config" xsi:type="array">
                <item name="submit_url" xsi:type="url" path="*/*/*"/>
            </item>
        </argument>
    </argument>
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="component"     xsi:type="string">Magento_Ui/js/form/provider</item>
        </item>
    </argument>
</dataSource>
<fieldset>
  ...
</fieldset>

Block/Adminhtml/Buttons/Save.php

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class Save implements ButtonProviderInterface
{
  public function getButtonData()
  {
    return [
        'label' => 'Save',
        'class' => 'save primary',
        'data_attribute' => [
            'mage-init' => [
                'button' => ['event' => 'save'],
            ],
        ],
        'sort_order' => 80,
    ];
  }
}

GenericButton.php

class GenericButton
{
  /**
   * @var Context
   */
  protected $context;

  /**
   * @param Context $context
   */
  public function __construct(Context $context)
  {
    $this->context = $context;
  }

  /**
   * Return Settings ID
   *
   * @return int
   */
  public function getSettingsId()
  {
    return 1;
  }

  /**
   * Generate url by route and parameters
   *
   * @param   string $route
   * @param   array $params
   * @return  string
   */
  public function getUrl($route = '', $params = [])
  {
    return $this->context->getUrlBuilder()->getUrl($route, $params);
  }
}

When I Inspect button html I have inside

onclick="location.href=… myModuleName/index/index … (why?)

Other thing is. Why after reload my url gets at the end /undefined string

mage.dev/index.php/admin/ModuleName/settings/index/settings_id/1/key/9a71fd4a4f84367a354a134e399addc67dd95868a29651b37f6a4083ecd0fc05/undefined

Best Answer

Yo, whats up guys. I just had to wrestle with this in 2.1.9 and think I may have a legit answer.

All of these answers are close, but none seem to be completely correct so far. I'll explain how I figured this out.

Unfortuantely, for any of us that have worked with any depth in Magento 2, likely we've come to realize that if you want to do anything... you need to be able to trace core code. Which... is difficult- with all the knockout , xml... fun.

Enough nonsense. Lets get to it.

First, save button:

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="provider" xsi:type="string">educationfiles_edit.educationfiles_edit_data_source</item>
        <item name="deps" xsi:type="string">educationfiles_edit.educationfiles_edit_data_source</item>
    </item>

    <item name="layout" xsi:type="array">
        <item name="type" xsi:type="string">tabs</item>
    </item>

    <item name="buttons" xsi:type="array">
        <item name="manage" xsi:type="array">
            <item name="name" xsi:type="string">manage</item>
            <item name="label" xsi:type="string" translate="true">Manage</item>
            <item name="class" xsi:type="string">manage</item>
            <item name="url" xsi:type="string">*/*/</item>
        </item>
        <item name="save" xsi:type="array">
            <item name="name" xsi:type="string">save</item>
            <item name="label" xsi:type="string" translate="true">save</item>
            <item name="class" xsi:type="string">primary</item>
        </item>
    </item>
</argument>

Within your top data block, shown above, you have the buttons section. Here your button is defined, note that there is no route defined on it... for some reason you don't need it. Meaning you DO NOT need:

<item name="url" xsi:type="string">*/*/save</item>

Like the manage button has above it. I'm guessing that the form expects the item named save to fire the submit_url. Whats the submit_url, right, onto that.

Within your datasource:

<dataSource name="educationfiles_edit_data_source">
    <argument name="dataProvider" xsi:type="configurableObject">
        <argument name="class" xsi:type="string">Augustash\EducationFiles\Model\Upload\DataProvider</argument>
        <argument name="name" xsi:type="string">educationfiles_edit_data_source</argument>
        <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
        <argument name="requestFieldName" xsi:type="string">id</argument>
    </argument>
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            <item name="submit_url" path="*/*/save" xsi:type="url" />
        </item>
    </argument>
</dataSource>

Take note of:

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

This will cause your save button to fire the defined path.

That solves the problem, for extra credit continue on.

How the hell did I figure this out? This is the most important part. Take note of the js_config definition within our datasource block:

<item name="js_config" xsi:type="array">
    <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
    <item name="submit_url" path="*/*/save" xsi:type="url" />
</item>

This means that Magento is using 'Magento_Ui/js/form/provider' to process this form block. Where is that file...?

'Magento_Ui' is a core module. It is located where all the core files are within your 'vendor' folder at the web root. Within 'vendor' the directory will be named 'module-[modulename]', because of '_Ui'.

Magento_Ui

vendor/magento/module-ui

Knockout files are within 'view', then the respective area- meaning 'base', 'adminhtml', or 'frontend', then the 'web' directory.

vendor/magento/module-ui/view/[area]

From there the path is 'vendor/magento/module-ui/view/base/web/js/form/provider.js'.

Magento_Ui/js/form/provider

vendor/magento/module-ui/view/base/web/js/form/provider.js

Meaning you have to know knockout files are in 'view/[area]/web', then you can piece together the actual file path.

Within provider.js you can find what Magento is looking for, to give yourself a clue as to why/what you are defining in your xml. In this case:

defaults: {
    clientConfig: {
        urls: {
            save: '${ $.submit_url }',
            beforeSave: '${ $.validate_url }'
        }
    }
},

This tells us that provider.js save is looking for a 'submit_url' definition. Which means the 'name' of an xml definition should be that, in our case:

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

The astute among you will realize that this means we can also define a validation url with 'validate_url'. I haven't tested that... we probly shouldn't assume that it works- but its intended to work, lol. Magento.

Then we know this is javascript, so we can guess that this definition should be set within the 'js_config':

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
        <item name="submit_url" path="*/*/save" xsi:type="url" />
    </item>
</argument>

In total, our form data and datasource xml:

<argument name="data" xsi:type="array">
    <item name="js_config" xsi:type="array">
        <item name="provider" xsi:type="string">educationfiles_edit.educationfiles_edit_data_source</item>
        <item name="deps" xsi:type="string">educationfiles_edit.educationfiles_edit_data_source</item>
    </item>

    <item name="layout" xsi:type="array">
        <item name="type" xsi:type="string">tabs</item>
    </item>

    <item name="buttons" xsi:type="array">
        <item name="manage" xsi:type="array">
            <item name="name" xsi:type="string">manage</item>
            <item name="label" xsi:type="string" translate="true">Manage</item>
            <item name="class" xsi:type="string">manage</item>
            <item name="url" xsi:type="string">*/*/</item>
        </item>
        <item name="save" xsi:type="array">
            <item name="name" xsi:type="string">save</item>
            <item name="label" xsi:type="string" translate="true">save</item>
            <item name="class" xsi:type="string">primary</item>
        </item>
    </item>
</argument>

<dataSource name="educationfiles_edit_data_source">
    <argument name="dataProvider" xsi:type="configurableObject">
        <argument name="class" xsi:type="string">Augustash\EducationFiles\Model\Upload\DataProvider</argument>
        <argument name="name" xsi:type="string">educationfiles_edit_data_source</argument>
        <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
        <argument name="requestFieldName" xsi:type="string">id</argument>
    </argument>
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            <item name="submit_url" path="*/*/save" xsi:type="url" />
        </item>
    </argument>
</dataSource>

Obviously you'll have to replace various details within this code for it to work.