How to display configurable product in each color in Magento product listing?
Asked Answered
E

4

6

I have a configurable product which is available in many different colors and sizes. I want the configurable product to appear once for every color. My idea is to assign one simple product of the configurable product in every color to the category of the configurable product. Then I want to change the listing, so that the (colored) simple product links to it's master product (the configurable one).

The other way would be, to just assign the configurable product to a category and then list it multiple times with different colors. But I think this would be to complicated.

Solution

Sincerely I have lost my code. But here is how I've managed it:

  1. Set visibility for all slave products to catalog so that they appear in the product listing
  2. Override the Product Model and it's getProductUrl function:
    public function getProductUrl($useSid = null)
    {
    $product = $this;
    $product->loadParentProductIds();
        $parentIds = $product->getParentProductIds();

    if(count($parentIds) > 0 && $product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE)
    {
            $parent = Mage::getModel("catalog/product")->setId($parentIds[0])->load();
            return $this->getUrlModel()->getProductUrl($parent, $useSid);
    }

    return $this->getUrlModel()->getProductUrl($product, $useSid);
    }

This way each slave product links to it's master product. The tricky part is to attach the attributes to the url. You can add #attributecode1=value1&attributecode2=value2 to the url to preselect the attribute select boxes. I only had this part quick & dirty and am pretty sure someone can do this much better.

Example for preselection:

http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html#502=43

Estabrook answered 17/5, 2010 at 7:2 Comment(1)
To clarify, you have a series of simple SKUs, and you want to have a bunch of product pages for them, each of which will allow the customer to choose both color and size? If so, is it really appropriate to label a product for color if the color is isn't specified?Hussite
A
4

I don't understand why you just don't make a configurable product based on size for every color? That way you don't need to hack the way Magento works.

If you make a simple product that is part of a configurable product visible on the frontend, it will not link to a configurable product, if it is part of one (as you have found out). It wouldn't really make sense for you either because if your configurable products are based on size AND color, the simple products are going to have a set size and set color.

You would be done, fully functional, and hack-free if you just made a configurable product for each shirt color. Then, you can also use related products to show other shirt colors.

The less hacking, the better. That's my opinion.

Assimilate answered 18/5, 2010 at 12:23 Comment(1)
You're right with your advice, but the customer wants the color and the product to be configurable on the detail page. Anyway, yesterday I wrote a module to display the products as I want them to.Valenba
V
1

Necroing this thread in case others need to do this in Magento 2.

Below is my solution. Please keep in mind that it is hacky and will break many things, so only use if you're a Magento developer who knows what he/she is doing and can either fix or live with the negative side-effects of this code.

registration.php

<?php
use \Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Antti_ConfigurableProductSplitter', __DIR__);

etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Antti_ConfigurableProductSplitter" >
        <sequence>
            <module name="Magento_Catalog" />
        </sequence>
    </module>
</config>

etc/frontend/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="catalog_block_product_list_collection">
        <observer name="cps_catalog_block_product_list_collection" instance="Antti\ConfigurableProductSplitter\Observer\CatalogBlockProductCollectionBeforeToHtmlObserver" shared="false" />
    </event>
    <event name="cps_product_data_merge_after">
        <observer name="cps_product_data_merge_after" instance="Antti\ConfigurableProductSplitter\Observer\SetColorPreselectedAfterProductDataMerge" shared="false" />
    </event>
</config>

Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php

<?php
namespace Antti\ConfigurableProductSplitter\Observer;

use Magento\Framework\Event\ObserverInterface;
use Antti\ConfigurableProductSplitter\Model\ProductCollectionSplitter;

class CatalogBlockProductCollectionBeforeToHtmlObserver implements ObserverInterface
{
    /**
     * @var ProductCollectionSplitter
     */
    private $productSplitter;

    /**
     * CatalogBlockProductCollectionBeforeToHtmlObserver constructor.
     *
     * @param ProductCollectionSplitter $productSplitter
     */
    public function __construct(
        ProductCollectionSplitter $productSplitter
    ) {
        $this->productSplitter = $productSplitter;
    }

    /**
     * @param \Magento\Framework\Event\Observer $observer
     *
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $productCollection = $observer->getEvent()->getCollection();
        if ($productCollection instanceof \Magento\Framework\Data\Collection) {
            if (!$productCollection->isLoaded()) {
                $productCollection->load();
            }
            $this->productSplitter->splitConfigurables($productCollection);
        }

        return $this;
    }
}

Observer/SetColorPreselectedAfterProductDataMerge.php

<?php
namespace Antti\ConfigurableProductSplitter\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Eav\Model\Config as EavConfig;

class SetColorPreselectedAfterProductDataMerge implements ObserverInterface
{
    /**
     * @var EavConfig
     */
    private $eavConfig;

    /**
     * ProductDataMerger constructor.
     *
     * @param EavConfig $eavConfig
     */
    public function __construct(
        EavConfig $eavConfig
    ) {
        $this->eavConfig = $eavConfig;
    }

    /**
     * @param \Magento\Framework\Event\Observer $observer
     *
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $product = $observer->getEvent()->getSimple();
        $merged = $observer->getEvent()->getMerged();

        $this->setColorPreselected($merged, $product->getColor());

        return $this;
    }

    /**
     * @param ProductInterface $product
     * @param int $color
     *
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function setColorPreselected(ProductInterface &$product, int $color)
    {
        $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'color');

        $preconfiguredValues = new \Magento\Framework\DataObject();
        $preconfiguredValues->setData('super_attribute', [$attribute->getId() => $color]);
        $product->setPreconfiguredValues($preconfiguredValues);

        // TODO: should test whether this works if there is no url rewrite
        $product->setRequestPath(sprintf('%s#%d=%d', $product->getRequestPath(), $attribute->getId(), $color));
    }
}

Model/ProductDataMerger.php

<?php
namespace Antti\ConfigurableProductSplitter\Model;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\EntityManager\EventManager;

class ProductDataMerger
{
    /**
     * @var EventManager
     */
    private $eventManager;

    /**
     * @param EventManager $eventManager
     */
    public function __construct(
        EventManager $eventManager
    ) {
        $this->eventManager = $eventManager;
    }

    /**
     * @param ProductInterface $product
     * @param ProductInterface $parentProduct
     *
     * @return ProductInterface
     */
    public function merge(ProductInterface $product, ProductInterface $parentProduct)
    {
        $merged = clone $parentProduct;
        $merged->setParentId($merged->getId());
        $merged->setId($product->getId());

        $this->setImageFromChildProduct($merged, $product);

        $this->eventManager->dispatch(
            'cps_product_data_merge_after',
            ['merged' => $merged, 'simple' => $product, 'configurable' => $parentProduct]
        );

        return $merged;
    }

    /**
     * @param ProductInterface $product
     * @param ProductInterface $childProduct
     */
    public function setImageFromChildProduct(ProductInterface &$product, ProductInterface $childProduct)
    {
        foreach (['image', 'small_image', 'thumbnail'] as $imageType) {
            if ($childProduct->getData($imageType) && $childProduct->getData($imageType) !== 'no_selection') {
                $product->setData($imageType, $childProduct->getData($imageType));
            } else {
                $product->setData($imageType, $childProduct->getData('image'));
            }
        }
    }
}

Model/ProductCollectionSplitter.php

<?php
namespace Antti\ConfigurableProductSplitter\Model;

use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Antti\ConfigurableProductSplitter\Model\ProductDataMerger;
use Magento\Catalog\Model\Layer\Resolver;

class ProductCollectionSplitter
{
    /**
     * @var \Magento\Catalog\Model\Layer
     */
    private $catalogLayer;

    /**
     * @var ProductDataMerger
     */
    private $productDataMerger;

    /**
     * ProductCollectionSplitter constructor.
     *
     * @param Resolver $layerResolver
     * @param ProductDataMerger $productDataMerger
     */
    public function __construct(
        Resolver $layerResolver,
        ProductDataMerger $productDataMerger
    ) {
        $this->catalogLayer = $layerResolver->get();
        $this->productDataMerger = $productDataMerger;
    }

    /**
     * @param \Magento\Framework\Data\Collection $collection
     *
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function splitConfigurables(\Magento\Framework\Data\Collection $collection)
    {
        $items = $collection->getItems();

        if (sizeof($items) == 0) {
            return $this;
        }

        $configurables = $otherProducts = [];

        $colorFilterValue = $this->getCurrentColorFilterValue();

        foreach ($items as $index => $product) {
            if ($product->getTypeId() === Configurable::TYPE_CODE) {
                /** @var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection $childProducts */
                $childProducts = $product->getTypeInstance()->getUsedProductCollection($product);
                if ($colorFilterValue !== null) {
                    $childProducts->addAttributeToFilter('color', ['eq' => $colorFilterValue]);
                }
                $childProducts->groupByAttribute('color');

                foreach ($childProducts as $childProduct) {
                    $childProduct->setParentId($product->getId());
                    $otherProducts[] = $childProduct;
                }

                $configurables[$product->getId()] = $product;
            } else {
                $otherProducts[] = $product;
            }

            $collection->removeItemByKey($index);
        }

        foreach ($otherProducts as $product) {
            if ($product->getParentId() && isset($configurables[$product->getParentId()])) {
                $product = $this->productDataMerger->merge($product, $configurables[$product->getParentId()]);
            }
            $collection->addItem($product);
        }

        return $this;
    }

    /**
     * @return string|null
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function getCurrentColorFilterValue()
    {
        /** @var \Magento\Catalog\Model\Layer\Filter\Item $filter */
        foreach ($this->catalogLayer->getState()->getFilters() as $filter) {
            if($filter->getFilter()->getAttributeModel()->getName() == 'color') {
                return $filter->getValueString();
            }
        }

        return null;
    }
}

Known issues:

  • Because of modifying collection items after it has loaded, the collection count will be invalid, and this may cause issues elsewhere.
  • Also product ids in the collection will be invalid since configurable items' ids gets replaced by simple products' ids.
  • If the collection will be loaded again elsewhere, configurable products do not get splitted.
  • Products per page limiter does not work anymore.
  • Products count on product list is invalid.
  • 3rd party search modules such as Klevu might not work
  • Other 3rd party modules may have issues with the implementation.
  • Product stock data, reviews etc. are broken on product list (although is probably easy to fix in frontend).
  • Not sure if implementation will work without url rewrites (should be easy to fix though).
  • Other issues I might not be aware of.
Venerable answered 28/10, 2019 at 12:53 Comment(1)
the known issues are so critical ... and i don't think it is a good solution which affects many built-in function e.g. filter and count and pagintationSachsen
D
0

One way would be to make the size and color part of the catalog number (or whatever unique identifying number you are using for the product)

So lets say you have a widget that comes in 2 colors and 3 sizes, and it's catalog number is "qwe123". You would enter the following 2 items into the system, along with appropriate images. I'm assuming you already have a way to deal with the sizes.

qwe123-red
qwe123-blue

There is no extra programing involved to do it this way, but if you want to link to the other colors that are available from the product page then you will have to parse out the first part of the catalog number and search for the ones that match.

Dr answered 17/5, 2010 at 7:16 Comment(2)
I can't change the SKU because it will be the EAN of the article (imported from inventory management). When I add a single product to the category, it does not link to it's master (configurable) product, but to a product detail page for itself.Valenba
I just noticed you tagged this "Magento" which I know nothing about. So I don't think I will be much help. Sorry, I thought you were just writing php.Dr
S
0

In order to redirect simple products to configurable parent product, you can create a Plugin (Interceptor) for Magento\Catalog\Model\Product::getProductUrl(), where to change URL for simple products:

if ($product->getTypeId() === 'simple') {
    /*Get the configurable parent product URL and assign it to a simple product.*/
}

To preselect a simple product in a configurable product, the address of a simple product should look like this for example:

/mona-pullover-hoodlie.html#143=167&93=53

where

/mona-pullover-hoodlie.html - configurable product URL,

143, 93 - attributes IDs,

167, 53 - option IDs.

Attributes IDs and option IDs can be obtained using Magento\ConfigurableProduct\Model\Product\Type\Configurable::getConfigurableAttributesAsArray($product) function.


I made a VCT Simple Product URL module on Magento Marketplace that solves this problem.

Sylvestersylvia answered 2/12, 2021 at 21:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.