Wiki source code of Your First Stateful Project

Version 12.1 by Pascal Robert on 2012/08/09 04:59

Hide last authors
Pascal Robert 3.1 1 {{info}}
2 Work in progress
3 {{/info}}
4
5 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.
6
7 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.
8
9 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:
10
11 * The main page will display a list of blog entries, with a link to see the blog entry.
12 * The main page will have a link to an "admin" page that will show a login form.
13 * After login, a list of blog entries with links to edit, delete and create blog entries will be show.
Pascal Robert 5.1 14 * We need a form to edit/create blog entries.
Pascal Robert 3.1 15
16 Let's start by creating a new project in Eclipse. You need to create a **Wonder Application** project type, and name it **StatefulBlog**.
17
18 [[image:Capture d’écran 2012-08-06 à 04.56.13.png||border="1"]]
Pascal Robert 5.1 19
Pascal Robert 8.1 20 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**. 
Pascal Robert 5.1 21
22 [[image:Capture d’écran 2012-07-29 à 14.25.46.png||border="1"]]
23
Pascal Robert 8.1 24 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:
Pascal Robert 5.1 25
Pascal Robert 8.1 26 [[image:Capture d’écran 2012-08-06 à 05.15.32.png||border="1"]]
27
Pascal Robert 11.1 28 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.
Pascal Robert 8.1 29
30 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:
31
32 {{code}}
33
Pascal Robert 11.1 34 import your.app.model.BlogEntry;
35
Pascal Robert 8.1 36 import com.webobjects.appserver.WOContext;
37 import com.webobjects.eoaccess.EODatabaseDataSource;
38 import com.webobjects.eocontrol.EOEditingContext;
39
40 import er.extensions.batching.ERXBatchingDisplayGroup;
41 import er.extensions.components.ERXComponent;
42 import er.extensions.eof.ERXEC;
43
44 public class Main extends ERXComponent {
Pascal Robert 11.1 45
46 private EOEditingContext _ec;
47 private BlogEntry _blogEntryItem;
48
Pascal Robert 8.1 49 public Main(WOContext context) {
50 super(context);
51 EODatabaseDataSource dataSource = new EODatabaseDataSource(editingContext(), BlogEntry.ENTITY_NAME);
52 ERXBatchingDisplayGroup<BlogEntry> dg = new ERXBatchingDisplayGroup<BlogEntry>();
53 dg.setNumberOfObjectsPerBatch(20);
54 dg.setDataSource(dataSource);
55 dg.setObjectArray(BlogEntry.fetchAllBlogEntries(editingContext(), BlogEntry.LAST_MODIFIED.descs()));
56 }
57
58 private EOEditingContext editingContext() {
59 if (_ec == null) {
60 _ec = ERXEC.newEditingContext();
61 }
62 return _ec;
63 }
64
Pascal Robert 11.1 65 public void setBlogEntryItem(BlogEntry blogEntryItem) {
66 this._blogEntryItem = blogEntryItem;
67 }
68
69 public BlogEntry blogEntryItem() {
70 return this._blogEntryItem;
71 }
72
Pascal Robert 8.1 73 }
74
75 {{/code}}
Pascal Robert 11.1 76
77 [[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.
78
79 Let's edit the component. Open **Main.wo** and edit the content in the top panel to be:
80
81 {{code}}
82
83 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
84 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
85 <head>
86 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
87 <title>untitled</title>
88 </head>
89 <body>
90 <h1>Our super blog</h1>
91 <wo:if condition="$displayGroup.hasMultipleBatches">
92 <div>
93 <wo:link action="$displayGroup.displayPreviousBatch">Previous</wo:link>
94 | Batch
95 <wo:str value="$displayGroup.currentBatchIndex" />
96 of
97 <wo:str value="$displayGroup.batchCount" />
98 |
99 <wo:link action="$displayGroup.displayNextBatch">Next</wo:link>
100 </div>
101 </wo:if>
102 <wo:loop list="$displayGroup.displayedObjects" item="$blogEntryItem">
103 <p><wo:str value="$blogEntryItem.title" /></p>
104 <p><wo:str value="$blogEntryItem.content" escapeHTML="false" /></p>
105 <p>Created by
106 <wo:str value="$blogEntryItem.author.fullName" />
107 on
108 <wo:str value="$blogEntryItem.creationDate" />
109 </p>
110 <hr />
111 </wo:loop>
112 </body>
113 </html>
114
115 {{/code}}
116
117 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.
118
119 You can run the application to check if everything is working. If everything is ok, terminate the app.
120
121 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:
122
123 {{code}}
124
125 private Author _loggedAuthor;
126
127 public Session() {
128 this._loggedAuthor = null;
129 }
130
131 public Author loggedAuthor() {
132 return this._loggedAuthor;
133 }
134
135 public void setAuthor(Author loggedAuthor) {
136 this._loggedAuthor = loggedAuthor;
137 }
138
139 {{/code}}
140
141 Save the file. 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**.
142
143 After the component have been created, open **AdminMainPage.java** and override the content of the class with the following code:
144
145 {{code}}
146
147 package your.app.components;
148
149 import your.app.Session;
150 import your.app.model.Author;
151 import your.app.model.BlogEntry;
152
153 import com.webobjects.appserver.WOActionResults;
154 import com.webobjects.appserver.WOContext;
155 import com.webobjects.eoaccess.EODatabaseDataSource;
156 import com.webobjects.eocontrol.EOEditingContext;
157
158 import er.extensions.batching.ERXBatchingDisplayGroup;
159 import er.extensions.components.ERXComponent;
160 import er.extensions.eof.ERXEC;
161
162 public class AdminMainPage extends ERXComponent {
Pascal Robert 12.1 163
Pascal Robert 11.1 164 private ERXBatchingDisplayGroup<BlogEntry> _dg;
165
166 public AdminMainPage(WOContext context) {
167 super(context);
168 EODatabaseDataSource dataSource = new EODatabaseDataSource(editingContext(), BlogEntry.ENTITY_NAME);
169 _dg = new ERXBatchingDisplayGroup<BlogEntry>();
170 _dg.setNumberOfObjectsPerBatch(20);
171 _dg.setDataSource(dataSource);
172 _dg.setObjectArray(BlogEntry.fetchBlogEntries(editingContext(), BlogEntry.AUTHOR.eq(session().loggedAuthor()), BlogEntry.LAST_MODIFIED.descs()));
173 }
Pascal Robert 12.1 174
Pascal Robert 11.1 175 public ERXBatchingDisplayGroup<BlogEntry> displayGroup() {
176 return this._dg;
177 }
Pascal Robert 12.1 178
Pascal Robert 11.1 179 private String _emailAddress;
Pascal Robert 12.1 180
Pascal Robert 11.1 181 public String emailAddress() {
182 return this._emailAddress;
183 }
Pascal Robert 12.1 184
Pascal Robert 11.1 185 public void setEmailAddress(String emailAddress) {
186 this._emailAddress = emailAddress;
187 }
Pascal Robert 12.1 188
Pascal Robert 11.1 189 private BlogEntry _blogEntryItem;
190
191 public void setBlogEntryItem(BlogEntry blogEntryItem) {
192 this._blogEntryItem = blogEntryItem;
193 }
Pascal Robert 12.1 194
Pascal Robert 11.1 195 public BlogEntry blogEntryItem() {
196 return this._blogEntryItem;
197 }
Pascal Robert 12.1 198
Pascal Robert 11.1 199 @Override
200 public Session session() {
201 return ((Session)super.session());
202 }
Pascal Robert 12.1 203
Pascal Robert 11.1 204 public boolean isLogged() {
205 return ((session()).loggedAuthor() == null) ? false: true;
206 }
Pascal Robert 12.1 207
Pascal Robert 11.1 208 private EOEditingContext _ec;
Pascal Robert 12.1 209
Pascal Robert 11.1 210 public EOEditingContext editingContext() {
211 if (_ec == null) {
212 _ec = ERXEC.newEditingContext();
213 }
214 return _ec;
215 }
Pascal Robert 12.1 216
Pascal Robert 11.1 217 private String _errorMessage = null;
Pascal Robert 12.1 218
Pascal Robert 11.1 219 public String errorMessage() {
220 return this._errorMessage;
221 }
Pascal Robert 12.1 222
Pascal Robert 11.1 223 public WOActionResults login() {
224 Author loggedAuthor = Author.validateLogin(editingContext(), _emailAddress);
225 if (loggedAuthor != null) {
226 session().setAuthor(loggedAuthor);
227 } else {
228 _errorMessage = "Invalid email address";
229 }
230 return null;
Pascal Robert 12.1 231 }
Pascal Robert 11.1 232 }
233
234 {{/code}}
235
236 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.//
237
238 Open **AdminMainPage.wo** and override all the content between the <body> tag to be:
239
240 {{code}}
241
242 <wo:AjaxUpdateContainer id="main">
243 <wo:if condition="$isLogged">
244 <wo:if condition="$displayGroup.hasMultipleBatches">
245 <div>
246 <wo:link action="$displayGroup.displayPreviousBatch">Previous</wo:link>
247 | Batch
248 <wo:str value="$displayGroup.currentBatchIndex" />
249 of
250 <wo:str value="$displayGroup.batchCount" />
251 |
252 <wo:link action="$displayGroup.displayNextBatch">Next</wo:link>
253 </div>
254 </wo:if>
Pascal Robert 12.1 255 <table>
256 <tr>
257 <th><wo:WOSortOrder displayGroup="$displayGroup" key="title" /> Title</th>
258 <th>Author</th>
259 <th><wo:WOSortOrder displayGroup="$displayGroup" key="creationDate" /> Created on</th>
260 <th><wo:WOSortOrder displayGroup="$displayGroup" key="lastModified" /> Last modified</th>
261 </tr>
262 <wo:loop list="$displayGroup.displayedObjects" item="$blogEntryItem">
263 <tr>
264 <td>
265 <wo:str value="$blogEntryItem.title" />
266 </td>
267 <td> <wo:str value="$blogEntryItem.author.fullName" /> </td>
268 <td> <wo:str value="$blogEntryItem.creationDate" dateformat="%Y/%m/%d" /> </td>
269 <td> <wo:str value="$blogEntryItem.lastModified" dateformat="%Y/%m/%d" /> </td>
270 </tr>
271 </wo:loop>
272 </table>
Pascal Robert 11.1 273 </wo:if>
274 <wo:else>
275 <wo:if condition="$errorMessage">
276 <span style="text-color: red;">Error: <wo:str value="$errorMessage" /></span>
277 </wo:if>
278 <wo:form>
279 <div>
280 <label>Email address:</label>
281 <wo:textField value="$emailAddress" />
282 </div>
283 <div><wo:AjaxSubmitButton updateContainerID="main" action="$login" value="Login" /></div>
284 </wo:form>
285 </wo:else>
286 </wo:AjaxUpdateContainer>
287
288 {{/code}}
289
290 Last step: we need a link to the admin page. Open **Main.wo** and just before the </body> tag, add the following:
291
292 {{code}}
293
Pascal Robert 12.1 294 <wo:link pageName="AdminMainPage">Admin</wo:link>
Pascal Robert 11.1 295
296 {{/code}}
297
298 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.
299
300 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.
Pascal Robert 12.1 301
302 Create a new component, and name it **EditBlogEntry**. Open **EditBlogEntry.java** and override the code with:
303
304 {{code}}
305
306 package your.app.components;
307
308 import your.app.Session;
309 import your.app.model.Author;
310 import your.app.model.BlogEntry;
311
312 import com.webobjects.appserver.WOActionResults;
313 import com.webobjects.appserver.WOContext;
314 import com.webobjects.eocontrol.EOEditingContext;
315
316 import er.extensions.components.ERXComponent;
317 import er.extensions.eof.ERXEC;
318 import er.extensions.eof.ERXEOControlUtilities;
319
320 public class EditBlogEntry extends ERXComponent {
321
322 public EditBlogEntry(WOContext context) {
323 super(context);
324 }
325
326 private BlogEntry _blogEntry;
327
328 public BlogEntry blogEntry() {
329 return this._blogEntry;
330 }
331
332 public void setBlogEntry(BlogEntry blogEntry) {
333 if (blogEntry == null) {
334 this._blogEntry = ERXEOControlUtilities.createAndInsertObject(editingContext(), BlogEntry.class);
335 Author localUser = ERXEOControlUtilities.localInstanceOfObject(editingContext(), session().loggedAuthor());
336 this._blogEntry.setAuthorRelationship(localUser);
337 } else {
338 this._blogEntry = ERXEOControlUtilities.localInstanceOfObject(editingContext(), blogEntry);
339 }
340 }
341
342 private EOEditingContext _ec;
343
344 public EOEditingContext editingContext() {
345 if (_ec == null) {
346 _ec = ERXEC.newEditingContext();
347 }
348 return _ec;
349 }
350
351 @Override
352 public Session session() {
353 return ((Session)super.session());
354 }
355
356 public WOActionResults save() {
357 editingContext().saveChanges();
358 return pageWithName(AdminMainPage.class);
359 }
360 }
361
362 {{/code}}
363
364 Open **EditBlogEntry.wo** and between the <body> tag, add the following:
365
366 {{code}}
367
368 <wo:form>
369 <div>
370 <label>Title:</label>
371 <wo:textfield value="$blogEntry.title" />
372 </div>
373 <div>
374 <label>Content:</label>
375 <wo:text value="$blogEntry.content" rows="20" cols="80" />
376 </div>
377 <div>Author: <wo:str value="$session.loggedAuthor.fullName" /></div>
378 <div><wo:submitButton action="$save" value="Save changes" /></div>
379 </wo:form>
380
381 {{/code}}
382
383 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:
384
385 {{code}}
386
387 public WOActionResults editBlogEntry() {
388 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
389 nextPage.setBlogEntry(_blogEntryItem);
390 return nextPage;
391 }
392
393 public WOActionResults createBlogEntry() {
394 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
395 nextPage.setBlogEntry(null);
396 return nextPage;
397 }
398
399 {{/code}}
400
401 Open **AdminMainPage.wo** and just after <wo:if condition="$isLogged">, add the following line:
402
403 {{code}}
404
405 <div><wo:link action="$createBlogEntry">Create a new blog entry</wo:link></div>
406
407 {{/code}}
408
409 Find this line:
410
411 {{code}}
412
413 <wo:str value="$blogEntryItem.title" />
414
415 {{/code}}
416
417 and replace it with:
418
419 {{code}}
420
421 <wo:link action="$editBlogEntry"><wo:str value="$blogEntryItem.title" /></wo:link>
422
423 {{/code}}
424
425 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!