After prototyping using gracelets you will many times want to split up true controller
code from your gracelet views. However you may want the option to continue using gracelet
features and even develop with a reloadable controller.
Gracelets provides the option of writing a pogo that will work as a controller, being a scoped
singleton (conversational, session, etc.,) and being able to put common shareable code from the
different views.
Again, this is just an option, you can and may prefer to write a controller in some other framework (like Seam, Spring) or
some other form of controller. But again, here you have the option and gracelets gives you the power
in your controller that you may have gotten acustomed to in your views.
All gracelet controllers go into your WEB-INF/gracelet/controller directory. Being there you
can update them as often as you need to and hey will be hot replaced.
To see the benefit lets see an example.
Here is view1.groovy and view2.groovy:
view1.groovyToggle Line Numbers |
1
Factory("someBean") { [:] } 2
3
SelectItems("countryList") { ["Country 1", "Country 2", "Country 3"] } 4
5
xh.html { 6
head { title("View 1") } 7
body { 8
h.form { 9
10
print "Select the country: " 11
h.selectOneMenu(value: Value { someBean.country }) { 12
j.selectItems(value: { countryList }) 13
} 14
15
} 16
} 17
}
|
view2.groovyToggle Line Numbers |
1
xh.html { 2
head { title("View 2") } 3
body { 4
h.form { 5
6
print "First Name:" 7
h.inputText(value: Value { someBean.firstName }) 8
9
print "Last Name:" 10
h.inputText(value: Value { someBean.lastName }) 11
12
print "Address:" 13
h.inputText(value: Value { someBean.address }) 14
15
print "City:" 16
h.inputText(value: Value { someBean.city }) 17
18
print "Select the country: " 19
h.selectOneMenu(value: Value { someBean.country }) { 20
j.selectItems(value: { countryList }) 21
} 22
23
} 24
} 25
}
|
In the above examples we have two views that access the someBean bean and the
country list. It will all work ok as long as someone hits view1.groovy before anyone
hits view2.groovy. Since view1.groovy will not create the Factory() and SelectItems()
code until it runs. This means that view2 will fail until view1 is processed by someone.
Obviously it is not good to rely on that type of logic. This would show the need for a controller
of some type. Below is a controller that would initiate the factories before either view is processed:
viewController.groovyToggle Line Numbers |
1
class AppController { 2
3
static name = "appController" 4
static scope = "session" 5
6
def countryList = ["Country 1", "Country 2", "Country 3"] 7
8
static void initialize (binding) { 9
binding.Factory("someBean") { [:] } 10
binding.SelectItems("countryList") { countryList } 11
} 12
13
}
|
Since gracelet controllers are loaded right after libraries, the are sure to run before any view.
When you provide a static method called initialize() that takes one parameter it will be executed
when the controller manager has been created, that is, once per application lifecycle (or when a controller
is hot reloaded).
Here this controller is called "appController" and it initializes both factories for the view list and
bean. A side benefit with this being a groovy class is that there is also now a getter called getCountryList() and you
can access that by simply calling the following whereever in your views (or even other controllers):
Controlling navigation
Since the 2.0.0 Beta 6 release, controllers can now get involved with navigation page flow. At the moment it is a basic logic
closure or a logic map. The logic will be called when matching URL's are referenced. The callbacks will be invoked when a page is about to
be rendered (RENDER_RESPONSE phase) and also when an action component has been invoked (INVOKE_APPLICATION phase).
You can add a static field called 'flowControl' and assign it a map. The map must consist of at least 2 keys. One is called 'for' and
it should be either a String or a regular expression Pattern object. The 'for' determines for which URL's/views the controller flow logic
is called for. The second required map key is 'logic'. The 'logic' key can be either a Closure or a Map. If it is a closure, the closure
will be called and passed two optional arguments, the FacesContext and an object called the decision context. You can use it to see
what the outcome, the current url, the current view id, the next view id (see Gracelet navigation) and use that information to decide whether or not to change
the view id to actually be rendered. This does not include the default implementation, which means that if the Gracelet navigation system
does not know which view to go to and the closure logic does not either then it will pass control onto the default navigation system
such as the default JSF faces-config.xml navigation based definition.
You can also provide a logic map instead of a logic closure. The format of this map must be
[flow1: "viewId.groovy", flow2: [start: "viewId2.groovy", outcome1: "viewId3.groovy", outcome2: "viewId4" ...] ...]
.
Each key is an outcome, including the root keys, which are special because they are the beginning of a particular flow of pages.
The value can either be a string view id, a closure that returns a view id, for simple outcome <-> view id mapping, or a sub map,
which allows you to have a sub flow of pages. When using logic map, you need to provide a value binding that will be used to
hold a collection of view id's and outcomes for automating the process, using the 'state' key of the primary map.
A few examples will help to clarify how this is accomplished:
ClosureFlowController.groovyToggle Line Numbers |
1
2
3
4
class ClosureFlowController { 5
6
static def name = "flowControl1" 7
8
9
10
11
12
13
static def flowControl = [for: "/restricted/", logic: { ctx, dctx -> 14
if (!flowControl1.authenticated) return "login.groovy" 15
return dctx.nextViewId 16
}] 17
18
boolean authenticated 19
20
void authenticate (user, pass) { 21
22
authenticated = true 23
} 24
25
}
|
MapFlowController.groovyToggle Line Numbers |
1
2
3
4
class MapFlowController { 5
6
static def name = "flowControl2" 7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static def flowControl = [for: "/signup/", state: { flowControl2.navState }, logic: [ 27
cancel: { flowControl2.cancel(); "cancel.groovy" }, 28
signup: [start: "intro.groovy", address: "address.groovy", userInfo: "user.groovy", finish: "accountCreated.groovy"] 29
]] 30
31
32
33
34
def navState = [] 35
36
void cancel () { 37
38
} 39
40
}
|