diff --git a/app/code/Magento/Ui/Component/AbstractComponent.php b/app/code/Magento/Ui/Component/AbstractComponent.php
index f6b0d543c0469..e98cf83baaaac 100644
--- a/app/code/Magento/Ui/Component/AbstractComponent.php
+++ b/app/code/Magento/Ui/Component/AbstractComponent.php
@@ -6,6 +6,8 @@
namespace Magento\Ui\Component;
use Magento\Framework\DataObject;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponentInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponent\DataSourceInterface;
@@ -85,6 +87,13 @@ public function getName()
*/
public function prepare()
{
+ if ($this->getData(UiComponentFactory::IMPORT_CHILDREN_FROM_META)) {
+ $children = (array)$this->getContext()->getDataProvider()->getMeta();
+ foreach ($children as $name => $childData) {
+ $this->createChildComponent($name, $childData);
+ }
+ }
+
$jsConfig = $this->getJsConfig($this);
if (isset($jsConfig['provider'])) {
unset($jsConfig['extends']);
@@ -92,12 +101,78 @@ public function prepare()
} else {
$this->getContext()->addComponentDefinition($this->getComponentName(), $jsConfig);
}
+
+ if ($this->hasData('actions')) {
+ $this->getContext()->addActions($this->getData('actions'), $this);
+ }
+
if ($this->hasData('buttons')) {
$this->getContext()->addButtons($this->getData('buttons'), $this);
}
$this->getContext()->getProcessor()->notify($this->getComponentName());
}
+ /**
+ * Create child Ui Component
+ *
+ * @param string $name
+ * @param array $childData
+ * @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function createChildComponent($name, array $childData)
+ {
+ if (empty($childData)) {
+ return $this;
+ }
+
+ $childComponent = $this->getComponent($name);
+ if ($childComponent === null) {
+ $argument = [
+ 'context' => $this->getContext(),
+ 'data' => [
+ 'name' => $name,
+ 'config' => $childData
+ ]
+ ];
+
+ if (!isset($childData['componentType'])) {
+ throw new LocalizedException(
+ __('The configuration parameter "componentType" is a required for "%1" component.', $name)
+ );
+ }
+
+ $childComponent = $this->getContext()
+ ->getUiComponentFactory()
+ ->create($name, $childData['componentType'], $argument);
+ $this->prepareChildComponent($childComponent);
+ $this->addComponent($name, $childComponent);
+ } else {
+ $this->updateComponent($childData, $childComponent);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Call prepare method in the component UI
+ *
+ * @param UiComponentInterface $component
+ * @return $this
+ */
+ protected function prepareChildComponent(UiComponentInterface $component)
+ {
+ $childComponents = $component->getChildComponents();
+ if (!empty($childComponents)) {
+ foreach ($childComponents as $child) {
+ $this->prepareChildComponent($child);
+ }
+ }
+ $component->prepare();
+
+ return $this;
+ }
+
/**
* Produce and return block's html output
*
@@ -182,7 +257,7 @@ public function getTemplate()
*/
public function getConfiguration()
{
- return (array) $this->getData('config');
+ return (array)$this->getData('config');
}
/**
@@ -194,7 +269,7 @@ public function getConfiguration()
*/
public function getJsConfig(UiComponentInterface $component)
{
- $jsConfig = (array) $component->getData('js_config');
+ $jsConfig = (array)$component->getData('js_config');
if (!isset($jsConfig['extends'])) {
$jsConfig['extends'] = $component->getContext()->getNamespace();
}
@@ -264,4 +339,36 @@ protected function initObservers(array & $data = [])
}
}
}
+
+ /**
+ * Update component data
+ *
+ * @param array $componentData
+ * @param UiComponentInterface $component
+ * @return $this
+ */
+ protected function updateComponent(array $componentData, UiComponentInterface $component)
+ {
+ $config = $component->getData('config');
+ // XML data configuration override configuration coming from the DB
+ $config = array_replace_recursive($componentData, $config);
+ $component->setData('config', $config);
+
+ return $this;
+ }
+
+ /**
+ * Update DataScope
+ *
+ * @param array $data
+ * @param string $name
+ * @return array
+ */
+ protected function updateDataScope(array $data, $name)
+ {
+ if (!isset($data['dataScope'])) {
+ $data['dataScope'] = $name;
+ }
+ return $data;
+ }
}
diff --git a/app/code/Magento/Ui/Component/Form/Field.php b/app/code/Magento/Ui/Component/Form/Field.php
index d2ff377ae5bd4..80d4e7470a849 100644
--- a/app/code/Magento/Ui/Component/Form/Field.php
+++ b/app/code/Magento/Ui/Component/Form/Field.php
@@ -87,7 +87,12 @@ public function prepare()
(array) $this->getData('config')
)
);
- $this->wrappedComponent->prepare();
+
+ foreach ($this->components as $nameComponent => $component) {
+ $this->wrappedComponent->addComponent($nameComponent, $component);
+ }
+ $this->prepareChildComponent($this->wrappedComponent);
+
$this->components = $this->wrappedComponent->getChildComponents();
// Merge JS configuration with wrapped component configuration
$wrappedComponentConfig = $this->getJsConfig($this->wrappedComponent);
diff --git a/app/code/Magento/Ui/Component/Layout/Tabs.php b/app/code/Magento/Ui/Component/Layout/Tabs.php
index 34021f0d8782d..01174775cc7b0 100644
--- a/app/code/Magento/Ui/Component/Layout/Tabs.php
+++ b/app/code/Magento/Ui/Component/Layout/Tabs.php
@@ -11,6 +11,7 @@
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponentInterface;
use Magento\Framework\View\Element\UiComponent\LayoutInterface;
+use Magento\Framework\View\Element\UiComponent\BlockWrapperInterface;
/**
* Class Tabs
@@ -22,16 +23,6 @@ class Tabs extends \Magento\Framework\View\Layout\Generic implements LayoutInter
*/
protected $navContainerName;
- /**
- * @var UiComponentInterface
- */
- protected $component;
-
- /**
- * @var string
- */
- protected $namespace;
-
/**
* @var array
*/
@@ -42,21 +33,17 @@ class Tabs extends \Magento\Framework\View\Layout\Generic implements LayoutInter
*/
protected $sortIncrement = 10;
- /**
- * @var UiComponentFactory
- */
- protected $uiComponentFactory;
-
/**
* Constructor
*
* @param UiComponentFactory $uiComponentFactory
* @param null|string $navContainerName
+ * @param array $data
*/
- public function __construct(UiComponentFactory $uiComponentFactory, $navContainerName = null)
+ public function __construct(UiComponentFactory $uiComponentFactory, $navContainerName = null, $data = [])
{
$this->navContainerName = $navContainerName;
- $this->uiComponentFactory = $uiComponentFactory;
+ parent::__construct($uiComponentFactory, $data);
}
/**
@@ -72,15 +59,6 @@ public function build(UiComponentInterface $component)
$this->addNavigationBlock();
- // Register html content element
- $this->component->getContext()->addComponentDefinition(
- 'html_content',
- [
- 'component' => 'Magento_Ui/js/form/components/html',
- 'extends' => $this->namespace
- ]
- );
-
// Initialization of structure components
$this->initSections();
$this->initAreas();
@@ -185,11 +163,11 @@ protected function addChildren(array &$topNode, UiComponentInterface $component,
/**
* Add wrapped layout block
*
- * @param \Magento\Ui\Component\Wrapper\Block $childComponent
+ * @param BlockWrapperInterface $childComponent
* @param array $areas
* @return void
*/
- protected function addWrappedBlock(\Magento\Ui\Component\Wrapper\Block $childComponent, array &$areas)
+ protected function addWrappedBlock(BlockWrapperInterface $childComponent, array &$areas)
{
$name = $childComponent->getName();
/** @var TabInterface $block */
diff --git a/app/code/Magento/Ui/Component/Wrapper/Block.php b/app/code/Magento/Ui/Component/Wrapper/Block.php
index 8f352313d94fd..85ac604917a16 100644
--- a/app/code/Magento/Ui/Component/Wrapper/Block.php
+++ b/app/code/Magento/Ui/Component/Wrapper/Block.php
@@ -8,11 +8,12 @@
use Magento\Framework\View\Element\BlockInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Component\AbstractComponent;
+use Magento\Framework\View\Element\UiComponent\BlockWrapperInterface;
/**
* Class Block
*/
-class Block extends AbstractComponent
+class Block extends AbstractComponent implements BlockWrapperInterface
{
const NAME = 'blockWrapper';
@@ -66,4 +67,15 @@ public function render()
{
return $this->block->toHtml();
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfiguration()
+ {
+ return array_merge(
+ (array) $this->block->getData('config'),
+ (array) $this->getData('config')
+ );
+ }
}
diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/FieldTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/FieldTest.php
index a5462493e45d7..b30d546b8d39a 100644
--- a/app/code/Magento/Ui/Test/Unit/Component/Form/FieldTest.php
+++ b/app/code/Magento/Ui/Test/Unit/Component/Form/FieldTest.php
@@ -139,7 +139,7 @@ protected function getWrappedComponentMock()
->with('config', $this->logicalNot($this->isEmpty()));
$wrappedComponentMock->expects($this->once())
->method('prepare');
- $wrappedComponentMock->expects($this->once())
+ $wrappedComponentMock->expects($this->atLeastOnce())
->method('getChildComponents')
->willReturn($this->getComponentsMock());
$wrappedComponentMock->expects($this->any())
diff --git a/app/code/Magento/Ui/etc/data_source.xsd b/app/code/Magento/Ui/etc/data_source.xsd
index d93a2bfb19467..34fc05338313f 100644
--- a/app/code/Magento/Ui/etc/data_source.xsd
+++ b/app/code/Magento/Ui/etc/data_source.xsd
@@ -45,6 +45,7 @@
+
diff --git a/app/code/Magento/Ui/etc/di.xml b/app/code/Magento/Ui/etc/di.xml
index 7e813e1a8743d..f4fbebc43df99 100644
--- a/app/code/Magento/Ui/etc/di.xml
+++ b/app/code/Magento/Ui/etc/di.xml
@@ -162,6 +162,27 @@
+
+
+
+ -
+
- Magento_Ui/js/form/components/html
+ - html_content
+ - fieldset
+
+
+
+
+
+
+
+ -
+
- Magento_Ui/js/form/components/html
+ - html_content
+
+
+
+
Magento\Framework\Data\Argument\Interpreter\ArrayType
diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/BlockWrapperInterface.php b/lib/internal/Magento/Framework/View/Element/UiComponent/BlockWrapperInterface.php
new file mode 100644
index 0000000000000..3655cb1b34c60
--- /dev/null
+++ b/lib/internal/Magento/Framework/View/Element/UiComponent/BlockWrapperInterface.php
@@ -0,0 +1,22 @@
+contentTypeFactory = $contentTypeFactory;
$this->urlBuilder = $urlBuilder;
$this->processor = $processor;
+ $this->uiComponentFactory = $uiComponentFactory;
$this->setAcceptType();
}
@@ -139,9 +142,7 @@ public function addComponentDefinition($name, array $config)
}
/**
- * To get the registry components
- *
- * @return array
+ * {@inheritdoc}
*/
public function getComponentsDefinitions()
{
@@ -149,9 +150,7 @@ public function getComponentsDefinitions()
}
/**
- * Get render engine
- *
- * @return ContentTypeInterface
+ * {@inheritdoc}
*/
public function getRenderEngine()
{
@@ -159,7 +158,7 @@ public function getRenderEngine()
}
/**
- * @return string
+ * {@inheritdoc}
*/
public function getNamespace()
{
@@ -167,9 +166,7 @@ public function getNamespace()
}
/**
- * Getting accept type
- *
- * @return string
+ * {@inheritdoc}
*/
public function getAcceptType()
{
@@ -177,9 +174,7 @@ public function getAcceptType()
}
/**
- * Getting all request data
- *
- * @return mixed
+ * {@inheritdoc}
*/
public function getRequestParams()
{
@@ -187,11 +182,7 @@ public function getRequestParams()
}
/**
- * Getting data according to the key
- *
- * @param string $key
- * @param mixed|null $defaultValue
- * @return mixed
+ * {@inheritdoc}
*/
public function getRequestParam($key, $defaultValue = null)
{
@@ -216,9 +207,7 @@ public function getFilterParam($key, $defaultValue = null)
}
/**
- * Get data provider
- *
- * @return DataProviderInterface
+ * {@inheritdoc}
*/
public function getDataProvider()
{
@@ -226,8 +215,7 @@ public function getDataProvider()
}
/**
- * @param UiComponentInterface $component
- * @return array
+ * {@inheritdoc}
*/
public function getDataSourceData(UiComponentInterface $component)
{
@@ -252,9 +240,7 @@ public function getDataSourceData(UiComponentInterface $component)
}
/**
- * Get page layout
- *
- * @return PageLayoutInterface
+ * {@inheritdoc}
*/
public function getPageLayout()
{
@@ -262,11 +248,7 @@ public function getPageLayout()
}
/**
- * Add button in the actions toolbar
- *
- * @param array $buttons
- * @param UiComponentInterface $component
- * @return void
+ * {@inheritdoc}
*/
public function addButtons(array $buttons, UiComponentInterface $component)
{
@@ -331,10 +313,7 @@ protected function setAcceptType()
}
/**
- * Set data provider
- *
- * @param DataProviderInterface $dataProvider
- * @return void
+ * {@inheritdoc}
*/
public function setDataProvider(DataProviderInterface $dataProvider)
{
@@ -342,11 +321,7 @@ public function setDataProvider(DataProviderInterface $dataProvider)
}
/**
- * Generate url by route and parameters
- *
- * @param string $route
- * @param array $params
- * @return string
+ * {@inheritdoc}
*/
public function getUrl($route = '', $params = [])
{
@@ -372,10 +347,18 @@ protected function prepareDataSource(array & $data, UiComponentInterface $compon
}
/**
- * @inheritDoc
+ * {@inheritdoc}
*/
public function getProcessor()
{
return $this->processor;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUiComponentFactory()
+ {
+ return $this->uiComponentFactory;
+ }
}
diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php b/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php
index bd89985dc1490..97d1f70707c7f 100644
--- a/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php
+++ b/lib/internal/Magento/Framework/View/Element/UiComponent/ContextInterface.php
@@ -5,11 +5,11 @@
*/
namespace Magento\Framework\View\Element\UiComponent;
-use Magento\Framework\View\Element\UiComponent\Processor;
use Magento\Framework\View\Element\UiComponentInterface;
use Magento\Framework\View\Element\UiComponent\ContentType\ContentTypeInterface;
use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface;
use Magento\Framework\View\LayoutInterface as PageLayoutInterface;
+use Magento\Framework\View\Element\UiComponentFactory;
/**
* Interface ContextInterface
@@ -144,4 +144,11 @@ public function getUrl($route = '', $params = []);
* @return Processor
*/
public function getProcessor();
+
+ /**
+ * Get Ui Component Factory
+ *
+ * @return UiComponentFactory
+ */
+ public function getUiComponentFactory();
}
diff --git a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php
index c92ff423e8bc8..083e82bca3362 100755
--- a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php
+++ b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php
@@ -12,12 +12,15 @@
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponent\Config\ManagerInterface;
use Magento\Framework\View\Element\UiComponent\ContextFactory;
+use Magento\Framework\Phrase;
/**
* Class UiComponentFactory
*/
class UiComponentFactory extends DataObject
{
+ const IMPORT_CHILDREN_FROM_META = 'childrenFromMeta';
+
/**
* Object manager
*
@@ -162,6 +165,7 @@ public function create($identifier, $name = null, array $arguments = [])
}
$components = array_filter($components);
$componentArguments['components'] = $components;
+ $componentArguments['data'][self::IMPORT_CHILDREN_FROM_META] = true;
/** @var \Magento\Framework\View\Element\UiComponentInterface $component */
$component = $this->objectManager->create(
@@ -171,15 +175,69 @@ public function create($identifier, $name = null, array $arguments = [])
return $component;
} else {
- $defaultData = $this->componentManager->createRawComponentData($name);
- list($className, $componentArguments) = $this->argumentsResolver($identifier, $defaultData);
+ $rawComponentData = $this->componentManager->createRawComponentData($name);
+ list($className, $componentArguments) = $this->argumentsResolver($identifier, $rawComponentData);
+ $processedArguments = array_replace_recursive($componentArguments, $arguments);
+
+ if (isset($processedArguments['data']['config']['children'])) {
+ $components = [];
+ $bundleChildren = $this->getBundleChildren($processedArguments['data']['config']['children']);
+ foreach ($bundleChildren as $childrenIdentifier => $childrenData) {
+ $children = $this->createChildComponent(
+ $childrenData,
+ $processedArguments['context'],
+ $childrenIdentifier
+ );
+ $components[$childrenIdentifier] = $children;
+ }
+ $components = array_filter($components);
+ $processedArguments['components'] = $components;
+ }
+
/** @var \Magento\Framework\View\Element\UiComponentInterface $component */
$component = $this->objectManager->create(
$className,
- array_replace_recursive($componentArguments, $arguments)
+ $processedArguments
);
return $component;
}
}
+
+ /**
+ * Get bundle children
+ *
+ * @param array $children
+ * @return array
+ * @throws LocalizedException
+ */
+ protected function getBundleChildren(array $children = [])
+ {
+ $bundleChildren = [];
+
+ foreach ($children as $identifier => $config) {
+ if (!isset($config['componentType'])) {
+ throw new LocalizedException(new Phrase(
+ 'The configuration parameter "componentType" is a required for "%1" component.',
+ $identifier
+ ));
+ }
+
+ $rawComponentData = $this->componentManager->createRawComponentData($config['componentType']);
+ list(, $componentArguments) = $this->argumentsResolver($identifier, $rawComponentData);
+ $arguments = array_replace_recursive($componentArguments, ['data' => ['config' => $config]]);
+ $rawComponentData[ManagerInterface::COMPONENT_ARGUMENTS_KEY] = $arguments;
+
+ $bundleChildren[$identifier] = $rawComponentData;
+ $bundleChildren[$identifier]['children'] = [];
+
+ if (isset($arguments['data']['config']['children'])) {
+ $bundleChildren[$identifier]['children'] = $this->getBundleChildren(
+ $arguments['data']['config']['children']
+ );
+ }
+ }
+
+ return $bundleChildren;
+ }
}
diff --git a/lib/internal/Magento/Framework/View/Layout/Generic.php b/lib/internal/Magento/Framework/View/Layout/Generic.php
index a64af6ec44b46..72055de6a3ceb 100644
--- a/lib/internal/Magento/Framework/View/Layout/Generic.php
+++ b/lib/internal/Magento/Framework/View/Layout/Generic.php
@@ -6,14 +6,50 @@
namespace Magento\Framework\View\Layout;
use Magento\Framework\View\Element\UiComponent\DataSourceInterface;
+use Magento\Framework\View\Element\UiComponent\BlockWrapperInterface;
use Magento\Framework\View\Element\UiComponent\LayoutInterface;
use Magento\Framework\View\Element\UiComponentInterface;
+use Magento\Framework\View\Element\UiComponentFactory;
/**
* Class Generic
*/
class Generic implements LayoutInterface
{
+ const CONFIG_JS_COMPONENT = 'component';
+ const CONFIG_COMPONENT_NAME = 'componentName';
+ const CONFIG_PANEL_COMPONENT = 'panelComponentName';
+
+ /**
+ * @var UiComponentInterface
+ */
+ protected $component;
+
+ /**
+ * @var string
+ */
+ protected $namespace;
+
+ /**
+ * @var UiComponentFactory
+ */
+ protected $uiComponentFactory;
+
+ /**
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * @param UiComponentFactory $uiComponentFactory
+ * @param array $data
+ */
+ public function __construct(UiComponentFactory $uiComponentFactory, $data = [])
+ {
+ $this->uiComponentFactory = $uiComponentFactory;
+ $this->data = $data;
+ }
+
/**
* Generate Java Script configuration element
*
@@ -22,6 +58,17 @@ class Generic implements LayoutInterface
*/
public function build(UiComponentInterface $component)
{
+ $this->component = $component;
+ $this->namespace = $component->getContext()->getNamespace();
+
+ $this->component->getContext()->addComponentDefinition(
+ $this->getConfig(self::CONFIG_COMPONENT_NAME),
+ [
+ 'component' => $this->getConfig(self::CONFIG_JS_COMPONENT),
+ 'extends' => $this->namespace
+ ]
+ );
+
$children = [];
$context = $component->getContext();
$this->addChildren($children, $component, $component->getName());
@@ -58,6 +105,10 @@ protected function addChildren(
if ($child instanceof DataSourceInterface) {
continue;
}
+ if ($child instanceof BlockWrapperInterface) {
+ $this->addWrappedBlock($child, $childrenNode);
+ continue;
+ }
self::addChildren($childrenNode, $child, $child->getComponentName());
}
}
@@ -83,4 +134,73 @@ protected function addChildren(
$topNode[$component->getName()] = $nodeData;
}
}
+
+ /**
+ * Add wrapped layout block
+ *
+ * @param BlockWrapperInterface $childComponent
+ * @param array $childrenNode
+ * @return $this
+ */
+ protected function addWrappedBlock(BlockWrapperInterface $childComponent, array &$childrenNode)
+ {
+ $config = $childComponent->getConfiguration();
+ if (!$config['canShow']) {
+ return $this;
+ }
+
+ $name = $childComponent->getName();
+ $panelComponent = $this->createChildFormComponent($childComponent, $name);
+ $childrenNode[$name] = [
+ 'type' => $panelComponent->getComponentName(),
+ 'dataScope' => $name,
+ 'config' => $config,
+ 'children' => [
+ $name => [
+ 'type' => $this->getConfig(self::CONFIG_COMPONENT_NAME),
+ 'dataScope' => $name,
+ 'config' => [
+ 'content' => $childComponent->render()
+ ],
+ ]
+ ],
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Create child of form
+ *
+ * @param UiComponentInterface $childComponent
+ * @param string $name
+ * @return UiComponentInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function createChildFormComponent(UiComponentInterface $childComponent, $name)
+ {
+ $panelComponent = $this->uiComponentFactory->create(
+ $name,
+ $this->getConfig(self::CONFIG_PANEL_COMPONENT),
+ [
+ 'context' => $this->component->getContext(),
+ 'components' => [$childComponent->getName() => $childComponent]
+ ]
+ );
+ $panelComponent->prepare();
+ $this->component->addComponent($name, $panelComponent);
+
+ return $panelComponent;
+ }
+
+ /**
+ * Get config by name
+ *
+ * @param string $name
+ * @return mixed
+ */
+ protected function getConfig($name)
+ {
+ return isset($this->data['config'][$name]) ? $this->data['config'][$name] : null;
+ }
}