Important
You are browsing the documentation for version 4.1 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
Customize Product View Page¶
In this example we look at how to customize product with different product types, and change product page by category.
Every product view page contains the current product_type in the layout context. You can use it in your layout update conditions. When you customize any page, remember to use Symfony Profiler and look into the Layout section, where the current layout context data and actual layout tree can be found.
Please see the Debug Information section for more details.
First, create an import and a template that will be used in a Simple Product and a Configurable Product.
Import:
1 layout:
2 actions:
3 - '@setBlockTheme':
4 themes: 'AcmeProductBundle:layouts:default/imports/oro_product_view/layout.html.twig'
5
6 - '@addTree':
7 items:
8 product_view_page:
9 blockType: container
10 product_container:
11 blockType: product_view_container
12 options:
13 product: '=data["product"]'
14 product_title_mobile:
15 blockType: text
16 options:
17 text: '=data["locale"].getLocalizedValue(data["product"].getNames())'
18 product_image:
19 blockType: product_image
20 options:
21 product: '=data["product"]'
22 product_description_container:
23 blockType: container
24 product_title_container:
25 blockType: container
26 product_specification_container:
27 blockType: container
28 product_specification:
29 blockType: container
30 product_specification_sku:
31 blockType: text
32 options:
33 text: '=data["product"].getSku()'
34 product_description_content:
35 blockType: container
36 product_description:
37 blockType: text
38 options:
39 text: '=data["locale"].getLocalizedValue(data["product"].getDescriptions())'
40 escape: false
41 tree:
42 page_content:
43 product_view_page:
44 product_container:
45 product_image: ~
46 product_description_container:
47 product_title_container: ~
48 product_specification_container:
49 product_specification:
50 product_specification_sku: ~
51 product_description_content:
52 product_description: ~
53
54 - '@setOption':
55 id: title
56 optionName: params
57 optionValue:
58 '%name%': '=data["locale"].getLocalizedValue(data["product"].getNames())'
59
60 - '@setOption':
61 id: page_title
62 optionName: defaultValue
63 optionValue: '=data["locale"].getLocalizedValue(data["product"].getNames())'
64
65 - '@move':
66 id: page_title
67 parentId: product_title_container
Template:
1 {% block _product_container_widget %}
2 {% set attr = layout_attr_defaults(attr, {
3 'data-page-component-module': 'oroui/js/app/components/view-component',
4 '~data-page-component-options': {
5 view: 'oroproduct/js/app/views/base-product-view',
6 modelAttr: product.jsonSerialize()
7 },
8 'data-layout': 'separate',
9 '~class': ' product clearfix'
10 }) %}
11
12 <div {{ block('block_attributes') }}>
13 {{ block_widget(block) }}
14 </div>
15 {% endblock %}
16
17 {% block _page_title_widget %}
18 <h3 class="product-title">{{ text }}</h3>
19 {% endblock %}
20
21 {% block _product_image_widget %}
22 <div class="product-gallery-widget product-gallery-widget_vertical product-gallery-widget_l_floated">
23 <div class="product-gallery product-gallery_vertical">
24 <div class="product-gallery__image-holder">
25 <div class="product-gallery__image-holder__carousel" data-product-gallery>
26 <div class="product-gallery__image-holder__container">
27 {% set productImage = product.imagesByType('main')|length > 0 ? product.imagesByType('main').first.image : null %}
28 <img src="{{ product_filtered_image(productImage, 'product_extra_large') }}"
29 alt="{{ product.names|localized_value }}"
30 width="378"
31 height="378"
32 {% if productImage and isDesktopVersion() %}
33 data-zoom-image="{{ product_filtered_image(productImage, 'product_original') }}"
34 {% set options = {
35 widgetModule: 'jquery-elevatezoom',
36 widgetName: 'elevateZoom',
37 scrollZoom: true,
38 zoomWindowWidth: 630,
39 zoomWindowHeight: 376,
40 borderSize: 1,
41 borderColour: '#ebebeb',
42 lensBorderColour: '#7d7d7d',
43 lensColour: '#000',
44 lensOpacity: 0.22
45 }|json_encode() %}
46 data-page-component-module="oroui/js/app/components/jquery-widget-component"
47 data-page-component-options="{{ options }}"
48 {% endif %}
49 />
50 </div>
51 {% for productImage in product.imagesByType('additional') %}
52 <img src="{{ product_filtered_image(productImage.image, 'product_small') }}" width="82" height="82"/>
53 {% endfor %}
54 </div>
55 </div>
56 </div>
57 </div>
58 {% endblock %}
59
60 {% block _product_description_container_widget %}
61 <div class="product__description-container">
62 {{ block_widget(block) }}
63 </div>
64 {% endblock %}
65
66 {% block _product_title_container_widget %}
67 {% set attr = attr|merge({
68 class: attr.class|default('') ~ " product__title-container"
69 }) %}
70
71 <div {{ block('block_attributes') }}>
72 {{ block_widget(block) }}
73 </div>
74 {% endblock %}
75
76 {% block _product_specification_container_widget %}
77 <div class="product__specification-container product__specification-container_l_floated">
78 {{ block_widget(block) }}
79 </div>
80 {% endblock %}
81
82 {% block _product_specification_widget %}
83 <div class="product__specification">
84 {{ block_widget(block) }}
85 </div>
86 {% endblock %}
87
88 {% block _product_specification_sku_widget %}
89 <div>
90 {{ 'oro.product.frontend.index.item'|trans }} <span class="red">{{ block_widget(block) }}</span>
91 </div>
92 {% endblock %}
93
94 {% block _product_description_content_widget %}
95 <div class="product__description-content product__description-content_l_floated">
96 <div class="product__description">
97 {{ block_widget(block) }}
98 </div>
99 </div>
100 {% endblock %}
Simple Product¶
In our case a simple product inherits all import properties.
Create a layout update that includes the oro_product_view import and has the conditions to check if the current product has a simple product type.
Our simple product has the following look:
1 layout:
2 imports:
3 - oro_product_view
4
5 actions: []
6
7 conditions: 'context["product_type"] == "simple"'
Configurable Product¶
Create a layout update that includes the oro_product_view import and has the conditions to check if the current product has a configurable product type.
Add the product variants block and the template.
Our configurable product has the following look:
Product Variants Block:
1 layout:
2 imports:
3 - oro_product_view
4
5 actions:
6 - '@setBlockTheme':
7 themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/configurable_product.html.twig'
8
9 - '@add':
10 id: product_variants
11 blockType: product_variants
12 parentId: product_specification_container
13 siblingId: product_specification
14 options:
15 variants: '=data["product_variants"].getVariants(data["product"])'
16
17 conditions: 'context["product_type"] == "configurable"'
Template:
1 {% block _product_variants_widget %}
2 {% for variant in variants %}
3 {% set key = variant.name|lower %}
4 <div class="product__{{ key }}s">
5 <label for="product_variants_{{ key }}">Select {{ variant.name }}</label>
6
7 {% if key == 'color' %}
8 {% for key, name in variant.elements %}
9 <span class="badge badge_sm product__colors__item color-item_{{ key }}">
10 <input type="checkbox" name="{{ key }}" alt="{{ name }}" />
11 </span>
12 {% endfor %}
13 {% else %}
14 <select id="product_variants_{{ key }}" class="select select--size-s">
15 {% for key, name in variant.elements %}
16 <option value="{{ key }}">{{ name }}</option>
17 {% endfor %}
18 </select>
19 {% endif %}
20 </div>
21 {% endfor %}
22 {% endblock %}
Block Types¶
For this example we need to create a product_variants block type used in the configurable product layout update.
1 services:
2 ...
3 acme_product.layout.type.product_variants:
4 parent: oro_layout.block_type.abstract_configurable_container
5 calls:
6 - [setOptionsConfig, [{variants: {required: true}}]]
7 - [setName, ['product_variants']]
8 tags:
9 - { name: layout.block_type, alias: product_variants }
10 ...
Data Providers¶
Also, we need to create a product_variants data provider used in the configurable product layout update.
1 services:
2 ...
3 acme_product.layout.data_provider.product_variants:
4 class: Acme\Bundle\ProductBundle\Layout\DataProvider\ProductVariantsProvider
5 tags:
6 - { name: layout.data_provider, alias: product_variants }
7 ...
The following is an example of the data provider:
1 <?php
2
3 namespace Acme\Bundle\ProductBundle\Layout\DataProvider;
4
5 use Symfony\Component\PropertyAccess\PropertyAccess;
6
7 use Oro\Bundle\EntityExtendBundle\Entity\AbstractEnumValue;
8 use Oro\Bundle\ProductBundle\Entity\Product;
9
10 class ProductVariantsProvider
11 {
12 /**
13 * @param Product $product
14 *
15 * @return array
16 */
17 public function getVariants(Product $product)
18 {
19 $variants = [];
20 $variantFields = $product->getVariantFields();
21 foreach ($variantFields as $variantField) {
22 $variants[strtolower($variantField)]['name'] = $variantField;
23 }
24
25 $propertyAccessor = PropertyAccess::createPropertyAccessor();
26
27 $variantLinks = $product->getVariantLinks();
28 foreach ($variantLinks as $variantLink) {
29 $childProduct = $variantLink->getProduct();
30 foreach ($variants as $key => $variant) {
31 /** @var AbstractEnumValue $enumValue */
32 $enumValue = $propertyAccessor->getValue($childProduct, $key);
33 $variants[$key]['elements'][$enumValue->getId()] = $enumValue->getName();
34 }
35 }
36
37 return $variants;
38 }
39 }
Change Product Page by Category¶
Every product view page contains the current category_id and the category_ids in the layout context. You can use these values to evaluate the layout conditions. When you customize any page, remember to use Symfony Profiler and look into the Layout section, where the current layout context data and actual layout tree can be found.
Please see the Debug Information section for more details.
Example 1 (by category ID)¶
We have a “Headlamps” category and we want to add some static html to all products in this category.
The condition is: conditions: ‘context[“category_id”] == 4’.
The result is:
1 layout:
2 actions:
3 - '@setBlockTheme':
4 themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/headlamps.html.twig'
5
6 - '@add':
7 id: product_sale_banner
8 blockType: block
9 parentId: product_view_main_container
10 siblingId: ~
11 prepend: false
12
13 conditions: 'context["category_id"] == 4'
Template:
1 {% block _product_sale_banner_widget %}
2 <div class="text-right">
3 <img src="{{ asset('/bundles/oroproduct/default/images/headlamps.jpg') }}"/>
4 </div>
5 <br />
6 {% endblock %}
Example 2 (by parent category ID)¶
As an example, we want to add a sale banner to all products in first level category and their children. We have a category called “Furniture” with id = 6.
The condition is: conditions: ‘6 in context[“category_ids”]’.
The result is:
1 layout:
2 actions:
3 - '@setBlockTheme':
4 themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/furniture.html.twig'
5
6 - '@add':
7 id: product_sale_banner
8 blockType: block
9 parentId: page_content
10 siblingId: ~
11 prepend: true
12
13 conditions: '6 in context["category_ids"]'
Template:
1 {% block _product_sale_banner_widget %}
2 <div class="text-center">
3 <img src="{{ asset('/bundles/oroproduct/default/images/furniture_sale.jpg') }}"/>
4 </div>
5 <br />
6 {% endblock %}
Product Page Templates¶
Please see the Page Templates section for more details.
You can modify the visual presentation of the product view page for every product, or choose a page template for all of them by default.
First, create a config for the page_templates in our theme.
1 templates:
2 -
3 label: Custom page template
4 description: Custom page template description
5 route_name: oro_product_frontend_product_view
6 key: custom
7 -
8 label: Parent Additional page template
9 description: Additional page template description
10 route_name: oro_product_frontend_product_view
11 key: additional
12 titles:
13 oro_product_frontend_product_view: Product Page
Next, add some layout updates:
1 layout:
2 actions:
3 - '@remove':
4 id: product_view_attribute_group_images
5 - '@move':
6 id: product_view_specification_container
7 parentId: product_view_aside_container
1 layout:
2 actions:
3 - '@setBlockTheme':
4 themes: 'AcmeProductBundle:layouts:default/oro_product_frontend_product_view/page_template/additional/layout.html.twig'
5 - '@add':
6 id: product_view_banner
7 blockType: block
8 parentId: product_view_content_container
Add some templates:
1 {% block _product_view_banner_widget %}
2 <div class="text-center">
3 <img src="{{ asset('/bundles/oroproduct/default/images/flashlights.png') }}"/>
4 </div>
5 <br />
6 {% endblock %}
Global Level¶
To apply a custom page template to all products, go to System > Configuration > Commerce > Design > Theme.
In the Page Templates section, choose Custom page template in the Product Page select.
In the storefront, here is what it will look like:
Entity level¶
To apply a custom page template to the selected products, go to Products > Products, find your product and click edit.
In the Design section, choose Additional page template in the Page Template select.
In the storefront, here is what it will look like: