13

I try to turn on inline edit in catalog grid as it described in DevDocs: http://devdocs.magento.com/guides/v2.0/ui-components/ui-secondary-inline.html

The main problem is how to override this block of configuration in

\vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml

            <item name="childDefaults" xsi:type="array">
                <item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">product_listing.product_listing.product_columns.actions</item>
                    <item name="target" xsi:type="string">applyAction</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">edit</item>
                        <item name="1" xsi:type="string">${ $.$data.rowIndex }</item>                            
                    </item>
                </item>
            </item>

with custom "params" required for inline edit in

\app\code\MyVendor\MyModule\view\adminhtml\ui_component\product_listing.xml

                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>   

It throws error while loading configuration by the core framework function

**Magento\Ui\Model\Manager->evaluateComponentArguments()**

I don't know how to cure it except overriding framework components such as

Magento\Ui\Model\Manager or Magento\Framework\View\Layout\Generator\UiComponent,

but it's a bad idea

Another decision is to comment "fieldAction" item in original Magento product_listing.xml

With this modification inline edit works.

Here is the full sample code:

\app\code\MyVendor\MyModule\etc\adminhtml\di.xml

<preference for="Magento\Catalog\Ui\Component\Listing\Columns" type="MyVendor\MyModule\Magento\Catalog\Ui\Component\Listing\Columns" />

\app\code\MyVendor\MyModule\etc\adminhtml\routes.xml

<router id="admin">
    <route id="my_product" frontName="my_product">
        <module name="MyVendor_MyModule"/>
    </route>
</router>

Modified original magento config:

\vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml

             ------
             <item name="childDefaults" xsi:type="array">
                <!--<item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">product_listing.product_listing.product_columns.actions</item>
                    <item name="target" xsi:type="string">applyAction</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">edit</item>
                        <item name="1" xsi:type="string">${ $.$data.rowIndex }</item>                           
                    </item>
                </item>--> 
            </item>
            --------

Custom config:

\app\code\MyVendor\MyModule\view\adminhtml\ui_component\product_listing.xml

<dataSource name="product_listing_data_source"></dataSource>
<listingToolbar name="listing_top"></listingToolbar>
<columns name="product_columns" class="Magento\Customer\Ui\Component\Listing\Columns">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="editorConfig" xsi:type="array">
                <item name="selectProvider" xsi:type="string">product_listing.product_listing.product_columns.ids</item>
                <item name="enabled" xsi:type="boolean">true</item>
                <item name="indexField" xsi:type="string">entity_id</item>
                <item name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="my_product/product/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </item>
            </item>
            <item name="childDefaults" xsi:type="array">
                <item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">product_listing.product_listing.product_columns_editor</item>
                    <item name="target" xsi:type="string">startEdit</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>                        
                </item>
            </item>
        </item>
    </argument>
    <column name="name">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editor" xsi:type="string">text</item>
            </item>
        </argument>
    </column>
    <column name="my_pku">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="editor" xsi:type="string">text</item>                    
            </item>
        </argument>
    </column>
    ----- other columns ----
 </columns>

\app\code\MyVendor\MyModule\Controller\Adminhtml\Product.php

namespace MyVendor\MyModule\Controller\Adminhtml;
use Magento\Backend\App\Action;
abstract class Product extends \Magento\Backend\App\Action {
    const ADMIN_RESOURCE = 'Magento_Catalog::products';

    public function __construct(
        \Magento\Backend\App\Action\Context $context
    ) {
        parent::__construct($context);
    }
}

\app\code\MyVendor\MyModule\Controller\Adminhtml\Product\InlineEdit.php

namespace MyVendor\MyModule\Controller\Adminhtml\Product;

use Magento\Backend\App\Action;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;

class InlineEdit extends \Magento\Backend\App\Action {

    const ADMIN_RESOURCE = 'Magento_Catalog::products';

    private $product;
    protected $productRepository;
    protected $resultJsonFactory;
    protected $dataObjectHelper;
    protected $logger;

    public function __construct(
    Action\Context $context, ProductRepositoryInterface $productRepository,
            \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
            \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
            \Psr\Log\LoggerInterface $logger
    ) {
        $this->productRepository = $productRepository;
        $this->resultJsonFactory = $resultJsonFactory;
        $this->dataObjectHelper = $dataObjectHelper;
        $this->logger = $logger;
        parent::__construct($context);
    }

    public function execute() {
        $resultJson = $this->resultJsonFactory->create();

        $postItems = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && count($postItems))) {
            return $resultJson->setData([
                        'messages' => [__('Please correct the data sent.')],
                        'error' => true,
            ]);
        }

        foreach ($postItems as $productId => $productRow) {
            $this->setProduct($this->productRepository->getById($productId));

            $data = $this->product->getData();
            // ---- formatting and validating
            // $this->my_server_side_validator($data);
            // $this->my_server_side_formatter($data);

            // saving
            $dataUpdated = array_replace($data, $productRow);
            $this->product->setData($dataUpdated);
            $this->product->save();
        }

        return $resultJson->setData([
                    'messages' => $this->getErrorMessages(),
                    'error' => $this->isErrorExists()
        ]);
    }

    protected function getErrorMessages() {
        $messages = [];
        foreach ($this->getMessageManager()->getMessages()->getItems() as $error) {
            $messages[] = $error->getText();
        }
        return $messages;
    }

    protected function isErrorExists() {
        return (bool) $this->getMessageManager()->getMessages(true)->getCount();
    }

    protected function setProduct(ProductInterface $product) {
        $this->product = $product;
        return $this;
    }

    protected function getProduct() {
        return $this->product;
    }
}

\app\code\MyVendor\MyModule\Magento\Catalog\Ui\Component\Listing\Columns.php

namespace MyVendor\MyModule\Magento\Catalog\Ui\Component\Listing;
class Columns extends \Magento\Catalog\Ui\Component\Listing\Columns {
    // Array of attributes not included in
    //  \vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml
    protected $additional_fields = [
        'my_pku'
    ];
    public function __construct(
    \Magento\Framework\View\Element\UiComponent\ContextInterface $context,
            \Magento\Catalog\Ui\Component\ColumnFactory $columnFactory,
            \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository,
            array $components = [], array $data = []
    ) {
        parent::__construct($context, $columnFactory, $attributeRepository,
                $components, $data);
    }
    /**
     * {@inheritdoc}
     */
    public function prepare() {
        $columnSortOrder = self::DEFAULT_COLUMNS_MAX_ORDER;
        foreach ($this->attributeRepository->getList() as $attribute) {
            $attr_code = $attribute->getAttributeCode();
            $config = [];
            if (!isset($this->components[$attr_code]) || in_array($attr_code,
                            $this->additional_fields)) {
                $config['sortOrder'] = ++$columnSortOrder;
                if ($attribute->getIsFilterableInGrid()) {
                    $config['filter'] = $this->getFilterType($attribute->getFrontendInput());
                }
                // Copy editor configuration for additional attributes
                if (isset($this->components[$attr_code]->_data['config']['editor'])) {
                    $config['editor'] = $this->components[$attr_code]->_data['config']['editor'];
                }
                $column = $this->columnFactory->create($attribute,
                        $this->getContext(), $config);
                $column->prepare();
                $this->addComponent($attribute->getAttributeCode(), $column);
            }
        }
        parent::prepare();
    }
}

I need to extend Column.php because custom product_listing.xml also can't add editor config for additional product attributes like "my_pku" code, that not included in original Magento product_listing.xml

Without this, I can inline edit only predefined fields in original Magento product_listing.xml

So, QUESTIONS are:

  1. How to enable catalog inline edit without modifying \vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml ?

  2. What is an acceptable method to replace original ui_component config XML files safely, when item types are different?

UPDATE:

to Pramod Kumar Sharma

In 2.1.6 I had to comment out this block of code in

\vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml

             ------
         <item name="childDefaults" xsi:type="array">
            <!--<item name="fieldAction" xsi:type="array">
                <item name="provider" xsi:type="string">product_listing.product_listing.product_columns.actions</item>
                <item name="target" xsi:type="string">applyAction</item>
                <item name="params" xsi:type="array">
                    <item name="0" xsi:type="string">edit</item>
                    <item name="1" xsi:type="string">${ $.$data.rowIndex }</item>                           
                </item>
            </item>--> 
        </item>
        --------

In 2.2.1 it works without commenting out. I have no separate extension for this, it's part of my module with many other improvements

Just do as described above

Here it is

  • Have you completed the inline edit in product grid ? – Pramod Kumar Sharma Jan 9 '18 at 5:54
  • yes, it works. And after upgrade from 2.1.6 to 2.2.1 it works without modifying \vendor\magento\module-catalog\view\adminhtml\ui_component\product_listing.xml – my display name Jan 15 '18 at 1:22
  • Can you please send me the extension so i can refer that extension. – Pramod Kumar Sharma Jan 15 '18 at 5:02
  • How you override the product_listing.xml in your module can you please explain me. It will be helpful for me. Thanks – Pramod Kumar Sharma Jan 19 '18 at 5:59
  • Pramod Kumar Sharma, I've updated post – my display name Jan 23 '18 at 5:34
0

I have found the way for it. I have overridden all the files.I have done inline edit for below attributes.

-- brand, sku, name, price, visibility, status

Also i have done it for below custom dropdown attributes which are

-- supplier , show on home page or not(boolean)

Please follow below steps

Step 1 Show attributes in grid

PackageName/ProductInlineEdit/view/adminhtml/ui_component/product_listing.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <listingToolbar name="listing_top">
        <massaction name="listing_massaction" component="Magento_Ui/js/grid/tree-massactions" class="\Magento\Catalog\Ui\Component\Product\MassAction">
            <action name="edit">
                <settings>
                    <callback>
                        <target>editSelected</target>
                        <provider>product_listing.product_listing.product_columns_editor</provider>
                    </callback>
                    <type>edit</type>
                    <label translate="true">Edit</label>
                </settings>
            </action>
        </massaction>
    </listingToolbar>
    <columns name="product_columns" class="Magento\Catalog\Ui\Component\Listing\Columns">
        <settings>
            <editorConfig>
                <param name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="productinlineedit/product/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </param>
                <param name="indexField" xsi:type="string">entity_id</param>
                <param name="enabled" xsi:type="boolean">true</param>
                <param name="selectProvider" xsi:type="string">product_listing.product_listing.product_columns.ids</param>
            </editorConfig>
            <childDefaults>
                <param name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">product_listing.product_listing.product_columns_editor</item>
                    <item name="target" xsi:type="string">startEdit</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>
                </param>
            </childDefaults>
        </settings>
        <column name="name" sortOrder="30">
            <settings>
                <editor>
                    <editorType>text</editorType>
                </editor>
            </settings>
        </column>
        <column name="sku" sortOrder="60">
            <settings>
                <editor>
                    <editorType>text</editorType>
                </editor>
            </settings>
        </column>
        <column name="price" class="Magento\Catalog\Ui\Component\Listing\Columns\Price" sortOrder="70">
            <settings>
                <editor>
                    <editorType>text</editorType>
                </editor>
            </settings>
        </column>
        <column name="visibility" component="Magento_Ui/js/grid/columns/select" sortOrder="80">
            <settings>
                <editor>
                    <editorType>select</editorType>
                </editor>
            </settings>
        </column>
        <column name="status" component="Magento_Ui/js/grid/columns/select" sortOrder="90">
            <settings>
                <editor>
                    <editorType>select</editorType>
                </editor>
            </settings>
        </column>
    </columns>
</listing>

Step 2 Remove cursor over the fields

PackageName/ProductInlineEdit/view/adminhtml/layout/default.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
   <head>
     <css src="PackageName_ProductInlineEdit::css/inlineedit.css"/>
   </head>
</page>

Add css file in below path:-

PackageName/ProductInlineEdit/view/adminhtml/web/css/inlineedit.css

.catalog-product-index .admin__data-grid-wrap .data-row{cursor: default !important;}

Step 3 Inline edit fields with the use of plugin method

PackageName/ProductInlineEdit/etc/adminhtml/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Ui\Component\ColumnFactory">
        <plugin name="packagename.product.inlineedit" type="PackageName\ProductInlineEdit\Plugin\Component\Listing\ColumnFactory" sortOrder="10" disabled="false"  />
    </type>
</config>

Create plugin file in path :-

PackageName\ProductInlineEdit\Plugin\Component\Listing\ColumnFactory

<?php

namespace PackageName\ProductInlineEdit\Plugin\Component\Listing;
/**
 * {@inheritdoc}
 */
class ColumnFactory {

    /**
     * @var array
     */
    protected $editorMap = [
        'default' => 'text',
        'select' => 'select',
        'boolean' => 'select',
        'multiselect' => 'select',
        'date' => 'dateRange',
    ];

    /**
     * Add Inline Edit for custom Attributes
     * 
     * @param $subject
     * @param $attribute
     * @param $context
     * @param $config
     */
    public function beforeCreate(\Magento\Catalog\Ui\Component\ColumnFactory $subject, $attribute, $context, array $config = [])
    {
        $editorType = $attribute->getFrontendInput();
        if(isset($this->editorMap[$editorType])){
            $editorType = $this->editorMap[$editorType];
        }

        $config['editor'] = ['editorType'=> $editorType];
        return [$attribute, $context, $config];
    }
}

Step 4 Create js file to control columns

PackageName/ProductInlineEdit/view/adminhtml/requirejs-config.js

var config = 
{
    map: 
    {
        '*': 
        {
            'Magento_Ui/js/grid/controls/columns':'PackageName_ProductInlineEdit/js/grid/controls/columns'
        }
    }
};

PackageName/ProductInlineEdit/view/adminhtml/web/js/grid/controls/columns.js

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/**
 * @api
 */
define([
    'underscore',
    'mageUtils',
    'mage/translate',
    'uiCollection'
], function (_, utils, $t, Collection) {
    'use strict';

    return Collection.extend({
        defaults: {
            template: 'ui/grid/controls/columns',
            minVisible: 1,
            maxVisible: 30,
            viewportSize: 18,
            displayArea: 'dataGridActions',
            columnsProvider: 'ns = ${ $.ns }, componentType = columns',
            imports: {
                addColumns: '${ $.columnsProvider }:elems'
            },
            templates: {
                headerMsg: $t('${ $.visible } out of ${ $.total } visible')
            }
        },

        /**
         * Resets columns visibility to theirs default state.
         *
         * @returns {Columns} Chainable.
         */
        reset: function () {
            this.elems.each('applyState', 'default', 'visible');

            return this;
        },

        /**
         * Applies last saved state of columns visibility.
         *
         * @returns {Columns} Chainable.
         */
        cancel: function () {
            //this.elems.each('applyState', '', 'visible');

            return this;
        },

        /**
         * Adds columns whose visibility can be controlled to the component.
         *
         * @param {Array} columns - Elements array that will be added to component.
         * @returns {Columns} Chainable.
         */
        addColumns: function (columns) {
            columns = _.where(columns, {
                controlVisibility: true
            });

            this.insertChild(columns);

            return this;
        },

        /**
         * Defines whether child elements array length
         * is greater than the 'viewportSize' property.
         *
         * @returns {Boolean}
         */
        hasOverflow: function () {
            return this.elems().length > this.viewportSize;
        },

        /**
         * Helper, checks
         *  - if less than one item choosen
         *  - if more then viewportMaxSize choosen
         *
         * @param {Object} elem
         * @returns {Boolean}
         */
        isDisabled: function (elem) {
            var visible = this.countVisible();

            return elem.visible ?
                    visible === this.minVisible :
                    visible === this.maxVisible;
        },

        /**
         * Counts number of visible columns.
         *
         * @returns {Number}
         */
        countVisible: function () {
            return this.elems.filter('visible').length;
        },

        /**
         * Compile header message from headerMessage setting.
         *
         * @returns {String}
         */
        getHeaderMessage: function () {
            return utils.template(this.templates.headerMsg, {
                visible: this.countVisible(),
                total: this.elems().length
            });
        }
    });
});

Step 5 Save inline data with the use of custom controller. We have already defined controller path in product_listing.xml file

Create router file to define controller name:-

PackageName/ProductInlineEdit/etc/adminhtml/routes.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="productinlineedit" frontName="productinlineedit">
            <module name="PackageName_ProductInlineEdit" />
        </route>
    </router>
</config>

Create controller file in below path :-

PackageName/ProductInlineEdit/Controller/Adminhtml/Product/InlineEdit.php

<?php

namespace PackageName\ProductInlineEdit\Controller\Adminhtml\Product;

class InlineEdit extends \Magento\Backend\App\Action
{
    /**
     * @var \Magento\Framework\Controller\Result\JsonFactory
     */
    protected $jsonFactory;

    /**
     * @var \Magento\Catalog\Model\ProductFactory $productFactory
     */
    protected $productFactory;

    /**
    * @param \Magento\Backend\App\Action\Context $context
    * @param \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
    * @param \Magento\Catalog\Model\ProductFactory $productFactory
    */

    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonFactory,
        \Magento\Catalog\Model\ProductFactory $productFactory
    ) {
        parent::__construct($context);
        $this->jsonFactory = $jsonFactory;
        $this->_productFactory = $productFactory;
    }

    /**
     * @return \Magento\Framework\Controller\Result\JsonFactory
     */
    public function execute()
    {
        $resultJson = $this->jsonFactory->create();
        $error = false;
        $messages = [];

        $postItems = $this->getRequest()->getParam('items', []);
        if (!($this->getRequest()->getParam('isAjax') && count($postItems))) {
            return $resultJson->setData([
                'messages' => [__('Please correct the data sent.')],
                'error' => true,
            ]);
        }
        foreach ($postItems as $value)
        {
            if ($value) {
                $productObj = $this->_productFactory->create();
                if ($value['entity_id']) {
                    $productObj->load($value['entity_id']);
                }
                $productObj->setStoreId(0);
                if(array_key_exists('name', $value)) {
                    $productObj->setName($value['name']);
                }

                if(array_key_exists('sku', $value)) {
                    $productObj->setSku($value['sku']);
                }

                if(array_key_exists('price', $value)) {
                    $productObj->setPrice($value['price']);
                }

                if(array_key_exists('visibility', $value)) {
                    $productObj->setVisibility($value['visibility']);
                }

                if(array_key_exists('status', $value)) {
                    $productObj->setStatus($value['status']);
                }
                if(array_key_exists('brand', $value)) {
                    $productObj->setBrand($value['brand']);
                }

                if(array_key_exists('showinhome', $value)) {
                    $productObj->setShowinhome($value['showinhome']);
                }
                if(array_key_exists('supplier_id', $value)) {
                    $productObj->setSupplierId($value['supplier_id']);
                }

                try {
                    $productObj->save();
                } catch (\Magento\Framework\Model\Exception $e) {
                    $this->messageManager->addError($e->getMessage());
                }
            }
        }
        return $resultJson->setData([
            'messages' => $messages,
            'error' => $error
        ]);
    }
}

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.