Tutorial: Creating a Magento Widget - Part 1

You can start with an introductory blog post on Magento Widgets to get understanding of the key concepts and terminology.

Introduction

The Magento Community Edition Version 1.4 facilitates the use of custom frontend extensions by introducing a new concept of customizable widgets, which can provide more control over the frontend behavior and visual block placement to store owners.

Developing a widget doesn’t differ much from developing a regular Magento extension which provides some frontend functionality. Each Magento extension can have any number of widgets. You can also develop extensions which add configuration options and placement control to the functionality which already exists in other extensions.

Widget Basics

We'll start by creating a sample module which provides 3 simple widgets. Every widget outputs a link to a corresponding bookmarking service or a social network on the page.

Create an empty module

To start a new Magento module, you have to create a module code folder under an appropriate code pool (we're going to use local code pool for our samples here). The folder should contain a module configuration file called config.xml and a default module helper. You should also add a small "enabler" file under the app/etc/modules folder, which lets Magento know that the new module is available and resides in the specific code pool.

Directory Structure
Define the "enabler" file

You have to define the code pool and dependencies of the module in this file. Specifying the dependencies guarantees that your module is installed after the modules which your module depends on.

You can also specify here whether the module is enabled or disabled.

app/etc/modules/Sample_WidgetOne.xml:

<?xml version="1.0"?>
<config>
    <
modules>
        <
Sample_WidgetOne>
            <
active>true</active>
            <
codePool>local</codePool>
            <
depends>
                <
Mage_Cms />
            </
depends>
        </
Sample_WidgetOne>
    </
modules>
</
config>
Create the default module helper

The default helper should be defined to make translation subsystem work properly. You shouldn't write any code here, only define a class that extends Mage_Core_Helper_Abstract.

app/code/local/Sample/WidgetOne/Helper/Data.php:

<?php
/**
 * Sample Widget Helper
 */
class Sample_WidgetOne_Helper_Data extends Mage_Core_Helper_Abstract
{
}
Create the configuration file

You should define the module version in the configuration file. Also, as long as we need to create our module in the custom namespace (Sample_), we have to define a helper and blocks base class names here as well.

app/code/local/Sample/WidgetOne/etc/config.xml:

<?xml version="1.0"?>
<config>
    <
modules>
        <
Sample_WidgetOne>
            <
version>0.0.1</version>
        </
Sample_WidgetOne>
    </
modules>
    <global>
        <
helpers>
            <
widgetone>
                <class>
Sample_WidgetOne_Helper</class>
            </
widgetone>
        </
helpers>
        <
blocks>
             <
widgetone>
                <class>
Sample_WidgetOne_Block</class>
             </
widgetone>
        </
blocks>
    </global>
</
config>

Declare widgets

Widgets provided by the module must be declared in the widget.xml file under the module's etc/ folder (the same folder the module configuration file config.xml is located).

A minimal declaration of the widget must contain the following:

  • A node with a name being unique in the system (it's a good idea to include the simplified module label in the node name to make sure that there'll be no nodes with the same name coming from other modules).
  • type="..." attribute, which is typical block reference (constructed in the same way as in layout files).
  • Widget name declaration.
  • Short description of the widget.
<?xml version="1.0"?>
<widgets>
    <
widgetone_digg type="widgetone/digg">
        <
name>BookmarkDigg</name>
        <
description type="desc">Adds a simple "You Digg?" link</description>
    </
widgetone_digg>
</
widgets>

The following is an example with multiple widget definitions.

<?xml version="1.0"?>
<widgets>
    <
widgetone_twitter type="widgetone/twitter">
        <
name>BookmarkTwitter</name>
        <
description type="desc">Adds a simple "Tweet This!" link</description>
    </
widgetone_twitter>
    <
widgetone_digg type="widgetone/digg">
        <
name>BookmarkDigg</name>
        <
description type="desc">Adds a simple "You Digg?" link</description>
    </
widgetone_digg>
    <
widgetone_delicious type="widgetone/delicious">
        <
name>BookmarkDelicious</name>
        <
description type="desc">Adds a simple delicious.com bookmarking link</description>
    </
widgetone_delicious>
</
widgets>

Create frontend blocks for our widgets

Widget block interface

A widget block can be any class which implements the Mage_Widget_Block_Interface. The interface declaration includes 3 methods:

  • toHtml() — to produce the actual output
  • addData(array $arr), setData($key, $value = null) — to allow Magento to assign the widget configration parameters to a widget instance
<?php
interface Mage_Widget_Block_Interface
{
    
/**
     * Produce and return widget's html output
     *
     * @return string
     */
    
public function toHtml();

    
/**
     * Add data to the widget.
     * Retains previous data in the widget.
     *
     * @param array $arr
     * @return Mage_Widget_Block_Interface
     */
    
public function addData(array $arr);

    
/**
     * Overwrite data in the widget.
     *
     * $key can be string or array.
     * If $key is string, the attribute value will be overwritten by $value.
     * If $key is an array, it will overwrite all the data in the widget.
     *
     * @param string|array $key
     * @param mixed $value
     * @return Varien_Object
     */
    
public function setData($key$value null);
}
Frontend widget block

Let's create our first widget frontend block in app/code/local/Sample/WidgetOne/Block/Digg.php

<?php
class Sample_WidgetOne_Block_Digg
    
extends Mage_Core_Block_Abstract
    
implements Mage_Widget_Block_Interface
{

    
/**
     * Produces digg link html
     *
     * @return string
     */
    
protected function _toHtml()
    
{
        $html 
'...';
        return 
$html;
    
}

}

Wait a minute! Why have you just created a class that is supposed to implement an interface, but doesn't have all the methods of that interface implemented? — That's okay, as we have all necessary methods already implemented in the ancestors of the class — method toHtml() is implemented in Mage_Core_Block_Abstract; addData() and setData() are implemented in Varien_Object which is the parent class of Mage_Core_Block_Abstract.

<?php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
    
//...
    
final public function toHtml()
    
{
        
//...
    
}
}

class Varien_Object
{
    
//...
    
public function addData(array $arr)
    
{
        
//...
    
}
    
public function setData($key$value=null)
    
{
        
//...
    
}
}

Therefore, you can see that Mage_Core_Block_Abstract already implements the Mage_Widget_Block_Interface. It's highly recommended to extend all frontend blocks from Mage_Core_Block_Abstract (or from any of its subclasses) as it contains the necessary functions that are usually required to do some frontend work (working with URLs, getting access to other frontend blocks on the page, and many others). In case you are going to use templates in your widget block, extend it from Mage_Core_Block_Template or one of its ancestors instead.

As far as the method toHtml() is declared final in Mage_Core_Block_Abstract, it has been forbidden to override it the descendant widget block classes in order to protect the HTML output generation and extra processing sequence (please see below). But we can override the _toHtml() method which is called internally to produce the initial HTML.

<?php
abstract class Mage_Core_Block_Abstract extends Varien_Object
{
    
...
    
/**
     * Produce and return block's html output
     *
     * It is a final method, but you can override _toHmtl() method in descendants if needed
     *
     * @return string
     */
    
final public function toHtml()
    
{
        
/**
         * The code in this method performs the following:
         *  - dispatches necessary events
         *  - disables module output if needed (System -> Configuration -> Advanced -> Disable Modules Output)
         *  - caches the HTML output
         *  - extra processing if Inline Translation tool is enabled (System -> Configuration -> Developer -> Translate Inline)
         *
         * To see the full code of this method please check Mage_Core_Block_Abstract file in your Magento installation,
         * we left only the lines which show the HTML generation sequence here:
         */
        
if (!($html $this->_loadCache())) {
            $this
->_beforeToHtml();
            
$html $this->_toHtml();
            
$this->_saveCache($html);
        
}
        $html 
$this->_afterToHtml($html);
        return 
$html;
    
}

Let's see the complete code of our three widgets:

app/code/local/Sample/WidgetOne/Block/Digg.php:

<?php
class Sample_WidgetOne_Block_Digg
    
extends Mage_Core_Block_Abstract
    
implements Mage_Widget_Block_Interface
{

    
/**
     * Produces digg link html
     *
     * @return string
     */
    
protected function _toHtml()
    
{
        
return '<a class="digg" href="http://www.digg.com/submit?url='
            
$this->getUrl('*/*/*', array('_current' => true'_use_rewrite' => true))
            . 
'&amp;phase=2" title="You Digg?">You Digg?</a>';
    
}

}

app/code/local/Sample/WidgetOne/Block/Delicious.php:

<?php
class Sample_WidgetOne_Block_Delicious
    
extends Mage_Core_Block_Abstract
    
implements Mage_Widget_Block_Interface
{

    
/**
     * Produces delicious link html
     *
     * @return string
     */
    
protected function _toHtml()
    
{
        $pageTitle 
'';
        
$headBlock $this->getLayout()->getBlock('head');
        if (
$headBlock{
            $pageTitle 
$headBlock->getTitle();
        
}

        $html 
'<a class="delicious" href="'
            
'http://del.icio.us/post?url='
            
$this->getUrl('*/*/*', array('_current' => true'_use_rewrite' => true))
            . 
'" onclick="window.open(\'http://del.icio.us/post?v=4&amp;noui&amp;jump=close&amp;url=\'+encodeURIComponent(\''
            
$this->getUrl('*/*/*', array('_current' => true'_use_rewrite' => true))
            . 
"')+'&amp;title='+encodeURIComponent('"
            
$pageTitle
            
"'),'delicious', 'toolbar=no,width=700,height=400'); return false;"
            
'" title="Add to del.icio.us">Del.icio.us</a>';

        return 
$html;
    
}

}

app/code/local/Sample/WidgetOne/Block/Twitter.php:

<?php
class Sample_WidgetOne_Block_Twitter
    
extends Mage_Core_Block_Abstract
    
implements Mage_Widget_Block_Interface
{

    
/**
     * Produces twitter link html
     *
     * @return string
     */
    
protected function _toHtml()
    
{
        $pageTitle 
'';
        
$headBlock $this->getLayout()->getBlock('head');
        if (
$headBlock{
            $pageTitle 
$headBlock->getTitle();
        
}

        $html 
'<a title="Tweet about this page"'
            
' href="http://twitter.com/home?status=Currently reading '
            
$pageTitle
            
' at '
            
$this->getUrl('*/*/*', array('_current' => true'_use_rewrite' => true))
            . 
'" target="_blank">Tweet This!</a>';

        return 
$html;
    
}

}

Add widget instances in the admin panel using content editor

Now we are done with programming and can go to the admin panel to check if our brand new widgets are available and can be added to either a CMS page or a static block.

Let's go to CMS > Pages in the admin panel and add a few widget instances on the homepage (select homepage from the list of CMS pages, click the "Content" tab on the left).

"Add Widget" button in WYSIWYG mode:

Widget selection and configuration popup:

Widget in the CMS page content in the WYSIWYG and plain text mode:

Check over the frontend

We've just created our first Magento widget.

This module is available on the Magento Connect.

You can also download the full source code archive in tar.gz, tar.bz2 or zip formats.

Explore the Knowledge Base