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; + } }