blob: decb01e22bd98606858a0671fa6d5f1008014490 [file] [log] [blame]
<?xml version="1.0"?>
<!--
/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<document>
<properties>
<title>Velocity Site Howto</title>
</properties>
<body>
<section name="Velocity Site">
<p>
In Turbine, we have excellent integration with the template tool
<a href="http://jakarta.apache.org/velocity/">Velocity</a>,
we call this VelocitySite building. The reason why we want to wrap Turbine
around Velocity instead of using it on its own is to provide a completely
MVC model for building web applications where the framework has control
over the authentication, security, connection pool, etc and Velocity is
simply used as the View portion of the MVC model. Turbine is responsible
for helping you manage all the different templates as well so that you
can easily construct a site that both designers and engineers can work
together on (<strong>that is our primary goal!</strong>). The reason why this is
good is that it will help you design and build web applications that have
more functionality and less duplication of code since Turbine is fully
re-usable.
</p>
<p>
Knowledge of how Velocity works and what a Velocity Context object is
are required for understanding this documentation. This documentation also
assumes that you are using a Servlet API 2.2 and higher serlvet engine
such as <a href="http://jakarta.apache.org/tomcat/">Tomcat</a> because
we are now targeting towards using WAR archives. Although, this should
work with older servlet engines as well...it just may be slightly harder
for you to setup. You should also be familiar with the rest of the Turbine
documentation that is referenced on the <a href="index.html">index page</a>.
</p>
<p>
Here is a brief description of the way that the system works: all requests
are passed through the Turbine Servlet. The servlet is then responsible
for brokering the request, building up the Context object and then calling
Velocity's template engine to process your template document and return
the results. Pretty simple. Now, lets move on to some more detailed instructions...:-)
</p>
</section>
<section name="Screens">
<p>
Lets start off with a simple Velocity Screen to get things rolling
so you can see how powerful things are. You should compile this class into
your <em>WEB-INF/classes</em> directory.
</p>
<p>
Note: In the examples below, you should replace <code>com.yourcompany.app</code>
with the correct value for your application.
</p>
<source>
package com.yourcompany.app.modules.screens;
// Velocity Stuff
import org.apache.velocity.context.Context;
// Turbine Stuff
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.screens.VelocityScreen;
public class HelloWorld extends VelocityScreen
{
public void doBuildTemplate( RunData data, Context context )
throws Exception
{
// the context object has already been setup for you!
context.put ("hello", "this is a test...");
}
}
</source>
<p>
Ok, as you can see, you do not even need to return a value from your method!
You simply stuff objects into the Velocity Context and that is it! Next,
you will want to create a .vm template of the same name as your class:
HelloWorld.vm...within that template you will put the BODY portion of your
page. In other words, there is no reason to put the header/footer into
this file since that will be built seperately (more documentation on that
futher down...). You should save this document in your <em>templates/screens/</em>
directory.
</p>
<p>
You will also need to modify your TurbineResources.properties file to tell
Turbine where to look for your new screen class.
</p>
<source>
module.packages=org.apache.turbine.modules, com.yourcompany.app.modules
</source>
<source><![CDATA[
<p>
<font color="red">
$hello of the emergency broadcast station.
</font>
</p>
]]></source>
<p>
When you request a URL like this:
</p>
<p>
http://www.server.com/servlet/Turbine/template/HelloWorld.vm
</p>
<p>
What that will do is cause the system to first execute the HelloWold.java
class (if it exists) and then it will call Velocity's template engine directly
and execute the HelloWorld.vm template and then return the results back
to you.
</p>
<p>
<em>NOTE:</em> Turbine capitalizes the first letter in the class file name
before looking for the matching class in the classpath. This allows you to
follow (somewhat) normal class naming guidelines. For example:
</p>
<p>
index.vm and Index.vm both map to Index.class
roleeditor.vm maps to Roleeditor.class
role_editor.vm maps to Role_editor.class
</p>
<p>
The result will be a fully formed HTML document with the <em>$hello</em>
replaced with the value: "this is a test...". If you want to make the URI
above shorter or easier to read, you could simply re-write things using
<a href="http://www.apache.org/docs/mod/mod_rewrite.html">mod_rewrite</a>.
For example, by removing the "<em>/servlet/Turbine/template</em>" portion
of the URI.
</p>
<p>
As you can see, this is much easier than doing servlet after servlet
because it removes a lot of the setup and other things that could potentially
go wrong. It also makes the individual template files easier to manage
and more re-usable because there isn't any surrounding page layout mixed
in with the page.
</p>
<p>
If you want to do something that is more complicated than this that
is reflected across all of your Screens, then you should write a class
that is a subclass of VelocityScreen and then put your specific code
in there. For example, if you wanted a security method to be called automaticially
without having to normally call super() to get it from an overridden method
in the superclass, you could have something like this (semi-pseudo untested
code):
</p>
<source>
package com.yourcompany.app.modules.screens;
// Velocity Stuff
import org.apache.velocity.context.Context;
// Turbine Stuff
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.screens.VelocityScreen;
public abstract class SecureScreen extends VelocityScreen
{
protected boolean doCheckSecurity(RunData data)
throws Exception
{
if (data.isSecure())
return true;
else
return false;
}
/*
* override the doBuild() method of the
* TemplateScreen to always check security first
*/
public void doBuild(RunData data, Context context) throws Exception
{
if ( !this.doCheckSecurity(data))
{
// set the template and screen to be different or do
// some other short circuit code here
}
doBuildTemplate(data, context);
return super.buildTemplate(data, context);
}
}
</source>
<p>
What this would do is override the doBuild() method in TemplateScreen (which
is the base class that VelocityScreen subclasses from) and have it
always do a security check before displaying the content. The benefit of
this is that all you need to do to add security to your screen is to simply
subclass this class instead of VelocityScreen. There is also no need
to remember to call super() since the doBuild() method will be called for
you automaticially by Turbine. You can start to see how Turbine is simply
an extension of the servlet framework itself. For example, the Servlet
Engine will call HttpServlet's service() method for you, just like Turbine
will call your doBuild() method for you. Tight integration with tools like
Velocity just make things even cleaner. :-)
</p>
<p>
One other feature in this system is that if you are not building up
a Context object that is specific for your screen, then you do not need
to create a Java class to match the template file. You simply create the
.vm template and put it in a directory and call it with the same URI described
above. This is really useful when you have a static site of content that
you are converting to be dynamic and you only want to do small portions
of it at any one time. You can also do the opposite of this which is to
only create a Java class file and no template file. You can do this by
simply overriding the doBuild() method in TemplateScreen and returning
your results directly instead of attempting to build a template. Later,
when you want to add a template and remove the HTML code, you can change
the doBuild() into a doBuildTemplate(), build the Context object up and
you are done. Cool!
</p>
<p>
Here is an example of having templates in subdirectories and making
links to each of them.
</p>
<p>
Create a directory structure like this:
</p>
<p>
WEB-INF/templates/screens/admin/
Put <em>index.vm</em> into the <em>screens/</em> directory.
Put <em>UserAdmin.vm</em> into the <em>screens/admin/</em> directory.
</p>
<p>
In index.vm, if you want to link to UserAdmin.vm, you would add something
like this to the HTML:
</p>
<source><![CDATA[
<a href="$link.setPage("admin,UserAdmin.vm")">User Admin Screen</a>
]]></source>
<p>
As you can see above, I used a "," instead of a "/". You can use either
one here. The above will create a fully formed URI with the session information
encoded into the link if the clients brower has cookies turned off.
</p>
</section>
<section name="Layout and Navigations">
<p>
Now that you know how to create simple Screens, you are probably wondering
where the layout and navigation portions of the page come from and how
you control that. If you were not wondering that, then shame on you. :-)
Essentially, it is the same exact procedure as before except you subclass
VelocitySiteLayout and VelocitySiteNavigation instead. Again, it is possible
to not have Java class files always match up with the template and in most
cases, you probably won't need to have a user defined Context in the Layout.
</p>
<p>
Below is an example Layout. It will be searched for in the templates/layouts
directory structure and also takes advantage of the same template path
lookup code as described below:
</p>
<source><![CDATA[
#if ( $data.getMessage() )
{
$data.getMessage()
}
<table width="100%">
<tr>
<td>$navigation.setTemplate("/default_top.vm")</td>
</tr>
<tr>
<td>$screen_placeholder</td>
</tr>
<tr>
<td>$navigation.setTemplate("/default_bottom.vm")</td>
</tr>
</table>
]]></source>
<p>
The variable <em>$screen_placeholder</em> is important here because that
is where the output from your Screen will be placed. VelocitySiteLayout
is responsible for taking care of this. The other variables are for including
your Navigations into the system. The benefit of all of this is that it
enforces the View portion of the MVC model because the "body" and "navigation"
portions of your page simply becomes a variable that you can plug into
any number of Layouts and in any location. If you want to write code so
that the Layout is determined by a variable in the database or in the HTTP
request or whever, all you need to do is write your own VelocitySiteLayout
and override some of the methods in there to determine the layout based
on your own conditions instead of the system default conditions.
</p>
</section>
<section name="How the templates are found">
<p>
Since everything is keyed off the template variable, if
<em>data.getParameters().getString("template")</em>
returns <em>/about_us/directions/driving.vm</em>, the search for the Screen
class is as follows (in order):
</p>
<ol>
<li>about_us.directions.Driving</li>
<li>about_us.directions.Default</li>
<li>about_us.Default</li>
<li>Default</li>
<li>VelocityScreen</li>
</ol>
<p>
If the template variable does not exist, then <em>VelocityScreen</em>
will be executed and <em>templates/screens/index.vm</em> will be executed.
If <em>index.vm</em> is not found or if the template is invalid or Velocity
execution throws an exception of any reason, then <em>templates/screens/error.vm</em>
will be executed.
</p>
<p>
For the Layouts and Navigations, the following paths will be searched
in the layouts and navigations template subdirectories (in order):
</p>
<ol>
<li>/about_us/directions/driving.vm</li>
<li>/about_us/directions/default.vm</li>
<li>/about_us/default.vm</li>
<li>/default.vm</li>
</ol>
</section>
<section name="Actions">
<p>
Actions happen when you have an <em>action</em> parameter defined in the
URI. For example:
</p>
<p>
<ul><u>http://www.server.com/servlet/Turbine/template/HelloWorld.vm/action/UpdateWorld</u></ul>
</p>
<p>In this case, what happens is that the class UpdateWorld class (located in your
<em>WEB-INF/classes/com/yourcompany/app/modules/actions/</em> directory)
is executed first before
anything. Then your HelloWorld class (located in your
WEB-INF/classes/com/yourcompany/app/modules/screens/
directory) is executed. Then your template (for screen/navigation/layout)
HelloWorld.vm is executed. The point of an action is that it should perform
some sort of "action" on your system. Usually, this means storing some
information from a POST request into a database or sending email or something
of that nature. Actions themselves do not return results, but may set a
message with data.setMessage(). They can also short circuit the system
by changing the Template and Screen to be executed. You might want to do
that if there is an error with the form data and you want to re-display
the same page again. Unlike most of the rest of the modules (ie: Screens,
Navigations, Layouts), there is no corresponding Velocity template file
for an Action.
</p>
<source>
package com.yourcompany.app.modules.actions;
// Velocity Stuff
import org.apache.velocity.context.Context;
// Turbine Stuff
import org.apache.turbine.util.RunData;
import org.apache.turbine.modules.actions.VelocityAction;
public class AddUser extends VelocityAction
{
public void doPerform( RunData data, Context context ) throws Exception
{
if ( data.getParameters().getString("username",null) == null)
{
data.setMessage("Username does not exist");
setTemplate ( data, "AddUser.vm" );
return;
}
// store user info into database
data.setMessage("Information stored!");
setTemplate( data, "MainMenu.vm");
// stuff something into the Velocity Context
context.put ("variable", "foo");
}
}
</source>
<p>
In the very basic example Action above, a check is performed to make sure
that
the form data contained a "username" variable. If there is no data, the
template is changed back to the "AddUser" template and the processing is
stopped with an error message that can be easily displayed in the template.
If the processing finishes, then the MainMenu.vm template will be shown
and the message can be displayed.
</p>
</section>
<section name="Action Event">
<p>
There is also a new feature of Turbine that the VelocitySiteAction takes
advantage of, it is called <a href="action-event-howto.html">Action Event</a>.
Please click the link and read more documentation about it. This is an
excellent way to write your actions because now they are entirely event
driven based on which button was clicked in the HTML form.
</p>
</section>
<section name="Velocity Frames">
<p>
Frames are easily achieved with Velocity, the frameset tag being implemented at
the screen level. The default setting for the layout.default directive in the
TurbineResources.properties is the VelocityECSLayout class.
The VelocityECSLayout wraps the Screen in Body tags. For frames this needs to be
removed. The layout.default needs to be changed to;
</p>
<source>
layout.default=VelocityOnlyLayout
</source>
<p>
The layout Velocity template needs to point to the screen_placeholder. As an
example modify the Default.vm in the /WEB-INF/templates/layouts/;
</p>
<source>
$screen_placeholder
</source>
<p>
The screen_placeholder marker will load and process the template marked as the
template.homepage in the TurbineResources.properties. To create frames this is
the template which will need to contain the frameset tag. Edit
/WEB-INF/templates/screens/Index.vm;
</p>
<source><![CDATA[
$page.setTitle("Frames example");
<frameset>
<frame src="$link.getPage("FrameTop.vm")" />
<frame src="http://www.something.com/" />
</frameset>
]]></source>
<p>
Create a FrameTop.vm template in the /WEB-INF/templates/screens directory. The
top frame will load this template. The reference to something.com requires an
internet connection and is shown as an example, anything can be linked to the
lower frame, another Velocity Screen for instance.
</p>
<source><![CDATA[
## Example FrameTop.vm
<p><b>The FrameTop Velocity Template</b>
]]></source>
</section>
<section name="Multiple Velocity Paths">
<p>
The VelocityService can support many paths for the Velocity Templates to
be loaded and read from. This is specified in the
TurbineResources.properties file;
</p>
<source>
services.VelocityService.file.resource.loader.path = /templates,/my-templates,/more-tempates
</source>
<p>
The multiple paths are comma delimited and are specified from the
Servlet Engines context.
</p>
</section>
</body>
</document>