Modified: 06/13/2008, Created: 03/06/2008
A guide to get up and running an intermediate? Grails web app. The following concepts will be touched upon:
Also see the official Grails Quick Start .
The spirit of Grails is to do convention over configuration . Behind the Covers, Grails is using Java, Groovy, Spring, Hibernate, JUnit, Ant, Log4J and Sitemesh. The term CRUD will mean create, read, update, delete. Model View Controller is an accepted web framework pattern. This presentation will show writing Models, Controllers and Views. Closures are like methods that are objects. Closures are provided via the Groovy language. Controllers have actions that are implemented as closures. This presentation will not focus on GORM (Grails Object Relational Mapping) or creating Domain relationships.
Here is the presentation source code in zip format projecttracker-20080617.zip .
This section will take on a Windows OS slant. I recommend installing the Grails binary vs. using the Grails Windows Installer. I don't recommend using the Grails Windows Installer because of the issues raised in this blog article, Some notes about the Windows installer for Grails
REM// set up Grails set DEV_TOOLS_DIR=C:\dev\tools set GRAILS_HOME=%DEV_TOOLS_DIR%\grails-1.0.2 set PATH=%GRAILS_HOME%\bin;%PATH%
grails help grails create-app projecttracker cd projecttracker
This create-app command created the web application's directory layout and many files.
Note that the create-app command set the application version to "0.1" in the file application.properties. The application name is also stored in the same application.properties file.
#Do not edit app.grails.* properties, they may change automatically. DO NOT put application configuration in here, it is not the right place! #Thu Apr 17 17:56:36 CDT 2008 app.version=0.1 app.servlet.version=2.4 app.grails.version=1.0.2 app.name=projecttracker
The Grails developers have decided to not reuse Maven's Standard Directory Layout. See the blog article Why Grails doesn't use Maven for reasons. Lets get familiar with Grails Standard Project Directory Layout. Here are the highlights.
grails-app - This is where most of the Grails specific source code will go
grails-app/conf - This is where configuration souce code will go
grails-app/controllers - This is where controller source code will go
grails-app/domain - This is where your persisted data domain source code will go
grails-app/views - This is where view and view template source code will go
grails-app/services - This is where the Spring POGO service source code will go
src/groovy - This is where extra Groovy source code will go
src/java - This is where extra Java source code will go
templates - This is only used if you decide to override the templates that Grails will use to generate code and configuration
plugins - This is used if you install plugins
lib - This is where extra jar library files go
test/integration - This is where integration test source code will go
test/unit - This is where unit test source code will go
web-app - This is the root of the web site files go
web-app/css - This is where the web site css files go
web-app/js - This is where the web site JavaScript files go
web-app/WEB-INF - This is where controlled Java web files go
web-app/WEB-INF/classes - This where the compiled source code files are placed
grails list-plugins
and
grails plugin-info ivy
grails install-plugin ivy
plugins/ivy-0.1
in the project directory and
C:\Documents and Settings\yourusername\.grails\1.0.2\plugins\ivy
ivy.xml ivyconf.xml
and possible placed the jar file ivy-1.4.1.jar in GRAILS_HOME/lib
grails create-domain-class project
The above command created the files:
grails-app/domain/Project.groovy test/integration/ProjectTests.groovy
from
class Project {
}
to be
class Project
{
static constraints = {
displayOrder(nullable: true)
name(blank: true, nullable: true)
status(blank: false, inList: ['notstarted', 'inprogress', 'onhold', 'abandoned', 'completed'] )
referenceNbr(blank: true, nullable: true)
description(blank: true, nullable: true)
percentDevComplete(nullable: true)
briefStatusDescription(blank: true, nullable: true)
workersAssigned(blank: true, nullable: true)
started(nullable: true)
completed(nullable: true)
onHold(nullable: true)
startDate(nullable: true)
estimatedDueDate(nullable: true)
completedDate(nullable: true)
lastModifiedTs(nullable: true)
lastModifiedBy(nullable: true)
}
/*
lower number is considered more important
i.e. 1, then 10, then 22, then 100, etc.
*/
Integer displayOrder
String name
// one word status
String status
String referenceNbr
String description
BigDecimal percentDevComplete = new BigDecimal("0.00")
// sentence status
String briefStatusDescription
// who is working on the project as a string
String workersAssigned
Boolean started
Boolean completed
Boolean onHold
Date startDate
Date estimatedDueDate
Date completedDate
Date lastModifiedTs = new Date()
String lastModifiedBy
}
The "constraints" are used for:
grails dev run-app
Grails generates the WEB-INF/web.xml file at load time. The web.xml file probably has contents similar to this code listing .
You will see a page similar to the following

So far, we do not have any controllers. If we did they would be listed on the Web Page
if an Id is involved
if an Id is not involved
grails generate-all project
However, don't do that; because, I want to show you a different way.
With one line of code, we can use Grails dynamic scaffolding which gives you a basic Create/Retrieve/Update/Delete (CRUD) Web interface.
grails create-controller dbmaint.ProjectDb
This created the following files:
grails-app/controllers/dbmaint/ProjectDbController.groovy test/integration/dbmaint/ProjectDbControllerTests.groovy
and the directory
grails-app/views/projectDb
package dbmaint
class ProjectDbController {
def index = { }
}
to
package dbmaint
class ProjectDbController
{
def scaffold = Project
}
grails dev run-app
You will see a page similar to the following

Now, we see the controller that we created listed.




grails create-domain-class user2
The above command created the files:
grails-app/domain/User2.groovy test/integration/User2Tests.groovy
from
class User2 {
}
to be
//---------------------------------------------------------
//---------------------------------------------------------
class User2
{
static constraints = {
username(maxLength:60, blank:false, unique:true, nullable:false)
email(email:true, maxLength:60, blank:true, nullable:true)
// blank password is ok!
password(maxLength:60, blank:true, nullable:false)
enabled(nullable:true)
}
String username
String password
String email
Boolean enabled = true
//---------------------------------------------------------
String toString()
{
String strTemp = "${this.class.name}: username: ${username}";
return strTemp;
}
}
grails create-controller dbmaint.UserDb
This created the following files:
grails-app/controllers/dbmaint/UserDbController.groovy test/integration/dbmaint/UserDbControllerTests.groovy
and the directory
grails-app/views/userDb
package dbmaint
class UserDbController {
def index = { }
}
to
package dbmaint
class UserDbController
{
def scaffold = User2
}
The database Settings are located in the file grails-app/conf/DataSource.groovy
The default database is HSQLDB .
For the development environment, the database is an in-memory HSQLDB database.
If desired, you can change what database is used and you can have different settings per environment.
To load sample data in the database at startup you can add save commands in the file grails-app/conf/BootStrap.groovy
Note: use BootStrap.groovy in conjunction with the dbCreate property in DataSource.groovy.
Modify the grails-app/conf/BootStrap.groovy file from
class BootStrap {
def init = { servletContext ->
}
def destroy = {
}
}
to
import grails.util.GrailsUtil
//---------------------------------------------------------
//---------------------------------------------------------
class BootStrap
{
def init = { servletContext ->
//println "**** BootStrap, basedir: ${basedir}"
println "**** BootStrap, GrailsUtil.environment: ${GrailsUtil.environment}"
switch (GrailsUtil.environment)
{
case "development":
println "**** detected development"
configureForDevelopment()
break
case "test":
println "**** detected test"
//configureForTest()
configureForDevelopment()
break
case "production":
println "**** detected production"
configureForProduction()
break
}
}
def destroy = {
}
//---------------------------------------------------------
/**
Tasks to do when Grails is running in dev environment.
*/
void configureForDevelopment()
{
println "configureForDevelopment() called"
def dataItem = null
println "*** BootStrap, at add projects"
dataItem = new Project(
displayOrder: 10,
name: "Project Tracker",
status: 'inprogress',
referenceNbr: 100,
description: "A web app to track development projects",
percentDevComplete: 64.2,
briefStatusDescription: "going good",
workersAssigned: "ME",
started: true,
completed: false,
onHold: false,
startDate: new Date(),
estimatedDueDate: null,
completedDate: null,
)
println "attempt to save project #1"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
dataItem = new Project(
displayOrder: 20,
name: "To Do List Manager",
status: 'notstarted',
referenceNbr: 200,
description: "A web app to manage to do lists",
percentDevComplete: 0.0,
briefStatusDescription: "waiting for a round \"TUIT\"",
workersAssigned: null,
started: false,
completed: false,
onHold: false,
startDate: null,
estimatedDueDate: null,
completedDate: null,
)
println "attempt to save project #2"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
dataItem = new Project(
displayOrder: 30,
name: "Person Time Worked Tracker",
status: 'notstarted',
referenceNbr: 300,
description: "A web app to track time worked on tasks",
percentDevComplete: 110.0,
briefStatusDescription: "DONE",
workersAssigned: "ME, Stan, Brenda",
started: true,
completed: true,
onHold: false,
startDate: new Date(),
estimatedDueDate: new Date(),
completedDate: new Date(),
)
println "attempt to save project #3"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
// Users
// User2s
User2 user2 = new User2()
user2.setUsername("steve")
user2.setPassword("steve")
user2.save()
user2 = new User2()
user2.setUsername("jay")
user2.setPassword("jay")
user2.save()
user2 = new User2()
user2.setUsername("sally")
user2.setPassword("sally")
user2.save()
println "*** BootStrap, Done"
}
//---------------------------------------------------------
void configureForTest()
{
println "configureForTest() called"
}
//---------------------------------------------------------
void configureForProduction()
{
println "configureForProduction() called"
}
}
When you issue the command grails run-app , Grails is starting a Jetty web server . Grail's Jetty web server runs on port 8080 by default. This is also the same default port number as Tomcat.
There are at least two ways to change the port number of the Grails (Jetty) web server.
Edit the file GRAILS_HOME/scripts/Init.groovy and change the phrase 8080 to another port number like 9090 .
When running the Grails web app, add a command line parameter of -Dserver.port=9090 before the run-app phrase.
Example:
grails -Dserver.port=9090 run-app
To get a war file of your Gails app configured to use production environment, run the command
grails war
When the command completes, a war file named projecttracker-0.1.war will be located in the root of your project.
To get a war file of your Gails app configured to use the production environment with a custom file name, run the command
grails prod war projecttracker.war
or
grails war projecttracker.war
The war file named projecttracker.war will be located in the root of your project.
To get a war file of your Gails app configured to use development environment and using a custom file name, run the command
grails dev war projecttracker.war
The war file will be located in the root of your project.
Controllers have Actions that are implemented as Closures .
The Closure Action s generally have the following returning statements:
grails create-controller restV1
grails-app/controllers/RestV1Controller.groovy test/integration/RestV1ControllerTests.groovy
and the directory
grails-app/views/restV1
class RestV1Controller {
def index = { }
}
to
import grails.converters.*
class RestV1Controller
{
//---------------------------------------------------------
/*
returns a REST GET view
URL can contain the following params:
id specify the id for display; if not present,
a list of all items will be returned
output type of return results data format
XML is default format
User can specify json result format with output=json
User can specify html result format with output=html
*/
def project = {
def results = null
// get data needed
if (params.id && Project.exists(params.id))
{
results = Project.get( params.id )
}
else
{
results = Project.list()
}
switch (params.output)
{
case ~/(?i)html/:
if (params.id == null)
{
// Call another action in another controller class
redirect(controller:'projectDb', action:'list', params:params)
}
else
{
// Call another action in another controller class
redirect(controller:'projectDb', action:'show', params:params)
}
break
case ~/(?i)json/:
render results as JSON
break
default:
render results as XML
break
}
}
}
http://localhost:9090/projecttracker/restV1 http://localhost:9090/projecttracker/restV1/project http://localhost:9090/projecttracker/restV1/project?id=1 http://localhost:9090/projecttracker/restV1/project?id=2 http://localhost:9090/projecttracker/restV1/project?id=3 http://localhost:9090/projecttracker/restV1/project/1 http://localhost:9090/projecttracker/restV1/project/2 http://localhost:9090/projecttracker/restV1/project/3 http://localhost:9090/projecttracker/restV1/project/1?output=json http://localhost:9090/projecttracker/restV1/project/2?output=json http://localhost:9090/projecttracker/restV1/project/3?output=json http://localhost:9090/projecttracker/restV1/project/1?output=html http://localhost:9090/projecttracker/restV1/project/2?output=html http://localhost:9090/projecttracker/restV1/project/3?output=html

Here is an example of JSON output (newlines added for format):
[{"id":1,
"class":"Project",
"briefStatusDescription":"going good",
"completed":false,
"completedDate":null,
"description":"A web app to track development projects",
"displayOrder":10,
"estimatedDueDate":null,
"lastModifiedBy":null,
"lastModifiedTs":new Date(1210435279075),
"name":"Project Tracker",
"onHold":false,
"percentDevComplete":64.2,
"referenceNbr":"100",
"startDate":new Date(1210435279075),
"started":true,
"status":"inprogress",
"workersAssigned":"ME"},
{"id":2,
"class":"Project",
"briefStatusDescription":"waiting for a round \"TUIT\"",
"completed":false,
"completedDate":null,
"description":"A web app to manage to do lists",
"displayOrder":20,
"estimatedDueDate":null,
"lastModifiedBy":null,
"lastModifiedTs":new Date(1210435280151),
"name":"To Do List Manager",
"onHold":false,
"percentDevComplete":0,
"referenceNbr":"200",
"startDate":null,
"started":false,
"status":"notstarted",
"workersAssigned":null},
{"id":3,"class":"Project",
"briefStatusDescription":"DONE",
"completed":true,
"completedDate":new Date(1210435280151),
"description":"A web app to track time worked on tasks",
"displayOrder":30,
"estimatedDueDate":new Date(1210435280151),
"lastModifiedBy":null,
"lastModifiedTs":new Date(1210435280151),
"name":"Person Time Worked Tracker",
"onHold":false,
"percentDevComplete":110,
"referenceNbr":"300",
"startDate":new Date(1210435280151),
"started":true,"status":"notstarted",
"workersAssigned":"ME, Stan, Brenda"}]
You can read about custom URL Mapping in the Grails Reference Guide, URL Mappings .
To add a special mapping for the REST web service, modify the file grails-app/conf/UrlMappings.groovy from
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"500"(view:'/error')
}
}
to
class UrlMappings {
static mappings = {
"/restwebservice/v1/$action?/$id?"(controller: "restV1")
// primary default Grails URL mapping
"/$controller/$action?/$id?" {
constraints {
// apply constraints here
}
}
"500"(view:'/error')
}
}
http://localhost:9090/projecttracker/restwebservice/v1 http://localhost:9090/projecttracker/restwebservice/v1/project http://localhost:9090/projecttracker/restwebservice/v1/project?ouput=xml http://localhost:9090/projecttracker/restwebservice/v1/project?ouput=json http://localhost:9090/projecttracker/restwebservice/v1/project?ouput=html http://localhost:9090/projecttracker/restwebservice/v1/project?id=1 http://localhost:9090/projecttracker/restwebservice/v1/project?id=2 http://localhost:9090/projecttracker/restwebservice/v1/project?id=3 http://localhost:9090/projecttracker/restwebservice/v1/project/1 http://localhost:9090/projecttracker/restwebservice/v1/project/2 http://localhost:9090/projecttracker/restwebservice/v1/project/3 http://localhost:9090/projecttracker/restwebservice/v1/project/1?output=json http://localhost:9090/projecttracker/restwebservice/v1/project/2?output=json http://localhost:9090/projecttracker/restwebservice/v1/project/3?output=json http://localhost:9090/projecttracker/restwebservice/v1/project/1?output=html http://localhost:9090/projecttracker/restwebservice/v1/project/2?output=html http://localhost:9090/projecttracker/restwebservice/v1/project/3?output=html
Read more about Sitemesh in the Grails Reference Guide, Layouts with Sitemesh .
Layouts are located in the grails-app/views/layouts directory. The primary and default layout file is grails-app/views/layouts/main.gsp .
We can change the look and feel of all web pages by modifying this file.
<html>
<head>
<title><g:layoutTitle default="Grails" /></title>
<link rel="stylesheet" href="${createLinkTo(dir:'css',file:'main.css')}" />
<link rel="shortcut icon" href="${createLinkTo(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
<g:javascript library="application" />
</head>
<body>
<div id="spinner" class="spinner" style="display:none;">
<img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
</div>
<div class="logo"><img src="${createLinkTo(dir:'images',file:'grails_logo.jpg')}" alt="Grails" /></div>
<g:layoutBody />
</body>
</html>
to
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd"
xml:lang="en" >
<head>
<title><g:layoutTitle default="Demo App" /> - Project Tracker Demo App</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- start YUI CSS -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/reset-fonts-grids/reset-fonts-grids.css">
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/base/base-min.css">
<!-- end YUI CSS -->
<!-- start other YUI skin CSS -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.1/build/assets/skins/sam/skin.css">
<!-- end other YUI skin CSS -->
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js"></script>
<!--
<g:javascript src="jquery-1.2.3.min.js" />
-->
<script type="text/javascript">
var $j = jQuery.noConflict();
</script>
<!-- start the YUI Loader script: -->
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.1/build/yuiloader-dom-event/yuiloader-dom-event.js"></script>
<!-- end the YUI Loader script: -->
<g:javascript>
var MYCOMPANY = YAHOO.namespace("mycompany777com");
</g:javascript>
<link rel="stylesheet" href="${createLinkTo(dir:'css',file:'main.css')}" />
<link rel="stylesheet" href="${createLinkTo(dir:'css',file:'additional.css')}" />
<link rel="shortcut icon" href="${createLinkTo(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<g:layoutHead />
<g:javascript library="application" />
<!-- page-specific scripts -->
<g:javascript src="attach-yui.js" />
<g:javascript src="progress-bar-yui.js" />
</head>
<body class=" yui-skin-sam">
<div id="spinner" class="spinner" style="display:none;">
<img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
</div>
<div id="doc3">
<div id="hd">
<!-- header -->
<div class="header">
<!--
<div class="logo"><img src="${createLinkTo(dir:'images',file:'grails_logo.jpg')}" alt="Grails" /></div>
-->
<div class="banner">
<div class="banner-text">
<a href="${createLinkTo(dir:'',file:'index.gsp')}">Project Tracker</a>
</div>
</div>
<!-- application menu begins here -->
<g:if test="${session?.user != null}">
<div id="mymenu" class="yuimenubar yuimenubarnav">
<div class="bd">
<ul class="first-of-type">
<li class="yuimenubaritem first-of-type">
<a class="yuimenubaritemlabel" href="<g:createLink controller="project" action="list" />">Project</a>
<div id="projectmenu" class="yuimenu">
<div class="bd">
<ul>
<li class="yuimenuitem"><a class="yuimenuitemlabel" href="<g:createLink controller="project" action="list" />">Project List</a></li>
<li class="yuimenuitem"><a class="yuimenuitemlabel" href="<g:createLink controller="project" action="listusetemplate" />">Project List (Using Template)</a></li>
</ul>
</div>
</div>
</li>
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="dbMaint" action="index" />">Database Maintenance</a>
</li>
<li class="yuimenubaritem"><a class="yuimenubaritemlabel" href="#">Profile</a>
<div id="profilemenu" class="yuimenu">
<div class="bd">
<ul>
<li class="yuimenuitem"><a class="yuimenuitemlabel" href="<g:createLink controller="userProfile" action="changepasswordform" />">Change Password...</a></li>
</ul>
</div>
</div>
</li>
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="project" action="uploadform" />">File Upload</a>
</li>
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="project" action="uploadformajax" />">File Upload (Ajax)</a>
</li>
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="help" action="index" />">Help</a>
</li>
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="login" action="logout" />">Logout</a>
</li>
</ul>
</div>
</div>
</g:if>
</div>
</div><!-- end div id="hd" -->
<div id="bd">
<!-- body -->
<div class="article">
<g:layoutBody />
<p>
</p>
<p>
</p>
</div>
</div><!-- end div id="bd" -->
<div id="ft">
<!-- footer -->
<div class="footer">
<p class="text-small">
Project Tracker Application<br />
Copyright © 2008 Me Enterprises
</p>
</div>
</div><!-- end div id="hd" -->
</div><!-- end div id="doc" -->
</body>
</html>

(Feel free to delete the commented out CSS code.)
/*
html * {
margin: 0;
/*padding: 0; SELECT NOT DISPLAYED CORRECTLY IN FIREFOX *8/
}
*/
/* GENERAL */
.spinner
{
padding: 5px;
position: absolute;
right: 0;
}
body
{
/*
background: #fff;
color: #333;
font: 11px verdana, arial, helvetica, sans-serif;
*/
}
a:link, a:visited, a:hover
{
/*
color: #666;
font-weight: bold;
text-decoration: none;
*/
}
h1
{
/*
color: #006dba;
font-weight: normal;
font-size: 16px;
margin: .8em 0 .3em 0;
*/
}
ul
{
/*
padding-left: 15px;
*/
}
input, select, textarea
{
background-color: #fcfcfc;
border: 1px solid #ccc;
/*
font: 11px verdana, arial, helvetica, sans-serif;
margin: 2px 0;
padding: 2px 4px;
*/
}
select
{
/*
padding: 2px 2px 2px 0;
*/
}
textarea
{
width: 250px;
height: 150px;
vertical-align: top;
}
input:focus, select:focus, textarea:focus
{
border: 1px solid #b2d1ff;
}
.body
{
/*
float: left;
margin: 0 15px 10px 15px;
*/
}
/* NAVIGATION MENU */
.nav {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
border-style: solid none solid none;
margin-top: 5px;
padding: 7px 12px;
}
.menuButton {
font-size: 10px;
padding: 0 5px;
}
.menuButton a {
color: #333;
padding: 4px 6px;
}
.menuButton a.home {
background: url(../images/skin/house.png) center left no-repeat;
color: #333;
padding-left: 25px;
}
.menuButton a.list {
background: url(../images/skin/database_table.png) center left no-repeat;
color: #333;
padding-left: 25px;
}
.menuButton a.create {
background: url(../images/skin/database_add.png) center left no-repeat;
color: #333;
padding-left: 25px;
}
/* MESSAGES AND ERRORS */
.message {
background: #f3f8fc url(../images/skin/information.png) 8px 50% no-repeat;
border: 1px solid #b2d1ff;
color: #006dba;
margin: 10px 0 5px 0;
padding: 5px 5px 5px 30px
}
div.errors {
background: #fff3f3;
border: 1px solid red;
color: #cc0000;
margin: 10px 0 5px 0;
padding: 5px 0 5px 0;
}
div.errors ul {
list-style: none;
padding: 0;
}
div.errors li {
background: url(../images/skin/exclamation.png) 8px 0% no-repeat;
line-height: 16px;
padding-left: 30px;
}
td.errors select {
border: 1px solid red;
}
td.errors input {
border: 1px solid red;
}
/* TABLES */
table {
/*
border: 1px solid #ccc;
width: 100%
*/
}
tr {
/*
border: 0;
*/
}
td, th {
/*
font: 11px verdana, arial, helvetica, sans-serif;
line-height: 12px;
padding: 5px 6px;
text-align: left;
vertical-align: top;
*/
}
th {
/*
background: #fff url(../images/skin/shadow.jpg);
color: #666;
font-size: 11px;
font-weight: bold;
line-height: 17px;
padding: 2px 6px;
*/
}
th a:link, th a:visited, th a:hover {
/*
color: #333;
display: block;
font-size: 10px;
text-decoration: none;
width: 100%;
*/
}
th.asc a, th.desc a {
background-position: right;
background-repeat: no-repeat;
}
th.asc a {
background-image: url(../images/skin/sorted_asc.gif);
}
th.desc a {
background-image: url(../images/skin/sorted_desc.gif);
}
.odd {
background: #f7f7f7;
}
.even {
background: #fff;
}
/* LIST */
.list table {
border-collapse: collapse;
}
.list th, .list td {
border-left: 1px solid #ddd;
}
.list th:hover, .list tr:hover {
background: #b2d1ff;
}
/* PAGINATION */
.paginateButtons {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
border-top: 0;
color: #666;
font-size: 10px;
overflow: hidden;
padding: 10px 3px;
}
.paginateButtons a {
background: #fff;
border: 1px solid #ccc;
border-color: #ccc #aaa #aaa #ccc;
color: #666;
margin: 0 3px;
padding: 2px 6px;
}
.paginateButtons span {
padding: 2px 3px;
}
/* DIALOG */
.dialog table {
padding: 5px 0;
}
.prop {
padding: 5px;
}
.prop .name {
text-align: left;
width: 15%;
white-space: nowrap;
}
.prop .value {
text-align: left;
width: 85%;
}
/* ACTION BUTTONS */
.buttons {
background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
border: 1px solid #ccc;
color: #666;
font-size: 10px;
margin-top: 5px;
overflow: hidden;
padding: 0;
}
.buttons input {
background: #fff;
border: 0;
color: #333;
cursor: pointer;
font-size: 10px;
font-weight: bold;
margin-left: 3px;
overflow: visible;
padding: 2px 6px;
}
.buttons input.delete {
background: transparent url(../images/skin/database_delete.png) 5px 50% no-repeat;
padding-left: 28px;
}
.buttons input.edit {
background: transparent url(../images/skin/database_edit.png) 5px 50% no-repeat;
padding-left: 28px;
}
.buttons input.save {
background: transparent url(../images/skin/database_save.png) 5px 50% no-repeat;
padding-left: 28px;
}

<html>
<head>
<title>Welcome to Grails</title>
<meta name="layout" content="main" />
</head>
<body>
<h1 style="margin-left:20px;">Welcome to Grails</h1>
<p style="margin-left:20px;width:80%">Congratulations, you have successfully started your first Grails application! At the moment
this is the default page, feel free to modify it to either redirect to a controller or display whatever
content you may choose. Below is a list of controllers that are currently deployed in this application,
click on each to execute its default action:</p>
<div class="dialog" style="margin-left:20px;width:60%;">
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses}">
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
</g:each>
</ul>
</div>
</body>
</html>
to
<html>
<head>
<title>Welcome to Grails</title>
<meta name="layout" content="main" />
</head>
<body>
<h1>Welcome to Grails</h1>
<p>Congratulations, you have successfully started your first Grails application! At the moment
this is the default page, feel free to modify it to either redirect to a controller or display whatever
content you may choose. Below is a list of controllers that are currently deployed in this application,
click on each to execute its default action:</p>
<div class="dialog">
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses}">
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
</g:each>
</ul>
</div>
</body>
</html>

class SecurityFilters {
def filters = {
authenticated(controller: "*", action: "*")
{
before = {
// ignore exception actions
if (!actionName.equals("login") && !actionName.equals("handleLogin"))
{
if (!session.user)
{
println "failed security check!"
redirect(controller: "login", action: "login")
return false
}
}
}
}
} // end closure filters
}
To create the controllers, run the following commands:
grails create-controller Project grails create-controller DbMaint grails create-controller UserProfile grails create-controller Help grails create-controller Login
This will have created at least two files per create-controller command.
//---------------------------------------------------------
//---------------------------------------------------------
class DbMaintController
{
//---------------------------------------------------------
def index = {
// send to a gsp view page
// render the view with the controller as the model
return new org.springframework.web.servlet.ModelAndView(
"/dbMaint/dbmaintmain")
}
}
//---------------------------------------------------------
//---------------------------------------------------------
class HelpController
{
//def index = { }
//---------------------------------------------------------
def index = {
// send to a gsp view page
// render the view with the controller as the model
return new org.springframework.web.servlet.ModelAndView(
"/help/help")
}
}
//---------------------------------------------------------
//---------------------------------------------------------
class LoginController
{
//def index = { }
//---------------------------------------------------------
def login = {
if (session.user)
{
redirect(uri:"/index.gsp")
}
}
//---------------------------------------------------------
def handleLogin = {
def u = User2.findByUsername( params.username )
if (u)
{
if (u.password == params.password)
{
session.user = u
redirect(uri:"/index.gsp")
}
else
{
log.debug('incorrect password for ${params.username}')
flash.message = "Username or password is not correct"
redirect(action:'login')
}
}
else
{
log.debug('username ${params.username} does not exist')
flash.message = "Username or password is not correct"
redirect(action:'login')
}
}
//---------------------------------------------------------
def logout = {
if (session.user)
{
session.user = null
redirect(uri:"/login/login.gsp")
}
}
}
//---------------------------------------------------------
//---------------------------------------------------------
class ProjectController
{
List projects = null
// services
def importService
//---------------------------------------------------------
def index = {
redirect(action:'list', params:params)
}
//---------------------------------------------------------
def list = {
//projects = Project.list(params)
projects = Project.list(sort:"displayOrder", order:"asc")
// this is the model for the view
[ dataItemList : projects ]
}
//---------------------------------------------------------
def listusetemplate = {
//projects = Project.list(params)
projects = Project.list(sort:"displayOrder", order:"asc")
return new org.springframework.web.servlet.ModelAndView(
"/project/list-use-template", [ dataItemList : projects ])
}
//---------------------------------------------------------
def uploadform = {
return new org.springframework.web.servlet.ModelAndView(
"/project/upload-one-file-form")
}
//---------------------------------------------------------
def upload = {
println "begin upload process"
//println "request type: ${request.class.name}"
String strResult = importService.handleUploadAndImport(request);
flash.message = strResult
println "end upload process"
// show the same upload form
redirect(controller: "project", action: "uploadform")
}
//---------------------------------------------------------
def uploadformajax = {
return new org.springframework.web.servlet.ModelAndView(
"/project/upload-one-file-form-ajax")
}
//---------------------------------------------------------
def uploadajax = {
println "begin upload process"
//println "request type: ${request.class.name}"
String strResult = importService.handleUploadAndImport(request);
println "end upload process"
render strResult
}
}
//---------------------------------------------------------
//---------------------------------------------------------
class UserProfileController
{
//---------------------------------------------------------
def changepasswordform =
{
// send to a gsp view page
// render the view with the controller as the model
return new org.springframework.web.servlet.ModelAndView(
"/userProfile/change-pw-form")
}
//---------------------------------------------------------
def changePassword = {
def u = User2.get(session.user.id)
if (params.pwd == params.pwconfirm)
{
u.setPassword( params.pwd )
u.save()
flash.message = "Password change successful."
}
else
{
flash.message = "The password and the confirmation password don't match."
}
redirect(action:'changepasswordform')
}
}
<html>
<head>
<title>Database Maintenance</title>
<meta name="layout" content="main" />
</head>
<body>
<h1>Database Maintenance</h1>
<p>
Pick the table to maintain.
</p>
<div class="dialog">
<ul>
<g:each var="c" in="${grailsApplication.controllerClasses}">
<g:if test="${c.fullName.indexOf('dbmaint.') != -1}">
<li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
</g:if>
</g:each>
</ul>
</div>
</body>
</html>
<html>
<head>
<meta name="layout" content="main" />
<title>Help</title>
</head>
<body>
<h1>Help</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<p>
TODO: put some helpful descriptions here.
</p>
<p>
TODO: put some helpful descriptions here.
</p>
<p>
TODO: put some helpful descriptions here.
</p>
</body>
</html>
<html>
<head>
<title>Login Page</title>
<meta name="layout" content="main" />
</head>
<body>
<h1>Login</h1>
<p>Welcome to Project Tracker. Login below.</p>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<g:form controller="login" action="handleLogin" method="post">
<table>
<tr>
<td><label for="username">Username:</label> </td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td><label for="password">Password:</label> </td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td> </td>
<td><input id="login-button" class="button" type="submit" value="Login" /></td>
</tr>
</table>
</g:form>
<g:javascript>
MYCOMPANY.initPageJs = function() {
// apply nice look to the form submit button
var oButton1 = new YAHOO.widget.Button("login-button");
}; // end of MYCOMPANY.initPageJs;
</g:javascript>
</body>
</html>
<html>
<head>
<meta name="layout" content="main" />
<title>Project List</title>
<style type="text/css" media="screen">
h2
{
background-color: rgb(178, 209, 255);
margin: 0 0 0 0;
padding: 0.5em 0.5em 0.5em 0.5em;
}
td
{
border-style: none;
}
.even
{
background-color: rgb(249, 248, 232);
background-color: rgb(250, 249, 235);
background-color: rgb(254, 254, 241);
background-color: rgb(249, 250, 243);
}
.odd
{
background-color: rgb(233, 237, 243);
}
.small-text
{
font-size: x-small;
}
.important-text
{
font-size: 2em;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Project List</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<div class="projectlist">
<table>
<tbody>
<g:each in="${dataItemList}" status="i" var="dataItem">
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3"><h2><span style="font-size: 0.5em;">Project Name: </span><br />
${dataItem.name?.encodeAsHTML()}</h2></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Ref Nbr: ${dataItem.referenceNbr?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Description: ${dataItem.description?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}"
<td colspan="3">Percent Dev Complete: <span class="important-text">${dataItem.percentDevComplete?.encodeAsHTML()} %</span></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">
<div style="background-color: rgb(214, 220, 201); width: ${100.0 * 0.2}em; height: 2em;">
<div style="background-color: rgb(240, 66, 92); width: ${dataItem.percentDevComplete * 0.2}em; height: 2em;"> </div>
</div>
</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Status: <span class="important-text">${dataItem.status?.encodeAsHTML()}</span></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">DB Id: ${dataItem.id?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Brief Status Desc.: ${dataItem.briefStatusDescription?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Workers Assigned: ${dataItem.workersAssigned?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text">Started: ${dataItem.started?.encodeAsHTML()}</td>
<td class="small-text">Completed: ${dataItem.completed?.encodeAsHTML()}</td>
<td class="small-text">On Hold: ${dataItem.onHold?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">StartDate: ${dataItem.startDate?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Estimated Due Date: ${dataItem.estimatedDueDate?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<g:if test="${dataItem.completed}">
<td colspan="3"><strong>Completed Date:
${dataItem.completedDate?.encodeAsHTML()}<strong>
</td>
</g:if>
</tr>
</g:each>
</tbody>
</table>
</div>
<div class="paginateButtons">
<g:paginate total="${Project.count()}" />
</div>
</body>
</html>
<html>
<head>
<title>Change Password</title>
<meta name="layout" content="main" />
</head>
<body>
<h1>Change Password</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<g:form controller="userProfile" action="changePassword" method="post">
<table>
<tr>
<td><label>New Password:</label> </td>
<td><input type="password" name="pwd" validate="required" /></td>
</tr>
<tr>
<td><label>New Password (Confirm):</label> </td>
<td><input type="password" name="pwconfirm" validate="required" /><td>
</tr>
<tr>
<td></td>
<td><input class="button" type="submit" value="Submit" /></td>
<tr>
</table>
</g:form>
</body>
</html>
<html>
<head>
<title>Welcome to Project Tracker</title>
<meta name="layout" content="main" />
</head>
<body>
<% if (!session.user) response.sendRedirect("login/login.gsp"); %>
<h1>Welcome to Project Tracker</h1>
<p>
You are now logged in as
<strong><%= session?.user?.username %></strong>
</p>
</body>
</html>


grails stats
grails clean
Grails includes Log4j to enable application logging. The default logging level for the application is "error" level logging.
To change the logging level and the log message pattern just for the development environment, add the following code snipplet into the file grails-app/conf/Config.groovy at the proper location.
environments {
development {
// change log level and pattern for development environment
log4j {
appender.'stdout.layout'="org.apache.log4j.PatternLayout"
appender.'stdout.layout.ConversionPattern'='%d{yyyyMMdd HH:mm:ss} %-5p [%t] %C{2} %L %M: %m%n'
rootLogger="info,stdout"
logger.org.hibernate="debug"
}
}
}
If you want to see the SQL statements from Hibernate, add or modify the following code fragment
hibernate {
loggingSql = true
}
in grails-app/conf/DataSource.groovy within the "environments, development" code block.
Resulting in something like the following:
// environment specific settings
environments {
development {
...
hibernate {
loggingSql = true
}
...
}
...
}
or add the line
logSql = true
within the dataSource code block in the file grails-app/conf/DataSource.groovy, for the result
dataSource {
...
logSql = true
...
}
By convention, the following will receive a variable named log that you can use to create log messages:
class ProjectControllerTests extends GroovyTestCase {
void testSomething() {
}
}
to
//---------------------------------------------------------
//---------------------------------------------------------
class ProjectControllerTests extends GroovyTestCase
{
//---------------------------------------------------------
void setUp()
{
Project.list()*.delete()
def dataItem = null
dataItem = new Project(
displayOrder: 10,
name: "Project Tracker",
status: 'inprogress',
referenceNbr: 100,
description: "A web app to track development projects",
percentDevComplete: 64.2,
briefStatusDescription: "going good",
workersAssigned: "ME",
started: true,
completed: false,
onHold: false,
startDate: new Date(),
estimatedDueDate: null,
completedDate: null,
)
println "attempt to save project #1"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
dataItem = new Project(
displayOrder: 20,
name: "To Do List Manager",
status: 'notstarted',
referenceNbr: 200,
description: "A web app to manage to do lists",
percentDevComplete: 0.0,
briefStatusDescription: "waiting for a round \"TUIT\"",
workersAssigned: null,
started: false,
completed: false,
onHold: false,
startDate: null,
estimatedDueDate: null,
completedDate: null,
)
println "attempt to save project #2"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
dataItem = new Project(
displayOrder: 30,
name: "Person Time Worked Tracker",
status: 'completed',
referenceNbr: 300,
description: "A web app to track time worked on tasks",
percentDevComplete: 110.0,
briefStatusDescription: "DONE",
workersAssigned: "ME, Stan, Brenda",
started: true,
completed: true,
onHold: false,
startDate: new Date(),
estimatedDueDate: new Date(),
completedDate: new Date(),
)
println "attempt to save project #3"
if( !dataItem.save() )
{
dataItem.errors.each
{
println "error: " + it
}
}
}
//---------------------------------------------------------
void tearDown()
{
Project.list()*.delete()
}
//---------------------------------------------------------
void testList()
{
ProjectController projectController = new ProjectController()
assertNotNull(projectController)
assertNull(projectController.importService)
def mapModel = projectController.list()
assertNotNull(mapModel)
if (mapModel instanceof Map)
{
assertNotNull(mapModel.dataItemList)
def list = mapModel.dataItemList
assertEquals(list.size(), 3)
}
}
//---------------------------------------------------------
void testListusetemplate()
{
ProjectController projectController = new ProjectController()
assertNotNull(projectController)
assertNull(projectController.importService)
def mapModel = projectController.listusetemplate()
assertNotNull(mapModel)
if (mapModel instanceof Map)
{
assertNotNull(mapModel.dataItemList)
def list = mapModel.dataItemList
assertEquals(list.size(), 3)
}
}
}
grails test-app
grails create-service import
The above command created the files:
grails-app/services/ImportService.groovy test/integration/ImportServiceTests.groovy
class ImportService {
boolean transactional = true
def serviceMethod() {
}
}
to be
import com.mycompany777.projecttracker.Constants
//import org.codehaus.groovy.grails.commons.ApplicationHolder
import org.codehaus.groovy.grails.web.context.ServletContextHolder as SCH
import org.apache.commons.lang.StringUtils
import org.apache.commons.io.IOUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.IOCase
import org.apache.commons.lang.time.DateUtils
//import org.codehaus.groovy.grails.commons.ConfigurationHolder
//---------------------------------------------------------
//---------------------------------------------------------
class ImportService
{
boolean transactional = false
String strEncoding = Constants.STR_UTF_8
String strXhtmlLineBreak = Constants.STR_XHTML_LINE_BREAK
String strXhtmlIndent = Constants.STR_XHTML_INDENT
//---------------------------------------------------------
def serviceMethod()
{
//def servletContext = ApplicationHolder.getApplication().getParentContext().getServletContext()
def servletContext = SCH.servletContext
return "Hello from service"
}
//---------------------------------------------------------
def greeting()
{
//def servletContext = ApplicationHolder.getApplication().getParentContext().getServletContext()
def servletContext = SCH.servletContext
return "Hello from service"
}
//---------------------------------------------------------
/*
1: Project data
etc...
-1: None of the above
*/
Integer determineFileTypeBasedOnFileName(String filename)
{
if (StringUtils.isBlank(filename))
{
return Constants.INT_FILE_TYPE_INVALID
}
String wildcardMatcher = "*project*.txt"
if (FilenameUtils.wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE))
{
return Constants.INT_FILE_TYPE_BLAH
}
return Constants.INT_FILE_TYPE_INVALID
}
//---------------------------------------------------------
Boolean isInputFileNameValid(String filename)
{
if (StringUtils.isBlank(filename))
{
return false
}
String extension = "txt"
if (FilenameUtils.isExtension(filename, extension))
{
String wildcardMatcher = "*data*.txt"
if (FilenameUtils.wildcardMatch(filename, wildcardMatcher, IOCase.INSENSITIVE))
{
return true
}
}
return false
}
//---------------------------------------------------------
/*
*/
Map processUploadSingleFile(javax.servlet.http.HttpServletRequest request,
String strWebFormFileId,
String strOutputDirectory)
{
// create output directory if it does not exist
File fileDirOutput = new File(strOutputDirectory)
if (!fileDirOutput.exists())
{
// this will make all the directories
log.debug("forceMkdir, fileDirOutput: ${fileDirOutput}")
FileUtils.forceMkdir(fileDirOutput)
}
//org.springframework.web.multipart.commons.CommonsMultipartFile
Map mapSingleFileStatus = [:]
// assume it is empty unless find otherwise
mapSingleFileStatus.empty = true
def f = request.getFile(strWebFormFileId)
log.debug("f type is ${f.class.name}")
Integer nFileType1 = Constants.INT_FILE_TYPE_INVALID
log.debug("f.getOriginalFilename(): ${f.getOriginalFilename()}")
mapSingleFileStatus.originalFileName = f.getOriginalFilename()
nFileType1 = determineFileTypeBasedOnFileName(f.getOriginalFilename())
mapSingleFileStatus.fileType = nFileType1
if (isInputFileNameValid(f.getOriginalFilename()))
{
if (!f.empty)
{
mapSingleFileStatus.empty = false
String strDataFileNameExcel = strOutputDirectory + "/" + f.getOriginalFilename()
log.debug("strDataFileNameExcel: ${strDataFileNameExcel}")
File fileOutput = new File(strDataFileNameExcel)
log.debug("transferTo, fileOutput: ${fileOutput}")
f.transferTo( fileOutput )
log.debug("file upload was successful")
}
else
{
mapSingleFileStatus.empty = true
}
}
else
{
log.debug("file name is not valid")
}
return mapSingleFileStatus
}
//---------------------------------------------------------
/*
*/
String handleUploadAndImport(javax.servlet.http.HttpServletRequest request)
{
StringBuffer sbuffStatusMessage = new StringBuffer()
sbuffStatusMessage << "<strong>Upload</strong>"
sbuffStatusMessage << strXhtmlLineBreak
sbuffStatusMessage << "${strXhtmlIndent}${new Date()}: Begin Upload"
sbuffStatusMessage << strXhtmlLineBreak
//def servletContext = ApplicationHolder.getApplication().getParentContext().getServletContext()
def servletContext = SCH.servletContext
log.debug("begin upload process")
String strOutputDirectory = servletContext.getRealPath("/WEB-INF" + "/" + Constants.STR_DATA_DIRECTORY)
List listStatusFilesUploaded = []
Map mapSingleFileStatus = null
mapSingleFileStatus = processUploadSingleFile(request, "myFile", strOutputDirectory)
listStatusFilesUploaded << mapSingleFileStatus
if (mapSingleFileStatus.fileType != Constants.INT_FILE_TYPE_INVALID)
{
sbuffStatusMessage << "${strXhtmlIndent}${mapSingleFileStatus.originalFileName} uploaded"
sbuffStatusMessage << strXhtmlLineBreak
}
log.debug("Done uploading files")
log.debug("listStatusFilesUploaded: ${listStatusFilesUploaded}")
Boolean booleanDoImport = false
log.debug("listStatusFilesUploaded: ")
for (item in listStatusFilesUploaded)
{
log.debug("item: ${item}")
if (item.fileType != Constants.INT_FILE_TYPE_INVALID)
{
booleanDoImport = true
}
}
sbuffStatusMessage << "${strXhtmlIndent}${new Date()}: Upload Complete"
sbuffStatusMessage << strXhtmlLineBreak
sbuffStatusMessage << "<strong>Import</strong>"
sbuffStatusMessage << strXhtmlLineBreak
String strResult = ""
if (booleanDoImport == true)
{
sbuffStatusMessage << "${strXhtmlIndent}${new Date()}: Begin Import"
sbuffStatusMessage << strXhtmlLineBreak
for (item in listStatusFilesUploaded)
{
log.debug("item: ${item}")
switch (item.fileType)
{
case Constants.INT_FILE_TYPE_BLAH:
log.debug("calling processDataInFile()")
processDataInFile(item)
log.debug("finished calling processDataInFile()")
sbuffStatusMessage << "${strXhtmlIndent}${new Date()}: Imported Data File"
sbuffStatusMessage << strXhtmlLineBreak
break;
default:
break;
}
}
sbuffStatusMessage << "${strXhtmlIndent}${new Date()}: Import Complete"
sbuffStatusMessage << strXhtmlLineBreak
}
else
{
sbuffStatusMessage << "${strXhtmlIndent}<span class=\"error-msg\">No valid files were uploaded.</span>"
sbuffStatusMessage << strXhtmlLineBreak
}
return sbuffStatusMessage.toString()
}
//---------------------------------------------------------
String processDataInFile(Map mapFileInfo)
{
//def servletContext = ApplicationHolder.getApplication().getParentContext().getServletContext()
def servletContext = SCH.servletContext
log.debug("processDataInFiles; begin import all")
log.debug("ImportService doImport begin")
log.debug("ImportService doImport complete")
return "processDataInFiles; finished importing all"
}
}
// services def importService
//---------------------------------------------------------
def uploadform = {
return new org.springframework.web.servlet.ModelAndView(
"/project/upload-one-file-form")
}
//---------------------------------------------------------
def upload = {
println "begin upload process"
//println "request type: ${request.class.name}"
String strResult = importService.handleUploadAndImport(request);
flash.message = strResult
println "end upload process"
// show the same upload form
redirect(controller: "project", action: "uploadform")
}
src/groovy/com/mycompany777/projecttracker/BasicUtil.groovy
package com.mycompany777.projecttracker
import org.apache.commons.io.FileUtils
//---------------------------------------------------------
//---------------------------------------------------------
/**
This requires commons-io-1.4.jar or higher
and not commons-io-1.2.jar to work correctly.
*/
class BasicUtil
{
//---------------------------------------------------------
/**
*/
static void writeStringToFile(String strFileName, String data)
{
File file = new File(strFileName)
FileUtils.writeStringToFile(file, data)
}
}
and
src/groovy/com/mycompany777/projecttracker/Constants.groovy
package com.mycompany777.projecttracker
//---------------------------------------------------------
//---------------------------------------------------------
/**
*/
class Constants
{
static final Integer INT_FILE_TYPE_BLAH = 1
static final Integer INT_FILE_TYPE_INVALID = -1
public static final String STR_DATA_DIRECTORY = "imported-data"
public static final String STR_PATTERN_DATE_FOR_VIEWING = "EEE, dd MMM yyyy HH:mm:ss z" // RFC_822
public static final String STR_UTF_8 = "UTF-8"
public static final String STR_XHTML_LINE_BREAK = "<br />"
public static final String STR_XHTML_INDENT = " "
}
<li class="yuimenubaritem">
<a class="yuimenubaritemlabel" href="<g:createLink controller="project" action="uploadform" />">File Upload</a>
</li>
<html>
<head>
<meta name="layout" content="main" />
<title>Upload A Data File</title>
</head>
<body>
<h1>Upload A Data File</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<p>
This form allows you to upload some data file.
</p>
<g:form id="form3" controller="project" action="upload" method="post" enctype="multipart/form-data">
<table>
<tr>
<td><label for="myFile">Some Data File:</label> </td>
<td><input type="file" name="myFile" id="myFile" size="70" /></td>
</tr>
<tr>
<td> </td>
<td>
<input type="submit" value="Upload and Import"/>
</td>
</tr>
</table>
</g:form>
</body>
</html>

//---------------------------------------------------------
def uploadformajax = {
return new org.springframework.web.servlet.ModelAndView(
"/project/upload-one-file-form-ajax")
}
//---------------------------------------------------------
def uploadajax = {
println "begin upload process"
//println "request type: ${request.class.name}"
String strResult = importService.handleUploadAndImport(request);
println "end upload process"
render strResult
}
<html>
<head>
<meta name="layout" content="main" />
<title>Upload A Data File</title>
</head>
<body>
<h1>Upload A Data File (Using Ajax)</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<div id="message-import" class="status-msg" style="visibility: hidden;"></div>
<p>
This form allows you to upload some data file.
</p>
<form id="ajaxForm" name="ajaxForm" enctype="multipart/form-data">
<table>
<tr>
<td><label for="myFile">Some Data File:</label> </td>
<td><input type="file" name="myFile" id="myFile" size="70" /></td>
</tr>
<tr>
<td> </td>
<td>
<input type="button" id="link-upload" value="Upload and Import">
</td>
</tr>
</table>
</form>
<g:javascript>
MYCOMPANY.initPageJs = function() {
/*
var elResultStatus = document.getElementById('message-import');
elResultStatus.style.visibility = "hidden";
*/
// apply nice look to the form submit button
var oButton1 = new YAHOO.widget.Button("link-upload");
// BEGIN upload form ajax stuff
//---------------------------------------------------------
//---------------------------------------------------------
var objUploadFileImport = {
handleSuccess: function(o) {
YAHOO.progressbar.yui.containter.hideProgressBar();
YAHOO.log("The success handler was called. tId: " + o.tId + ".", "info", "example");
if (o.responseText !== undefined)
{
//alert(o.responseText)
// fill in the "import directory" location
var elImportDirectory = document.getElementById("message-import");
elImportDirectory.innerHTML = o.responseText;
}
},
//---------------------------------------------------------
makeAjaxRequest_UploadFileImport: function ()
{
var callbackUploadFileImport = {
upload: this.handleSuccess,
cache: false
};
var sUrl = "<g:createLink controller="project" action="uploadajax" />";
// argument formId can be the id or name attribute value of the
// HTML form, or an HTML form object.
var formObject = document.getElementById("ajaxForm");
// the second argument is true to indicate file upload.
YAHOO.util.Connect.setForm(formObject, true);
var objAjaxRequest = YAHOO.util.Connect.asyncRequest('POST', sUrl, callbackUploadFileImport);
YAHOO.log("Initiating request; tId: " + objAjaxRequest.tId + ".", "info", "example");
}
}; // end of objUploadFileImport
var elLinkSubmitForm = new YAHOO.util.Element("link-upload");
//create an event listener:
var fnHandleClick_UploadFileImport = function(e)
{
// prevent default behavior
YAHOO.util.Event.preventDefault(e);
YAHOO.log("do import button clicked", "info", "example");
//alert(elLinkSubmitForm.get('id') + ' clicked');
// clear any previous call's message
var elResultStatus = document.getElementById('message-import');
elResultStatus.innerHTML = "<p>Importing, please wait...</p>";
elResultStatus.style.visibility = "visible";
YAHOO.progressbar.yui.containter.showProgressBar("Importing, please wait...");
objUploadFileImport.makeAjaxRequest_UploadFileImport();
};
// assign an event listener using 'on';
// el.addListener and el.on are identical here:
elLinkSubmitForm.on('click', fnHandleClick_UploadFileImport);
//YAHOO.util.Event.addListener(elLinkSubmitForm, "click", fnHandleClick_UploadFileImport);
}; // end of MYCOMPANY.initPageJs;
</g:javascript>
</body>
</html>
If desired, one can contain snipplets of html view code in a template file. This is useful if the snipplet of html view code is used in multiple view pages. Read more about templates in the Grails Reference Guide, Views and Templates . For demonstration, we will use a template file for the individual project view code.
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3"><h2><span style="font-size: 0.5em;">Project Name: </span><br />
${dataItem.name?.encodeAsHTML()}</h2></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Ref Nbr: ${dataItem.referenceNbr?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Description: ${dataItem.description?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}"
<td colspan="3">Percent Dev Complete: <span class="important-text">${dataItem.percentDevComplete?.encodeAsHTML()} %</span></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">
<div style="background-color: rgb(214, 220, 201); width: ${100.0 * 0.2}em; height: 2em;">
<div style="background-color: rgb(240, 66, 92); width: ${dataItem.percentDevComplete * 0.2}em; height: 2em;"> </div>
</div>
</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Status: <span class="important-text">${dataItem.status?.encodeAsHTML()}</span></td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">DB Id: ${dataItem.id?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td colspan="3">Brief Status Desc.: ${dataItem.briefStatusDescription?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Workers Assigned: ${dataItem.workersAssigned?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text">Started: ${dataItem.started?.encodeAsHTML()}</td>
<td class="small-text">Completed: ${dataItem.completed?.encodeAsHTML()}</td>
<td class="small-text">On Hold: ${dataItem.onHold?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">StartDate: ${dataItem.startDate?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td class="small-text" colspan="3">Estimated Due Date: ${dataItem.estimatedDueDate?.encodeAsHTML()}</td>
</tr>
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<g:if test="${dataItem.completed}">
<td colspan="3"><strong>Completed Date:
${dataItem.completedDate?.encodeAsHTML()}<strong>
</td>
</g:if>
</tr>
<html>
<head>
<meta name="layout" content="main" />
<title>Project List</title>
<style type="text/css" media="screen">
h2
{
background-color: rgb(178, 209, 255);
margin: 0 0 0 0;
padding: 0.5em 0.5em 0.5em 0.5em;
}
td
{
border-style: none;
}
.even
{
background-color: rgb(249, 248, 232);
background-color: rgb(250, 249, 235);
background-color: rgb(254, 254, 241);
background-color: rgb(249, 250, 243);
}
.odd
{
background-color: rgb(233, 237, 243);
}
.small-text
{
font-size: x-small;
}
.important-text
{
font-size: 2em;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Project List (using a template for each project)</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<div class="projectlist">
<table>
<tbody>
<g:each in="${dataItemList}" status="i" var="dataItem">
<g:render template="projectTemplate" model="[i:i, dataItem:dataItem]" />
</g:each>
</tbody>
</table>
</div>
<div class="paginateButtons">
<g:paginate total="${Project.count()}" />
</div>
</body>
</html>
We already have the following action in the ProjectController.groovy that will render out our view that uses the template.
//---------------------------------------------------------
def listusetemplate = {
projects = Project.list(sort:"displayOrder", order:"asc")
return new org.springframework.web.servlet.ModelAndView(
"/project/list-use-template", [ dataItemList : projects ])
}
We also already have a menu item ready to call the correct controller and action.
This works in the development environment only.
Add the query string "showSource" on the end of the URL to see the source code for a gsp view.
Example URL: http://localhost:9090/projecttracker/project/list?showSource
Try doing a "grails clean" before you begin to import your Grails project files into source code control.
grails clean
Generally you should check in all files and directories and leave out the following:
stacktrace.log (file)
web-app\WEB-INF\classes (directory)
web-app\WEB-INF\spring (directory)
If I had been using Grails version 1.0.1 and then Grails version 1.0.2 was released and I wanted to upgrade, I could do the following.
Install Grails version 1.0.2, update GRAILS_HOME, and update my path to include the bin directory of Grails version 1.0.2. In my Grails project I would then issue the command:
grails upgrade
I would answer a few prompts and then I am done.
To export the database schema, read the following blog entry, A Gant Script to Call Hibernate's SchemaExport
grails prod schema-export ddl.sql.txt
The schema will be written to the file ddl.sql.txt
Your Grails web application is probably running pretty slow. In the file grails-app/conf/DataSource.groovy, set the pooled element of dataSource to true to speed up the Grails web application
dataSource {
pooled = true
...
}