Copyright © 2008, 2009, 2010 Interchange Development Group
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 |
---|---|
We don't pay any heed to backward compatibility for Wellwell until it is announced as stable and mature enough. |
Table of Contents
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.
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]
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 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>
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.
within pages only appends the list.
So to skip insertion of some of the default components, you can use
attribute PLACEHOLDER
=...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 .
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:
Field | Description | Remarks |
---|---|---|
code | category identifier | numeric, assigned automatically |
name | category name | used as title in category navigation |
type | category type | e.g. genre, star, tag |
parent | identifier of parent category | 0 for top level categories |
priority | category priority for sorting | numeric, highest number comes first |
uri | page to view the category |
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.
Permissions can be checked for a complete page:
[compose acl.check="view_titles" acl.bounce="index" components.body="title_info" ]
![]() | Note |
---|---|
Forms created and submitted via bypass this permission check because they are evaluated earlier, during autoload routine. |
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 |
---|---|
Forms without matching virtual component will be ignored. |
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:
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 | +----------+--------------+--------------+----------+----------+
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.
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.
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.
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
There are two hooks for forms:
form_
(e.g.
form_checkout_load) and
NAME
_loadform_
(e.g.
form_checkout_save)
NAME
_save
The first parameter for both hook subs is the part name.
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
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
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
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 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.
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
that contains basic information about the plugin, such as:
NAME
.info
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
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
1. Autoload Autoload checks for form submissions so we can reroute the request based on certain conditions.
More flexible routes needs modifications to Interchange itself, but here we go: - complementing record files with record macros
Vend::Compose::Address => addresses, [address] Vend::Compose::Taxonomy