El código PHP para un componente de UI representa una inicialización de JavaScript que se ve así
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app":{
"types":{...},
"components":{...},
}
}
}
</script>
Este bit de código en la página significa que Magento invocará el Magento_Ui/js/core/app
módulo RequireJS para recuperar una devolución de llamada, y luego llamará a esa devolución de llamada que pasa en el {types:..., components:...}
objeto JSON como argumento (a data
continuación)
#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);
};
});
El objeto de datos contiene todos los datos necesarios para representar el componente de la interfaz de usuario, así como una configuración que vincula ciertas cadenas con ciertos módulos Magento RequireJS. Esa asignación ocurre en los módulos types
y layout
RequireJS. La aplicación también carga la Magento_Ui/js/lib/ko/initialize
biblioteca RequireJS. El initialize
módulo inicia la integración KnockoutJS de 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 bind/...
módulo RequireJS individual configura un enlace personalizado único para Knockout.
Los extender/...
módulos RequireJS agregan algunos métodos auxiliares a los objetos nativos de KnockoutJS.
Magento también extiende la funcionalidad del motor de plantillas javascript de Knockout en el ./template/engine
módulo RequireJS.
Finalmente, Magento llama applyBindings()
al objeto KnockoutJS. Esto es normalmente donde un programa Knockout vincularía un modelo de vista a la página HTML; sin embargo, Magento llama applyBindings
sin un modelo de vista. Esto significa que Knockout comenzará a procesar la página como una vista, pero sin datos vinculados.
En una configuración original de Knockout, esto sería un poco tonto. Sin embargo, debido a los enlaces de Knockout personalizados mencionados anteriormente, hay muchas oportunidades para que Knockout haga cosas.
Estamos interesados en el alcance vinculante. Puede ver eso en este HTML, también representado por el sistema de componentes 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>
Específicamente, el data-bind="scope: 'customer_listing.customer_listing'">
atributo. Cuando se inicie Magento applyBindings
, Knockout verá este scope
enlace personalizado e invocará el ./bind/scope
módulo RequireJS. La capacidad de aplicar un enlace personalizado es KnockoutJS puro. La implementación del enlace de alcance es algo que Magento Inc. ha hecho.
La implementación del enlace de alcance está en
#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js
Lo importante en este archivo está aquí
var component = valueAccessor(),
apply = applyComponents.bind(this, el, bindingContext);
if (typeof component === 'string') {
registry.get(component, apply);
} else if (typeof component === 'function') {
component(apply);
}
Sin entrar demasiado en los detalles, el registry.get
método extraerá un objeto ya generado utilizando la cadena en la component
variable como identificador, y lo pasará al applyComponents
método como el tercer parámetro. El identificador de cadena es el valor de scope:
( customer_listing.customer_listing
arriba)
En 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);
}
la llamada a createChildContext
creará lo que es, esencialmente, un nuevo objeto viewModel basado en el objeto componente ya instanciado, y luego lo aplicará a todos los elementos descendientes del original div
que se usó data-bind=scope:
.
Entonces, ¿cuál es el objeto componente ya instanciado ? ¿Recuerdas la llamada para layout
volver app.js
?
#File: vendor/magento/module-ui/view/base/web/js/core/app.js
layout(data.components);
La layout
función / módulo descenderá al pasado data.components
(nuevamente, estos datos provienen del objeto pasado a través de text/x-magento-init
). Para cada objeto que encuentre, buscará un config
objeto, y en ese objeto de configuración buscará una component
clave. Si encuentra una clave de componente, lo hará
Utilícelo RequireJS
para devolver una instancia de módulo, como si se llamara al módulo en una dependencia requirejs
/ define
.
Llame a esa instancia de módulo como un constructor de JavaScript
Almacene el objeto resultante en el registry
objeto / módulo
Entonces, eso es mucho para asimilar. Aquí hay una revisión 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 punto de partida El scope
valor es customer_listing.customer_listing
.
Si miramos el objeto JSON desde la text/x-magento-init
inicialización
{
"*": {
"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 el components.customer_listing.customer_listing
objeto tiene un config
objeto, y ese objeto de configuración tiene un component
objeto establecido en uiComponent
. La uiComponent
cadena es un módulo RequireJS. De hecho, es un alias RequireJS que corresponde al 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',
En layout.js
, Magento ha ejecutado un código que es equivalente al siguiente.
//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 los realmente curiosos, si echas un vistazo al modelo de colección y sigues su ruta de ejecución, descubrirás que collection
es un objeto javascript que ha sido mejorado tanto por el lib/core/element/element
módulo como por el lib/core/class
módulo. Investigar estas personalizaciones está más allá del alcance de esta respuesta.
Una vez instanciado, layout.js
almacena esto object
en el registro. Esto significa que cuando Knockout comienza a procesar los enlaces y encuentra el scope
enlace personalizado
<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
<!-- snip -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- snip -->
</div>
Magento recuperará este objeto del registro y lo vinculará como el modelo de vista para las cosas dentro del div
. En otras palabras, el getTemplate
método que se llama cuando Knockout invoca el enlace sin etiquetas ( <!-- ko template: getTemplate() --><!-- /ko -->
) es el getTemplate
método en el new collection
objeto.