What are the "source" Items in UI Component Files

The source is, or should be, the data provider. From what I can tell, however, the <item name="source"> node in the XML example you gave makes no measurable difference and can be removed without consequence.

Here's how I came to that: in the initModules() method of elements/element.js, there is a check to see if this.source is a callable function:

if (!_.isFunction(this.source)) {
    this.source = registry.get(this.provider);
}

If this.source is not a callable function, it overrides this.source with a UI Component from the registry using this.provider. Again, this is the provider, though, and not the source. As such, if the source is not a callable function at that point, it simply loads the provider and the original this.source goes the way of the wind.

this.source often is empty, but in the case of the cms_block_form, this.source would be 'block' to start with. Since that is a string and not a callable function, it is simply overridden.

Also note that a UI Component could easily add some logic to set this.source to a callable function, based off of a string from XML, before initModules() is run.


Now, why is this source there in the first place? I don't know why it's in the XML, but it serves a purpose in the Javascript. For an example, I pulled up grid/columns/column.js. In defaults: {}, the following is there:

modules: {
    source: '${ $.provider }'
}

Back in elements/element.js, this is evaluated in initModules():

_.each(this.modules, function (name, property) {
    if (name) {
        this[property] = this.requestModule(name);
    }
}, this);

Here is the requestModule() method:

requestModule: function (name) {
    var requested = this._requesetd;
    if (!requested[name]) {
        requested[name] = registry.async(name);
    }
    return requested[name];
},

The async() method is returned from the registry, and in initModules(), assigned to the property given. In this case, this.source is set to be the async() method from the registry. This would happen for anything within modules:{}, not just source, but sheds light on what happens with the source in some components. The async() function returned from is - not surprisingly - a callable function. As a result, this evaluates to false and is skipped:

initModules: function () {
    ...

    if (!_.isFunction(this.source)) {
        this.source = registry.get(this.provider);
    }

    return this;
}, 

Back in grid/columns/column.js, source is used to change the sorting of the grid.

exportSorting: function () {
    ...
    this.source('set', 'params.sorting', {
        field: this.index,
        direction: this.sorting
    });
},

The async() method handles the functionality, but here, it is calling the set() method on this.source(). The source, or, dataProvider, is grid/provider.js and it does not have a set() method. It's parent, element/element.js does, though:

set: function (path, value) {
    var data = this.get(path),
        diffs;

    diffs = !_.isFunction(data) && !this.isTracked(path) ?
        utils.compare(data, value, path) :
        false;

    utils.nested(this, path, value);

    if (diffs) {
        this._notifyChanges(diffs);
    }

    return this;
},

The concept with set() is somewhat simple in that it updates values and notifies subscribers. So, as a result of columns.js declaring a source, it has direct access to execute methods on it's dataProvider.


Conclusion: the source appears to be what is used, at least in Javascript classes, as the data provider. If a source is set in a Javascript class, and is a callable function, it can be used to execute methods directly on the dataProvider.

This leaves me with a few questions still, though:

  • Is it possible to use source in XML in order to proxy a dataProvider class?
  • Was it supposed to serve a purpose in XML but get deprecated at some point?
  • Are there any core classes that look at this.source (from XML) and do something interesting with it before initModules() is run?

Went to "the source" (groan) for this one and it looks like these <item name="source"/> nodes are, indeed, redundant. Or, the Magento engineer currently in charge of them thinks they're redundant, so that's as close to truth as we'll get.


The source is the key using which the ui component can read the data provided by "DataProvider" class. It is very useful when there are multiple tabs and fieldsets.

For example : refer module-customer/view/base/ui_component/customer_form.xml

<fieldset name="customer">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Account Information</item>
        </item>
    </argument>
    <field name="entity_id">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="visible" xsi:type="boolean">false</item>
                <item name="dataType" xsi:type="string">text</item>
                <item name="formElement" xsi:type="string">input</item>

                **<item name="source" xsi:type="string">customer</item>**

            </item>
        </argument>
    </field>
. 
. 
.

<fieldset name="address">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="is_collection" xsi:type="boolean">true</item>
            <item name="label" xsi:type="string" translate="true">Addresses</item>
            <item name="removeMessage" xsi:type="string" translate="true">Are you sure you want to delete this item?</item>
        </item>
    </argument>
    <field name="parent_id">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="visible" xsi:type="boolean">false</item>
                <item name="dataType" xsi:type="string">number</item>
                <item name="formElement" xsi:type="string">input</item>

                **<item name="source" xsi:type="string">address</item>**

            </item>
        </argument>
    </field>

The getData() method in DataProvider class will return an array with keys 'customer' and 'address' and the corresponding fields in the field-sets will be mapped from it. The screen-shot displays the result of getData() method.

Output of getData() method of DataProvider class

Afterwards when the getDataSourceData() method in Magento\Ui\Component\Form is called it processes the above data.

public function getDataSourceData()
{
    $dataSource = [];

    $id = $this->getContext()->getRequestParam($this->getContext()->getDataProvider()->getRequestFieldName(), null);
    $filter = $this->filterBuilder->setField($this->getContext()->getDataProvider()->getPrimaryFieldName())
        ->setValue($id)
        ->create();
    $this->getContext()->getDataProvider()
        ->addFilter($filter);

    $data = $this->getContext()->getDataProvider()->getData();

    if (isset($data[$id])) {
        $dataSource = [
            'data' => $data[$id]
        ];
    } elseif (isset($data['items'])) {
        foreach ($data['items'] as $item) {
            if ($item[$item['id_field_name']] == $id) {
                **$dataSource = ['data' => ['general' => $item]];**
            }
        }
    }
    return $dataSource;
}