Magento 2: Come / dove si trova la funzione knockout `getTemplate`?


19

Molte pagine di backend Magento contengono quanto segue nel loro codice sorgente

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

Capisco (o pensi di farlo?) Che <!-- ko templateè un'associazione di modelli senza contenitore KnockoutJS .

Ciò che non è chiaro per me è: in quale contesto viene getTemplate()chiamata la funzione? Negli esempi che ho visto online, di solito c'è un oggetto javascript dopo il template:. Suppongo che getTemplatesia una funzione javascript che restituisce un oggetto, ma non c'è una funzione javascript globale denominata getTemplate.

Dov'è getTemplate legato? O, forse una domanda migliore, dove si verifica l'associazione dell'applicazione KnockoutJS su una pagina di backend Magento?

Sono interessato a questo da un puro punto di vista HTML / CSS / Javascript. So che Magento 2 ha molte astrazioni di configurazione, quindi (in teoria) gli sviluppatori non devono preoccuparsi dei dettagli di implementazione. Sono interessato ai dettagli di implementazione.

Risposte:


38

Il codice PHP per un componente UI esegue il rendering di un'inizializzazione javascript simile a questa

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

Questo bit di codice nella pagina indica che Magento invocherà il Magento_Ui/js/core/appmodulo RequireJS per recuperare un callback, quindi chiamerà quel callback passando {types:..., components:...}nell'oggetto JSON come argomento ( datasotto)

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

L'oggetto dati contiene tutti i dati necessari per eseguire il rendering del componente UI, nonché una configurazione che collega determinate stringhe con determinati moduli Magento RequireJS. Tale mappatura avviene nei moduli typese layoutRequireJS. L'applicazione carica anche la Magento_Ui/js/lib/ko/initializelibreria RequireJS. Il initializemodulo dà il via all'integrazione di Magento KnockoutJS.

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

Ogni singolo bind/...modulo RequireJS imposta una singola associazione personalizzata per Knockout.

Il extender/... moduli RequireJS aggiungono alcuni metodi di supporto agli oggetti nativi KnockoutJS.

Magento estende anche la funzionalità del motore di template javascript di Knockout nel ./template/enginemodulo RequireJS.

Infine, Magento chiama applyBindings()l'oggetto KnockoutJS. Questo è normalmente dove un programma Knockout vincolerebbe un modello di vista alla pagina HTML, tuttavia Magento chiama applyBindings senza un modello di vista. Ciò significa che Knockout inizierà a elaborare la pagina come vista, ma senza alcun limite di dati.

In una configurazione Knockout di serie, questo sarebbe un po 'sciocco. Tuttavia, a causa dei collegamenti Knockout personalizzati precedentemente menzionati, ci sono molte opportunità per Knockout di fare qualcosa.

Siamo interessati all'ambito dell'associazione. Puoi vederlo in questo HTML, reso anche dal sistema dei componenti dell'interfaccia utente di 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>

In particolare, l' data-bind="scope: 'customer_listing.customer_listing'">attributo. Quando Magento prende il via applyBindings, Knockout vedrà questa scopeassociazione personalizzata e invocherà il ./bind/scopemodulo RequireJS. La possibilità di applicare un'associazione personalizzata è pura KnockoutJS. L' implementazione del binding dell'ambito è qualcosa che Magento Inc. ha fatto.

L'implementazione del perimetro di applicazione è a

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

Il bit importante in questo file è qui

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

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

Senza entrare troppo nei dettagli, il registry.getmetodo estrarrà un oggetto già generato usando la stringa nella componentvariabile come identificatore e lo passerà al applyComponentsmetodo come terzo parametro. L'identificatore di stringa è il valore di scope:(customer_listing.customer_listing sopra)

Nel 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 chiamata a createChildContextcreerà quello che è, essenzialmente, un nuovo oggetto viewModel basato sull'oggetto componente già istanziato, quindi lo applicherà a tutti gli elementi discendenti dell'originale divutilizzato data-bind=scope:.

Quindi, qual è l' oggetto componente già istanziato ? Ricordi la chiamata per layoutrientrare app.js?

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

layout(data.components);

La layoutfunzione / modulo scenderà nel passato data.components(di nuovo, questi dati provengono dall'oggetto passato tramite text/x-magento-init). Per ogni oggetto che trova, cercherà un configoggetto e in quell'oggetto di configurazione cercherà una componentchiave. Se trova una chiave componente, lo farà

  1. Utilizzare RequireJSper restituire un'istanza del modulo - come se il modulo fosse chiamato in una requirejs/ definedipendenza.

  2. Chiama l'istanza del modulo come costruttore javascript

  3. Memorizza l'oggetto risultante registrynell'oggetto / modulo

Quindi, è molto da prendere in considerazione. Ecco una breve recensione, utilizzando

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

come punto di partenza. Il scopevalore è customer_listing.customer_listing.

Se osserviamo l'oggetto JSON text/x-magento-initdall'inizializzazione

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

Vediamo che l' components.customer_listing.customer_listingoggetto ha un configoggetto e quell'oggetto di configurazione ha un componentoggetto su cui è impostato uiComponent. La uiComponentstringa è un modulo RequireJS. In realtà, è un alias RequireJS che corrisponde al Magento_Ui/js/lib/core/collectionmodulo.

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

In layout.js, Magento ha eseguito un codice equivalente al seguente.

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

Per i davvero curiosi, se dai un'occhiata al modello di raccolta e segui il suo percorso di esecuzione, scoprirai che collectionè un oggetto javascript che è stato migliorato sia dal lib/core/element/elementmodulo che dal lib/core/classmodulo. La ricerca di queste personalizzazioni va oltre lo scopo di questa risposta.

Una volta istanziato, lo layout.jsmemorizza objectnel registro. Ciò significa che Knockout inizia a elaborare i binding e incontra l' scopeassociazione personalizzata

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

Magento recupererà questo oggetto dal registro e lo legherà come modello di visualizzazione per le cose all'interno di div. In altre parole, il getTemplatemetodo che viene chiamato quando Knockout invoca l'associazione senza tag ( <!-- ko template: getTemplate() --><!-- /ko -->) è il getTemplatemetodo new collectionsull'oggetto.


1
Odio solo porre la domanda "perché" alla tua risposta, quindi sarebbe una domanda più mirata, cosa guadagna M2 usando questo sistema (apparentemente contorto) per chiamare i modelli KO?
cerchi il

1
@circlesix Fa parte di un sistema più ampio per il rendering <uiComponents/>dal sistema XML di layout. I vantaggi che ottengono è la possibilità di scambiare i modelli di visualizzazione nella stessa pagina con un diverso set di tag.
Alan Storm,

16
Non so se ridere o piangere! Che casino.
Koosa,

8
Penso che stiano scavando la loro stessa tomba. Se continuano a complicare cose come questa, le aziende smetteranno di usarlo a causa dei costi di sviluppo
Marián Zeke Šedaj,

2
Trascorro solo circa 5 ore cercando di capire come associare un comportamento personalizzato a una forma resa da tutta questa "magia". Una parte del problema è che questo framework altamente generico ha bisogno di passare attraverso una tonnellata di strati fino a quando non hai la possibilità di capire come fare le cose. Anche il monitoraggio della provenienza di una determinata configurazione diventa incredibilmente noioso.
greenone83,

12

L'associazione per uno dei modelli knockout di JS si verifica nei file .xml del modulo. Utilizzando il modulo Checkout come esempio è possibile trovare la configurazione per il contentmodello 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 questo file puoi vedere che la classe di blocchi ha nodi che definiscono "jsLayout" e richiamano il file <item name="minicart_content" xsi:type="array">. È un po 'un round robin di logica, ma se ci vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtmlsei vedrai questa linea:

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

Quindi il data-bind indica dove cercare qualsiasi modello nidificato, in questo caso è il Magento_Checkout/js/view/minicartof vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jsper la logica (o MV nel sistema knockouts Model-View-View Model) e tu hai Magento_Checkout/minicart/content(o V in knockouts Model-View-View Model sistema) per la chiamata modello. Quindi il modello che viene tirato in questo punto è vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Davvero non è difficile capire una volta che ci si abitua a guardare nei file .xml. La maggior parte di questo ho imparato qui se riesci a superare l'inglese rotto. Ma finora mi sembra che l'integrazione Knockout sia la parte meno documentata di M2.


2
Informazioni utili, quindi +1, ma per la domanda, so che Magento ha delle astrazioni da affrontare - ma sono curioso di conoscere i dettagli dell'implementazione. cioè - quando configuri qualcosa in quel file XML, magento fa qualcos'altro per assicurarti che i tuoi valori configurati facciano una terza cosa . Sono interessato a qualcos'altro e alla terza cosa.
Alan Storm,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.