Magento 2: Wie / Wo ist die Knockout-Funktion "getTemplate" gebunden?

19

Viele Magento-Backend-Seiten enthalten im Quellcode Folgendes

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

Ich verstehe (oder glaube ich?), Dass <!-- ko templatees sich um eine Vorlage ohne KnockoutJS- Container handelt .

Mir ist nicht klar, in welchem ​​Kontext die getTemplate()Funktion aufgerufen wird. In den Beispielen, die ich online gesehen habe, steht normalerweise ein Javascript-Objekt nach dem template:. Ich gehe davon aus, dass getTemplatees sich um eine JavaScript-Funktion handelt, die ein Objekt zurückgibt. Es gibt jedoch keine globale JavaScript-Funktion mit dem Namen getTemplate.

Wo ist getTemplategebunden? Oder möglicherweise eine bessere Frage: Wo findet die KnockoutJS-Anwendungsbindung auf einer Magento-Backend-Seite statt?

Das interessiert mich aus reiner HTML / CSS / Javascript Sicht. Ich weiß, dass Magento 2 viele Konfigurationsabstraktionen enthält, sodass Entwickler sich (theoretisch) nicht um die Implementierungsdetails kümmern müssen. Ich interessiere mich für die Details der Implementierung.

Alan Storm
quelle

Antworten:

38

Der PHP-Code für eine UI-Komponente gibt eine JavaScript-Initialisierung wieder, die so aussieht

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

Dieses Codebit auf der Seite bedeutet, dass Magento das Magento_Ui/js/core/appRequireJS-Modul aufruft , um einen Rückruf abzurufen, und diesen Rückruf dann {types:..., components:...}als Argument im JSON-Objekt übergibt ( datasiehe unten).

#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);
    };
});

Das Datenobjekt enthält alle zum Rendern der UI-Komponente erforderlichen Daten sowie eine Konfiguration, die bestimmte Zeichenfolgen mit bestimmten Magento RequireJS-Modulen verknüpft. Diese Zuordnung erfolgt in den Modulen typesund layoutRequireJS. Die Anwendung lädt auch die Magento_Ui/js/lib/ko/initializeRequireJS-Bibliothek. Das initializeModul startet die KnockoutJS-Integration von 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();
});

Jedes einzelne bind/...RequireJS-Modul richtet eine einzelne benutzerdefinierte Bindung für Knockout ein.

Die extender/...RequireJS-Module fügen systemeigenen KnockoutJS-Objekten einige Hilfsmethoden hinzu.

Magento erweitert auch die Funktionalität der JavaScript-Template-Engine von Knockout im ./template/engineRequireJS-Modul.

Schließlich ruft Magento applyBindings()das KnockoutJS-Objekt auf. Dies ist normalerweise der Fall, wenn ein Knockout-Programm ein Ansichtsmodell an die HTML-Seite bindet. Magento ruft jedoch applyBindings ohne Ansichtsmodell auf. Dies bedeutet, dass Knockout die Seite als Ansicht verarbeitet, jedoch keine Daten gebunden sind.

In einem Standard-Knockout-Setup wäre dies ein wenig albern. Aufgrund der zuvor erwähnten benutzerdefinierten Knockout-Bindungen gibt es für Knockout jedoch viele Möglichkeiten, Dinge zu tun.

Wir interessieren uns für die Umfangsbindung . Sie können dies in diesem HTML sehen, das auch vom PHP UI Component-System gerendert wird.

<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>

Insbesondere das data-bind="scope: 'customer_listing.customer_listing'">Attribut. Wenn Magento startet applyBindings, sieht Knockout diese benutzerdefinierte scopeBindung und ruft das ./bind/scopeRequireJS-Modul auf. Die Möglichkeit, eine benutzerdefinierte Bindung anzuwenden, ist reines KnockoutJS. Die Implementierung der Scope-Bindung wurde von Magento Inc. durchgeführt.

Die Umsetzung der Scope-Bindung erfolgt um

#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js

Das wichtige Bit in dieser Datei ist hier

var component = valueAccessor(),
    apply = applyComponents.bind(this, el, bindingContext);

if (typeof component === 'string') {
    registry.get(component, apply);
} else if (typeof component === 'function') {
    component(apply);
}

Ohne auf die Details einzugehen, ruft die registry.getMethode ein bereits generiertes Objekt unter Verwendung der Zeichenfolge in der componentVariablen als Bezeichner ab und übergibt es applyComponentsals dritten Parameter an die Methode. Die Zeichenkettenkennung ist der Wert von scope:( customer_listing.customer_listingoben)

Im 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);
}

Durch den Aufruf von createChildContextwird im Wesentlichen ein neues viewModel-Objekt erstellt, das auf dem bereits instanziierten Komponentenobjekt basiert, und dann auf alle untergeordneten Elemente des verwendeten Originals divangewendet data-bind=scope:.

Also, was ist das bereits instanziierte Komponentenobjekt? Denken Sie daran , um den Anruf layoutauf der Rückseite app.js?

#File: vendor/magento/module-ui/view/base/web/js/core/app.js

layout(data.components);

Die layoutFunktion / der Baustein steigt in das übergebene data.componentsObjekt ab (diese Daten stammen wiederum aus dem übergebenen Objekt text/x-magento-init). Für jedes gefundene Objekt wird nach einem configObjekt gesucht, und in diesem Konfigurationsobjekt wird nach einem componentSchlüssel gesucht. Wenn es einen Komponentenschlüssel findet, wird es

  1. Verwenden Sie RequireJSdiese Option , um eine Modulinstanz zurückzugeben - als ob das Modul in einer Abhängigkeit requirejs/ aufgerufen worden wäre define.

  2. Rufen Sie diese Modulinstanz als JavaScript-Konstruktor auf

  3. Speichern Sie das resultierende Objekt im registryObjekt / Modul

Das ist also eine Menge zu beachten. Hier ist eine kurze Übersicht über die Verwendung von

<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>

als Ausgangspunkt. Der scopeWert ist customer_listing.customer_listing.

Betrachten wir das JSON-Objekt aus der text/x-magento-initInitialisierung

{
    "*": {
        "Magento_Ui/js/core/app": {
            /* snip */
            "components": {
                "customer_listing": {
                    "children": {
                        "customer_listing": {
                            "type": "customer_listing",
                            "name": "customer_listing",
                            "children": /* snip */
                            "config": {
                                "component": "uiComponent"
                            }
                        },
                        /* snip */
                    }
                }
            }
        }
    }
}

Wir sehen, dass das components.customer_listing.customer_listingObjekt ein configObjekt hat und dass das Konfigurationsobjekt ein Objekt hat component, das auf gesetzt ist uiComponent. Die uiComponentZeichenfolge ist ein RequireJS-Modul. Tatsächlich ist es ein RequireJS-Alias, der dem Magento_Ui/js/lib/core/collectionModul entspricht.

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

In layout.jshat Magento Code ausgeführt, der dem folgenden entspricht.

//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*/})
}

Wenn Sie einen Blick in das Sammlungsmodell werfen und dessen Ausführungspfad verfolgen, werden Sie feststellen, dass collectiones sich um ein JavaScript-Objekt handelt, das sowohl vom lib/core/element/elementModul als auch vom lib/core/classModul verbessert wurde . Die Untersuchung dieser Anpassungen geht über den Rahmen dieser Antwort hinaus.

Nach der Instanziierung wird layout.jsdies objectin der Registrierung gespeichert. Dies bedeutet, wenn Knockout mit der Verarbeitung der Bindungen beginnt und auf die benutzerdefinierte scopeBindung stößt

<div class="admin__data-grid-outer-wrap" data-bind="scope: 'customer_listing.customer_listing'">
    <!-- snip -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- snip -->
</div>

Magento holt dieses Objekt wieder aus der Registrierung und bindet es als Ansichtsmodell für die Objekte in der div. Mit anderen Worten, die getTemplateMethode, die aufgerufen wird, wenn Knockout die taglose Bindung ( <!-- ko template: getTemplate() --><!-- /ko -->) aufruft, ist die getTemplateMethode für das new collectionObjekt.

Alan Storm
quelle
1
Ich hasse es, nur die Frage nach dem Warum zu Ihrer Antwort zu stellen, also wäre es eine konzentriertere Frage, was M2 durch die Verwendung dieses (scheinbar verwickelten) Systems zum Aufrufen von KO-Vorlagen gewinnt.
Kreiseix
1
@circlesix Teil eines größeren Systems zum Rendern <uiComponents/>aus dem Layout-XML-System. Die Vorteile, die sie erhalten, sind die Möglichkeit, Ansichtsmodelle auf derselben Seite gegen einen anderen Satz von Tags auszutauschen.
Alan Storm
16
Ich weiß nicht, ob ich lachen oder weinen soll! Was für ein Chaos.
Koosa
8
Ich denke, sie graben ihr eigenes Grab. Wenn sie
solche
2
Ich verbringe nur ungefähr 5 Stunden damit, herauszufinden, wie man ein benutzerdefiniertes Verhalten an ein Formular bindet, das durch all diese "Magie" wiedergegeben wird. Ein Teil des Problems besteht darin, dass Sie in diesem hochgradig generischen Framework eine Menge Ebenen durchlaufen müssen, bis Sie die Möglichkeit haben, zu verstehen, wie Dinge zu tun sind. Auch das Verfolgen, woher eine bestimmte Konfiguration kommt, wird unglaublich mühsam.
Greenone83
12

Die Bindung für alle Knockout-JS-Vorlagen erfolgt in den XML-Dateien des Moduls. Am Beispiel des Checkout-Moduls finden Sie die Konfiguration für die contentVorlage invendor/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>

In dieser Datei können Sie sehen, dass die Blockklasse Knoten hat, die das "jsLayout" definieren und das "jsLayout" aufrufen <item name="minicart_content" xsi:type="array">. Es ist ein bisschen wie ein Round Robin der Logik, aber wenn Sie dabei sind vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtml, werden Sie diese Zeile sehen:

<div id="minicart-content-wrapper" data-bind="scope: 'minicart_content'">
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

So ist die Daten-bind leitet , wo für jede verschachtelte Vorlage zu suchen, in diesem Fall ist es die Magento_Checkout/js/view/minicartvon vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jsder Logik (oder MV in Vorprägungen Model-View-Ansicht Modell - System) und Sie haben Magento_Checkout/minicart/content(oder V in Vorprägungen Model-View-Ansicht Modell System) für den Vorlagenaufruf. Die Vorlage, die an dieser Stelle gezogen wird, ist also vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Wirklich ist es nicht zu schwer herauszufinden, wenn Sie sich erst einmal daran gewöhnt haben, in den XML-Dateien zu suchen. Das meiste davon habe ich hier gelernt , wenn du an dem kaputten Englisch vorbeikommst. Ich bin jedoch der Meinung, dass die Knockout-Integration der am wenigsten dokumentierte Teil von M2 ist.

circlesix
quelle
2
Nützliche Informationen, also +1, aber ich weiß, dass Magento Abstraktionen hat, um damit umzugehen - aber ich bin neugierig auf die Implementierungsdetails selbst. dh - wenn Sie etwas in dieser XML-Datei konfigurieren, unternimmt magento etwas anderes , um sicherzustellen, dass Ihre konfigurierten Werte eine dritte Sache tun . Ich interessiere mich für das Andere und das Dritte.
Alan Storm