Reusing components and models
If I understand correctly, currently Wicket creates a new Page
instance, if I request a BookmarkablePage. Take SignIn example:
* from examples list click on "signin"
* because user is not logged in, he is redirected from Home to SignIn
* new SignIn page is created and form is displayed
* If username is wrong, _same_ page is redisplayed with error message
(I turned versioning off)
* If I go back to the example list, and then forward, I will see the
same form. Good.
* But if i go back to the example list, and click on the "signin"
link, I will get a new instance of SignIn Page with empty model. This
is what I actively dislike.
My main grief is that I want to retain model if I click the "signin"
link again. Also, reusing the Page object makes sense as well, it
decreases the load on the session.
On the other hand, if the Page instance is preserved, and its model is
preserved, how do I clean the form fields? I need some kind of reset
request parameter for that.
Here is what I have done just as a sketch, it works, but I am sure it
can be implemented more cleanly.
(1) Initialization request parameter
------------------------------------
The init parameter can be sent to a Page instance to signal that
Page's model should be cleaned. This parameter produces the same
effect as current Wicket behavior, when a new and clean instance is
created each time I click a link. Only in case of parameter, the
instance can be reused.
Apparently, I have to create a custom Link type, which will be
rendered as url with init parameter. I have not done that, for now I
pass the parameter directly. I use "wicket-init" as parameter. I also
set it to 1 just because sometimes it is easier to search a request
parameter which has value. So, the request looks like:
http://localhost:8080/wicket-examples/signin?wicket-reset=1
In WebRequestCycle.parseRequest() I check if parameter is passed. If
yes, I call responsePage.setMustReset(true). Methods setMustReset()
and reset() are defined in Page class. I have to set reset flag
instead of calling reset() directly, because I might be addressing to
one page like Home, but in the end be redirected to another, like
SignIn. So, I set a flag, which is then set again on the "real" page.
(2) Reusing page instance
-------------------------
I am reusing instance of SignIn page, because originally it is created
in AuthenticatedWebPage.checkAccess() method, so it is easy to plug in
my own code. But the instance of the Home page itself seems to be
recreated each time, and I don't know yet how to reuse its instance.
The newPage() methods are not called from anywhere but
AuthenticatedWebPage, so I don't know yet how Wicket creates new
instance internally.
Anyway, here is what I do to reuse SignIn instance. I defined
AuthenticatedWebPage.myNewPage() method which creates a new page.
Actually, I wanted to override Page.newPage() but it is final. In
myNewPage() method I check if the page to be created should have a
single instance. I created a static field:
static protected boolean singleton;
which I set to true for Page classes which are supposed to have a
single instance. So, the myNewPage() method looks like this:
public Page myNewPage(final Class c) {
Page page = null;
Field isSingletonField = c.getDeclaredField("singleton");
boolean isSingleton = isSingletonField.getBoolean(null);
if (isSingleton) {
// This map is defined in SignInSession class
Map recentPages = getSignInSession().getRecentPages();
if (recentPages != null) {
page = (Page) recentPages.get(SignIn.class.getName());
}
// Reset existing page, this is what we are after
if (page != null && page.isMustReset()) {
page.reset();
}
// Singleton page not found, create and store
if (page == null) {
page = newPage(c);
recentPages.put(c.getName(), page);
}
// Page is not singleton, create unconditionally
} else {
page = newPage(c);
}
return page;
}
SignInSession class contains page map:
protected Map recentPages = new HashMap();
public Map getRecentPages() { return recentPages; }
(3) How it works
----------------
I define all pages in SignIn example as nonversioned. SignIn page is
defined as singleton. When I access Home page using
http://localhost:8080/wicket-examples/signin link, the checkAccess()
method creates a new instance of SignIn page if it was not created
yet, or just reuses existing one. Therefore, I see the same data I
entered before.
If I click http://localhost:8080/wicket-examples/signin?wicket-reset=1
link and SignIn instance exists, then Home.mustReset is set, then
SignIn.mustReset is set, then myNewPage is called and in the end it
calls SignIn.reset(), which has the following code:
public void reset() {
// This check is here because I think maybe
// I want to call reset() from outside unconditionally
if (isMustReset()) {
MarkupContainer form = null;
if (formId != null) {
form = (MarkupContainer) get(formId);
}
// Resetting form model by removing
// the corresponding objects altogether
if (form != null) {
properties.remove("username");
properties.remove("password");
}
}
}
So, the fields are cleared and a user sees an empty form.
(4) TO-DO
---------
Now I need to find out how to reuse Home page, and I need to wrap
reset parameter into a ResetLink class or something.
I would like this functionality to be included in Wicket. I can clean
up and submit the code patches if this would be considered worthwhile.
Michael.
-------------------------------------------------------
SF.Net email is Sponsored by the Better Software Conference & EXPO
September 19-22, 2005 * San Francisco, CA * Development Lifecycle