Advertisement

Magento for Designers: Part 5

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Magento for Designers.
Magento for Designers: Part 4
Magento for Designers: Part 6

Magento is a stunningly powerful e-commerce platform. In this miniseries, we'll learn how to get started with the platform, getting to know the terminologies, setting up a store and all related aspects of it and finally learn how to customize it to make it our very own.

In this fifth part, we'll be building one of the main views of our store - the product information page. Excited? Let's get started!


The Full Series


A Quick Recap

In the last part, we laid down the framework for the theme by building the repeating portions of the theme and essentially defining the general layout of the theme.

We also took a practical look at how theming in Magento works by going through how the layout is constructed, how the blocks work and how all the different pieces of the puzzle fit in together.


What are We Building Today?

Today, we'll build the individual product view page. Just like before, the source files, both the front end and back end, are included. You can use it to see how the page looks but outside of that, the theme should look broken since, you know, we haven't touched the rest of the views yet. So till we build the rest, try to not venture out of our sandbox.


Goals for the Page

Our goal for this specific page is relatively simple. We want a no-nonsense page that does only the essentials. Thus, I've drawn up a short list of elements that I think it needs:

  • The product's title: Fairly obvious
  • Ability to show an image of the product
  • A quick and full overview
  • Availability and price of the product and finally
  • An add to cart button to place it in the cart

That's it. I want to keep it as simple as possible and thus opted to not over do it. Once you've learnt the general principles, feel free to add as many whizbang features as possible.


The Basic Look

The page basically has to look like so:


Step 1 - The HTML

We'll first look at the HTML for the content part alone. I'm assuming you're fairly fluent in HTML and CSS so I'll skip to the fairly important parts.

<div id="content" class="product">
<div id="main-product-image"><img src="images/primg.gif" /></div>

<div id="product-details">
<div id="product-availability">Availability <span class="available">In stock</span></div>
<div id="product-price">Price <span>$29.00</span></div>
<a class="button" href="#">Add to cart</a>
</div>

<h1>Photoshop to HTML</h1>

<h2>Quick Overview</h2>
<p class="quick-overview">
</p>

<h2>Product Description</h2>
<div class="product-description">

</div>

First up, notice that I've wrapped the product image with a div to make it easier to add features in the future. Suppose you want to add a quick caption to the displayed image in the future, this way will let us add this a lot more quicker.

We have another div element holding the availability and pricing information along with the add to cart button. We'll be sprucing it up with a little CSS3 in a bit.

The rest of the HTML is very straightforward. The title resides in a h1 section while the individual section headings take up h2. The quick overview is nested in a paragraph while the full overview takes up a div.

We can move on to the styling part now.


Step 2 - The CSS

/* Product page */

#main-product-image {
	margin: 0 20px 10px 0;
	padding: 10px;
	float: left;
	border: 1px solid #E1E1E1;
	background: #F3F3F3;
}

#product-details {
	width: 180px;
	padding: 10px;
	float: right;
	border: 1px solid #E1E1E1;
	background: #F3F3F3;
	margin: 0 0 0 20px;
}

#product-availability span.available, #product-price span {
	color: #7db000;
	float: right;
}

.button {
	margin: 10px auto;
	display: block;
	width: 140px;
	padding: 5px 10px;
	text-align: center;
	text-decoration: none;
	color: #555;
	border: 1px solid #ccc;
	font-size: 18px;
	background: #ddd;
	border-radius: 12px;
	-webkit-border-radius: 12px;
	-moz-border-radius: 12px;
	box-shadow: 1px 1px 2px rgba(0,0,0,.5);
	-webkit-box-shadow: 1px 1px 2px rgba(0,0,0,.5);
	-moz-box-shadow: 1px 1px 2px rgba(0,0,0,.5);
	text-shadow: #fff 0px 1px 1px;
	background: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#cccccc));
	background: -moz-linear-gradient(top,  #eeeeee,  #cccccc);

	}

.button:hover {
	background: #014464;
 	background: -webkit-gradient(linear, left top, left bottom, from(#cccccc), to(#999999));
	background: -moz-linear-gradient(top,  #cccccc,  #999999);
	color: #000;
	}

.button:active {
	-moz-box-shadow: 0 2px 6px black;
	-webkit-box-shadow: 0 2px 6px black;
	}

Nothing fancy here. Very basic CSS to place the elements in position.

I've also used a bit of CSS3 to make the buttons a little better looking.


Step 3 - Creating our catalog.xml File

As I mentioned in the earlier part, each module gets its own XML file to dictate what items to include and the general layout of the page. The page we're building today relies on a file called catalog.xml to define its contents and structure.

This file should be present in the layout folder so let's create an XML file and name it catalog.

The complete file for today looks like so. I'll explain each bit part by part below.

<?xml version="1.0"?>
<layout version="0.1.0">
    <catalog_product_view translate="label">
        <label>Catalog Product View (Any)</label>
        <!-- Mage_Catalog -->
        <reference name="root">
            <action method="setTemplate"><template>page/1column.phtml</template></action>
        </reference>
		<reference name="head">
            <action method="addCss"><stylesheet>css/product.css</stylesheet></action>
        </reference>
        <reference name="content">
            <block type="catalog/product_view" name="product.info" template="catalog/product/view.phtml">
               <block type="catalog/product_view_media" name="product.info.media" as="media" template="catalog/product/view/media.phtml"/>
               <block type="catalog/product_view_description" name="product.description" as="description" template="catalog/product/view/description.phtml"/>
               <block type="catalog/product_view_type_simple" name="product.info.simple" as="product_type_data" template="catalog/product/view/type/simple.phtml"/>
               <block type="catalog/product_view" name="product.info.addtocart" as="addtocart" template="catalog/product/view/addtocart.phtml"/>
            </block>
        </reference>
    </catalog_product_view>
</layout>

Disregard the initial XML and layout version declarations. They're of no significance to us now.

<catalog_product_view translate="label">

First, we let the system know that we're intending to modify the product view part of the system. This is because catalog.xml houses the layout for a number of other views and so it's imperative that we specify which view we intend to modify.

<reference name="root">
            <action method="setTemplate"><template>page/1column.phtml</template></action>
</reference>

Now, we tell Magento to load up the 1column.phtml file as the main master template for this view. This is because each individual view can use any predefined structure. For example, your home page could use a very complex custom structure, your product page a dual column and your search page a single column layout.

If nothing is specified, it'll load up the default template mentioned in page.xml. Since we're using it for everything else, this part is redundant but when you're modifying this template for your personal use, editing the file's name is a lot easier than adding chunks of XML to the layout file.

<reference name="head">
    <action method="addCss"><stylesheet>css/product.css</stylesheet></action>
</reference>

And now we come across one of the niftier parts of Magento. Yes, we can throw all our view specific CSS into one giant CSS file but we aren't cavemen, are we? Cries of multiple HTTP requests aside, this method lets us streamline our CSS better.

First we acquire a reference to the head section of the file and then insert our page specific content into it. Here, I'm inserting a file called product.css which contains all the page specific CSS we looked at above.

Note that you aren't limited to CSS. Including JS and other assets is possible too.

<block type="catalog/product_view" name="product.info" template="catalog/product/view.phtml">

We now ask Magento to use a specific template for the content portion of the page

 <block type="catalog/product_view_media" name="product.info.media" as="media" template="catalog/product/view/media.phtml"/>
 <block type="catalog/product_view_description" name="product.description" as="description" template="catalog/product/view/description.phtml"/>
 <block type="catalog/product_view_type_simple" name="product.info.simple" as="product_type_data" template="catalog/product/view/type/simple.phtml"/>
 <block type="catalog/product_view" name="product.info.addtocart" as="addtocart" template="catalog/product/view/addtocart.phtml"/>

This block defines all the individual blocks inside the main content block. Essentially, we use individual templates for displaying the product's image, overview and description, availability/price and finally the add to cart functionality.

And with this, our base catalog.xml file is complete.


Step 4 - Creating our Main Template

Ok, now that we've specified our layout we can move on to creating the catalog/product/view.phtml file that we specified as the main template for the content section in the XML earlier.

This file is slightly different from the skeleton template we created in the last part because we'll be adding some small things directly instead of going through the template path to avoid bloat along with a smidgen of API calls to help us.

<?php
    $_helper = $this->helper('catalog/output');
    $_product = $this->getProduct();
?>

<form action="<?php echo $this->getAddToCartUrl($_product) ?>" method="post" id="product_addtocart_form"<?php if($_product->getOptions()): ?> enctype="multipart/form-data"<?php endif; ?>>
        <div class="no-display">
            <input type="hidden" name="product" value="<?php echo $_product->getId() ?>" />
            <input type="hidden" name="related_product" id="related-products-field" value="" />
<div id="main-product-image"><?php echo $this->getChildHtml('media') ?></div>

<div id="product-details">
<?php echo $this->getChildHtml('product_type_data') ?>
<?php echo $this->getChildHtml('addtocart') ?>
</div>

<h1><?php echo $_helper->productAttribute($_product, $_product->getName(), 'name') ?></h1>

<?php if ($_product->getShortDescription()):?>
                    <h2><?php echo $this->__('Quick Overview') ?></h2>
                    <p class="quick-overview"><?php echo $_helper->productAttribute($_product, nl2br($_product->getShortDescription()), 'short_description') ?></p>
<?php endif;?>

<?php echo $this->getChildHtml('description') ?>
</form>
</div>

If you take a quick look, you'll notice that we're making a number of getChildHtml calls to acquire that blocks content. These work just like as expected and the contents of these contents will be covered a little later below.

<?php
    $_helper = $this->helper('catalog/output');
    $_product = $this->getProduct();
?>

At the moment, we don't need to know what this means. In layman's terms though, we're essentially asking Magento for this specific product's store information so we can process it and then display it on the page.

<form action="<?php echo $this->getAddToCartUrl($_product) ?>" method="post" id="product_addtocart_form"<?php if($_product->getOptions()): ?> enctype="multipart/form-data"<?php endif; ?>>

Here we use one of Magento's API methods to dynamically generate the URL the form needs to post to.

<h1><?php echo $_helper->productAttribute($_product, $_product->getName(), 'name') ?></h1>

We use another of Magento's methods to acquire the product's title directly.

<?php if ($_product->getShortDescription()):?>
                    <h2><?php echo $this->__('Quick Overview') ?></h2>
                    <p class="quick-overview"><?php echo $_helper->productAttribute($_product, nl2br($_product->getShortDescription()), 'short_description') ?></p>
<?php endif;?>

And again, since the quick overview is going to be just a sentence or two, I'm opting to import it directly instead of using a template. We use the same function we used to acquire the title.


Step 5 - Creating the Templates for our Blocks

With all the main parts done, we can concentrate on the individual blocks now. Now that we've already dealt with this when constructing the main template, this should be a lot easier now.

We'll tackle each in order of appearance in our code:

Product image

getChildHtml('media') maps directly to catalog/product/view/media.phtml. Our file looks like so:

<?php
    $_product = $this->getProduct();
    $_helper = $this->helper('catalog/output');
?>

    <?php
        $_img = '<img id="image" src="'.$this->helper('catalog/image')->init($_product, 'image').'" alt="'.$this->htmlEscape($this->getImageLabel()).'" title="'.$this->htmlEscape($this->getImageLabel()).'" />';
        echo $_helper->productAttribute($_product, $_img, 'image');
?>

Some simple PHP code. We use the helper methods to acquire the product's image and then render it on screen.

Availability/Price

getChildHtml('product_type_data') maps directly to catalog/product/view/type/simple.phtml. Our file looks like so:

<?php $_product = $this->getProduct() ?>
<div id="product-availability">Availability 
<?php if($_product->isSaleable()): ?>
    <span class="available"><?php echo $this->__('In stock') ?></span>
<?php else: ?>
    <span class="unavailable"><?php echo $this->__('Out of stock') ?></span>
<?php endif; ?>
</div>
<div id="product-price">Price <span><?php echo $this->getPriceHtml($_product) ?></span></div>

We first check whether the item is available or not and then output the required HTML. Acquiring the price of the product is a simple method call away!

Add to cart

getChildHtml('addtocart') maps directly to catalog/product/view/addtocart.phtml. Our file looks like so:

<?php $_product = $this->getProduct() ?>

<?php if($_product->isSaleable()): ?>
        <button title="<?php echo $this->__('Add to Cart') ?>" class="button btn-cart"><?php echo $this->__('Add to Cart') ?></button>
<?php endif; ?>

As is logical, we check whether an item is up for sale before we output the required HTML. It's a simple button as is apparent.

Product Description

getChildHtml('description') maps directly to catalog/product/view/description.phtml. Our file looks like so:

<?php $_description = $this->getProduct()->getDescription(); ?>
<?php if ($_description): ?>
        <h2>Product Description</h2>
		<div class="product-description"><?php echo $this->helper('catalog/output')->productAttribute($this->getProduct(), nl2br($_description), 'description') ?></div>
<?php endif; ?>

Similar to how we included the quick overview, we use Magento's inbuilt methods to acquire the necessary information. We do check for the description's existence before we render it.

In case you're curious, I typically tend to include the description separately via templates mostly because in production templates you'll almost always be post procession the description in some way. In those scenarios, it's easier to split this logic off onto its own template. A quick overview, on the other hand, are usually quite small and thus are included directly.


What We'll be Building in the Next Part

.. is completely up to you. There are quite a number of views you could design but it's rather presumptuous of me to pick one. Thus I'm asking you, the reader, to suggest a view to cover in the next part through a comment in the comments section below. Don't forget to tell me which one to cover next!


The Last Word

And we are done! Today, we created the first view of our custom Magento theme, the product view along with looking a how Magento lets us add page specific assets and content. Hopefully this has been useful to you and you found it interesting. Since this is a rather new topic for a lot of readers I'll be closely watching the comments section so chime in there if you're having any doubts.

Questions? Nice things to say? Criticisms? Hit the comments section and leave me a comment. Happy coding!

Advertisement