• Fat Cats Boardgame
  • Wicket UI Library
  • About Me

Java and Wicket web development thoughts, tutorials, and tips

roman@coderdreams.com
Coder DreamsCoder Dreams
  • Fat Cats Boardgame
  • Wicket UI Library
  • About Me

Understanding Wicket Models

October 5, 2019 Posted by Roman Sery models, wicket No Comments

Models are the key

The most important part of being an effective Wicket developer is understanding how to use Models. The basic rule to remember is, always use Models! Using Models correctly is important for keeping your UI simple and avoid having out-dated data. It’s also important for performance reasons to keep the page size small. We’ll look at both of these, but first a general discussion of Models.

IModel interface

A Wicket Model is a class that implements the IModel interface. The interface is very simple, which consists of a getter method, setter method, and the type of the object. The setter can be excluded in the case of read-only models. Let’s look at an example model to retrieve/set a user’s email:

Model<String> userEmailModel = new Model<String>() {
            @Override public String getObject() { return user.getEmail(); }                
            @Override public void setObject(String object) { user.setEmail(object); }
        };

Now this is just for the purposes of demonstrating how simple a model implementation can be, but I would recommend you use other implementations of IModel that are provided by Wicket. Fortunately, you can use LambaModel’s which we looked at in a previous post!

Let’s look at an example of how to use this model in a edit user form page:

TextField<String> emailField = new TextField<String>("email", userEmailModel);

Using Models to keep UI simple and synced

Let’s look at a slightly more complex example of a user profile page which contains the user’s country and state selections. Selecting the country will update the state dropdown. We also have a label that displays the user’s address in the format {state, Country}. Using models, we can implement it in this way:

countryField = new DropDownChoice<CountryCode>("country", LambdaModel.of(user::getCountryCode, user::setCountryCode), new ListModel<CountryCode>(CountryCode.VALUES));
countryField.add(new OnChangeAjaxBehavior() {
    @Override protected void onUpdate(AjaxRequestTarget target) { target.add(stateField, stateCountryLbl); }
});
testForm.addOrReplace(countryField);

stateField = new DropDownChoice<State>("state", LambdaModel.of(user::getState, user::setState), new LoadableDetachableModel<List<State>>() {
    @Override
    protected List<State> load() {
	return user.getCountryCode() == null ? Collections.emptyList() : State.getByCountry(user.getCountryCode());
    }
});
stateField.add(new OnChangeAjaxBehavior() {
    @Override protected void onUpdate(AjaxRequestTarget target) { target.add(stateCountryLbl); }
});
testForm.addOrReplace(stateField.setOutputMarkupId(true));

stateCountryLbl = new Label("stateCountry", new LoadableDetachableModel<String>() {
    @Override
    protected String load() {
	return String.format("%s, %s", user.getState() != null ? user.getState().getAbbreviation() : "",
		user.getCountryCode() != null ? user.getCountryCode().getName() : "");
    }
});
testForm.addOrReplace(stateCountryLbl.setOutputMarkupId(true));

So we first have a country selection which updates the state dropdown options upon being changed. We can see that when the countryField is updated, it adds the stateField to the target which re-renders it. The stateField uses a LDM(LoadableDetachableModel) to return the State’s based on the country selection.

The most important thing you should notice is that because we are using models correctly, we never have to manually update the options for the stateField; they are always up to date. Nor do we have to manually update the value of stateCountryLbl.

A wrong way to do it

We’ve seen a correct way to do it, now let’s show a typical implementation you might see without a good understanding of models:

countryField = new DropDownChoice<CountryCode>("country", LambdaModel.of(user::getCountryCode, user::setCountryCode), new ListModel<CountryCode>(CountryCode.VALUES));
countryField.add(new OnChangeAjaxBehavior() {
    @Override protected void onUpdate(AjaxRequestTarget target) {
	stateField.setChoices(user.getCountryCode() == null ? Collections.emptyList() : State.getByCountry(user.getCountryCode()));
	stateCountryLbl.setDefaultModelObject(getSateCountryLbl());
	target.add(stateField, stateCountryLbl);
    }
});
testForm.addOrReplace(countryField);

stateField = new DropDownChoice<State>("state", LambdaModel.of(user::getState, user::setState), user.getCountryCode() == null ? Collections.emptyList() : State.getByCountry(user.getCountryCode()));
stateField.add(new OnChangeAjaxBehavior() {
    @Override protected void onUpdate(AjaxRequestTarget target) {
	stateCountryLbl.setDefaultModelObject(getSateCountryLbl());
	target.add(stateCountryLbl);
    }
});
testForm.addOrReplace(stateField.setOutputMarkupId(true));

stateCountryLbl = new Label("stateCountry", getSateCountryLbl());
testForm.addOrReplace(stateCountryLbl.setOutputMarkupId(true));


private String getSateCountryLbl() {
	return String.format("%s, %s", user.getState() != null ? user.getState().getAbbreviation() : "",
	user.getCountryCode() != null ? user.getCountryCode().getName() : "");
}

Notice the highlighted lines. Without models, we are now responsible for manually keeping the value of stateCountryLbl updated as well as the options for stateField. It might not seem like a huge difference from this example, but in more complex real-world UI’s, this type of approach will create a lot of problems.

Performance impact on Serialization

The other issue is the impact on Wicket page size. When you don’t correctly use detachable models such as LoadableDetachableModel, you might inadvertently be serializing large lists of objects. Let’s look at an example of a dropdown that displays a user selection:

 List<User> allUsers = userService.getAllUsers();
 DropDownChoice<User> userSelectionField = new DropDownChoice<User>("userSelection", new PropertyModel<User>(this, "selectedUser"), allUsers);

It might not seem obvious, but when the page is serialized by Wicket, the allUser list will be serialized along with the page. Imagine you have 5K users in your database! The correct way of doing this, is to use a detachable model:

DropDownChoice<User> userSelectionField = new DropDownChoice<User>("userSelection", new PropertyModel<User>(this, "selectedUser"), new LoadableDetachableModel<List<User>>() {
    @Override protected List<User> load() { return userService.getAllUsers(); }
});

Now the list of users does not get serialized along with the page. You need to always be aware of large objects or lists of objects being serialized.

Hopefully this has given you a good handle on using models correctly, I will probably write more posts about the topic in the future. Go forth and be a model citizen(developer) 馃檪

No Comments
Share
7

About Roman Sery

I've been a software developer for over 10 years and still loving Java!

You also might be interested in

How to implement record locking in Wicket

May 28, 2020

The concept of record locking is useful when you want to allow only one user to edit a data record at a time. In a multi-user environment, you want to avoid the case where multiple users are simultaneously editing the same record and possibly overwriting each other's changes.

Wicket quick tip #2: Print to PDF

Apr 11, 2020

A series of quick tips aimed at improving your productivity. Generating PDF's from Wicket pages.

Happy holidays

Dec 24, 2019

Just a short note

Categories

  • aws
  • customization
  • database
  • debugging
  • enum
  • java
  • models
  • performance
  • projects
  • react
  • software design
  • Spring
  • tool
  • Uncategorized
  • wicket

Recent Posts

  • Rent Day
  • Self-contained Wicket Fragments
  • Pros and cons of unit testing
  • Themeable React Monopoly board
  • Please dont use client-specific release branches

Recent Comments

  • TCI Express Thanks for sharing such insightful information. TCI Express truly stands out as the best air logistics company, offering fast, secure, and efficient air express and cold chain transportation services....

    Tracking down a bug in production Wicket application 路  March 25, 2025

  • Tom Error: A zip file cannot include itself Can you please correct the plugin part so it doesn't use the same folder as input?

    Deploying Spring Boot app to AWS Beanstalk with Nginx customization 路  September 3, 2021

  • Golfman: Reality always wins I've used both Wicket and front-end JS frameworks and, having worked extensively on both, I can tell you that "Speed of development" is definitely NOT with the JS frameworks. You basically end up...

    Five reasons you should use Apache Wicket 路  August 29, 2021

  • Kiriller Sorry can not agree with you, wicket might be a well built technical framework. But the advantages of using a front-end framework like react.js and vue.js can not be beaten by Wicket nowadays. - Speed...

    Five reasons you should use Apache Wicket 路  August 23, 2021

  • Bernd Lauert Sorry but i have to refute your claims with the following arguments: 1. the Wicket community may be small but it is also very responsive, you usually get a helpful answer from the core devs on the...

    Five reasons you should use Apache Wicket 路  July 1, 2021

Archives

  • May 2021
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • April 2020
  • March 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • September 2019
  • August 2019
  • July 2019

Contact Me

Send Message
Prev Next