After spending certain time on wep-app development on GAE I came to the point where I wanted more AJAX and more interaction on my pages. Playing with pure “home-made” AJAX development clearly shown me that:
- it is time consuming
- it is not ok with cross-browsers compatibility
Logically my first though was to look into GWT. But this time I decided not to eagerly start learning all the GWT controls and there layouting. Instead I wanted to get high-level impression on strengths and weaknesses of this technology.
Soon I figured out its main weakness or let’s say inconvenience which was referenced by many and many blogs/posts online by different people. GWT plays very nice when it concerns browsers support BUT it generates a single host HTML page where all other essential for your business logic UI is built dynamically by javascript. This may be a good option if you want to build you web application from scratch but in real world we often need to mix different parts. Especially I was interested in how I can integrate GWT components, or widgets like they call it, into my already existing JSPs.
Let me describe an example. Suppose I want to have a component called NewsBlock. Suppose also I want to place it in my JSPs by a custom tag and pass a parameter to specify news category for example. So it should be something like:
<div> <gwt-newsblock category='Politics'/> </div>
Then I want this custom tag rendered to GWT implementation of the NewsBlock component.
Internet search offered only naive and primitive solutions which required hardcoding ids and presumed NO parametrisation. Namely they proposed to do the next thing:
In HTML:
<div id='containerID'> </div>
In GWT EntryPoint.onModuleLoaded() method:
RootPanel.get('containerID').add(anyNeededWidget);
But you see here a hardcoding of the container ID in my GWT code which DOES NOT allow to isolate components from outer world (HTML/JSP).
Next research revealed me that GWT creators provided a very powerfull tool that may give me some more space for experiment: JSNI.
JSNI builds a bridge between your outer context javascript and the javascript that lives inside GWT. It also allows you to add in GWT Java code native functions (i.e. inject pure javascript code into not-yet-compiled Java).
The main idea is to expose part of the GWT module API to the outer javascript world. If I could have in my GWT Java code base a method like public void addNewsBlock(String category); that actually adds a widget and which is callable from my JSP-generated javascript then the problem would be solved.
API can be exported in the following way:
public class Refucktoring implements EntryPoint {
private native void exportAPI()
/*-{
$wnd.addNewsBlock = @com.refucktoring.client.Refucktoring::addNewsBlock(Ljava/lang/String;);
}-*/;
public void onModuleLoad() {
exportAPI();
}
private static void addNewsBlock(String containerId, String newsCategory)
{
RootPanel.get(containerId).add(new NewsBlock(newsCategory));
}
}
Here 2 things are done:
- addNewsBlock(String, String) method is made visible to external javascript.
- containerId parameter is added to resolve IDs hardcoding problem. I will use this parameter at the point where my JSP custom tag will inject the GWT widget into some DIV element for example.
The nex step is integration in HTML. The very rude form to express it is:
<div id='containerId'>
<script type='text/javascript'>addNewsBlock('containerId', 'Politics');</script>
</div>
But there is a hidden obstacle: bootsrap sequence of the GWT module DOES NOT guarantee you that at the time when this HTML block evaluation will occur the GWT module is already loaded. So it may happen that when this short script will try to call addNewsBlock() method the onModuleLoaded() and its inner exportAPI() methos are not yet called and thus nothing will be added to your DIV.
So we need to add one more thing to allow GWT execute all this little outer javascrips itself after the module is loaded. Naturally would be good to handle this in the onModuleLoaded() method. Thus we add following to our EntryPoint class implementation:
public class Refucktoring implements EntryPoint {
private native void exportAPI()
/*-{
$wnd.addNewsBlock = @com.refucktoring.client.Refucktoring::addNewsBlock(Ljava/lang/String;);
}-*/;
private native void executeExternalInjections()
/*-{
$wnd.executeInjections();
}-*/;
public void onModuleLoad() {
exportAPI();
executeExternalInjections();
}
private static void addNewsBlock(String containerId, String newsCategory)
{
RootPanel.get(containerId).add(new NewsBlock(newsCategory));
}
}
Native method executeExternalInjections() is called automatically after exportAPI() to guarantee the proper sequence. That method calls external javascript function executeInjections() that should be added, as you already thought, by our JSP and is responsible for execution of all those widget-injections small scripts.
The last piece of the puzzle is actually implementation of that javascript method and nicely collecting this all together. Your JSP/HTML should add the following script to the HEAD of the document:
<script type="text/javascript">
var injectorsHolder = new Object();
function executeInjections()
{
for (var i in injectorsHolder)
{
injectorsHolder[i]();
}
}
</script>
<script type="text/javascript" language="javascript" src="refucktoring/refucktoring.nocache.js"></script>
As you see the sequence is important here: first preparatory script and only then GWT module loader script. Finally in places of injection (that can be generated by your custom JSP tags) you have this for example:
<div id="NewsContainer1">
<script type="text/javascript">
injectorsHolder['NewsContainer1'] = function() {addNewsBlock('NewsContainer1', 'Politics'');};
</script>
</div>
<div id="NewsContainer2">
<script type="text/javascript">
injectorsHolder['NewsContainer2'] = function() {addNewsBlock('NewsContainer2', 'Sport'');};
</script>
</div>
The framework is ready and you can do now with GWT literally what you want ![]()
In the next post I will add an update to show the JSP custom tag implementation and show the overall solution working example.
——————————————————————————————————————————————————————
UPDATE
Here I give a simple example of the JSP custom tag since some of you asked for this in comments. This tag inserts one NewBlock gwt widget into the page.
So in the context described above our tag will look like this:
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
@SuppressWarnings("serial")
public class NewsBlockTag extends TagSupport {
public void setId(String id)
{
this.id = id;
}
public int doStartTag()
{
try
{
pageContext.getResponse().setCharacterEncoding("UTF-8");
JspWriter out = pageContext.getOut();
out.println("<div id='" + id + "'>");
out.println("<script type='text/javascript'>");
out.println("injectorsHolder['NewsContainer1'] = function() {addNewsBlock('NewsContainer1', 'Politics'');};");
out.println("</script>");
out.println("</div>");
}
catch (Exception e)
{
e.printStackTrace();
}
return Tag.SKIP_BODY;
}
private String id;
}
So, the injection of 2 widgets which was arranged above as
<div id="NewsContainer1">
<script type="text/javascript">
injectorsHolder['NewsContainer1'] = function() {addNewsBlock('NewsContainer1', 'Politics'');};
</script>
</div>
<div id="NewsContainer2">
<script type="text/javascript">
injectorsHolder['NewsContainer2'] = function() {addNewsBlock('NewsContainer2', 'Sport'');};
</script>
</div>
transforms into much compact form:
<gwt:newsBlock id="NewsContainer1"/> <gwt:newsBlock id="NewsContainer2"/>
I omitted here other little things like your taglib descriptor, etc. But you can read it on the web, there are plenty of articles.
im wondering is it still possible to debug such solution with gwt debugger?
come here and I will show you
it is debug-able
Good post, you cover bits I took for granted in mine in http://supplychaintechnology.wordpress.com/2009/12/18/the-role-of-jsps-in-a-gwt-world/.
Yeah Jim, I’ve read your post when was looking for info on this subject. Thanks a lot, it was helpful.
Can you post a little example with 2 or more pages?
Your post its nice
Posted.
Can’t spend more time on this since have a lot of work.
Hope this helps.
Thanks Alex. Another good way of dealing with these sort of injections.
I have dealt with similar kind of scenario in our application, where in, instead of GWT JSNI calling public non-GWT JS functions, we have implemented it in the other way as follows.
public JS function, called on window load, will have a time out thread running for every 100msec which keeps checking for a DOM object presence which has the same id as GWT module (Typically all GWT modules will generate an IFrame element of id same as the GWT module’s id) and then invoke one JSNI function.
Hi Nanich,
everything can be done in few ways.
I think there are even more means to resolve this problem
Regards,
Alex
Yeah true. I agree.
But I just wanted to share a different approach that we have followed.
Your approach is good. Especially because it resolved that particular problem in your particular project. That’s the main thing.
can you publish the entire solution?
post is updated.
Please take a look.
Just a question/note:
Javascript VM is a single threaded VM and I have always had the impression that the page is rendered from the upper to the bottom of the code.
Inserting the GWT application call first that your javascript function call is going to suffice?
Explaining better:
Does EntryPoint’s constructor execute first when GWT application call is first inside the browser’s body?
P.s: I have understood that there are event handlers but I am looking at the code simply inserted into the page.
P.s:
Does the new GWT’s iframe spawn a fresh new JsVM which is totally asyncronous with the main page’s one.
In this case compiling with -xs linker should solve syncronization issues.
Regards?
I have forgotten a “?”
ERRATA:
Does the new GWT’s iframe spawn a fresh new JsVM which is totally asyncronous with the main page’s one”?”
Regarding this one: I am not such a specialist in JS VM guts. I think you are much better at this. Can’t answer this question.
second ERRATA, I have found out where “?” is gone
Regards? —> Regards
Hi Fabiano,
I have reviewed the post once again after your comment and see that there might be a case when my code will not work.
I made an assumption while writing the post that HTML page’s code is quite short and browser will process the injections inside divs much faster then the nocache GWT script will be loaded. In my example case that was exactly the case.
But I think if the HTML code is quite long and contains a lot of other calls (so those DIVs are somewhere very distantly) it may happen that GWT script will be loaded and EntryPonint class instantiated at the moment when injectorsHolder object is still empty or partially empty. So not all injections could be executed.
I am not sure if this may happen though…
The possible solution may be the moving of the GWT script line from the HEAD of the document to the very last line in it’s BODY. Though I have not tested it.
Thanks for putting this together, very useful.
you are welcome
Hi,
I found this entry informative. I’m new to GWT though. Can you clue me in: do I still need to define Application.gwt.xml? If so, how do I set this up so all of my site JSPs can inject GWT snippets?
Transferred to Blogger: http://mem0ry-leak.blogspot.com/2010/08/custom-integration-of-gwt-widgets-into.html
I think there is a solution that does not imvolves javascript: the GWT module can just scan the DOM when it is loaded and then find the special components tags and place the GWT widgets in the right places. I did it once, it worked just fine and I didn’t have to call javascript methods directly.
Hi
Thanks Alexandar for this article. Whe I tried to simulate the code snippets you provided here. I create a sample web project having JSP page, then I have a separate gwt project having the module. Then I put generated no-cache.js file and associated generated fles inside the web project in web content folder. But when I am running the web project one. It says “Gwt Module “samplegwtproject” may need be (re)compiled”. Am I doing anything wrong in configuring the gwt module inside the web project. Would you please help to get rid of this?
Thanks,
Raja
Hi Raja,
sorry but I can’t unfortunately.
I have not been doing things with GWT for a recent year or so…