Working with REAL Java Objects

Tutorial - Apache Wicket: The Fun Web Framework - Part 3

      What about AJAX?

Okay, so it's easy to make a link. So what, right? Now watch how easy it is to make that link an AJAX link that fully supports non-AJAX requests. First, we make our label instance final so that we can access it within the anonymous inner class and we call setOutputMarkupId(true) so that its unique ID is rendered in the span tag and it can be found in the DOM on the client-side. We change our Link class to AjaxFallbackLink and change the onClick method signature to match the superclass. The AjaxRequestTarget that is passed in can be thought of as a queue of things to send back in XML to the client side for processing. By adding the label to that queue, it will be re-rendered, streamed across the wire in XML and replaced in the DOM on the client-side. When it is re-rendered, it will use its model (remember counterModel?) to get the up-to-date value to render. All without us writing a line of code that does any transformations or DOM manipulation!

Why do we check if the AjaxRequestTarget is null? Because this link will automatically work for you on browsers that do not have JavaScript enabled. In this event, the page would simply re-render just like it did with a regular link. Yes, you heard me – it degrades gracefully without any additional work! Almost too good to be true, right? You'll find that Wicket works this way for you by default most of the time. Form submissions and even file uploads can be done in either regular HTTP or AJAX with very little, if any, extra work. And in the 1.5 version, a very handy event mechanism is added to make the work of adding components to the AjaxRequestTarget even easier. 

HomePage.java (note: only the constructor is shown)

public HomePage() {
    IModel<Integer> counterModel = new AbstractReadOnlyModel<Integer>() { … }
    // above code folded for brevity

    final Label label = new Label("counter", counterModel);
    add(label.setOutputMarkupId(true));
    
    AjaxFallbackLink<Void> link = new AjaxFallbackLink<Void>("increment") {
        private static final long serialVersionUID = 1L;

        @Override
        public void onClick(AjaxRequestTarget target) {
            counter++;
            if (target != null)
                target.addComponent(label);
        }
    };
    add(link);
}

Working With User Input

In many web applications, a lot of the work to be done in building the app goes into the forms that accept user input. In Wicket, forms are regular objects just like all other components. To see an example, let's look at a simple form that accepts a user's name, email, and date of birth

 

Looking at the HTML, you see that there is nothing special about it. Your designers can give you an HTML form and you can add wicket:id attributes to the input fields and the form itself. The div with the wicket:id “feedback” is a placeholder for a built-in Wicket component that shows the user all feedback messages that were generated by the form. In the picture above, the user has just submitted the form with bad data which caused validation messages to appear in our feedback panel component. These can easily be customized to have a feedback panel near each form field that only shows the messages for that particular field – a common requirement in many applications.

Example of a Single Form

<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
    <head>  
        <title>Quickstart Homepage</title>
    </head>
    <body>
        Add / Edit Person:
        <div wicket:id="feedback"></div>
        <form wicket:id="form">
            First Name: <input type="text" wicket:id="firstName" /><br />
            Email Address: <input type="text" wicket:id="email" /><br />
            Date of Birth: <input type="text" wicket:id="dob" /><br />
            <input type="submit" value="Save" />
        </form>
    </body>
</html>

Now looking at the Java code, you see that we created two constructors. This is so the form can be used as both an “add person” and an “edit existing person” form. By default, the no-argument constructor is called which makes this an “add person” form because it is using a model that holds a new person. But, a link could easily be written that shows the user an “edit person” page simply by passing a model to the page with an existing person. (Note that we don't cover that example in this article, but if you were doing this, you would likely want to use a LoadableDetachableModel, which is discussed more in the section on models.)

Next you see that we instantiate our Form object. We implement the onSubmit method and make it contain our business logic – in this case a simple call to a service-layer class. Moving along, you see where we create each field and configure it with our UI rules – which fields are required, and what is required of them (for instance, a valid email address format). What you don't see is refreshing – no calls to HttpRequest to get strings out, no conversion of strings to dates, no manual retrieval of data from our form post and no manual setting of data on our backing Person object. In long forms, this saves hundreds of lines of copy-and-paste and highly error-prone code. This is because we are dealing with real Java objects – not a bunch of strings that have to be manually manipulated. See the form processing image in the “top ten” list and the link to the full top ten list below for more information.

Note that we are using a service-layer object (IPersonService) to do our persistence. This is a Spring-managed bean, and it is extremely simple to use because of Wicket's built-in Spring (or Guice, if you prefer) integration.

Form object 

public class HomePage extends WebPage {
    
    @SpringBean
    IPersonService personService;
    
    public HomePage() {
        this(new Model<Person>(new Person()));
    }

    public HomePage(IModel<Person> model) {
        Form<Person> form = new Form<Person>("form", model) {
            protected void onSubmit() {
                Person person = this.getModelObject();
                System.out.println("Saving: " + person);
                personService.savePerson(person);
            }
        };
        
        TextField<String> firstName = new TextField<String>("firstName", new PropertyModel<String>(model, "firstName"));
        firstName.setRequired(true);
        form.add(firstName);
        
        TextField<Date> dob = new TextField<Date>("dob", new PropertyModel<Date>(model, "birthDate"));
        dob.setRequired(true);
        form.add(dob);
        
        TextField<String> email = new TextField<String>("email", new PropertyModel<String>(model, "emailAddress"));
        email.setRequired(true);
        email.add(EmailAddressValidator.getInstance());
        form.add(email);
        
        add(form);
        add(new FeedbackPanel("feedback"));
    }
}

What are Models?

In the code examples, you've seen several mentions of models, earlier described as “a wrapper for retrieving or setting a piece of data”. So, just what is a model, and why is it so important? As mentioned earlier, Wicket stores your entire component hierarchy (the page and all the components you added to it, as well as components added to those components and so on) in memory, and then to disk so that you have a stateful way of working with Java objects over the stateless HTTP. But these components that display this data do not necessarily need to store the data itself between requests. They only need to store their state – perhaps a flag indicating if they are visible or invisible, enabled or disabled, expanded or collapsed. It is very likely that the actual data they are displaying is already persisted elsewhere – likely in the database, meaning that we don't really need to save it to the users' HTTP sessions. This is one reason models exist – to cut down on the amount of memory needed to persist our component hierarchy.

A second, and just as important reason, is to abstract the retrieval and storage of data from the component itself. Think of our simple TextField component in our form example above. It does not need to know if the string that it is editing is coming from the session, from a web service, or from a field on a POJO. It simply needs to be able to get a string from somewhere and set an edited string somewhere. The IModel interface has two methods for this – getObject and setObject – that allow it to do just that. Before you start using Wicket, you should review my resource page on them too

    

Pages

Jeremy Thomerson
Jeremy Thomerson

What do you think?

Comments

Latest opinions