Many web applications have a login page. On it there is a form where you fill in your username and password. Suppose the page is mounted to:
http://example.com/login
mountBookmarkablePage("login", LoginPage.class);
http://example.com/?wicket:interface=:0:::
One of the proposed solutions is that you redirect to another page in the
onError
method of the form. E.g.
add(new Form(...) {
@Override
protected void onError() {
setResponsePage(LoginPage.class);
}
}
The redirection will happen, and the URL is indeed that of the login page, but you will have no error messages. Instead of going to the second version of the login page, you have created a new instance of the login page! Another attempt
We could pass the error code in the URL:
add(new Form(...) {
@Override
protected void onError() {
PageParameters pp = new PageParameters();
pp.setString("error", "wronglogin");
setResponsePage(new LoginPage(pp));
}
}
Unfortunately, with the default URL encoding stratey the URL will now be:
http://example.com/login/error/wronglogin
A fairly recent addition to Wicket is the HybridUrlCodingStrategy (and subclasses). Let's mount the login page with one of these:
mount(new HybridUrlCodingStrategy("login", LoginPage.class));
If a user now enters wrong credentials, Wicket will redirect you to
http://example.com/login.2
.2
means the second version of the login page for the current session. If the user would delete the .2
from the URL, the HybridUrlCodingStrategy will find the latest available version of the page and serve that. A whole lot nicer but still not perfect.
The solutionIf only if we could force Wicket to not display the version number. Well... we can! We'll have to do some coding first:
// First attempt, DO NOT USE
public class NonVersionedHybridUrlCodingStrategy
extends HybridUrlCodingStrategy {
// ... trivial ctor
@Override
protected String addPageInfo(
String url, PageInfo pageInfo) {
// Do not add the version number as
// super.addPageInfo would do.
return url;
}
}
And now mount with:
mount(new NonVersionedHybridUrlCodingStrategy(
"login", LoginPage.class));
/**
* UrlCodingStrategy that will give the same
* URL for every version of a page.
* @author Erik van Oosten
*/
public class NonVersionedHybridUrlCodingStrategy
extends HybridUrlCodingStrategy {
public NonVersionedHybridUrlCodingStrategy(
String mountPath, Class pageClass) {
super(mountPath, pageClass, false);
}
@Override
protected String addPageInfo(
String url, PageInfo pageInfo) {
// Do not add the version number as
// super.addPageInfo would do.
return url;
}
}
I named the URL strategy 'non versioned' as it no longer makes sense to have multiple versions of a page, just the last one will do. You should therefore also add the following fragment to each page constructor:
setVersioned(false);
Try it out! Go to tipSpot.com and try to login.
Hi Erik,
ReplyDeleteLooks nice! We still have an open ticket to implement RESTful urls in our project. When we get to it, we'll definitely use this. Thanks for the writeup!
Hi Erik,
ReplyDeleteAfter reading your post I played around with your ideas a bit - and implemented some of my thoughts. Changing the UrlCodingStrategy is easy for a LoginPage but more difficult for any page with parameters where other UrlCodingStrategies are used. Currently, FeedbackMessages are stored in the session, so it should be somehow possible to preserve them when redirecting to a bookmarkable page. After looking at the code, I'd say it's a *major* hack though.
However, it would definitely be nice to have out of the box. Maybe a wish for Wicket 1.5?
Best regards, Stefan
Stefan,
ReplyDeleteIt is not so difficult. The key is that you override the addPageInfo method and return the bare URL it is given. This works for any subclass of HybridUrlCodingStrategy. For example, I have a MixedParamHybridUrlCodingStrategy implementation which I extended in exactly the same way.
But you are right that it would be nice to have this in Wicket already. For example a flag in the constructor of HybridUrlCodingStrategy directly.
Great, finally a solution for consistent user-friendly bookmarkable URLs! Sounds like a new url encoding strategy for the next release to me :-)
ReplyDeleteIgor Vaynberg wrote:
ReplyDeletewhile this might work for your usecase this will pretty much break things. the version number is in the url for a reason.
1) it completely kills the backbutton for that page. since the url remains the same the browser wont record your actions in the history. based on what you are trying to do this may or may not be a bad thing.
2) even if you manage to get the back button working this will completely kill applications that use any kind of panel replacement because you no longer have the version information in the url. you have a page with panel A, you click a link and it is swapped with panel B. go back, click a link on A and you are hosed because wicket will look for the component you clicked on panel B instead of A.
in all the applications ive written there was at least a moderate amount of panel replacement going on. one of the applications i worked on had the majority of its navigation consist of panel replacement. so i dont think this is a good idea.
-igor
We are building a mostly stateless app with Wicket (as far as possible) so this fits in really nice.
ReplyDeleteI also added an annotation for the non versioned strategy:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MountDefinition(strategyClass = NonVersionedHybridUrlCodingStrategy.class, argOrder = {})
@Inherited
@Documented
public @interface MountNonVersionedHybrid {}
I've also added another subclass for mixed params: http://pastebin.com/m344b370b
ReplyDeleteIf the URL ends on a /, it should be remove otherwise you get an exception that there are too many path parts.
ReplyDeleteI've tried with a form that was a StatelessForm and it doesn't work.
ReplyDeleteI'm still having an unconsistent url : login/wicket:interface/:0:form$cnx:form$authent::IFormSubmitListener::
Does someone know where is the problem ?
I have problem with this solution. If I open two tabs/windows on two URLs mounted with this strategy, wicket goes in an endless 302/200 loop. Have you experienced this?
ReplyDelete@ildella, no I have not seen this kind of behavior.
ReplyDeleteHi Erik, I use wicket-1.5-M2.1.
ReplyDeleteThere are no more URL coding strategies.
Can you provide an update ?
Thanks
I have the same problem as ildella using GAE. It doesn't happen when I run it locally with Jetty but when I deploy the app to app engine I get an infinite loop :(
ReplyDeleteI enabled all logs but I don't see any exception I don't understand why the page gets reloaded
@Guillaume, the mounting architecture changed in 1.5, check the migration guide from 1.4 to 1.5 in their wiki.
ReplyDelete@ZeoS and @Ildella:
ReplyDeleteThat problem arises when you use "setAutomaticMultiWindowSupport(true)" since the strategy are not attaching any pagemap to each page. Unfortunately, I have not found a good solution for this.