Magento 2 - Alan Storm 博客 - 介绍 UI 组件

9.27K 浏览翻译

Magento 2 - Alan Storm 博客 - 介绍 UI 组件

来源: https://alanstorm.com/magento_2_introducing_ui_components/

返回文章列表

UI 组件是组建用户界面元素的一个强有力的方法,并且很多新的后台控制台都建立在这个功能之上。今天我们将深入了解 UI 组件系统的目标,包括尽可能地深入了解执行细节,并且最后结束会用 pestle 生成 grid/listing UI 组件。

从理论上来讲

理解 UI 组件目的最简单的方法是讨论 Magento 1 的后台用户界面形成的代码。这是一个 Magento 1 布局更新 XML 代码的例子。



    
    
        
    
    
        
    
    
        
        
    

那是 4 个添加到产品编辑表单中的单独的布局更新 XML 节点。如果你考虑可重用的 节点之后布局更新 XML。


    
        1
        
        
        
        
        
        
        
        js_cssprototype/windows/themes/default.css
        lib/prototype/windows/themes/magento.css
    

你看给一个页面添加产品编辑表单甚至更复杂。

UI 组件背后的目的就是想隐藏这种复杂。Magento 2 为它的布局句柄 XML 文件引入了一个新的 标签(Magento 2 句柄 XML 文件和 Magento 1 布局更新 XML 文件相似)。在 Magento 2 中,你可以按照以下配置给一个页面添加产品编辑表单。

通过引入 这一概念,Magento 2 让在不同的地方重用不同的组件变得更容易。虽然可以在不同地方放置 Mangento 1 UI 表单和栅格,但是你需要知道这个特定的组件由哪个 block 和 JavaScript 文件组成的。Mangento 1 的方法偶然间让设置一个网格列表和表单变的容易,所以组件几乎就生效了。

Magento 2 的 UI 组件打算解决这个难题,并且大大地简化了每个人的布局句柄 XML 文件。

实际情况
虽然刚我们说到一切已经足够了,但现实中的 UI 组件系统并没有蓝图中那么美好。这是因为 UI 组件系统还有其他几个目的,并且这些目标带来了相当大的复杂度。
就我所知,UI 组件系统
  • 简化了布局句柄 XML 文件
  • 把管理用户界面元素从 HTML+JavaScript 变成了“纯 JavaScript” 自定义组件系统
  • 是由更小的组件建造更复杂的 UI 组件的系统
  • 作为 JSON 为 UI 组件预渲染数据,跟 Mangento 后台数据对象紧密地绑定在一起
  • 使用 AJAX 来更新数据
  • 引入一个新的 DSL 来创建以上所有
UI 组件系统是一个有雄心的系统,像 Magento 2 中的很多东西一样,他还没有特别完善。虽然你可能想要远离一个没有完全成型的系统,但大多数的核心网格和很多表单使用 UI 组件系统来渲染他们的界面元素,并且其他的使用传统 block 和 JavaScript 文件混合完成的。如果你想要建立一个功能完全齐全的模块,你将需要使用 UI 组件系统来实现。
这篇文章剩下的部分代表了我此时对 UI 组件的最好的理解(Magento 2.1 时代)。许多细节在未来可能改变,但是希望核心思想会保持一样。对于想要(或需要)开发后台用户界面元素的开发者来说,没有好的标准措施,和往常一样最好是去看核心团队用他们自己的组件在做什么,模仿它,并且无论何时 Magneto 版本有更新,都密切关注你的模块或者扩展代码。

除非你对复杂的执行细节感兴趣,不然你可能想要直接跳到最后我们用 pestle 创建一个 UI 组件的地方。

纯 JavaScript
在 Magento 2 的后台,如果你去到 Content -> Block ,你会看到一个在你 Magento 系统中的所有 CMS Block 的网格列表。如果你对 Block 不熟悉,他们是为你的店铺创建有用的大块 HTML 的方式。Block 信息使用 CRUD 模块存储在 Magneto 的后台。
你看到的列表是一个 UI 组件,用以下布局句柄 XML 配置的:



    

如果你对 Magento 的布局 XML 是个新手,那么对于上面内容的平白解读:获取已创建的名为 content 的容器的引用,并且给它添加 cms_block_listing UI 组件。

如果你查看未解析的 HTML 页面资源, 标签代表渲染以下 HTML。


    
        
            
        
    
                         {"*": {"Magento_Ui/js/core/app": {...very large js object...}}}         
如果你完成 Magento 2 JavaScript 进阶 系列,尤其是 初始化 JavaScript 教程,你会知道 x-magneto-init script 标签将会把 Magento_Ui/js/core/app RequireJS 模块作为程序调用,把非常大的对象作为实参传入程序。
不深入执行细节(其中一些你可以在 Stack Exchange 回答中查看),JavaScript 代码最终创建一系列 JavaScript 构造函数对象, Magento 将其作为 KnockoutJS view model 来使用。
在浏览器中渲染界面元素的实际上是 KnockoutJS 。这个 HTML 骨架的外面的 div 使用 Magento 的自定义 KnockoutJS scop 绑定 来绑定一个由 text/x-magento-init JavaScript 创建的 view model 。

然后,UI 组件的渲染由 KnockoutJS “无标签”模板绑定实现。
getTemplate 的调用实际上触发了一些 KnockoutJS 嵌套的模板绑定 —— 从名为 collection.html 的文件开始。通过在浏览器的 XHR 调试窗口中查找 .html 文件,你可以找到这所有模板。如果你对 Magento 的扩展 KnockoutJS 模板到 XHR 或者任何其他 KnockoutJS 代码不熟悉,尝试阅读 Magento 2 的 JavaScript 进阶系列中的 KnockoutJS 集成的文章。同时要牢记 Magento 的核心团队用一些自定义的标签和属性改善了 KnockoutJS ,这可能有点迷惑。

总之,Magento 1 用 HTML 中渲染了一个列表,然后用 JavaScript 增强了用户界面的功能。然而 Magento 2 仍然使用一些 HTML 骨架,也把大部分的用户元素的渲染转换成了 RequireJS 模块和 KnockoutJS 模板。

子组件
如果你进一步查看 x-magento-init JSON 对象,你将会看到有一些嵌套的子 JavaScript 对象。
{
    "*": {
            "Magento_Ui/js/core/app": {
                  "types": /*...*/
                  "components": {
                             "cms_block_listing": {
                                   "children": {
                                            "cms_block_listing": {
                                                 /*...*/
                                                 "children": {
                                                     "listing_top": {
                                                             "type": "container",                                    
                                                             "name": "listing_top",                                    
                                                             "children": {                                        
                                                             "bookmarks": {/*...*/},                                        
                                                             "columns_controls": {/*...*/},                                        
                                                             "fulltext": {/*...*/},                                        
                                                             "listing_filters": {/*...*/},                                        
                                                             "listing_massaction": {/*...*/},                                        
                                                             "listing_paging": {/*...*/}
                                                    },
老程序员注意到返回的节点中名为 children 的节点 将会迷惑 —— 我们认为这是 Magento 1 留下来的。这些子元素他们本身都是功能完整的 UI 组件。cms_block_listing 组件由名为 listing_top,bookmarks 等组成。

正如我们之前提到的,最初的 getTemplate 的调用最终会渲染很多子组件。第一个 KnockoutJS 模板之所以命名为 collection.html 是因为它是很多不同的 UI 组件的集合。不过今天我们涉及这整个渲染过程。

今天我们涉及的是 PHP 开发人员如何控制在 JavaScript 树中渲染什么。如果我们回到 标签。


Magento 使用 uiComponent 的名字来查找一个名为 cms_block_listing.xml 的新 XML 文件。
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
    
        
            cms_block_listing.cms_block_listing_data_source
            cms_block_listing.cms_block_listing_data_source
        
        cms_block_columns
        
            
                add
                Add New Block
                primary
                */*/new
            
        
    
    

这些 UI 组件 XML 文件是一个新的 DSL(domain specific language)。以上的命令告诉 Magento 要:
  1. 为根级别的 listing 节点查询 PHP 类名和默认参数
  2. 用 argument 节点作为构造函数的参数来实例化这个类
Magento 将会查询下面这个文件中的 PHP 类名和默认参数:

#File: vendor/magento/module-ui/view/base/ui_component/etc/definition.xml

    
    
        
            templates/listing/default
            1
            mui/index/render
            
                uiComponent
            
        
        
    
所以,当 Magento 作为 JSON 渲染的时候,它以运行像这样的代码(过于简单的)开始:
$uiComponent = new MagentoUiComponentListing(
    $context, $components, [
            'template'=>'templates/listing/default',
             'save_parameters_in_session'=>'1',
             'client_root'=>'mui/index/render',        
             'config'=>[
                         'component'=>'uiComponent'
              ],        
              'js_config'=>[
                          'provider'=>'',            
                          'deps'=>''
             ],        
             'spinner'=>'cms_block_columns',        
             'buttons'=>[
                         'add'=>[ 
                                        'name'=>'add',                
                                        'label'=>'Add New Block',                
                                        'class'=>'primary',                
                                        'url'=>'*/*/new'
            ]
        ],
    ]
)

以上参数的数据来自于合并在一起的节点。每一个参数有不同影响——但是我们感兴趣的是 templates/listing/default 这个参数。它指的是渲染这个 UI 组件的 XHTML 模板。template/listing/default 这个字符串对应以下这个模板。

#File: vendor/magento//module-ui/view/base/ui_component/templates/listing/default.xhtml

    
        
            
        
    
    

这个 XHTML 模板通过一个完整不同于标准的 Magento 模板的渲染引擎进行渲染的。
Magento 通过调用 PHP 方法替换 {{...}} 这个文字在 UI 组件对象(getName()),或者直接获取一些对象的数据属性({{spinner}})。
聪明的你可能注意到在模板中没有列出 x-magento-init 。UI 组件的渲染 x-magento-init 的部分仍然由 XHTML 渲染引擎处理——这里尤其是 appendLayoutConfiguration 方法的调用:
#File: vendor/magento/module-ui/TemplateEngine/Xhtml/Result.phppublic function __toString()
{    try {        //...
        $this->appendLayoutConfiguration();        $result = $this->compiler->postprocessing($this->template->__toString());
    } catch (Exception $e) {        $this->logger->critical($e->getMessage());        $result = $e->getMessage();
    }    return $result;
}//...public function appendLayoutConfiguration()
{    $layoutConfiguration = $this->wrapContent(        json_encode(            $this->structure->generate($this->component)
        )
    );    $this->template->append($layoutConfiguration);
}//...protected function wrapContent($content)
{    return '';
}
Magento 将 UI 组件对象的结构作为 JSON 字符串渲染,然后把这个字符串添加到模板上。
你问什么是 UI 组件的结构?还记得我们的另一个方法吗?

#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
    
        
            cms_block_listing.cms_block_listing_data_source
            cms_block_listing.cms_block_listing_data_source
        
        cms_block_columns
        
            
                add
                Add New Block
                primary
                */*/new
            
        
    
    
如果你看到这些节点的实际内容
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml

    
        
        
    
        
        

我们看到更多的配置的 UI 组件。UI 组件中任何名字不为 argument 的子节点被当做父对象的子节点。比如,当 Magento 渲染 listing 组件时,它也在 definitions.xml 中查找 listingToolbar,columns 等的类名和参数。

#File: vendor/magento/module-ui/view/base/ui_component/etc/definition.xml

    
    
并且,我们之前用的伪代码像下面这样:
$uiComponent = new MagentoUiComponentListing(...);
$listingToolbar = new MagentoUiComponentContainer(...);
$columns        = new MagentoUiComponentListingColumns(...);
$uiComponent->addComponent($listingToolbar);     
$uiComponent->addComponent($columns);
值得注意的是:这些子组件使用 RequireJS 模块名配置的。
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml

    
        
            Magento_Ui/js/grid/listing
            
        
    

Magento 把这些 RequireJS 模块转化成了 KnockoutJS view model 。如果你查找这些 KnockoutJS view models 的来源——你将会发现是在 view model 构造函数里配置了 KnockoutJS 模板。
#File: vendor/magento//module-ui/view/base/web/js/grid/listing.js
define([    
    'ko',    
    'underscore',    
    'Magento_Ui/js/lib/spinner',    
    'uiLayout',    
'uiCollection'], function (ko, _, loader, layout, Collection) {    'use strict';    return Collection.extend({
        defaults: {
            template: 'ui/grid/listing',
        }        //...
    });
});

数据来源节点
最后,有一个更特殊的 UI 组件子节点 —— 节点。
#File: vendor/magento//module-cms/view/adminhtml/ui_component/cms_block_listing.xml
    
        
            cms_block_listing.cms_block_listing_data_source
            cms_block_listing.cms_block_listing_data_source
        
        cms_block_columns
        
            
                add
                Add New Block
                primary
                */*/new
            
        
    
    
    
        
    
名为 DataSource 的节点仍然是 UI 组件,但是得到了特别待遇。当 Magento 渲染 UI 组件 JSON 的时候,dataSource 节点会获取子结构,并且 Magento 沿着主线渲染他们,第一级组件(使用组件名加上字符串 _data_source 作为 key)。
{
    "*": {
        "Magento_Ui/js/core/app": {
             "types": {/*...*/},
                     "components": {
                            "cms_block_listing": {
                                   "children": {
                                          "cms_block_listing": {/*...*/},
                                                "cms_block_listing_data_source": {
                                                        "type": "dataSource",
                                                         "name": "cms_block_listing_data_source",
                                                         "dataScope": "cms_block_listing",
                                                         "config": {
                                                               "data": {     
                                                                      "items": [],                                    
                                                                      "totalRecords": 0
                                                                },                                
                                                                "component": "Magento_Ui/js/grid/provider", 
                                                                "update_url": "http://magento-2-1-0.dev/admin/mui/index/render/key/e628fdf18db9219474935e85ab3f25b445287503a00a230704b4168c566f8059/",                                
                                                                "storageConfig": {                                    
                                                                     "indexField": "block_id"
                                                                 },                               
                                                                 "params": {
                                                                      "namespace": "cms_block_listing"
                                                                 }
                                                          }
                                                  }
                                        }
                              }
                    }
           }
    }
}

Magento 将在 dataSource 组件中查询组成 UI 组件的实际数据(比如,model 已渲染的数据集合)。

UI 组件渲染 DSL 的总结
好的,信息量很大哈。我写完了,我甚至都不确定我能不能跟上,所以如果你感觉晕头转向也不要担心。这里是高度概括。
  1. UI 组件渲染 x-magento-init 脚本,这个脚本填入了 KnockoutJS view models 的全局注册表。
  2. UI 组件也渲染 HTML 骨架,HTML 使用 KnockoutJS 和自定义 scope 绑定来渲染构成组件的 DOM 节点。
  3. ui_component XML 文件是一个特定领域的语言来表示 UI 组件对象的嵌套的等级,最终,Magento 将会用它来渲染 x-magento-init 脚本的 JSON 。
  4. ui_component 的 XML 节点 name 用来查询 PHP 类以示例化。
  5. Magento 把所以子 节点作为那个类的构造函数的参数。
  6. Magento 使用任何名为 的子节点来渲染用在 UI 组件中的实际数据(比如,网格列表信息)。
  7. 其他任何子节点将会被用在渲染子 UI 组件,这些子 UI 组件将遵循父级的规则。
  8. 顶层 UI 节点配置一个 XHTML 模板,Magento 通过 PHP 渲染它。
  9. UI 组件节点配置 RequireJS 模块,Magento 把 RequireJS 模块当做 KnockoutJS view model 构造函数。

就像你看到的,虽然 UIComponent 标签很好地简化了 Magento 2 的布局句柄 XML 文件,他们仍然隐藏了很多更复杂的 UI 渲染系统,包括前端和后台的 Magento 系统代码,并且要求开发人员理解 Magneto 的自定义 RequireJS 和 KnockoutJS 。

用 Pestle 创建一个 网格列表
从上面可看出,UI 组件系统和 Magento 1 的 XML 系统在复杂度和缺乏使用指导上不相上下。换句话说,实际上像 pestle 那样的代码生成工具对于工作在 Magento 的开发人员来说,可以让事情变得更好。最近一个版本的 pestle 包含了一个 magento2:generate:ui:grid 命令为创建 UI 列表,还有更多的命令即将到来。
我们会通过运行 pestle 来创建 UI 表格。我们假设你已经完成了用于数据库访问的 CRUD 模块的教程,并且用过 Pulsestorm_ToDoCrud 模块。我们同样认为你已经可以用布局句柄 XML 文件来创建一个管理断点,并且有一个可浏览的后台网页。
为了创建一个栅格列表,调用传入以下参数的 pestle 的 magento2:generate:ui:grid 命令。
$ pestle.phar magento2:generate:ui:grid
Which Module? (Pulsestorm_Gridexample)] Pulsestorm_ToDoCrud
Create a unique ID for your Listing/Grid! (pulsestorm_gridexample_log)] pulsestorm_todo_listing
What Resource Collection Model should your listing use? (MagentoCmsModelResourceModelPageCollection)] PulsestormToDoCrudModelResourceModelTodoItemCollection
What's the ID field for you model? (pulsestorm_gridexample_log_id)] pulsestorm_todocrud_todoitem_id

Which Module 参数告诉 pestle 你想要创建你栅格列表的 Magento 模块。一般来说,跟 collection 文件是同一个模块,但是在这个惯例上没有硬性规定。之前的教程里我们已经指定了 Pulsestorm_ToDoCrud 模块。

Create a unique ID for your Listing/Grid! 这个参数是我们想要的 UI 组件 的名称。这将是在 标签中属性 name="" ,也是在磁盘上的 UI 组件 XML 文件的基本文件名。

What Resource Collection Model should your listing use? 这个参数是 model 集合用的类名。我们想要栅格列表展示 Pulsestorm_ToDoCrud model,所以我们使用 PulsestormToDoCrudModelResourceModelTodoItemCollection 这个集合。

What’s the ID field for you model? 这个参数是一个 model 的数据库表的主键数据库列。

在运行了以上命令之后,添加以下 到你模块的布局句柄 XML 文件。


    
                        
             
        
    
完成上述步骤后,清缓存,并且你应该有一个单独简单列出每个PulsestormToDoCrudModelToDoItem model 的 model ID 的 UI Grid 。如果你想要给 model 的标题属性添加一列,就把下面的 节点添加到形成的 UI 组件 XML 文件中。




    

    
        
            
                text
                Item Title
                20
            
        
    

    

除了生成 UI 组件的 pulsestorm_todo_listing.xml 文件,pestle 也生成了一个 “提供数据” 类和一个“操作页面” 类。

这个“数据提供”类包括了 collection 资源 model 。
#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/DataProviders/Pulsestorm/Todo/Listing.php
collection = $collectionFactory->create();
    }
}
并且“页面操作”类负责渲染在最后一列的编辑链接。
#File: app/code/Pulsestorm/ToDoCrud/Ui/Component/Listing/Column/Pulsestormtodolisting/PageActions.php
getData("name");                
                                      $id = "X";                
                                      if(isset($item["pulsestorm_todocrud_todoitem_id"]))
                                      {              
                                            $id = $item["pulsestorm_todocrud_todoitem_id"];
                                      }                
                                      $item[$name]["view"] = [              
                                            "href"=>$this->getContext()->getUrl(                        
                                            "adminhtml/pulsestorm_todo_listing/viewlog",["id"=>$id]),                    
                                            "label"=>__("Edit")
                                      ];
                      }
                 }        
        
                return $dataSource;
       }    

}
虽然没有功能全面完善,但是 magento2:generate:ui:grid 命令可以让你以一个基础的栅格列表配置开始。从那里,应该可以检查 Magento 的核心栅格的类,并且可以复制你在核心模块看到的任何功能。

4

已翻译完成,但是有一些真的太口语化了,实在不知道咋翻译,要是有需要修改的,大家可以评论哈

0

66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 谢谢小威

0

66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 谢谢小威

0

我来给你改进下标题看看:
从正面来讲(我也不知道咋翻译)——UI组件,似乎让一切变得更简单
实际情况(真不知道它在表达啥)——但是,现实仍然存在很多挑战

0



stelazjcn

我来给你改进下标题看看:
从正面来讲(我也不知道咋翻译)——UI组件,似乎让一切变得更简单
实际情况(真不知道它在表达啥)——但是,现实仍然存在很多挑战

谢谢你给了我启发,这个短语本意是正向旋转,咱们是不是可以理解成——从理论上讲,因为下面的详细介绍好像在说这样做的缘由,你真棒,谢谢你哦

0