Magento 2: Как / Где привязана функция выбивания getTemplate?

19

Многие бэкэнд-страницы Magento содержат следующее в своем исходном коде

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

Я понимаю (или думаю, что понимаю?), Что <!-- ko templateэто привязка шаблона без контейнера KnockoutJS .

Что мне неясно - в каком контексте вызывается getTemplate()функция? В примерах, которые я вижу в Интернете, обычно после javascript есть объект template:. Я предполагаю, что getTemplateэто функция javascript, которая возвращает объект, но нет глобальной названной функции javascript getTemplate.

Где это getTemplateсвязано? Или, возможно, лучший вопрос, где привязка приложения KnockoutJS происходит на бэкэнд-странице Magento?

Я заинтересован в этом с точки зрения чистого HTML / CSS / Javascript. Я знаю, что в Magento 2 есть много абстракций конфигурации, поэтому (теоретически) разработчикам не нужно беспокоиться о деталях реализации. Я заинтересован в деталях реализации.

Алан Сторм
источник

Ответы:

38

Код PHP для компонента пользовательского интерфейса выполняет инициализацию JavaScript, которая выглядит следующим образом

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

Этот бит кода на странице означает, что Magento вызовет Magento_Ui/js/core/appмодуль RequireJS для извлечения обратного вызова, а затем вызовет этот обратный вызов, передавая {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);
    };
});

Объект данных содержит все данные, необходимые для визуализации компонента пользовательского интерфейса, а также конфигурацию, которая связывает определенные строки с определенными модулями Magento RequireJS. Такое сопоставление происходит в модулях typesи layoutRequireJS. Приложение также загружает Magento_Ui/js/lib/ko/initializeбиблиотеку RequireJS. В initializeмодуле пинает интеграцию KnockoutJS Magento в.

/**
 * 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 также расширяет функциональность механизма шаблонов JavaScript Knockout в ./template/engineмодуле RequireJS.

Наконец, Magento вызывает applyBindings()объект KnockoutJS. Обычно в этом случае программа Knockout связывает модель представления с HTML-страницей, однако Magento вызывает applyBindings без модели представления. Это означает, что Knockout начнет обрабатывать страницу как представление, но без привязки к данным.

В настройке Knockout это было бы немного глупо. Тем не менее, из-за ранее упомянутых пользовательских привязок Knockout, у Knockout есть много возможностей что-то сделать.

Мы заинтересованы в привязке объема . Вы можете видеть это в этом HTML, также представленном системой компонентов PHP UI.

<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/scopeмодуль RequireJS. Возможность применить пользовательскую привязку - чисто 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>

в качестве отправной точки. scopeЗначение customer_listing.customer_listing.

Если мы посмотрим на объект JSON из text/x-magento-initинициализации

{
    "*": {
        "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объект, и у этого объекта конфигурации есть componentобъект, который установлен в uiComponent. uiComponentСтрока представляет собой модуль RequireJS. Фактически, это псевдоним RequireJS, соответствующий Magento_Ui/js/lib/core/collectionмодулю.

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

В layout.jsMagento есть код, который эквивалентен следующему.

//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в реестре. Это означает, что когда Knockout начинает обрабатывать привязки и встречает пользовательскую 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
Я не хочу просто задавать вопрос «почему» на ваш ответ, поэтому более сфокусированный вопрос будет таким: какую выгоду получит М2, используя эту (на первый взгляд запутанную) систему для вызова шаблонов КО?
circleix
1
@circlesix Это часть более крупной системы рендеринга <uiComponents/>из системы макетов XML. Преимущества, которые они получают, - это возможность поменять модели представлений на одной странице на другой набор тегов.
Алан Сторм
16
Я не знаю, смеяться или плакать! Какой беспорядок
koosa
8
Я думаю, что они роют свою могилу. Если они продолжат усложнять подобные вещи, компании перестанут использовать это из-за затрат на разработку
Мариан Зеке Шедай
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>

В этом файле вы можете видеть, что класс блока имеет узлы, которые определяют "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/minicartиз vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jsза логику (или MV в нокауты Model-View-View система Model) и у вас есть Magento_Checkout/minicart/content(или V в нокауты Model-View-View Model система) для шаблона вызова. Так что шаблон, который тянется в этом месте, есть vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

На самом деле это не сложно понять, как только вы привыкнете смотреть в .xml. Большую часть этого я узнал здесь, если вы сможете пробиться через сломанный английский. Но до сих пор я чувствую, что интеграция Knockout является наименее документированной частью M2.

circlesix
источник
2
Полезная информация, так что +1, но по вопросу, я знаю, что у Magento есть абстракции, чтобы справиться с этим - но мне любопытно сами детали реализации. То есть - когда вы конфигурируете что-то в этом XML-файле, magento делает что-то еще, чтобы убедиться, что ваши сконфигурированные значения делают третье . Меня интересует что-то еще и третье.
Алан Шторм
4

Я уверен, что глобальный getTemplate метод JS, который вы ищете, определен ниже, app/code/Magento/Ui/view/base/web/js/lib/core/element/element.jsвы можете найти его здесь: https://github.com/magento/magento2/blob/4d71bb4780625dce23274c90e45788a72f345dd9/app/code/Magento/Ui/view/base /web/js/lib/core/element/element.js#L262

Поскольку я разговариваю по телефону, мне трудно узнать, как именно выполняется привязка.

Рафаэль в цифровом пианизме
источник