14 min read

Creating the Cart Views and Forms

Now that we have our Model and Controller created, we can now start putting everything together and get the cart working.

Cart forms

The Cart will use two forms Storefront_Form_Cart_Add and Storefront_Form_Cart_Table. The add form is displayed next to the products so users can add items to the Cart, and the table form is used to display all the items in the cart so users can edit them.

Add form

The add form can be used by customers browsing the store to quickly add items to their shopping cart. This form will look like the one shown in the screenshot below when it is rendered:

Creating a Shopping Cart using Zend Framework: Part 2

Let’s add the code to create the add form now.

application/modules/storefront/forms/Cart/Add.php

class Storefront_Form_Cart_Add extends SF_Form_Abstract
{
public function init()
{
$this->setDisableLoadDefaultDecorators(true);
$this->setMethod('post');
$this->setAction('');
$this->setDecorators(array(
'FormElements',
'Form'
));
$this->addElement('text', 'qty', array(
'decorators' => array(
'ViewHelper'
),
'style' => 'width: 20px;',
'value' => 1
));
$this->addElement('submit', 'buy-item', array(
'decorators' => array(
'ViewHelper'
),
'label' => 'Add to cart'
));
$this->addElement('hidden', 'productId', array(
'decorators' => array(
'ViewHelper'
),
));
$this->addElement('hidden', 'returnto', array(
'decorators' => array(
'ViewHelper'
),
));
}
}

The add form contains four elements—qty, buy-item, productId, and returnto. We can see that it is much like the other forms we have created previously. The only major difference here is that we use the setDisableLoadDefaultDecorators() method to disable the default decorators for the form (not the elements). We do this because we do not want the form to contain the default definition list markup (<dl>). We also only use the ViewHelper decorator on each element so that the <dt> and <dd> tags are omitted

Table form

The table form is going to form the customer shopping cart. Customers will use this form to view, update, and remove items from their cart. This form will look similar to the one showed below when it is rendered:

Creating a Shopping Cart using Zend Framework: Part 2

Let’s add the code for the table form now:

application/modules/storefront/forms/Cart/Table.php

class Storefront_Form_Cart_Table extends SF_Form_Abstract
{
public function init()
{
$this->setDisableLoadDefaultDecorators(true);
$this->setDecorators(array(
array(
'ViewScript',
array('viewScript' => 'cart/_cart.phtml')
),
'Form'
));
$this->setMethod('post');
$this->setAction('');
$this->addElement('submit', 'update-cart', array(
'decorators' => array(
'ViewHelper'
),
'label' => 'Update'
));
}
}

Th e table form is highly specialized. Therefore, we have chosen to use a ViewScript decorator. To do this, we fi rst disable the default decorators using the setDisableLoadDefaultDecorators().

We then need to configure the forms decorators. We will only have two decorators for the form, ViewScript and Form. This means that if we render the form, the update-cart element will not be rendered because we have not included the FormElements decorator. This is where the ViewScript decorator comes in. We can use this decorator to render a View script, in this case cart/_cart.phtml. We then have access to all the elements within the form inside this View script, meaning we can create highly specialized markup without needing to use lots of complicated decorators.

Also, the table form will need to have fi elds dynamically added to it as we need a form element for each cart item. We will look at this shortly when we create the View Helper and Views for the Cart.

The ViewScript decorator uses a View Partial to render its view script. This has an overhead as it clones the view instance. Generally, partials should be avoided in large numbers so do not over use them or the ViewScript decorator.

SF_Form_Abstract

You may have noticed that our forms did not subclass Zend_Form as in our previous examples. Also, this time we have extended from the SF_Form_Abstract class. This is because we have done some minor refactoring to the SF library so that we can inject the Model into the form.

library/SF/Form/Abstract.php

class SF_Form_Abstract extends Zend_Form
{
protected $_model;
public function setModel(SF_Model_Interface $model)
{
$this->_model = $model;
}
public function getModel()
{
return $this->_model;
}
}

The new SF_Form_Abstract class subclasses Zend_Form and adds two new methods, setModel() and getModel(). These simply set, and get, the protected $_model property. This then means that when we instantiate the form, we can pass in the model inside the options array.

$form = new SF_Form_Abstract(array('model' => new myModel()));

Here we are taking advantage of the fact that the setOptions() method will look for setters that match elements in the options array. In our case, the setOptions() class will find the setModel() method, call it, and pass in the model. This type of functionality is very common in Zend Framework components. It is always worth checking the setOptions() methods on components to see if you can extend them in this way.

To get the model injected on instantiation, we also need to make a minor change to the SF_Model_Abstract.

library/SF/Model/Abstract.php

public function getForm($name)
{
if (!isset($this->_forms[$name])) {
$class = join('_', array(
$this->_getNamespace(),
'Form',
$this->_getInflected($name)
));
$this->_forms[$name] = new $class(
array('model' => $this)
);
}
return $this->_forms[$name];
}

He re, we simply pass in an array containing the model ($this) when we first instantiate the form class. We now have access to our Model from within our forms.

Cart View Helper

Th e Cart View Helper is responsible for creating many of the display elements for the cart. Therefore, we will break it down and look at each method in turn.

application/modules/storefront/views/helpers/Cart.php

class Zend_View_Helper_Cart extends Zend_View_Helper_Abstract
{
public $cartModel;
public function Cart()
{
$this->cartModel = new Storefront_Model_Cart();
return $this;
}

The main Cart() method instantiates a new Cart Model and then returns a reference to itself so that we can chain calls to the other methods.

application/modules/storefront/views/helpers/Cart.php

public function getSummary()
{
$currency = new Zend_Currency();
$itemCount = count($this->cartModel);
if (0 == $itemCount) {
return '<p>No Items</p>';
}
$html = '<p>Items: ' . $itemCount;
$html .= ' | Total: '.$currency->toCurrency
($this->cartModel->getSubTotal());
$html .= '<br /><a href="';
$html .= $this->view->url(array(
'controller' => 'cart',
'action' => 'view',
'module' => 'storefront'
),
'default',
true
);
$html .= '">View Cart</a></p>';
return $html;
}

The getSummary() method creates the HTML that will be used to display a summary of the cart items and subtotal to the user. This will be displayed below the main category menus.

application/modules/storefront/views/helpers/Cart.php

public function addForm(Storefront_Resource_Product_Item$product)
{
$form = $this->cartModel->getForm('cartAdd');
$form->populate(array(
'productId' => $product->productId,
'returnto' => $this->view->url()
));
$form->setAction($this->view->url(array(
'controller' => 'cart',
'action' => 'add',
'module' => 'storefront'
),
'default',
true
));
return $form;
}

The addForm() method will return a form for adding a single product to the cart. This method accepts one parameter $product that must be an instance of Storefront_Resource_Product_Item. We will use this to render individual add to cart forms for each product.

application/modules/storefront/views/helpers/Cart.php

public function cartTable()
{
$cartTable = $this->cartModel->getForm('cartTable');
$cartTable->setAction($this->view->url(array(
'controller' => 'cart' ,
'action' => 'update'
),
'default'
));
$qtys = new Zend_Form_SubForm();
foreach($this->cartModel as $item) {
$qtys->addElement('text', (string) $item->productId,
array(
'value' => $item->qty,
'belongsTo' => 'quantity',
'style' => 'width: 20px;',
'decorators' => array(
'ViewHelper'
),
)
);
}
$cartTable->addSubForm($qtys, 'qtys');
// add shipping options
$cartTable->addElement('select', 'shipping', array(
'decorators' => array(
'ViewHelper'
),
'MultiOptions' => $this->_getShippingMultiOptions(),
'onChange' => 'this.form.submit();',
'value' => $this->cartModel->getShippingCost()
));
return $cartTable;
}

The cartTable() method will return the table containing all our cart items, their costs, and totals. This will be used to update items in the cart. We create a subform to dynamically add the cart items quantity elements at runtime. The reason we use a subform is so we can easily get the whole set of quantity fi elds from the form, and later iterate over them in the View script.

The form will need to contain an array of quantity text elements so that we can iterate over them in the updateAction in the controller. To create this array, we pass the belongsTo option to the addElement() method, which will tell the form that these elements are an array with the name quantity. We also set the value of the element to the qty held in the cart item. We also need a way of passing the productId for each cart item. To do this, we set the element name to the productId of the item. This also helps us by providing a unique name for each element (we have to cast this to a string). It will create a set of text form elements like:

<input type="text" style="width: 20px;" value="1" id="quantity-21"
name="quantity[21]"/>

<input type="text" style="width: 20px;" value="5" id="quantity-10"
name="quantity[10]"/>

Once we have all the quantity elements in the subform, we then add the whole subform to the main table form using the addSubForm() method. We give this the name of qtys, which we will use in the View script later to retrieve the elements.

We also add the shipping options to the main table form. Here, we use the _getShippingMultiOptions() method to populate the select elements options and set the value to the currently selected shipping option of the cart.

application/modules/storefront/views/helpers/Cart.php

public function formatAmount($amount)
{
$currency = new Zend_Currency();
return $currency->toCurrency($amount);
}

The formatAmount() method is a little helper method we use to display amounts from the Cart. This may not be necessary in the future as there is a proposal for a currency View Helper that we would use instead.

application/modules/storefront/views/helpers/Cart.php

private function _getShippingMultiOptions()
{
$currency = new Zend_Currency();
$shipping = new Storefront_Model_Shipping();
$options = array(0 => 'Please Select');
foreach($shipping->getShippingOptions() as $key => $value) {
$options["$value"] = $key . ' - ' . $currency->toCurrency($value);
}
return $options;
}
}

Our final method is the private _getShippingMultiOptions() method. This is used internally by the cartTable() method to populate the shipping select element’s options. This method gets the shipping options from the Shipping Model and creates an array suitable for the multiOptions option.

Cart View scripts

Now that we have all the tools created that we will need to build our cart, we can start creating the user interface.

Cart view.phtml

The view.phtml is the View that is rendered by the viewAction of the CartController. This View includes a title and renders the cartTable form

application/modules/storefront/views/scripts/cart/view.phtml

<h3>shopping <span>cart</span></h3>
<?=$this->Cart()->cartTable();?>

Cart _cart.phtml

The ViewScript decorator attached to the table form will render the _cart.phtml View. When it renders, the ViewScript decorator will create a view partial and pass in the form as the element property for this View script.

application/modules/storefront/views/scripts/cart/_cart.phtml

<div style="padding: 8px;">
<table style="width: 100%;">
<tbody>
<?
$i = 0;
foreach($this->element->getModel() as $item):
?>
<tr <? if($i % 2){ echo 'class="odd"';};?>>
<td><?=$this->Escape($item->name); ?></td>
<td><?=$this->element->qtys->getElement
($item->productId); ?></td>
<td class="rt"><?=$this->Cart()->formatAmount
($item->getLineCost()); ?></td>
</tr>
<?
++$i;
endforeach;
?>
<tr>
<td colspan="2" class="rt">SubTotal:</td>
<td class="rt colRight"><?=$this->Cart()
->formatAmount($this->element->getModel()
->getSubTotal()); ?></td>
</tr>
<tr>
<td colspan="2" class="rt">Shipping: <?=$this->element
->getElement('shipping');?></td>
<td class="rt colRight"><?=$this->Cart()
->formatAmount($this->element->getModel()
->getShippingCost()); ?></td>
</tr>
<tr>
<td colspan="2" class="rt">Total:</td>
<td class="rt"><?=$this->Cart()->formatAmount($this
->element->getModel()->getTotal()); ?></td>
</tr>
</tbody>
</table>
<?=$this->element->getElement('update-cart'); ?>
</div>

The HTML produced by this script will look similar to the following screenshot:

Creating a Shopping Cart using Zend Framework: Part 2

The main aspect here is the line items. We need to iterate over the cart and display each product line item.

<?
$i = 0;
foreach($this->element->getModel() as $item):
?>
<tr <? if($i % 2){ echo 'class="odd"';};?>>
<td><?=$this->Escape($item->name); ?></td>
<td>
<?=$this->element->qtys->getElement($item->productId); ?>
</td>
<td class="rt">
<?=$this->Cart()->formatAmount($item->getLineCost()); ?>
</td>
</tr>
<?
++$i;
endforeach;
?>

Here, we get the Cart Model from the form using our new getModel() method that we created earlier in the SF_Form_Abstract and iterate over it. As we iterate over the Cart Model, we display all the products and line costs. We also get the quantity form elements. To retrieve the correct quantity form element for each product, we access the qtys subform and use the getElement() method. We pass in the items productId as we named our quantity form elements using the productId earlier.

All of the other form data is rendered in a similar way. We either get data from the Cart Model, or get elements from the form itself. By using the ViewScript decorator, we can see that it is much easier to mix form and non-form elements.

Layout main.phtml

application/layouts/scripts/main.phtml

<div class="left categorylist">
<?= $this->layout()->categoryMain; ?>
<? if (0 < count($this->subCategories)):?>
<div class="sub-nav">
<h3>in this <span>category</span></h3>
<ul>
<? foreach ($this->subCategories as $category): ?>
<li><a href="<?=$this->url(array('categoryIdent' =>
$category->ident), 'catalog_category', true
);?>"><?=$category->name; ?></a></li>
<? endforeach; ?>
</ul>
</div>
<? endif; ?>
<div>
<h3>in your <span>cart</span></h3>
<?= $this->Cart()->getSummary(); ?>
</div>
</div>

We need to display the cart summary to the users so that they can see a brief overview of the items in their cart. To do this, we will use the Cart View Helper and the getSummary() method that looks similar to the following screenshot:

Creating a Shopping Cart using Zend Framework: Part 2

Catalog index.phtml

application/modules/storefront/view/scripts/catalog/index.phtml

<p><?=$this->productPrice($product); ?></p>
<?=$this->Cart()->addForm($product); ?>

When displaying a list of products, we want the user to be able to add the product to their cart at that point. To do this, we render the cart add form under the price. This will make our catalog listing look like the one shown below:

Creating a Shopping Cart using Zend Framework: Part 2

Catalog view.phtml

application/modules/storefront/view/scripts/catalog/view.phtml

<p><?=$this->productPrice($this->product); ?></p>
<?=$this->Cart()->addForm($this->product); ?>

Just like the index.phtml, we need to render the cart add form after the product price. This will make our details page look like this:

Creating a Shopping Cart using Zend Framework: Part 2

Summary

In this two-part article series, we learnt about:

  • Creating Models that do not use a database as a data source
  • Using Zend_Session_Namespace
  • Implementing the Cart Views and Controllers
  • More Forms, View Helpers, and so on

If you have read this article you may be interested to view :

LEAVE A REPLY

Please enter your comment!
Please enter your name here