Wiki source code of Your First Stateful Project

Version 14.1 by Pascal Robert on 2012/12/12 07:57

Show last authors
1 So far, we have seen two of the technologies, D2W and ERRest, that Project Wonder offers for viewing and managing the data. In this tutorial, we will show how to do it with the "stateful" way of doing things. Stateful have been around since the beginning of WebObjects in 1996, so it's the oldest way of presenting data and constructing pages.
2
3 Stateful means that you don't have to worry about creating sessions and keeping track of data coming from HTML input fields and controls. In fact, D2W is also stateful.
4
5 In this tutorial, we are also going to use the Ajax framework, who is stateful too. We will replicate the functionalities of the two other tutorials, but by creating pages ourselves. The application will have the following pages:
6
7 * The main page will display a list of blog entries, with a link to see the blog entry.
8 * The main page will have a link to an "admin" page that will show a login form.
9 * After login, a list of blog entries with links to edit, delete and create blog entries will be show.
10 * We need a form to edit/create blog entries.
11
12 Let's start by creating a new project in Eclipse. You need to create a **Wonder Application** project type, and name it **StatefulBlog**.
13
14 [[image:Capture d’écran 2012-08-06 à 04.56.13.png||border="1"]]
15
16 Just like the D2W tutorial, you need to link the application with the **BlogCommon**, **Ajax** and **H2PlugIn** frameworks. To do so, right-click on **StatefulBlog** and select **Build Path** > **Configure Build Path**. 
17
18 [[image:Capture d’écran 2012-07-29 à 14.25.46.png||border="1"]]
19
20 In the **Libraries** tab, click on **Add Library**. Select **WebObjects Frameworks** and click **Next**. Check **Ajax**, **BlogCommon** and **H2PlugIn** from the list and click **Finish**. The **Libraries** tab should look like this:
21
22 [[image:Capture d’écran 2012-08-06 à 05.15.32.png||border="1"]]
23
24 We are ready to code Open the **Components** folder of the project, and open **Main WO**. In the **Related** view (bottom-right), you see that all related files of the component are listed, and we need to open the Java code associated with the component. To do so, in the **Related** view, double-click on **Main.java** to open the Java class into an editor.
25
26 In **Main.java**, we need some Java code to get the list of blog entries so that we can show that list into the component. The following code will do what we need:
27
28 {{code}}
29
30 import your.app.model.BlogEntry;
31
32 import com.webobjects.appserver.WOContext;
33 import com.webobjects.eoaccess.EODatabaseDataSource;
34 import com.webobjects.eocontrol.EOEditingContext;
35
36 import er.extensions.batching.ERXBatchingDisplayGroup;
37 import er.extensions.components.ERXComponent;
38 import er.extensions.eof.ERXEC;
39
40 public class Main extends ERXComponent {
41
42 private EOEditingContext _ec;
43 private BlogEntry _blogEntryItem;
44
45 public Main(WOContext context) {
46 super(context);
47 EODatabaseDataSource dataSource = new EODatabaseDataSource(editingContext(), BlogEntry.ENTITY_NAME);
48 ERXBatchingDisplayGroup<BlogEntry> dg = new ERXBatchingDisplayGroup<BlogEntry>();
49 dg.setNumberOfObjectsPerBatch(20);
50 dg.setDataSource(dataSource);
51 dg.setObjectArray(BlogEntry.fetchAllBlogEntries(editingContext(), BlogEntry.LAST_MODIFIED.descs()));
52 }
53
54 private EOEditingContext editingContext() {
55 if (_ec == null) {
56 _ec = ERXEC.newEditingContext();
57 }
58 return _ec;
59 }
60
61 public void setBlogEntryItem(BlogEntry blogEntryItem) {
62 this._blogEntryItem = blogEntryItem;
63 }
64
65 public BlogEntry blogEntryItem() {
66 return this._blogEntryItem;
67 }
68
69 }
70
71 {{/code}}
72
73 [[ERXBatchingDisplayGroup>>http://jenkins.wocommunity.org/job/Wonder/lastSuccessfulBuild/javadoc/er/extensions/appserver/ERXDisplayGroup.html]] is a subclass of WODisplayGroup, a utility that adds multiple actions and logic to a list of objects. One of the best features of ERXBatchingDisplayGroup is that it does real batching (if the RDBMS that you use supports it), so that means that if we specify a batch of 20 objects (//dg.setNumberOfObjectsPerBatch(20)//), it will fetch only the first 20 objects from the database, and if you switch to the next batch, the display group will go to the database to get the next 20 objects. ERXBatchingDisplayGroup is useful if you known that your list will contains hundred of objects.
74
75 Let's edit the component. Open **Main.wo** and edit the content in the top panel to be:
76
77 {{code}}
78
79 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
80 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
81 <head>
82 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
83 <title>untitled</title>
84 </head>
85 <body>
86 <h1>Our super blog</h1>
87 <wo:if condition="$displayGroup.hasMultipleBatches">
88 <div>
89 <wo:link action="$displayGroup.displayPreviousBatch">Previous</wo:link>
90 | Batch
91 <wo:str value="$displayGroup.currentBatchIndex" />
92 of
93 <wo:str value="$displayGroup.batchCount" />
94 |
95 <wo:link action="$displayGroup.displayNextBatch">Next</wo:link>
96 </div>
97 </wo:if>
98 <wo:loop list="$displayGroup.displayedObjects" item="$blogEntryItem">
99 <p><wo:str value="$blogEntryItem.title" /></p>
100 <p><wo:str value="$blogEntryItem.content" escapeHTML="false" /></p>
101 <p>Created by
102 <wo:str value="$blogEntryItem.author.fullName" />
103 on
104 <wo:str value="$blogEntryItem.creationDate" />
105 </p>
106 <hr />
107 </wo:loop>
108 </body>
109 </html>
110
111 {{/code}}
112
113 The condition //displayGroup.hasMultipleBatches// checks if we have more than 20 objects. If that's true, we will show a HTML div with links to navigate between batches. After that condition, we loop over the displayedObjects (the current batch of objects) and we use WOString elements to display details about each blog entry.
114
115 You can run the application to check if everything is working. If everything is ok, terminate the app.
116
117 The next steps is to build the administration part of the application. The first thing we need to do is to add a variable and two methods in **Session.java** that will store the logged user. Open **Session.java** and add the following code:
118
119 {{code}}
120
121 private Author _loggedAuthor;
122
123 public Session() {
124 this._loggedAuthor = null;
125 }
126
127 public Author loggedAuthor() {
128 return this._loggedAuthor;
129 }
130
131 public void setAuthor(Author loggedAuthor) {
132 this._loggedAuthor = loggedAuthor;
133 }
134
135 {{/code}}
136
137 Save the file. Now open Author.java and add the following method:
138
139 {{code}}
140 public static Author validateLogin(EOEditingContext editingContext, String _emailAddress) {
141 // TODO Auto-generated method stub
142 return null;
143 }
144 {{/code}}
145
146 Next: we need to add a component to present the login form to the user. Right-click on the **Components** folder in the project, and select **New** > **WOComponent**. Change the name of the component to be **AdminMainPage** and change the superclass to **er.extensions.components.ERXComponent**.
147
148 After the component have been created, open **AdminMainPage.java** and override the content of the class with the following code:
149
150 {{code}}
151
152 package your.app.components;
153
154 import your.app.Session;
155 import your.app.model.Author;
156 import your.app.model.BlogEntry;
157
158 import com.webobjects.appserver.WOActionResults;
159 import com.webobjects.appserver.WOContext;
160 import com.webobjects.eoaccess.EODatabaseDataSource;
161 import com.webobjects.eocontrol.EOEditingContext;
162
163 import er.extensions.batching.ERXBatchingDisplayGroup;
164 import er.extensions.components.ERXComponent;
165 import er.extensions.eof.ERXEC;
166
167 public class AdminMainPage extends ERXComponent {
168
169 private ERXBatchingDisplayGroup<BlogEntry> _dg;
170
171 public AdminMainPage(WOContext context) {
172 super(context);
173 EODatabaseDataSource dataSource = new EODatabaseDataSource(editingContext(), BlogEntry.ENTITY_NAME);
174 _dg = new ERXBatchingDisplayGroup<BlogEntry>();
175 _dg.setNumberOfObjectsPerBatch(20);
176 _dg.setDataSource(dataSource);
177 _dg.setObjectArray(BlogEntry.fetchBlogEntries(editingContext(), BlogEntry.AUTHOR.eq(session().loggedAuthor()), BlogEntry.LAST_MODIFIED.descs()));
178 }
179
180 public ERXBatchingDisplayGroup<BlogEntry> displayGroup() {
181 return this._dg;
182 }
183
184 private String _emailAddress;
185
186 public String emailAddress() {
187 return this._emailAddress;
188 }
189
190 public void setEmailAddress(String emailAddress) {
191 this._emailAddress = emailAddress;
192 }
193
194 private BlogEntry _blogEntryItem;
195
196 public void setBlogEntryItem(BlogEntry blogEntryItem) {
197 this._blogEntryItem = blogEntryItem;
198 }
199
200 public BlogEntry blogEntryItem() {
201 return this._blogEntryItem;
202 }
203
204 @Override
205 public Session session() {
206 return ((Session)super.session());
207 }
208
209 public boolean isLogged() {
210 return ((session()).loggedAuthor() == null) ? false: true;
211 }
212
213 private EOEditingContext _ec;
214
215 public EOEditingContext editingContext() {
216 if (_ec == null) {
217 _ec = ERXEC.newEditingContext();
218 }
219 return _ec;
220 }
221
222 private String _errorMessage = null;
223
224 public String errorMessage() {
225 return this._errorMessage;
226 }
227
228 public WOActionResults login() {
229 Author loggedAuthor = Author.validateLogin(editingContext(), _emailAddress);
230 if (loggedAuthor != null) {
231 session().setAuthor(loggedAuthor);
232 } else {
233 _errorMessage = "Invalid email address";
234 }
235 return null;
236 }
237 }
238
239 {{/code}}
240
241 You will notice that we are using a ERXBatchingDisplayGroup again. But this time, when we call dg.setObjectArray//, we set the array of objects so that only the blog entries created by the logged author are displayed.//
242
243 Open **AdminMainPage.wo** and override all the content between the <body> tag to be:
244
245 {{code}}
246
247 <wo:AjaxUpdateContainer id="main">
248 <wo:if condition="$isLogged">
249 <wo:if condition="$displayGroup.hasMultipleBatches">
250 <div>
251 <wo:link action="$displayGroup.displayPreviousBatch">Previous</wo:link>
252 | Batch
253 <wo:str value="$displayGroup.currentBatchIndex" />
254 of
255 <wo:str value="$displayGroup.batchCount" />
256 |
257 <wo:link action="$displayGroup.displayNextBatch">Next</wo:link>
258 </div>
259 </wo:if>
260 <table>
261 <tr>
262 <th><wo:WOSortOrder displayGroup="$displayGroup" key="title" /> Title</th>
263 <th>Author</th>
264 <th><wo:WOSortOrder displayGroup="$displayGroup" key="creationDate" /> Created on</th>
265 <th><wo:WOSortOrder displayGroup="$displayGroup" key="lastModified" /> Last modified</th>
266 </tr>
267 <wo:loop list="$displayGroup.displayedObjects" item="$blogEntryItem">
268 <tr>
269 <td>
270 <wo:str value="$blogEntryItem.title" />
271 </td>
272 <td> <wo:str value="$blogEntryItem.author.fullName" /> </td>
273 <td> <wo:str value="$blogEntryItem.creationDate" dateformat="%Y/%m/%d" /> </td>
274 <td> <wo:str value="$blogEntryItem.lastModified" dateformat="%Y/%m/%d" /> </td>
275 </tr>
276 </wo:loop>
277 </table>
278 </wo:if>
279 <wo:else>
280 <wo:if condition="$errorMessage">
281 <span style="text-color: red;">Error: <wo:str value="$errorMessage" /></span>
282 </wo:if>
283 <wo:form>
284 <div>
285 <label>Email address:</label>
286 <wo:textField value="$emailAddress" />
287 </div>
288 <div><wo:AjaxSubmitButton updateContainerID="main" action="$login" value="Login" /></div>
289 </wo:form>
290 </wo:else>
291 </wo:AjaxUpdateContainer>
292
293 {{/code}}
294
295 Last step: we need a link to the admin page. Open **Main.wo** and just before the </body> tag, add the following:
296
297 {{code}}
298
299 <wo:link pageName="AdminMainPage">Admin</wo:link>
300
301 {{/code}}
302
303 Save everything, run the app and try to login. If login is not successful, you will get an error message. If login is valid, you will see the blog entries that you created.
304
305 For the last part of this tutorial, we are going to add a link on each blog entry in the list that will bring us to a edit page where we can modify a blog entry. We are also going to add a link to create a new blog entry.
306
307 Create a new component, and name it **EditBlogEntry**. Open **EditBlogEntry.java** and override the code with:
308
309 {{code}}
310
311 package your.app.components;
312
313 import your.app.Session;
314 import your.app.model.Author;
315 import your.app.model.BlogEntry;
316
317 import com.webobjects.appserver.WOActionResults;
318 import com.webobjects.appserver.WOContext;
319 import com.webobjects.eocontrol.EOEditingContext;
320
321 import er.extensions.components.ERXComponent;
322 import er.extensions.eof.ERXEC;
323 import er.extensions.eof.ERXEOControlUtilities;
324
325 public class EditBlogEntry extends ERXComponent {
326
327 public EditBlogEntry(WOContext context) {
328 super(context);
329 }
330
331 private BlogEntry _blogEntry;
332
333 public BlogEntry blogEntry() {
334 return this._blogEntry;
335 }
336
337 public void setBlogEntry(BlogEntry blogEntry) {
338 if (blogEntry == null) {
339 this._blogEntry = ERXEOControlUtilities.createAndInsertObject(editingContext(), BlogEntry.class);
340 Author localUser = ERXEOControlUtilities.localInstanceOfObject(editingContext(), session().loggedAuthor());
341 this._blogEntry.setAuthorRelationship(localUser);
342 } else {
343 this._blogEntry = ERXEOControlUtilities.localInstanceOfObject(editingContext(), blogEntry);
344 }
345 }
346
347 private EOEditingContext _ec;
348
349 public EOEditingContext editingContext() {
350 if (_ec == null) {
351 _ec = ERXEC.newEditingContext();
352 }
353 return _ec;
354 }
355
356 @Override
357 public Session session() {
358 return ((Session)super.session());
359 }
360
361 public WOActionResults save() {
362 editingContext().saveChanges();
363 return pageWithName(AdminMainPage.class);
364 }
365 }
366
367 {{/code}}
368
369 Open **EditBlogEntry.wo** and between the <body> tag, add the following:
370
371 {{code}}
372
373 <wo:form>
374 <div>
375 <label>Title:</label>
376 <wo:textfield value="$blogEntry.title" />
377 </div>
378 <div>
379 <label>Content:</label>
380 <wo:text value="$blogEntry.content" rows="20" cols="80" />
381 </div>
382 <div>Author: <wo:str value="$session.loggedAuthor.fullName" /></div>
383 <div><wo:submitButton action="$save" value="Save changes" /></div>
384 </wo:form>
385
386 {{/code}}
387
388 We now have a form to edit or create a blog entry. Save the component and the Java class, and open **AdminMainPage.java** to add the following code:
389
390 {{code}}
391
392 public WOActionResults editBlogEntry() {
393 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
394 nextPage.setBlogEntry(_blogEntryItem);
395 return nextPage;
396 }
397
398 public WOActionResults createBlogEntry() {
399 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
400 nextPage.setBlogEntry(null);
401 return nextPage;
402 }
403
404 {{/code}}
405
406 Open **AdminMainPage.wo** and just after <wo:if condition="$isLogged">, add the following line:
407
408 {{code}}
409
410 <div><wo:link action="$createBlogEntry">Create a new blog entry</wo:link></div>
411
412 {{/code}}
413
414 Find this line:
415
416 {{code}}
417
418 <wo:str value="$blogEntryItem.title" />
419
420 {{/code}}
421
422 and replace it with:
423
424 {{code}}
425
426 <wo:link action="$editBlogEntry"><wo:str value="$blogEntryItem.title" /></wo:link>
427
428 {{/code}}
429
430 Save everything, run the app, click on the "admin" link, login and check if you can create or edit a blog entry. Everything should be working, and just created your first stateful Project Wonder application [[It's time to deploy an application>>Your First Deployment]].