Reference Manual for Gracelets - 2.0.0.RC2SourceForge.net Logo
2.13 - Tutorial : Using Controllers
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.groovy
Toggle 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.groovy
Toggle 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.groovy
Toggle 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):

Toggle Line Numbers
1 appController.countryList 

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.groovy
Toggle Line Numbers
1 /** 
2  * This is a flow controller demonstrating the use of a logic closure 
3  */ 
4 class ClosureFlowController { 
5      
6     static def name = "flowControl1" 
7      
8     /** 
9      * Here we check to see if the authenticated flag of this same controller is true 
10      * otherwise we force the login page no matter what. If the flag is true then we  
11      * pass on the view that was going to be rendered. 
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.groovy
Toggle Line Numbers
1 /** 
2  * This is a flow controller demonstrating flow control using a logic map. 
3  */ 
4 class MapFlowController { 
5      
6     static def name = "flowControl2" 
7      
8     /** 
9      * Below we define a flow control setup saying that we want to get involved with any 
10      * page rendering and command invocation for JSF views that start with /signup/.  
11      *  
12      * Then we say that the state for what page we are on will be held in this same controller using 
13      * the navState field. 
14      *  
15      * Finally we define the logic map. It defines a simple mapping saying that the 'cancel' outcome 
16      * should execute this closure which calls the cancel method, then go to the cancel.groovy (which is  
17      * resolved to /signup/cancel.groovy) view. 
18      *  
19      * Then we define the signup flow, which means that when the signup outcome is returned it should begin 
20      * a new flow of pages, starting with /signup/intro.groovy. Note that 'start' is a required outcome for 
21      * sub maps and is used to begin the sub flow. Then we say that in this sub flow, if the 'address' outcome 
22      * is returned then we should go to /signup/address.groovy. And so on and so forth. If at anytime in this 
23      * sub flow, the 'cancel' outcome is called then we will call the cancel method and the proceed to the /signup/cancel.groovy 
24      * view.  
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      * This is referred to in the state binding for navigation 
33      */ 
34     def navState = [] 
35      
36     void cancel () { 
37          // cancel code goes here 
38      } 
39      
40 }