You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by Ones Self <on...@nyc.rr.com> on 2004/01/05 00:31:07 UTC

Velocity

  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

Re: Velocity

Posted by Nathan Bubna <na...@esha.com>.
Ones Self said:
...
> 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" )
...

you can use the RenderTool (http://jakarta.apache.org/velocity/tools/generic/)
or ViewRenderTool (http://jakarta.apache.org/velocity/tools/view/)

> The basic idea behind my project is that the vast majority of Velocity
> driven HTML pages have the same basic structure.

this is one reason people often use Velocity (and VelocityTools) with web
frameworks like Maverick, Struts, WebWork, or Turbine.  if you're looking for
a lighterweight solution, the combination of VelocityTools and Velosurf
(http://www.sf.net/projects/velosurf) seems quick and easy for small/simple
webapps.

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

sounds like it would be a new mini-framework of sorts.

> The basic structure of my API is as follows: It contains a
> TemplateServlet, which extends VelocityServlet, and overwrites the
> handleRequest method.

FYI: the VelocityServlet has recently been deprecated in favor of the
VelocityViewServlet in the VelocityTools project.

...
> 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?

the rendering tools mentioned above are the canonical hack nowadays, though i
should warn you that they presently don't work with the setting:
velocimacro.permissions.allow.inline.local.scope=true
(http://issues.apache.org/bugzilla/show_bug.cgi?id=25834)

> A good solution might be to have #parse directive
> (possibly with a different name like #parses, for parse string) that
> would parse a string.

a better name would be #render, and this isn't a bad idea.  i doubt it would
ever be accepted into the core of VTL, but i think it would be nice to have
one of these in the contrib directory or some sort of optional package.

> 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>

i don't think i understand this, but it doesn't look good to me.  for building
URIs, you should look at the LinkTool (in VelocityTools).

> This code obviously needs more time to mature before it's really
> useful.

depends on what you mean by "really useful", it sounds like it's already
fairly useful to you. :)

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

yeah, there are plenty of webapp frameworks out there and several work very
well with Velocity, but this does look like a somewhat unique approach (in my
experience). given that, it piques my curiousity, but i admit i don't have
much time to look into it and probably won't since i'm already invested in
other frameworks.

Nathan Bubna
nathan@esha.com


---------------------------------------------------------------------
To unsubscribe, e-mail: velocity-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: velocity-dev-help@jakarta.apache.org