eXtropia: the open web technology company
Technology | Support | Tutorials | Development | About Us | Users | Contact Us
Free support
 ::   Support forums
 ::   Frequently asked questions
 ::   Functional Specifications
 ::   eXtropia Tutorials
 ::   Books by eXtropia
 ::   Other books
 ::   Offsite resources
eXtropia ADT Documentation

Modifying the Look-and-Feel of eXtropia Applications


[ TOC ]
[ TOC ]

Overview

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 ]


How Extropia::View Implements Look-And-Feel

All Views have the same basic structure:

  1. Define their package name.

  2. Import supporting modules and tools.

  3. Declare the inheritance as Extropia::View sub-classes.

  4. 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 ]


Defining the Package Name

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:

 
    package MyNewView;

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 ]


Importing Supporting Modules

 
    use strict;
    use Extropia::Base qw(_rearrange);

All views import a standard set of modules including the following:

  • strict

    Used to enforce good coding of views. It will warn you if you make careless errors.

  • Extropia::Base

    Provides the _rearrange() method that we'll use to parse incoming configuration and send parameters.

[ TOC ]


Declaring View Inheritance

 
    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 ]


Defining the display() Method

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:

  1. Parse incoming global display parameters into local variables using the _rearrange() method from Extropia::Base.

  2. Define the $content variable.

  3. Add HTML code to the $content variable.

  4. 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 ]


Sticky Forms

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 ]


Error Messages

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 ]


Maintaining Application State

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 ]


Views Within Other Views

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 ]


Adding Your own Parameters

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 ]


Walking Through Record Sets

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