Wiki source code of Your First Stateful Project

Last modified by Bastian Triller on 2021/08/07 03:59

Hide last authors
Pascal Robert 3.1 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.
Pascal Robert 5.1 10 * We need a form to edit/create blog entries.
Pascal Robert 3.1 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
Pascal Robert 17.1 14 [[image:attach:Capture d’écran 2012-08-06 à 04.56.13.png]]
Pascal Robert 5.1 15
Pascal Robert 17.1 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**.
Pascal Robert 5.1 17
Pascal Robert 17.1 18 [[image:attach:Capture d’écran 2012-07-29 à 14.25.46.png]]
Pascal Robert 5.1 19
Pascal Robert 17.1 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:
Pascal Robert 5.1 21
Pascal Robert 17.1 22 [[image:attach:Capture d’écran 2012-08-06 à 05.15.32.png]]
Pascal Robert 8.1 23
Pascal Robert 17.1 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.
Pascal Robert 8.1 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
Pascal Robert 11.1 30 import your.app.model.BlogEntry;
31
Pascal Robert 8.1 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 {
Pascal Robert 11.1 41
42 private EOEditingContext _ec;
43 private BlogEntry _blogEntryItem;
44
Pascal Robert 8.1 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
Pascal Robert 11.1 61 public void setBlogEntryItem(BlogEntry blogEntryItem) {
62 this._blogEntryItem = blogEntryItem;
63 }
64
65 public BlogEntry blogEntryItem() {
66 return this._blogEntryItem;
67 }
68
Pascal Robert 8.1 69 }
70
71 {{/code}}
Pascal Robert 11.1 72
Theodore Petrosky 19.1 73 [[ERXBatchingDisplayGroup>>url:http://jenkins.wocommunity.org/job/Wonder/lastSuccessfulBuild/javadoc/er/extensions/appserver/ERXDisplayGroup.html||shape="rect"]] 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 know that your list will contains hundred of objects.
Pascal Robert 11.1 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
Pascal Robert 16.1 137 Save the file. Now open Author.java and add the following method:
Pascal Robert 11.1 138
Pascal Robert 16.1 139 {{code}}
140 public static Author validateLogin(EOEditingContext editingContext, String _emailAddress) {
141     Author user = Author.fetchAuthor(ERXEC.newEditingContext(), Author.EMAIL.eq(_emailAddress));
142 return user;
143 }
144
145 {{/code}}
146
Pascal Robert 17.1 147 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**.
Pascal Robert 16.1 148
Pascal Robert 11.1 149 After the component have been created, open **AdminMainPage.java** and override the content of the class with the following code:
150
151 {{code}}
152
153 package your.app.components;
154
155 import your.app.Session;
156 import your.app.model.Author;
157 import your.app.model.BlogEntry;
158
159 import com.webobjects.appserver.WOActionResults;
160 import com.webobjects.appserver.WOContext;
161 import com.webobjects.eoaccess.EODatabaseDataSource;
162 import com.webobjects.eocontrol.EOEditingContext;
163
164 import er.extensions.batching.ERXBatchingDisplayGroup;
165 import er.extensions.components.ERXComponent;
166 import er.extensions.eof.ERXEC;
167
168 public class AdminMainPage extends ERXComponent {
Pascal Robert 12.1 169
Pascal Robert 11.1 170 private ERXBatchingDisplayGroup<BlogEntry> _dg;
171
172 public AdminMainPage(WOContext context) {
173 super(context);
174 EODatabaseDataSource dataSource = new EODatabaseDataSource(editingContext(), BlogEntry.ENTITY_NAME);
175 _dg = new ERXBatchingDisplayGroup<BlogEntry>();
176 _dg.setNumberOfObjectsPerBatch(20);
177 _dg.setDataSource(dataSource);
178 _dg.setObjectArray(BlogEntry.fetchBlogEntries(editingContext(), BlogEntry.AUTHOR.eq(session().loggedAuthor()), BlogEntry.LAST_MODIFIED.descs()));
179 }
Pascal Robert 12.1 180
Pascal Robert 11.1 181 public ERXBatchingDisplayGroup<BlogEntry> displayGroup() {
182 return this._dg;
183 }
Pascal Robert 12.1 184
Pascal Robert 11.1 185 private String _emailAddress;
Pascal Robert 12.1 186
Pascal Robert 11.1 187 public String emailAddress() {
188 return this._emailAddress;
189 }
Pascal Robert 12.1 190
Pascal Robert 11.1 191 public void setEmailAddress(String emailAddress) {
192 this._emailAddress = emailAddress;
193 }
Pascal Robert 12.1 194
Pascal Robert 11.1 195 private BlogEntry _blogEntryItem;
196
197 public void setBlogEntryItem(BlogEntry blogEntryItem) {
198 this._blogEntryItem = blogEntryItem;
199 }
Pascal Robert 12.1 200
Pascal Robert 11.1 201 public BlogEntry blogEntryItem() {
202 return this._blogEntryItem;
203 }
Pascal Robert 12.1 204
Pascal Robert 11.1 205 @Override
206 public Session session() {
207 return ((Session)super.session());
208 }
Pascal Robert 12.1 209
Pascal Robert 11.1 210 public boolean isLogged() {
211 return ((session()).loggedAuthor() == null) ? false: true;
212 }
Pascal Robert 12.1 213
Pascal Robert 11.1 214 private EOEditingContext _ec;
Pascal Robert 12.1 215
Pascal Robert 11.1 216 public EOEditingContext editingContext() {
217 if (_ec == null) {
218 _ec = ERXEC.newEditingContext();
219 }
220 return _ec;
221 }
Pascal Robert 12.1 222
Pascal Robert 11.1 223 private String _errorMessage = null;
Pascal Robert 12.1 224
Pascal Robert 11.1 225 public String errorMessage() {
226 return this._errorMessage;
227 }
Pascal Robert 12.1 228
Pascal Robert 11.1 229 public WOActionResults login() {
230 Author loggedAuthor = Author.validateLogin(editingContext(), _emailAddress);
231 if (loggedAuthor != null) {
232 session().setAuthor(loggedAuthor);
233 } else {
234 _errorMessage = "Invalid email address";
235 }
236 return null;
Pascal Robert 12.1 237 }
Pascal Robert 11.1 238 }
239
240 {{/code}}
241
Pascal Robert 17.1 242 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.
Pascal Robert 11.1 243
244 Open **AdminMainPage.wo** and override all the content between the <body> tag to be:
245
246 {{code}}
247
Pascal Robert 13.1 248 <wo:AjaxUpdateContainer id="main">
Pascal Robert 11.1 249 <wo:if condition="$isLogged">
250 <wo:if condition="$displayGroup.hasMultipleBatches">
251 <div>
252 <wo:link action="$displayGroup.displayPreviousBatch">Previous</wo:link>
Pascal Robert 13.1 253 | Batch
Pascal Robert 11.1 254 <wo:str value="$displayGroup.currentBatchIndex" />
Pascal Robert 13.1 255 of
Pascal Robert 11.1 256 <wo:str value="$displayGroup.batchCount" />
Pascal Robert 13.1 257 |
Pascal Robert 11.1 258 <wo:link action="$displayGroup.displayNextBatch">Next</wo:link>
259 </div>
260 </wo:if>
Pascal Robert 12.1 261 <table>
262 <tr>
263 <th><wo:WOSortOrder displayGroup="$displayGroup" key="title" /> Title</th>
264 <th>Author</th>
265 <th><wo:WOSortOrder displayGroup="$displayGroup" key="creationDate" /> Created on</th>
266 <th><wo:WOSortOrder displayGroup="$displayGroup" key="lastModified" /> Last modified</th>
267 </tr>
268 <wo:loop list="$displayGroup.displayedObjects" item="$blogEntryItem">
269 <tr>
270 <td>
271 <wo:str value="$blogEntryItem.title" />
272 </td>
273 <td> <wo:str value="$blogEntryItem.author.fullName" /> </td>
274 <td> <wo:str value="$blogEntryItem.creationDate" dateformat="%Y/%m/%d" /> </td>
275 <td> <wo:str value="$blogEntryItem.lastModified" dateformat="%Y/%m/%d" /> </td>
276 </tr>
277 </wo:loop>
278 </table>
Pascal Robert 11.1 279 </wo:if>
280 <wo:else>
281 <wo:if condition="$errorMessage">
282 <span style="text-color: red;">Error: <wo:str value="$errorMessage" /></span>
283 </wo:if>
284 <wo:form>
285 <div>
286 <label>Email address:</label>
287 <wo:textField value="$emailAddress" />
288 </div>
289 <div><wo:AjaxSubmitButton updateContainerID="main" action="$login" value="Login" /></div>
290 </wo:form>
291 </wo:else>
292 </wo:AjaxUpdateContainer>
293
294 {{/code}}
295
296 Last step: we need a link to the admin page. Open **Main.wo** and just before the </body> tag, add the following:
297
298 {{code}}
299
Pascal Robert 12.1 300 <wo:link pageName="AdminMainPage">Admin</wo:link>
Pascal Robert 11.1 301
302 {{/code}}
303
304 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.
305
306 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 307
308 Create a new component, and name it **EditBlogEntry**. Open **EditBlogEntry.java** and override the code with:
309
310 {{code}}
311
312 package your.app.components;
313
314 import your.app.Session;
315 import your.app.model.Author;
316 import your.app.model.BlogEntry;
317
318 import com.webobjects.appserver.WOActionResults;
319 import com.webobjects.appserver.WOContext;
320 import com.webobjects.eocontrol.EOEditingContext;
321
322 import er.extensions.components.ERXComponent;
323 import er.extensions.eof.ERXEC;
324 import er.extensions.eof.ERXEOControlUtilities;
325
326 public class EditBlogEntry extends ERXComponent {
327
328 public EditBlogEntry(WOContext context) {
329 super(context);
330 }
331
332 private BlogEntry _blogEntry;
333
334 public BlogEntry blogEntry() {
335 return this._blogEntry;
336 }
337
338 public void setBlogEntry(BlogEntry blogEntry) {
339 if (blogEntry == null) {
340 this._blogEntry = ERXEOControlUtilities.createAndInsertObject(editingContext(), BlogEntry.class);
341 Author localUser = ERXEOControlUtilities.localInstanceOfObject(editingContext(), session().loggedAuthor());
342 this._blogEntry.setAuthorRelationship(localUser);
343 } else {
344 this._blogEntry = ERXEOControlUtilities.localInstanceOfObject(editingContext(), blogEntry);
345 }
346 }
347
348 private EOEditingContext _ec;
349
350 public EOEditingContext editingContext() {
351 if (_ec == null) {
352 _ec = ERXEC.newEditingContext();
353 }
354 return _ec;
355 }
356
357 @Override
358 public Session session() {
359 return ((Session)super.session());
360 }
361
362 public WOActionResults save() {
363 editingContext().saveChanges();
364 return pageWithName(AdminMainPage.class);
365 }
366 }
367
368 {{/code}}
369
370 Open **EditBlogEntry.wo** and between the <body> tag, add the following:
371
372 {{code}}
373
Pascal Robert 13.1 374 <wo:form>
Pascal Robert 12.1 375 <div>
376 <label>Title:</label>
377 <wo:textfield value="$blogEntry.title" />
378 </div>
379 <div>
380 <label>Content:</label>
381 <wo:text value="$blogEntry.content" rows="20" cols="80" />
382 </div>
383 <div>Author: <wo:str value="$session.loggedAuthor.fullName" /></div>
384 <div><wo:submitButton action="$save" value="Save changes" /></div>
385 </wo:form>
386
387 {{/code}}
388
389 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:
390
391 {{code}}
392
Pascal Robert 13.1 393 public WOActionResults editBlogEntry() {
Pascal Robert 12.1 394 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
395 nextPage.setBlogEntry(_blogEntryItem);
396 return nextPage;
397 }
Pascal Robert 13.1 398
Pascal Robert 12.1 399 public WOActionResults createBlogEntry() {
400 EditBlogEntry nextPage = pageWithName(EditBlogEntry.class);
401 nextPage.setBlogEntry(null);
Pascal Robert 13.1 402 return nextPage;
Pascal Robert 12.1 403 }
404
405 {{/code}}
406
407 Open **AdminMainPage.wo** and just after <wo:if condition="$isLogged">, add the following line:
408
409 {{code}}
410
411 <div><wo:link action="$createBlogEntry">Create a new blog entry</wo:link></div>
412
413 {{/code}}
414
415 Find this line:
416
417 {{code}}
418
419 <wo:str value="$blogEntryItem.title" />
420
421 {{/code}}
422
423 and replace it with:
424
425 {{code}}
426
427 <wo:link action="$editBlogEntry"><wo:str value="$blogEntryItem.title" /></wo:link>
428
429 {{/code}}
430
Bastian Triller 20.1 431 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>>doc:WEB.Home.Getting Started.Your First Deployment.WebHome]].