Magento 2: Bagaimana / Di mana Fungsi Knockout `getTemplate` Terikat?

19

Banyak halaman belakang Magento berisi yang berikut ini dalam kode sumbernya

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

Saya mengerti (atau saya pikir saya mengerti?) Yang <!-- ko templatemerupakan templat yang tidak menggunakan wadah KnockoutJS .

Apa yang tidak jelas bagi saya adalah - konteks apa getTemplate()fungsi dipanggil? Dalam contoh yang saya lihat online, biasanya ada objek javascript setelah template:. Saya berasumsi itu getTemplateadalah fungsi javascript yang mengembalikan objek - tetapi tidak ada fungsi javascript global bernama getTemplate.

Dimana getTemplateterikat? Atau, mungkin pertanyaan yang lebih baik, di mana pengikatan aplikasi KnockoutJS terjadi pada halaman backend Magento?

Saya tertarik dengan ini dari sudut pandang HTML / CSS / Javascript murni. Saya tahu Magento 2 memiliki banyak abstraksi konfigurasi sehingga (secara teori) pengembang tidak perlu khawatir tentang detail implementasi. Saya tertarik dengan detail implementasi.

Alan Storm
sumber

Jawaban:

38

Kode PHP untuk komponen UI membuat inisialisasi javascript yang terlihat seperti ini

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

Sedikit kode di halaman ini berarti Magento akan memanggil Magento_Ui/js/core/appmodul RequireJS untuk mengambil panggilan balik, dan kemudian memanggil panggilan balik yang lewat di {types:..., components:...}objek JSON sebagai argumen (di databawah)

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

Objek data berisi semua data yang diperlukan untuk membuat komponen UI, serta konfigurasi yang menghubungkan string tertentu dengan modul Magento RequireJS tertentu. Pemetaan itu terjadi di modul typesdan layoutRequireJS. Aplikasi ini juga memuat Magento_Ui/js/lib/ko/initializeperpustakaan RequireJS. The initializeModul kicks off integrasi 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();
});

Setiap bind/...modul RequireJS individu menyiapkan satu ikatan khusus untuk Knockout.

The extender/...RequireJS modul menambahkan beberapa metode pembantu untuk KnockoutJS benda asli.

Magento juga memperluas fungsi mesin templat javascript Knockout di ./template/enginemodul RequireJS.

Akhirnya, Magento memanggil applyBindings()objek KnockoutJS. Ini biasanya di mana program Knockout akan mengikat model tampilan ke halaman HTML - namun, Magento panggilan applyBindings tanpa model tampilan. Ini berarti Knockout akan mulai memproses halaman sebagai tampilan, tetapi tanpa data terikat.

Dalam pengaturan Knockout stok, ini akan sedikit konyol. Namun, karena binding Knockout yang disebutkan sebelumnya, ada banyak peluang bagi Knockout untuk melakukan sesuatu.

Kami tertarik dengan pengikatan lingkup . Anda dapat melihat bahwa dalam HTML ini, juga ditampilkan oleh sistem Komponen 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>

Secara khusus, data-bind="scope: 'customer_listing.customer_listing'">atribut. Saat Magento dimulai applyBindings, Knockout akan melihat scopepengikatan khusus ini , dan menjalankan ./bind/scopemodul RequireJS. Kemampuan untuk menerapkan pengikatan khusus adalah KnockoutJS murni. The pelaksanaan lingkup mengikat adalah sesuatu Magento Inc telah dilakukan.

Implementasi dari pengikatan lingkup adalah di

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

Bit penting dalam file ini ada di sini

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

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

Tanpa terlalu detail, registry.getmetode akan mengeluarkan objek yang sudah dibuat menggunakan string dalam componentvariabel sebagai pengidentifikasi, dan meneruskannya ke applyComponentsmetode sebagai parameter ketiga. Pengidentifikasi string adalah nilai scope:(di customer_listing.customer_listingatas)

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

panggilan untuk createChildContextakan membuat apa yang pada dasarnya adalah objek viewModel baru berdasarkan objek komponen yang sudah dipakai sebelumnya, dan kemudian menerapkannya ke semua elemen turunan dari dokumen asli divyang digunakan data-bind=scope:.

Jadi, apa objek komponen yang sudah instantiated ? Ingat panggilan untuk layoutkembali app.js?

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

layout(data.components);

The layoutFungsi / modul akan turun ke disahkan pada data.components(sekali lagi, data ini berasal dari objek berlalu dalam melalui text/x-magento-init). Untuk setiap objek yang ditemukannya, ia akan mencari configobjek, dan dalam objek konfigurasi itu akan mencari componentkunci. Jika ia menemukan kunci komponen, itu akan

  1. Gunakan RequireJSuntuk mengembalikan instance modul - seolah-olah modul dipanggil dalam requirejs/ definedependensi.

  2. Sebut instance modul sebagai konstruktor javascript

  3. Simpan objek yang dihasilkan dalam registryobjek / modul

Jadi, itu banyak yang bisa diterima. Berikut ulasan singkatnya, gunakan

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

sebagai titik awal. The scopenilainya customer_listing.customer_listing.

Jika kita melihat objek JSON dari text/x-magento-initinisialisasi

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

Kita melihat components.customer_listing.customer_listingobjek memiliki configobjek, dan objek konfigurasi tersebut memiliki componentobjek yang diatur ke uiComponent. The uiComponentstring adalah modul RequireJS. Bahkan, ini merupakan alias RequireJS yang sesuai dengan Magento_Ui/js/lib/core/collectionmodul.

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

Di layout.js, Magento telah menjalankan kode yang setara dengan yang berikut ini.

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

Untuk yang benar-benar penasaran, jika Anda melihat dalam model koleksi dan mengikuti jalur eksekusi, Anda akan menemukan bahwa itu collectionadalah objek javascript yang telah ditingkatkan baik oleh lib/core/element/elementmodul dan lib/core/classmodul. Meneliti penyesuaian ini berada di luar cakupan jawaban ini.

Setelah dipakai, layout.jssimpan ini objectdi registri. Ini berarti ketika Knockout mulai memproses binding dan menemukan custom scopebinding

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

Magento akan mengambil objek ini kembali dari registri, dan mengikatnya sebagai model tampilan untuk hal-hal di dalamnya div. Dengan kata lain, getTemplatemetode yang dipanggil saat Knockout memanggil tagless binding ( <!-- ko template: getTemplate() --><!-- /ko -->) adalah getTemplatemetode pada new collectionobjek.

Alan Storm
sumber
1
Saya benci untuk hanya menanyakan pertanyaan 'mengapa' untuk jawaban Anda, jadi pertanyaan yang lebih terfokus adalah, apa manfaat M2 dengan menggunakan sistem (yang tampaknya berbelit-belit) ini untuk memanggil template KO?
circleix
1
@circlesix Bagian dari sistem yang lebih besar untuk rendering <uiComponents/>dari tata letak sistem XML. Manfaat yang mereka dapatkan adalah kemampuan untuk menukar model tampilan pada halaman yang sama untuk serangkaian tag yang berbeda.
Alan Storm
16
Saya tidak tahu apakah harus tertawa atau menangis! Berantakan sekali.
koosa
8
Saya pikir mereka sedang menggali kuburan mereka sendiri. Jika mereka terus menyulitkan hal-hal seperti ini, perusahaan akan berhenti menggunakannya karena biaya pengembangan
Marián Zeke Šedaj
2
Saya hanya menghabiskan waktu sekitar 5 jam untuk mencari tahu bagaimana cara mengikat perilaku kebiasaan ke bentuk yang diberikan oleh semua "keajaiban" ini. Salah satu bagian dari masalah adalah bahwa kerangka kerja yang sangat umum ini mengharuskan Anda untuk melalui banyak lapisan sampai Anda memiliki kesempatan untuk memahami bagaimana melakukan sesuatu. Juga melacak dari mana konfigurasi tertentu berasal menjadi sangat membosankan.
greenone83
12

Ikatan untuk salah satu templat JS KO terjadi di file .xml modul. Menggunakan modul Checkout sebagai contoh Anda dapat menemukan konfigurasi untuk contenttemplate divendor/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>

Dalam file ini Anda dapat melihat bahwa kelas blok memiliki node yang mendefinisikan "jsLayout" dan memanggil <item name="minicart_content" xsi:type="array">. Ini sedikit dari logika bulat, tetapi jika Anda berada di dalam vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtmlAnda akan melihat baris ini:

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

Jadi data-mengikat mengarahkan mana untuk mencari template bersarang, dalam hal ini adalah Magento_Checkout/js/view/minicartdari vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jslogika (atau MV di KO Model-View-View sistem Model) dan Anda memiliki Magento_Checkout/minicart/content(atau V di KO Model-View-View Model sistem) untuk panggilan templat. Jadi templat yang ditarik di tempat ini adalah vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Sungguh tidak sulit untuk mencari tahu begitu Anda terbiasa mencari di .xml. Sebagian besar dari ini saya pelajari di sini jika Anda dapat melewati bahasa Inggris yang rusak. Tapi sejauh ini saya merasa integrasi Knockout adalah bagian M2 yang paling tidak terdokumentasi.

circleix
sumber
2
Informasi yang berguna, jadi +1, tetapi per pertanyaan, saya tahu Magento memiliki abstraksi untuk menangani hal ini - tapi saya ingin tahu tentang detail implementasi itu sendiri. yaitu - ketika Anda mengkonfigurasi sesuatu dalam file XML itu, magento melakukan sesuatu yang lain untuk memastikan bahwa nilai yang Anda konfigurasikan melakukan hal ketiga . Saya tertarik pada sesuatu yang lain dan yang ketiga.
Alan Storm