UI components 完整案例

题外话

目前 Magento 2 官网上关于 UI components 的介绍文档 依然不够实用,如果要实践还是要依靠社区内容,以及自己参考 Magento 2 自带的模块。

之前发布了两篇关于 UI components 的文章,一篇是 Alan Storm 的 Magento 2 的 UI Components 介绍(翻译),另一篇是 How to Create Admin Grid in Magento 2,但这篇文章只用到了 Listing component ,没有继续下去,完成完整的增删改查。此外,该文章末尾的参考文档,其实是很有参考价值的,博主深挖到了文档对应 github 地址。

文档:How to Create Admin Grid in Magento 2
代码:mageplaza/magento-2-sample-module

上面的模块基于 Magento 2.1 ,使用了 ui component 的 listng component ,但是 form 部分是用的是 layout 方式而不是 component 方式。ui components 的 xml 写法 2.1 和 2.2 有一些差别,但是 2.2 兼容 2.1 的写法。

比如 2.1.7 中 vendor\magento\module-customer\view\base\ui_component\customer_form.xml 中的写法:

    <dataSource name="customer_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Customer\Model\Customer\DataProvider</argument>
            <argument name="name" xsi:type="string">customer_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="submit_url" xsi:type="url" path="customer/index/save"/>
                    <item name="validate_url" xsi:type="url" path="customer/index/validate"/>
                </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>

在 2.2.0 版本中是这样的:

    <dataSource name="customer_form_data_source">
        <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>
        <settings>
            <validateUrl path="customer/index/validate"/>
            <submitUrl path="customer/index/save"/>
        </settings>
        <dataProvider class="Magento\Customer\Model\Customer\DataProvider" name="customer_form_data_source">
            <settings>
                <requestFieldName>id</requestFieldName>
                <primaryFieldName>entity_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>

该模块在 2.2.0 版本上 di:compile 的时候会报错,主要是因为 2.2.0 中类的变动,后面还会提到,但不影响执行和研究。总的来说,是一个很好的例子,有兴趣的童鞋可以继续研究。

本文中项目的出发点是使用 UI components 完成一个完整的增删改查。和参考文档不同的是,form 部分用的是 form component 。

具体的功能点如下:

  • ui listing 的展示、筛选
  • 单个记录的增加(form component)
  • 单个记录的修改(form component)
  • 单个记录的删除
  • 批量删除
  • ui listing 的行内批量修改

Magento_Ui 模块中的 readme.md 是这样介绍的:
The Magento\Ui module introduces a set of common UI components, which could be used and configured via layout XML files.

他是为了统一 UI ,希望可以通过 layout xml 来进行配置和使用。但目前看来模块还够不稳定。

实践

创建一个模块

创建模块 ThankIT_UIcomponents

ThankIT\UIcomponents\registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'ThankIT_UIcomponents',
    __DIR__
);

ThankIT\UIcomponents\etc\module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="ThankIT_UIcomponents" setup_version="1.0.0" />
</config>

创建数据表和 model 等

ThankIT\UIcomponents\Setup\InstallSchema.php

<?php
namespace ThankIT\UIcomponents\Setup;

class InstallSchema implements \Magento\Framework\Setup\InstallSchemaInterface
{
    /**
     * install tables
     *
     * @param \Magento\Framework\Setup\SchemaSetupInterface $setup
     * @param \Magento\Framework\Setup\ModuleContextInterface $context
     * @return void
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(
        \Magento\Framework\Setup\SchemaSetupInterface $setup,
        \Magento\Framework\Setup\ModuleContextInterface $context
    ) {
        $installer = $setup;
        $installer->startSetup();
        if (!$installer->tableExists('thankit_uicomponents_post')) {
            $table = $installer->getConnection()->newTable(
                $installer->getTable('thankit_uicomponents_post')
            )
                ->addColumn(
                    'post_id',
                    \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                    null,
                    [
                        'identity' => true,
                        'nullable' => false,
                        'primary'  => true,
                        'unsigned' => true,
                    ],
                    'Post ID'
                )
                ->addColumn(
                    'name',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    ['nullable => false'],
                    'Post Name'
                )
                ->addColumn(
                    'url_key',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post URL Key'
                )
                ->addColumn(
                    'post_content',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    '64k',
                    [],
                    'Post Post Content'
                )
                ->addColumn(
                    'tags',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Tags'
                )
                ->addColumn(
                    'status',
                    \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                    1,
                    [],
                    'Post Status'
                )
                ->addColumn(
                    'featured_image',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Featured Image'
                )
                ->addColumn(
                    'sample_country_selection',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    3,
                    [],
                    'Post Sample Country Selection'
                )
                ->addColumn(
                    'sample_upload_file',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    255,
                    [],
                    'Post Sample File'
                )
                ->addColumn(
                    'sample_multiselect',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    '64k',
                    [],
                    'Post Sample Multiselect'
                )
                ->addColumn(
                    'created_at',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
                    null,
                    [],
                    'Post Created At'
                )
                ->addColumn(
                    'updated_at',
                    \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
                    null,
                    [],
                    'Post Updated At'
                )
                ->setComment('Post Table');
            $installer->getConnection()->createTable($table);
            $installer->getConnection()->addIndex(
                $installer->getTable('thankit_uicomponents_post'),
                $setup->getIdxName(
                    $installer->getTable('thankit_uicomponents_post'),
                    ['name', 'url_key', 'post_content', 'tags', 'featured_image', 'sample_upload_file'],
                    \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
                ),
                ['name', 'url_key', 'post_content', 'tags', 'featured_image', 'sample_upload_file'],
                \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_FULLTEXT
            );
        }
        $installer->endSetup();
    }
}

ThankIT\UIcomponents\Model\Post.php

<?php
namespace ThankIT\UIcomponents\Model;
class Post extends \Magento\Framework\Model\AbstractModel
{

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('ThankIT\UIcomponents\Model\ResourceModel\Post');
    }
}

ThankIT\UIcomponents\Model\ResourceModel\Post.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel;

class Post extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * Date model
     *
     * @var \Magento\Framework\Stdlib\DateTime\DateTime
     */
    protected $_date;

    /**
     * constructor
     *
     * @param \Magento\Framework\Stdlib\DateTime\DateTime $date
     * @param \Magento\Framework\Model\ResourceModel\Db\Context $context
     */
    public function __construct(
        \Magento\Framework\Stdlib\DateTime\DateTime $date,
        \Magento\Framework\Model\ResourceModel\Db\Context $context
    ) {
        $this->_date = $date;
        parent::__construct($context);
    }

    /**
     * Initialize resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('thankit_uicomponents_post', 'post_id');
    }

    /**
     * before save callback
     *
     * @param \Magento\Framework\Model\AbstractModel|\Mageplaza\HelloWorld\Model\Post $object
     * @return $this
     */
    protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
    {
        $object->setUpdatedAt($this->_date->date());
        if ($object->isObjectNew()) {
            $object->setCreatedAt($this->_date->date());
        }
        return parent::_beforeSave($object);
    }
}

ThankIT\UIcomponents\Model\ResourceModel\Post\Collection.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel\Post;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    // 这个很有意思,本来应该取到 Model\ResourceModel\Post 中设置的值
    // 如果这里不设置,那么 massDelete 中 filter 中 getIdFieldName 为空
    // 所以这里加上了,但是为啥呢?
    protected $_idFieldName = 'post_id';

    protected function _construct()
    {
        $this->_init('ThankIT\UIcomponents\Model\Post', 'ThankIT\UIcomponents\Model\ResourceModel\Post');
    }
}

后台入口

ThankIT\UIcomponents\etc\adminhtml\menu.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
       <add id="ThankIT_UIcomponents::posts" title="ThankIT" module="ThankIT_UIcomponents" sortOrder="51" resource="ThankIT_UIcomponents::posts"/>
       <add id="ThankIT_UIcomponents::post" title="Posts" module="ThankIT_UIcomponents" sortOrder="10" action="thankit_uicomponent/post/index" resource="ThankIT_UIcomponents::post" parent="ThankIT_UIcomponents::posts"/>
    </menu>
</config>

ThankIT\UIcomponents\etc\adminntml\routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
    <router id="admin">
        <route id="thankit_uicomponent" frontName="thankit_uicomponent">
            <module name="ThankIT_UIcomponents" after="Magento_Ui"/>
        </route>
    </router>
</config>

ThankIT\UIcomponents\etc\acl.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
     <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="ThankIT_UIcomponents::posts" title="ThankIT" sortOrder="51">
                    <resource id="ThankIT_UIcomponents::post" title="Posts" sortOrder="10"/>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

grid 的实现

admin grid 效果

ThankIT\UIcomponents\Controller\Adminhtml\Post\Index.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Index extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $resultPageFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $page = $this->resultPageFactory->create();
        // 如果没有下面这句,title 是 Magento Admin ,active menu 正常
        // 如果下面这句的 id 不存在,则 active menu 不正常 title 还是 Magento Admin
        $page->setActiveMenu('ThankIT_UIcomponents::post');
        // title 替换成 Posts
        $page->getConfig()->getTitle()->prepend((__('Posts')));

        // class: Magento\Backend\Model\View\Result\Page
        // addBreadcrumb($label, $title, $link = null)
        $page->addBreadcrumb(__('ThankIT'), __('ThankIT'));
        $page->addBreadcrumb(__('Hello World'), __('Manage Blogs'));
        return $page;
    }

    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed(static::ADMIN_RESOURCE);
    }

}

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_listing"/>
        </referenceContainer>
    </body>
</page>

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml

<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
            <item name="deps" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">thankit_uicomponents_post_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="thankit_uicomponents_post_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
            <argument name="name" xsi:type="string">thankit_uicomponents_post_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">post_id</argument>
            <argument name="requestFieldName" xsi:type="string">post_id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </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/grid/provider</item>
            </item>
        </argument>
    </dataSource>
    <container name="listing_top">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="template" xsi:type="string">ui/grid/toolbar</item>
                <item name="stickyTmpl" xsi:type="string">ui/grid/sticky/toolbar</item>
            </item>
        </argument>
        <bookmark name="bookmarks">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="namespace" xsi:type="string">thankit_uicomponents_post_listing</item>
                    </item>
                </item>
            </argument>
        </bookmark>
        <component name="columns_controls">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsData" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
                    </item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item>
                    <item name="displayArea" xsi:type="string">dataGridActions</item>
                </item>
            </argument>
        </component>
        <exportButton name="export_button">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                </item>
            </argument>
        </exportButton>
        <filterSearch name="fulltext">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing_data_source</item>
                    <item name="chipsProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.listing_filters_chips</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.search</item>
                    </item>
                </item>
            </argument>
        </filterSearch>
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="columnsProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.filters</item>
                    </item>
                    <item name="templates" xsi:type="array">
                        <item name="filters" xsi:type="array">
                            <item name="select" xsi:type="array">
                                <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                                <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
                            </item>
                        </item>
                    </item>
                    <item name="childDefaults" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.listing_filters</item>
                        <item name="imports" xsi:type="array">
                            <item name="visible" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.${ $.index }:visible</item>
                        </item>
                    </item>
                </item>
                <item name="observers" xsi:type="array">
                    <item name="column" xsi:type="string">column</item>
                </item>
            </argument>
        </filters>
        <massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="thankit_uicomponent/post/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Posts</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Posts?</item>
                        </item>
                    </item>
                </argument>
            </action>
            <action name="edit">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">edit</item>
                        <item name="label" xsi:type="string" translate="true">Edit</item>
                        <item name="callback" xsi:type="array">
                            <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                            <item name="target" xsi:type="string">editSelected</item>
                        </item>
                    </item>
                </argument>
            </action>
        </massaction>
        <paging name="listing_paging">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="storageConfig" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top.bookmarks</item>
                        <item name="namespace" xsi:type="string">current.paging</item>
                    </item>
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                </item>
            </argument>
        </paging>
    </container>

    <columns name="thankit_uicomponents_post_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">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="enabled" xsi:type="boolean">true</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                    <item name="clientConfig" xsi:type="array">
                        <item name="saveUrl" xsi:type="url" path="thankit_uicomponent/post/inlineEdit"/>
                        <item name="validateBeforeSave" xsi:type="boolean">false</item>
                    </item>
                </item>
                <!-- column clickable -->
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_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>
        <selectionsColumn name="ids">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">true</item>
                    <item name="resizeDefaultWidth" xsi:type="string">55</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
        </selectionsColumn>
        <column name="post_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="url_key">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">URL Key</item>
                    <item name="dataType" xsi:type="string">text</item>
                </item>
            </argument>
        </column>
        <column name="tags">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">Tags</item>
                    <item name="dataType" xsi:type="string">text</item>
                </item>
            </argument>
        </column>
        <column name="status">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Yesno</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="sample_country_selection">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Magento\Config\Model\Config\Source\Locale\Country</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="label" xsi:type="string" translate="true">Sample Country Selection</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="dataType" xsi:type="string">select</item>
                </item>
            </argument>
        </column>
        <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created</item>
                </item>
            </argument>
        </column>
        <column name="updated_at" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Modified</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">107</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>

    <container name="sticky">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/sticky/sticky</item>
                <item name="toolbarProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.listing_top</item>
                <item name="listingProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns</item>
            </item>
        </argument>
    </container>
</listing>

这个文件是关键。
这里用的 2.1 的写法,和 How to Create Admin Grid in Magento 2 是一样的。

这里声明了一个 dataSource thankit_uicomponents_post_listing_data_source

我们需要在 di.xml 创建它

ThankIT\UIcomponents\etc\di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="thankit_uicomponents_post_listing_data_source" xsi:type="string">ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <type name="ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">thankit_uicomponents_post</argument>
            <argument name="resourceModel" xsi:type="string">ThankIT\UIcomponents\Model\ResourceModel\Post</argument>
        </arguments>
    </type>
</config>

这里和之前文章中的相比,有一些简化。

ThankIT\UIcomponents\Model\ResourceModel\Post\Grid\Collection.php

<?php
namespace ThankIT\UIcomponents\Model\ResourceModel\Post\Grid;

use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Psr\Log\LoggerInterface as Logger;

class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
    public function __construct(
        EntityFactory $entityFactory,
        Logger $logger,
        FetchStrategy $fetchStrategy,
        EventManager $eventManager,
        $mainTable,
        $resourceModel
    ) {
        parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
    }
}

这个 collection 和之前不一样,继承的是不同的父类,这也是之前代码在 2.2 上 di:compile 会报错的原因。

add new

新增

在 ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_index.xml 中,关键代码如下:

<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        ...
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/new</item>
            </item>
        </item>
    </argument>

这里我们给了 url thankit_uicomponent/post/new

下面创建它的 controller 和对应的 layout
ThankIT\UIcomponents\Controller\Adminhtml\Post\NewAction.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class NewAction extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $resultPageFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $page = $this->resultPageFactory->create();

        $page->setActiveMenu('ThankIT_UIcomponents::post');

        $page->getConfig()->getTitle()->prepend((__('New Post')));

        $page->addBreadcrumb(__('ThankIT'), __('ThankIT'));
        $page->addBreadcrumb(__('Hello World'), __('Manage Blogs'));
        return $page;
    }

}

这里要用 NewAction.php 作为名字。

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_new.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_form"/>
        </referenceContainer>
    </body>
</page>

关键的地方来了,这里用的是 2.2 的写法,参考了 vendor\magento\module-catalog\view\adminhtml\ui_component\category_form.xml 的用法。

ThankIT\UIcomponents\view\adminhtml\ui_component\thankit_uicomponents_post_form.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">thankit_uicomponents_post_form.post_form_data_source</item>
            <item name="deps" xsi:type="string">thankit_uicomponents_post_form.post_form_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="save" xsi:type="array">
                <item name="name" xsi:type="string">save</item>
                <item name="label" xsi:type="string" translate="true">Save Post</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/save</item>
            </item>
            <item name="back" xsi:type="array">
                <item name="name" xsi:type="string">back</item>
                <item name="label" xsi:type="string" translate="true">Back</item>
                <item name="class" xsi:type="string">back</item>
                <item name="url" xsi:type="string">thankit_uicomponent/post/index</item>
            </item>
        </item>
    </argument>
    <dataSource name="post_form_data_source">
        <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>
        <settings>
            <submitUrl path="thankit_uicomponent/post/save"/>
        </settings>
        <dataProvider class="ThankIT\UIcomponents\Model\Post\DataProvider" name="post_form_data_source">
            <settings>
                <requestFieldName>post_id</requestFieldName>
                <primaryFieldName>post_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="post_form">
        <settings>
            <label translate="true">Post Information</label>
            <collapsible>false</collapsible>
        </settings>
        <field name="post_id" formElement="hidden">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">Post ID</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="name" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
            <settings>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="url_key" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">url key</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="post_content" formElement="wysiwyg" template="ui/form/field">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">post content</item>
                </item>
            </argument>
            <formElements>
                <wysiwyg>
                    <settings>
                        <rows>8</rows>
                        <wysiwyg>true</wysiwyg>
                    </settings>
                </wysiwyg>
            </formElements>
        </field>
        <field name="featured_image" formElement="fileUploader">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                </item>
            </argument>
            <settings>
                <elementTmpl>ui/form/element/uploader/uploader</elementTmpl>
                <dataType>string</dataType>
                <label translate="true">Featured Image</label>
                <visible>true</visible>
                <required>false</required>
            </settings>
            <formElements>
                <fileUploader>
                    <settings>
                        <required>false</required>
                        <uploaderConfig>
                            <param xsi:type="url" name="url" path="catalog/category_image/upload"/>
                        </uploaderConfig>
                        <previewTmpl>Magento_Catalog/image-preview</previewTmpl>
                    </settings>
                </fileUploader>
            </formElements>
        </field>
        <field name="tags" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">tags</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
        </field>
        <field name="status" formElement="select">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">status</item>
                </item>
            </argument>
            <settings>
                <dataType>number</dataType>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="Magento\Config\Model\Config\Source\Yesno"/>
                    </settings>
                </select>
            </formElements>
        </field>
        <field name="sample_country_selection" component="Magento_Ui/js/form/element/country" formElement="select">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">post</item>
                    <item name="label" xsi:type="string" translate="true">sample country selection</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="\Magento\Config\Model\Config\Source\Locale\Country"/>
                    </settings>
                </select>
            </formElements>
        </field>
    </fieldset>
</form>

这里面声明了

<dataProvider class="ThankIT\UIcomponents\Model\Post\DataProvider" name="post_form_data_source">
            <settings>
                <requestFieldName>post_id</requestFieldName>
                <primaryFieldName>post_id</primaryFieldName>
            </settings>
</dataProvider>

ThankIT\UIcomponents\Model\Post\DataProvider.php

<?php
namespace ThankIT\UIcomponents\Model\Post;

use ThankIT\UIcomponents\Model\ResourceModel\Post\CollectionFactory as postCollectionFactory;

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{
    protected $request;

    protected $loadedData;

    public function __construct(
        postCollectionFactory $postCollectionFactory,
        \Magento\Framework\App\RequestInterface $request,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        $name,
        $primaryFieldName,
        $requestFieldName,
        array $meta = [],
        array $data = []
    ) {
        $this->collection  = $postCollectionFactory->create();
        $this->postFactory = $postFactory;
        $this->request     = $request;
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {

        $requestId = $this->request->getParam($this->requestFieldName);

        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $post = $this->postFactory->create();
        // 如果是 update 操作
        if ($requestId) {
            $post->load($requestId);
            if (!$post->getId()) {
                throw NoSuchEntityException::singleField('id', $requestId);
            }
            $postData = $post->getData();

            $this->loadedData[$requestId]['post_form'] = $postData;
        } else {
            // new 操作
            $this->loadedData = [];
        }

        return $this->loadedData;
    }
}

这里遇到过的错误类似:

PHP Fatal error: Method Magento\Ui\TemplateEngine\Xhtml\Result::__toString() must not throw an exception, caught Error: Call to a member function addFieldToFilter() on null in /data/sites/project_name/vendor/magento/module-ui/Component/Wrapper/UiComponent.php on line 0

通过:$this->collection = $postCollectionFactory->create(); 解决。

getData() 可以先不要管,返回 [] 也行。

保存

ThankIT\UIcomponents\view\adminhtml\ui_component\thankit_uicomponents_post_form.xml 中的保存按钮路径是 thankit_uicomponent/post/save

ThankIT\UIcomponents\Controller\Adminhtml\Post\Save.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Save extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory
    ) {
        parent::__construct($context);
        $this->postFactory = $postFactory;
    }

    public function execute()
    {
        $data = $this->getRequest()->getParam('post_form');

        // var_dump($data);
        // return;

        $post = $this->postFactory->create();

        $postId = $data['post_id'];
        unset($data['post_id']);

        // 如果是 update
        if ($postId) {
            $post->load($postId);

            // setData 的用法有点怪 在他前面的都被 overwrite 了
            // 如果没有 post_id 则会新增
            $post->setData($data);
            $post->setData('post_id', $postId);
        } else {
            $post->setData($data);
        }

        // todo 处理图片
        if (array_key_exists('featured_image', $data)) {
            $featuredImage = $data['featured_image'];
            unset($data['featured_image']);

            // $post->setFeaturedImage($featuredImage[0]['url']);
        }
        $post->save();

        $this->_redirect('*/*/index');
    }

}

这个仅是参考,所写的考虑也不周全,尤其是图片处理部分,根本就没有处理。

update

修改

其实 update 和新增使用的是相同的 form ,相同的 save

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml 中

<actionsColumn name="actions" class="ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="resizeEnabled" xsi:type="boolean">false</item>
                    <item name="resizeDefaultWidth" xsi:type="string">107</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
</actionsColumn>

ThankIT\UIcomponents\Ui\Component\Listing\Column\PostActions.php

<?php
namespace ThankIT\UIcomponents\Ui\Component\Listing\Column;

class PostActions extends \Magento\Ui\Component\Listing\Columns\Column
{
    /**
     * Url path  to edit
     *
     * @var string
     */
    const URL_PATH_EDIT = 'thankit_uicomponent/post/edit';

    /**
     * Url path  to delete
     *
     * @var string
     */
    const URL_PATH_DELETE = 'thankit_uicomponent/post/delete';

    /**
     * URL builder
     *
     * @var \Magento\Framework\UrlInterface
     */
    protected $_urlBuilder;

    /**
     * constructor
     *
     * @param \Magento\Framework\UrlInterface $urlBuilder
     * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
     * @param \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory
     * @param array $components
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\UrlInterface $urlBuilder,
        \Magento\Framework\View\Element\UiComponent\ContextInterface $context,
        \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory,
        array $components = [],
        array $data = []
    ) {
        $this->_urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as &$item) {
                if (isset($item['post_id'])) {
                    $item[$this->getData('name')] = [
                        'edit'   => [
                            'href'  => $this->_urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'post_id' => $item['post_id'],
                                ]
                            ),
                            'label' => __('Edit'),
                        ],
                        'delete' => [
                            'href'    => $this->_urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'post_id' => $item['post_id'],
                                ]
                            ),
                            'label'   => __('Delete'),
                            'confirm' => [
                                'title'   => __('Delete "${ $.$data.name }"'),
                                'message' => __('Are you sure you wan\'t to delete the Post "${ $.$data.name }" ?'),
                            ],
                        ],
                    ];
                }
            }
        }
        return $dataSource;
    }
}

所以我们的 edit 对应的 url 是 thankit_uicomponent/post/edit
delete 对应的 url 是 thankit_uicomponent/post/delete 并且他们都会将 post_id 传递过去。

ThankIT\UIcomponents\Controller\Adminhtml\Post\Edit.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Edit extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        // $data = $this->getRequest()->getParams();
        $page = $this->resultPageFactory->create();
        $page->setActiveMenu('ThankIT_UIcomponents::post');
        // title 替换成 Posts
        $page->getConfig()->getTitle()->prepend((__('Edit Post')));
        return $page;
    }

}

ThankIT/UIcomponents/view/adminhtml/layout/thankit_uicomponent_post_edit.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="thankit_uicomponents_post_form"/>
        </referenceContainer>
    </body>
</page>

thankit_uicomponents_post_form 我们之前已经声明过了。

delete

删除

ThankIT\UIcomponents\Controller\Adminhtml\Post\Delete.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class Delete extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        $id = $this->getRequest()->getParam('post_id');
        if ($id) {
            $post = $this->postFactory->create();
            $post->load($id);
            $post->delete();
            $this->messageManager->addSuccess(__('The Post has been deleted.'));
        }
        $this->_redirect('*/*/index');
    }

}

批量删除

批量删除

ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml 中定义批量删除对应的 url 为 thankit_uicomponent/post/massDelete

<massaction name="listing_massaction">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="selectProvider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                    <item name="indexField" xsi:type="string">post_id</item>
                </item>
            </argument>
            <action name="delete">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="type" xsi:type="string">delete</item>
                        <item name="label" xsi:type="string" translate="true">Delete</item>
                        <item name="url" xsi:type="url" path="thankit_uicomponent/post/massDelete"/>
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Delete Posts</item>
                            <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected Posts?</item>
                        </item>
                    </item>
                </argument>
            </action>
             ...
</massaction>

ThankIT\UIcomponents\Controller\Adminhtml\Post\MassDelete.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class MassDelete extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Ui\Component\MassAction\Filter $filter,
        \ThankIT\UIcomponents\Model\ResourceModel\Post\CollectionFactory $collectionFactory,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        parent::__construct($context);
        $this->postFactory       = $postFactory;
        $this->resultPageFactory = $resultPageFactory;
        $this->collectionFactory = $collectionFactory;
        $this->filter            = $filter;
    }

    public function execute()
    {
        // $data = $this->getRequest()->getParam('selected');
        // var_dump($data);

        $collection     = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $page) {
            $page->delete();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));

        $this->_redirect('*/*/index');

    }

}

批量修改

批量行内修改
ThankIT/UIcomponents/view/adminhtml/ui_component/thankit_uicomponents_post_listing.xml

...
<container name="listing_top">
...
<massaction name="listing_massaction">
    ...
    <action name="edit">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">edit</item>
                <item name="label" xsi:type="string" translate="true">Edit</item>
                <item name="callback" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns_editor</item>
                    <item name="target" xsi:type="string">editSelected</item>
                </item>
            </item>
        </argument>
    </action>
</massaction>
</container>
<columns name="thankit_uicomponents_post_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">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_columns.ids</item>
                <item name="enabled" xsi:type="boolean">true</item>
                <item name="indexField" xsi:type="string">post_id</item>
                <item name="clientConfig" xsi:type="array">
                    <item name="saveUrl" xsi:type="url" path="thankit_uicomponent/post/inlineEdit"/>
                    <item name="validateBeforeSave" xsi:type="boolean">false</item>
                </item>
            </item>
            <!-- column clickable -->
            <item name="childDefaults" xsi:type="array">
                <item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">thankit_uicomponents_post_listing.thankit_uicomponents_post_listing.thankit_uicomponents_post_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="filter" xsi:type="string">text</item>
                <item name="editor" xsi:type="array">
                    <item name="editorType" xsi:type="string">text</item>
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                </item>
                <item name="label" xsi:type="string" translate="true">Name</item>
            </item>
        </argument>
   </column>
   ....
</columns>
...

批量修改的 url 是 thankit_uicomponent/post/inlineEdit

这是一个 ajax 请求。

ThankIT\UIcomponents\Controller\Adminhtml\Post\InlineEdit.php

<?php
namespace ThankIT\UIcomponents\Controller\Adminhtml\Post;

class InlineEdit extends \Magento\Backend\App\Action
{
    const ADMIN_RESOURCE = 'ThankIT_UIcomponents::post';
    protected $postFactory;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \ThankIT\UIcomponents\Model\PostFactory $postFactory,
        \Magento\Framework\Controller\Result\JsonFactory $jsonFactory
    ) {
        parent::__construct($context);
        $this->postFactory = $postFactory;
        $this->jsonFactory = $jsonFactory;

    }

    public function execute()
    {
        // 请求类似 http://192.168.0.251/M220/admin/thankit_uicomponent/post/inlineEdit/key/5fae8c36c82d28ec5e605d8787782dce781512bb4f1f7f14520f00c5cfb5ab53/?isAjax=true
        $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 (array_keys($postItems) as $postId) {

            $post = $this->postFactory->create()->load($postId);
            try {
                $postData = $postItems[$postId]; //todo: handle dates
                $post->addData($postData);
                $post->save();
            } catch (\Magento\Framework\Exception\LocalizedException $e) {
                $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
                $error      = true;
            } catch (\RuntimeException $e) {
                $messages[] = $this->getErrorWithPostId($post, $e->getMessage());
                $error      = true;
            } catch (\Exception $e) {
                $messages[] = $this->getErrorWithPostId(
                    $post,
                    __('Something went wrong while saving the Post.')
                );
                $error = true;
            }
        }
        return $resultJson->setData([
            'messages' => $messages,
            'error'    => $error,
        ]);

    }

    protected function getErrorWithPostId(\ThankIT\UIcomponents\Model\Post $post, $errorText)
    {
        return '[Post ID: ' . $post->getId() . '] ' . $errorText;
    }

}

3 comments

  1. hugo

    看了博主的不少精彩文章,受益良多。ui-components-完整案例,照着上面的事例代码,post 却出现了Page not found. 404 Error。回头重看文章thankit_uicomponent_post_index.xml 重复出现不同的两套代理。这是否只是代码片段,是否可提供完整的事例代码呢。多谢。

    1. Pisces Post author

      po 出来的代码一定是可以实际操作的完整代码。404 的话,请使用 php bin/magento cache:clean 清理缓存,是路由没有找到。我找了下并没有重复的代码片段呢。

      1. hugo

        OK. 真的是很好的案例。期待更多好的文章。

发表评论

电子邮件地址不会被公开。 必填项已用*标注