All about Magento E-commerce Store.......MagentoForum: Bestseller module (with Toolbar!)

Tuesday, July 12, 2011

Bestseller module (with Toolbar!)

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:

01.<?php
02.//bestseller module - grabs from all products, returns in order of total quantity ordered
03.class Mage_Catalog_Block_Product_Bestseller extendsMage_Catalog_Block_Product_Abstract {
04. 
05./**************************************************************************
06.Override _preparyLayout() method found in Mage_Core_Block_Abstract
07.Retrieve sold products collection, prepare toolbar
08. 
09.@Return Mage_Catalog_Block_Product_Bestseller
10.*************************************************************************/
11.public function _prepareLayout() {
12.$storeId = Mage::app()->getStore()->getId();
13. 
14.$products = Mage::getResourceModel('reports/product_collection')
15.->addOrderedQty()
16.->addAttributeToSelect('*'//Need this so products show up correctly in product listing
17.->setStoreId($storeId)
18.->addStoreFilter($storeId);
19. 
20.Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($products);
21.Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($products);
22. 
23.$this->setToolbar($this->getLayout()->createBlock('catalog/product_list_toolbar''Toolbar'));
24. 
25.$toolbar $this->getToolbar();
26. 
27.$toolbar->setAvailableOrders(array(
28.'ordered_qty'  => $this->__('Most Purchased'),
29.'name'      => $this->__('Name'),
30.'price'     => $this->__('Price')
31.))
32.->setDefaultOrder('ordered_qty')
33.->setDefaultDirection('desc')
34.->setCollection($products);
35.return $this;
36.}
37. 
38./**************************************************************************
39.Retrieve product collection. A protected function in keeping
40.with OOP principals
41. 
42.@Return Mage_Reports_Model_Mysql4_Product_Collection
43.*************************************************************************/
44.protected function _getProductCollection() {
45.return $this->getToolbar()->getCollection();
46.}
47. 
48./**************************************************************************
49.Public interface to read toobar object template HTML
50. 
51.@Return String (HTML for Toolbar)
52.*************************************************************************/
53.public function getToolbarHtml() {
54.return $this->getToolbar()->_toHtml();
55.}
56. 
57./**************************************************************************
58.Public interface to get list vs grid mode form toolbar object.
59. 
60.@Return String (grid || list)
61.*************************************************************************/
62.public function getMode() {
63.return $this->getToolbar()->getCurrentMode();
64.}
65. 
66./**************************************************************************
67.Public interface and alias to protected _getProductCollection method
68. 
69.@Return Mage_Reports_Model_Mysql4_Product_Collection
70.*************************************************************************/
71.public function getLoadedProductCollection() {
72.return $this->_getProductCollection();
73.}
74.}
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 _prepareLayoutmethod, 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 ourBestseller 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:
  1. 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).
  2. 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.
  3. 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!)

No comments:

Post a Comment