O código PHP para um componente de interface do usuário renderiza uma inicialização em javascript parecida com esta
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app":{
"types":{...},
"components":{...},
}
}
}
</script>
Esse bit de código na página significa que o Magento chamará o Magento_Ui/js/core/appmódulo RequireJS para buscar um retorno de chamada e, em seguida, chamará esse retorno de chamada que passa no {types:..., components:...}objeto JSON como argumento ( dataabaixo)
#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);
};
});
O objeto de dados contém todos os dados necessários para renderizar o componente da interface do usuário, bem como uma configuração que vincula determinadas seqüências de caracteres com determinados módulos do Magento RequireJS. Esse mapeamento acontece nos módulos RequireJS typese layout. O aplicativo também carrega a Magento_Ui/js/lib/ko/initializebiblioteca RequireJS. O initializemódulo inicia a integração KnockoutJS do 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();
});
Cada indivíduo bind/... módulo RequireJS configura uma única ligação personalizada para Knockout.
Os extender/...módulos RequireJS incluem alguns métodos auxiliares nos objetos KnockoutJS nativos.
O Magento também estende a funcionalidade do mecanismo de modelo javascript do Knockout no ./template/engine módulo RequireJS.
Finalmente, o Magento chama applyBindings()o objeto KnockoutJS. Normalmente é onde um programa Knockout vincula um modelo de exibição à página HTML - no entanto, o Magento chamaapplyBindings sem um modelo de visualização. Isso significa que o Knockout começará a processar a página como uma exibição, mas sem dados vinculados.
Em uma configuração de Knockout de estoque, isso seria um pouco bobo. No entanto, devido às ligações personalizadas do Knockout mencionadas anteriormente, há muitas oportunidades para o Knockout fazer as coisas.
Estamos interessados na ligação do escopo . Você pode ver isso neste HTML, também renderizado pelo sistema PHP UI Component.
<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>
Especificamente, o data-bind="scope: 'customer_listing.customer_listing'">atributo Quando o Magento começar applyBindings, o Knockout verá essa scopeligação personalizada e invocará o ./bind/scopemódulo RequireJS. A capacidade de aplicar uma ligação personalizada é pura KnockoutJS. A implementação da ligação do escopo é algo que a Magento Inc. fez.
A implementação da ligação do escopo está em
#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js
A parte importante deste arquivo está aqui
var component = valueAccessor(),
apply = applyComponents.bind(this, el, bindingContext);
if (typeof component === 'string') {
registry.get(component, apply);
} else if (typeof component === 'function') {
component(apply);
}
Sem entrar em detalhes demais, o registry.getmétodo extrairá um objeto já gerado usando a string na componentvariável como um identificador e passará para o applyComponentsmétodo como o terceiro parâmetro. O identificador de sequência é o valor de scope:(customer_listing.customer_listing acima)
Dentro 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);
}
a chamada para createChildContextcriará o que é, essencialmente, um novo objeto viewModel com base no objeto componente já instanciado e o aplicará a todo o elemento descendente do original divusado data-bind=scope:.
Então, qual é o objeto componente já instanciado ? Lembra da ligação para layoutvoltar app.js?
#File: vendor/magento/module-ui/view/base/web/js/core/app.js
layout(data.components);
A layoutfunção / módulo descerá para o passado data.components(novamente, esses dados provêm do objeto passado via text/x-magento-init). Para cada objeto que encontrar, ele procurará um configobjeto e, nesse objeto de configuração, procurará uma componentchave. Se encontrar uma chave de componente, será
Use RequireJSpara retornar uma instância do módulo - como se o módulo fosse chamado em requirejs/ definedependency.
Chame essa instância de módulo como um construtor javascript
Armazene o objeto resultante no registryobjeto / módulo
Então, isso é muito necessário. Aqui está uma revisão rápida, usando
<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>
como ponto de partida. O scopevalor é customer_listing.customer_listing.
Se olharmos para o objeto JSON a partir da text/x-magento-initinicialização
{
"*": {
"Magento_Ui/js/core/app": {
/* snip */
"components": {
"customer_listing": {
"children": {
"customer_listing": {
"type": "customer_listing",
"name": "customer_listing",
"children": /* snip */
"config": {
"component": "uiComponent"
}
},
/* snip */
}
}
}
}
}
}
Vemos que o components.customer_listing.customer_listingobjeto tem um configobjeto e esse objeto de configuração tem um componentobjeto definido uiComponent. A uiComponentsequência é um módulo RequireJS. De fato, é um alias RequireJS que corresponde ao Magento_Ui/js/lib/core/collectionmódulo.
vendor/magento/module-ui/view/base/requirejs-config.js
14: uiComponent: 'Magento_Ui/js/lib/core/collection',
No layout.jsMagento, o código de execução é equivalente ao seguinte.
//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*/})
}
Para os realmente curiosos, se você der uma olhada no modelo de coleção e seguir seu caminho de execução, descobrirá que esse collectioné um objeto javascript aprimorado pelo lib/core/element/elementmódulo e pelo lib/core/classmódulo. A pesquisa dessas personalizações está além do escopo desta resposta.
Uma vez instanciado, layout.jsarmazena isso objectno registro. Isso significa que quando o Knockout começa a processar as ligações e encontra a scopeligação personalizada
<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
<!-- snip -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- snip -->
</div>
O Magento buscará esse objeto de volta no registro e o vinculará como o modelo de exibição das coisas dentro do div. Em outras palavras, o getTemplatemétodo chamado quando Knockout chama a ligação sem marca ( <!-- ko template: getTemplate() --><!-- /ko -->) é o getTemplatemétodo no new collectionobjeto.