How to suppress the token change for Hyperlink

317 views
Skip to first unread message

Kevin Qiu

unread,
May 5, 2009, 12:15:02 PM5/5/09
to Google Web Toolkit
Hi all,

I have a bunch of hyperlinks and I want them to act like buttons
(respond to ClickEvent) but I don't want them to change the history
token which will mess up my history mechanism. How would I do that? I
tried setting target history token to null but it doesn't work.

Thanks,

Isaac Truett

unread,
May 5, 2009, 2:50:27 PM5/5/09
to Google-We...@googlegroups.com
Use Labels, give them ClickHandlers, and style them the same way you
style hyperlinks.

George Georgovassilis

unread,
May 5, 2009, 3:12:48 PM5/5/09
to Google Web Toolkit
Hi Kevin,

had the same problem. Similar to Isaac's solution I used labels to do
the trick, but that left a shallow aftertaste since you have to put a
lot of work into them (make them focuseable etc for keyboard
navigation) and they still don't feel like real links. Left aside the
cool stuff you can do by right clicking on links.

In one case I manipulated the history mechanism to check for
conditions in which a "rollback" should happen. The short storry is:
1. Yes, I _do_ assign a history token to that link
2. a history listener listens to that token and sets a boolean
variable (i.e. dontChange)
3. the listener then invokes history.back()
4. the history handler is invoked for the pervious token, but because
the variable dontChange is set, will not perform any processing for
that token but just reset the variable

Uggly, but at least you can use real links.
Hth,
G.

Kevin Qiu

unread,
May 5, 2009, 3:28:58 PM5/5/09
to Google Web Toolkit
Thanks for the replies guys.

I tried overwrite onBrowserEvent for Hyperlink and cancel event
bubbling when the link is clicked but it didn't work out. It still
invoked history change. Maybe I didn't do it the right way, but
anyways, I used a styled label - it's a quick and easy fix.


On May 5, 3:12 pm, George Georgovassilis <g.georgovassi...@gmail.com>
wrote:

Ian Bambury

unread,
May 5, 2009, 7:38:12 PM5/5/09
to Google-We...@googlegroups.com
What is the advantage of a 'real link'? Who will ever know?

If you need focus and no history, use a styled PushButton

Ian

http://examples.roughian.com


2009/5/5 George Georgovassilis <g.georgo...@gmail.com>

X

unread,
May 5, 2009, 8:57:35 PM5/5/09
to Google-We...@googlegroups.com
Download the <a href='google.ca?q=download+gwt+incubator'>incubator</a>, and use the HyperlinkOverride class as an example.

It is a proposed change to the regular Hyperlink object that cares not whether the alt, ctrl or shift buttons are pressed {allowing users to spawn multiple pages of your app at once}.  It implements ClickListener, so it's not really 1.6 friendly, but it DOES show you how override the general functionality of one class with another. 

If you redo this class with your own event handling mechanism, you can just deferred-binding overwrite all your hyperlinks with some fancy new class that does whatever you need.

For example, one hack I've been known to use is to give a group of button-links titles that start with different characters, and then sub in a class that auto-adds a single static listener that switches over the first character of the target element's title to call some static tasks {think OK, Cancel, Help buttons, with anonymous inner class implementations}.  ...I only use this hack so I can just define an interface and some static functions to reduce boilerplate code on tiny projects...


    <replace-with class="com.yourdomain.ui.LinkOverride">
        <when-type-is class="com.google.gwt.user.client.Hyperlink" />
    </replace-with>

Thomas Broyer

unread,
May 6, 2009, 9:18:27 AM5/6/09
to Google Web Toolkit


On 5 mai, 21:12, George Georgovassilis <g.georgovassi...@gmail.com>
wrote:
> Hi Kevin,
>
> had the same problem. Similar to Isaac's solution I used labels to do
> the trick, but that left a shallow aftertaste since you have to put a
> lot of work into them (make them focuseable etc for keyboard
> navigation) and they still don't feel like real links. Left aside the
> cool stuff you can do by right clicking on links.

OK, so just use an Anchor instead of Hyperlink if you want a link
without "history management".

Alyxandor

unread,
May 6, 2009, 11:47:02 AM5/6/09
to Google Web Toolkit
George's hack with the boolean for "don't do history stuff" is
actually a surprisingly good way to deal with History Events.

In order to achieve History events from multiple sources with multiple
targets, some validation on HistoryToken is needed, and using a
boolean variable will let you change this functionality
programmatically. This might not seem like a big deal until you
notice how you can't click a history-link after it's been clicked
once. Say, you open an iframe on a history event, and once it's
closed, you want to let the user click on it again without having to
click on anything else. The only way is to inject a useless
HistoryToken with History.newItem("do nothing",false);, but now you
will lose directionality {as forward to "do nothing" looks like
backward to "do nothing"}, which means you now need to inject ("go
forward",false); AND ("go backward",false), and when a REAL history
event is fired, do History.goBack();History.goBack(); or
History.goForward();History.goForward();

It may seem like a gross hack, but it DOES help you to make your
application more static and bookmarkable.

Using anchor overrides and doing it all in javascript means you lose
state when the user actually presses back or forward, AND when power-
users view your site, they might want to open a few windows at the
same time by Ctrl+clicking links. We all do it, and if every link
opens "javascript:void(0);", the only way to have multiple tabs of
content is to open each to the home page and manually navigate the to
the application state you want to test / experience.

Taking the time to hack with History support will be worth it when
your users start bookmarking your content, and that bookmark actually
points to the content they want to see next time.

Thomas Broyer

unread,
May 6, 2009, 12:40:33 PM5/6/09
to Google Web Toolkit


On 6 mai, 17:47, Alyxandor <a.revolution.ultra.b...@gmail.com> wrote:
> George's hack with the boolean for "don't do history stuff" is
> actually a surprisingly good way to deal with History Events.
>
> In order to achieve History events from multiple sources with multiple
> targets, some validation on HistoryToken is needed, and using a
> boolean variable will let you change this functionality
> programmatically.  This might not seem like a big deal until you
> notice how you can't click a history-link after it's been clicked
> once.  Say, you open an iframe on a history event, and once it's
> closed, you want to let the user click on it again without having to
> click on anything else.  The only way is to inject a useless
> HistoryToken with History.newItem("do nothing",false);, but now you
> will lose directionality {as forward to "do nothing" looks like
> backward to "do nothing"}, which means you now need to inject ("go
> forward",false); AND ("go backward",false), and when a REAL history
> event is fired, do History.goBack();History.goBack(); or
> History.goForward();History.goForward();
>
> It may seem like a gross hack, but it DOES help you to make your
> application more static and bookmarkable.

I find it much easier/better to just use an Anchor with a ClickHandler
that mimics the Hyperlink (Hyperlink would be equivalent to wrap the
Anchor in a SimplePanel and set the panel's primary style name to gwt-
Hyperlink).

final String TARGET_HISTORY_TOKEN = "targetHistorytoken";
Anchor anchor = new Anchor("text", "#" + TARGET_HISTORY_TOKEN);
anchor.addClickHandler(new ClickHandler() {
static final HyperlinkImpl impl = GWT.create(HyperlinkImpl.class);

public void onClick(ClickEvent event) {
if (impl.handleAsClick(Event.as(event.getNativeEvent()))) {
// do your stuff here and choose whether to call the
following History.newItem
History.newItem("targetHistoryToken");
// ...though always prevent default (a priori), unless that's
not what you want...
event.preventDefault();
}
}
});

> Using anchor overrides and doing it all in javascript means you lose
> state when the user actually presses back or forward, AND when power-
> users view your site, they might want to open a few windows at the
> same time by Ctrl+clicking links.  We all do it, and if every link
> opens "javascript:void(0);", the only way to have multiple tabs of
> content is to open each to the home page and manually navigate the to
> the application state you want to test / experience.
>
> Taking the time to hack with History support will be worth it when
> your users start bookmarking your content, and that bookmark actually
> points to the content they want to see next time.

In your "iframe" sample use case above, though, you're mis-using the
history/hyperlink, as the history state is not "stable" (you can
"close the iframe", whatever it means to you), and you're expecting
the hyperlink to take an action (re-open the "closed iframe"), not
only navigate to a given history state. It would be quite easy to
solve (hack!) by testing the current history getToken() and then call
newItem(x) or fireCurrentHistoryState(). I'm using such a "hack" (in
response to async requests though; instead of rewriting my app) and it
works pretty well.

George Georgovassilis

unread,
May 7, 2009, 3:36:36 AM5/7/09
to Google Web Toolkit
Anyone who uses the keyboard, the context menu or uses some browser
extension that adds functionality to links or just overrides link
styles in the browser will notice the forgery ;-) You can't win the
duck typing [1] race here. Thomas' Anchor solution is of course better
than my history-trick, to my shame I must admit the mere existence of
the Anchor class has eluded me so far.

To put this discussion into some context, history events can be used
not only for history, but for the main and only messaging mechanism in
the application. You don't need to register click handlers on links,
buttons, nor do you need to register any particularly clever callbacks
to RPC services. They just fire history events which you can process
in client-side controller classes (I call them "flows"). Is very
convenient, especially through development because you don't need to
click through to that page you are currently working on but can
instantly navigate to it by adding the right history token to the
initial URL.

[1] http://en.wikipedia.org/wiki/Duck_typing

On May 6, 1:38 am, Ian Bambury <ianbamb...@gmail.com> wrote:
> What is the advantage of a 'real link'? Who will ever know?
> If you need focus and no history, use a styled PushButton
>
> Ian
>
> http://examples.roughian.com
>
> 2009/5/5 George Georgovassilis <g.georgovassi...@gmail.com>

Alyxandor

unread,
May 14, 2009, 7:22:43 AM5/14/09
to Google Web Toolkit
@Thomas, AYE! Your example does work best, provided users don't Ctrl
+Click to open in a new Window.

My iframe example was certainly incomplete. I leave the History
tokens alone {don't inject directional "do not fire" tokens}, until
the user clicks the close button or opens a new link. THEN, if the
user clicks close {but not open} I add the extra tokens to change the
hash, because #Links won't refire if they are the same. I used
multiple tokens and internal booleans to record which direction the
user is navigating through history, as well as a static String to make
sure nothing gets opened twice. The user can go ahead and click on it
again to re-open it, or click back/forward to close the current iframe
and open the next/prev target. If I just insert a single "do nothing"
token, the user has to press back or forward twice to use their
history. {and I have no way of knowing whether the user has pressed
back or forward unless I use the two token method}.

Problem is, I want to programmatically remove the iframe container
when the user closes it, AND when the user clicks on another valid
link... I'll just copy n paste...


private boolean xManual = true,xAuto=false;//Tricky variable for
history magic
private String xCurHist="";//Records the current history position
public void onValueChange(ValueChangeEvent<String> event) {//Whenever
it changes
xCurHist=event.getValue();//Get the current #Token

if (xCurHist.length()>3){//If that value is longer than three
characters,
xLocus.xNotify(xCurHist);//Send notification to any listeners {that
build widgets}. This is where iframe is build and added
xAuto=false;//Remember that the user caused this action
}
else if (xCurHist.equals(xNS.xGO_BACKWARD)&&xManual){//If we are
programaticaly changing history
xManual=false;//Reset from manual to auto
xAuto=true;//ditto
History.back();//Go back once, to the GO_FORWARD item
History.back();//Go back AGAIN, to the actual previous history item
xManual=true;//Change back to auto
//This is called because I add extra history tokens, * and . to
note user's history direction
}
else if (xCurHist.equals(xNS.xGO_FORWARD)&&xManual){//If the user
has pressed forward, and there is a GO_FORWARD token
xManual=false;//Set to manual
xAuto=true;
History.forward();//Skip the go back token
History.forward();//Go to the actual history item
xManual=true;//Back to auto
}

}



AND the onUnload of a static Panel called xPop, where the iframe is
added and displayed... It is removed when the user clicks close or
opens a new link...

protected void onUnload() {//When Widget's removed
try{//Attempt the following; it might fail
final String xCur = History.getToken();//Get the current history
if (!xAuto)//If we're on manual settings
new Timer(){//Hack used in case this widget was detached by clicking
a valid token
@Override
public void run() {
try{
if(xPop.isAttached())//xPop is iframe container element. If a new
panel is made and attached, a valid token was clicked, and we don't
need the hack
return;//Stop, or else we break the history
if (xCur.length()>0&&xCur.equals(History.getToken())){//If we're
still on a history token
History.newItem(xNS.xGO_FORWARD,false);//Insert a GO_FORWARD
marker
History.newItem(xNS.xGO_BACKWARD,false);//Then a GO_BACKWARD.
//This is how we tell if the user pressed back or forward in the
browser
}
}catch(Exception e){//If something fails
xLog.xErrorLog(e, "History Check 2 In xContent::xBuildUI");//Save
an error message to server
}
}
}.schedule(300);//Wait 300 milliseconds, in case a new page is
built...
}catch(Exception e){//If something bigger fails
xLog.xErrorLog(e, "History Check 1 In xContent::xBuildUI");//Save a
different error log
}



Don't mind the comments, this is code I marked up because clients who
knew very little about gwt had to read it.


...And yes, I DO overcomplicate everything. Mainly because I'm oh-so-
picky. This was what it took for me to get directional history
support that would prevent two iframes from being opened, prevent
"link-lock" when the iframe was closed without changing the token,
detect which direction the user clicked, and differentiate between
closing the iframe and opening a new one {which also closes the old
window, but doesn't bother playing with the tokens.}

Simple answers are usually the best, but I had to do this because my
client was picky and actually tested everything.

Thomas Broyer

unread,
May 14, 2009, 9:11:03 AM5/14/09
to Google Web Toolkit


On 14 mai, 13:22, Alyxandor <a.revolution.ultra.b...@gmail.com> wrote:
> @Thomas, AYE!  Your example does work best, provided users don't Ctrl
> +Click to open in a new Window.

That's handled by the "if (impl.handleAsClick(Event.as
(event.getNativeEvent())))" line (in 1.6; in 1.5 you'd have to use
something similar to the HyperlinkOverride from the Incubator)

[...]
> ...And yes, I DO overcomplicate everything.  Mainly because I'm oh-so-
> picky.  This was what it took for me to get directional history
> support that would prevent two iframes from being opened, prevent
> "link-lock" when the iframe was closed without changing the token,
> detect which direction the user clicked, and differentiate between
> closing the iframe and opening a new one {which also closes the old
> window, but doesn't bother playing with the tokens.}

If you clearly defined the UI flow before hand, you probably wouldn't
have had to go such a convoluted path. Simple things work best.

> Simple answers are usually the best, but I had to do this because my
> client was picky and actually tested everything.

Did they test clicking twice very quickly on either prev or next page
browser buttons? History (except in IE) is handled using timers that
regularly check the URL and compare its "hash" part with the one
that's supposed to be the "current one"; in the History design, what
counts is the "final" token, not which tokens are navigated through
towards the "final" token (when clicking more than once on the prev/
next browser buttons I mean).

Using the kind of code I described at the ned of my last message would
make things much more reliable (and better represents you UI flow):

1. in onValueChange (history event), open your popup with the iframe
2. use Anchors instead of Hyperlinks (wrap them in a SimplePanel if
you need the wrapping div) and:
a) if History.getToken() is the same as the link's target token,
History.fireCurrentHistorystate()
b) else, call History.newItem
both would result in onValueChange being called and re-open the
popup

(eventually, in 2, I think you could just continue using Hyperlinks
and add a ClickHandler doing the a) thing; the default History.newItem
behavior would in this case not fire the handlers; I just find it
better to use an Anchor here --Hyperlink::addClickHandler will soon be
deprecated, so it seems I'm not the only one thinking this way--)

You could also, of course, call History.newItem in the method that
opens the popup/iframe (ensures that you're at the "correct" history
state), and have your Anchor's ClickHandler and History's
ValueChangeHandler call that method directly (the extra call to
newItem when the method is called from the History's
ValueChangeHandler would be a no-op, but you could eventually
"protect" it comparing History.getToken() with your target token).

I still think you shouldn't use History to open popups (particularly
if you don't change the history state when the popup is closed).
Reply all
Reply to author
Forward
0 new messages