GXT's MVC Framework
For the past couple of months, I've been developing a GWT application using a mix of plain ol' GWT and GXT widgets. When I first started developing it, I didn't know how to best organize my code and separate the logic. The solution I came up with was to adopt some sort of MVC framework. Since I was already using GXT, I opted for GXT's lightweight MVC implementation.
As mentioned in Testing GWT Applications, GXT's MVC doesn't have much documentation. The best reference documentation seems to be Christian's Getting started with Ext-GWT: The Mail reference application.
Page Transitioning with Dispatcher
After working with GXT MVC for a couple months, I'm still not sure I fully understand how navigation and event dispatching works. The biggest point of confusion for me is how to best use GXT's Dispatcher class.
The problem with Dispatcher is it has a two methods that seem to do the same thing.
forwardEvent
(4 variations)dispatch
(3 variations)
In addition to these methods in Dispatcher, there's two fireEvent
methods in GXT's View class. According to my calculations, that means there's 9 different options for transitioning from one view to the next. Which one is best to use?
From what I've learned, I think it's best to use fireEvent
in Views and forwardEvent
in Controllers and other widgets. IMO, dispatcher
should never be used except in your HistoryListener's implementation onHistoryChanged
method.
The important thing to realize about this method is it should only work if the View's Controller is registered for the event.
protected void fireEvent(AppEvent event) { Controller c = controller; while (c != null) { if (c.canHandle(event)) { c.handleEvent(event); } c = c.parent; } }
However, fireEvent
seems to work even when the View's Controller isn't registered for that event. This is because onHistoryChanged
gets called in the EntryPoint. For experienced GXT MVC users, does this navigation handling mesh with your findings?
The most important thing for navigation to work successfully is enabling History support. The next section talks about how to do this effectively.
Enabling History Support
To help explain things better, I created a simple GWT MVC Example application and used Maven to create an archetype with it. You can create a project from the archetype using the following command:
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes \ -DarchetypeArtifactId=gwt-mvc -DarchetypeVersion=1.0-SNAPSHOT \ -DgroupId=com.mycompany.app -DartifactId=myproject \ -DremoteRepositories=http://oss.sonatype.org/content/repositories/appfuse-snapshots
To enable history support in this application, I implemented HistoryListener in my EntryPoint (Application.java) and added the following logic to initialize:
// If the application starts with no history token, redirect to 'home' state String initToken = History.getToken(); if (initToken.length() == 0) { History.newItem(HistoryTokens.HOME); } // Add history listener History.addHistoryListener(this); // Now that we've setup our listener, fire the initial history state. History.fireCurrentHistoryState();
In this example, HistoryTokens is a class that contains all the URLs of the "views" in the application.
public class HistoryTokens { public static final String HOME = "home"; public static final String CALENDAR = "calendar"; public static final String NOTES = "notes"; public static final String SEARCH = "search"; }
In order to make URLs like http://localhost:8080/#calendar go to the calendar view, the following logic exists in the onHistoryChanged
method.
Dispatcher dispatcher = Dispatcher.get(); if (historyToken != null) { if (historyToken.equals(HistoryTokens.HOME)) { dispatcher.dispatch(AppEvents.GoHome); } else if (historyToken.equals(HistoryTokens.CALENDAR)) { dispatcher.dispatch(AppEvents.Calendar); } else if (historyToken.equals(HistoryTokens.NOTES)) { dispatcher.dispatch(AppEvents.Notes); } else if (historyToken.equals(HistoryTokens.SEARCH)) { dispatcher.dispatch(AppEvents.Search); } else { GWT.log("HistoryToken '" + historyToken + "' not found!", null); } }
Controllers are registered in the EntryPoint as follows:
final Dispatcher dispatcher = Dispatcher.get(); dispatcher.addController(new CalendarController()); dispatcher.addController(new HomeController()); dispatcher.addController(new NotesController()); dispatcher.addController(new SearchController());
Controllers respond to events they're registered for. This is done in their constructor:
public CalendarController() { registerEventTypes(AppEvents.Calendar); }
In order for navigation to work, you have to create links with history tokens1. For example, here's a link from the HomeView
class:
Hyperlink notesLink = new Hyperlink("Notes", HistoryTokens.NOTES); notesLink.addClickListener(new ClickListener() { public void onClick(Widget widget) { Dispatcher.get().fireEvent(AppEvents.Notes); } });
You'll notice in this example, I'm using Dispatcher's fireEvent
method. If I wanted to pass some data with your event, you'll need to use forwardEvent
. Here's an example from CalendarView
:
Button submit = new Button("Submit"); submit.addSelectionListener(new SelectionListener<ButtonEvent>() { public void componentSelected(ButtonEvent ce) { AppEvent<Date> event = new AppEvent<Date>(AppEvents.GoHome, date.getValue(), HistoryTokens.HOME); Dispatcher.forwardEvent(event); } });
In this example, you could also use Dispatcher.dispatcher()
, but I believe this will cause the transition to happen twice because the onHistoryChanged
method gets called too. This doesn't matter for the most part, except when you start to use DispatcherListeners.
Hopefully this article has helped you understand how GXT's MVC framework works. I'm interested in learning how other GWT MVC frameworks work. If you've used one, I'd love to hear about your experience.
Friday Fun Test
Here's a test for those interested in digging into the GXT MVC example. There's a bug in this application that prevents something from happening. I'll buy a drink for the person that finds the bug and I'll buy two drinks for the person that comes up with a solution.
1. If you use the default constructor on Hyperlink and use setText()
, make sure to call setTargetHistoryToken()
too. If you don't, a blank history token will be used and # causes the browser to scroll to the top before a page transition happens.
Matt, very timely post, as I'm just getting started with GXT MVC too (and GWT), trying to wrap my head around it and figure out the best place to do things like RPC and where should the AsyncCallback's go. Your posts are informative on this topic, so I hope to see more GWT/GXT posts.
Oh, and there's at least one other way of forwarding to a view; Controller has the forwardToView(View, AppEvent) method.
Posted by MarkB on March 14, 2009 at 08:22 AM MDT #
Hi Matt,
this is highly interesting since I am thinking about migrating my Gwt-Ext application to GXT.
Does GXT make it easier to create different view-areas for your application? In a non-ajax-app one constantly sees different screens, becasue the server sends new HTML on every request/navigation flow. In a Gwt-Ext application everything feels very static, since everything needs to be there up front and it feels a little too much like a desktop application. Although everything is very responsive and fast, I does not have a "web"-feel around it.
For example in Gmail, most of the common stuff is in the same view (inbox, trash, etc), but once you hit the settings link, the view changes (you get this yellow tabbed settings panel).
I miss those kind of "flow"-transitions in my Gwt-Ext application, since everything is just there once the app is loaded. You cant navigate away from it without leaving the entire application. It is hard to put all of the functions in this static area, because it is bad for usability.
My problem is not easy to describe, as you have probably felt during reading. The only way to escape this problem traditionally (GWT-only, not Ext-widgets) is to create separate modules for your application, but that means I have to download everything again and again. It wont be as fast as a "single module" that gets loaded once and lets the user navigate inside it.
Posted by Sakuraba on March 14, 2009 at 10:52 AM MDT #
Posted by Jeff Genender on March 14, 2009 at 02:18 PM MDT #
Posted by Andreas Andreou on March 15, 2009 at 04:04 AM MDT #
Hi,
we are using GXT since a couple of month.
Because of the irritating features we use the following (simplified) approach:
Only the View talks to the controller via events (handleEvent). This is the case for events which are only interesting in a distinct, logical area. The view is not aware of the concrete type of the controller instance. The controller is responsible for further processing.
The controller talk to his view(s) via direct method calls because of the controller is aware of his view(s).
For "public" events we use the dispatcher mechanism.
We found the GXT mechanism used in GXT sample apps to complicated and irritating.
Of course we implemented more powerfull base Controller classes (extended from GXT Controller) which support "Chain of Responsibility" and so on...
I think our approach is a good compromise in flexibility and nevertheless usability.
Regards
Posted by Martin on March 15, 2009 at 12:01 PM MDT #
Hi to all (and Matt)
Yes I agree, the dispatching mechanism in GXT is not that straightforward and takes some time getting used to.
This is what I feel makes it easy to manage -
Now, on another note - How do you manage Model (in the MVC) for your GXT applications? (I know about the ModelData,BaseModel etc,) I am curious to know general use case scenarios? I will follow up with more questions later.
Posted by AKumar on March 15, 2009 at 07:30 PM MDT #
Man, this archetype could be the solution to my problem described above!
Matt, is the bug, that when pressing "Submit" in the Calendar-View, the history token gets not updated accordingly? Because after I press submit I cannot press on the "Calendar" link anymore, because the new history token will be the same as the old one despite being on the home site and not on the calendar site.
Posted by Sakuraba on March 16, 2009 at 12:04 PM MDT #
@Andreas - You might try changing the version to GXT version 1.2.2. If that doesn't work, installing the 1.2.3 JAR locally should solve your problem.
@Sakuraba - You have correctly identified the bug! The onHistoryChanged() method checks for duplicates and since the calendar history token is a duplicate, it doesn't transition to the next page. I owe you a drink. Please send an e-mail to [email protected] to collect.
Posted by Matt Raible on March 16, 2009 at 06:14 PM MDT #
Posted by Sakuraba on March 16, 2009 at 07:03 PM MDT #
The gwtruts framework seems quite promising.The developer has made an appfuse integration as well. http://apps.sourceforge.net/trac/gwtruts/
Regards,
Matteo
Posted by Matteo on March 18, 2009 at 08:28 AM MDT #
Posted by MAbidi on April 22, 2009 at 03:16 AM MDT #
Matt,
It will be great if you discuss about the data binding and and persistance layers
Regards,
DJ
Posted by DJ on April 25, 2009 at 03:52 PM MDT #
Posted by Olivier Gérardin on May 04, 2009 at 09:17 PM MDT #
Why do we need classes to implement MVC here? To me, MVC is just a design pattern. And to simplify things, you can combine the view and the controller. No need to decouple this. Then you can write JUnit tests for the model, and use GWT unit tests for View/Controller.
I've just started watching the PureMVC presentation (puremvc.org) after reading the referenced url from Olivier. Man, this framework seems totally overdesigned. Just another level of abstraction you need to understand.
Because the model could be implemented in different ways, it's difficult to abstract that out into a class. If you do, you'll have more overhead (such as PureMVC's proxy classes).
Posted by 188.97.230.73 on June 13, 2009 at 04:52 AM MDT #
Posted by Morten Thorsen on June 15, 2009 at 09:35 PM MDT #
Posted by Matt Raible on June 15, 2009 at 10:13 PM MDT #
Hi Matt,
Can you name a few what widgets you decided to use from GWT, what from GXT? Did you use GXT Layout widgets? I am looking for advice whether I should go with pure GXT widgets or mixing them.
Thanks,
Posted by Brian on August 14, 2009 at 03:37 PM MDT #
Posted by Matt Raible on August 14, 2009 at 08:31 PM MDT #
Posted by The Code-House Blog on November 05, 2009 at 08:16 PM MST #
Hello,
I'm researching on how to apply the internal GXT MVC structure into a GXT desktop application.
I started out integrating the Login dialog from the Maill application (using the MVC structure) into the GXT desktop. The login is already working and upon a successful login, the desktop and shortcuts are displayed.
Now, I have some questions on how to move forward. I hope you can help me out by pointing me to the right direction.
1. I'm just starting out with GXT MVC, and the only guide I have on how to implement it is the GXT mail application sample. To integrate the MVC structure into a a web desktop app, I'm planning to make a Controller and a View that corresponds to a DEsktop Shortcut/window. IS this correct?
Ex:
- My desktop has a shortcut/window called My Machines (A masterlist grid of all the machines, with all machine info and CRUD)
- MachineView -> Constructs the view (grid with CRUD on a window).
- MachineController -> Registers the ShowMachineWindow event. IF the event is dispatched, this controller displays the view MachineView.
This structure is repeated for every shortcut/window in the desktop. IS this how to go about it? OR am I doing it all wrong? What's the best way to do it?
Thanks!
Posted by kurt on January 20, 2010 at 02:17 AM MST #
kurt,
Having a Controller per Window may make sense. Window in the webdesktop really represent applications that run independently to each other. So it is really a Controller per application type. Depending on what the Window does, you could have one to many VIews. It would even be possible to have child Controllers. It really depends on what the Window is doing. A Window could contain something simple, or a Window could represent a complex application.
Hope that helps.
Posted by Darrell Meyer on March 16, 2010 at 01:39 AM MDT #
I am starting with GXT also, but I plan on using MVP, as shown here:
http://www.youtube.com/watch?v=PDuhR18-EdM
and here: http://extgwt-mvp4g-gae.blogspot.com/2009/10/gwt-app-architecture-best-practices.html.
I never understood MVC and have indeed heard a few different explanations of what it is. The MVP pattern seems simpler and better.
Anyone had similar thoughts? Anyone care? um...ok. I'll be leaving now. adios.
Posted by jorel on April 27, 2010 at 05:50 PM MDT #
GXT is not very well suited for MVP, but it doesnt mean it's not feasible.
GXT's MVC is so bad anyway that I would have gone with MVP too if I had to start the project again.
Posted by Olivier on April 28, 2010 at 01:35 PM MDT #
Posted by Chantu on June 03, 2010 at 06:05 AM MDT #
Actually, GXT MVC works out well for me. I like to use code splitting via controller.
Code splitting with GXT MVC on GAE
Posted by Shawn on July 22, 2010 at 04:20 AM MDT #