LemonStand Documentation

Displaying a list of products

There are at least 3 cases when you may want to display a list of products - a list of a categories products, a list of a custom group's products and a list of related products. That's why it is a good idea to place the code displaying a product list into a partial and render this partial when it is needed, passing a product collection as a parameter.

Start with creating a new partial. It doesn't matter what name you assign to it, but it is better if you assign some meaningful name, like shop:product_list.

To display a list of products, you should pass a variable, containing a list of products from outside page to the partial, as a parameter (please see the Displaying a full list of the category products section below for example of passing a list of products to the partial). In the partial code we assume what the variable $products is defined and passed to the partial. To output a list of products, use the standard foreach loop:

<ul>
  <? foreach ($products as $product): ?>
    <li>
      <h3><a href="<?=  $product->page_url('/product') ?>"><?=  h($product->name) ?></a></h3>
      <p><?= h($product->short_description) ?></p>
    </li>
  <? endforeach ?>
</ul>

The code creates links to the product page, using the page_url method of the Shop_Product class. In the example we suppose that products on your website have URLs like these: /product/product_1, /product/product_2. The base part of the URL (/product) - is what you pass to the page_url method as a method parameter. The remaining part (product_1, product_2) is added by the method and equals to a product's URL name.

You can also output a product image in the list. As products can have multiple images assigned to them and we need to output only a single image, we just display the first image, the one with with index 0 (zero). Also, it is always a good idea to check whether image exists, because you can forget to assign an image to some product. The following code snippet outputs a thumbnail, with width or height not greater than 130 pixels, of the first image of a product.

<?
  $image_url = $product->image_url(0, 130, 130);
  if ($image_url):
?>
  <img src="<?= $image_url ?>" alt="<?= h($product->name) ?>"/>
<? endif ?>

The final product list code:

<ul>
  <? foreach ($products as $product): ?>
    <li>
      <h3><a href="<?=  $product->page_url('/product') ?>"><?= h($product->name) ?></a></h3>
      
      <?
        $image_url = $product->image_url(0, 130, 130);
        if ($image_url):
      ?>
        <img src="<?= $image_url ?>" alt="<?= h($product->name) ?>"/>
      <? endif ?>
      
      <p><?= h($product->short_description) ?></p>
    </li>
  <? endforeach ?>
</ul>

Displaying a full list of the category products

To output a list of category products, you can place the following code to the category page.

<? $this->render_partial('shop:product_list', array(
  'products'=>$category->list_products()->find_all()
)) ?>

The list_products() method of the Shop_Category class returns a list of category products. The method accepts a number of parameters, which you can use for sorting a list of the category products. The result of this method is the Shop_Product object, which, in turns is a successor of the Db_ActiveRecord class. Thus the result can be used for further processing, for example for the paginating the product list, or for obtaining a collection of all products in the category. For displaying a full list of the category products we call the find_all() method, which returns a collection of the Shop_Product objects. Please read the Shop_Category class documentation for details about the list_products() method parameters. 

Paginating a product list

Product pagination allows you to split a large product list into pages. To output a single page of products, you need to know the following:

  • What is current page index? Page index determines an zero-based index of a currently opened page.
  • How many products each page should contain?
  • What is base URL of a page?

All these parameters will be passed to the product list partial from outside, because it is a good practice to develop universal partials which could be used in different places of your website. To prepare a paginated list of products, place the following code to the top of the product list partial code:

<?
  if (isset($paginate) && $paginate)
  {
    $page_index = isset($page_index) ? $page_index-1 : 0;
    $records_per_page = isset($records_per_page) ? $records_per_page : 3;
    $pagination = $products->paginate($page_index, $records_per_page);
  }
  else
    $pagination = null;

  $products = $products instanceof Db_ActiveRecord ? $products->find_all() : $products;
?>

This code snippet check whether the $paginate variable exists and if its value is true. If so, the code initializes the $page_index, $records_per_page variables and $pagination object. The $paginate variable is reserved for turning the pagination feature on and off.

The tricky code in the end of the snippet makes the code more dynamic. If the $products variable passed to the partial is an instance of the Db_ActiveRecord class, the code calls its find_all() method, which returns a product collection.

Now, we need to customize the product list displaying code on the category page:

<? $this->render_partial('shop:product_list', array(
  'products'=>$category->list_products(),
  'records_per_page'=>5,
  'paginate'=>true,
  'pagination_base_url'=>$category->page_url('category'),
  'page_index'=>$this->request_param(1, 0)
)) ?>

First, notice the value passed to the proaducts parameter. We do not call the find_all() method on the result of the category list_products() method this time, because now we want the product list partial to paginate the product list before displaying it.

The page_index parameter represents an index of a currently open page. Page index is passed through the URL. To read values from URL you use method request_param of the current page object. This method has 2 parameters - an index of the URL segment to return and a default value to return if the index does not exist. For category pages, page index follows the category URL name, like this: http://yoursite.com/category/computers/2 - the category URL name is "computers", the page is 2. To load the page index value from the URL you need to use the following code: $this->request_param(1, 0). This code returns a value of the URL parameter with index 1. If the index is not present, the code return 0. Indexes are zero-based. For category pages a parameter with index 0 represents the category URL name, so we need to load a parameter with index 1.

The pagination_base_url parameter will be used in the following chapter.

Displaying links to other pages

Obviously, you will want to output a list of links to other pages. You can do it, using the $pagination object obtained with $products->paginate() method call. $pagination object is an instance of the Phpr_Pagination class and it contains all pagination-related information. The way you output the page list is not limited with any restrictions. Let's output the page list in format like this:

It is better if you create a separate partial for displaying the pagination markup, because you may need to use pagination not only on a products page, but also on any other page (a list of customer orders, for example). Create a new partial and assign some name to it, for example "pagination". To output a list of links the partial will accept 2 parameters - the pagination object and a base URL of pages. Base URL is a page URL without a page index in the end. For example, if you display a list of a category products, your base URL could be /category/computers. An URL of a specific page could be /category/computers/2.

The following code outputs a pagination markup exactly like displayed on the image above. You can customize the code if it is needed.

<?
  $curPageIndex = $pagination->getCurrentPageIndex();
  $pageNumber = $pagination->getPageCount();
  $suffix = isset($suffix) ? $suffix : null;
?>
<p>
  Showing  <strong><?= ($pagination->getFirstPageRowIndex()+1).'-'.($pagination->getLastPageRowIndex()+1) ?></strong>
  of <strong><?= $pagination->getRowCount() ?></strong> records.
  Page:
  <? for ($i = 1; $i <= $pageNumber; $i++): ?>
    <? if ($i != $curPageIndex+1): ?><a href="<?= $base_url.'/'.$i.$suffix ?>"><? endif ?>
      <?= $i ?>
    <? if ($i != $curPageIndex+1): ?></a><? endif ?>
  <? endfor ?>
</p>
<p>
  <? if ($curPageIndex): ?><a href="<?= $base_url.'/'.$curPageIndex.$suffix ?>"><? endif ?>
    ? Previous page
  <? if ($curPageIndex): ?></a><? endif ?>
  |
  <? if ($curPageIndex < $pageNumber-1): ?><a href="<?= $base_url.'/'.($curPageIndex+2).$suffix ?>"><? endif ?>
    Next page ?
  <? if ($curPageIndex < $pageNumber-1):  ?></a><? endif ?>
</p>

The optional $suffix parameter can be used for cases when you need to pass some extra parameters in the URL after the page index. In the Demo website we use the $suffix parameter for passing the search string through the URL.

Now you can output the pagination markup below the product list in the product list partial. Just render the partial we just created:

<?
if ($pagination)
  $this->render_partial('pagination', array('pagination'=>$pagination, 'base_url'=>$pagination_base_url));
?>

See also:

Next: Product page
Previous: Category Page
Return to Building your online store

Comments

Jamie Brightmore

Monday, January 11, 2010

"To display a list of products, you should pass a variable, containing a list of products from outside page to the partial, as a parameter." - how do you do this?

I am getting an ' Undefined variable: products' message.

Aleksey Bobkov

Tuesday, January 12, 2010

@Jamie Brightmore

You can find the example of passing the $products variable to the partial in the Displaying a full list of the category products section on this page. In general, to pass parameters to a partial, you should pass an array of parameters to the $this->render_partial() method. For example:
$this->render_partial('some_partial', array('param1'=>$value1, 'param2'=>$value2)).
In the partial the passed parameters will be presented as variables - $param1 and $param2.

The render_partial method is described on this page: http://lemonstandapp.com/wiki/class_cms_controller/

philc

Monday, January 18, 2010

I'm running into the same problem as Jamie. I used the code listed in the "Displaying a fill list of the category products" section, but then it complains that "Call to a member function list_products() on a non-object", presumably meaning that the $category variable is not set.

How can we tell when a variable is set or defined in LemonStand? Is there an easy way to set variables manually for testing purposes?

Aleksey Bobkov

Monday, January 18, 2010

@ philc, @Jamie

The use of the shop:category action means that you creating a category page. If you look to the wiki API sections explaining the category page (http://lemonstandapp.com/wiki/category_page/) or the shop:category action (http://lemonstandapp.com/wiki/action_shop_category/) you will see that the $category object can be null if a category was not found. That is why you need to add a conditional block to check whether the category was found:

<? if (!$category): ?>
<h2>We are sorry, the specified category not found.</h2>
<? else: ?>
... your normal category page ...
<? endif ?>

In your case the category was not found because you do not pass the category URL name to the page URL. The shop:category action loads a category (the $category object variable) by its URL name, and it loads the category URL name from the actual page URL. In your case the URL could look like this:
http://localhost:8888/lemonstand/product-list/your_category_url_name
You specify a category URL name in the Category form. If the category URL Name is 'animals', the URL would look like this:
http://localhost:8888/lemonstand/product-list/animals

Add your comment

Loading form...