GWT history handling library - give it a try

366 views
Skip to first unread message

Kostya Kulagin

unread,
Mar 27, 2012, 11:36:21 AM3/27/12
to Google Web Toolkit
Simple open source etc library for automatical handling of GWT
components state in URL (browser history). Contains sample application
which demonstrates its usage. Give it a try :)

http://code.google.com/p/gwt-state-navigator/wiki/StateNavigator

Michael Allan

unread,
Mar 27, 2012, 7:04:14 PM3/27/12
to Google Web Toolkit
Page "StateNavigator" Not Found

Thomas Broyer

unread,
Mar 28, 2012, 4:13:10 AM3/28/12
to google-we...@googlegroups.com
I tend to not "trust" projects that have an empty source repository and only provide downloads. I generally take it as a sign that the developer doesn't use a VCS, and so that the project is in bad shape; not really a sign of quality if you ask me. If there are other reasons, then please make them explicit.

Also, the MainIdea [1] wiki page starts with “Core GWT as far as I know does not support URL history handling”, which is either plain wrong [2] or doesn't mean what I think it means (and without the ability to browse the source online, I really can't tell; and I won't take the time to download the source to evaluate the project).

As for the general approach (from what I can read from the wiki pages), I don't think it really "works". Do you really want to save the open/close state of each tree item in the browser's history? Navigating the app would quickly become a nightmare. URLs are for navigation, not about application state. This is why the HTML5 History API provides a specific argument in pushState() to store some state independently of the URL: there are things that you want to keep around as long as the app is "running" but that don't need to be carried along in the URL if you bookmark it or copy/paste it to a mail or IM.
As for combining the state of several components into the URL (as the YUI Library provides, for instance), that makes ugly URLs that don't carry any "real" meaning; not quite "Cool URIs" [3,4].
URLs are for navigation, and GWT provides a cool way of handling it for quite some time now: Places [5] (and if you ask me, you should use Activities too, but that doesn't fit everyone, so feel free to use GWTP, Mvp4g or whatever)

All of the above are just a "first impression" though, as I haven't looked at the code (and no, I'm not going to download it)


Kostya Kulagin

unread,
Mar 28, 2012, 10:42:07 AM3/28/12
to Google Web Toolkit
Hi!

Thanks for the response - personally I like critisism very much. Noone
perfect (this is about myself).

Comments inlined

> I tend to not "trust" projects that have an empty source repository and
> only provide downloads. I generally take it as a sign that the developer
> doesn't use a VCS, and so that the project is in bad shape; not really a
> sign of quality if you ask me. If there are other reasons, then please make
> them explicit.

Absolyutely correct and agree, however lib is so small that I've
decided skip this step. Anyways - you are right.

> Also, the MainIdea [1] wiki page starts with “Core GWT as far as I know
> does not support URL history handling”, which is either plain wrong [2] or
> doesn't mean what I think it means (and without the ability to browse the
> source online, I really can't tell; and I won't take the time to download
> the source to evaluate the project).
>
> As for the general approach (from what I can read from the wiki pages), I
> don't think it really "works". Do you really want to save the open/close
> state of each tree item in the browser's history? Navigating the app would
> quickly become a nightmare. URLs are for navigation, not about application
> state. This is why the HTML5 History API provides a specific argument in
> pushState() to store some state independently of the URL: there are things
> that you want to keep around as long as the app is "running" but that don't
> need to be carried along in the URL if you bookmark it or copy/paste it to
> a mail or IM.
> As for combining the state of several components into the URL (as the YUI
> Library provides, for instance), that makes ugly URLs that don't carry any
> "real" meaning; not quite "Cool URIs" [3,4].
> URLs are for navigation, and GWT provides a cool way of handling it for
> quite some time now: Places [5] (and if you ask me, you should use
> Activities too, but that doesn't fit everyone, so feel free to use GWTP,
> Mvp4g or whatever)
>
Again good points, again thank you.
Places are intended to descibe set of components on page but not their
(components) state.
And though if you want to support back forward buttons in a browser
for example for paging.
What will you do? You'll probably append some parameter to URL to
indicate which page you are currently on?
What it mean? It means ou'll have to save scomponent state indeed.

> All of the above are just a "first impression" though, as I haven't looked
> at the code (and no, I'm not going to download it)
>

Thanks!

Thomas Broyer

unread,
Mar 28, 2012, 10:59:35 AM3/28/12
to google-we...@googlegroups.com


On Wednesday, March 28, 2012 4:42:07 PM UTC+2, Kostya Kulagin wrote:
> As for the general approach (from what I can read from the wiki pages), I
> don't think it really "works". Do you really want to save the open/close
> state of each tree item in the browser's history? Navigating the app would
> quickly become a nightmare. URLs are for navigation, not about application
> state. This is why the HTML5 History API provides a specific argument in
> pushState() to store some state independently of the URL: there are things
> that you want to keep around as long as the app is "running" but that don't
> need to be carried along in the URL if you bookmark it or copy/paste it to
> a mail or IM.
> As for combining the state of several components into the URL (as the YUI
> Library provides, for instance), that makes ugly URLs that don't carry any
> "real" meaning; not quite "Cool URIs" [3,4].
> URLs are for navigation, and GWT provides a cool way of handling it for
> quite some time now: Places [5] (and if you ask me, you should use
> Activities too, but that doesn't fit everyone, so feel free to use GWTP,
> Mvp4g or whatever)
>
Again good points, again thank you.
Places are intended to descibe set of components on page but not their
(components) state.

No. Activities are a set of "components" on the page. A Place, by definition, describes the "location" within the app, which you can interpret as being the "state" of the app, thus the "state" of the activities (it's rather the other way around though: you navigate to some place, and activities will construct their state from the current place)

Joseph Lust

unread,
Mar 28, 2012, 11:09:51 AM3/28/12
to google-we...@googlegroups.com
Speaking of GoogleCode projects, did anyone else notice last week when they removed the "Activity" bar graph from the left hand side of project landing pages? Perhaps this was because most GoogleCode projects were in the "low" category. It would be nice to be able to get that back as it was a quick way to decide whether to use a project like this one or not.

Sincerely,
Joseph

Chris Price

unread,
Mar 28, 2012, 12:47:19 PM3/28/12
to google-we...@googlegroups.com
@Joseph apparently it was a performance issue -
http://code.google.com/p/support/issues/detail?id=24324

> --
> You received this message because you are subscribed to the Google Groups
> "Google Web Toolkit" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/google-web-toolkit/-/z4mYgMi8Bu0J.
>
> To post to this group, send email to google-we...@googlegroups.com.
> To unsubscribe from this group, send email to
> google-web-tool...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/google-web-toolkit?hl=en.

Kostya Kulagin

unread,
Mar 29, 2012, 11:14:28 AM3/29/12
to Google Web Toolkit
Thanks again

> > Again good points, again thank you.
> > Places are intended to descibe set of components on page but not their
> > (components) state.
>
> No. Activities are a set of "components" on the page. A Place, by
> definition, describes the "location" within the app, which you can
> interpret as being the "state" of the app, thus the "state" of the
> activities (it's rather the other way around though: you navigate to some
> place, and activities will construct their state from the current place)
> Have a look there for more details:http://tbroyer.posterous.com/gwt-21-places,http://tbroyer.posterous.com/gwt-21-places-part-iiand
> thentbroyer.posterous.com/gwt-21-activities

(Throwing away a question if an application does not use Places and
Activities but instead loads it components 'by hands' (there is no GWT
support for history))

Yes, this what I meant. I gave slightly incorrect description. Sorry.
Places are 'State' of a page, i.e. 'Place' in an application. This is
great.
Also correct that set of Activities will describe components for the
current Place. Let us leave notion of mayStop() feature of
Activities.
It is cool and it is actually applied if you change a *Place*.

But. You are in one Place in the application. You have a pager, for
example. What will you do in order to implement history (browser back-
forward) support for paging in this case?

For me personally: current page in URL is a *state* of some paginator
component managed by Activity. Not a new *Place*, correct?
So you'll have to have some functionality to write-read some params
for the paginator related to to current page. If you (nightmare!) have
2 paginators?
What you'll have to do to be assured that sub-params (or there is an
other way to do it?) in the browser history string does not intercept?

thanks!

Jens

unread,
Mar 29, 2012, 12:42:12 PM3/29/12
to google-we...@googlegroups.com
The problem is that your (bookmarked) URLs will become useless if you change the implementation of your UI. If you, as mentioned in your wiki page, store opened/closed tree nodes in the URL, what happens if you switch to a flat table or a totally different custom widget? The previous URL is pretty much useless now.

Thats why you shouldn't say that each UI component has a URL state. Sure it has a state, but you don't want to bookmark that specific state. You want to bookmark WHAT the application in the current place presents the user. Thats in most cases totally independent on how you implement the UI and which components you use.
Example: http://domain.com/#/employee/5/details. This loads all employees, preselects the one with ID 5 and shows the details section of the employees data. No word about the used UI components in the URL. Got the idea?

-- J.

Thomas Broyer

unread,
Mar 29, 2012, 12:49:46 PM3/29/12
to google-we...@googlegroups.com


On Thursday, March 29, 2012 5:14:28 PM UTC+2, Kostya Kulagin wrote:
Yes, this what I meant. I gave slightly incorrect description. Sorry.
Places are 'State' of a page, i.e. 'Place' in an application. This is
great.
Also correct that set of Activities will describe components for the
current Place. Let us leave notion of mayStop() feature of
Activities.
It is cool and it is actually applied if you change a *Place*.

But. You are in one Place in the application. You have a pager, for
example. What will you do in order to implement history (browser back-
forward) support for paging in this case?

If you want your state to be bookmarkable, then you'll navigate to another place. What's wrong with that?
(and note that changing place does not necessarily mean starting *new* activities)
 
For me personally: current page in URL is a *state* of some paginator
component managed by Activity. Not a new *Place*, correct?

No. If it's in the URL, it *locates* something (URL = Uniform Resource Locator; URI = Uniform Resource Identifier), so that you could send the link via mail or IM and *go to* that... place!

There are cases where you'd want entries in the browser history without changing the URL; for that GWT provides no support at all (Closure Library does, for instance; but you cannot mix history-without-url-changing and changing-url-to-reflect-app-state in the same app –I wonder how HTML5's pushState works without changing the URL–). But that's not what you're talking about here.

So you'll have to have some functionality to write-read some params
for the paginator related to to current page. If you (nightmare!) have
2 paginators?

Well, honestly, if you have to pagers *and* you want them to both create entries in the browser's history, you have a design issue!
 
What you'll have to do to be assured that sub-params (or there is an
other way to do it?) in the browser history string does not intercept?

Create a specific Place that can hold those two "parameters" and navigate from place to place, changing only one value at a time?
If you want to decouple your pagers from the places then, well, abstract that out behind some "navigator" component that'll manage the places for you (e.g. moveSecondPager(2) would navigate to a place with the same value for the first pager and the value 2 for the second pager; the "second pager" doesn't need to know about that).

Kostya Kulagin

unread,
Mar 30, 2012, 9:31:20 AM3/30/12
to Google Web Toolkit
Agreed with bookmarks. Thank you.
What about browser next-prev buttons support (sample I wrote with
paginator)?

Kostya Kulagin

unread,
Mar 30, 2012, 9:49:11 AM3/30/12
to Google Web Toolkit


On 29 mar, 19:49, Thomas Broyer <t.bro...@gmail.com> wrote:
> On Thursday, March 29, 2012 5:14:28 PM UTC+2, Kostya Kulagin wrote:
>
> > Yes, this what I meant. I gave slightly incorrect description. Sorry.
> > Places are 'State' of a page, i.e. 'Place' in an application. This is
> > great.
> > Also correct that set of Activities will describe components for the
> > current Place. Let us leave notion of mayStop() feature of
> > Activities.
> > It is cool and it is actually applied if you change a *Place*.
>
> > But. You are in one Place in the application. You have a pager, for
> > example. What will you do in order to implement history (browser back-
> > forward) support for paging in this case?
>
> If you want your state to be bookmarkable, then you'll navigate to another
> place. What's wrong with that?
> (and note that changing place does not necessarily mean starting *new*
> activities)
>
Thanks. Agreed with bookmarks - they probably should not contain a
state of components...
Or at least should not be handled in most cases. You navigate to some
place - you get it.
With pager on the first page, with the first opened tab.
Indeed the question is - what is Place. Should it contain information
required for components which are registered to this place?
For me Place is a separate this. Like marker. Like some new URL to
which you register Components through activities.
But. For history support (back-forward btns) you should have an
ability somehow modify history string. Yes?
And it is not a Place. And not connected to Place notion. It is
connected to Component.
I agree with you that such URLs like 'a=b&c=d' etc in history string
are ugly. And in general concept agree that for bookmarks you
generaly speaking should not care about state of a component within
Place. You are bookmarking Places, not component states (thanks to
Jens).
But I have to have some support for back-forward browser buttons. And
should not have such ugly URLs.
One of possible things is storing some generated key in URL in
*additions* to Place and store components data in Cookie for example.
Or in a local storage (HTML5). This is about Places.
If an application does not use Places and Activities then there should
be some 'intermediate' solution.


> There are cases where you'd want entries in the browser history without
> changing the URL; for that GWT provides no support at all (Closure Library
> does, for instance; but you cannot mix history-without-url-changing and
> changing-url-to-reflect-app-state in the same app –I wonder how HTML5's
> pushState works without changing the URL–). But that's not what you're
> talking about here.
>
I've started - see above.
You and Jens convinced me that worrying about staring namely
components state in bookmarks bad idea. Thank you!

> So you'll have to have some functionality to write-read some params
>
> > for the paginator related to to current page. If you (nightmare!) have
> > 2 paginators?
>
> Well, honestly, if you have to pagers *and* you want them to both create
> entries in the browser's history, you have a design issue!
>
Let me disagree. I'd like to have browsers buttons support for paging.
And I really can imaging other cases when it could be suitable.

> > What you'll have to do to be assured that sub-params (or there is an
> > other way to do it?) in the browser history string does not intercept?
>
> Create a specific Place that can hold those two "parameters" and navigate
> from place to place, changing only one value at a time?
> If you want to decouple your pagers from the places then, well, abstract
> that out behind some "navigator" component that'll manage the places for
> you (e.g. moveSecondPager(2) would navigate to a place with the same value
> for the first pager and the value 2 for the second pager; the "second
> pager" doesn't need to know about that).
This is a bad approach from my point of view (see big comment above).
Place should not depend or know anything about components inside of
it. It is mostly like marker.

thank you again - really helping comments.

Thomas Broyer

unread,
Mar 30, 2012, 10:09:23 AM3/30/12
to google-we...@googlegroups.com


On Friday, March 30, 2012 3:49:11 PM UTC+2, Kostya Kulagin wrote:
> > What you'll have to do to be assured that sub-params (or there is an
> > other way to do it?) in the browser history string does not intercept?
>
> Create a specific Place that can hold those two "parameters" and navigate
> from place to place, changing only one value at a time?
> If you want to decouple your pagers from the places then, well, abstract
> that out behind some "navigator" component that'll manage the places for
> you (e.g. moveSecondPager(2) would navigate to a place with the same value
> for the first pager and the value 2 for the second pager; the "second
> pager" doesn't need to know about that).
This is a bad approach from my point of view (see big comment above).
Place should not depend or know anything about components inside of
it. It is mostly like marker.

A Place is a type-safe representation of the URL. No more, no less. If you want to put something in the URL, then put it in a Place and have a PlaceTokenizer transform it to your URL (and back when navigating to the URL, either through a bookmark, link, or browser history).

As I said, there might be cases where you want some history entries to change the URL and some others that won't (but honestly, I still cannot find any use case). In that case, I'd investigate HTML5's pushState to see if it supports the use case, and if it does then simply punt for browsers that don't support it (only handle the case where it changes the URL, i.e. true navigation, not intermediate "state").
I believe it'd be possible to mix a hidden iframe "hidden state change" and manipulating the URL's #hash for "navigation", I'm really not sure it's worth it ⇒ use pushState and let oldIE users with a "not as good" experience as others (and possibly have them install Chrome Frame, so you could use pushState).

And I still strongly believe that if you have two truly independent things on a page, only one should affect the browser history, or you have a serious design issue (“Er, I clicked the back button 3 times, now if I click it once more, will it change the left side or the right side of the screen?”).
That was the main issue with frames (apart from "addressability", i.e. "bookmarkability"), that are now officially dead (as in: http://www.w3.org/TR/html5/obsolete.html#frames )

Of course, YMMV.

Kostya Kulagin

unread,
Apr 2, 2012, 4:33:52 AM4/2/12
to Google Web Toolkit
So, for me to resume (my opinion):

1) Using components state for bookmarks is a bad idea - URL should
contain only 'Place' information. If you need to have an ability to
bookmark state different then initial page state - use
different Place. As an example - if in a gmail I want to have a
shortcut to some of my favorite folders (for example:
https://mail.google.com/mail/#label/hello_thomas ;-) )- this is rather
different Place, not a state of all Components on a page.

2) For a history navigation probably it would be better to have some
key generated. Components states should be stored under that key
(either in cookies or in HTMLs 5 browser cache)

3) If an application does not use Places and Activities - I should
think what to do in that case.

Will make necessary updates, though.

thanks!
> worth it => use pushState and let oldIE users with a "not as good"
> experience as others (and possibly have them installChrome Frame, so you

l.denardo

unread,
Apr 2, 2012, 11:19:17 AM4/2/12
to Google Web Toolkit
--3) If an application does not use Places and Activities - I should
think what to do in that case.

I just stepped into reworking an application that used a custom
history mapper (developed before places were available) to a Places
design, so here's what I found.
If you don't use Places, you'll go and use GWT native history support
(https://developers.google.com/web-toolkit/doc/latest/
DevGuideCodingBasicsHistory) building an HistoryToken based on your
object properties, and viceversa.

This is a sample from my own code

private Map<String, String> parseHistoryToken(String token){
Map<String, String> properties = new HashMap<String, String>();
if (!token.startsWith(MAIN_LBL + "/"))
return properties;
String toParse = token.substring(MAIN_LBL.length() + 1,
token.length());
String[] propList = toParse.split("/");
for (int i = 0; i < propList.length; i++){
String property = propList[i];
if (property.length() < 3)// il controllo serve perché il primo
// split ritorna un array con una
// stringa vuota e non un array vuoto
continue;
String[] values = property.split("=");
//nella history il valore null è salvato come stringa == "null"
String value = (values[1].equals("null")) ? null : values[1];
properties.put(values[0], value);
}
return properties;
}

It will cut a history token like #home/prop1=123/prop2=zaq into a map
like <prop1, 123>, <prop2, zaq>, and map.get("prop1") will return a
String "123".

This is *exactly* what a PlaceTokenizer and a PlaceHistoryMapper will
do, except for the fact that you can then access your HistoryToken
properties in a type safe way (i.e. you'll call some methods on your
Place).

You'll write a PlaceTokenizer to build sort of a CustomPlace, and call
( (CustomPlace) PlaceController.getWhere()).getProp1().
The *huge* difference is that getProp1() can return a type (String,
Integer, CustomObject etc.) and once you defined a tokenizer from your
place to the URL and viceversa this will work correctly.
This means getPlace1() will return the Integer 123, not a String, and
if you build a "bad" URL you'll get (and handle) a meaningful
exception.

If you try to dig into the Places tutorial Thomas Broyer linked before
you'll find out that this is the behavior you get.

So if you don't use places, but want a good history support, the best
way is, er, go for Places :-) You don't need Activities for history
support, using Places, a PlaceHistoryMapper and listening to
PlaceChangeEvent will do a top class history support by themselves.
Again, the article mentioned above is a clear and helpful starting
point, just try to code a simple use case.

Regards
Lorenzo

On Apr 2, 10:33 am, Kostya Kulagin <kkula...@gmail.com> wrote:
> So, for me to resume (my opinion):
>
> 1) Using components state for bookmarks is a bad idea - URL should
> contain only 'Place' information. If you need to have an ability to
> bookmark state different then initial page state - use
> different Place. As an example - if in a gmail I want to have a
> shortcut to some of my favorite folders (for example:https://mail.google.com/mail/#label/hello_thomas;-) )- this is rather
> different Place, not a state of all Components on a page.
>
> 2) For a history navigation probably it would be better to have some
> key generated. Components states should be stored under that key
> (either in cookies or in HTMLs 5 browser cache
>

Kostya Kulagin

unread,
Apr 3, 2012, 11:58:54 AM4/3/12
to Google Web Toolkit
Thank you for a response.

If I understood you correctly - you propose some kind of Place which
will contain props of all components. (sorry if I misunderstood you)
Personally myself don't like this solution.
Place has nothing to do with its components state.

When writing an application I don't want speak on a language of params
in URL, parsing those and update state of components.
I want to speak on a Object language. My object has a state - it saves
it, it restores it. Serialization is a lib's job.

This is an idea.

thanks!

l.denardo

unread,
Apr 3, 2012, 12:17:14 PM4/3/12
to Google Web Toolkit
You're partly right in my opinion.

What I exposed is (part of) a (siplified) "library" approach VS the
typed, Place approach.

A Place is an Object Oriented representation of a client side URL,
tied to your browser history, and offers you typed methods to have in
an object oriented way all the parameters (from the URL) you need to
have to restore your objects' state correctly, and to save them to an
URL.

To have an efficient serialization the recommended approach is to have
a base Place with its tokenizer, i.e. one class handling serialization
and deserialization from and to your URL, then extend it.
It's just the matter to write a single serialization/deserialization
class (or a library, if you prefer).

The point is that all this comes in standard GWT, so looks like
there's no need to develop a separate library to do exactly the same.

regards
Lorenzo

l.denardo

unread,
Apr 3, 2012, 12:32:36 PM4/3/12
to Google Web Toolkit
To be clear, I looked into your library.

If I understand it correctly what you do (just like in the sample I
posted above) is to map an URL from/to a ComponentState, which has a
representation of the state of the object as a Map<String, String>.
Your ComponentState is at last a wrapper of the string map.
A Place is similar in concept, with the difference that you don't have
just a Map<String, String> but a series of methods to have the
properties with their correctly typed class.

The ComponentStateSerializer builds an history token using that map,
or breaks your current history token to the map.
This is the same as a PlaceTokenizer will do, with the difference that
the PlaceTokenizer returns a Place, which is a typed object and has
typed accessors.

Your library is very similar tho the one I developed when Places were
not in GWT, and if you look at my code above you'll notice it's
similar to your
DefaultComponentStateSerializer.toHistoryString(ComponentState ), the
only difference being some more configuration options for yours.

Just spend a couple of hours trying out the Places tutorial Thomas
Broyer posted and you'll immediately see the benefits of Places over
the approach you are using (which is not bad: I used it succesfully
for a long time. Places do the same, but better :-))

Regards
Lorenzo

Thomas Broyer

unread,
Apr 3, 2012, 12:41:20 PM4/3/12
to google-we...@googlegroups.com


On Tuesday, April 3, 2012 5:58:54 PM UTC+2, Kostya Kulagin wrote:
When writing an application I don't want speak on a language of params
in URL, parsing those and update state of components.
I want to speak on a Object language. My object has a state - it saves
it, it restores it. Serialization is a lib's job.

This is unfortunately (or fortunately) not how the Web works. Not that it's impossible to do it (on the contrary), just that in the end it does more harm than good to your users; and the *user* experience matters more than the developer's one.

I strongly suggest that you approach the problem from the other side: you "name" things (give them URLs, and use a Place –or whatever, but Place exists so why not use it?– to make a type-safe representation) and then you make your app reflect that thing. Going to a new place will translate into changing its internal state for a given component. Actions within a component do not necessarily have to be *replaced* by place changes, you can do the change in the internal state *and* go to a new place (and as I already said, you can factor that out using events of a "navigator" abstraction if you don't want your component to deal with places); the place change will go back to your component, but it'll be a no-op as the internal state already matches the one to "attain" given the new place.
Every state that's not in the "name" of the thing should IMO either not be persisted, or simply stored in localStorage or similar.
The browser's history is tightly bound to this, and this is the guide to draw the line between "named things" and the other states: for instance, going to the second "page" of a paged list/table should in most cases be reflected in the URL and the browser's history; which items are expanded/collapsed in a tree, which field has the focus, or which row has the "keyboard highlight" in a list/table should not.
Reply all
Reply to author
Forward
0 new messages