Bestseller module (with Toolbar!) – Magento 1.2.1
Posted on 07. Feb, 2009 by Fido in Development, Magento, Module
How many people were disappointed to install Magento’s test data and find out that the home page “Best sellers” was just pain HTML placed into the CMS home page? I certainly was one of those people. That’s why I decided to create a Bestseller Module that was dynamic and harnessed the power of Magento’s built in features. This post shows you the code and gives and explanation of what is happening.
For those of you impatient to get to the code, here it is:
<?php
//bestseller module - grabs from all products, returns in order of total quantity ordered
class Mage_Catalog_Block_Product_Bestseller extends Mage_Catalog_Block_Product_Abstract {
/**************************************************************************
Override _preparyLayout() method found in Mage_Core_Block_Abstract
Retrieve sold products collection, prepare toolbar
@Return Mage_Catalog_Block_Product_Bestseller
*************************************************************************/
public function _prepareLayout() {
$storeId = Mage::app()->getStore()->getId();
$products = Mage::getResourceModel('reports/product_collection')
->addOrderedQty()
->addAttributeToSelect('*') //Need this so products show up correctly in product listing
->setStoreId($storeId)
->addStoreFilter($storeId);
Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($products);
Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($products);
$this->setToolbar($this->getLayout()->createBlock('catalog/product_list_toolbar', 'Toolbar'));
$toolbar = $this->getToolbar();
$toolbar->setAvailableOrders(array(
'ordered_qty' => $this->__('Most Purchased'),
'name' => $this->__('Name'),
'price' => $this->__('Price')
))
->setDefaultOrder('ordered_qty')
->setDefaultDirection('desc')
->setCollection($products);
return $this;
}
/**************************************************************************
Retrieve product collection. A protected function in keeping
with OOP principals
@Return Mage_Reports_Model_Mysql4_Product_Collection
*************************************************************************/
protected function _getProductCollection() {
return $this->getToolbar()->getCollection();
}
/**************************************************************************
Public interface to read toobar object template HTML
@Return String (HTML for Toolbar)
*************************************************************************/
public function getToolbarHtml() {
return $this->getToolbar()->_toHtml();
}
/**************************************************************************
Public interface to get list vs grid mode form toolbar object.
@Return String (grid || list)
*************************************************************************/
public function getMode() {
return $this->getToolbar()->getCurrentMode();
}
/**************************************************************************
Public interface and alias to protected _getProductCollection method
@Return Mage_Reports_Model_Mysql4_Product_Collection
*************************************************************************/
public function getLoadedProductCollection() {
return $this->_getProductCollection();
}
}
Usage:
1) Install (copy) the Bestseller.php file here: app/code/local/Mage/Catalog/Block/Product/Bestseller.php
2) Copy and paste this line into any CMS page: {{block type=”catalog/product_bestseller” template=”catalog/product/list.phtml”}}
Explanation:
This module was designed as simply as possible, harnessing “stock” features of Magento. As such, it uses the standard “list.phtml” template used in Magento (used anytime you do a stock install and look at category of products, or other product listings). This is found at: app/design/frontend/default/default/template/catalog/product/list.phtml.
The Toolbar template is found here: app/design/frontend/default/default/template/catalog/product/list/toolbar.phtml
I did not create my own module for this since the Bestseller module works perfectly fine on it’s own as an addition to the Mage core (although in the Local folder of course! It doesn’t belong in the core files!)
This code doesn’t use the __constructor method, but instead goes right to the _prepareLayout method, as here we can access the Layout object (Mage_Core_Model_Layout) and thus include our Toolbar block. The first order of business it to grab our product collection. We grab it from the “reports/product_collection” resource model. The “Reports” module is where to go to create reports on past activity within Magento (further explanation is beyond the scope of this article). In this case, see: app/code/core/Mage/Reports/Model/Mysql4/Product/Collection.php for any code investigation. Our product collection does minium processing – We add the “OrderedQty” attribute, all other attributes (you can narrow these down if you need), set the store ID and add the store filter. We also add visibility filters so we don’t get products showing which should not be.
Next, we create a new Toolbar block (the createBlock method is a factory for blocks). The Toolbar block (Mage_Catalog_Block_Product_List_Toolbar) extends a pagination class (Mage_Page_Block_Html_Pager) and so is an ideal way to organize a product collection. This will handle limiting the number of products collected, the order they are in, grid vs list mode, and whether they are acsending or descending. We do bare minimum filtering to the product collection ourselves and let the pagination class do the rest for us! Note that we set the “available orders” in our code. Without setting this, the “stock” orders used to filter the results are “Position”, “Name” and “Price” (position will appear as “Best Value”). We don’t want Best Value, we obviously want Ordered Quantity (I used “Most Purchased”). We also set the default order, which is “Descending” (we want the most purchased first).
The rest of the functions here are utility functions to make the list.phtml work together with our Bestseller block.
- getToolbarHtml is called within the list.phtml file. Here we grab the HTML of the Toolbar using the block’s standard (built-in) _toHtml method which is responsable for outputting final HTML of the block.
- getMode is called within list.phtml also. It is used to grab the current mode, either “list” or grid”.
- getLoadedProductCollection is the public method that allows the list.phtml file to use the product collection and output its details. It is a public interface to the protected _getProductCollection method. Using two methods isn’t necessary for this, but it is in keeping with OOP principals of hiding your processing code from the “public” (code using your objects).
That’s basically it! We have a complete module that will successfully ouput best selling products and use the Toolbar.
Items to note:
- If you use the toolbar to order by “name” or “price” you will no longer be filtering by best selling products. This is because we are simply grabbing all sold products as the collection. To always be best selling and then grab by name or price would be a slightly more complicated feature (and hopefully something we explore in the near future).
- If you order by “name” or “price”, it changes the order to “Ascending”. If you then sort back to “Most Purchased”, it stays in Ascending order (showing the least purchased first). This is not so much a bug as it is an unintended consequence of how the Toolbar / Pagination class handles remembering the current order. It doesn’t like mixing in Asc and Desc defaults or the “order by” options.
- For “fixing” either of the above 2, the best bet might be to create our own module and override the Toolbar and/or Pager classes to do exactly what we want. This is something I hope to explore in the near future.
Please note that I use the class name a lot while referring you to objects. This is handy knowledge as their naming scheme relates directly to their location within the code libraries.
I hope you all find this enlightening! We have covered a few things: creating a product collection of sold products (via the “Reports” module), creating and implementing a Block within another Block (without using a custom module with layout xml files), and how to use the Toolbar within any product collection you use in the future!
(P.S. – I thoroughly plan on attempting to fix the conflict between the theme we are using and the comment boxes that show up in the code viewer plugin!)




18 Comments
matt
09. Feb, 2009
thank you for sharing this! this is exactly the area I am struggling with at the moment – i have products that fall under two categories, and am trying to figure out how (in the product list page) to show only those products that are in both categories). this code block should point me in the right direction.
looking forward to a better code-reader – it’s a little difficult to read the code within the narrow column
cheers
Kris
10. Feb, 2009
Glad to find someone explaining difficult Magento concepts clearly! Exactly what I’m looking for.
I followed the steps above (under “Usage”) but nothing show up in target page. The block statement is missing from the page’s code when I look at it as well. I’m assuming I’m missing something here. Should I be referencing another post for additional directions?
Thanks again!
K
Fido
10. Feb, 2009
The {{block section is there, it just goes on to a 2nd line due to the small width of blog posts in this theme.
These are the only 2 elements we used on this “module”, which are above.
Please make sure that you have the correct double-quotes when you copy and paste, as the ones shown above may not be the plain text ones they need to be. Make sure you have the full {{block … }} section. I just copied and pasted them into Dreamweaver (in code view) and saw that they double quotes were not the correct ones – which will effect your code (I blame our wysiwyg for changing those on me…)
Bloowerks
10. Feb, 2009
How should i go to CMS page to paste this?
{{block type=”catalog/product_bestseller” template=”catalog/product/list.phtml”}}
Under Magento Admin?
Fido
11. Feb, 2009
Yep, that code is meant to be used on a CMS page, which you can create / edit / delete in the Admin area.
Tomislav Bilic
21. Feb, 2009
Wow, looks lovely. I’ll surely give it a try in the next few days.
thomas
09. Apr, 2009
Hi thanks for this lovely code , i am actually looking to display it on category page … so this isn’t a CMS page , i tried to adapt
{{block type=”catalog/product_bestseller” template=”catalog/product/list.phtml”}}
to something like
and to copy it to catalog.xml …. but cant get it to work maybe someone here will be able to help me … ?
kenduret
08. Jun, 2009
Thanks for this- I’m finding some of your posts very helpful. The code comments are irritating though so I thought I would share this code highlighter I’ve been using.
http://www.frank-verhoeven.com/wordpress-plugin-fv-code-highlighter/
Fido
08. Jun, 2009
Thanks, We’ll look into that highlighter!
Jools
16. Jul, 2009
Thanks for the code. Very useful.
What do you think is the best way of handling “configurable products” which are made up from simple products that have visibility set to nowhere? Any ideas? I could either return products no matter what the visibility is, and then link off from them to the parent configurable product page perhaps, but better would be if there was another way. perhaps some post processing of the array data ?
Fido
16. Jul, 2009
That’s an interesting question:
Clearly in the code above, the code:
Mage::getSingleton(’catalog/product_status’)->addVisibleFilterToCollection($products);
21.Mage::getSingleton(’catalog/product_visibility’)->addVisibleInCatalogFilterToCollection($products);
handles that. However, since configurable products are a slightly different animal, it appears that Magento is not showing those using the code above (Is this correct? I haven’t tested that myself).
If you check out the ‘catalog/product_visibility’ singleton, you’ll see some other functions you may wish to try.
For instance:
1) addVisibleInSearchFilterToCollection
2) addVisibleInSiteFilterToCollection
Might want to see if you have any luck with those. I would try the visibility in “site” filter first. (Try replacing the filter in the above code with that function).
Hope this helps!
jools
20. Jul, 2009
There doesnt seem to be a way to do it really without postprocessing – as the purchasing amounts are stored in the simple products not the parent it seems) – so i must grab the top 20 bestsellers (with any visibility), then loop through them checking if they have parents. if they do, check visibility of parent and add to new object collection (if it hasnt already been added). if the product has no parents, check if the visibility is set, and add to collection if its visible. There are a few other small issues, but I don’t know if i mind about those (for example to place a configurable product made from two simple products with 20 purchases each, should appear before a simple product with 30 purchases.)
This method of course, wouldnt be compatible with your toolbar code, and wouldn’t be all that fast i guess either. I looked into another bestseller module, and it worked in a similar way as i describe.
Another way might be to have a “buy” counter on the configurable product, and increment when a child product is purchased. but then you need a filter for “has no children” when selecting, or you would still need some postprocessing.
deni
31. Jul, 2009
Interesting…:) Nice thanks
Jimmy Jørgensen
10. Aug, 2009
Hi.
I did try to follow your how to, but i’m having some problem. The first one is i havn’t got the dir/path : app/code/local/Mage/Catalog/Block/Product/Bestseller.php
mine only goes to app/code/local/Mage.
Why is that?.
Fido
10. Aug, 2009
Hi Jimmy -
You need to create the missing directories within the “local” directory – They do not yet exist in most cases.
Cole
03. Sep, 2009
I can’t seem to get anything to display
I copied all the code to the correct file, refreshed cache, placed in block declaration and I get nothing on my home page
I used your copy to clipboard for the Bestseller.php script. Has that been known to have issues?
Pagchen
01. Nov, 2009
Hi, I had to rename Bestseller.php to bestseller.php otherwise I got an 404 error (Linux server specifity I guess).
Still don’t get any items to show though.
Steffen
19. Jan, 2010
Thanks for the code, I think really going crazy to create a working custom module with a similar functionality.
Keep up the good work!
Leave a reply