:: Table of Contents ::
 
Subchapter 7.3: Themes
7.3.1 General Concepts

The Harmoni Theme and Layout system as a whole allows the application developer to separate their application code from the presentation code. The separation of Themes and Layouts takes this a step further and separates the placement (Layout) of page components from their "look" (Theme).

As mentioned in the previous subchapter, each Layout can be given a ThemeWidget and ThemeWidget index. ThemeWidgets are similar to the HTML heading tags; <h1>, <h2>, etc; except that they can do even more. Like the heading tags, ThemeWidgets have an index that starts at "1" for the "most main" version of the ThemeWidget and increasing for "less main" versions. Also like heading tags, ThemeWidgets can be given CSS styles, though they are not limited solely to CSS. More on this in the next section.

Themes and ThemeWidgets are simply a collection of classes (and maybe images) that can be instantiated either directly in the application or via the ThemeHandler. Whether or not to use the ThemeHandler, and how to do so if you choose to use it are in "The Theme Handler" section. Creation of your own Themes is described in the "Creating Themes" section.

7.3.2 ThemeWidgets and ThemeSettings

When creating and combining Layouts as mentioned in the previous subchapter, each Layout will take on one of the following ThemeWidget types:

When the Theme and Layout are being output, the ThemeWidget of the appropriate type and index will be requested from the current Theme and used to style each element. It is required that each Theme have at least one (index 1) version of each ThemeWidget type. More ThemeWidgets of higher index can be added, but if a Layout element specifies a higher index than the Theme supports, then the ThemeWidget of the highest index available will be used instead. This allows for the use of the same Themes across applications of varying complexity.

A ThemeWidget has complete control over the look of its contents. ThemeWidgets can, at the discretion of the Theme creator, put <div> tags, tables, or other HTML markup around its contents. The ThemeWidget also can return styles for these elements that will be added to the page's style sheet. An example of a simple TEXT_BLOCK_WIDGET's styles and tags are below:

Example
...

.
textblock3 {
    
background-color: #dddddd;
    
border: 1px solid #000000;
    
padding: 10px;
}

...

<
div class='textblock3'>

...

</
div>

...

The styles and markup for a complicated table/images-based TEXT_BLOCK_WIDGET:

Example
...

.
textblock1 {
    
background-color: #ffffff;
    
padding: 10px;
    
margin: 0px;
    
min-width: 800px;
}

.
imageborder {
    
margin: 0px
    padding
: 0px
}

#topleft {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/topleft.png') no-repeat;
    
width: 25px;
    
height: 25px;
}

#top {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/top.png') repeat-x;
    
height: 25px;
}

#topright {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/topright.png') no-repeat;
    
width: 25px;
    
height: 25px;
}

#left {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/left.png') repeat-y;
    
width: 25px;
}

#right {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/right.png') repeat-y;
    
width: 25px;
}

#bottomleft {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/bottomleft.png') no-repeat;
    
width: 25px;
    
height: 25px;
}

#bottom {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/bottom.png') repeat-x;
    
height: 25px;
}

#bottomright {
    
background: url('/afranco/harmoni/core/themeHandler/themes/ImageBox/images/dropshadow/bottomright.png') no-repeat;
    
width: 25px;
    
height: 25px;
}

...

<
table cellspacing='0px' cellpadding='0px' class='imageborder'>
    <
tr>
        <
td class='imageborder' id='topleft'></td>
        <
td class='imageborder' id='top'></td>
        <
td class='imageborder' id='topright'></td>
    </
tr>
    <
tr>
        <
td class='imageborder' id='left'></td>
        <
td>

            <
div class='textblock1'>
        
...

            </
div>
        </
td>

        <
td class='imageborder' id='right'>  </td>
    </
tr>
    <
tr>
        <
td class='imageborder' id='bottomleft'></td>
        <
td class='imageborder' id='bottom'></td>
        <
td class='imageborder' id='bottomright'></td>
    </
tr>
</
table>

...

If needed, new ThemeWidget types could be created, but the built-in set should be sufficient for most uses. Additionally, the addition of new ThemeWidget types would break the compatibility of an application with 3rd-party Themes.

:: ThemeSettings ::

ThemeWidgets and the Theme itself can have an arbitrary number of ThemeSettings. These settings are objects that extend the ThemeSettingInterface. ThemeSettings are accessed by calling the getSetting($key) or getSettings() methods on either the Theme or a ThemeWidget.

ThemeSettings handle the validation and storage of their values as well as the publishing of their requirements. ThemeSettings can specify a set of valid options via their hasOptions() and getOptions() methods. Whether or not the ThemeSetting has options, it will need to validate the user-supplied string passed via the setValue($value) method. Default values as well as DisplayNames and Descriptions can be set on any setting.

The usage of ThemeSetting objects allows a generic "Theme Modifier" application to show the user all of the settings that are available to them for the current Theme and to allow the user to choose those settings as they see fit. Several ThemeSetting classes are built into Harmoni,

but Themes may have their own as well.

7.3.3 The ThemeHandler

The ThemeHandler is not required for using the Harmoni Theme and Layout system. What the ThemeHandler provides is a system for the registering, storing, and loading of Themes and their settings. WARNING: This may be overkill for your application. Some of the applications that Harmoni is being designed to support need to allow users to choose, modify, and save Themes as well as use different Themes in different parts of the application. The ThemeHandler is designed to allow for this usage.

Below are a set of use-cases for the Harmoni Theme and Layout system and how to set up your application for each one.

:: One Theme and set of ThemeSettings, specified in a config ::

Below is the most basic way to use a Theme. Just instantiate a Theme and pass it to the $harmoni object for later use. In this case the Theme's default settings will be used and no other interaction is necessary.

Example
<?php

...

// Instantiate the Theme object
$theme =& new SimpleLinesTheme();

// Set the Harmoni Theme to the one we just created.
$harmoni->setTheme($theme);

// Continue on with our program execution.
$harmoni->execute();

...

?>

It is also possible to change the settings for the Theme and its widgets as shown in the example below. In order to provide extensability in the number and type of settings that any Theme or Widget can require, there is "standard set" of settings, so you must look at the Theme you wish to use to discover its settings.

Example
<?php

...

// Instantiate the Theme object
$theme =& new SimpleLinesTheme();


// Configure the theme
//
// Note: you need to look at the ThemeSettings published by the Theme to know
// which ThemeSettings reside where, as there are not nessisarily any settings
// that are common accross Themes.

// Body Color is the first setting of the Theme itself.
$bodyColor =& $theme->getSetting(0);
$bodyColor->setValue('ada');

// Get the Heading ThemeWidget
$heading1 =& $theme->getWidget(HEADING_WIDGET, 1);
// Text color is the first Heading setting.
$textColor =& $heading1->getSetting(0);
$textColor->setValue('2a2');
// Font Size is the second Heading setting.
$fontSize =& $heading1->getSetting(1);
$fontSize->setValue('150%');

// All other Widgets and their ThemeSettings will keep the default values
// given to them by the Theme unless otherwise changed.


// Set the Harmoni Theme to the one we just created.
$harmoni->setTheme($theme);

// Continue on with our program execution.
$harmoni->execute();

...

?>

The script below is a simple way to discover the settings available for a Theme without reading through the Theme's code. This script could be extended to write HTML forms and allow for the editing of ThemeSettings.

Example
<?php

require_once('/www/harmoni/harmoni.inc.php');
require_once(
'/www/harmoni/core/themeHandler/themes/SimpleLines.theme.php');

/**
* Prints out a list of all of the widgets in the Theme and their settings and
* values.
*/

// Instantiate the Theme object
$theme =& new SimpleLinesTheme();

// Settings for the Theme itself
print "\n<h1>".$theme->getDisplayName()."</h1>";
print
"\n<p><em>".$theme->getDescription()."</em></p>";
$settings =& $theme->getSettings();
while (
$settings->hasNext()) {
    
$setting =& $settings->next();
    
printSetting($setting);
}

// Settings for each Widget
$widgets =& $theme->getAllWidgets();
while (
$widgets->hasNext()) {
    
$widget =& $widgets->next();
    print
"\n<br /><br />----------------------------------------------------
            <h2>"
.$widget->getDisplayName()."</h2>";
    print
"\n<p><em>".$widget->getDescription()."</em></p>";
    
$settings =& $widget->getSettings();
    while (
$settings->hasNext()) {
        
$setting =& $settings->next();
        
printSetting($setting);
    }
}


/**
* Prints out the info for a setting.
* @param object ThemeSettingInterface The setting to print out.
* @return void
*/
function printSetting( & $setting ) {
    print
"\n<p><strong>".$setting->getDisplayName()."</strong>";
    print
"\n<br /><em>".$setting->getDescription()."</em>";
    print
"\n<br />Has options? ".(($setting->hasOptions())?"Yes":"No");
    if (
$setting->hasOptions()) {
        print
"\n<br />Options:<ul>";
        
$options =& $setting->getOptions();
        while (
$options->hasNext()) {
            
$option =& $options->next();
            print
"\n<li>".$option."</li>";
        }
        print
"\n</ul>";
    }
    print
"\n<br />Default Value: ".$setting->getDefaultValue();
    print
"\n<br />Current Value: ".$setting->getValue();
}

?>
:: One Theme, stored with ThemeHandler ::

Maybe you'd like to only use one Theme for your application, but want to be able to easily tweak the Theme or switch themes without rewriting the config each time. Using the ThemeHandler can allow you store your Theme settings and load them for later use. In the example below, we will assume that your application includes or has access to scripts for selecting and modifying the current Theme's Settings and saving it. Also, we are assuming that the Theme has been created and its id recorded in a configuration option.

The example below shows the code needed to load a Theme and its settings from a database if a Theme has not been stored in the session yet.

Example
<?php

...

// If we have loaded a theme, just use that, otherwise get the Theme settings
// from the database.
if (!$_SESSION['theme']) {

    
// Get the ThemeHandler service
    
$themeHandler =& Services::getService('Themes');
    
    
// If we know the Id of the Theme we want, use that.
    // Otherwise, just use one of the built-in Themes.
    
if ($config['theme_id']) {

        
// get the Id object for the Theme from our config
        
$sharedManager =& Services::getService('Shared');
        
$id =& $sharedManager->getId($config['theme_id']);
        
        
// Get the stored Theme from the ThemeHandler
        
$theme =& $themeHandler->getStoredTheme($id);
    
    } else {
        
        
// use a default built-in Theme
        
$theme =& $themeHandler->getRegisteredThemeByClass('SimpleLinesTheme');
        
    }
    
    
// Put the Theme in the session so we don't need to load it again.
    
$_SESSION['theme'] =& $theme;
    
}

// Set the Harmoni Theme to the one we just created.
$harmoni->setTheme($_SESSION['theme']);

...

?>
:: Many Themes, stored with ThemeHandler ::

Using the ThemeHandler to store and retrieve many Themes/settings is not much different from storing/retrieving one Theme. The main difference is that your application will need to keep track of which Theme it wants to use where; either by Id for stored Themes or by class-name for using non-stored Themes.

:: Registering Themes ::

The ThemeHandler provides for the registering of Theme classes. This function was added to allow an application to publish to its users a list of Themes for them to choose from. Any changes to the settings of registered Themes are not saved by the ThemeHandler outside of an execution cycle. This allows the application developer to modify the default ThemeSettings for users. When a registered Theme (or any other Theme for that matter) is stored, it is given an Id and can be accessed via the ThemeHandler's getStoredTheme($id) method.

Below is an example of registering a Theme class:

Example
<?php

...

// Get the ThemeHandler service
$themeHandler =& Services::getService('Themes');

// make sure we have our Theme classes availible
require_once('../myThemes/Monochrome.theme.php');
require_once(
'../myThemes/Bubbles/Bubbles.theme.php');

// register the Theme classes
$themeHandler->registerThemeClass('MonochromeTheme');
$themeHandler->registerThemeClass('BubblesTheme');

...

// The registered Themes can be accessed via the following procedures.

// Get the ThemeHandler service
$themeHandler =& Services::getService('Themes');

// Get all registered Themes:
$allThemes =& $themeHandler->getRegisteredThemes();

// Or get a specific Theme (whose class-name has been passed in
// a $classname variable, for instance).
$myTheme =& $themeHandler->getRegisteredThemeByClass($classname);

...

?>
:: Storing Themes ::

Themes are stored by calling the storeTheme($theme) method on the ThemeHandler. If the Theme passed to the ThemeHandler already has an Id, then its settings will overwrite any previously stored settings with that Id. If the Theme doesn't have an Id, it will be given one. The Id can be retrieved from the Theme after it has been stored. Below is an example of storing and retrieving a Theme from a session variable.

Example
<?php

...

// Get the ThemeHandler service
$themeHandler =& Services::getService('Themes');

// Store the Theme (ex. one stored in the session)
$themeHandler->storeTheme($_SESSION['theme']);

// Get the Id so we can store it for later Theme retrieval.
$themeId =& $_SESSION['theme']->getId();

...

// The stored Theme can be accessed via the following procedures.

// Get the ThemeHandler service
$themeHandler =& Services::getService('Themes');

// Get our stored Theme
$_SESSION['theme'] =& $themeHandler->getStoredTheme($themeId);

// Or we can get all Stored Themes:
$allThemes =& $themeHandler->getStoredThemes();

...

?>
7.3.4 Creating Themes

A Theme is simply a set of classes, one that extends the ThemeInterface and several that extend the ThemeWidgetInterface, that are linked together. The Theme and its widgets can have and publish ThemeSettings to allow users to change their properties. To ease the development of Themes, an abstract classes, "Theme" and "ThemeWidget", have been added to Harmoni with all of the non-Theme-specific functions already implemented. Themes do not have to use these abstract classes as long as they extend the ThemeInterface.

When creating a Theme that extends the abstract "Theme" and "ThemeWidget" classes, the following methods need to be implemented.

:: Theme Methods ::

An example Theme class is below.

Example
<?php

require_once(HARMONI."/themeHandler/Theme.abstract.php");
require_once(
HARMONI."/themeHandler/ThemeWidget.abstract.php");
require_once(
HARMONI."/themeHandler/BlankThemeWidget.class.php");
require_once(
HARMONI."/themeHandler/common_settings/ColorSetting.class.php");
require_once(
HARMONI."/themeHandler/common_settings/SizeSetting.class.php");
require_once(
HARMONI."/themeHandler/common_settings/BorderSetting.class.php");

/**
* A simple line and color-block based theme.
*
* @package harmoni.themes
* @version $Id: 7.3.html,v 1.7 2005/04/06 22:35:03 adamfranco Exp $
* @copyright 2004
**/

class SimpleLinesTheme
    
extends Theme {
    
    
/**
     * Constructor, throws an error since this is an abstract class.
     * The constructor as well as the print(& $layoutObj) method will need to be
     * implimented for any classes that extend this abstract class.
     */
    
function SimpleLinesTheme () {

        
// Set the Display Name:
        
$this->_displayName = "Simple Lines Theme";
        
        
// Set the Descripiton:
        
$this->_description = "A simple line and color-block based theme.";
    
        
// Set up any Setting objects for this theme and add them.
        
$this->_bodyColorId =& $this->addSetting(new ColorSetting, "Body Color", "The color of the page body.", "aaaaaa");
                
        
// Set up our widgets:
        // In this example there are two types of menus and one type of everything else.
        
$this->addWidget(BLANK_WIDGET, new Blank);
        
$this->addWidget(TEXT_BLOCK_WIDGET, new SimpleLinesTextBlock1);
        
$this->addWidget(TEXT_BLOCK_WIDGET, new SimpleLinesTextBlock2);
        
$this->addWidget(TEXT_BLOCK_WIDGET, new SimpleLinesTextBlock3);
        
$this->addWidget(MENU_WIDGET, new SimpleLinesMenu1);
        
$this->addWidget(MENU_ITEM_WIDGET, new SimpleLinesMenuItem1);
        
$this->addWidget(SELECTED_MENU_ITEM_WIDGET, new SimpleLinesSelectedMenuItem1);
        
$this->addWidget(MENU_HEADING_WIDGET, new SimpleLinesMenuHeading1);
        
$this->addWidget(HEADING_WIDGET, new SimpleLinesHeading1);
        
$this->addWidget(HEADING_WIDGET, new SimpleLinesHeading2);
        
$this->addWidget(FOOTER_WIDGET, new SimpleLinesFooter1);
    }

    
/**
     * Returns a SettingsIterator object with this Theme's ThemeSetting objects.
     * @access public
     * @return string A set of CSS styles corresponding to this theme's settings. These
     *        are to be inserted into the page's <head><style> section.
     *        Note: these styles do not include those of the theme's child widgets.
     *        Those must be accessed otherwise.
     **/
    
function getStyles () {        
        
$styles = "\n\t\t\tbody {";
        
$bodyColor =& $this->getSetting($this->_bodyColorId);
        
$styles .= "\n\t\t\t\tbackground-color: #".$bodyColor->getValue().";";
        
$styles .= "\n\t\t\t}";
        
        return
$styles;
    }
    
    
/**
     * Takes a {@link Layout} object and outputs a full HTML page with the layout's contents in the body section.
     * @param ref object $layoutObj The {@link Layout} object.
     * @access public
     * @return void
     **/
    
function printPage (& $layoutObj) {
        
ArgumentValidator::validate($layoutObj, new ExtendsValidatorRule("LayoutInterface"));
        
        print
"<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>";
        print
"\n<html>";
        print
"\n\t<head>";

        print
"\n\t\t<title>".$this->_pageTitle."</title>";

        print
"\n\t\t<style type='text/css'>";
        print
$this->_getAllStyles();
        print
"\n\t\t</style>";
        
        print
"\n\t\t<script type='text/JavaScript'>";
        print
$this->_headJavascript;
        print
"\n\t\t</script>";
        
        print
"\n\t</head>";
        print
"\n\t<body>";
        
        
$widget =& $this->getWidget($layoutObj->getThemeWidgetType(),
                                    
$layoutObj->getThemeWidgetIndex());
        
$widget->output($layoutObj, $this);
        
        print
"\n\t</body>";
        print
"\n</html>";
    }
}

?>
:: ThemeWidget Methods ::

An example ThemeWidget class is below.

Example
<?php

/**
* The main TextBlock Widget for the SimpleLines theme.
*
* @package harmoni.themes
* @version $Id: 7.3.html,v 1.7 2005/04/06 22:35:03 adamfranco Exp $
* @copyright 2004
**/

class SimpleLinesTextBlock1
    
extends ThemeWidget {
    
    
/**
     * Constructor.
     */
    
function SimpleLinesTextBlock1 () {
        
// Set the Display Name:
        
$this->_displayName = "TextBlock 1";
        
        
// Set the Descripiton:
        
$this->_description = "The main block that most of the page content goes in.";
        
        
// Set up any Setting objects for this theme and add them.
        
$this->_backgroundColorId =& $this->addSetting(new ColorSetting, "Background Color", "The color of the main block background.", "ffffff");
        
$this->_borderColorId =& $this->addSetting(new ColorSetting, "Border Color", "The color of the main block's border.", "000000");
        
$this->_leftTopBorderThicknessId =& $this->addSetting(new SizeSetting, "Top/Left Border Size", "The size of the top and left sides of the main block's border.", "1px");
        
$this->_rightBottomBorderThicknessId =& $this->addSetting(new SizeSetting, "Bottom/Right Border Size", "The size of the top and left sides of the main block's border.", "3px");
        
$this->_borderStyleId =& $this->addSetting(new BorderSetting, "Border Style", "The style of the main block's border.", "solid");
        
$this->_paddingId =& $this->addSetting(new SizeSetting, "Padding", "The size (in px) of padding on the inside of the main block.", "10px");
        
$this->_marginId =& $this->addSetting(new SizeSetting, "Margin", "The size (in px) of margin around the outside of the main block.", "5px");
    }

    
/**
     * Returns a SettingsIterator object with this ThemeWidget's ThemeSetting objects.
     * @access public
     * @return string A set of CSS styles corresponding to this widget's settings. These
     *        are to be inserted into the page's <head><style> section.
     **/
    
function getStyles () {    
        
$styles = "\n\n\t\t\t.textblock1 {";
        
        
$backgroundColor =& $this->getSetting($this->_backgroundColorId);
        
$styles .= "\n\t\t\t\tbackground-color: #".$backgroundColor->getValue().";";
    
        
$borderColor =& $this->getSetting($this->_borderColorId);
        
$leftTopBorderThickness =& $this->getSetting($this->_leftTopBorderThicknessId);
        
$rightBottomBorderThickness =& $this->getSetting($this->_rightBottomBorderThicknessId);
        
$borderStyle =& $this->getSetting($this->_borderStyleId);
        
        
$styles .= "\n\t\t\t\tborder-top: ".$leftTopBorderThickness->getValue()." ".$borderStyle->getValue()." #".$borderColor->getValue().";";
        
$styles .= "\n\t\t\t\tborder-left: ".$leftTopBorderThickness->getValue()." ".$borderStyle->getValue()." #".$borderColor->getValue().";";
        
$styles .= "\n\t\t\t\tborder-right: ".$rightBottomBorderThickness->getValue()." ".$borderStyle->getValue()." #".$borderColor->getValue().";";
        
$styles .= "\n\t\t\t\tborder-bottom: ".$rightBottomBorderThickness->getValue()." ".$borderStyle->getValue()." #".$borderColor->getValue().";";

        
$padding =& $this->getSetting($this->_paddingId);
        
$styles .= "\n\t\t\t\tpadding: ".$padding->getValue().";";

        
$margin =& $this->getSetting($this->_marginId);
        
$styles .= "\n\t\t\t\tmargin: ".$margin->getValue().";";
        
        
$styles .= "\n\t\t\t\tmin-width: 800px;";
//        $styles .= "\n\t\t\t\toverflow: visible;";
        
        
$styles .= "\n\t\t\t}";
        
        return
$styles;
    }

?>

A Theme must have at least one ThemeWidget for each of the following widget types:

If a widget index higher than the number of available widgets of the type is requested, then the widget of the highest index available is returned.