1

I am trying to setup Form Validation in Spring, therefore I am using javax.validation Annotations. This works pretty well, it gets the Errors pretty well.

I have a form:options Field in my Form that gets precalculated Values from my Controller, but if you submit wrong data, these precalculated Values get lost.

The form looks like this:

<form:form method="post" action="../add" commandName="new-booking"
        modelAttribute="new-booking" class="form-vertical">
        <table>
            <tr>
                <td><form:label path="numberOfBikesBooked">Anzahl der Fahrräder</form:label><font
                    color='red'><form:errors path='numberOfBikesBooked' /></font></td>
            </tr>
            <tr>
                <td><form:select path="numberOfBikesBooked">
                        <form:options items="${possibleBikeValues}" />
                    </form:select></td>
            </tr>
            <tr>
                <td><form:label path="firstName">Vorname</form:label><font
                    color='red'><form:errors path='firstName' /></font></td>
            </tr>
            <tr>
                <td><form:input path="firstName" /></td>
            </tr>
            <tr>
                <td><form:label path="lastName">Nachname</form:label><font
                    color='red'><form:errors path='firstName' /></font></td>
            </tr>
            <tr>
                <td><form:input path="lastName" /></td>
            </tr>
            <tr>
                <td><form:label path="mailAddress">Email</form:label><font
                    color='red'><form:errors path='mailAddress' /></font></td>
            </tr>
            <tr>
                <td><form:input path="mailAddress" /></td>
            </tr>
            <tr>
                <td><input type='submit' value='Buchen!' class="btn" /></td>
            </tr>
        </table>
        <form:hidden path="Date" />
        <form:hidden path="cancelURI" />
    </form:form>

The Controller like this (the add validates the Form and the AvailableBikes renders the Form):

@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addPerson(@ModelAttribute("new-booking") @Valid Booking booking,
        BindingResult result, Map<String, Object> model) {

    if(result.hasErrors()){
        return "booking";
    }

    logger.info(String.format("Adding a Booking with the following data: Name: %s %s Email: %s Date: %s", booking.getFirstName(), booking.getLastName(), booking.getMailAddress(), booking.getDate()));

    bookingService.addBooking(booking);

    model.put("booking", booking);

    return "success";
}

@RequestMapping(value = "/AvailableBikes", method = RequestMethod.POST)
public String getAvailableBikes(@ModelAttribute("date") StringDate parDate,
        Map<String, Object> model) {

    // Parse Date
    DateFormat dateFormat = new SimpleDateFormat("dd-MM-yy");
    Date inDate = Calendar.getInstance().getTime();
    try {
        inDate = dateFormat.parse(parDate.getDate());
    } catch (ParseException e) {
        logger.severe(e.getMessage());
        e.printStackTrace();
    }

    logger.info(String.format("Listing the available Bookings for the %s", inDate));

    int availableBookings = bookingService.getAvailableBookings(inDate);
    model.put("NumAvailableBikes", Integer.toString(availableBookings));

    // TODO: Fix this with the AvailableBikesRange
    List<Integer> allPossibleBikeValues = new ArrayList<Integer>();
    for (int i = 1; i <= 30; i++) {
        allPossibleBikeValues.add(i);
    }
    model.put("possibleBikeValues", allPossibleBikeValues);

    // Get ready for new booking
    Booking booking = new Booking();
    booking.setDate(inDate);
    // TODO: How to handle the Cancel?
    booking.setCancelURI("xyz");
    model.put("new-booking", booking);

    return "booking";
}

Here is the Model (I think this won't matter):

@Entity public class Booking {

@Id
@GeneratedValue
private Integer id;

@Column(name = "date", nullable = false)
@NotNull
private Date date;

@Column(name = "numberOfBikesBooked", nullable = false)
@Min(1)
@Max(100)
private int numberOfBikesBooked;

@Column(name = "mailAddress", nullable = false)
@Email
private String mailAddress;

@Column(name = "firstName", nullable = false)
@NotBlank
@Size(min=3, max=100)
private String firstName;

@Column(name = "lastName", nullable = false)
@NotBlank
@Size(min=3, max=100)
private String lastName;

@Column(name = "cancelURI", nullable = true)
private String cancelURI;

1 Answer 1

1

The problem with the firstName is that you have two form:input for it :) The second must be mailAddress.

The problem with precalculated values is simple: when you return the view name string, controller does not get called. The model for this case is empty. The are several solutions: you can extract precalculation to a method and call it where you need. Or (I prefer this way) you can set up interceptors to populate model for urls:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/context-relative-url.html" />
        <bean class="a.b.c.DropDownPopulator" />
    </mvc:interceptor>
</mvc:interceptors>

And the populator:

public class DropDownPopulator extends HandlerInterceptorAdapter {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        Map<String, String> result = new LinkedHashMap<String, String>(/*map for dropdown*/);

        modelAndView.addObject("values", result);
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Typical Copy and Paste Error, thank you for this. Any Idea on the precalculated Values?
Wow, your the best, nice edit. I started to google for the interceptor, but I think this could be a more a less done solution! Just one thing, where to put the XML? In the applicationContext.xml?
Yep. Do not forget to add mvc namespace, if it is not added already. The interceptor will be called when request URL mathes the configured one.
I implemented the DropDownPopulator and it works pretty nice. The Problem is, it's not called, when the /add fails, because i'm on the example.com/add URL and not on example.com/AvailableBikes (The /add will be called by the Form, and won't change again, if the Form fails). Any ideas on this one? Also, is it possible to check the input request, for the URL?
Yes, it is the problem with this approach. You need to configure the same interceptor on every URL you need. And yes, you can add interceptor to wildcard URL, and check for the request URL manually and then populate model or not.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.