Wiki source code of D2W Flow Control

Last modified by David Holt on 2012/09/20 11:23

Show last authors
1 == Related articles ==
2
3 [[What is Direct to Web?>>doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).What is Direct to Web?.WebHome]]
4 [[doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).The D2W Rule System.WebHome]]
5
6 = D2W Flow Control =
7
8 by {{mention reference="XWiki.ramsey" style="FULL_NAME" anchor="XWiki-ramsey-haMlO"/}}
9
10 Setting up a D2W app is almost too easy. You only need to build a data model and you get an instant application. You don't have to spend any time thinking about creating lots of page components, setting up proper input validation in your view, making sure everything is localizable, ... you get all that for free. A lot of really tedious work is simply done already. It is very liberating.
11
12 Once you are unshackled by D2W, you immediately want to modify it to fit your needs. To customize the structure of your app and the components used, you only need to learn your way around the rule system. With the rule system, you can switch out practically anything with a few rule changes. Understanding the rule system and the D2WContext leads you to creating custom components to handle tasks and object properties in new ways.
13
14 It is here where you must be very careful. Good D2W components are object neutral. They don't care what EO you stick in them, they just work. If you don't understand D2W flow control, you might end up coding a lot of specialized components to do the job. If you find you are writing a component for a specific entity or set of entities and it won't work with just any entities, it's possible you may be sticking control code, model code, or both in your view layer. WebObjects is designed to have a clean separation between Model, View, and Controller: MVC. If you do mix control code in your View or Model layer, you will only waste a lot of your own time by making your app more difficult to maintain.
15
16 == Learn to wield D2W. ==
17
18 If you are just starting out with D2W and you don't understand flow control, you may feel like you are stuck on rails. You go wherever the D2W train takes you. Understanding how flow control works in a D2W app is the key to taking the reigns and steering the application in any direction you would like to go. At the most basic level, D2W flow is very simple. D2W pages have a binding called nextPage and nextPageDelegate. These two simple bindings are the entry point to the controller code you use to direct the flow of a D2W app. What generally happens in well behaved D2W view components is that the nextPageDelegate is checked first. If the delegate exists, nextPage() is called on it. If the delegate does not exist, the component falls back to nextPage() on the D2WPage component. If you are not using Wonder, you can stop reading here. Sorry, that's all you get. The rest of this article will be discussing some of the custom flow control concepts found in Project Wonder's ERDirectToWeb framework.
19
20 ==== ERDActionButton subclasses ====
21
22 The first group of flow control components I'd like to mention are simply button components that perform specific tasks. Edit, delete, inspect, select, ... these tasks, common to all enterprise objects, can be accomplished with a number of components that subclass ERDActionButton. You can drop one into your page using using the rule system. They are typically used in conjunction with the actions RHS key, but can potentially be used in displayPropertyKeys repetitions as well. By default they are loaded with ERDDefaultActionAssignment.
23
24 === Control Delegates ===
25
26 ==== ERDQueryDataSourceDelegateInterface ====
27
28 ERDQueryDataSourceDelegateInterface isn't a next page delegate. What it allows you to do is manipulate the datasource generated by an ERD2WQueryPage. If you need to apply custom control logic to the results generated by your query pages, this is the place to do it. For example, if you wanted to replace the default AND qualifier used by the query page with an OR qualifier, you could do something like this:
29
30 {{code language="none"}}
31 100 : entity.name = 'MyEntity' => queryDataSourceDelegate = com.example.delegates.MyQueryDataSourceDelegate [WO:ERDDelayedObjectCreationAssignment]
32 {{/code}}
33
34 {{noformat}}
35
36 package com.example.delegates;
37
38 import com.webobjects.eoaccess.EODatabaseDataSource;
39 import com.webobjects.eocontrol.EODataSource;
40 import com.webobjects.eocontrol.EOFetchSpecification;
41 import com.webobjects.eocontrol.EOQualifier;
42 import com.webobjects.foundation.NSArray;
43
44 import er.directtoweb.delegates.ERDQueryDataSourceDelegateInterface;
45 import er.directtoweb.pages.ERD2WQueryPage;
46
47 public class MyQueryDataSourceDelegate implements ERDQueryDataSourceDelegateInterface {
48 public EODataSource queryDataSource(ERD2WQueryPage sender) {
49 EODataSource ds = sender.dataSource();
50 if (ds == null || !(ds instanceof EODatabaseDataSource)) {
51 ds = new EODatabaseDataSource(sender.session().defaultEditingContext(), sender.entity().name());
52 sender.setDataSource(ds);
53 }
54
55 EOFetchSpecification fs = ((EODatabaseDataSource) ds).fetchSpecification();
56 fs.setQualifier(qualifierFromSender(sender));
57 fs.setIsDeep(sender.isDeep());
58 fs.setUsesDistinct(sender.usesDistinct());
59 fs.setRefreshesRefetchedObjects(sender.refreshRefetchedObjects());
60
61 int limit = sender.fetchLimit();
62 if (limit != 0) {
63 fs.setFetchLimit(limit);
64 }
65 NSArray prefetchingRelationshipKeyPaths = sender.prefetchingRelationshipKeyPaths();
66 if (prefetchingRelationshipKeyPaths != null && prefetchingRelationshipKeyPaths.count() > 0) {
67 fs.setPrefetchingRelationshipKeyPaths(prefetchingRelationshipKeyPaths);
68 }
69 return ds;
70 }
71
72 private EOQualifier qualifierFromSender(ERD2WQueryPage sender) {
73 EOQualifier q = sender.qualifier();
74 // q = ... create your OR qualifier here
75 return q;
76 }
77 }
78
79 {{/noformat}}
80
81 === NextPageDelegate ===
82
83 ==== ERDPageNameDelegate ====
84
85 The ERDPageNameDelegate is an extremely simple next page delegate, so I'll mention it briefly. To use it, just hand it a page name in its constructor and it will return that component by invoking pageWithName on the sender WOComponent. That's it. Hand it to a D2WPage's setNextPageDelegate method and you're done.
86
87 {{noformat}}
88
89 D2WPage page = ...;
90 ERDPageNameDelegate delegate = new ERDPageNameDelegate("SomePageName");
91 page. setNextPageDelegate(delegate);
92
93 {{/noformat}}
94
95 ==== ERDBranchDelegate & ERDControllerButton ====
96
97 There are several other NextPageDelegates in Wonder, but I'm only going to mention one more: My personal favorite. ERDBranchDelegate is an extremely useful next page delegate. It is also a fairly well documented delegate... if you know where to look It's located in the documentation for [[ERD2WPage's pageController() method>>url:http://wocommunity.org/documents/javadoc/wonder/latest/er/directtoweb/pages/ERD2WPage.html#pageController()||shape="rect"]] (bit more here: [[Page Controller in ERD2W>>url:http://osdir.com/ml/web.webobjects.wonder-disc/2006-05/msg00041.html||shape="rect"]]).
98
99 What this documentation is telling you is that in your 'WebSite' entity's list page, you will see an edit button and a cogwheel button in each row of your list table. The cogwheel button is the ERDControllerButton, a special subclass of ERDActionButton discussed earlier. It will produce a menu connected to your control methods. ERDBranchDelegate finds its control methods automatically via reflection. The control methods match a signature of
100
101 {{noformat}}
102
103 public WOComponent <method-name>(WOComponent sender) {
104 //control code goes here
105 }
106
107 {{/noformat}}
108
109 The menu items, by default, will be named "Copy Web Site" and "Delete Web Site" and they will execute the control code found in copyWebSite(WOComponent sender) and deleteWebSite(WOComponent sender), respectively.
110
111 A picture is worth a thousand words though... This is a screenshot of the ERDControllerButton in use.
112
113 [[image:attach:ERDControllerButton.png]]
114
115 In this picture, the entity is named Child and I'm using an inspectAction instead of an editAction. Clearly, the css there needs a little work as well, but ... you get the idea.
116
117 If your application uses ERModernLook, the controllerButton is hidden in the style sheet. To display it you should add a stylesheet to your application using [[this method (point 4)>>doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).ERModernLook.WebHome]], in which you specify how you want to display your controller button. For exemple :
118
119 {{code language="css"}}
120
121 span.ERDControllerButton {
122     display: block;
123 }
124
125 {{/code}}
126
127 Then there are three ways to display the ERDControllerButton. The first one is the default one, called "flyOver", which displays as a single button which contains your controller methods as links. The second one is "buttonList", and the third one is "linkList" (these are quite explicits).
128 You can switch between these configurations with the controllerButtonUIStyle rule. If you want the buttonList :
129
130 **true**{{code language="none"}}100 : => controllerButtonUIStyle = "buttonList" [WO:Assignment]{{/code}}
131
132 What this documentation **doesn't** tell you is that the ERDBranchDelegate is even more powerful than that. For one, you can easily customize/localize those menu item strings. For instance, your Spanish Localizable.strings file might contain
133
134 "Button.copyWebSite" = "Copie el Web site";
135 "Button.deleteWebSite" = "Web site de la cancelación";
136
137 Also, you can use Java annotations to define which control methods are available based on scope, task, and pageConfiguration.
138
139 {{noformat}}
140
141 @D2WDelegate (
142 scope = "object", availableTasks = "inspect list", availablePages = "ListWebSite InspectWebSite"
143 )
144 public WOComponent copyWebSite(WOComponent sender) {
145 setSender(sender);
146 WOComponent result = null;
147 return result;
148 }
149
150 {{/noformat}}
151
152 The values for scope can be either object or selection, availableTasks values are your d2wContext().task() keys, and availablePages values are equivalent to the page configuration names. What do these annotations mean? They define when the control method is available. For instance, in the above example, the "object" scope means the control method is only available at the object level. If you put it into a list page, then it will be available in the list repetition of objects, but not in a control button above or below the list. The other annotations further restrict the method to the specified tasks and page configurations.
153
154 Using this technique, you could conceivably define the branching control code for an entire application in a single file! As {{mention reference="XWiki.anjo" style="FULL_NAME" anchor="XWiki-anjo-oQEKm"/}} has pointed out on the list, using annotations means that you're compiling something that really belongs in the rule system. Of course, he's right... So how do you use the rule system to define your branch choices? That's easy enough. Just use a rule like
155
156 {{code language="none"}}
157 100 : (task = 'list' and pageConfiguration = 'ListWebSite') => branchChoices = ("copyWebSite","deleteWebSite") [WO:Assignment]
158 {{/code}}
159
160 And you will get the exact same effect, but you'll get the full power and flexibility of the rule system instead. ERDBranchDelegate is an extremely clever solution
161
162 ==== ERDActionBar ====
163
164 Finally, I'd like to mention the ERDActionBar. Much like the ERDControllerButton, ERDActionBar also makes use of the ERDBranchDelegateInterface to provide branching control. However, with this component, instead of providing a single menu style selection, you get a repetition of buttons. Another big difference is that ERDActionBar looks for a branch delegate on the nextPageDelegate instead of looking at the pageController RHS key.
165
166 {{code language="none"}}
167 100 : (entity.name = 'WebSite' and task = 'list') => nextPageDelegate = your.app.ListWebSiteController [WO:ERDDelayedObjectCreationAssignment]
168 {{/code}}
169
170 Also worth mentioning, the ERDControllerButton uses the pageController from the innermost ERD2WPage with a pageController, whereas the ERDActionBar goes uses the outermost NextPageDelegate that is a branch delegate. Otherwise, the two components are pretty much the same. They fire methods and localize button names identically.
171
172 One tricky part to using an ERDActionBar is that the keys you use to insert one into a page are not very consistent. For instance, with the ERD2WListPageTemplate, it's embedded in the page. There's no way to turn it off besides having no branch delegate in your page's nextPageDelegate(). With ERDQueryPageTemplate, it's inserted with the 'actionBarComponentName' RHS key. With any of the ERNeutral components, you'll need to use 'belowDisplayPropertyKeys' or 'aboveDisplayPropertyKeys' in combination with a rule for an action propertyKey and a third rule for a component name for that action propertyKey. Similar key inconsistencies exists for the ERDControllerButton... So if you're having trouble figuring out how to insert one of these components, you might just want to dig around in the page components to look for the hooks.