Magento 2:`getTemplate`敲除函数的绑定方式/位置在哪里?


19

许多Magento后端页面的源代码中都包含以下内容

<!-- ko template: getTemplate() --><!-- /ko -->

我了解(或认为我知道吗?)这<!-- ko template是一个KnockoutJS无容器模板绑定

我不清楚的是- getTemplate()函数调用了什么上下文?在我在线上看到的示例中,通常在后面有一个javascript对象template:。我假设这getTemplate是一个返回对象的javascript函数-但没有名为的全局 javascript函数getTemplate

getTemplate绑定在哪里?或者,可能是一个更好的问题,在Magento后端页面上的哪里发生了KnockoutJS应用程序绑定?

从纯HTML / CSS / JavaScript的角度来看,我对此很感兴趣。我知道Magento 2具有许多配置抽象,因此(理论上)开发人员无需担心实现细节。我对实施细节感兴趣。

Answers:


38

UI组件的PHP代码呈现了如下所示的javascript初始化

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app":{
                "types":{...},
                "components":{...},
            }
        }
    }
</script>       

页面中的这段代码意味着Magento将调用Magento_Ui/js/core/appRequireJS模块以获取回调,然后通过将该{types:..., components:...}JSON对象作为参数传递来调用该回调(data如下)

#File: vendor/magento/module-ui/view/base/web/js/core/app.js
define([
    './renderer/types',
    './renderer/layout',
    'Magento_Ui/js/lib/ko/initialize'
], function (types, layout) {
    'use strict';

    return function (data) {
        types.set(data.types);
        layout(data.components);
    };
});

数据对象包含呈现UI组件所需的所有数据,以及将某些字符串与某些Magento RequireJS模块链接的配置。该映射发生在typeslayoutRequireJS模块中。该应用程序还加载Magento_Ui/js/lib/ko/initializeRequireJS库。该initialize模块启动了Magento的KnockoutJS集成。

/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
/** Loads all available knockout bindings, sets custom template engine, initializes knockout on page */

#File: vendor/magento/module-ui/view/base/web/js/lib/ko/initialize.js
define([
    'ko',
    './template/engine',
    'knockoutjs/knockout-repeat',
    'knockoutjs/knockout-fast-foreach',
    'knockoutjs/knockout-es5',
    './bind/scope',
    './bind/staticChecked',
    './bind/datepicker',
    './bind/outer_click',
    './bind/keyboard',
    './bind/optgroup',
    './bind/fadeVisible',
    './bind/mage-init',
    './bind/after-render',
    './bind/i18n',
    './bind/collapsible',
    './bind/autoselect',
    './extender/observable_array',
    './extender/bound-nodes'
], function (ko, templateEngine) {
    'use strict';

    ko.setTemplateEngine(templateEngine);
    ko.applyBindings();
});

每个单独的bind/...RequireJS模块都为Knockout 设置了一个自定义绑定

extender/...RequireJS模块的一些辅助方法添加到本地KnockoutJS对象。

Magento还在./template/engineRequireJS模块中扩展了Knockout的javascript模板引擎的功能。

最后,Magento调用applyBindings()KnockoutJS对象。通常,这是Knockout程序将视图模型绑定到HTML页面的地方-但是,Magento调用时applyBindings 没有视图模型。这意味着淘汰赛将开始将页面作为视图处理,但没有数据绑定。

在股票淘汰赛设置中,这有点傻。但是,由于前面提到的自定义Knockout绑定,Knockout有很多做事情的机会。

我们对范围绑定感兴趣。您可以在同样由HTML UI组件系统呈现的HTML中看到这一点。

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
    <div data-role="spinner" data-component="customer_listing.customer_listing.customer_columns" class="admin__data-grid-loading-mask">
        <div class="spinner">
            <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">
    </script>
</div>

具体来说,data-bind="scope: 'customer_listing.customer_listing'">属性。当Magento启动时applyBindings,Knockout将看到此自定义scope绑定,并调用./bind/scopeRequireJS模块。应用自定义绑定的能力是纯KnockoutJS。范围绑定的实现是Magento Inc.所做的。

范围绑定的实现在

#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js

该文件的重要位置在这里

var component = valueAccessor(),
    apply = applyComponents.bind(this, el, bindingContext);

if (typeof component === 'string') {
    registry.get(component, apply);
} else if (typeof component === 'function') {
    component(apply);
}

无需赘述,该registry.get方法将使用component变量中的字符串作为标识符提取已经生成的对象,并将其applyComponents作为第三个参数传递给该方法。字符串标识符是scope:customer_listing.customer_listing上面的)的值

applyComponents

function applyComponents(el, bindingContext, component) {
    component = bindingContext.createChildContext(component);

    ko.utils.extend(component, {
        $t: i18n
    });

    ko.utils.arrayForEach(el.childNodes, ko.cleanNode);

    ko.applyBindingsToDescendants(component, el);
}

调用createChildContext将基于已实例化的组件对象创建实质上是一个新的viewModel对象,然后将其div应用于所使用的原始对象的所有后代元素data-bind=scope:

那么,什么是已经实例化的组件对象?还记得打layout回的电话app.js吗?

#File: vendor/magento/module-ui/view/base/web/js/core/app.js

layout(data.components);

layout功能/模块将下降到在传递data.components(再次,此数据来自在经由传递的对象text/x-magento-init)。对于找到的每个对象,它将寻找一个config对象,并在该配置对象中寻找一个component钥匙。如果找到组件密钥,它将

  1. 使用RequireJS返回一个模块实例-仿佛模块都在一个叫做requirejs/ define依赖。

  2. 将该模块实例称为javascript构造函数

  3. 将生成的对象存储在registry对象/模块中

所以,要花很多钱。这是使用

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
    <div data-role="spinner" data-component="customer_listing.customer_listing.customer_columns" class="admin__data-grid-loading-mask">
        <div class="spinner">
            <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
        </div>
    </div>
    <!-- ko template: getTemplate() --><!-- /ko -->
    <script type="text/x-magento-init">
    </script>
</div>

作为起点。该scopecustomer_listing.customer_listing

如果我们从text/x-magento-init初始化中查看JSON对象

{
    "*": {
        "Magento_Ui/js/core/app": {
            /* snip */
            "components": {
                "customer_listing": {
                    "children": {
                        "customer_listing": {
                            "type": "customer_listing",
                            "name": "customer_listing",
                            "children": /* snip */
                            "config": {
                                "component": "uiComponent"
                            }
                        },
                        /* snip */
                    }
                }
            }
        }
    }
}

我们看到该components.customer_listing.customer_listing对象有一个config对象,而config对象有一个component设置为的对象uiComponent。该uiComponent字符串是RequireJS模块。实际上,它是与Magento_Ui/js/lib/core/collection模块相对应的RequireJS别名。

vendor/magento/module-ui/view/base/requirejs-config.js
14:            uiComponent:    'Magento_Ui/js/lib/core/collection',

在中layout.js,Magento具有与以下等效的运行代码。

//The actual code is a bit more complicated because it
//involves jQuery's promises. This is already a complicated 
//enough explanation without heading down that path

require(['Magento_Ui/js/lib/core/collection'], function (collection) {    
    object = new collection({/*data from x-magento-init*/})
}

出于真正的好奇,如果您查看集合模型并遵循其执行路径,您会发现这collection是一个JavaScript对象,lib/core/element/element模块和lib/core/class模块均对其进行了增强。研究这些自定义项超出了此答案的范围。

实例化后,layout.js将其存储object在注册表中。这意味着当淘汰赛开始处理绑定并遇到自定义scope绑定时

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
    <!-- snip -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- snip -->
</div>

Magento将从注册表中获取该对象,并将其绑定为视图模型,以将其绑定到div。换句话说,getTemplate当Knockout调用无标签绑定(<!-- ko template: getTemplate() --><!-- /ko -->)时调用的getTemplate方法是new collection对象上的方法。


1
我不想问您的答案“为什么”,所以更集中的问题是,使用此(看似复杂的)系统调用KO模板,M2会获得什么?
circleix

1
@circlesix它是一个较大的系统的一部分,用于<uiComponents/>从布局XML系统进行呈现。他们获得的好处是能够将同一页面上的视图模型交换为一组不同的标签。
艾伦·斯托姆

16
我不知道该笑还是哭!真是一团糟。
koosa

8
我认为他们正在挖掘自己的坟墓。如果他们不断复杂化这样的事情,公司将停止使用它,因为开发成本
玛丽安齐克Šedaj

2
我只花了大约5个小时来弄清楚如何将自定义行为绑定到由所有这些“魔术”呈现的表单上。问题的一部分是,这个高度通用的框架需要您经历很多层,直到您有机会了解如何做事。跟踪特定配置的来源也变得非常繁琐。
greenone83

12

任何敲除JS模板的绑定都在模块的.xml文件中进行。以Checkout模块为例,您可以在以下位置找到content模板的配置vendor/magento/module-checkout/view/frontend/layout/default.xml

<block class="Magento\Checkout\Block\Cart\Sidebar" name="minicart" as="minicart" after="logo" template="cart/minicart.phtml">
    <arguments>
        <argument name="jsLayout" xsi:type="array">
            <item name="types" xsi:type="array"/>
                <item name="components" xsi:type="array">
                    <item name="minicart_content" xsi:type="array">
                        <item name="component" xsi:type="string">Magento_Checkout/js/view/minicart</item>
                            <item name="config" xsi:type="array">
                                <item name="template" xsi:type="string">Magento_Checkout/minicart/content</item>
                            </item>

在此文件中,您可以看到block类具有定义“ jsLayout”并调用的节点<item name="minicart_content" xsi:type="array">。这只是逻辑上的循环,但如果您在其中vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtml,则会看到以下行:

<div id="minicart-content-wrapper" data-bind="scope: 'minicart_content'">
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

因此,数据绑定指示在哪里寻找任何嵌套模板,在这种情况下,它是逻辑(或在组合模型-视图-视图模型系统Magento_Checkout/js/view/minicartvendor/magento/module-checkout/view/frontend/web/js/view/minicart.js为MV)的形式,而您Magento_Checkout/minicart/content(或在组合模型-视图-视图模型中为V的形式)系统)进行模板调用。因此,在该位置拉出的模板是vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html

确实,一旦习惯了使用.xml,就不难弄清楚。如果您可以克服残破的英语,我在这里学到的大部分内容。但是到目前为止,我觉得淘汰赛集成是M2中记录最少的部分。


2
有用的信息,所以+1,但是对于每个问题,我知道Magento都有抽象可以处理这个问题-但我对实现细节本身很好奇。即-当您在该XML文件中配置某些内容时,magento会执行其他操作以确保您配置的值执行第三项操作。我对其他和第三件事感兴趣。
艾伦·斯托姆

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.