Magento 2: Jak / gdzie jest ograniczona funkcja „getTemplate”?

19

Wiele stron zaplecza Magento zawiera następujące elementy w kodzie źródłowym

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

Rozumiem (a może tak rozumiem?), Że <!-- ko templatejest to szablon KnockoutJS bez kontenerów .

Nie jest dla mnie jasne - w jakim kontekście jest getTemplate()wywoływana funkcja? W przykładach, które widziałem online, po pliku jest zwykle obiekt javascript template:. Zakładam, że getTemplatejest to funkcja javascript, która zwraca obiekt - ale nie ma globalnej funkcji javascript o nazwie getTemplate.

Gdzie jest getTemplatezwiązany? Lub, być może, lepsze pytanie, gdzie zachodzi wiązanie aplikacji KnockoutJS na stronie wewnętrznej Magento?

Interesuje mnie to z czystego punktu widzenia HTML / CSS / JavaScript. Wiem, że Magento 2 ma wiele abstrakcji konfiguracji, więc (teoretycznie) programiści nie muszą się martwić o szczegóły implementacji. Interesują mnie szczegóły implementacji.

Alan Storm
źródło

Odpowiedzi:

38

Kod PHP dla komponentu interfejsu użytkownika renderuje inicjalizację javascript, która wygląda następująco

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

Ten fragment kodu na stronie oznacza, że ​​Magento Magento_Ui/js/core/appwywoła moduł RequireJS w celu pobrania wywołania zwrotnego, a następnie wywoła to wywołanie zwrotne w {types:..., components:...}obiekcie JSON jako argument ( dataponiżej)

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

Obiekt danych zawiera wszystkie dane potrzebne do renderowania komponentu interfejsu użytkownika, a także konfigurację, która łączy określone ciągi z niektórymi modułami Magento RequireJS. To mapowanie odbywa się w modułach typesi layoutRequireJS. Aplikacja ładuje również Magento_Ui/js/lib/ko/initializebibliotekę RequireJS. Te initializerzuty modułów off integracji 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();
});

Każdy bind/...moduł RequireJS tworzy pojedyncze niestandardowe powiązanie dla Knockout.

Te extender/...moduły RequireJS dodać pewne metody pomocnika do rodzimych KnockoutJS obiektów.

Magento rozszerza także funkcjonalność silnika szablonów javascript w Knockout w ./template/enginemodule RequireJS.

Wreszcie Magento wywołuje applyBindings()obiekt KnockoutJS. Jest to zwykle miejsce, w którym program Knockout powiązałby model widoku ze stroną HTML - jednak Magento wywołuje applyBindings bez modelu widoku. Oznacza to, że Knockout rozpocznie przetwarzanie strony jako widoku, ale bez powiązania danych.

W standardowej konfiguracji Knockout byłoby to trochę głupie. Jednak ze względu na wspomniane wcześniej niestandardowe wiązania Knockout, Knockout ma wiele możliwości do zrobienia różnych rzeczy.

Jesteśmy zainteresowani wiążącym zakresem . Możesz to zobaczyć w tym kodzie HTML, również renderowanym przez system komponentu interfejsu użytkownika PHP.

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

W szczególności data-bind="scope: 'customer_listing.customer_listing'">atrybut. Kiedy Magento się rozpocznie applyBindings, Knockout zobaczy to niestandardowe scopepowiązanie i ./bind/scopewywoła moduł RequireJS. Możliwość zastosowania niestandardowego wiązania jest czysta KnockoutJS. Realizacja wiążącego zakresie jest coś Magento Inc. zrobiła.

Implementacja wiązania zakresu jest na

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

Ważny fragment tego pliku jest tutaj

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

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

Bez wchodzenia w szczegóły, registry.getmetoda wyciągnie już wygenerowany obiekt, używając łańcucha w componentzmiennej jako identyfikatora, i przekaże go do applyComponentsmetody jako trzeciego parametru. Identyfikator ciągu to wartość scope:( customer_listing.customer_listingpowyżej)

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

wywołanie createChildContextto utworzy nowy obiekt viewModel oparty na już utworzonym obiekcie składowym, a następnie zastosuje go do wszystkich elementów potomnych oryginału, divktóry został użyty data-bind=scope:.

Czym jest już utworzony obiekt komponentu? Pamiętasz wezwanie do layoutpowrotu app.js?

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

layout(data.components);

layoutFunkcji / moduł spadnie do przekazany w data.components(ponownie, w tym dane pochodzą z obiektu został przekazany text/x-magento-init). Dla każdego znalezionego obiektu będzie szukał configobiektu, aw tym obiekcie konfiguracyjnym będzie szukał componentklucza. Jeśli znajdzie klucz komponentu, to zrobi to

  1. Służy RequireJSdo zwracania instancji modułu - tak jakby moduł był wywoływany w zależności requirejs/ define.

  2. Wywołaj tę instancję modułu jako konstruktor javascript

  3. Zapisz wynikowy obiekt w registryobiekcie / module

To jest więc bardzo dużo do zrobienia. Oto krótka recenzja, za pomocą

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

jako punkt wyjścia. scopeWartość customer_listing.customer_listing.

Jeśli spojrzymy na obiekt JSON z text/x-magento-initinicjalizacji

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

Widzimy, że components.customer_listing.customer_listingobiekt ma configobiekt, a ten obiekt konfiguracyjny ma componentobiekt ustawiony na uiComponent. uiComponentCiąg jest moduł RequireJS. W rzeczywistości jest to alias RequireJS, który odpowiada Magento_Ui/js/lib/core/collectionmodułowi.

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

W layout.jsMagento uruchomiono kod, który jest równoważny z poniższym.

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

Dla naprawdę ciekawych, jeśli spojrzysz na model kolekcji i podążasz jego ścieżką wykonania, odkryjesz, że collectionjest to obiekt javascript, który został ulepszony zarówno przez lib/core/element/elementmoduł, jak i lib/core/classmoduł. Badanie tych dostosowań wykracza poza zakres tej odpowiedzi.

Po utworzeniu wystąpienia layout.jszapisuje to objectw rejestrze. Oznacza to, że Knockout rozpoczyna przetwarzanie powiązań i napotyka niestandardowe scopepowiązanie

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

Magento pobierze ten obiekt z rejestru i powiąże go jako model widoku dla elementów wewnątrz div. Innymi słowy, getTemplatemetoda wywoływana, gdy Knockout wywołuje tagless binding ( <!-- ko template: getTemplate() --><!-- /ko -->), jest getTemplatemetodą na new collectionobiekcie.

Alan Storm
źródło
1
Nie chcę po prostu zadawać odpowiedzi na pytanie „dlaczego”, więc bardziej skoncentrowane byłoby pytanie, co zyskuje M2, używając tego (pozornie skomplikowanego) systemu do wywoływania szablonów KO?
circlesix
1
@circlesix Jest to część większego systemu do renderowania <uiComponents/>z układu XML układu. Korzyści, jakie otrzymują, to możliwość zamiany modeli widoków na tej samej stronie na inny zestaw tagów.
Alan Storm,
16
Nie wiem, czy się śmiać, czy płakać! Co za bałagan.
koosa
8
Myślę, że kopią swój własny grób. Jeśli będą komplikować takie rzeczy, firmy przestaną go używać z powodu kosztów rozwoju
Marián Zeke Šedaj
2
Po prostu spędzam około 5 godzin, próbując dowiedzieć się, jak powiązać niestandardowe zachowanie z formą renderowaną przez całą tę „magię”. Jedną z części problemu jest to, że ta bardzo ogólna struktura wymaga przejścia przez mnóstwo warstw, dopóki nie będziesz w stanie zrozumieć, jak to zrobić. Również śledzenie, skąd pochodzi określona konfiguracja, staje się niezwykle nudne.
greenone83
12

Wiązanie dowolnego z nokautowych szablonów JS odbywa się w plikach .xml modułu. Korzystając z modułu Checkout jako przykładu, możesz znaleźć konfigurację contentszablonu wvendor/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>

W tym pliku widać, że klasa bloku ma węzły, które definiują „jsLayout” i wywołują <item name="minicart_content" xsi:type="array">. To trochę okrągły logik, ale jeśli tam vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtmljesteś, zobaczysz następującą linię:

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

Więc danych wiążą kieruje gdzie szukać jakiejkolwiek zagnieżdżonego szablonu, w tym przypadku jest to Magento_Checkout/js/view/minicartod vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jsdo logiki (lub MV w nokautów Model-View-View System modelu) i masz Magento_Checkout/minicart/content(lub V w nokautów Model-View-Widok Model system) dla wywołania szablonu. Tak więc szablon, który jest pobierany w tym miejscu, to vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Naprawdę nie jest trudno to rozgryźć, kiedy przyzwyczaisz się do przeglądania plików .xml. Większość tego nauczyłem się tutaj, jeśli potrafisz przejść przez zepsuty angielski. Ale do tej pory uważam, że integracja Knockout jest najmniej udokumentowaną częścią M2.

circlesix
źródło
2
Przydatne informacje, więc +1, ale na pytanie wiem, że Magento ma abstrakcje, aby sobie z tym poradzić - ale jestem ciekawy samych szczegółów implementacji. tj. - kiedy konfigurujesz coś w tym pliku XML, magento robi coś innego, aby upewnić się, że skonfigurowane wartości robią trzecią rzecz . Interesuje mnie coś jeszcze i trzecia rzecz.
Alan Storm,