Thursday, January 8, 2015

GWT Progress Bar

Recently, I needed to add a Progress Bar to my main work project.

I had a look around, and there are a few different solutions, but I ended up writing something that uses the HTML5  <progress> element.
eg.,


This picture shows the progress bar dropped into a dialog box, with a status label underneath it.



And, here is the code, in case anyone wants to add a progress bar in a few lines:


package ...;

import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.HTML;

public class ProgressBar extends HTML 
{
    public ProgressBar()
    {
        super("<progress style='width: 98%;'></progress>");
    }
 
    /**
     * Set the progress indicator the the specified values
     * @param value Current progress value
     * @param max Target/complete value
     */
    public void setProgress(int value, int max)
    {
        Element progressElement = getElement().getFirstChildElement();
        progressElement.setAttribute("value", String.valueOf(value));
        progressElement.setAttribute("max", String.valueOf(max));
    }
 
    /**
     * Remove the progress indicator values.  On firefox, this causes the
     * progress bar to sweep back and forth.
     */
    public void clearProgress()
    {
        Element progressElement = getElement().getFirstChildElement();
        progressElement.removeAttribute("value");
        progressElement.removeAttribute("max");
    }
}

Have fun!

Monday, November 12, 2012

GWT(2.4+) Drag & Drop in a Tree

For an interface I am working on for Green Motion Travel, I needed to create a file organizer.

The files are presented in a tree, and the organizer allows you to drag files and folders around within the tree.

To accomplish this, I decided to use GWT's new Drag And Drop features.

I couldn't find any real documentation on how to use it, but there is a video on the subject on youtube, here:
    Google I/O 2011: GWT + HTML5: A web developers dream!
The bit on Drag&Drop starts at about 21 minutes in.
It turned out to be fairly easy!

Where to start?

With GWT, you can add Drag & Drop to almost anything.
I wanted to add D&D to TreeItems.  As it turns out, TreeItems don't really work well as D&D targets.  The target somehow ends up being the tree itself, so that you can't really drop a file onto another treeitem.

So, instead of adding D&D to the TreeItem objects, I instead created HTML objects and set those as widgets in the tree, and then added D&D to those.  Labels would work too, of course, but I wanted to add icons to represent folders and files.  For this tutorial, I will just use Labels, as it is a bit cleaner.

To make a widget draggable, you must do the following:
  • Set the 'draggable' property
  • Add a drag start handler
Some widgets support a draggable property directly, however Label does not. So, we need to go to the element and set it there.
In the Drag Start Handler, you must set the event data, or some browsers will not allow you to drag it.
In my case, I don't really care what the data is.  Instead, I will set a class field member (called 'dragging') to remember what label is being dragged.
Finally, we will copy an image of the existing label as the drag image.

Making a Label Draggable

Label draggableLabel = new Label("drag me!");
dragLabel .getElement().setDraggable(Element.DRAGGABLE_TRUE);
dragLabel.addDragStartHandler(new DragStartHandler()
{
  @Override
  public void onDragStart(DragStartEvent event)
  {
    // Remember what's being dragged
    dragging = dragLabel;
    // Must set anyway for FireFox
    event.setData("text", "hi there");
    // Copy the label as the drag image
    event.getDataTransfer().setDragImage(getElement(), 10, 10);
  }
});

If you were looking closely, you might have noticed that dragLabel is not final, as it would have to be in the above code.  I didn't bother, as I have wrapped everything up into a class that extends Label, as you will see below.

To make a widget droppable, you must:
  • Add a DragOver handler
  • Add a Drop handler
Again, some widgets support dropping in the API, however Label does not.  So instead, you need to add it using addDomHandler.  No problem.
The DragOver handler doesn't have to do anything.  I will use it, however, to apply a style to the label.
To counter the drag-over style, I will also add a DragLeave event.

Making a Label Droppable

Label dropLabel = new Label("Drop onto me");
dropLabel.addDomHandler(new DragOverHandler()
{
    @Override
    public void onDragOver(DragOverEvent event)
    {
        dropLabel.addStyleName("dropping");
    }
}, DragOverEvent.getType());

dropLabel.addDomHandler(new DragLeaveHandler()
{
    @Override
    public void onDragLeave(DragLeaveEvent event)
    {
        dropLabel.removeStyleName("dropping");
    }
}, DragLeaveEvent.getType());

dropLabel.addDomHandler(new DropHandler()
{
    @Override
    public void onDrop(DropEvent event)
    {
        event.preventDefault();
        // Do something with dropLabel and dragging
        etc...
    }
}, DropEvent.getType());

BIG NOTE:  Make sure to call event.preventDefault() in the onDrop, or else the browser might navigate away from the current page!

That's about it for making my labels draggable and droppable.
For my situation, I needed to create:
  • Leaf nodes for files, that are draggable and not droppable.
  • Root folders that are droppable, but not draggable
  • Subfolders that are both draggable and droppable.
I wrapped all of the above up into a class, called DragDropLabel.
The DropHandler for this example will move the TreeItem of the source under the TreeItem of the drop target.
Here is the implementation of my DragDropLabel class:

class DragDropLabel extends Label 
{
     private static DragDropLabel dragging = null;
     final boolean droppable;
     public DragDropLabel(String text, boolean draggable, boolean droppable)
     {
         super(text);
         if (draggable)
         {
             initDrag();
         }
         if (droppable)
         {
             initDrop();
         }
         this.droppable = droppable;
         if (droppable)
         {
             addStyleName("droppable");
         }
         else if (draggable)
         {
             addStyleName("draggable");
         }
     }
    
     private void initDrag()
     {
         getElement().setDraggable(Element.DRAGGABLE_TRUE);
         addDragStartHandler(new DragStartHandler()
         {
             @Override
             public void onDragStart(DragStartEvent event)
             {
                 // Remember what's being dragged
                 dragging = DragDropLabel.this;
                 // Must set for FireFox
                 event.setData("text", "hi there");

                // Copy the label image for the drag icon
                // 10,10 indicates the pointer offset, not the image size.
                event.getDataTransfer().setDragImage(getElement(), 10, 10);
             }
         });
    }

     private void initDrop()
     {
         addDomHandler(new DragOverHandler()
         {
             @Override
             public void onDragOver(DragOverEvent event)
             {
                 addStyleName("dropping");
             }
         }, DragOverEvent.getType());

         addDomHandler(new DragLeaveHandler()
         {
             @Override
             public void onDragLeave(DragLeaveEvent event)
             {
                 removeStyleName("dropping");
             }
         }, DragLeaveEvent.getType());

         addDomHandler(new DropHandler()
         {
             @Override
             public void onDrop(DropEvent event)
             {
                 event.preventDefault();
                 if (dragging != null)
                 {
                     // Target treeitem is found via 'this';
                     // Dragged treeitem is found via 'dragging'.

                     TreeItem dragTarget = null;
                     TreeItem dragSource = null;
                     // The parent of 'this' is not the TreeItem, as that's not a Widget.
                     // The parent is the tree containing the treeitem.
                     Tree tree = (Tree)DragDropLabel.this.getParent();
 
                     // Visit the entire tree, searching for the drag and drop TreeItems
                     List<TreeItem> stack = new ArrayList<TreeItem>();
                     stack.add(tree.getItem(0));
                     while(!stack.isEmpty())
                     {
                         TreeItem item = stack.remove(0);
                         for(int i=0;i<item.getChildCount();i++)
                         {
                             stack.add(item.getChild(i));
                         }

                         Widget w = item.getWidget();
                         if (w != null)
                         {
                             if (w == dragging)
                             {
                                 dragSource = item;
                                 if (dragTarget != null)
                                 {
                                     break;
                                 }
                             }
                             if (w == DragDropLabel.this)
                             {
                                 dragTarget = item;
                                 w.removeStyleName("dropping");
                                 if (dragSource != null)
                                 {
                                     break;
                                 }
                             }
                         }
                     }
                     if (dragSource != null && dragTarget != null)
                     {
                         // Make sure that target is not a child of dragSource

                         TreeItem test = dragTarget;
                         while(test != null)
                         {
                             if (test == dragSource)
                             {
                                 return;
                             }
                             test = test.getParentItem();
                         }
                         dragTarget.addItem(dragSource);
                         dragTarget.setState(true);
                     }
                     dragging = null;
                 }
             }
         }, DropEvent.getType());
     }
 }


And so, with that class, now all I have to do is add some of them to a tree, like so:
Tree tree = new Tree();
RootPanel.get("main").add(tree);
  
// root is not draggable.
TreeItem root = new TreeItem(new DragDropLabel("root", false, true));
tree.addItem(root);
  
// Add some draggable and droppable folders to 'root'
root.addItem(new DragDropLabel("folder1", true, true));
root.addItem(new DragDropLabel("folder2", true, true));

etc...

Here is a screenshot of the tree in action, dragging a file onto a folder.



You can download the source code for the example here.
Select "File/Download" to save it.
Note that it does not contain the GWT libs to save space.

Please leave a comment if this has helped you out!


Thursday, November 8, 2012

GWT Auto Logout & Open Dialogs

Currently, I work for CounterPath, developing a large GWT application (as well as doing some GWT work for Green Motion Travel).

At CounterPath, my GWT application has an auto-logout feature.
This is to avoid the situation where an application user doesn't do anything for a while, and then edits a pile of information and clicks 'Save', only to find out their session timer has expired, meaning they just lost their work.

The auto-logout feature keeps an internal timer that matches the server's session timeout.
The internal timer is reset every time I do some server interaction.
To avoid inserting these calls all over the place in my code, I added an Async RPC facade;  My application uses the facade, and the facade makes the call to the server.
The downside is that I have to maintain another copy of the RPC interface.  Fortunately, that part is quite stable.

For example, I have:
  • MyData - the RPC interface
  • MyDataAsync - the Async version of MyData
and now,
  • MyDataAsyncSession - a class that implements MyDataAsync

MyData:

public interface MyData extends RemoteService
{
    public Data getData();
}

MyDataAsync:

public interface MyDataAsync
{
    public void getData(AsyncCallback<Data> callback);
    etc...
}

And, in my client code,

MyDataAsyncSession:

public class MyDataAsyncSession implements MyDataAsync
{
    MyDataAsync realRPC = (MyDataAsync)GWT.create(MyData.class);

    @Override
    public void getData(AsyncCallback<Data> result)
    {
        resetSessionTimer();
        realRPC.getData(result);
    }

    etc...
In my actual client code, I would then have:
MyDataAsyncSession rpc = new MyDataAsyncSession();
rpc.getData(new AsyncCallback<Data>(){...});
In this way, I don't have to worry about sprinkling calls to resetSessionTimer all over my code.
If this was all on the server, I could probably use AOP to insert calls to resetSessionTimer() in the right places...

On to the main point of this post.
In a few places, I use popup dialogs.
This leads to a problem: If you do something that generates a popup dialog, and then let it sit there for 30 minutes, the GWT application will auto-log-out, which will hide the other panels and show the login panel.  However, it won't hide the popup dialogs!  They will continue to show whatever was being worked on.

At first, I was considering a popup registration system, maintaining a list of popups that are currently open.  This could get messy, with some popups being auto-close, and others closed with a button or other control.
Fortunately, I found an easier way:  Just search the default root panel for PopupPanel widgets, and close them!

Here is the code, which is now part of my doLogout() function:

Code to Auto-Close Popup Panels

// Create a widget processing list, and
  // add the default root panel to it.
  List widgetList = new ArrayList();
  widgetList.add(RootPanel.get());
  while(!widgetList.isEmpty())
  {
      // Pull the first widget from the list
      Widget w = widgetList.remove(0);

      // If it is a popup, hide it!
      if (w instanceof PopupPanel)
      {
          ((PopupPanel)w).hide();
      }
      else if (w instanceof HasWidgets)
      {
          // Add any child widgets to the processing list.
          Iterator iter = ((HasWidgets)w).iterator();
          while(iter.hasNext())
          {
              Widget child = iter.next();
              widgetList.add(child);
          }
      }
  }

That's a lot easier than having to maintain a list of open popups!

Thursday, August 25, 2011

GWT and Username/Password autocomplete

Userbase Demanded Login-Autocomplete!

For one of my work projects, I had to pass-through login from my server code to another service.  No problem.  My initial design just used a GWT RPC post to send the username and login credentials to my server, which sent the login request to the other server.  If that succeeded, then I would assign a variable to the session and return success.

However,  the input boxes for username and password did not have the desired autocomplete feature.


After lots of messing about, and some Googling, I came up with a pretty easy strategy.

Part 1: Getting the browser to save the password

1. Convert the login to a form, and have the form submit a POST to a login servlet.  The inputs must be named 'username' and 'password', and the password input must be of type 'password' as well.

As an aside, I was disabling the inputs and the login button before submitting the post, so the user can't keep clicking it.  This caused the post to be empty!  In order for the post to actually send the username and password, I had to disable the inputs after submitting the post.


Essentially:
public class LoginForm extends VerticalPanel
{
    final TextBox username;
    final PasswordTextBox password;
    final private FormPanel formPanel = new FormPanel();
    final SubmitButton button = new SubmitButton("Log in");

    public LoginForm()
    {
        add(formPanel);
        formPanel.setAction("servletpath/login");
        formPanel.setMethod(FormPanel.METHOD_POST);

        VerticalPanel vp = new VerticalPanel();
        formPanel.setWidget(vp);

        username = new TextBox();
        username.setName("username");
        vp.add(username);

        password = new PasswordTextBox();
        password.setName("password");
        vp.add(password); 
        vp.add(button);

        formPanel.addSubmitCompleteHandler(
            new SubmitCompleteHandler(){...});
    }
}
Okay, that works for me: After clicking the Login button, Firefox asks if I want to save the password.
Also works for IE and Safari.  Chrome *sometimes* works.  I can't believe IE works better than Chrome for this.

Part 2: Getting the Presets Back into the Login Form
So, we've got the values saved.  But it doesn't work!
The browser does not apply the password database to forms that are added by JavaScript.

I read about one technique, which was to add a hidden to the root HTML page, and then grab the pre-filled values from those inputs.
However, this does not help in the case where you have more than one username/password!
In my case, this is a real concern, as some users need to log in as the super admin from time to time.

Here's my variation of the technique:
Add a hidden form to the root HTML page, and then wrap the inputs.
Works like a charm!

Here is my root HTML page:

<html>
  <head>
    ... usual GWT stuff ...
  </head>
  <body>
    <div id="title">
      etc...
    </div>

    <div style="display:none;">
      <form id="login_form" method="post" action="ccsadmin/login">
        <input id="creds_username" name="username"/>
        <input id="creds_password" name="password" type="password"/>
      </form>
    </div>
  </body>
</html>

Then, in my LoginForm class:

public LoginForm(String message)
{
    formPanel = new FormPanel();
    add(formPanel);
    formPanel.setAction("loginservlet/login");
    formPanel.setMethod(FormPanel.METHOD_POST);

    VerticalPanel vp = new VerticalPanel();
    formPanel.setWidget(vp);

    username = TextBox.wrap(DOM.getElementById("creds_username"));
    vp.add(username);

    password = PasswordTextBox.wrap(DOM.getElementById("creds_password"));
    vp.add(password);
    vp.add(button);
    formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler(){...});
}

There you have it!
Clicking in the username box will show the list of saved credentials for my login page.

Note:
I just searched and found another example that just wraps the entire hidden form.  However, I like my version a bit better, as my app is internationalized, and so I am using the GWT i18n code to add labels and text programmatically.

Update 1:

Previously, I was using a normal Button to submit the form.
To try and get Chrome working, I converted it to a SubmitButton.
This does seem to work sometimes, not sure what's wrong.

Update 2:

I have zipped up a complete example of a GWT application.  You can download it here:
autologin.zip
To download it, click the link, then select File/Download.
To use it:

  • Start Eclipse
  • Then, select 'File/Import...'
  • Select 'Existing projects into Worksace' and click Next...
  • Choose 'Select archive file' and choose the downloaded file
  • Click 'Finish'
Note that you must have GEP installed, and with GWT 2.4+.



Friday, August 13, 2010

A few examples of using svg2canvas

The canvas items on this page were created as SVG files, and then converted to javascript using my online service, svg2canvas.
This first one is just something I whipped up quick, using Inkscape. The text "Pow!" was converted to a path.
Next, I have a ladybug that I downloaded from www.openclipart.org, drawn by someone named "/ lemmling". You can see the original SVG here: Original SVG
Finally, I have a gauge that I drew up. This gauge is animated, which required a bit of coding, to animate only the needle.

Tuesday, August 10, 2010

HTML5 Canvas Editing

I have been experimenting for a while now with the HTML5 canvas element.
I have encountered a few drawbacks while doing so.
  • The first drawback is that there is no ellipse support!  You can draw a circle, or a circle segment / arc.  But you will have to use cubic bezier curves if you want to draw an ellipse.
  • Ever try to edit a bezier curve by hand? Ouch.
  • And finally, there isn't really any editor support out there yet.
With that in mind, let me present my solution, for all you HTML5 canvas developers:  svg2canvas.
I wrote up a bare-bones converter applet, that will accept a small svg file, and attempt to convert it to a javascript file using canvas.
As noted in the webapp, there is nowhere near full SVG support.  Text is not even supported yet!  However, you can always convert text to a path.


Here it is:
http://www.antennamap.com/svg2canvas
Note: This is currently broken.  If anyone is interested in trying it out, let me know and I'll get it working somewhere else.

Instructions:
  • Download and install Inkscape
  • Draw up a nice SVG graphic
  • Upload it to svg2canvas
  • Preview and download javascript!
  • Maybe think about posting a comment!
I make no guarantee that your SVG file will work.  Here's hoping!

Wednesday, June 30, 2010

Experimenting with HTML Canvas

I am starting to get interested in using the HTML canvas element with GWT.
I have downloaded the gwt-canvas project, which provides a very nice API wrapper.  The first thing I did was extend it to add text support.  Okay, I only added text support for browsers that support it.

Anyway, that brings me to my first pain point.  Experimenting with canvas in GWT is extremely time consuming, as you must recompile to JavaScript every time you make a change.  (Canvas is not supported in hosted mode, AFAIK!)

However, there is an extremely easy way to experiment with the canvas:
  • Get firefox, and the firebug addon.
  • Save the following text to the file canvas.html :

<html>
<body>
<canvas id="canvas" width="300" height="200" style="border: 1px solid black;">Time to upgrade!</canvas>
</body>
</html>

  • In Firefox, select "File/Open File", and open the file you just saved.
  • You should see the following:


  • Open firebug (in the bottom right corner), and select the Console tab
  • You are now ready to experiment!  Just type your javascript into the console command prompt, ">>>".
  • For example, you might go to the Mozilla Drawing Graphics with Canvas tutorial, and copy those commands, one line at a time, into the command prompt:

There you have it! A command line interface for experimenting with canvas, with things you probably had lying around the home!