Magento 2: Як / де пов'язана функція нокаут `getTemplate`?


19

Багато сторінок Magento Backkend містять у своєму вихідному коді наступне

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

Я розумію (чи гадаю, що я роблю?), Що <!-- ko templateце прив'язка шаблону без контейнера KnockoutJS .

Що мені незрозуміло - це те, в якому контексті викликається getTemplate()функція? У прикладах, які я бачу в Інтернеті, зазвичай є об'єкт javascript після template:. Я припускаю, що getTemplateце функція javascript, яка повертає об'єкт - але немає глобальної функції JavaScript getTemplate.

Куди getTemplateпов'язаний? Або, можливо, краще питання, де відбувається прив'язка програми KnockoutJS на сторінці Magento Bakend?

Мене це цікавить з чистої точки зору HTML / CSS / Javascript. Я знаю, що Magento 2 має багато абстракцій конфігурації, тому (теоретично) розробникам не потрібно турбуватися про деталі реалізації. Мене цікавлять деталі реалізації.

Відповіді:


38

Код PHP для компонента інтерфейсу надає ініціалізацію javascript, яка виглядає приблизно так

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

Цей біт коду на сторінці означає, що Magento викликає Magento_Ui/js/core/appмодуль RequireJS для отримання зворотного дзвінка, а потім викликає цей зворотний виклик, що передається в {types:..., components:...}об'єкт JSON як аргумент ( dataнижче)

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

Об'єкт даних містить усі дані, необхідні для візуалізації компонента інтерфейсу, а також конфігурацію, яка пов'язує певні рядки з певними модулями Magento RequireJS. Це відображення відбувається в модулях typesі layoutRequireJS. Додаток також завантажує Magento_Ui/js/lib/ko/initializeбібліотеку RequireJS. У initializeмодулі штовхає інтеграцію 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();
});

Кожен окремий bind/...модуль RequireJS встановлює єдину власну прив'язку для нокауту.

У extender/...RequireJS модулі додати деякі допоміжні методи для власних об'єктів KnockoutJS.

Magento також розширює функціональність механізму шаблонів JavaScript Knockout в ./template/engineмодулі RequireJS.

Нарешті, Magento викликає applyBindings()об'єкт KnockoutJS. Зазвичай програма Knockout прив'язує модель перегляду до сторінки HTML, однак Magento дзвонить applyBindings без моделі перегляду. Це означає, що Knockout почне обробляти сторінку як перегляд, але без жодних даних.

У налаштуваннях нокауту це було б трохи нерозумно. Однак через раніше згадані користувацькі прив'язки на нокауті, у Knockout є багато можливостей робити щось.

Нас цікавить обов'язковість сфери застосування . Ви можете бачити, що в цьому HTML, також виведеному системою PHP UI Component.

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

Зокрема, data-bind="scope: 'customer_listing.customer_listing'">атрибут. Коли Magento стартує applyBindings, Knockout побачить цю власну scopeприв'язку та викликає ./bind/scopeмодуль RequireJS. Можливість застосувати власну прив'язку є чистою KnockoutJS. Реалізація сфери зв'язування є те , що Magento Inc. зробив.

Реалізація прив'язки сфери дії знаходиться на

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

Тут важливий біт

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

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

Не вдаючись в деталі, то registry.getметод буде витягнути вже створений об'єкт , використовуючи рядок в componentзмінної в якості ідентифікатора, і передати його applyComponentsметоду в якості третьої параметра. Ідентифікатор рядка - це значення scope:( customer_listing.customer_listingвище)

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

заклик до createChildContextстворить те, що є, по суті, новим об'єктом viewModel на основі вже створеного компонентного об'єкта, а потім застосує його до всього нащадкового елемента оригіналу, divякий використовувався data-bind=scope:.

Отже, що таке вже створений об'єкт компонента? Пам'ятаєте дзвінок, щоб layoutповернутися назад app.js?

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

layout(data.components);

layoutФункція / модуль буде спускатися в переданому в data.components(знову ж , це дані надходять від об'єкта , переданого в через text/x-magento-init). Для кожного об'єкта, який він знайде, він буде шукати configоб'єкт, і в цьому об’єкті конфігурації він шукатиме componentключ. Якщо він знайде компонентний ключ, він буде

  1. Використовуйте RequireJSдля повернення екземпляра модуля - як би модуль викликався в requirejs/ defineзалежності.

  2. Викликайте цей екземпляр модуля як конструктор javascript

  3. Збережіть отриманий об'єкт у registryоб'єкті / модулі

Отже, це багато для чого взяти. Ось короткий огляд із використанням

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

як вихідний пункт. scopeЗначення customer_listing.customer_listing.

Якщо ми подивимось на об'єкт JSON з text/x-magento-initініціалізації

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

Ми бачимо, що в components.customer_listing.customer_listingоб'єкта є configоб'єкт, а в цьому конфігураційному об'єкті встановлений componentоб'єкт uiComponent. uiComponentРядок являє собою модуль RequireJS. Фактично, його псевдонім RequireJS відповідає Magento_Ui/js/lib/core/collectionмодулю.

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

У layout.jsMagento запущений код, еквівалентний наступному.

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

По-справжньому цікавим, якщо ви подивитеся на модель колекції і дотримуєтесь її шляху виконання, ви виявите, що collectionце об’єкт javascript, який був розширений і lib/core/element/elementмодулем, і lib/core/classмодулем. Дослідження цих налаштувань виходить за рамки цієї відповіді.

Після того, як миттєво встановлено, layout.jsзберігає це objectв реєстрі. Це означає, що Knockout починає обробку прив’язок і стикається з власною scopeприв'язкою

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

Magento поверне цей об’єкт із реєстру та прив’яже його як модель перегляду для речей всередині div. Іншими словами, getTemplateметод, який викликається, коли Knockout викликає зв'язок tagless ( <!-- ko template: getTemplate() --><!-- /ko -->), - це getTemplateметод на new collectionоб'єкті.


1
Мені не хочеться просто ставити запитання "чому" до вашої відповіді, тому більш цілеспрямованим було б питання, що отримує M2, використовуючи цю (начебто згорнуту) систему для виклику шаблонів KO?
circleix

1
@circlesix Його частина більшої системи для візуалізації <uiComponents/>з макетної системи XML. Переваги, які вони отримують, - це можливість замінювати моделі перегляду на одній сторінці на інший набір тегів.
Алан Шторм

16
Я не знаю, сміятися чи плакати! Який безлад.
koosa

8
Я думаю, що вони копають власну могилу. Якщо вони продовжуватимуть ускладнювати подібні речі, компанії припиняють її використовувати через витрати на розробку
Marián Zeke Šedaj

2
Я просто витрачаю близько 5 годин, намагаючись зрозуміти, як прив’язати власну поведінку до форми, яку надає вся ця "магія". Одна з проблем полягає в тому, що ця дуже загальна рамка потребує того, щоб ви пройшли тонну шарів, поки у вас є шанс зрозуміти, як робити справи. Також відстеження, звідки береться певна конфігурація, стає неймовірно стомлюючим.
greenone83

12

Прив'язка для будь-якого з шаблонів JS-нокаутів відбувається у файлах .xml модуля. Використовуючи модуль Checkout як приклад, ви можете знайти конфігурацію contentшаблону вvendor/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>

У цьому файлі ви можете бачити, що блок-клас має вузли, які визначають "jsLayout" і викликують <item name="minicart_content" xsi:type="array">. Це трохи кругленька логіка, але якщо ви знаходитесь, vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtmlви побачите цей рядок:

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

Таким чином, дані, прив'язувати направляє де шукати будь-вкладеної шаблон, в даному випадку це Magento_Checkout/js/view/minicartз vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jsза логіку (або MV в нокаути Model-View-View система Model) і у вас є Magento_Checkout/minicart/content(або V в нокаути Model-View-View Model система) для виклику шаблону. Тож шаблон, який витягують у цьому місці, є vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Дійсно, це не важко розібратися, як тільки ти звикнеш шукати .xml. Більшу частину цього я дізнався тут, якщо ви можете пройти повз сломанной англійської мови. Але поки що я відчуваю, що інтеграція з нокаутом - найменш задокументована частина M2.


2
Корисна інформація, тож +1, але на питання, я знаю, що Magento має абстракції для вирішення цього питання, - але мені цікаво про самі деталі впровадження. тобто - коли ви конфігуруєте щось у цьому XML-файлі, magento робить щось інше, щоб переконатися, що ваші налаштовані значення роблять третю справу . Мене цікавить щось інше і третє.
Алан Шторм

4

Я впевнений, що глобальний getTemplate метод JS, який ви шукаєте, визначений під ним, app/code/Magento/Ui/view/base/web/js/lib/core/element/element.jsви можете знайти його тут: https://github.com/magento/magento2/blob/4d71bb4780625dce23274c90e45788a72f345dd9/app/code/Magento/Ui/view/base /web/js/lib/core/element/element.js#L262

Поки я перебуваю на своєму телефоні, мені важко з’ясувати, як саме відбувається зв'язування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.