In an attempt to orient myself and my colleagues with GWT, I've been reading up on GWT, trying things out, and blogging about them here. My goal is to start with simple use cases and build on to them with more complex ones. The way it's been working so far is that each post builds on the prior one.

Friday, February 19, 2010

GWT’s Back Button and Bookmark Support

 

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!



Bookmark Support



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:



image



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!



image



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.



Back and Bookmark Summary



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.

14 comments:

  1. Hi,
    very nice article.
    For the contact links, wouldn't be better to use the gwt-hyperlink object?

    ReplyDelete
  2. Hi Djay,

    Thanks for the comment. I put the following in the post. Does it address your question?....

    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.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Well Said, you have furnished the right information that will be useful to anyone at all time. Thanks for sharing your Ideas.

    angularjs Training in chennai
    angularjs Training in chennai

    angularjs-Training in tambaram

    angularjs-Training in sholinganallur

    ReplyDelete
  5. I have visited this blog first time and i got a lot of informative data from here which is quiet helpful for me indeed. 
    Python training in marathahalli | Python training institute in pune

    ReplyDelete
  6. Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot. it is really explainable very well and i got more information from your blog.

    blueprism training in chennai | blueprism training in bangalore | blueprism training in pune | blueprism online training

    ReplyDelete
  7. Thank you for this post. Thats all I are able to say. You most absolutely have built this blog website into something speciel. You clearly know what you are working on, youve insured so many corners.thanks
    Java training in Chennai | Java training in Bangalore

    Java interview questions and answers | Core Java interview questions and answers

    ReplyDelete
  8. Thank you so much for a well written, easy to understand article on this. It can get really confusing when trying to explain it – but you did a great job. Thank you!
    Data Science training in kalyan nagar | Data Science training in OMR

    Data Science training in chennai | Data science training in velachery

    Data science training in tambaram | Data science training in jaya nagar

    ReplyDelete
  9. Nice information, valuable and excellent design, as share good stuff with good ideas and concepts, lots of great information and inspiration, both of which I need, thanks to offer such a helpful information here.

    devops online training

    aws online training

    data science with python online training

    data science online training

    rpa online training

    ReplyDelete
  10. Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.
    Microsoft Azure online training
    Selenium online training
    Java online training
    Python online training
    uipath online training

    ReplyDelete
  11. Great post! I am actually getting ready to across this information, It’s very helpful for this blog.Also great with all of the valuable information you have Keep up the good work you are doing well. Web Designing Course Training in Chennai | Web Designing Course Training in annanagar | Web Designing Course Training in omr | Web Designing Course Training in porur | Web Designing Course Training in tambaram | Web Designing Course Training in velachery

    ReplyDelete

Followers