PDA

View Full Version : Is there a way to override the getter and setter methods


testing123
Apr 25th, 2007, 04:45 PM
You may be saying to yourself "WHY ON EARTH WOULD YOU WANT TO DO THAT?"
Let me explain this issue and maybe that will help you either understand why I want to do this or help me understand what I should be doing so that I can avoid this.

The Situation
Currently the inputs that I have (in the legacy code that I am trying to convert to spring mvc) are as follows:
1. A dropdown that specifies a particular material.
2. A text input field where you can enter in the percentage of that material

The user can also add fields to specify additional materials and their percentages.

Example
Say the question asks what kind of exterior do you have on your home. The user wants to enter in 30 percent brick, 10 percent aluminum siding, and 60 percent stucco.

The Problem
Because I don't know how many field they are going to need I start with one the dynamically (javascript currently) add another row so that user can specify how much of a bunch of materials makes up the exterior of his/her home.

Is binding impossible in this case since I don't know how many of these inputs will be needed by the user? My thought was that maybe I could somehow override the getter and setter methods to that I could customize the way things are bound to their respective beans.

ANY suggestions or ideas are welcome. Perhaps I am overlooking a simple solution. Perhaps you could help me brainstorm some possible solutions.

Additional Infomation (that may not be userful)
First of all I have 5 views. 4 of them are for entering information and the final is a report displaying the results of what the user entered. My first thought was to use an AbstractWizardFormController but I cannot use that(I think) because at any time the user can nav back and forth to previous pages or skip certain pages and I want to save the data that they have entered when they do this (i.e. they input their name on page1 then nav to page4 then they fill our info on page4 but instead of clicking a submit button they can choose to nav to any of the other pages and when they do, the info they entered on page4 will be saved). According to Thread 11086 (http://forum.springframework.org/showthread.php?t=11086) in order to do this I have to do some kind of MultiActionFormController.

Thanks in advanced....sorry for the huge post. I wanted to make sure I explained everything properly.

testing123
Apr 25th, 2007, 04:54 PM
My initial thought was to override the bindAndValidate method found in BaseCommandController but that is a "final" method so I am not allowed to do that.

testing123
Apr 25th, 2007, 05:46 PM
I think I'll have to override the bind method. I was looking at MultiActionController's bind method and it seems like I could just override the bind method and put in what I was using before to parse through all the inputs that I have. The only problem is...it seems like that defeats the purpose of using spring mvc in the first place. Is there a better way. Any one with any ideas or comments please feel free to express them. Hopefully I won't be talking to myself this whole thread :)

Steve O
Apr 25th, 2007, 06:22 PM
Testing,

I think that you are right - you need to override the onBind method. You will probably then want to get a materials array an percentage array from the request. From there, you can do what you want with the arrays.

Good luck,

Steve O

testing123
Apr 25th, 2007, 06:53 PM
Bind method to be overridden:

protected void bind(HttpServletRequest request, Object command) throws Exception {
logger.debug("Binding request parameters onto MultiActionController command");
ServletRequestDataBinder binder = createBinder(request, command);
binder.bind(request);
if (this.validators != null) {
for (int i = 0; i < this.validators.length; i++) {
if (this.validators[i].supports(command.getClass())) {
ValidationUtils.invokeValidator(this.validators[i], command, binder.getBindingResult());
}
}
}
binder.closeNoCatch();
}


So far my plan is to replace:

ServletRequestDataBinder binder = createBinder(request, command);
binder.bind(request);

With my custom way of grabing form data (thus throwing away the binding functionality which seems like a waste to me but may be the only option)

Next the validate code:

if (this.validators != null) {
for (int i = 0; i < this.validators.length; i++) {
if (this.validators[i].supports(command.getClass())) {
ValidationUtils.invokeValidator(this.validators[i], command, binder.getBindingResult());
}
}
}


Should I just paste this into my new bind method (making my own array and then calling getValidators() ofcourse since the validators array is private)?

finally binder.closeNoCatch();. The docs say the following:

Treats errors as fatal. Use this method only if it's an error if the input isn't valid. This might be appropriate if all input is from dropdowns, for example.

I don't understand what this means (maybe showing my ignorance here). Should I call this too? Or just leave it out of my overridden method.

Although this way of doing it seems like it will work I have an intuition that it may not be the best way (what if the bind method changes in future versions to include other stuff that I need...I will no longer be able to use that functionality or I will have to constantly make sure my code is up-to-date with current versions).

cwilkes
Apr 25th, 2007, 07:19 PM
I'm not sure what your problem is ... does your form backing bean look something like this:

class HomeInfo {
public List<WallMaterial> wallMaterials;
}
class WallMaterial {
float percentage;
MaterialType type;
}

Are you concerned in your validation that the sum of percentages should add up to 100%?

You can use javascript to add additional fields on your form, just look and see how the spring reads in arrays / list and mimic that with your javascript action.

testing123
Apr 26th, 2007, 01:02 PM
I'm not sure what your problem is ... does your form backing bean look something like this:

class HomeInfo {
public List<WallMaterial> wallMaterials;
}
class WallMaterial {
float percentage;
MaterialType type;
}

Are you concerned in your validation that the sum of percentages should add up to 100%?

You can use javascript to add additional fields on your form, just look and see how the spring reads in arrays / list and mimic that with your javascript action.

Right now I am in the design process and wasn't sure how to make a form backing bean that would handle the situation I mentioned above. Thanks for your suggestion! I am still trying to get a handle on how this would work. How would this look in the jsp? Something like this?

<spring:bind path="homeInfo">
<c:forEach items="${wallMaterial}" var="wm">
<tr>
<td>
<select name="wm.type" value="<c:out value="${status.value}"/>">
</td>
<td>
<input type="text" name="wm.percentage" value="<c:out value="${status.value}"/>">
</td>
</tr>
</c:forEach>
</spring:bind>


Will this get saved to my form backing object automagically! That seems to make sense. Adding the rows with javascript is still a little fuzzy. Do you know where I can find some good reference on how to get the javascript adding additional rows in a way that it will get picked up by spring mvc? I found a few but they didn't really address the issue I am looking for. I am willing to put in the time to read and learn so just send me a link or something and I will check it out.

Finally, yes I am concerned about validation with the sum of percentages but that should be easy enough right? If I can get the binding working all I would have to do it loop through the backing objects wall materials and make sure it equaled 100 percent...right?

Thank you soooo much for your help. Sorry for my ignorance...I am trying to get this stuff figured out. So far I have be incredibly impressed with how customizable spring is and look forward to learning more.

testing123
Apr 26th, 2007, 01:17 PM
yet again showing my ignorance how would you use the new

<form:select> in this case. The type needs to be a drop down of all the types of wall material. Which brings me to my next problem.

With the above code I don't think that would work. Thats probably why I was getting mixed up. Seems like the above code would have to print out each type on a separate line with its respective percent field. Any Ideas on how to accomplish this? Let me know if I am not making sense and I will clarify. Thanks again for all your help!
Tad

testing123
Apr 26th, 2007, 07:15 PM
Ok so I am trying to get a simple abstractwizardform working but I can't get the foreach to display anything. I print out logs and it looks like the command object has the initial ComboValue object but nothing is displayed in the jsp.

As a side not is there a conventional way of populating the first objects when dealing with a list like I am...just curious. Thanks for those who have helped thus far. Anybody see what I am doing wrong? I am not getting any errors, but the wallMaterials objects are not getting printed out.

This is my form backing bean:

public class FormData
{
public ArrayList<ComboWidgetValue> wallMaterials;

public void setWallMaterials(ArrayList<ComboWidgetValue> wmList)
{
wallMaterials = wmList;
}

public ArrayList<ComboWidgetValue> getWallMaterials()
{
if (null == wallMaterials || wallMaterials.size() == 0)
{
wallMaterials = new ArrayList();
wallMaterials.add(new ComboWidgetValue());
}
logger.info("getWallMaterials set to " + wallMaterials);
return wallMaterials;
}

}

class ComboWidgetValue
{
int percentage;
String typeId;
}


This is what my jsp looks like:

<form:form commandName="formData">
<table>
<c:forEach items="${wallMaterials}" var="wm">
<tr>
<td align="right" width="20%">Wall Material:</td>
<td width="20%">
<form:select path="wm.typeId" items="${wmMap}" multiple="false" />
<form:input path="wm.percentage" />%
</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</tr>
</c:forEach>
</table>
...
</form:form>


My controller code to set the reference data looks like:

protected Map referenceData(HttpServletRequest req, int page) throws Exception
{
HashMap wmMap = new HashMap();
wmMap.put("XT_WM_WF", "Wood Frame");
wmMap.put("XT_WM_ST", "Stucco");

HashMap model = new HashMap();
model.put("wmMap", wmMap);

logger.info("returning the model: " + model);
return model;
}

protected Object formBackingObject(HttpServletRequest req) throws Exception
{
FormData formData = new FormData();
logger.info("returning the wm: " + formData.getWallMaterials());
return formData;
}

fox
Apr 27th, 2007, 03:31 AM
you wrote this:

<c:forEach items="${wallMaterials}" var="wm">

but 'wallMaterials' is empty since you didn't assign any values to it in the referenceMap

testing123
Apr 27th, 2007, 10:54 AM
This is probably what I am not understanding. I thought the form backing bean was available throughout the wizard pages. How do I get a reference to the form backing bean in so that I can set it in referenceData()?

testing123
Apr 27th, 2007, 10:59 AM
protected Map referenceData(HttpServletRequest req, int page) throws Exception
{
HashMap wmMap = new HashMap();
wmMap.put("XT_WM_WF", "Wood Frame");
wmMap.put("XT_WM_ST", "Stucco");

FormData formData = new FormData();

HashMap model = new HashMap();
model.put("wmMap", wmMap);
model.put("formData", formData);


logger.info("returning the model: " + model);
return model;
}

fox
Apr 27th, 2007, 11:08 AM
yes, in the real wizard you have one backing object and it follows the page flow you designed

but here you just have two standard forms which are not connected with each other. they use the same definition of backing object, but they do not share it

i advice you to look at the descriptions of AbstractWizardFormController and try to use it instead :-)

Cheers,
fox

edit:

and yeah - putting wmMap will work (the forEach will work) but it is not what you want here :)

testing123
Apr 27th, 2007, 11:21 AM
Am I not override the right methods though? The book I am using "Expert Spring MVC and Web Flows" says to override these methods. Also I tried this and still no luck. THANK YOU SO MUCH for helping me fox! This has got me baffled. I know I'm a newbie and probably asking dumb questions I just can't figure out whats wrong. Here is my full controller (perhaps I am missing a method I should be overriding or maybe I am overriding one that I shouldn't):


public class FormDataController extends AbstractWizardFormController
{
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private MasterWidget widget;

@Override
protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object command, BindException exception)
{
FormData formData = (FormData) command;
widget.saveToMasterWidget(formData);
// Generate the report here

logger.info("process finished command: " + command);
return new ModelAndView("results");
}

@Override
protected Object formBackingObject(HttpServletRequest req) throws Exception
{
FormData formData = new FormData();
logger.info("returning the wm: " + formData.getWallMaterials());
return formData;
}


@Override
protected Map referenceData(HttpServletRequest req, int page) throws Exception
{
HashMap wmMap = new HashMap();
wmMap.put("XT_WM_WF", "Wood Frame");
wmMap.put("XT_WM_ST", "Stucco");

FormData formData = new FormData();

HashMap model = new HashMap();
model.put("wmMap", wmMap);
model.put("formData", formData);


logger.info("returning the model: " + model);
return model;
}

@Override
protected void validatePage(Object command, Errors errors, int page)
{
FormData formData = (FormData) command;
FormDataValidator validator = (FormDataValidator) getValidator();
switch (page)
{
case 0:
validator.validatePage0(formData, errors);
break;
case 1:
validator.validatePage1(formData, errors);
break;
}
}

public void setMasterWidget(MasterWidget mw)
{
widget = mw;
}

public MasterWidget getMasterWidget()
{
return widget;
}
}

fox
Apr 27th, 2007, 03:48 PM
yes, you override good methods... (in simple form you have 'showForm' where you can make some initial actions.. but in the wizard we are only left with referenceData)

testing123
Apr 27th, 2007, 03:54 PM
I think I just need a good tutorial on the new <form:select...> and such fields and how to use them with lists. Have you seen any good tutorials or references. I have been using the Spring 2.0 Reference (http://www.springframework.org/docs/reference/mvc.html#mvc-formtaglib) but I need examples with lists and the using jstls foreach to make multiple rows. The closes thing I found was This (http://mattfleming.com/node/134) but he is using the old way of binding and I want to learn the new way. Anyone know of some good references?

fox
Apr 27th, 2007, 04:09 PM
the spring reference is the best i found, and frankly - it is enough (for me at least)

for multiple option select i think you can have path like this: variablename[]
hat's my guess anyway.. i didn't use multiple select but my checkboxes work in similar way:

<c:forEach items="${command.messages}" var="message" varStatus="status">
<tr>
<th><form:checkbox path="ids[${status.index}]" value="${message.messageId}"/></th>
<td>${message.messageTitle}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>${message.messageContent}</td>
</tr>
</c:forEach>

testing123
Apr 27th, 2007, 04:18 PM
I can get this working fine:


<form:form commandName="formData">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">


<tr>
<td align="right" width="20%">Wall Material:</td>
<td >
<spring:nestedPath path="comboVal">
<form:select path="typeId" items="${wmMap}" multiple="false" />
<form:input path="percentage" />%
</spring:nestedPath>
</td>
</tr>

</table>
<br>
<spring:hasBindErrors name="formData">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<br>
<br>
<input type="submit" value="Calculate" />
<input type="hidden" name="_page1" value="true" />
<input type="hidden" name="_finish" value="true" />
</form:form>

I can also get this working:


<spring:nestedPath path="formData">
<c:forEach items="${mytest}" var="wm">
<tr>
<td align="right" width="20%">Wall Material:</td>
<td width="20%">

<spring:bind path="wallMaterials[0].typeId">
<td width="20%">
<select>
<c:forEach items="${wmMap}" var="wmTypes">
<option VALUE="<c:out value="${wmTypes.key}"/>" > <c:out value="${wmTypes.value}"/> </OPTION>
</c:forEach>
</select>
</td>
</spring:bind>
<spring:bind path="wallMaterials[0].percentage">
<td width="20%">
<input type="text" name="percentage" value="<c:out value="${status.value}"/>">%
</td>
</spring:bind>

</td>
<td width="60%">
<font color="red"><c:out value="${status.errorMessage}"/></font>
</td>
</tr>
</c:forEach>
</spring:nestedPath>


But I can't figure out how to get change the zero into whatever the current index is. If I can figure that out using the new <form:...> tags I will be able to do what I want. I think I may be mixing syntax up somewhere. Thanks again for all you help!

fox
Apr 27th, 2007, 04:35 PM
you can replace <spring:hasBindErrors/> with appropriate <form:errors/>

and about the zero..

in my forEach i have a status (varStatus="status")

and in my path i use its index value:

ids[${status.index}]

and it will be 0,1,2,3....

testing123
Apr 27th, 2007, 04:41 PM
you wrote this:

<c:forEach items="${wallMaterials}" var="wm">

but 'wallMaterials' is empty since you didn't assign any values to it in the referenceMap

Sorry about that...I should have caught that...you posted it already. That should do the trick, I'll try it out. Thank you so much for helping me through this...hopefully I wasn't to irritating!

testing123
Apr 27th, 2007, 04:46 PM
Did you have to do something in particular to get your command object to be available (i.e. set it in the formBackingObject() or in referenceData())? The information doesn't seem to be sticking? Maybe thats the way it is supposed to work...not sure. Is there any way I can keep the command object available with all the information I need so that I can loop over lists in my command object (form backing object)?

<c:forEach items="${command.messages}" var="message" varStatus="status">

fox
Apr 27th, 2007, 05:45 PM
yes, i always use formBacking object when using forms... i can set there the default values which is really nice

it remembers chosen fields (when i go through pages in wizard or when one of the fields is not validated - the other ones stay) but you need the session...

it is written in the javadoc:

Note that you need to activate session form mode to reuse the form-backing object across the entire form workflow. Else, a new instance of the command class will be created for each submission attempt, just using this backing object as template for the initial form.

p.s. i'm not irritated - i like to help others and i like the others to help me if needed :-)

testing123
Apr 27th, 2007, 07:12 PM
Thanks for you help. Hopefully as I learn more I will be able to be familiar enough to return the favor. I got this working finally and I think my issues were:
1. I was missing up jstl stuff with spring stuff
2. I wasn't understanding out referenceData was working.

For any future people who might be looking for somthing similar heres the page:

<c:forEach items="${formData.wallMaterials}" var="wm" varStatus="status">
<spring:nestedPath path="wallMaterials[${status.index}]">
<tr>
<td align="right" width="20%">Wall Material:</td>
<td>
<form:select path="typeId" items="${widgets['XTOL_WM']}" multiple="false" />
<form:input path="percentage" size="3"/>%
</td>
<td>
<font color="red"><form:errors path="typeId" /></font>
<font color="red"><form:errors path="percentage" /></font>
</td>
</tr>
</spring:nestedPath>
</c:forEach>


time to move on to my next problem...but I will start another thread for that one. Really Fox, thanks for walking me through this. I sincerly appreciate it.

fox
Apr 28th, 2007, 04:54 AM
i'm glad i could be of assistance :-)