Per a very cursory reading of the KnockoutJS documentation, initializing a very basic Knockout view looks like the following
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.firstName = "Bert";
this.lastName = "Bertington";
}
// Activates knockout.js
ko.applyBindings(new AppViewModel());
i.e. — you create a javascript function intended to be used as an object constructor, instantiate an object from it, and then pass that object into the ko.applyBindings
method of the global knockout object (ko
)
However, in Magento 2, if you load a backend page with a Grid UI, Magento will initialize the js/core/app.js
RequireJS module
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
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);
};
});
This module, in turn, loads the Magento_Ui/js/lib/ko/initialize
module, which appears to initialize Magento's use of KnockoutJS. However, if you look at the source of initialize module.
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();
});
You see Magento's called the ko.applyBindings();
object without a view object. This doesn't make any sense, and I'm not sure if it's my limited understanding of Knockout, or Magento doing something custom/strange here.
Is this where Magento actually applies Knockout bindings? Or does that happen somewhere else? Or is Magento doing something tricky to intercept Knockout code and process it elsewhere?
Best Answer
The
Magento_Ui/js/lib/ko/initialize
library is, indeed, where Magento initializes its Knockout instance. Magento does not assign a ViewModel when it applies bindings.The missing key here is the custom KnockoutJS binding named
scope
.When Magento's Knockout instance encounters a
scope:
binding like thisIt takes the value of that binding (named
customer
), and uses it to load and apply a ViewModel for the inner nodes from theuiRegistry
. You can debug the data bound for a particular scope with some simple KnockoutJSpre
debuggingThe
uiRegistry
is a simple dictionary like object, implemented in theMagento_Ui/js/lib/registry/registry
RequireJS module.Objects are put into the registry via the bits of javascript that look like this
The program in the
Magento_Ui/js/core/app
module will examine thecomponents
key of the passed in object, and for each sub-object willFetch the object returned by the specified
RequireJS
module from thecomponent
key (Magento_Customer/js/view/customer
)Use that object to instantiate a new javascript object (see below)
Assign any extra data keys to that same object
Add that same object to the
uiRegistry
with the key of the original object (customer
above)If you're not sure how the
x-magento-init
script works, I've written an article about it here.There's a more in depth examination of the
app.js
process over in this answer.The implementation of the scope binding is defined here