Displaying list of products somewhere at store fronted is very common task when developing for Magento. Whether it's featured products, sale items or the new arrivals, most developers take the custom block - custom .phtml approach. To be honest, there's nothing wrong with that if you have a good reason to roll your own solution, but 99% of the time, Magento way should be the only way. With that being said, idea behind this article is demonstrating Magento way of displaying custom product collection at store frontend by using Magento built-in blocks and templates combined with a little bit of layout magic.
First things first, lets go over Magento blocks used for rendering product list at category pages, different built in widgets or virtually anywhere at Magento frontend. As an extension of Mage_Catalog_Block_Product_Abstract, the most important block is definitely Mage_Catalog_Block_Product_List. This block contains functions to connect product collection with Mage_Catalog_Block_Product_List_Toolbar in order to add different kinds of representational and sorting capabilities. Now lets to proceed to code snippets.
Lets say we want to display our custom product collection at our custom controller/action inside our custom Magento module, here's an example layout update XML allowing us to complete this task:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <?xml version="1.0" encoding="UTF-8"?> <layout version="0.1.0"> <mmartinovic_productlist_index_index> <!-- Set title --> <reference name="head"> <action method="setTitle"> <title>Mmartinovic Productlist</title> </action> </reference> <!-- Switch root template to 1 column --> <reference name="root"> <action method="setTemplate"> <template>page/1column.phtml</template> </action> </reference> <reference name="content"> <!-- Add product list to content --> <block type="catalog/product_list" name="product_list" template="catalog/product/list.phtml"> <!-- Add toolbar to product list --> <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml"> <!-- Add pager to toolbar --> <block type="page/html_pager" name="product_list_toolbar_pager"/> </block> <!-- Specify toolbar block name --> <action method="setToolbarBlockName"> <name>product_list_toolbar</name> </action> <!-- Use custom product collection --> <action method="setCollection"> <value helper="mmartinovic_productlist/getProductCollection" /> </action> <!-- Use custom available sort by orders --> <action method="setAvailableOrders"> <value helper="mmartinovic_productlist/getAvailableOrders" /> </action> <!-- Set the default sort by order --> <action method="setSortBy"> <value>price</value> </action> <!-- Set default direction to ascending --> <action method="setDefaultDirection"> <value>asc</value> </action> <action method="setColumnCount"> <coulumn>5</coulumn> </action> </block> </reference> </mmartinovic_productlist_index_index> </layout> |
As you can see from preceding code snippet, we've basically switched layout to 1 column, filled content with catalog/product_list
type block and added catalog/product_list_toolbar
with page/html_pager
inside.
You probably noticed we've used couple of catalog/product_list_toolbar
method invocations to set different presentational and sorting parameters, more importantly we've set the product collection using Mage_Catalog_Block_Product_List::setCollection()
method. In order to instruct product list block to use our custom product collection, we're passing our collection object as parameter to setCollection
method using helper="helper/helperMethod">
syntax of layout XML. If you inspect the Mage_Catalog_Block_Product_List code closely, you will notice that Mage_Catalog_Block_Product_List "listens" for certain class properties when adjusting Mage_Catalog_Block_Product_List_Toolbar instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php /** * Product list * * @category Mage * @package Mage_Catalog * @author Magento Core Team <core@magentocommerce.com> */ class Mage_Catalog_Block_Product_List extends Mage_Catalog_Block_Product_Abstract { // Other code protected function _beforeToHtml() { $toolbar = $this->getToolbarBlock(); // called prepare sortable parameters $collection = $this->_getProductCollection(); // use sortable parameters if ($orders = $this->getAvailableOrders()) { $toolbar->setAvailableOrders($orders); } if ($sort = $this->getSortBy()) { $toolbar->setDefaultOrder($sort); } if ($dir = $this->getDefaultDirection()) { $toolbar->setDefaultDirection($dir); } if ($modes = $this->getModes()) { $toolbar->setModes($modes); } // set collection to toolbar and apply sort $toolbar->setCollection($collection); $this->setChild('toolbar', $toolbar); Mage::dispatchEvent('catalog_block_product_list_collection', array( 'collection' => $this->_getProductCollection() )); $this->_getProductCollection()->load(); return parent::_beforeToHtml(); } // Other code } |
This behavior is what makes it possible for us to adjust product list settings using layout update file. We also used helper function to adjust list of available sort orders, something that otherwise gets pulled from config by toolbar block constructor. For reference, here's my helper class code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <?php class Mmartinovic_Productlist_Helper_Data extends Mage_Core_Helper_Abstract { /** * Array of available orders to be used for sort by * * @return array */ public function getAvailableOrders() { return array( // attribute name => label to be used 'price' => $this->__('Price') ); } /** * Return product collection to be displayed by our list block * * @return Mage_Catalog_Model_Resource_Product_Collection */ public function getProductCollection() { $rootCategoryId = Mage::app()->getStore()->getRootCategoryId(); $collection = Mage::getModel('catalog/category') ->load($rootCategoryId) ->getProductCollection() ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes()) ->addMinimalPrice() ->addFinalPrice() ->addTaxPercents() ->addUrlRewrite($rootCategoryId); Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection); Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection); return $collection; } } |
One thing worth mentioning is that preceding code is fetching root category product collection and in order to get results you expect (all store products), root category must have "Is Anchor" set to "Yes". But this is just an example and you will most probably fetch your custom product collection here.
For more visual amongst you, here's how this code looks like when accessed from store frontend:
Basically, that's it when it comes to displaying custom product collection in Magento way. If you want to perform further customizations, just take a look at Mage_Catalog_Block_Product_List code and keep in mind that you can call any public function from layout XML. If this isn't enough you can always programatically extend the product list block to suite your needs, and add this extended block trough layout instead of catalog/product_list
.
One more thing before I wrap up. Preceding code snippets were taken from fully functional Magento extension built for the purposes of writing this article. If you want to see this code in action, the most simplest way is to visit this extensions GitHub page and download your copy.
Quick heads up. With goal of making it easier for your store customers to filter your custom product collection, in my next article I'll show you how to build upon the same principle and add the layered navigation block. Enjoy!
Can this be used to show off items that are on ‘sale’ that have had sale prices added in the item configurations?
How do you use it in your store though?
Do you create a CMS page then reference in a block? Or do you paste in the XML into that page?
If a product collection has more than 30 products in it then it is taking forever to load. Any tips on speeding up the rendering of the page?
Hello Robert,
this example goes only as far as to get you started. In production environment you should really use some form of caching (either block cache or you should cache the collection block is displaying). This way block loads almost instantly and your server doesn’t have to suffer fetching the same or nearly the same product collection again and again.
Regards,
Marko
Can you please suggest me how to add layered navigation in product collection, i have added following code in xml but filter not applied.
Can you please help me.
Hi what you’re looking for is:
http://www.techytalk.info/adding-layered-navigation-custom-controller-action-magento/
Cheers
Hello Marko,
Thanks for the helpful article on Collections, I hope, you or one of your readers is able to help me with a query that our development team has.
We are developing a multi-vendor Marketplace solution using Magento. One of our requirements is to allow Sellers to setup Stores with just Collection(s), the content of these Collection(s) shall be Products listed by different Seller(s) within the Marketplace. The idea is to let these “Virtual” Seller(s) aggregate Products (from several “Actual” Sellers) and influence a sale to earn affiliate commission from the Actual Seller. Does Magento support this kind of feature out of the box? If not is there a Plugin which does this?
Thanks again for your time and also for any help with the query.
With best Regards
Hello Marko,
Thanks for sharing this article, it helps a lot to boost for newbies like us.
Can you guide me for such requirement:
I am creating collection based on rules(like, product older than 20days and sold 0%) then apply special price discount on that particular product.
Any help would highly appreciated to get me to next step.
Thanks & Regards
Good write up on the subject – thanks. I was using a standard product collection method using layered navigation but didn’t want the filtering side-bar stuff. This code did the trick. Thanks again.