Chapter 2. Beginning With Spring Surf Developer Tools: The Tutorial

In this chapter we'll build a Spring Surf application step-by-step together. In the first part of this tutorial, we will use Spring Roo shell to create, deploy and test a Surf application. We will then show you how to build the same Surf application using Spring Tool Suite. With Spring Tool Suite, we can either use Surf Spring Template Project approach to quickly setup our Spring Surf application or we can create it manually. Details of both approaches will be covered in this tutorial.

2.1. What You'll Learn

  • How to setup a Surf application from scratch.

  • Structure of a typical Surf application.

  • How to deploy, run and test a Surf application.

  • Surf Roo addon commands for creating new Surf pages, components, templates etc.

  • How to build a Surf application efficiently.

2.2. Tutorial Application Use Cases

Joe Smith is a web developer working for a large manufacture company, Green Energy Corporation. Green Energy currently starts a new initiative to provide local communities with information about green energy related products and technologies. The key requirement of this new project is building an interactive and extensible web application that leverage latest web technologies and can be integrated with existing services that are available within Green Energy Corporation.

The project time line is very tight and Joe is the only resource assigned to this project. After doing some research, Joe decides to use Spring Surf for this project.

2.3. Starting a Surf Project using Spring Roo

Once Joe has Spring Roo, Spring Developer Suite and Surf Developer Tools installed, he first tries to setup a basic Surf project for his future development. His goal is to get a basic Surf site running with a simple page. He also wants to find out how to deploy, run and test his application.

2.3.1. Step 1: Creating a Spring project

The first step for Joe is to create an empty directory and load Spring Roo:

$ mkdir community
$ cd community
$ roo

If Joe followed the installation instructions, he should be greeted with the Roo logo shown below:

    ____  ____  ____
   / __ \/ __ \/ __ \
  / /_/ / / / / / / /
 / _, _/ /_/ / /_/ /
/_/ |_|\____/\____/    ABC [rev XYZ]

Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.

There are quite a few usability features within Roo. If Joe types "help" he will see all of the commands currently available (these change at different phases of your project lifecycle). Plus he can always press TAB in almost all cases for completion services. The first step for Joe is to type the "create project" command into the Roo shell:

roo> project --topLevelPackage com.greenenergy.community
Created /Users/drq/dev/spring/roo/sandbox/community/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Created SRC_MAIN_RESOURCES/META-INF/spring/log4j.properties

The required parameter for the "create project" command is the top level project name which, Joe will learn later, is also going to be used as the default Surf site name. For this case, the default Surf site name will be community. For this tutorial we will use SRC_MAIN_WEBAPP to reference the root directory of your web application.

As shown from the console output, Roo has created a Maven 2 project structure. Even if he quits Roo at this point and never reload it, at this moment Joe has a properly-configured Spring 3 web application, complete with URL rewriting, annotation-based classpath scanning and dependency injection of any class – even those created with the "new" keyword or via an ORM like Hibernate. He can even use "mvn jetty:run" to deploy and test the application on a local Jetty server. In general, Spring Roo helps developers to improve their productivities. However, they can always work on the project files directly using any editor or IDE.

2.3.2. Step 2: Installing Surf artifacts and Surf Quick Start Sample Site

With the basic Spring project ready, the "surf install" command will be available to Joe. This command will install all required Surf artifacts as well as a Quick Start Sample Site which Joe can use as a starting point.

Surf is a scriptable view composition framework for Spring MVC. It enables developers to quickly develop rich web application interfaces using templates and scripting. Surf provides view resolution for Spring MVC applications and integrates nicely with the Spring technology stack including Spring Web Flow, SpringSource Tool Suite and alternative view resolvers technologies.

Surf was initially developed by Alfresco Software Inc. in 2006 as its main web framework for products such as Alfresco Share, Alfresco Web Studio etc. Since November 2009, Surf has been available as a community extension of Spring.

Back to Joe's project, after running the "surf install" command, he gets following messages in Roo shell.

roo> surf install
Created SRC_MAIN_WEBAPP/WEB-INF/config
Created SRC_MAIN_WEBAPP/WEB-INF/config/surf-config.xml
Created SRC_MAIN_WEBAPP/WEB-INF/config/surf-interop-config.xml
Created SRC_MAIN_WEBAPP/WEB-INF/config/web-application-config.xml
Created SRC_MAIN_WEBAPP/WEB-INF/web.xml
Managed ROOT/pom.xml
Created SRC_MAIN_WEBAPP/WEB-INF/urlrewrite.xml
Created SRC_MAIN_WEBAPP/WEB-INF/surf.xml
Managed ROOT/pom.xml
Created SRC_MAIN_WEBAPP/surf-sample-site.zip
Created SRC_MAIN_WEBAPP/css
Created SRC_MAIN_WEBAPP/css/sample.css
Created SRC_MAIN_WEBAPP/images
Created SRC_MAIN_WEBAPP/images/alfresco3d.jpg
Created SRC_MAIN_WEBAPP/images/AlfrescoLogo200.jpg
Created SRC_MAIN_WEBAPP/images/AlfrescoLogo32.jpg
Created SRC_MAIN_WEBAPP/images/background1.gif
Created SRC_MAIN_WEBAPP/images/bg.gif
Created SRC_MAIN_WEBAPP/images/chrome/box
Created SRC_MAIN_WEBAPP/images/chrome/box/box_chrome_header_bg.gif
Created SRC_MAIN_WEBAPP/images/cmis32.jpg
Created SRC_MAIN_WEBAPP/images/cmis_logo_100.jpg
Created SRC_MAIN_WEBAPP/images/gifts-and-gadgets.jpg
Created SRC_MAIN_WEBAPP/images/our-services.jpg
Created SRC_MAIN_WEBAPP/images/powered-by-spring.jpg
Created SRC_MAIN_WEBAPP/images/PoweredBySurf.jpg
Created SRC_MAIN_WEBAPP/images/products-overview.jpg
Created SRC_MAIN_WEBAPP/images/surf32.jpg
Created SRC_MAIN_WEBAPP/images/SurfLogo200.jpg
Created SRC_MAIN_WEBAPP/WEB-INF/chrome/box
Created SRC_MAIN_WEBAPP/WEB-INF/chrome/box/chrome.jsp
Created SRC_MAIN_WEBAPP/WEB-INF/chrome/titled
Created SRC_MAIN_WEBAPP/WEB-INF/chrome/titled/chrome.jsp
Created SRC_MAIN_WEBAPP/WEB-INF/classes/surf/site/chrome
Created SRC_MAIN_WEBAPP/WEB-INF/classes/surf/site/chrome/box.xml
Created SRC_MAIN_WEBAPP/WEB-INF/classes/surf/site/chrome/titled.xml
Managed SRC_MAIN_WEBAPP/WEB-INF/config/web-application-config.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/calendar
Created SRC_MAIN_WEBAPP/WEB-INF/pages/calendar/calendar.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/home.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/main.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/main.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/main.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/side.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/home/side.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/pages/products
Created SRC_MAIN_WEBAPP/WEB-INF/pages/products/main.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/pages/products/main.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/pages/products/products.xml
Managed SRC_MAIN_WEBAPP/WEB-INF/surf.xml
Created SRC_MAIN_WEBAPP/WEB-INF/templates
Created SRC_MAIN_WEBAPP/WEB-INF/templates/home.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/templates/home.xml
Created SRC_MAIN_WEBAPP/WEB-INF/templates/landing.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/templates/landing.xml
Created SRC_MAIN_WEBAPP/WEB-INF/templates/sample
Created SRC_MAIN_WEBAPP/WEB-INF/templates/sample/login.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/templates/sample/logout.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/templates/sample/userinfo.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/calendar
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/calendar/calendar.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/calendar/calendar.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/calendar/calendar.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/footer
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/footer/footer.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/footer/footer.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/footer/footer.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/footer/footer.get.js
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/header
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/header/header.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/header/header.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/header/header.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/header/header.get.js
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/horizontal.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/horizontal.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/horizontal.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/horizontal.get.js
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/vertical.get.desc.xml
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/vertical.get.head.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/vertical.get.html.ftl
Created SRC_MAIN_WEBAPP/WEB-INF/webscripts/navigation/vertical.get.js
Created SRC_MAIN_WEBAPP/WEB-INF/classes/log4j.dtd
Created SRC_MAIN_WEBAPP/WEB-INF/classes/log4j.xml
Deleted SRC_MAIN_WEBAPP/surf-sample-site.zip
Created SRC_MAIN_WEBAPP/WEB-INF/classes/surf/site/configurations
Created SRC_MAIN_WEBAPP/WEB-INF/classes/surf/site/configurations/
default.site.configuration.xml

Joe didn't specify "siteName" parmeter for this "surf install" command so it will assume he wants to use community as the default value for both project name and Surf site name.

One thing that Joe has noticed is that all Surf configurations are placed into XML files. He can use any of his favorite XML editor or IDE to add, modify or delete them.

Before Joe moves on to next step, he spends some time going through all new files and tries to understand what they are for.

  • SRC_MAIN_WEBAPP\WEB-INF\web.xml

    It defines a "UrlRewriteFilter" filter which Enables clean URLs with JSP views e.g. enabling url /welcome instead of /page/welcome.

    Spring MVC Dispatcher Servlet is also defined and pointed to /WEB-INF/config/web-application-config.xml for its context configuration.

  • SRC_MAIN_WEBAPP\WEB-INF\urlrewrite.xml

    This configuration file provides UrlRewriteFilter filter with a set of Surf related inbound and outbound url rewrite rules.

  • SRC_MAIN_WEBAPP\WEB-INF\surf.xml

    It defines Surf specific configurations for runtime and mode. As default, it uses webapp runtime and development mode. (TODO: more details on the options)

  • SRC_MAIN_WEBAPP\WEB-INF\config\web-application-config.xml

    As defined in web.xml, this file provides context configurations for the Spring MVC Dispatcher Servlet. It first imports required infrastructure imports from SRC_MAIN_WEBAPP\WEB-INF\config\surf-config.xml and then defines a list of required interceptors for the default Spring MVC annotation handler. Rest of the configurations are for interoperability with Spring annotated controllers and simple controllers. It also configues the default Spring multipart resolver for file uploading.

  • SRC_MAIN_WEBAPP\WEB-INF\config\surf-config.xml

    This configuration file defines context locations for both Surf Web Scripts Framework and Surf Framework. It also sets up to be auto-resolved to url based views.

  • SRC_MAIN_WEBAPP\WEB-INF\config\surf-interop-config.xml

    TODO: What is this file for?

  • SRC_MAIN_WEBAPP\css\sample.css

    This is the style sheet of the Quick Start Sample Site.

  • SRC_MAIN_WEBAPP\images\*

    Image files for the Quick Start Sample Site.

  • SRC_MAIN_WEBAPP\WEB-INF\chrome\*

    Quick Start sample site provides two sample JSP chromes, box and titled. Chrome describes the border elements around a region or a component. These border elements may include styling elements like shadows, or they may introduce drag and drop capabilities into the page. They may also introduce user-functionality like buttons for popping up component settings (as you frequently see in portals).

  • SRC_MAIN_WEBAPP\WEB-INF\pages\*

    To help users to get started with their project, Surf Quick Start Sample Site provides three sample pages, home, products and calendar. Surf only requires page configuration XMLs to be placed under SRC_MAIN_WEBAPP\WEB-INF. However for best practice it is highly recommended to place them under SRC_MAIN_WEBAPP\WEB-INF\pages and create a separated folder for each page using page ID as folder name, e.g. SRC_MAIN_WEBAPP\WEB-INF\pages\home.

    A page is a navigable page in your web application. It may have associations to other pages and multiple formats keyed from it to templates. A page is a top-level object from which you can traverse either additional navigation or rendering paths.

  • SRC_MAIN_WEBAPP\WEB-INF\templates\*

    Quick Start Sample Site is shipped with two sample Freemarker templates, home.ftl and landing.ftl, and corresponding template instances. A Template Instance is an instance of a template type, for instance, a Freemarker template for the Freemaker Template Type. A Surf page object has a required field for template instance therefore the Surf dispatcher knows which template to use to render view for the page. Other types of templates that Surf supports are JSP and Webscript.

  • SRC_MAIN_WEBAPP\WEB-INF\webscripts\*

    This is the place for storing Websripts that generating components such header, footer,navigation etc. Some of the webscripts for page scoped components can also be placed in page directory. Again it is just for better organizing files.

    A component is an instance of a component type that has been "bounded" into a region or a slot. It represents a binding along with the instance-specific state for that component instance. The Surf framework supports three types of scopes for the region/component binding, global scope, template scope and page scope. The Global Scope is for the component that is same across the site such as header and footer. The Template Scope is for the component that is same across the template such as a Text Block component which shows the same text for any page using this template. The page scope is for any page specific component.

  • SRC_MAIN_WEBAPP\WEB-INF\classes\log4j.*

    These are log4j related configuration files that can be used for controlling logging levels.

  • SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\configurations\default.site.configuration.xml

    This file defines site level configuration such as root page name, site name etc.

  • ROOT\pom.xml

    On top of the pom.xml generated by "create project" command, Surf Roo addon adds two new dependencies to the pom.xml for getting all Surf required library jars.

2.3.3. Step 3: Testing the Surf Quick Start Sample Site

With everything in place, Joe can't wait for giving it a try. Since Spring Surf is maven based, Joe doesn't need to worry about where to get required libraries for his project. Maven also lets Joe use a local tomcat server for building, deploying and testing the site he has so far.

Joe opens another terminal, loads Jetty and deploys the application war

mvn clean package jetty:run

Once it is deployed, Joe is ready to open his browser and visit his new Surf application for the first time. The home page url is http://localhost:8180/.

Joe can also access Surf console to get a report on Surf objects as well as tools for launching Javacript Debugger, refreshing webscripts, templates and object registry etc.

2.3.4. Step 3: Modifying Header and Footer Component

At this point, Joe has a running site with a few pages, templates and components. Joe also gets familar with the process of building, deploying and testing. To start his journey with Surf development, he decides to take a baby step first.

The first thing Joe will try to do is to customize the Sample site header and footer. He will use the existing templates and component webscripts that render the header and footer.

Joe first takes a look at following two websripts.

Websript for Footer:

SRC_MAIN_WEBAPP\WEB-INF\webscripts\footer\footer.get.desc.xml
SRC_MAIN_WEBAPP\WEB-INF\webscripts\footer\footer.get.head.ftl
SRC_MAIN_WEBAPP\WEB-INF\webscripts\footer\footer.get.html.ftl
SRC_MAIN_WEBAPP\WEB-INF\webscripts\footer\footer.get.js

Websript for Header:

SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.desc.xml
SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.head.ftl
SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.html.ftl
SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.js

Before making changes to those two webscripts, Joe spends some time on learning Web Script. Joe learns that Web Scripts allow him to build custom URI-identified and HTTP accessible Web Services. A Web Script implementation consists of the following components: (often summarized as MVC):

  • A description document, which describes the URI that will initiate the script. For example, the service is given a short name and description, along with authentication and transactional needs. URI bindings are described as URI templates.

  • An optional controller script, written in JavaScript, which will do the actual work. For example, the script may query the external data repository to build a set of data items, known as a model to render in the response, or for URIs that intend to modify the repository (hopefully, PUT, POST and DELETE method bindings), the script may update the data repository. The JavaScript has access to all URI arguments, external services and repository data entry points.

  • One or more FreeMarker response templates, known as views (or Representations in REST parlance) that will render output in the correct format for your specific needs. For example, HTML, ATOM, XML, RSS, JSON, CSV, or any combination of these. The URL response is rendered via one of the supplied templates where the chosen template is based on the required response content-type or status outcome. The template has access to all URI arguments, common repository data entry points and any data items built by the optional controller script.

For the footer, Joe only wants to change the copyright message. Since the Javascript template of the footer webscript defines the value of the message as part of the "model" object which is then be passed on to the FreeMarker template for rendering.

After the change, Joe's new footer.get.js looks like

SRC_MAIN_WEBAPP\WEB-INF\webscripts\footer\footer.get.js

model.message = "@ 2009 Green Energy, Co. All Rights Reserved.";

For the header, Joe changes the header title and adds a new tag line.

SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.js

model.headerTitle = "Green Energy";
model.tagLine = "Community Information Hub";
header.get.html.ftl

<div class="left">
	<table width="100%">
	   <tr>
	      <td style="padding-left: 5px;padding-top: 20px;" nowrap>
	      <img src="${url.context}/res/images/agelogo.png" alt="GreenEnergy" /></td>
	      <td nowrap>
	      	<div style="padding-left: 5px;padding-top: 20px; font-weight:bold; 
	      	font-size: 32px; font-family: Helvetica; color:#669900;">${headerTitle}</div>
	      	<div style="padding-left: 5px;padding-top: 2px;  font-weight:bold; 
	      	font-size: 16px; font-family: Helvetica; color:#669900;">${tagLine}</div>
	      </td>
	      <td width="100%"></td>
	   </tr>
	</table>
</div>

Joe also wants to change the styles of the header. For that he needs to make changes to SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.head.ftl which is responsible for modifying or injecting styles, javascript includes etc. into the rendered HTML page head.

SRC_MAIN_WEBAPP\WEB-INF\webscripts\header\header.get.head.ftl

<style>
#header
{ 
    padding: 5px,5px,5px,0px;
	background-color: white;
	border-top: 1px #bbb solid;
	border-left: 1px #bbb solid;
	border-bottom: 1px #bbb solid;
	border-right: 1px #bbb solid;
	background: #47b1ff url(res/images/homepage01.jpg) left top;
	height: 124px;
}

</style>

For the additional images(agelogo.png,homepage01.jpg), Joe puts them under SRC_MAIN_WEBAPP\images folder and they can be referenced in the Freemarker template simply as ${url.context}/res/images/IMAGE_FILE_NAME. In the URL, url.context is a Surf system property which can be used to retrieve the Surf web-server context path while /res is the url prefix defined as part of the URL rewrite rules.

After Joe made the changes and added additonal image files to the SRC_MAIN_WEBAPP\images folder, Joe refreshes and checkes the site home page to verify the changes are there.

New Header:

New Footer:

2.3.5. Step 3: Creating a new template

Joe made his first milestone of getting header and footer customized. During the process, Joe learns some basics about web scripts and understands footer and header are two template scoped components which are bonded to the two regions defined in the home template, SRC_MAIN_WEBAPP\WEB-INF\templates\home.ftl.

SRC_MAIN_WEBAPP\WEB-INF\templates\home.ftl
    	
...
<div id="header" class="clearfix">
	<@region id="header" scope="template" />
</div>
...
<div id="footer" class="clearfix">
	<@region id="footer" scope="template" />
</div>
...

Each Surf template can have one or many template instances. Each Surf page needs to be associated with one and only one template instance while a Surf template instance can be associated with multiple pages. Template instance is always defined as an XML document. A typical template instance XML consists of ID field, name field, decription field and all template scoped component definitions. This gives us great flexibility of reusing template for various scenarios. For example, we can use same two-columun layout for both home page and products page with different component configured for the same regions.

To create a new template, Joe needs to create a new Freemarker template. Joe starts with an HTML UI template which he got from his UI designer and added the region definitions into the places he want to make them rendered by Surf components. Surf Roo Addon provides a command for generating new template.

roo>surf template create --path news

As default, all templates will be placed under SRC_MAIN_WEBAPP\templates.

SRC_MAIN_WEBAPP\templates\news.ftl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>${page.title}</title>
	<link type="text/css" rel="stylesheet" href="${url.context}/res/css/sample.css" />
	${head}
</head>
<body>

	<div id="page">
		<div id="header" class="clearfix">
			<@region id="header" scope="template" />
		</div>	
		<div id="horznav" class="clearfix">
			<@region id="horznav" scope="template" />
		</div>
		<div id="content" class="clearfix">
			<table width="100%">			
				<tr>
					<td valign="top" nowrap width="20%">
						<div id="vertnav">
							<@region id="vertnav" scope="template" />
						</div>
					</td>
					<td valign="top" width="60%">
						<div id="center">
							<@region id="center" scope="page" />
						</div>
					</td>
					<td valign="top" width="20%">
						<div id="right">
							<@region id="right" scope="global" />
						</div>
					</td>
				</tr>
			</table>
		</div>
		<div id="footer" class="clearfix">
			<@region id="footer" scope="template" />
		</div>
	</div>
</body>
</html>

Above is three-column layout template that Joe creates. In addition to the header, footer and horizonal navigation region, it has three middle regions which are defined as template, page and global scope respectively.

With a new template ready, Joe wants to give it a try before creating and adding components to the regions.

2.3.6. Step 3: Creating a new page

Surf Roo addon provides two ways for creating a new page. First approach is to create a new template instance and then uses it to create a new page. The other approach is to create a new page directly from the template. If the new template instance id is not provided, it will assume that the new page will use the template instance with the same id as the template. If the template instance with the given or assumed id doesn't exist, the addon will create a new template instance with that id.

Here are some examples:

// Create a new page by only providing template id.
surf page create --id news --template news

// Create a new template instance and place it under templates\news2 directory.
surf template instance create --id news2 --path templates\news2 --template news
// Create a new page using the template instance we just created and place it 
// under pages\news2.
surf page create --id news2 --path pages\news2 --templateInstance news2

Joe takes the first approach so he runs following command in Roo shell:

surf page create --id news --template news
    	

If everything goes as expected, Joe will get following message in the Roo shell.

roo> surf page create --id news --template news
Created SRC_MAIN_WEBAPP\WEB-INF\news
Created SRC_MAIN_WEBAPP\WEB-INF\news\news.xml
New template instance created.
Created SRC_MAIN_WEBAPP\WEB-INF\pages\news
Created SRC_MAIN_WEBAPP\WEB-INF\pages\news\news.xml

The new page is now ready for viewing with the url http://localhost:8180/news. Surf provides easy URLs for pages. As Joe notices, he can simple append the page name to the home page URL. Joe has not configured anything for the page yet so he will only get a blank page as this point. But if he checks the source of the page, he will noticed the layout has been renderd with messages about missing component bindings.

2.3.7. Step 4: Creating a new component

Before setting up components for the new page, Joe wants to learn how to create his own component. For his requirement, he needs to create a "Green Enery News Feed" component for community users. This component needs to be able to invoke an online news service, retrieve related news feed in XML or JSON format and display the information in a nice HTML format.

Within the Surf framework, Joe will need to create a Webscript and mapped it to a URL which later can be bounded to his page as a component.

Joe starts with creating a new folder, newsfeed, under SRC_MAIN_WEBAPP\WEB-INF\webscripts. Webscript can be placed anywhere under SRC_MAIN_WEBAPP\WEB-INF\. This is just for file orgnization purpose. Once the folder is created, Joe creates all necessary files for this webscript.

  • newsfeed.get.desc.xml (download): This is a required description XML file for the webscript. Joe adds minimal required fields. As we can see from the file, the new webscript will be mapped to the URL /news/feed. According Webscript's naming convention, this webscript will be accessable through HTTP GET.

    <webscript>
      <shortname>News Feed</shortname>
      <description>News Feed</description>
      <url>/news/feed</url>
    </webscript>

  • newsfeed.get.js (download): This will be the core part of the webscript. It will use Surf "remote" object to create HTTP connection with a remote news feed service, retrieve the feed RSS XML document and parse it into a JSON object which will then be passed on to the Freemarker template for the final rendering.

    //Define the remove service url    					
    var newsServiceUrl = "http://www.renewableenergyworld.com/rss/renews.rss";
    //Create an instance of remote connector. We will use http type.
    var connector = remote.connect("http");
    var re = /^http:\/\//;
    if (!re.test(newsServiceUrl))
    {
       newsServiceUrl = "http://" + newsServiceUrl;
    }
    //Make the HTTP connection.
    var result = connector.call(newsServiceUrl);
    
    if (result !== null)
    {
    	//Conver the result into String.
    	var rssXml = new String(result);	
    	var re = /<[r|R][s|S]{2}/; // Is this really an RSS document?
    	//Make sure it is a RSS XML document.
    	if (re.test(rssXml))
    	{
    		// Strip out any preceding xml processing instructions or E4X will choke
    		var idx = rssXml.search(re);
    		rssXml = rssXml.substring(idx);
    		
    		// It looks we need to get rid of the trailing junk as well.
    		if ( rssXml.indexOf('</rss>') != -1 ) {		
    			rssXml = rssXml.substring(0,rssXml.indexOf('</rss>')+6);
    		}
    		 
    		// Parse the xml document using E4X APIs.
    		var rss = new XML(rssXml); 
    		model.title = rss.channel.title.toString();
    		model.items = [];
    	
    		var item, obj;
    		//Loop over all feed items.
    		for each (item in rss.channel..item)
    		{
    		   //Retrieve field valuse and populate the JSON object that later will be
    		   //passed on the the view template.
    		   obj = {
    		      "title": item.title.toString(),
    		      "description": item.description.toString(),
    		      "link": item.link.toString()
    		   };
    		   
    		   model.items.push(obj);
    		}		
    		
    	}	
    }

  • newsfeed.get.html.ftl (download): This is the view part of the webscript. It will take the parsed news feeds from the JavaScript template and render them in to a feed list in HTML format.

    <div class="dashlet">
       <div class="title" > ${title}</div>
       <div class="body scrollableList">
    	<#if items?exists && items?size &gt; 0>
    		<#list items as item>
             	<div style="clear:both;"><a href="item.link">${item.title}</a></div>
             	<div style="clear:both;">${item.description}</div>
    		</#list>
    	</#if>
    	</div><#-- end of body -->
    </div><#-- end of dashlet -->

  • newsfeed.get.head.ftl (download): This file will include all css styles that the view template needs.

    <style>
    .dashlet
    {
       padding: 0px;
       margin:  0px;
       background-color: white;
    }
    
    .dashlet .title
    {
       padding: 5px 9px 5px 9px;
       font-weight:bold;
    }
    
    .dashlet .body
    {
    	overflow-x: hidden;
    }
    
    .dashlet .body .item
    {
    	padding:5px;
    }
    
    .dashlet .body a,
    .dashlet .body a:visited,
    .dashlet .body a:hover
    {
       outline: none;
       text-decoration: none;
    }
    
    .dashlet .body a:hover
    {
       text-decoration: underline;
    }
    
    .dashlet .scrollableList
    {
        height: 400px;
        padding: 8px 0px;
        overflow: auto;
        margin-right: 1px;
    }
    
    </style>

Once Joe finishes the coding for the webscript, he can deploy and test the webscript. Each webscript is mapped to a unique URL. For this News Feed webscript, it will be mapped to http://localhost:8180/news/feed.

2.3.8. Step 5: Adding components to a page

Now Joe has built a nice Green Enery News Feed component. It is time to add it to the page he just created. The component will be placed in the center of the page and will be associated with a page scoped region. This means Joe will need to modify the page XML to add the new component configuration for the Green Enery News Feed component. Since it is page scoped, this configured component will be available only for this particular page.

For template scoped region, the component configuration will be placed in template instance xml which means any page that are based on this template instance will have same component configured.

For global scoped region, the component configuration will be placed in its own XMLs which as default needs to be place under SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\components. The configuration will be available across the application which means it needs to be configured once and only once.

Surf Roo addon provides several component related commands. For Joe's case, he needs to run following command in Roo shell.

roo> surf component create --page news --region center --url /news/feed
Managed SRC_MAIN_WEBAPP\WEB-INF\pages\news\news.xml

As the message suggests, the change is made to template instnace news.xml and now Joe has the center region configued successfully.

2.3.9. Step 6: Generating report on a page

As Joe keeps working on his Surf site, he realizes that a lot of work is about configuring the pages. It would be very helpful to have commands that can show current page configuration as well as other related metadata.

Surf Roo addon provides a few report related commands. To generate and show report on his News page, Joe needs to run following command:

roo> surf report page --id news
-----------------------------------------------------------
Report on Page news

-----------------------------------------------------------
Basic Information

Id: news
Name: news
Path: pages\news\news.xml
Instance: news
Template: news
-----------------------------------------------------------
Page Scoped Components

Region: center Url: /news/feed
-----------------------------------------------------------
Template Scoped Components


-----------------------------------------------------------
Other Components

Region: right Scope: global Configued:false
Region: horznav Scope: template Configued:false
Region: vertnav Scope: template Configued:false
Region: footer Scope: template Configued:false
Region: header Scope: template Configued:false
-----------------------------------------------------------
Page Associations

-----------------------------------------------------------

The report shows Joe has configured one component and still needs to configure five other components for his News page. Before Joe configures header, foot and two navigation regions. Joe wants to explore a little more on global scoped region and component properties.

Joe first creates a new component which displays a HTML message which will be loaded from a component property. The new webscript will be mapped to /blocks/html and placed under SRC_MAIN_WEBAPP\WEB-INF\webscripts\html.

SRC_MAIN_WEBAPP\WEB-INF\webscripts\html\html.get.desc.xml (download)
 
 <webscript>
  <shortname>Html - Web Component</shortname>
  <description>Html - Web Component</description>
  <url>/blocks/html</url>
</webscript>
SRC_MAIN_WEBAPP\WEB-INF\webscripts\html\html.get.js (download)
 
 var html = instance.properties["html"];
if(html != null)
{
	html = html.replace('${url.context}', url.context);
	model.html = html;
}
SRC_MAIN_WEBAPP\WEB-INF\webscripts\html\html.get.html.ftl (download)

<div class="motd">
	<div class="title">Message Of The Day</div>
	<div class="body">
		<#if html?exists>
			${html}
		<#else>
			Unable to find contents
		</#if>
	</div>	
</div>
SRC_MAIN_WEBAPP\WEB-INF\webscripts\html\html.get.head.ftl (download)

<style>
.motd
{
   padding: 0px;
   margin:  0px;
   background-color: white;
}

.motd .title
{
   padding: 5px 9px 5px 9px;
   font-weight:bold;
}

.motd .body
{
	overflow-x: hidden;
}
</style>

Now Joe can associate it with the "right" region. Since it is global scoped, Surf will create a seperated XML file and as default the file will be placed under SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\components

roo> surf component create --page news --region right --url /blocks/html
Created SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\components
Created SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\components\global.right.xml

The component will load the display message from its "html" property. So Joe will have to edit the global.right.xml to add the property.

SRC_MAIN_WEBAPP\WEB-INF\classes\surf\site\components\global.right.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component> 
  <guid>global.right</guid>  
  <component-type-id>webscript</component-type-id>  
  <title>global.right</title>  
  <description>global.right</description>  
  <url>/blocks/html</url>  
  <resources/>  
  <scope>global</scope>  
  <source-id>global</source-id>  
  <region-id>right</region-id>  
  <properties>
  	<html>Welcome to Green Energy!</html>
    </properties>
</component>

Joe also configures rest of regions for his News page.

roo> surf component create --page news --region header --url /company/header
Managed SRC_MAIN_WEBAPP\WEB-INF\news\news.xml

roo> surf component create --page news --region footer --url /company/footer
Managed SRC_MAIN_WEBAPP\WEB-INF\news\news.xml

roo> surf component create --page news --region horznav --url /navigation/horizontal
Managed SRC_MAIN_WEBAPP\WEB-INF\news\news.xml

roo> surf component create --page news --region vertnav --url /navigation/vertical
Managed SRC_MAIN_WEBAPP\WEB-INF\news\news.xml

Now if Joe runs report on the News page again, it will show all regions have been configured.

-----------------------------------------------------------
Report on Page news

-----------------------------------------------------------
Basic Information

Id: news
Name: news
Path: pages\news\news.xml
Instance: news
Template: news
-----------------------------------------------------------
Page Scoped Components

Region: center Url: /news/feed
-----------------------------------------------------------
Template Scoped Components

Region: header Url: /company/header
Region: footer Url: /company/footer
Region: horznav Url: /navigation/horizontal
Region: vertnav Url: /navigation/vertical
-----------------------------------------------------------
Global Scoped Components

Region: right Scope: global Component:global.right Url:/blocks/html
-----------------------------------------------------------
Page Associations

-----------------------------------------------------------

After deploying and testing, Joe finally gets his page ready. Now he wants to explore Surf page association which is key for building site navigation. As we can see from the screen shot, Joe's news page is not on the horizonal navigation bar and left side vertical navigation menu is empty.

2.3.10. Step 7: Associating pages

Surf supports parent-child type of page associations. This allows Joe to designate a page as parent and have another page associated with it as child. For Surf site, the association will be available through Java and JavaScript APIS and thus can be used to build site page hierachy and site navigation.

As shown in the following code snippet, Surf allows navigation component webscript to get a list of child pages for current page and it can then be used for rendering site navigation.

model.pages = sitedata.findChildPages(context.pageId);
if (model.pages.length == 0)
{
	// find the parent page
	var parentPages = sitedata.findParentPages(context.pageId);
	if (parentPages.length > 0)
	{
		model.pages = sitedata.findChildPages(parentPages[0].id);
	}
}    	
    	

To get the news page on the horizontal navigation bar, Joe will need to associate the page as child of the site root page. For this site, it will be the home page.

roo> surf page association create --sourceId home --destId news
Managed SRC_MAIN_WEBAPP\WEB-INF\pages\home\home.xml    	
		

Now if Joe redeploys the site, he should see the News page show up on the horizontal navigation bar. For the vertical navigation menu, it shows a list of child pages of parent page of the news page since news page doesn't have any child pages.

Now if Joe creates another page and makes it as a child of the news page, he will see the list of child pages on the left region.

roo> surf template instance create --id news2 --path templates\news2 --template news
Created SRC_MAIN_WEBAPP\WEB-INF\templates\news2
Created SRC_MAIN_WEBAPP\WEB-INF\templates\news2\news2.xml

roo> surf page create --id news2 --path pages\news2 --templateInstance news2
Created SRC_MAIN_WEBAPP\WEB-INF\pages\news2
Created SRC_MAIN_WEBAPP\WEB-INF\pages\news2\news2.xml

roo> surf page association create --sourceId news --destId news2
Managed SRC_MAIN_WEBAPP\WEB-INF\pages\news\news.xml

As a side note, Joe can also add chorom and title configuration to the vertical navigation component so that it can render nicer view.

SRC_MAIN_WEBAPP\WEB-INF\news\news.xml
...
    <component>
            <region-id>vertnav</region-id>
            <url>/navigation/vertical</url>
         	<chrome>box</chrome>
         	<title>Child Pages</title>          
    </component>
...

2.3.11. Step 8: Packaging the site

With a new page and several components built, Joe wants to package them and deploy it to his QA server. For Surf, packaging site is relatively easy task. All Joe needs to run is following maven command.

mvn clean package

Once it finishes, it will generate a WAR file which includes all needed library jars for running the site in any application server.

Joe is very happy with his experience with Surf so far. Now he plans to check out the IDE part of Surf Developer tools.

2.3.12. Step 8: Installing additional Surf addons

Spring Surf roo addon also provides addtional addons that Joe can install. To get a list of available addons, Joe would need to run following roo command in roo shell.

surf addon list

This command will display a list of addon ids with descriptions.

roo> surf addon list
ID:   php
DESC: Adds support for scripting in Php.

ID:   groovy
DESC: Adds support for scripting in Groovy.

ID:   studio
DESC: Adds Spring Surf Web Studio.

ID:   documentation-core
DESC: Adds Spring Surf documentation plugin.

ID:   documentation
DESC: Adds Spring Surf documentation plugin and basic site templates.

If Joe wants to install any of above addons, he would need to run "surf addon install" command with id parameter.

For example, Joe wants to install the latest Spring Surf documentation plugin to his Surf project.

roo> surf addon install --id documentation
Managed ROOT/pom.xml
Managed ROOT/pom.xml
Created ROOT/document-addon.zip
Managed ROOT/src/docbkx/resources/css/html.css
Managed ROOT/src/docbkx/resources/images/banner.png
Managed ROOT/src/docbkx/resources/images/important.png
Managed ROOT/src/docbkx/resources/images/note.png
Managed ROOT/src/docbkx/resources/images/Thumbs.db
Managed ROOT/src/docbkx/resources/images/tip.png
Managed ROOT/src/docbkx/resources/images/xdev-spring_logo.jpg
Managed ROOT/src/docbkx/resources/xsl/fopdf.xsl
Managed ROOT/src/docbkx/resources/xsl/html.xsl
Managed ROOT/src/docbkx/resources/xsl/html_chunk.xsl
Managed ROOT/src/site/apt/index.apt
Managed ROOT/src/site/apt/reference/index.apt
Managed ROOT/src/site/docbook/reference/index.xml
Managed ROOT/src/site/site.xml
Deleted ROOT/document-addon.zip

Please note that after this addon installation, roo will need to be restarted. Once roo is restarted, Joe is ready to build site documentation using following maven command

mvn site

Once it finishs, Joe will get nice site reference documentation in mulitple formats such as HTML, singl-page HTML and PDF. All documents will include auto-generated Webscripts API docs, JavaScript API docs, Freemarker API docs etc.

2.4. Building a Surf Project using Spring Tool Suite

As Joe has learned from the first part of the tutorial, Surf provides an addon for Spring Roo which allows him to quickly scaffold Surf pages and views on top of Spring MVC application. Since Spring Roo is bundled with Spring Tool Suite, he can use the same Surf roo commands as long as he installs the Surf Roo addon jar in STS.

Spring Tool Suite, as its name indicates, is a developer toolset building upon Eclipse. It provides Spring developers with a set of wizards, views, embedded servers etc. for higher productivity.

As part of Spring Tool Suite, Roo shell can be accssed through an Eclipse view. From STS menu, Joe can select Window -> Show View -> Other... -> SpringSource Tool Suite -> Roo Shell. Once that view is opened, it will show a list of Roo projects that are under his current workspace. He can then select the project and the Roo shell interface will be available to him.

There are multiple ways of setting up a Surf project within STS. Joe first tries to set it up manually.

2.4.1. Step 1: Setting up a basic Dynamic Web Project.

  • Start STS and from the STS menu, select File -> New -> Other...
  • Create a Web -> Dynamic Web Project and give it a name, community. Leave all of the default settings. Click through to the final screen and uncheck the option to automatically generate the web.xml file if it appears on the last page of the creation wizard.

2.4.2. Step 2: Enabling the Dynamic Web Project with Spring Roo Nature.

Right click on the project and select Spring Tools -> Add Roo Project Nature The Dynamic Web Project is now set up to use Spring Roo.

2.4.3. Step 3: Creating a basic Spring project using Spring Roo

  • Start the Spring Roo console. Joe can do this by right clicking on the project and selecting Spring Tools -> Roo Shell. The Roo Shell will start up and he'll see it appear as shown here:
  • Type in project --topLevelPackage com.greenenergy.community and press enter. This will generate a basic Spring project.

With that, a very simple Spring project has been generated for Joe with a few important files and structures. These include Spring MVC application context settings, Maven build file (pom.xml)and Maven project structure.

2.4.4. Step 4: Installing Surf artifacts.

Joe can now add Surf into his Spring MVC projects by simply using the Surf addon for Spring Roo. Just like he did within Roo shell, Joe installs Surf artifacts by running "surf install" command. This command will then update Maven build (pom.xml) to include Surf as a dependency, provide you with a /WEB-INF/surf.xml configuration file with Surf automatically configured for development mode, update /WEB-INF/urlrewrite.xml file to include necessary redirects for Surf, unpack a sample site, pages, templates and a few components to help Joe get started.

2.4.5. Step 5: Enabling Maven Dependency Management

The final step for Joe to do is to enable Maven dependency management for this project. By doing this, he allows STS to automatically download all of the necessary dependencies from pre-defined Maven repositories for his project.

  • Right click on the project and select Maven -> Enable Dependency Management.

Joe needs to wait till Maven gets all required dependencies. Once it finishes, Joe is ready to deploy and test his Surf site.

2.4.6. Step 6: Deploying and testing Surf project.

To deploy and test Surf project within STS, Joe can keep using the pre-configued Jetty server by running jetty:run command. He will need to right click on the project and select Run As -> Run Configurations.

Joe will then need to setup a new "Maven Build" launch configuration as shown in the following screen shot.

Joe can also use SpringSource tc Server which comes with STS for his testing. He can either run the deploy Roo command within Roo shell

  • Run the command deploy --server "SpringSource tc Server v6.0"

or

  • Right click on the project and select Run As -> Run on Server.
  • Select either SpringSource dm server or tc server and then click Next.
  • In the next window, make sure the current is selected in the right-hand site list and then click Finish button.

The project will be built and deployed to the embedded Tomcat instance. When it starts up, Joe can point a browser to the following path: http://localhost:8080/community He will then see the home page of sample site.

Surf also provides an additonal template for SpringSource Tool Suite Template Project Wizard. The STS template wizard lets Joe quickly create new Spring Surf projects through unpacking a prepacked STS project. It will save Joe all the manual steps.

2.4.7. Step 1: Verifying Surf Project Template has been installed

The additonal Surf template is packaged as a custom STS/Eclipse plugin.The custom plugin extends extension point com.springsource.sts.content.core.descriptors. This will make Spring to include additional templates that are provided by Surf.

Just like rest of Spring template projects, the description XML file and acutal project zip files are hosted remotely. When users tried to install the template project for the first time, it will download the remote copy and save a local copy under .metadata/.sts/content directory of the current workspace. For each template project, it will create a sub directory using the template id as the directory name.

After the first time downloading, STS will use the local copy unless a newer version is available on the remote server. In that case, the newer version will be downloaded and the local copy will be updated.

To verify the Surf project template has been installed through the custom plugin, Joe can

  • With STS menu, select File -> New -> Spring Template Project.
  • Within the project selection wizard, click on the Refresh button. If the Spring Surf project item shows up, Joe can move on to next step. Otherwise, he will need to go back to installation guide to make sure the custom Eclipse plugin has been installed correctly.

2.4.8. Step 2: Creating a new Surf Project

Once Joe selects the Surf project template and click Next button. If he is using it for the first time, it will popup a message box to confirm the download. Otherwise, it will show a dialog asking for Eclipse project name, Spring Surf site name and top level package. Joe enters valid inputs for all required fields and click Finish.

STS will then unpack the project zip file and create a new project for Joe. This project will have Spring Roo shell nature enabled and a Surf project created with all samples from the Quick Start sample Site.

2.4.9. Step 5: Enabling Maven Dependency Management

Just as the manual approach, the final thing for Joe to do is to enable Maven dependency management for this project.

  • Right click on the project and select Maven -> Enable Dependency Management.

Joe needs to wait till Maven does its job getting all required dependencies. Once it finises, Joe is ready to deploy and test his Surf site.

2.4.10. Step 6: Deploying and testing Surf project.

Same as the manual apparach, Joe can either run Roo shell deploy command or use the STS menu link to deploy and test the Surf project.

2.5. Summary

For this tutorial, Joe has learned how to use Surf Developer Tools to build, run and test a Spring Surf site. During the process, he gains experience of Surf framework, Webscript and Surf Roo addon and feels more comfortable with his project.