We will call our new Bundle CustomRelatedBundle , we’re going to add our custom related block section inside edit product page like Related products and Upsell products .
Download The Full Bundle from Github
For that we use EventListener inside
services.yml: ibnab_customrelated.event_listener.related_customrelated_edit_listener: class: Ibnab\Bundle\CustomRelatedBundle\EventListener\RelatedCustomRelatedEditListener arguments: - '@translator' - '@security.authorization_checker' - '@ibnab_customrelated.service.related_items_handler' tags: - { name: kernel.event_listener, event: oro_ui.scroll_data.before.product-edit, method: onProductEdit } - { name: kernel.event_listener, event: oro_ui.scroll_data.before.product-related-items-update, method: onProductEdit } - { name: kernel.event_listener, event: oro.form.update_handler.before_form_data_set.oro_product, method: onFormDataSet } - { name: kernel.event_listener, event: oro.form.update_handler.before_entity_flush.oro_product, method: onPostSubmit }
The event tags for kernel.event_listener , we're adding our block section by oro_ui.scroll_data.before.product-edit & oro_ui.scroll_data.before.product-related-items-update managed by function onProductEdit , and two other before_form_data_set.oro_product & oro.form.update_handler.before_entity_flush.oro_product to save to manage data of our custom related products.
This part of class Ibnab\Bundle\CustomRelatedBundle\EventListener\RelatedCustomRelatedEditListener which responsible of rendering of our custom block:
<?php namespace Ibnab\Bundle\CustomRelatedBundle\EventListener; use Oro\Bundle\FormBundle\Event\FormHandler\FormProcessEvent; use Oro\Bundle\FormBundle\Form\Type\EntityIdentifierType; use Oro\Bundle\ProductBundle\Entity\Product; use Oro\Bundle\UIBundle\Event\BeforeListRenderEvent; use Oro\Bundle\UIBundle\View\ScrollData; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Oro\Bundle\FormBundle\Event\FormHandler\AfterFormProcessEvent; use Twig\Environment; use Symfony\Component\Form\FormInterface; use Oro\Bundle\ProductBundle\Form\Handler\RelatedItemsHandler; /** * Adds related product information (tabs, grids, forms) to the product edit page. */ class RelatedCustomRelatedEditListener { const RELATED_ITEMS_ID = 'relatedItems'; /** @var int */ const BLOCK_PRIORITY = 1500; /** @var TranslatorInterface */ private $translator; /** @var AuthorizationCheckerInterface */ private $authorizationChecker; /** @var RelatedItemsHandler */ private $relatedItemsHandler; /** * @param TranslatorInterface $translator * @param RelatedItemConfigHelper $relatedItemConfigHelper * @param AuthorizationCheckerInterface $authorizationChecker */ public function __construct( TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker, RelatedItemsHandler $relatedItemsHandler ) { $this->translator = $translator; $this->authorizationChecker = $authorizationChecker; $this->relatedItemsHandler = $relatedItemsHandler; } /** * @param BeforeListRenderEvent $event */ public function onProductEdit(BeforeListRenderEvent $event) { $twigEnv = $event->getEnvironment(); $tabs = []; $grids = []; $tabs[] = [ 'id' => 'customrelated-products-block', 'label' => $this->translator->trans('ibnab.customrelated.tabs.customrelatedProducts.label') ]; $grids[] = $this->getCustomRelatedProductsEditBlock($event, $twigEnv); $this->addEditPageBlock($event->getScrollData(), $grids); } } /** * @param ScrollData $scrollData * @param string[] $htmlBlocks */ { $subBlock = $scrollData->addSubBlock(self::RELATED_ITEMS_ID); $scrollData->addSubBlockData( self::RELATED_ITEMS_ID, $subBlock, 'relatedItems' ); } /** * @param BeforeListRenderEvent $event * @param Environment $twigEnv * @return string */ private function getCustomRelatedProductsEditBlock(BeforeListRenderEvent $event, Environment $twigEnv) { return $twigEnv->render( '@IbnabCustomRelated/CustomRelated/customrelatedProducts.html.twig', [ 'form' => $event->getFormView(), 'entity' => $event->getEntity(), 'relatedProductsLimit' => 8 ] ); } /** * @param Environment $twigEnv * @param array $tabs * @return string */ { return $twigEnv->render( '@OroProduct/Product/RelatedItems/tabs.html.twig', [ 'relatedItemsTabsItems' => $tabs ] ); }
The function onProductEdit adding our custom block by usage of $twigEnv = $event->getEnvironment(); to pass our new tab to @OroProduct/Product/RelatedItems/tabs.html.twig and the content of our tab is @IbnabCustomRelated/CustomRelated/customrelatedProducts.html.twig (refer to functions renderTabs & getCustomRelatedProductsEditBlock in the code above ) and adding to related items section by function :
{ $subBlock = $scrollData->addSubBlock(self::RELATED_ITEMS_ID); $scrollData->addSubBlockData( self::RELATED_ITEMS_ID, $subBlock, 'relatedItems' ); }
let’s explore the content of our tab inside @IbnabCustomRelated/CustomRelated/customrelatedProducts.html.twig , the content is:
{% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% set gridName = 'products-customrelated-products-edit' %} {% set relatedGridParams = { relatedItemsIds: get_customrelated_products_ids(entity), _parameters: { data_in: [], data_not_in: [] } } %} {% set relatedGridParams = relatedGridParams|merge({ _parameters: relatedGridParams._parameters|merge({ }) }) %} {% endif %} {% set relatedGridParams = relatedGridParams|merge({ _parameters: relatedGridParams._parameters|merge({ }) }) %} {% endif %} <div id="customrelated-products-block" class="tab-content"> {{ form_widget(form.appendCustomRelated, {'id': 'productAppendCustomRelated'}) }} {{ form_widget(form.removeCustomRelated, {'id': 'productRemoveCustomRelated'}) }} {{ placeholder('ibnab_customrelated_related_ibnab_customrelateds_buttons', { 'entity': entity, 'relatedProductsLimit': -1, 'gridName': gridName }) }} {{ dataGrid.renderGrid(gridName, relatedGridParams, {cssClass: 'inner-grid'}) }} {{ placeholder('ibnab_customrelated_related_items_edit', {'entity': entity}) }} </div>
Is clear that we well use datagrid macro to render our custom datagrid :
{% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} {% set gridName = 'products-customrelated-products-edit' %} and the params passed to datagrid: {% set relatedGridParams = { relatedItemsIds: get_customrelated_products_ids(entity), _parameters: { data_in: [], data_not_in: [] } } %}
we’re using get_customrelated_products_ids(entity) is our twig function responsible to get all customs related products of current product, the function get_customrelated_products_ids(entity) is inside Ibnab/Bundle/CustomRelatedBundle/Twig/ProductExtension.php you can explore from bundle in github .
and our block content is:
<div id="customrelated-products-block" class="tab-content"> {{ form_widget(form.appendCustomRelated, {'id': 'productAppendCustomRelated'}) }} {{ form_widget(form.removeCustomRelated, {'id': 'productRemoveCustomRelated'}) }} {{ placeholder('ibnab_customrelated_related_ibnab_customrelateds_buttons', { 'entity': entity, 'relatedProductsLimit': -1, 'gridName': gridName }) }} {{ dataGrid.renderGrid(gridName, relatedGridParams, {cssClass: 'inner-grid'}) }} {{ placeholder('ibnab_customrelated_related_items_edit', {'entity': entity}) }} </div>
We added two placeholder the first is for adding a button responsible to show the dialog content and datagrid of products to choose our custom related products.
Our placeholder Ibnab/Bundle/CustomRelatedBundle/Resources/config/oro/placeholders.yml:
placeholders: placeholders: ibnab_customrelated_related_items_edit: ~ ibnab_customrelated_related_ibnab_customrelateds_buttons: items: ibnab_customrelated_select_related_customrelateds_button: ~ items: ibnab_customrelated_select_related_customrelateds_button: template: IbnabCustomRelatedBundle:CustomRelated:selectCustomRelatedButton.html.twig
Note : 'id': 'productAppendCustomRelated' and 'id': 'productRemoveCustomRelated' the naming format is very important is used to define appended and removed product . See Datagrid on bottom
let’s explore our Ibnab/Bundle/CustomRelatedBundle/Resources/config/oro/datagrids.yml it has many many sub datagrid to explore but the one of important is:
products-customrelated-products-edit: extends: products-customrelated-items-edit options: rowSelection: dataField: id columnName: isRelated selectors: included: '#productAppendCustomRelated' excluded: '#productRemoveCustomRelated'
And parent:
products-customrelated-items-edit: acl_resource: oro_product_update extends: products-customrelated-items-base options: jsmodules: - orodatagrid/js/datagrid/listener/action-form-listener - oroproduct/js/app/datagrid/listener/related-product-listener source: query: where: or: - 'product.id IN (:relatedItemsIds) AND product.id NOT IN (:data_not_in)' - '(product.id IN (:data_in)) AND product.id NOT IN (:data_not_in)' bind_parameters: - name: relatedItemsIds default: [] actions: delete: type: frontend label: oro.grid.action.delete icon: trash configuration: triggerAction: excludeRow
Which got relatedItemsIds param of our current custom related products to using it in clause where.
What about our IbnabCustomRelatedBundle:CustomRelated:selectCustomRelatedButton.html.twig button which responsible to show new dialog to select our unselect products the content is:
{% import 'OroProductBundle:Product/RelatedItems:macros.html.twig' as relatedItemsUI %} <div class="related-items-widget"> {{ relatedItemsUI.renderRelatedItemButton( path('ibnab_customrelated_possible_products_for_customrelated_products', {'id': entity.id}), 'ibnab.customrelated.widgets.select_products.label'|trans, 'ibnab.customrelated.widgets.select_products.title'|trans({'{{ sku }}': entity.sku, '{{ name }}': entity.name}), relatedProductsLimit, '#productAppendCustomRelated', '#productRemoveCustomRelated', gridName ) }} </div>
I hope that you’re seeing #productAppendCustomRelated & #productRemoveCustomRelated , but the more important is the on click action call a new controller with path path('ibnab_customrelated_possible_products_for_customrelated_products', {'id': entity.id}) is our controller to show all available products which compatible with current products , our controller content:
<?php namespace Ibnab\Bundle\CustomRelatedBundle\Controller; use Oro\Bundle\ProductBundle\Entity\Product; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; /** * CRUD controller for the Product entity. */ class ProductController extends AbstractController { /** * @Route( * "/get-possible-products-for-customrelated-products/{id}", * name="ibnab_customrelated_possible_products_for_customrelated_products", * requirements={"id"="\d+"} * ) * @Template(template="IbnabCustomRelatedBundle:Product:selectCustomRelatedProducts.html.twig") * * @param Product $product * @return array */ public function getPossibleProductsForCustomRelatedProductsAction(Product $product) { return ['product' => $product]; } }
Note: don’t forget to declare inside Ibnab/Bundle/CustomRelatedBundle/Resources/config/oro/routing.yml:
ibnab_customrelated: resource: "@IbnabCustomRelatedBundle/Controller/ProductController.php" type: annotation prefix: /customrelatedproduct
This URL using action getPossibleProductsForCustomRelatedProductsAction and the action get rendered inside IbnabCustomRelatedBundle:Product:selectCustomRelatedProducts.html.twig by passing the current product to this twig which render a new Grid to select products:
{% import 'OroDataGridBundle::macros.html.twig' as dataGrid %} <div class="widget-content"> <input type="hidden" data-role="related-items-appended-ids" name="productAppendRelatedSelect" id="productAppendCustomRelatedSelect" value="{{ app.request.get('addedProductRelatedItems')|join(',') }}" /> <input type="hidden" data-role="related-items-removed-ids" name="productRemoveCustomRelatedSelect" id="productRemoveCustomRelatedSelect" value="{{ app.request.get('removedProductRelatedItems')|join(',') }}" /> {{ dataGrid.renderGrid('products-customrelated-products-select', {'productId': product.id, 'relatedItemsIds': get_customrelated_products_ids(product)}) }} <div class="widget-actions form-actions"> <button class="btn" type="reset">{{ 'Cancel'|trans }}</button> <button class="btn btn-primary" data-role="related-items-submit-button" type="button"> {{ 'oro.product.widgets.select_products'|trans }} </button> </div> </div>
And the datagrid content is :
products-customrelated-products-select: extends: products-customrelated-items-select options: rowSelection: dataField: id columnName: isRelated selectors: included: '#productAppendCustomRelatedSelect' excluded: '#productRemoveCustomRelatedSelect'
I think the big points has get showed in this course but to explore how to manage save and find strategy you should explore the full bundle in github by your self .
Download The Full Bundle from Github
Comments