Magento 2: Làm thế nào / Bound Hàm chức năng Knockout` ở đâu?


19

Nhiều trang phụ trợ Magento chứa các phần sau trong mã nguồn của chúng

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

Tôi hiểu (hoặc nghĩ rằng tôi làm?) Đó <!-- ko templatelà một ràng buộc mẫu không chứa container KnockoutJS .

Điều không rõ ràng với tôi là - getTemplate()chức năng được gọi trong bối cảnh nào? Trong các ví dụ tôi thấy trực tuyến, thường có một đối tượng javascript sau template:. Tôi giả sử rằng đó getTemplatelà một hàm javascript trả về một đối tượng - nhưng không có chức năng javascript toàn cầu có tên getTemplate.

Bị getTemplateràng buộc ở đâu? Hoặc, có thể là một câu hỏi tốt hơn, ràng buộc ứng dụng KnockoutJS xảy ra ở đâu trên trang phụ trợ Magento?

Tôi quan tâm đến điều này từ quan điểm thuần HTML / CSS / Javascript. Tôi biết Magento 2 có nhiều khái niệm trừu tượng về cấu hình, vì vậy (về lý thuyết) các nhà phát triển không cần phải lo lắng về các chi tiết triển khai. Tôi quan tâm đến các chi tiết thực hiện.

Câu trả lời:


38

Mã PHP cho thành phần UI hiển thị khởi tạo javascript trông như thế này

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

Đoạn mã này trong trang có nghĩa là Magento sẽ gọi Magento_Ui/js/core/appmô-đun RequireJS để tìm nạp lại một cuộc gọi lại, sau đó gọi cuộc gọi lại đó đi qua trong {types:..., components:...}đối tượng JSON làm đối số ( databên dưới)

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

Đối tượng dữ liệu chứa tất cả dữ liệu cần thiết để kết xuất thành phần UI, cũng như cấu hình liên kết các chuỗi nhất định với các mô-đun Magento RequireJS nhất định. Ánh xạ đó xảy ra trong các mô-đun typeslayoutRequireJS. Ứng dụng này cũng tải Magento_Ui/js/lib/ko/initializethư viện RequireJS. Các initializeđá mô-đun tắt tích hợp 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();
});

Mỗi bind/...mô-đun RequireJS riêng lẻ thiết lập một ràng buộc tùy chỉnh duy nhất cho Knockout.

Các extender/...mô-đun RequireJS thêm một số phương thức trợ giúp vào các đối tượng KnockoutJS riêng.

Magento cũng mở rộng chức năng của công cụ mẫu javascript của Knockout trong ./template/enginemô-đun RequireJS.

Cuối cùng, Magento gọi applyBindings()đối tượng KnockoutJS. Đây thường là nơi chương trình Knockout sẽ liên kết mô hình xem với trang HTML - tuy nhiên, Magento gọiapplyBindings mà không có mô hình xem. Điều này có nghĩa là Knockout sẽ bắt đầu xử lý trang dưới dạng xem, nhưng không bị ràng buộc dữ liệu.

Trong một thiết lập Knockout chứng khoán, điều này sẽ là một chút ngớ ngẩn. Tuy nhiên, do các ràng buộc Knockout tùy chỉnh đã đề cập trước đó, có rất nhiều cơ hội để Knockout thực hiện.

Chúng tôi quan tâm đến phạm vi ràng buộc. Bạn có thể thấy rằng trong HTML này, cũng được hiển thị bởi hệ thống Thành phần UI 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>

Cụ thể là data-bind="scope: 'customer_listing.customer_listing'">thuộc tính. Khi Magento khởi động applyBindings, Knockout sẽ thấy scoperàng buộc tùy chỉnh này và gọi ./bind/scopemô-đun RequireJS. Khả năng áp dụng một ràng buộc tùy chỉnh là KnockoutJS thuần túy. Việc thực hiện ràng buộc phạm vi là điều Magento Inc. đã làm.

Việc thực hiện ràng buộc phạm vi là tại

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

Bit quan trọng trong tập tin này là ở đây

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

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

Không đi sâu vào chi tiết, registry.getphương thức sẽ lấy ra một đối tượng đã được tạo bằng cách sử dụng chuỗi trong componentbiến làm định danh và truyền nó cho applyComponentsphương thức làm tham số thứ ba. Mã định danh chuỗi là giá trị của scope:( customer_listing.customer_listingở trên)

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

về cơ bản, lệnh gọi createChildContextsẽ tạo ra một đối tượng viewModel mới dựa trên đối tượng thành phần đã được khởi tạo và sau đó áp dụng nó cho tất cả các phần tử hậu duệ của bản gốc divđược sử dụng data-bind=scope:.

Vì vậy, các đối tượng thành phần đã được khởi tạo là gì? Nhớ cuộc gọi để layoutquay lại app.js?

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

layout(data.components);

Các layoutchức năng / mô-đun sẽ rơi vào thông qua năm data.components(một lần nữa, số liệu này được lấy từ các đối tượng thông qua năm qua text/x-magento-init). Đối với mỗi đối tượng mà nó tìm thấy, nó sẽ tìm kiếm một configđối tượng và trong đối tượng cấu hình đó, nó sẽ tìm kiếm một componentkhóa. Nếu nó tìm thấy một khóa thành phần, nó sẽ

  1. Sử dụng RequireJSđể trả về một thể hiện của mô-đun - như thể mô-đun được gọi trong requirejs/ definephụ thuộc.

  2. Gọi ví dụ mô-đun đó như là một hàm tạo javascript

  3. Lưu trữ đối tượng kết quả trong registryđối tượng / mô-đun

Vì vậy, đó là rất nhiều để tham gia. Đây là một đánh giá nhanh, sử dụng

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

như một điểm khởi đầu. Các scopegiá trị customer_listing.customer_listing.

Nếu chúng ta nhìn vào đối tượng JSON từ khi text/x-magento-initkhởi tạo

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

Chúng ta thấy components.customer_listing.customer_listingđối tượng có một configđối tượng và đối tượng config đó có một componentđối tượng được đặt thành uiComponent. Các uiComponentchuỗi là một module RequireJS. Trong thực tế, bí danh RequireJS tương ứng với Magento_Ui/js/lib/core/collectionmô-đun.

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

Trong layout.js, Magento đã chạy mã tương đương như sau.

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

Đối với những người thực sự tò mò, nếu bạn xem mô hình bộ sưu tập và theo con đường thực thi của nó, bạn sẽ phát hiện ra đó collectionlà một đối tượng javascript được tăng cường cả bởi lib/core/element/elementmô-đun và lib/core/classmô-đun. Nghiên cứu các tùy chỉnh này nằm ngoài phạm vi của câu trả lời này.

Sau khi khởi tạo, layout.jslưu trữ này objecttrong sổ đăng ký. Điều này có nghĩa là khi Knockout bắt đầu xử lý các ràng buộc và gặp scoperàng buộc tùy chỉnh

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

Magento sẽ lấy đối tượng này ra khỏi sổ đăng ký và liên kết nó làm mô hình xem cho những thứ bên trong div. Nói cách khác, getTemplatephương thức được gọi khi Knockout gọi ràng buộc tagless ( <!-- ko template: getTemplate() --><!-- /ko -->) là getTemplatephương thức trên new collectionđối tượng.


1
Tôi ghét chỉ đặt câu hỏi 'tại sao' cho câu trả lời của bạn, vì vậy câu hỏi tập trung hơn sẽ là gì, M2 có được gì khi sử dụng hệ thống (dường như bị chia rẽ) này để gọi các mẫu KO?
circleix

1
@circlesix Một phần của hệ thống lớn hơn để kết xuất <uiComponents/>từ hệ thống XML bố trí. Lợi ích họ nhận được là khả năng hoán đổi các mô hình xem trên cùng một trang cho một bộ thẻ khác nhau.
Alan Storm

16
Tôi không biết nên cười hay khóc! Thật là một mớ hỗn độn.
koosa

8
Tôi nghĩ rằng họ đang đào mộ của chính họ. Nếu họ cứ làm phức tạp những thứ như thế này, các công ty sẽ ngừng sử dụng nó vì chi phí phát triển
Marián Zeke edaj

2
Tôi chỉ dành khoảng 5 giờ để cố gắng tìm ra cách liên kết một hành vi tùy chỉnh với một hình thức được thể hiện bởi tất cả "ma thuật" này. Một phần của vấn đề là khuôn khổ chung chung này cần bạn trải qua hàng tấn lớp cho đến khi bạn có cơ hội hiểu cách làm mọi thứ. Cũng theo dõi nơi một cấu hình nhất định đến từ vô cùng tẻ nhạt.
greenone83

12

Liên kết cho bất kỳ mẫu JS loại trực tiếp nào xảy ra trong các tệp .xml của mô-đun. Sử dụng mô-đun Checkout làm ví dụ, bạn có thể tìm thấy cấu hình cho contentmẫu trongvendor/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>

Trong tệp này, bạn có thể thấy rằng lớp khối có các nút xác định "jsLayout" và gọi ra <item name="minicart_content" xsi:type="array">. Đó là một chút của một vòng logic, nhưng nếu bạn ở trong đó vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtmlbạn sẽ thấy dòng này:

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

Vì vậy, các dữ liệu ràng buộc đạo nơi để tìm kiếm bất kỳ mẫu lồng nhau, trong trường hợp này đó là Magento_Checkout/js/view/minicartcủa vendor/magento/module-checkout/view/frontend/web/js/view/minicart.jscho logic (hoặc MV trong knockout Model-View-View hệ thống Model) và bạn có Magento_Checkout/minicart/content(hoặc V trong knockout Model-View-Xem các mẫu hệ thống) cho cuộc gọi mẫu. Vì vậy, mẫu đang được kéo ở vị trí này là vendor/magento/module-checkout/view/frontend/web/template/minicart/content.html.

Thực sự không khó để tìm ra một khi bạn đã quen với việc tìm kiếm trong .xml. Hầu hết điều này tôi đã học được ở đây nếu bạn có thể vượt qua tiếng Anh bị hỏng. Nhưng cho đến nay tôi cảm thấy như tích hợp Knockout là phần ít tài liệu nhất của M2.


2
Thông tin hữu ích, vì vậy +1, nhưng theo câu hỏi, tôi biết Magento có sự trừu tượng để giải quyết vấn đề này - nhưng tôi tò mò về chính các chi tiết triển khai. tức là - khi bạn định cấu hình một cái gì đó trong tệp XML đó, magento sẽ làm một cái gì đó khác để đảm bảo rằng các giá trị được cấu hình của bạn thực hiện điều thứ ba . Tôi quan tâm đến thứ khác và điều thứ ba.
Alan Storm

4

Tôi khá chắc chắn rằng phương pháp JS toàn cầu getTemplate mà bạn đang tìm kiếm được xác định theo app/code/Magento/Ui/view/base/web/js/lib/core/element/element.jsbạn có thể tìm thấy ở đây: https://github.com/magento/magento2/blob/4d71bb4780625dce23274c90e45788a72f345dd9/app/code/Magento/Ui/view/base /web/js/lib/core/element/element.js#L262

Khi tôi sử dụng điện thoại, tôi gặp khó khăn trong việc tìm hiểu chính xác cách thức ràng buộc được thực hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.