Busy again :))
Published by admin on Tuesday, July 21, 2009 - 04:47:11 - Filed under General, News, Plugins/beans, Servoy, Java
Admit it, you thought I was on holiday!
5 days without a post, and you thought: that’s it! It wasn’t meant to be, he just got tired of it this time!
Truly I needed some vacations from the tutorial series, the last one was big and took a long time to do (37 pages!), and I fear that the next part will be big too. So to make for some recreation, I had been busy again… on the busy plugin!
This time I had the plan to make it web compatible…
This is all because deep inside, I have this foolish belief that every plugins and beans should be web compatible, or not exist at all! That’s right! To be true to the platform, every single thing you do in the smart client SHOULD have its counterpart for the web client. As my daughter repeated after seeing Peter Pan lately:
“I do believe in fairies, I do, I do!”
In fact, I’m already dreaming of a web compatible JSplitPane… I have some wild ideas about it but I can’t tell right now if will be able to do it or not, I haven’t started it yet! There are so many amazing things you can do with javascript widgets nowadays and so many nice javascript frameworks around that I can’t believe that it will be impossible. Split panes browser implementations in JavaScript already exists, so why not make them available in Servoy? Something for later…
In the meantime I settled to work on an easy one. Or so I thought!
The busy plugin - for those of you who don’t know it - has been released lately as open source by Scott “Servoy Guy” Buttler. I have been made contibutor to the project on google code so I wanted to see how easy it would be to make a web compatible version out of it.
It turns out that there is a very close JavaScript browser implementation as a jQuery plugin, the “blockUI” plugin (see http://malsup.com/jquery/block/ for the plugin and http://jquery.com/ of course for jQuery). jQuery is a very clever JavaScript library that allows you to do a lot with very little code, if you don’t know about, shame on you! Run to the jQuery website and feel much more clever!
A good thing about the jQuery lib is that it has a compatibility mode, and it turned out that it is a must when you need to put it inside Servoy’s Wicket pages. This is because Servoy’s Wicket pages already use prototype.js (another seminal JavaScript library), and prototype define the $ as a core method, when jQuery does it too! But you can actually tell jQuery to run in compatibility mode so that was one problem less!
I first made a static version of the javascript code I wanted to use in Servoy to allow for the same kind of parameters the Swing version already had (plus a few useful ones). You can play around with this “static” version by downloading the code here (unzip and launch the well-named “test.html” in your preferred browser).
This static version allowed me to:
1/ tests for the jQuery compatibility mode with prototype inserted before (as it is in Servoy),
2/ tests for parameters and how to pass them from a method that would be called by an Ajax triggered Servoy method
3/ tests for a callback function which is an essential part of the plugin (the “cancel” button should trigger a Servoy method on the server side to stop the process)
Once I had this running, I only had to integrate it in Servoy as a Wicket behavior.
But to do this, I had to do a fair amount of refactoring.
Now for those who likes to know the all the gory details (I know you are there :), I can enumerate of few things I did and why I did it:
1/ the plugin in its previous version was called from the smart client and actually running from INSIDE the smart client (no need to get back to the server to run the method). So it was called from code like the following (”use in a try/finally” said the disclaimer help comments because it blocked the UI and if anything went wrong, your user was f***ed!):
try {
plugins.busy.busy([message],[dialogName],
[showPane],[showCancelBtn]);
// some lengthy process here
} catch (e) {
// whatever you want to do with it
application.output(e);
} finally {
// release the GUI:
plugins.busy.ready();
}
This code could run fine in the smart client because the line plugins.busy.busy() is first executed (showing the glassPane for example) and then the rest of the code is executed.
This is not so in the web client at all!
When you call the plugin in a method, the code of the whole method is actually executed before getting back to the browser, meaning that your lengthy process is done and then you see the glassPane for a brief moment before it is closed by the ready() command!!!
The only possible workaround of this implementation in the web client would have been to ask the users to make use of JavaScript Continuations, which is a fairly advanced JavaScript topic that I doubt a lot of Servoy developers have ever used (see Paul Bakker very interesting webinar - http://forum.servoy. … hp?f=26&t=12457 - to learn more if you don’t know about Continuations, - this is very clever stuff, but not too easy to grasp and use).
So I gathered that to be used in the web client, the process method (”the lengthy process”) was to be called by the plugin itself.
That made for another parameter, and then I wanted to add a callback parameter where the user could give the plugin a method to callback when the user clicked the cancel button (because I thought the use of the isCanceled was a bit too old school and not very practical - not enought encapsulation for my taste). So that made for 2 parameters more!
And I also wanted to add a few niceties like the ability to give the cancel button another text than the default “Cancel”, and make it i18n compatible (I’m French and when I see a “cancel” button inside a French interface I frown :). - And of course the message itself was to be i18n compatible!
Another parameter that I wanted was the opacity, that I thought would be nice to let the user modify.
And even the color of the glassPane was a good candidate for customisation (you can try it with red and set the opacity to 0.5 and you will see how your life will be nicely pink all of the sudden! Anyway my daughter loves it!
2/ So all in all, that was 5 parameters to add to the 4 already existing!
And as you might know, to be able to manage “optional” parameters in java, you need to code each possibilities as alias methods, but that was a lot of alias method to write. So I thought that using Rhino, it was actually easy to retrieve a simple JavaScript object as a parameter. From the java point of view a “Scriptable” (which is the equivalent to a JavaScript object) is very similar to a Map, and it allows to retrieve values nicely in one single method. So that’s how I coded the new js_block() method (I couldn’t keep the old js_busy() method name since I needed to make it deprecated)
3/ Another problem is how you call a method from Servoy in a web client callback.
There is actually 2 ways of calling a javascript servoy method from inside a plugin or bean:
The first is to by the Rhino Function itself and use the call() method on it (pretty easy, look at the sources of the web_client_utils plugin that Sean Devlin made recently available on google code to see an example of that http://code.google.c … /p/web-client-utils/).
The second way is to use the name of the method (parse it to test if it is a global, starting with the “globals.” prefix, and then use the IClientPluginAccess method executeMethod() with it).
Now the only usable way to do it if you don’t want to have to wait for the completion of the method before executing the rest of your plugin code is actually the executeMethod(), because it as an “asynchronous” flag that you can use for that matter. The call() on a Rhino function is synchronous so that’s our problem again: if you call the “lengthy process” this way, you will end up showing the pane in the web client after the “lengthy process” has finished!!!
So that’s why I can’t accept a Function as a parameter for the process function, it needs to be the name of a method (since there is no way to retrieve the Servoy method name from a Rhino Function).
4/ Now another big problem that made for refactoring was the fact that the Smart client (not launched from developer, but from a Server) doesn’t know anything about Wicket: the wicket.jar is not downloaded in the client’s java cache, which is OK of course because it’s not needed! Except that if your plugin has any kind of reference (imports) to a class that is in a Wicket package, you will see these kind of exceptions in the client’s java console:
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/apache/wicket/behavior/IBehavior
And after that your plugin will not be loaded at all… You won’t be able to use it! Any subsequent call will result in more Exceptions! It might be OK if your plugin doesn’t have a smart client side (like Sean’s web_client_utils) although I don’t like to see my user’s console full of nasty Exceptions (and I will probably submit a patch to that one soon :) - but when you actually want to use the plugin in the smart client as well you need to find a way to hide your calls to Wicket packages.
Now I know of one plugin which does something similar already, and that’s the “popupmenu” plugin: this is a very nice one that you can use in the smart client and the web client as well (and the sources are included in the jar)! I will probably make a tutorial on this one because I actually used it to build an automatic menu system which I think could be useful to some adventurous developers who like me, want their solutions to be web compatible as much as possible…
So the answer to that problem was to repackage the Wicket classes and the Swing classes separately and defer the instanciation of the final component to the very last minute (not in the IClientPlugin, but in the IScriptObject itself which in fact becomes a Factory).
So that was another reason for this big refactoring.
5/ Then there was the fact that since I used the jQuery library, I thought that I might have to use it in some other components later as well, so I extracted it into its own HeaderContributor and the same thing for the jQueryBlockUI, to make them reusable from any other components, and put it in a new package “net.stuff.servoy.util.js”, with a BEAHVIOR_TAG constant used when inserted in the header. This is to avoid having many inserts of the same library. That’s planning for the future, that is!
6/ And since some methods were going to be used from the Swing classes as well as the Wicket classes I made an abstract base class with all these common methods (we will probably see these abstract beasts in the next part of my bean tutorial, wait for it!).
OK, so now you know why I made some of the design decisions I made on this plugin and you will be able to see the result of it on the google code project (you can use any svn client to retrieve the sources, it is an Eclipse project, and you can get it even as anonymous user).
One last thing I need to talk to you about…
There would be a lot more actually, but I’m not writing a tutorial here, just giving you pointers to understand the actual code, and justifying my 5 days without posts ;-)
The plugin has still a problem in Firefox (and in IE when used the first time from a modal dialog).
I gave some clue about it in the issue tracking system of the google code site, but I can tell you a bit more here (because I know you like to know about these stuff):
As I said earlier, I’m using jQuery and jQuery-blockUI plugin, and I also add my own method, and all this needs to be added to the html header (at least the <script src> tags used to insert the libs).
But these insertions in the header (made by renderHead() methods of Wicket AbstractBehavior, to tell you the truth) are actually made only when the plugin is called from an Ajax update method.
IE seems to cope pretty well with it and accept that insertion on the fly (at least when not in a modal window) but Firefox doesn’t (or at least it doesn’t interpret the inserted libs coming from an Ajax request). Once the code has been called, if you actualize the form in the browser, or when you come back later to visit the same form, the insert will be there in the header already so the subsequent calls to the plugins won’t need to do the inserts again and the plugin will now work (in Firefox and in IE modal dialog).
I have tried to do the inserts before the calls from the plugin (but then I had problems because it was to be done when instantiating the plugin - and the smart client was complaining again about “NoClassDefFoundError” for IBehavior - see above), so it was not good!
In the end, I didn’t find in the public API any way to statically insert some code to all the pages rendered. Meaning a way to insert code to every pages systematically, so that you don’t need to insert it dynamically when you call your methods. I didn’t find any way to do that. The addBehavior of the IPageContributor interface only works when attached to a component (the plugin in this case), and it complains if it isn’t. And there is no global “addHeaderContribution” method that could be called.
A much needed method IMHO!
So I think that I will file a case to the support system for that feature.
Just wanted you to know what it was about…
Now after that interlude, I think I need to get back to the bean tutorial, part 6!
