At line 18 added 2 lines. |
* [7] Show an Entry's Category, and allow modification of it |
* [8] Add the ability to add and delete entries |
At line 50 changed 1 line. |
<img src="http://appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
<img src="http://demo.appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
At line 52 changed 1 line. |
<img src="http://appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
<img src="http://demo.appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
At line 54 changed 1 line. |
<img src="http://appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
<img src="http://demo.appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning"> |
At line 287 added 1 line. |
<nested:hidden property="categoryId"/> |
At line 301 changed 1 line. |
If you try to clicking the "Save" button, you'll get a nice and descriptive <span style="background: #ffd">javax.servlet.ServletException: BeanUtils.populate</span> stack trace. This is caused by the fact that the ''struts_form.xdt'' (prior to AppFuse 1.9) did not account for plurals that ended in "ies". To fix this, replace ''metadata/web/struts_form.xdt'' with [the latest one from CVS|https://appfuse.dev.java.net/source/browse/*checkout*/appfuse/metadata/templates/struts_form.xdt] (right-click, save as). |
If you try to clicking the "Save" button, you'll get a nice and descriptive <span style="background: #ffd">javax.servlet.ServletException: BeanUtils.populate</span> stack trace. This is caused by the fact that the ''struts_form.xdt'' (prior to AppFuse 1.9) did not account for plurals that ended in "ies". To fix this, replace ''metadata/templates/struts_form.xdt'' with [the latest one from CVS|https://appfuse.dev.java.net/source/browse/*checkout*/appfuse/metadata/templates/struts_form.xdt] (right-click, save as). |
At line 303 changed 3 lines. |
# Modify struts_form.xdt so collections ending with "ies" will get their POJO's names ending with "y" (i.e. entries -> Entry). |
# TODO: Figure out how to get DateConverter and TimestampConverter to play nicely with each other (maybe combine them) |
# Style up the "posted at" text |
<a name="timestampissue"></a> |
Run __and clean deploy__ and try clicking the ''Save'' button again. This time you might get the following lovely error: |
At line 307 changed 4 lines. |
# Add AVAILABLE_CATEGORIES to Constants.java |
# Grab Categories in StartupListener and expose them, discuss why not do it in edit() method |
# Add "extends Manager" to LookupManager interface so getObjects() can be used in StartupListener |
# LookupManager.getObjects results in NPE, change signature of setLookupDAO() method to call super.dao = dao: |
<div style="border: 2px solid red; background: #ffd; padding: 5px"> |
<img src="http://demo.appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning" /> |
<strong>The process did not complete. Details should follow.</strong><br/> |
<img src="http://demo.appfuse.org/appfuse/images/iconWarning.gif" class="icon" alt="Warning" /> |
Could not convert java.lang.String to java.sql.Timestamp<br/> |
</div> |
At line 316 added 2 lines. |
I'll admit, I struggled with this issue for ''hours'' and I still don't have a good solution. From my hours of trial-and-error, the only conclusion I can come up with is that Struts (and Commons BeanUtils in particular) can't handle java.util.Date and java.sql.Timestamp together very well. If you know of a solution that'll allow displaying and saving a date and timestamp on the same form, please [comment on APF-176|http://issues.appfuse.org/browse/APF-176]. The problem is basically that I can't get the DateConverter to recognize Date as a java.util.Date - for some reason it thinks it's a Timestamp in the following logic: |
|
At line 314 changed 4 lines. |
public void setLookupDAO(LookupDAO dao) { |
super.dao = dao; |
this.dao = dao; |
} |
if (value instanceof Date) { |
DateFormat df = new SimpleDateFormat(DateUtil.getDatePattern()); |
// this works in unit tests, but when running in Tomcat, a java.util.Date |
// comes through as a java.sql.Timestamp - wierd eh? |
if (value instanceof Timestamp) { |
df = new SimpleDateFormat(TS_FORMAT); |
} |
|
try { |
return df.format(value); |
} catch (Exception e) { |
e.printStackTrace(); |
throw new ConversionException("Error converting Date to String"); |
} |
} |
At line 320 changed 2 lines. |
# Add html:select tag to weblogForm.jsp, include prettying up - show screenshot |
# POSSIBLITY: Add "title" to Entry.java |
For the purpose of this tutorial, you'll need to replace your ''src/service/org/appfuse/util/__DateConverter.java__'' with the [latest one from CVS|https://appfuse.dev.java.net/source/browse/*checkout*/appfuse/src/service/org/appfuse/util/DateConverter.java]. After replacing this file and running __ant deploy__, you should be able to save the form successfully. |
At line 339 added 1 line. |
!!Show an Entry's Category, and allow modification of it [#7] |
At line 324 changed 1 line. |
!!Add the ability to delete an entry |
The last piece of the relationships puzzle is to show an entry's category, as well as allow it to be changed. To show the category of an entry, change: |
At line 326 changed 1 line. |
# Wrap nested:iterate with div id |
[{Java2HtmlPlugin |
At line 328 changed 21 lines. |
<nested:iterate property="entries" id="entry" indexId="index"> |
<div id="entry<%=index%>"> |
<nested:hidden property="entryId"/> |
<nested:hidden property="weblogId"/> |
<!--<nested:hidden property="timeCreated"/> --> |
<nested:textarea property="text" style="width: 400px; height: 100px"/> |
<br /> |
<span style="color: #333; font-size: 11px; margin: 0 5px"> |
Category: |
<nested:select property="categoryId" style="color: #333; font-size: 11px; margin: 2px 10px 0 0"> |
<html:options collection="availableCategories" property="categoryId" labelProperty="name"/> |
</nested:select> |
Posted at: <nested:write property="timeCreated"/></span> |
<input type="submit" class="button" style="font-size: 11px" |
onclick="parentNode.parentNode.removeChild($('entry<%=index%>'))" value="Delete"/> |
</div> |
</nested:iterate> |
|
# This will delete, but only the first one - might need some more JavaScript magic to shuffle the numbers |
# Delete only works if last entry is deleted |
# Add only works if there's already an existing one to replicate. |
Posted at: <nested:write property="timeCreated"/> |
}] |
|
To: |
|
[{Java2HtmlPlugin |
|
<span style="color: #333; font-size: 11px; margin: 0 5px"> |
<strong>Category:</strong> <nested:write property="category.name"/>, |
<strong>Posted at:</strong> <nested:write property="timeCreated"/></span> |
}] |
|
Run __ant deploy-web__ and refresh the weblog form. Your screen should resemble the screenshot below: |
|
%%(width: 700px; height: 316px; border: 1px solid black; margin: 10px auto 0 auto) |
[WeblogWithCategory.png] |
%% |
|
In order to allow modification of the Category, you'll need to change the ''categoryId'' from being a hidden field to being a drop-down. With Struts, the easiest way to populate a drop-down is when the application starts up. You ''could'' populate it in the Action's ''edit()'' method, but if any validation errors occur, the variable would not be re-added to the request. |
|
Add an AVAILABLE_CATEGORIES field to ''src/dao/**/__Constants.java__'': |
|
[{Java2HtmlPlugin |
|
/** |
* The name of the available categories list in application scope |
*/ |
public static final String AVAILABLE_CATEGORIES = "availableCategories"; |
}] |
|
Grab the list of possible categories in ''src/web/**/listener/__StartupListener.java__'' and stuff them into the servlet context: |
|
[{Java2HtmlPlugin |
|
context.setAttribute(Constants.AVAILABLE_ROLES, mgr.getAllRoles()); |
context.setAttribute(Constants.AVAILABLE_CATEGORIES, mgr.getObjects(Category.class)); |
}] |
|
If you're using < AppFuse 1.9, you'll need to fix [LookupManager.java|http://fisheye4.cenqua.com/browse/~raw,r=1453/appfuse/trunk/src/service/org/appfuse/service/LookupManager.java] and [LookupManagerImpl.java|http://fisheye4.cenqua.com/browse/~raw,r=1453/appfuse/trunk/src/service/org/appfuse/service/impl/LookupManagerImpl.java] so the above code works. |
|
Remove the hidden ''categoryId'' field from __weblogForm.jsp__: |
|
[{Java2HtmlPlugin |
|
<nested:hidden property="categoryId"/> |
}] |
|
And change <nested:write property="category.name"/> to: |
|
[{Java2HtmlPlugin |
|
<nested:select property="categoryId" style="color: #333; font-size: 11px; margin: 2px 10px 0 0"> |
<html:options collection="availableCategories" property="categoryId" labelProperty="name"/> |
</nested:select> |
}] |
|
Run __ant deploy__ and refresh your browser. You should now be able to modify the category for an entry. |
|
This covers most of the things you need to know for using Hibernate's relationships and modifying child elements. However, it doesn't cover adding or deleting child elements. |
|
!!Add the ability to add and delete entries [#8] |
|
There are many different ways to add and delete child elements from a form. This section is a work in progress and shows a quick-n-dirty (complete with bugs!) way of doing this. |
|
In __weblogForm.jsp__, add a "Delete" button just after the "Posted at" information: |
|
[{Java2HtmlPlugin |
|
Posted at: <nested:write property="timeCreated"/></span> |
<input type="button" class="button" name="delete" style="font-size: 11px" |
onclick="parentNode.parentNode.removeChild($('entry<%=index%>'))" value="Delete"/> |
}] |
|
Run __ant deploy-web__, refresh your browser - and you should be able to delete the entry. Click "Save" after clicking "Delete" to complete the process. While this works, it's not the ideal solution. Since it just removes the entry (and it's form elements) from the form, it doesn't re-adjust the index numbers on other entries. This means that deleting will only work for the last entry. In addition, the entry won't be deleted from the "entry" table (try ''select * from entry''). Both of these issues could likely be fixed if you linked the "Delete" button to an action (or Ajax) that deleted the entry from the database. |
|
To add a new entry, there are also a number of things you can do. The simplest one I've found is to duplicate an existing set of entry fields, re-index the input element names and clear the values. To do this: add the following after the ending </nested:iterate> for the "entries": |
|
[{Java2HtmlPlugin |
|
<hr /> |
<input type="button" class="button" onclick="toggleDisplay('newentry'); |
if ($('newentry').style.display == '') { |
var entries = parentNode.getElementsByTagName('div'); |
$('newentry').innerHTML = entries[entries.length-2].innerHTML; |
// reset all fields to blank, and change their names |
var inputs = $('newentry').getElementsByTagName('input'); |
var index = inputs[0].name.substring(inputs[0].name.indexOf('[')+1, inputs[0].name.indexOf(']')); |
var next = parseInt(index) + 1; |
for (i=0; i < inputs.length; i++) { |
inputs[i].name = inputs[i].name.replace(index, next); |
if (inputs[i].name == 'delete') { |
inputs[i].onclick = |
function() { $('newentry').innerHTML = ''; $('newentry').style.display='none'; }; |
} else if (inputs[i].name.indexOf('weblogId') == -1) { |
inputs[i].value = ''; |
} |
} |
// reset any textareas |
var boxes = $('newentry').getElementsByTagName('textarea'); |
for (i=0; i < boxes.length; i++) { |
boxes[i].name = boxes[i].name.replace(index, next); |
boxes[i].value = ''; |
} |
// reset any selects |
var dropdowns = $('newentry').getElementsByTagName('select'); |
for (i=0; i < dropdowns.length; i++) { |
dropdowns[i].name = dropdowns[i].name.replace(index, next); |
dropdowns[i].selectedIndex = 0; |
} |
boxes[0].focus(); |
this.value = this.value.replace('Add', 'Cancel'); |
} else { |
this.value = this.value.replace('Cancel', 'Add'); |
}" value="Add New Entry"/> |
<div id="newentry" style="display: none"></div> |
}] |
|
Run __ant deploy-web db-load__ and you should be able to add a new Entry by clicking on the <button>Add New Entry</button> button. There are a couple of issues with this technique: |
|
* Adding a new entry will only work if there is (at least) one entry already in the weblog. |
* The "timeCreated" field won't be set as part of the new entry. |
|
Since this tutorial is designed to show Hibernate relationships and how to edit them (moreso than techniques for adding/deleting child records), I'll leave the issues with Delete and Add as an exercise for the reader. <img src="http://raibledesigns.com/images/smileys/wink.gif" alt="Wink" style="vertical-align: middle" /> Of course, if you do figure out a good solution - please send an e-mail to the mailing list (or enter an issue in [JIRA|http://issues.appfuse.org]) so we can enhance this tutorial. |