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/app
mó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 ( data
abaixo)
#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 types
e layout
. O aplicativo também carrega a Magento_Ui/js/lib/ko/initialize
biblioteca RequireJS. O initialize
mó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 scope
ligação personalizada e invocará o ./bind/scope
mó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.get
método extrairá um objeto já gerado usando a string na component
variável como um identificador e passará para o applyComponents
mé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 createChildContext
criará o que é, essencialmente, um novo objeto viewModel com base no objeto componente já instanciado e o aplicará a todo o elemento descendente do original div
usado data-bind=scope:
.
Então, qual é o objeto componente já instanciado ? Lembra da ligação para layout
voltar app.js
?
#File: vendor/magento/module-ui/view/base/web/js/core/app.js
layout(data.components);
A layout
funçã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 config
objeto e, nesse objeto de configuração, procurará uma component
chave. Se encontrar uma chave de componente, será
Use RequireJS
para retornar uma instância do módulo - como se o módulo fosse chamado em requirejs
/ define
dependency.
Chame essa instância de módulo como um construtor javascript
Armazene o objeto resultante no registry
objeto / 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 scope
valor é customer_listing.customer_listing
.
Se olharmos para o objeto JSON a partir da text/x-magento-init
inicializaçã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_listing
objeto tem um config
objeto e esse objeto de configuração tem um component
objeto definido uiComponent
. A uiComponent
sequência é um módulo RequireJS. De fato, é um alias RequireJS que corresponde ao Magento_Ui/js/lib/core/collection
módulo.
vendor/magento/module-ui/view/base/requirejs-config.js
14: uiComponent: 'Magento_Ui/js/lib/core/collection',
No layout.js
Magento, 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/element
módulo e pelo lib/core/class
módulo. A pesquisa dessas personalizações está além do escopo desta resposta.
Uma vez instanciado, layout.js
armazena isso object
no registro. Isso significa que quando o Knockout começa a processar as ligações e encontra a scope
ligaçã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 getTemplate
método chamado quando Knockout chama a ligação sem marca ( <!-- ko template: getTemplate() --><!-- /ko -->
) é o getTemplate
método no new collection
objeto.