Interchange Guides: the "Wellwell" Tutorial

Stefan Hornburg

This documentation is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Abstract

Wellwell is a functional frame for building new Interchange catalogs.

Its main difference from previous template catalogs (i.e. Foundation / Standard) is that it doesn't tend to be a demo, but rather an officially-supported frame designed specifically for new catalog deployments.

The purpose of this Guide is to explain the concepts and show basic working examples that should help you understand the WellWell design decisions. You will then be able to read, understand and modify WellWell code as needed.

Some of the Wellwell design goals are:

  • No stone left unturned if it can be improved

  • KISS instead of convoluted ITL code in the Standard demo and its predecessors

  • Replace all of process with ActionMaps

  • Supersede Interchange's routes and profiles with a complete form framework

  • Keep core of WellWell small, use plugins for extended functionality

  • Move out as much as code as possible into generic Perl modules which don't use Interchange variables and functions. This is intended to improve code reusability and code testing.

  • Deprecate bloated code and tags (i.e. button, image)

  • Usefulness and clarity out of the box

  • Modern HTML & CSS

  • Ease of customization

[Caution]Caution
We don't pay any heed to backward compatibility for Wellwell until it is announced as stable and mature enough.

Table of Contents

Pre-requisites
Introduction
Templating system
Components
Default components and attributes
Taxonomy
Users, Roles and Permissions
Per-page permissions in [compose] calls
Menu permissions
Forms
Form parts
Form templates
Form elements
Form attributes
Form hooks
Form theming
Form components
Plugins
Authoring plugins
Product images
Paging
Processing
Routes
Modules for Composition Framework
Features

Pre-requisites

Minimum versions: Interchange 5.7.1, Dispatch.pm 1.103, Order.pm 2.104.

You should have all this if you checkout Git head of the Interchange tree.

Introduction

By convention, WellWell pages use the suffix .itl to indicate they contain ITL code. If a page with suffix .itl is not found, Interchange will look for .html, but you should not rely on this behavior.

The basis of all WellWell-enabled pages is the tag. Basically, it just elegantly loads the specified template and components, and fits components' output into the appropriate slots within the template.

Page body can, but doesn't have to be present. For example, the Login functionality is implemented completely within the Login component (including the supporting text and error any reporting), so an ITL page login.itl doesn't have anything to do but call the component.

Components rely heavily on CSS and try to avoid any hard-coded HTML. This allows you to re-use the same components (i.e. the Login component) in multiple contexts (i.e. small, boxed display and whole-page display). You only need to adjust the CSS, and options exist to auto-wrap the components in appropriate CSS <div>s that you can immediately use them as selectors in your CSS files.

Here's an example of page index.itl that fits two components into container named "RIGHT", and two components into container named "BODY". Also, options are passed to two of the components, product_info and products_list:

[compose
  components.right="login create_account"
  components.body="product_list product_info"
  
  attributes.product_info.sku="1"
  attributes.product_list.category="2"
]

(Body text goes here, but none needed in this context.)

[/compose]

 

Templating system

Templates are complete HTML files with no ITL code.

They cannot contain ITL as they're not parsed for Interchange tags.

The HTML in the templates should be complete, that means including the whole page, from the opening <html> to closing </html>.

The only and intended way to populate the templates with real data is via placeholders. Placeholders are uppercase words in curly braces, such as {LEFT}, {RIGHT} and {BODY}.

A very simple template saved in file templates/simple could look like this:

<html>

  <head>
  </head>
  
  <body>
  {BODY}
  </body>

</html>

As the contained HTML is complete and the {PLACEHOLDER} marks do not interfere with HTML parsing, you can create or edit templates in graphical HTML editors.

Components

Components are combined ITL/HTML/CSS blocks that produce certain output. That output is then fitted into template placeholders. Note that one placeholder can contain output of multiple components (including the same component multiple times), and that this work of loading, evaluating and fitting components into the template is carried out by the tag.

Here's an example of a component hello_world and a page that uses it, based on the above template:

Component saved in file components/hello_world:

Hello, World! The time is [time]

WellWell page saved in pages/test.itl:

[compose
  template=simple
  components.body="hello_world"
  skip_auto_components=1
/]

Final HTML output:

<html>

  <head>
  </head>
  
  <body>
  Hello, World! The time is Wed Oct  7 12:12:25 2009.

  </body>

</html>

 

Default components and attributes

As said, it is possible to specify default components that will be placed in containers, along with their corresponding options. The definitions are passed via MV_COMPONENT_AUTO and MV_ATTRIBUTE_AUTO. Example:

Variable MV_COMPONENT_AUTO <<EOD
  htmlhead:htmlhead
  left:menu,categorynav
  body:infobox
EOD

Variable MV_ATTRIBUTE_AUTO <<EOD
  menu.name=main
EOD

The default components are always inserted, because a manual setting of components.PLACEHOLDER=... within pages only appends the list. So to skip insertion of some of the default components, you can use attribute skip_auto_components=. It can contain names of the components to skip, or "1" to skip all default components.

Many more options are supported — for a full reference, see tag .

Taxonomy

The taxonomy system in WellWell allows products to be categorized in various ways, e.g. by genre, producer and star for videos.

Categories are kept in the categories table:

FieldDescriptionRemarks
codecategory identifiernumeric, assigned automatically
namecategory nameused as title in category navigation
typecategory typee.g. genre, star, tag
parentidentifier of parent category0 for top level categories
prioritycategory priority for sortingnumeric, highest number comes first
uripage to view the category 

Users, Roles and Permissions

Users are stored in the users table. The non-modifiable primary key is uid, quite like an Unix user id, which is used to identify the user through the system. Other settings like username and email can be changed by the user.

Roles allow to group users and grant them permissions. Users are allowed to be in multiple roles. Roles are stored in the roles table. The primary key is rid, quite like an Unix group id, with two default roles (anonymous and authenticated).

The default user roles are:

  • Anonymous user (rid 1): this role is used for users that don't have a user account or that are not authenticated

  • Authenticated user (rid 2): this role is automatically granted to all logged in users

The relationship between users and roles is kept in the user_roles table.

Permissions can be assigned to a role or to an user. The tag to use in checking the appropriate permissions is .

The following example produces a link only if the current user has the "create_content" permission:

  [acl check create_content]
	  <a href="[area new_content]">Create content</a>
  [/acl]

returns its body on success, or the first matching permission if body is empty. Please note that [acl check] without a permission specified is always successful.

Per-page permissions in [compose] calls

Permissions can be checked for a complete page:

[compose
  acl.check="view_titles"
  acl.bounce="index"

  components.body="title_info"
]

 

[Note]Note
Forms created and submitted via bypass this permission check because they are evaluated earlier, during autoload routine.

Menu permissions

It is possible to only show menu entries for which users have corresponding permissions, using the field permission in the menus table.

Forms

Forms are displayed using the tag.

The recommended way to call them is within the tag:

[compose
  components.body="product_info wishlist_add"
  forms.wishlist_add.name="wishlist_add"
/]

In this example product_info refers to an existing component, while wishlist_add can be seen as virtual component which will be replace by the form specified in the second parameter of .

[Note]Note

Forms without matching virtual component will be ignored.

Form parts

Each form consists of one or multiple subforms, called parts. The parts are stored in the table form_series with the following fields:

  • name: Form name

  • part: Part name

  • label: Label displayed on top of the form (optional)

  • template: Template (optional)

  • profile: Profile to check input in this part (optional)

  • position: Position of part (1,2,3,...)

As example we are looking at the checkout form, with the following parts:

  • Shipping
  • Payment
  • Confirmation
  • Receipt

These are the corresponding entries in form_series:

mysql> select name,part,label,profile,position from form_series where name = 'checkout' order by position;
+----------+--------------+--------------+----------+----------+
| name     | part         | label        | profile  | position |
+----------+--------------+--------------+----------+----------+
| checkout | shipping     | Shipping     | shipping |        1 | 
| checkout | payment      | Payment      |          |        2 | 
| checkout | confirmation | Confirmation |          |        3 | 
| checkout | receipt      | Receipt      |          |        4 | 
+----------+--------------+--------------+----------+----------+
		

Form templates

Forms too are built from templates. The default template is located in templates/form. Alternative templates can be specified through form_series field as shown above.

The default template is:

{PREPEND}
{TOP}
<fieldset>
<legend>{TITLE}</legend>
{FIELDS}
{SUBMIT}
</fieldset>
{BOTTOM}

Explanations:

  • {PREPEND}: placeholder for form components

  • {TOP}: starts HTML form

  • {TITLE}: shows the label field from the form_series table

  • {FIELDS}: contains the regular form elements

  • {SUBMIT} contains the button form elements (as specified in form_elements table or default submit button)

  • {BOTTOM} ends the HTML form.

Form elements

The elements of a form (part) are stored in the form_elements table with the following fields:

  • code: Unique serial number

  • name: Name of form element

  • label: Label for form element

  • component: Part name

  • priority: Sort order (descending)

  • widget: Widget to display

The widget is passed by the form_element_field theme function to the display tag. Exceptions to that are explained below.

Special Form Widgets

A form element with the widget named "legend" just displays the label of the form_element. This is useful if you want to separate your form into separate visual parts.

Form elements with the widget named "button" are used to produce submit buttons. This is useful if you want to have multiple buttons in your form without changing your form template, like Preview or Delete.

Form attributes

Every form element can have a set of attributes, stored in the form_attributes table. They are working pretty much the same as the metadata table for the Interchange UI.

Attributes can be applied for every form element with a certain name or only for a certain form:

wellwell=> select * from form_attributes where name = 'country';
 code |  name   | component |  attribute   |                           value                            
------+---------+-----------+--------------+------------------------------------------------------------
   32 | country |           | lookup_query | SELECT code,name FROM country ORDER BY priority DESC, name



wellwell=> select * from form_attributes where component = 'content_edit';
 code | name |  component   | attribute | value 
------+------+--------------+-----------+-------
   30 | uri  | content_edit | width     | 200
   31 | body | content_edit | height    | 400

The form load hook can also be used to provide form atttributes:

Sub form_address_edit_load <<EOS
sub {
	my %attributes;

	# load shipping address
	$Tag->address({function => 'load',
				type => 'shipping'});

	# setup dropdown for country
	$attributes{country}->{lookup_query} = q{SELECT code,name FROM country ORDER BY priority DESC, name};

	return {attributes => \%attributes};
}
EOS

 

Form hooks

There are two hooks for forms: form_NAME_load (e.g. form_checkout_load) and form_NAME_save (e.g. form_checkout_save)

The first parameter for both hook subs is the part name.

Load hook

The load hook is called for the setup of a form part. It is not called if the profile check for the form part has failed. The return value of the load hook is either a false value or a hash reference where the following members are recognized:

  • page — loads the specified page through the $CGI->{mv_nextpage} mechanism

  • attributes — hash reference providing defaults for form attributes

Save hook

The save hook is called if the form part has been successfully submitted (e.g. profile check successful). The return value of the load hook is either a false value or a hash reference where the following members are recognized:

  • page — loads the specified page through the $CGI->{mv_nextpage} mechanism

  • redirect — redirects to the specified page if true

Form theming

Most aspects of a form can be 'themed': the title, elements (complete, label and field) and the submit button.

Currently, you can modify one of the existing theme functions:

theme_form_title
theme_form_element
theme_form_element_label
theme_form_element_field
theme_form_submit

Form components

Regular components can also included in forms. Examples are dynamic form parts and supplementary content.

They are stored in form_components table with the following fields:

  • name: Form name

  • part: Form part (empty if component applies to all parts of the form)

  • component: Component name

  • location: Placeholder used to place the component, e.g prepend for {PREPEND} placeholder

  • priority: Sort order in placeholder (descending)

Plugins

Plugins are living in the plugins/ subdirectory.

They are activated by adding them to variable PLUGINS (comma separated list of plugins).

Please read the README for the plugin first and follow the instructions before activating the plugin.

Authoring plugins

Plugins are basically a small WellWell catalog on its own. The configuration file is called plugin.cfg. The basic plugin.cfg configuration can be as follows:

  Message Loading helloworld plugin.
  
  Variable CURPLUGIN helloworld
  include plugins/default.cfg

Each plugin should have an info file NAME.info that contains basic information about the plugin, such as:

  name = Hello world!
  version = 0.1
  author = Stefan Hornburg (Racke) <racke@linuxia.de>
  
  Directory structure
  
  NAME.info  - info file (see Info file)
  plugin.cfg - configuration file (see Configuration)
  code       - directory for custom code (tags, functions, ...)
  pages/NAME - directory for pages
  components - directory for components

 

Product images

Originals for product images are kept in images table.

Paging

Paging can be controlled by passing parameters to :

paging_startlinks => always show # links at the beginning
paging_endlinks => always show # links at the end
paging_slidelength => length of sliding window

Example:
96 pages, startlinks 3, endlinks 1, slidelength 5

page 1 shows: 1,2,3,4,5,..,96
page 8 shows: 1,2,3,..,6,7,8,9,10,..,96

Processing

1. Autoload Autoload checks for form submissions so we can reroute the request based on certain conditions.

Routes

More flexible routes needs modifications to Interchange itself, but here we go: - complementing record files with record macros

Modules for Composition Framework

Vend::Compose::Address => addresses, [address]
Vend::Compose::Taxonomy

Features

- deal gracefully with discontinued items (through order_missing SpecialSub)

DocBook!Interchange!