Код 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ключ. Если он находит ключ компонента, он будет
Используйте RequireJSдля возврата экземпляра модуля - как если бы модуль был вызван в requirejs/ defineзависимость.
Вызовите этот экземпляр модуля как конструктор JavaScript
Сохранить полученный объект в 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объекта.