A JS component is a RequireJS module.
Magento will load these based on JSON given in custom script tags or data-* attributes. The crucial difference is that this module has to return a function. This function will be called with two arguments: the configuration object passed via the JSON, and the element (singular, non-jQuery object) that the component is called on. It is called once per matched element.
Edit: Is this a Magento thing?
This pattern is not specific to Magento. Instead, it comes from jQuery UI widgets. At their core, after all the configuration, they essentially boil down to a function that takes configuration as its first argument, and an element as its second. To see an example of this, look at the collapsible
widget. It builds itself as a jQuery plugin, then returns the constructor at the end. If you inspect this function, you'll see the same argument order. Magento hijacked this pattern so that it can use jQuery UI.
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 this
<li class="greet welcome" data-bind="scope: 'customer'">
<span data-bind="text: customer().fullname ? $t('Welcome, %1!').replace('%1', customer().fullname) : 'Default welcome msg!'"></span>
</li>
It takes the value of that binding (named customer
), and uses it to load and apply a ViewModel for the inner nodes from the uiRegistry
. You can debug the data bound for a particular scope with some simple KnockoutJS pre
debugging
<div data-bind="scope: 'someScope'">
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>
The uiRegistry
is a simple dictionary like object, implemented in the Magento_Ui/js/lib/registry/registry
RequireJS module.
vendor/magento/module-ui/view/base/requirejs-config.js
17: uiRegistry: 'Magento_Ui/js/lib/registry/registry',
Objects are put into the registry via the bits of javascript that look like this
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"customer": {
"component": "Magento_Customer/js/view/customer",
"extra_data_1":"some_value",
"more_extra":"some_other_value",
}
}
}
}
}
</script>
The program in the Magento_Ui/js/core/app
module will examine the components
key of the passed in object, and for each sub-object will
Fetch the object returned by the specified RequireJS
module from the component
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
vendor/magento//module-ui/view/base/web/js/lib/ko/bind/scope.js
Best Answer
If you want to use validation in UI component you need to use
data-bind="mageInit: {'validation': {}}"
instead ofdata-mage-init='{"validation":{}}'
.So you form tag should looks like this:
<form data-bind="mageInit: {'validation': {}}" class="form" id="custom-form" method="post" autocomplete="off"> </form>