When we talk about an interactive web page, it means a user can do a lot of things on that single web page and, depending upon the application, the user reasonably expects that 1) the browser’s ‘Back’ button takes them to prior places they were within that same web page and 2) if the user has navigated to a particular place within page, they reasonably expect they can bookmark the current URL and return back to that page with that URL and the page will look similar to the point at which they bookmarked it. In this post, we’ll look at what GWT offers to enable an application to provide Back Button and Bookmark support.
Back Button Support
If we did nothing special to manage ‘Back’ support in the Contact Management application, the browser’s ‘Back’ button, no matter what the user had clicked on, would take the user back to the place they were before the Contact Management application. For example, the user opens their browser to their home page, www.google.com, then they go in to the Contact Management application. Then they click on Jim, then Joe, then Jane. When they clicked ‘Back’ at this point, they browser leaves the Contact Management application and returns to www.google.com. For our Contact Management application, it would be better if the ‘Back’ button in this case, returned them back through their clicks: from Jane back to Joe, and from Joe back to Jim. And only if they clicked ‘Back’ again, would they be returned to www.google.com.
The Browser’s ‘Back’ button really does nothing more than return the user to the prior URL they were on. So it makes sense that, in order for the Contact Management application to have Back support, each click from Jim to Joe to Jane will need the URL to change. This way, when the user clicks ‘Back’, the URL will go back through those previous URL’s. GWT has provided this capability with its History.newItem(String) method. When History.newItem(String) is called, the URL changes to reflect the new item. The String passed in to the method is not the entire URL, though. It is just a portion that GWT will append at the end of the URL after a ‘#’ symbol. Appending information to the existing URL after the ‘#’ symbol will make it so the browser doesn’t treat this as a new URL that needs to be requested from the server.
Here’s how it could look embedded in the click-handler of the Contact List UI Component:
public void setContactList(Collection contacts){
this.contacts.clear();
for (final Contact contact : contacts){
final Text text = contactListView.addContact(contact.getFirstName());
text.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// when user clicks a Contact, add History item
History.newItem(contact.getFirstName());
eventBus.fireEvent(new ContactSelectedEvent(contact));
showContactAsSelected(contact);
}
});
this.contacts.put(contact, text);
}
}
Note: We haven’t seen the showContactAsSelected() in prior post, but all it does is change the style of the selected Contact to a ‘selected’ style and all other Contacts get a ‘normal’, unselected style.
Also, in order for the History class to be used the HTML host page must have a “special” iframe in it. With the iframe added for the Contact Management application, the HTML host page now looks like:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" language="javascript" src="com.gwtdesign.history.contactlist.ContactListTest.nocache.js"></script>
</head>
<body>
<!-- OPTIONAL: include this if you want history support -->
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
<h1>Contact List Test Page</h1>
</body>
</html>
With the special history iframe in the HTML page and the History.newItem() call from the Contact click-handler, the URL changes work! When the user clicks on ‘Jim’, the URL changes to look something like:
http://localhost:8888/com.gwtdesign.history.contactlist.ContactListTest/ContactListTest.html#contact=Jim
And then Joe:
http://localhost:8888/com.gwtdesign.history.contactlist.ContactListTest/ContactListTest.html#contact=Joe
And then Jane:
http://localhost:8888/com.gwtdesign.history.contactlist.ContactListTest/ContactListTest.html#contact=Jane
With this in place, now the user can click the ‘Back’ button without leaving the Contact Management application. Only the portion of the URL after the ‘#’ symbol will change. However, with just this use of History.newItem(), clicking ‘Back’ will not yet fully do what we want. We see the URL change as expected when we click ‘Back’, but the page doesn’t change at all to reflect the different URL’s. The page stays completely static through these ‘Back’ clicks. This is somewhat expected, since as we discussed above, a change to a URL where the only change is after the ‘#’ symbol is an indication to the Browser that it shouldn’t refresh the page with the new URL. This means, when the URL changes, it’s up to us to determine what the page should look like. And this also means we need to know when the URL changes in this way.
GWT provides a handle to the URL-changed event via the History. addValueChangeHandler(ValueChangeHandler) method. When the page loads, we can add a ValueChangeHandler. Then as the user clicks the back button, not only will the URL change based on the new items we’ve added to the History stack, but our event handler will also get called so that we can see have our code modify the page to reflect the user’s new location.
For the Contact List, we can accomplish this with some small changes and a refactor. The Contact click-handler will no longer fire the ContactSelectedEvent. It will only change the URL (i.e. call History.newItem()):
public void setContactList(Collection contacts){
this.contacts.clear();
for (final Contact contact : contacts){
final Text text = contactListView.addContact(contact.getFirstName());
text.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
History.newItem(contact.getFirstName());
}
});
this.contacts.put(contact, text);
}
}
During the ContactListPresenter’s constructor, we’ll add a History ValueChangeHandler so that the presenter is notified of URL changes. Then when the URL change is handled, at that point, we’ll raise the ContactSelectedEvent.
// Constructor
public ContactListPresenter(ContactListView contactListView, final EventBus eventBus){
this.contactListView = contactListView;
this.eventBus = eventBus;
History.addValueChangeHandler(new ValueChangeHandler() {
public void onValueChange(ValueChangeEvent event) {
String selectedContactName = event.getValue();
if (selectedContactName == null || selectedContact.equals(“”)){
// do nothing
}else{
Contact contact = findContactByName(selectedContactName);
/*
* Prior to refactoring, this was on the click-handler.
* To provide 'Back' support, we will now do this on a URL
* change. All the click-handler does now is change the URL
* which, in turn, calls this History ValueChangeHandler.
*/
eventBus.fireEvent(new ContactSelectedEvent(contact));
showContactAsSelected(contact);
}
}
});
}
With these changes, the user can now click through various Contacts in the Contact Management application and the Back button will back the user up through those clicks!
Our Contact Management application is currently designed to display a list of Contacts on the left-hand side. In its initial state, there is no Contact selected. At this point, the user can select a Contact and the ‘Contact Details UI Component’ will display the selected Contact. If we did nothing special to add Bookmark support, the URL the user captures after their click to the selected-contact would not return them to a page where the contact was selected. It would simply return the user to the Contact List application in its initial state, having no Contact selected. For our Contact List application, it would be better if the URL the user bookmarked could return the user to a page where a particular contact was selected.
Considering what we added above for ‘Back’ support, there is a lot of overlap for what we need to get URL/Bookmark support. When the user goes in to the app initially, they are at a URL like this:
http://localhost:8888/com.gwtdesign.history.contactlist.ContactListTest/ContactListTest.html
With the ‘Back’ support we added above, they click on ‘Jim’ and the URL changes to look like this:
http://localhost:8888/com.gwtdesign.history.contactlist.ContactListTest/ContactListTest.html#Jim
For URL/Bookmark support, we now need to be able to capture the ‘Jim’ link, navigate to some random web page outside of the Contact List Application, and then be able to go back to the Contact List application with the ‘Jim’ link. With only the Back support that we’ve added, this will NOT work. Going back to the Contact List Application with the ‘Jim’ link puts the user in the initial state of the Contact List Application. The reason is that a History value-change event is not raised when the HTML host page is first loaded. Therefore, the History ValueChangeHandler we have created in our ContactListPresenter will not get called when the page first loads.
GWT has provided an API to force this History value-change event to be raised. It is the History.fireCurrentHistoryState(). This will typically be called at the end of the Entry Point’s onModuleLoad() since all the valueChangeHandlers are expected to be in place to handle the event.
By putting a call History.fireCurrentHistoryState() as the last line of our Entry Point’s onModuleLoad() method, we can see that the URL/Bookmark support is working!
Putting it all together for the Contact List, this is now approximately what happens when the user selects a Contact from the Contact List:
More Browser Navigation Support
In the prior sections, we made some enhancements to the Contact Management application so that we can have Back and Bookmark support. That’s good, but there’s still an issue. Even though the names in the Contact List sort of behave like links (click on them and they do what we want them to do), the names in the list don’t look like links. They just look like plain text.
We could treat this as a styling issue and make sure they have all the link styles (e.g. underline) to ensure they look like typical web links. However, doing this will still leave the Contact links missing some browser features that users commonly expect on links: Open in New Tab, Open in New Window, Copy Shortcut, etc.
Let’s make a few tweaks to the Contact List links so that these features are all available. Earlier in the document, we implemented the Contact List names as Anchors. To give them the desired behavior, we set the text on them to be the Contact’s name and put an onClick() listener on the links so we could change the page how we wanted when the user clicked. So the pseudo-HTML that gets generated sort of looks like this:
<a onClick=“showJimInContactDetail();”>Jim</a>
By not have the href set, we lose the browser support we’re looking for to copy shortcut, open a new window, etc. The anchor doesn’t behave like a link. Specifically, the browser doesn’t even present the right-click context menu. We could in theory change it to look more like:
<a href=”javascript:showJimInContactDetail();”>Jim</a>
Now the anchor will behave like a link in that the browser will present the right-click context menu but the navigation functions in that menu won’t work. If the user, for example, copies the shortcut of the link, all they are copying is the JavaScript call which will do nothing for them if they bookmark it and try to use it at a later date.
In order to get the browser behavior we want, the link will need to look more like this, where the HREF has the history token that our Contact Management application understands:
<a href=”#Jim”>Jim</a>
If the link looked like this and our Contact Management app continued to keep its GWT History interactions, then we’d have everything: the Back support, the Bookmark support, and the New Tab/New Window/Copy Shortcut/etc. support.
To do this, let’s have our ContactListView.addContact() return an interface on which the Presenter can do more than just add click handlers and change the text:
public interface ContactListView {
public IAnchor addContact(String contactName);
}
The IAnchor interface will extend HasClickHandlers, HasText as expected and we’ll also add a method to set an internal href:
public interface IAnchor extends HasClickHandlers, HasText {
public void setInternalHref(String anchor);
}
And the AnchorImpl will implement that method like so:
public class AnchorImpl extends Anchor {
public void setInternalHref(String anchor) {
super.setHref("#" + anchor);
}
}
Now we can go back to the ContactListPresenter and set that property on the link:
public void setContactList(Collection contacts){
this.contacts.clear();
for (final Contact contact : contacts){
final IAnchor anchor = contactListView.addContact(contact.getFirstName());
anchor.setInternalHref(contact.getFirstName());
anchor.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
History.newItem(contact.getFirstName());
}
});
this.contacts.put(contact, text);
}
}
If the user clicks on the link normally, the History.newItem() is called in the anchor’s ClickHandler and the Contact List Presenter will fire off a ContactSelectedEvent from its History.ValueChangeHandler. If the user were to right-click/New Window on the link, then the page will open using the anchor’s HREF. This re-loads the host-page with the anchor’s HREF. This means the Entry Point’s onModuleLoad() method will execute and the last part of the onModuleLoad() method will call in to GWT’s History.fireCurrentHistoryState(). At this point, the Contact List Presenter will fire off a ContactSelectedEvent from its History.ValueChangeHandler and the user will see the appropriate contact selected in their new page.
Now the links have the full browser navigation support our users would expect!
This technique we just used leveraged the GWT’s Anchor.setHref() and then our code made a call to History.newItem(). That was more of an illustration to progressively enhance what we already had. In fact, GWT’s Hyperlink class has some of this built in for free. We can construct the Hyperlink with a “targetHistoryToken” and it will wire up the event-handling to call History.newItem() if a user clicks on the link. Then if we used this, our ContactListPresenter would not need to add any click-handlers to the link. All it would need to do is pass the appropriate targetHistoryToken in to the Hyperlink.
In this post we looked at what the GWT framework offers an application to support the Browser’s Back button and support Bookmarking. This support is primarily provided by the through use of the following methods on the History class: addNewItem(), addValueChangeHandler(), and fireCurrentHistoryState().
Although the GWT History methods provide the underpinnings for an application to provide Back Button and Bookmark support, the way we used the History methods in this post is probably too low-level for any reasonably complex page. In the next post, we’ll discuss problems with how the History class was used in this post and also look at a ways to address those problems.