Modifying the Look-and-Feel of eXtropia Applications
[ TOC ]
[ TOC ]
We admit it. We at eXtropia are not necessarily the best graphic designers
in the world. We try our best to write clean interfaces with which to demo
our tools. But we don’t write anything too schnazzy.
In fact, it is our intent that you should never use the designs we
distribute as the default application. Instead, we have taken great pains
to isolate the user interface from the programming code. That way, you
don’t need to be an expert programmer to make the application look like
part of your website.
If you know a smidgen of Perl and HTML, you can make any of our
applications look any way you’d like.
All this capability stems from the powerful Extropia::View hierarchy.
Views allow you to create plug and playable user interface components that
you can use in one application or share amongst many. Because they support
filters, they can even integrate into an existing SSI architecture so that
your CGI applications can use the same SSI files as your HTML documents.
As a result, when you change the look-and-feel of your site, you don't need
to hire a programmer to come in and clean up the scripts. Your applications
should transfer relatively easily!
Views are typically stored in the appname/Views/eXtropia
directory.
By default eXtropia applications have a standard look-and-feel that
includes a base frame that is divided into a top, bottom and middle frame.
The middle frame usually includes the active part of the application. The
following figure shows how the views fit together in a typical application.
Although each application has plenty of application-specific views, there
are seven views that are shared by all applications.
An illustrated, concrete example of how all these views fit together is
shown below:
[ TOC ]
All Views have the same basic structure:
- Define their package name.
- Import supporting modules and tools.
- Declare the inheritance as Extropia::View sub-classes.
- Define a display() method that returns the view content.
Consider the following simple view:
|
|
package MyNewView;
use strict;
use Extropia::Base qw(_rearrange);
use Extropia::View;
use vars qw(@ISA);
@ISA = qw(Extropia::View);
sub display {
my $self = shift;
@_ = _rearrange([
-SCRIPT_DISPLAY_NAME,
-SCRIPT_NAME
-CGI_OBJECT
],
[
-SCRIPT_DISPLAY_NAME,
-SCRIPT_NAME
=CGI_OBJECT
],@_);
my $script_display_name = shift;
my $script_name = shift;
my $cgi = shift;
my $content = $cgi->header();
|
|
|
$content .= qq[
<HTML>
<HEAD>
<TITLE>Hello world</TITLE>
</HEAD>
<BODY>
Hello Cyberspace. Welcome to the application $script_display_name!
If you would like to go back to the beginning, click
<A HREF = "$script_name">here</A>
</BODY>
</HTML>
];
return $content;
}
|
[ TOC ]
The first thing any view will do is define its package name. The package
name is the same as the file name minus the .pm. Thus, if you had created a file called MyNewView.pm, you would use the following package definition:
You should note a couple of things about this package name other than the
fact that it must match the filename minus the .pm ending.
First, you can access the view by its name through any application. For
example, to call a view, you could use something to the effect of:
http://www.yourdomain.com/cgi-bin/appname/app_name.cgi?view=MyNewView
You could also reference it from a form using a HIDDEN form tag such as:
|
|
<INPUT TYPE = "HIDDEN" NAME = "view" VALUE = "MyNewView">
|
Either way, you will set the incoming CGI parameter view to
MyNewView and as a result, the _loadViewAndDisplay() method in the
application object (eg. MLM.pm) will call your view and display it (provided that it is included in the
@VALID_VIEWS array defined in the application executable such
as
mailinglistmanager.cgi).
[ TOC ]
|
|
use strict;
use Extropia::Base qw(_rearrange);
|
All views import a standard set of modules including the following:
[ TOC ]
|
|
use Extropia::View;
use vars qw(@ISA);
@ISA = qw(Extropia::View);
|
All views inherit from Extropia::View. Inheritance is achieved using the @ISA array.
[ TOC ]
The real work of a view is done in its display() method. A
sample display() method was shown earlier in this section of
the guide.
Every display() method performs the following functions:
- Parse incoming global display parameters into local variables using the _rearrange() method from Extropia::Base.
- Define the $content variable.
- Add HTML code to the $content variable.
- Return the $content variable to the caller.
Parsing incoming globals
All views can access the display parameters defined in the
@VIEW_DISPLAY_PARAMS in the application executable. As we
mentioned before, the benefit of defining view globals in a single array is
that to make application-wide look-and-feel changes, you may modify the one
array rather than all of the HTML.
The only trick is getting access to the globals.
To do so, you must use the _rearrange() method defined in Extropia::Base. As we have explained in the section on Application Toolkit Architecture
described in the Further Resources appendix, the method takes a list
specifying an order, a list specifying a set of required fields, and a list
of parameters. The _rearrange() method will order the list and
check for the required fields. Once reordered, you can then shift off the
parameters to local variables.
|
|
@_ = _rearrange([
-PARAM_ONE
-PARAM_TWO
],
[
-PARAM_ONE
-PARAM_TWO
],@_);
my $param_one = shift;
my $param_two = shift;
# You can use any of the globals defined in
# @VIEW_DISPLAY_PARAMS and if you
# wish to define other globals, you can just add them
# to that array and grab them here!
|
Using $content
Views do not actually 'display' themselves per se. Actually, they just
create a view (typically using HTML but possibly defining XML or even pipe
delimited streams) and hand it back as a string of content to the caller
application. The caller application can then filter the view or print it
out as it desires.
The important piece to understand is that you should never call
print() from a view. Instead you should use the .= operator to
continually 'append' to a growing view string.
Typically, we store the view in the variable $content.
When we are done creating the view, we then return $content.
For example, you might have
|
|
my $content = qq[
<HTML>
<HEAD>
<TITLE>Hello Cyberspace</TITLE>
</HEAD>
<BODY>
Hello Cyberspace
</BODY>
</HTML>
];
return $content;
|
[ TOC ]
Some views have a little more intelligence than others. For example,
consider the job of a TopFrameView. All it has to do is display a very
simple HTML page. A view that defines an 'Add' form, on the other hand,
must not only display the 'Add' form, but must be able to 'remember' the
value that the user typed in if they cause a data handler error and are
returned to the form to complete it correctly. The concept of memory and
states is a bit un intuitive, so let’s look at an example.
Consider the add form from the mailing list manager application shown in
the next figure. In this case, you can see that there has been a pretty
suspicious email address supplied.
Because we have specified that the email field should be validated, we can
assume that this email address will not pass the data handler stage.
However, we should also give the user the benefit of the doubt and allow
them to finish entering the right data. In this case, we want the form to
be sticky. That is, we want the values originally supplied by the user to
be shown again when the form is returned. The following figure shows the
results of that submission.

As you can see, the 'Add' form is again displayed, but this time, with an
error message, and all the users original information has been inserted
into the textfields.
How can the application remember the values and how do the views get this
information?
Well, if you look closely at @VIEW_DISPLAY_PARAMS in the
application executable, you will notice that -CGI_OBJECT is one of the
parameters that is passed to all views as a global.
As a result, you can easily pull out any value that was sent in from the
form using the CGI object’s easy-to-use param() method.
Consider the following example:
|
|
package MyNewView;
use strict;
use Extropia::Base qw(_rearrange);
use Extropia::View;
use vars qw(@ISA);
@ISA = qw(Extropia::View);
sub display {
my $self = shift;
@_ = _rearrange([
-SCRIPT_DISPLAY_NAME,
-SCRIPT_NAME,
-CGI_OBJECT
],[
-SCRIPT_DISPLAY_NAME,
-SCRIPT_NAME,
-CGI_OBJECT
],@_);
my $script_display_name = shift;
my $script_name = shift;
my $cgi = shift;
my $name = $cgi->param('name') || " ";
my $content = $cgi->header();
|
|
|
$content .= qq[
<HTML>
<HEAD>
<TITLE>Hello $name</TITLE>
</HEAD>
<BODY>
<FORM>
Name: <INPUT TYPE = "TEXT" NAME = "name" VALUYE = "$name">
<INPUT TYPE = "HIDDEN" NAME = "view" VALUE = "MyNewView">
<INPUT TYPE = "SUBMIT">
</FORM>
</BODY>
</HTML>
];
return $content;
|
Notice that you can easily get the value of the last form submission by
using param(). Notice also that we should specify that if
there is no value from the CGI form, that the alternative (||) value should
be an empty string.
If we don't do that we could get uninitialized variable warning messages
such as the one seen in the following figure. This is because, as you can
see, we can use this form multiple times. The first time the form is
displayed, the user would not have entered a name yet and the value would
be null causing the variable to be uninitialized.

[ TOC ]
Another useful global variable that is sent to all views is the -ERROR_MESSAGE parameter that contains an array of error messages that the application
object has built up. To access the values in the array, you simply need to
loop through them and do something with them.
By default, eXtropia applications typically display them using a <BR> break using the routine defined in
Views/StandardTemplates/ErrorDisplayView.pm.
|
|
if ($error_messages) {
my $error_messages = shift;
$content .= qq[
<TR>
<TD BGCOLOR = "000000" COLSPAN = "2">
<FONT FACE = "$page_font_face" COLOR = "WHITE"
SIZE = "$page_font_size">
<B>Error Notice</B>
</FONT>
</TD>
</TR>
<TR>
<TD COLSPAN = "2">
<FONT FACE = "$page_font_face" COLOR = "BLACK"
SIZE = "$page_font_size">
];
my $error_message;
foreach $error_message (@$error_messages) {
$content .= "$error_message<BR>";
}
$content .= qq[
<BR>Please try again!
</FONT>
</TD>
</TR>
];
}
|
[ TOC ]
Actually, when maintaining state, the least of your worries is getting at
the values of the last form submitted. In complex applications you will
likely need to get a hold of data submitted 10 or 12 forms ago!
To do that, views rely on the session object. The session object provides a
key that unlocks the doorway into the session memory and is accessible
through the
-VIEW_DISPLAY_PARAMS and -SESSION_OBJECT variables.
Getting values out of a session is as simple as using the
getAttributes() method as shown below:
|
|
$lname = $session->getAttributes('lname');
|
A side note worth mentioning is that every view that returns the user to
the application, must pass to the application the session id that ties the
application to a given session. Typically this is done with a HIDDEN form
tag in the case of HTML forms such as:
|
|
<INPUT TYPE = "HIDDEN" NAME = "session" VALUE = "$session_id">
|
or with a URL string in the case of a GET request such as in the following
example:
http://www.yourdomain.com/cgi-bin/mlm.cgi?session=DHFKSILK&HJK
But where do you get that strange looking session id from?
Well, you get it from the session object using the getId()
method as in the following example:
|
|
my $session_id = $session->getId();
|
[ TOC ]
Another crucial concept to understand is the ability for views to contain
other views. This makes your views extremely powerful because it allows you
to efficiently break out user interface components that can be reused
across applications.
A good example of how this works can be seen in the BasicDataView views
that we've discussed previously. To emphasize this we provide another
example view's display() method below that includes several
other view components:
|
|
sub display {
[...some variable definition stuff...]
my $content = $cgi->header();
$content .= qq[
<HTML>
<HEAD>
<TITLE>WebDB Result Set</TITLE>
</HEAD>
<BODY TEXT = "$page_font_color"
BGCOLOR = "$page_background_color" MARGINWIDTH = "0"
MARGINHEIGHT = "0" LINK = "BLACK" ALINK = "BLACK"
VLINK = "BLACK">
<CENTER>
<TABLE WIDTH = "90%" BORDER = "0" CELLSPACING = "0"
CELLPADDING = "0">
<TR>
<TD HEIGHT = "5"></TD>
</TR>
</TABLE>
<P>
];
my $error_view = $self->create(’ErrorDisplayView’);
$content .= $error_view->display(@display_params);
my $search_box_view = $self->create(’SearchBoxView’);
$content .= $search_box_view->display(@display_params);
$content .= qq[
<TABLE WIDTH = "90%" BORDER = "0" CELLSPACING = "0"
CELLPADDING = "0">
<TR>
<TD COLSPAN = "$number_of_columns" BGCOLOR = "$color_for_headers">
<FONT COLOR = "WHITE" FACE = "$page_font_face" SIZE = "-1">
<B>Result Set</B>
</FONT>
</TD>
</TR>
<TR>
<TD HEIGHT = "5"></TD>
</TR>
<TR>
];
my $field;
foreach $field (@columns_to_view) {
$content .= qq[
<TD BGCOLOR = "6699CC" VALIGN = "TOP">
<FONT COLOR = "BLACK" FACE = "$page_font_face" SIZE = "-1">
<B>$field_name_mappings{$field}</B>
</FONT>
</TD>
];
}
$content .= qq[
<TD BGCOLOR = "6699CC" ALIGN = "CENTER" VALIGN = "TOP">
<FONT COLOR = "BLACK" FACE = "$page_font_face" SIZE = "-1">
<B>Modify</B>
</FONT>
</TD>
<TD BGCOLOR = "6699CC" ALIGN = "CENTER" VALIGN = "TOP">
<FONT COLOR = "BLACK" FACE = "$page_font_face" SIZE = "-1">
<B>Delete</B>
</FONT>
</TD>
</TR>
];
my $record;
my $counter = 1;
$record_set->moveFirst();
while (!$record_set->endOfRecords()) {
[...Some code to display each record in the datasource...]
}
$content .= qq[
<TR>
<TD HEIGHT = "5"></TD>
</TR>
</TABLE>
];
my $footer_view = $self->create(’RecordSetDetailsFooterView’);
$content .= $footer_view->display(@display_params);
$content .= qq[
</CENTER>
</BODY>
</HTML>
];
return $content;
}
|
[ TOC ]
Finally, it is very possible that you will come up with your own parameters
that you will want to have globally defined for all your views. After all,
the more you define as global parameters, the less you have to change when
you do a look-and-feel revamp.
Adding new parameters is extremely easy. All you need to do is add the
parameters to the @VIEW_DISPLAY_PARAMS array in the
application executable (eg. webguestbook.cgi) and then prepare your views to accept the parameters as discussed
previously in the section on Defining the display() method.
Thus, if you would like to include a global view parameter such as -COPYRIGHT_NOTICE that will appear on the bottom of every one of your views, you should add
such a parameter to this configuration array such as in the following
example
|
|
my @VIEW_DISPLAY_PARAMS = (
-INPUT_WIDGET_DEFINITIONS => \%INPUT_WIDGET_DEFINITIONS,
-INPUT_WIDGET_DISPLAY_ORDER => \@INPUT_WIDGET_DISPLAY_ORDER,
-ROW_COLOR_RULES => \@ROW_COLOR_RULES,
-FIELD_COLOR_RULES => \@FIELD_COLOR_RULES,
-CGI_OBJECT => $CGI,
-DOCUMENT_ROOT_URL => 'http://www.mydomain.com/',
-IMAGE_ROOT_URL => 'http://www.mydomain.com/images/v,
-SCRIPT_DISPLAY_NAME => 'Mailing List Manager',
-SCRIPT_NAME => 'mlm.cgi',
-PAGE_BACKGROUND_COLOR => 'FFFFFF',
-PAGE_BACKGROUND_IMAGE => 'none defined',
-PAGE_LINK_COLOR => 'FFFFFF',
-PAGE_ALINK_COLOR => 'FFFFFF',
-PAGE_VLINK_COLOR => 'FFFFFF',
-PAGE_FONT_COLOR => '000000',
-PAGE_FONT_SIZE => '-1',
-PAGE_FONT_FACE => 'VERDANA, ARIAL, HELVETICA, SANS-SERIF',
-COPYRIGHT_NOTICE => ' - Consider everything I think, say, ' .
'or create to be public domain!'
);
|
Notice that we did not forget to put a comma after
-PAGE_FONT_FACE when we added -COPYRIGHT_NOTICE.
Now you know that your -COPYRIGHT_NOTICE will be passed as a global to all views. What you need to do now is make
sure that your views are prepared to accept the new parameter. To do that,
you'll need to modify the arguments passed to the _rearrange()
method in the view module.
What you will need to do is make sure the view is listening for the new
global. To do so, just make a few simple modifications as shown below:
|
|
package MyNewView;
use strict;
use Extropia::Base qw(_rearrange);
use Extropia::View;
use vars qw(@ISA);
@ISA = qw(Extropia::View);
sub display {
my $self = shift;
@_ = _rearrange([
-COPYRIGHT_NOTICE, # ADD THIS HERE TO PLACE THE VARIABLE
# FIRST ON THE @_ ARRAY
-PAGE_BACKGROUND_COLOR,
-PAGE_FONT_COLOR,
-PAGE_FONT_FACE,
-PAGE_FONT_SIZE],
[
-COPYRIGHT_NOTICE, # ADD THIS TO MAKE THE PARAMETER REQUIRED.
-PAGE_BACKGROUND_COLOR,
-PAGE_FONT_COLOR,
-PAGE_FONT_FACE,
-PAGE_FONT_SIZE],
@_);
my $copyright_notice = shift; # And add this one, but make sure it is
# added first (according to _rearrange())
my $page_background_color = shift;
my $page_font_color = shift;
my $page_font_face = shift;
my $page_font_size = shift;
my $content = qq[
<HTML>
<HEAD>
<TTILE></TITLE>
</HEAD>
<BODY BGCOLOR = "$page_background_color"
TEXT ="$page_font_color">
blah blah blah blah blah
$copyright_notice <!-- use it right here-->
</BODY>
</HTML>
];
return $content;
}
|
With five simple changes, you will now be able to use this variable in any
view! Let’s review the changes.
What is better, if you ever need to change the copyright notice, rather
than going into each view and changing it, you can just edit the view
configuration variable and it will be reflected in every view that uses it.
[ TOC ]
Another operation often performed in views is the walking through of record
sets. Typically, if an application uses a data source to store its data,
all views that display that data will have to walk through the record set
returned from data source search operations.
It actually sounds much worse than it is. Here is some sample view code
that prints the records in a record set.
|
|
sub display {
my $self = shift;
@_ = _rearrange([
-RECORD_SET,
-CGI_OBJECT
],
[
-RECORD_SET,
-CGI_OBJECT
],
@_
);
my $record_set = shift;
my $cgi = shift;
my $content = $cgi->header();
$content .= qq[
<HTML>
<HEAD>
<TITLE>Record Set Test</TITLE>
</HEAD>
<BODY>
<CENTER>
<TABLE>
];
$record_set->moveFirst();
while (!$record_set->endOfRecords()) {
my $field1 = $record_set->getField('field1');
my $field2 = $record_set->getField('field2');
my $field3 = $record_set->getField('field3');
$content .= qq[
<TR>
<TD>$field1</TD>
<TD>$field2</TD>
<TD>$field3</TD>
</TR>
];
$record_set->moveNext();
}
$content .= qq[
</TABLE>
</BODY>
</HTML>
];
return $content;
}
|
The code above represents a view that walks through and prints the contents
of a record set. The following steps summarize what we did to accomplish
this.
1. Listen for the record set in the variable declaration section
2. Shift off the record set so that you can use it locally.
3. Move to the first record in the record set
4. Loop through the record set by moving to the next record in the record
set while there are remaining records.
5. Extract the field values of the record set. Note that these field names
correspond with those you defined in the data source configuration in the
application executable.
6. Use the fields in your HTML display. In the case above, we just generate
a simple table row for each record.
[ TOC ]
[ TOC ]
[ TOC ]
|
Master Copy URL: http://www.extropia.com/support/docs/adt/
Copyright © 2000-2001 Extropia. All rights reserved.
|
[ TOC ]
|
Written by eXtropia. Last
Modified at 10/19/2001 |
|