Dear Velocity developers:

I've been using Velocity for awhile now, and I think it's awesome.
I'm currently developing an API for creating html pages using
Velocity, but I have two problems.  The first is that I would like to
contribute my code to Velocity, but I don't know how, where, or if my
ideas are up to par with the Velocity code-base.  The second problem
is that to make my code _really_ slick, I need some extra features
from Velocity.

What I need is a new directive (I'll call it #parses), which parses a
string instead of a file.  So for example:

#set( $var = "hello" )
#parses( "$var" )

Would display "hello".  In the next few pages, I'll try to explain why
I think that feature would be helpful.  I apologize in advance for
the length of this email, and I would very much like to get any and
all feedback you might have to offer.

The basic idea behind my project is that the vast majority of Velocity
driven HTML pages have the same basic structure.  If you look at an
example like a person information edit page.  That page might have a
Person.vm template file, and a Person.class that fetches the data from
a data base, and also saves it.  That class' handleRequest method
would probably look something like this:

if( req.getParameter("Submit") != null )
{
  // Save the data from the page
}

String personId = req.getParameter("person_id");
// Select data from db based on personId

// fill the context with data retrieved
ctx.put("user_name", userName);
...

The Person.vm file would then use that data:

<html>
<head>
  <title>Person</title>
<body>
</body>
<input type=text name="user_name" value="$user_name"><br>
...
</html>


This is a very redundant structure. We have a Java class that loads a template, preforms database operations based on an earlier submit or parameters, and then loads new data for the Context object. I thought it would be a good idea to incapsulate this structure into an API.

The basic structure of my API is as follows: It contains a
TemplateServlet, which extends VelocityServlet, and overwrites the
handleRequest method.  Inside, it decides which class needs to be
loaded to handle the request, and asks that class to fill the Context
object.  All the loaded classes are derived from another class called
TemplatePage.

In TemplatePage is where the magic happens.  To illustrate the
structure of TemplatePage better I thought I'd start with an example.

Person.java:

public class Person extends TemplatePage {

  public Person()
  {
     super( "Person.vm" );

personId = new HtmlItem( this, "person_id" );

personData = new PersonDataFetcher( this );

firstName = new HtmlItem( this, "first_name", personData );
lastName = new HtmlItem( this, "last_name", personData );
// ... other data items
new SaveHandler( this );
}


private class PersonDataFetcher extends DataMatrixFetcher {

     public PersonDataFetcher( TemplatePage page )
     {
        super( page, "person_data", "person_id" );
     }

     public DataMatrix fetchData( HtmlEvent event )
        throws SQLException
     {
        String sql = "SELECT\n" +
           "  person_id\n" +
           ", first_name\n" +
           ", last_name\n" +
   // ... other fields needed
           "FROM\n" +
           "    person\n" +
           "WHERE\n" +
           " person_id = '" + personId + "'";
        return DB.executeQuery( sql );
     }
  }


private class SaveHandler extends SubmitHandler {


     public SaveHandler( TemplatePage page )
     {
        super( page, "save", "Save" );
     }

public void handleBeforeDataEvent( HtmlEvent event )
throws AltTemplateException, SQLException
{
StringBuffer message = new StringBuffer( "<center><h3>Saved</h3></center><br><br><br>\n" );
event.getCtx().put( "message", message.toString() );
StringBuffer sqlQuery = new StringBuffer();
sqlQuery.append( "BEGIN;\n" );
sqlQuery.append( "UPDATE person\n" );
sqlQuery.append( "SET\n" );
sqlQuery.append( " first_name = '" + firstName + "'\n" );
sqlQuery.append( ", last_name = '" + lastName + "'\n" );
// ... save the rest of the data
sqlQuery.append( "WHERE person_id = '" + personId + "';\n" );
sqlQuery.append( "END;\n" );
DB.executeUpdate( sqlQuery.toString() );
throw new AltTemplateException( "SimpleMessage.vm" );


}
}



protected HtmlItem personId; protected HtmlItem firstName; protected HtmlItem lastName; // ... other data items protected HtmlDataFetcher personData; }



Person.vm:

<html>
<head>
<title>Person - $person_id</title>
</head>

<body>
<form name="mainForm" action="Person" method="POST">

#htmlHidden( $person_id "" )
<table>
<tr><td><b>First Name:</b></td><td>#htmlText( $first_name "" )</td></tr>
<tr><td><b>Last Name:</b></td><td>#htmlText( $last_name "" )</td></tr>
<!-- ... other fields -->
<tr><td colspan="2" align="center">#htmlSubmit( $save "" )</td></tr>
</table>
</form>
</body>
</html>


As you can see from the example, the page code comes out really clean. I will not go into the internals of the package and the macros (look to the end of the email on how to download the entire code), but the idea is that the HtmlItem objects contain the right value at the right time. In different stages HtmlItem.getValue() will return different values. For example, if the Submit button was just pressed, getValue() will first return the values from the request object. This allows the values to be saved in the database, and new data fetched based in them. In the second stage, the values fetched from getValue() will be those fetched from the database. At any stage the user (programmer), may overwrite these values.

I encounter a problem when I try to generate recurring table values,
for example in a person search page.  Each row of the table will
contains some data about the person, and probably has a link to open
that person's detailed info page.  I'm trying to maintain the elegance
that I managed to create in my API thus far.  Which means that in
order to generate a table of fields I would like to invoke a macro
that would look something like this:


#htmlTable( [ $first_name, $last_name, $account, $gender ], [ "#htmlText", "#htmlText", "#htmlText", "#htmlSelect"], $person_data, "heading-style", "light-style", "darrk-style" )

This macro defines which variable holds the data for each row item,
and which macro is used to generate it.  This macro might look
something like this:

#macro( htmlTable $columns $functions $data $heading $light $dark )
<table class="$table" cellspacing="0">
#* generate table heading *#
<tr>
#foreach( $column in $columns )
<th class="$heading">$column.getHeading()</td>
#end
</tr>
$data.first()
#foreach( $item in $data )
#* determine the row class *#
#if( $velocityCount % 2 == 0 )
#set( $row_class = "$light" )
#else
#set( $row_class = "$dark" )
#end
<tr>
#foreach( $column in $columns )
#foreach( $column in $columns )
#* for each column, use the right function and generate the value *#
<th class="$row_class">#parses( "$functions[$velocityCount]( $column \"\" )" )</td>
#end
#end
</tr>
#end
</table>
#end



The problem with this is that there's no way to pass a reference to a Velocity macro to another Velocity macro. I could also pass the string name of the macro, but there's no way to parse a string as VTL code, is there? A good solution might be to have #parse directive (possibly with a different name like #parses, for parse string) that would parse a string. Another use for the #parses directive, would be to be use as link templates. So you could do something like this:

#set( person_id = "jane" )
#* $someTemplate might be "Person?person_id=$person_id" set in Java *#
<a herf="#parses( $someTemplate )">link</a>

This code obviously needs more time to mature before it's really
useful. The full code is available at <http://www.generationr.net/eyal/example.tar.gz> http://www.generationr.net/eyal/example.tar.gz. I would love to hear your thought about this. Do I have something
interesting here? Are there ten other projects out there already doing
the same thing? Is my idea useless? Any comments would be greatly
appreciated.


Keep up the good work,
Eyal Erez

Reply via email to