GWT Right-click Context Menu

The Google Web Toolkit has been out for a while now, and yet there is still basic functionality that is missing from the toolkit.  Don’t get me started on the lack of draggable/resizable columns for the FlexTable, because that’s a rant and a half.  Given that GWT’s event handling model isn’t bad, you’d think they’d have included from the get-go the ability to handle right-clicks and bringing up a context menu or popup menu.  Well, even with 1.6 on the doorstep it seems they forgot again or just don’t care.   Now some people will spout out “web apps don’t need or shouldn’t have right-clicks handled or context menus overridden”……and for those I say STFU!  Web apps are used for more than just banking, news, forums and dare I say blogs.  The browser is becoming the new medium for running applications and just because an application is running in a browser doesn’t mean we should limit functionality.  That’s about as narrow minded as saying that we’ve only had one mouse button for this long, why add a second one?  Duh!

Anyway, enough with the blabbing.  I’ve put together a simple example to add a right click context menu and override the default browser context menu using GWT. 

In the box below you can try it out, right-click in there and you can demo it.

In case the iframe doesn’t show up in your browser you can see and try the example here.
Now here’s how it’s done.

I used a DeckPanel to switch between several panels. The popup context menu is used to choose.  To allow the DeckPanel to catch the right-click event (and you can also listen for double-clicks and several other things) I extended the DeckPanel.   For simplicity I added “Adv” (Advanced) in front of the several classes I’ve extended so this one will be AdvDeckPanel.  The first thing to do in the constructor is add sinkEvents(). Then we’ll override onBrowserEvent() in the class.  Here’s the code:

public AdvDeckPanel() {
  super();
  sinkEvents(Event.ONMOUSEUP | Event.ONDBLCLICK | Event.ONCONTEXTMENU);
}
 
public void onBrowserEvent(Event event) {
  GWT.log("onBrowserEvent", null);
  event.cancelBubble(true);//This will stop the event from being propagated
  event.preventDefault();
  switch (DOM.eventGetType(event)) {
    case Event.ONMOUSEUP:
      if (DOM.eventGetButton(event) == Event.BUTTON_LEFT) {
        GWT.log("Event.BUTTON_LEFT", null);
        listener.onClick(this, event);
      }
 
      if (DOM.eventGetButton(event) == Event.BUTTON_RIGHT) {
        GWT.log("Event.BUTTON_RIGHT", null);
        listener.onRightClick(this, event);
      }
      break;
    case Event.ONDBLCLICK:
      break;
 
    case Event.ONCONTEXTMENU:
      GWT.log("Event.ONCONTEXTMENU", null);
      break;
 
    default:
      break; // Do nothing
  }//end switch
}

Notice the two lines in onBrowserEvent():
event.cancelBubble(true);
event.preventDefault();

These are the two lines that tell the browser not to show it’s default context popup menu. Also note that overriding the default context menu doesn’t work in all browsers, I’m not sure if this is a bug in GWT. If you’re using firefox then you’ll have no problems, with IE you may need to add the following to your html:

<body oncontextmenu="return false;">

Other than that there’s just a switch statement that checks the event type, in this case we’re interested with ONMOUSEUP, and we’ll call the listener’s onClick() or onRightClick() based on the Event’s fields.

AdvDeckPanel also has a reference to AdvClickListener which looks like:

public interface AdvClickListener extends ClickListener {
  void onClick(Widget sender, Event event);
  void onRightClick(Widget sender, Event event);
}

This reference is basically the same as ClickListener, but has a separate method to handle the right-click.  I also pass the Event object so I can get the x and y from the click so the context menu shows up at that location instead of the top-left of the screen.

AdvDeckPanel implements AdvClickNotifier which does the same thing as GWT’s SourcesClickEvents interface, but handles the AdvClickListener instead.

public interface AdvClickNotifier {
  public void addClickListener(AdvClickListener listener);
  public void removeClickListener(AdvClickListener listener);
}

So, once you have your widget (in this case the AdvDeckPanel) and the listeners set up to handle the right-click, then we add in the code to build the popup menu and commands that go with it.  I put all of this in the EntryPoint.  I have three widgets (panels) and a Command for each like this:

private AdvDeckPanel deckPanel = new AdvDeckPanel();
final private PopupPanel popupPanel = new PopupPanel(true);
private VerticalPanel defaultPanel = new VerticalPanel();
private SimplePanel imagePanel = new SimplePanel();
private SimplePanel sponserPanel = new SimplePanel();
 
Command showAlertCommand = new Command() {
  public void execute() {
    deckPanel.showWidget(0);
    popupPanel.hide();
    Window.alert("Hope this example helps.");
  }
};
 
Command showImageCommand = new Command() {
  public void execute() {
    deckPanel.showWidget(1);
    popupPanel.hide();
  }
};
 
Command showSponserCommand = new Command() {
  public void execute() {
    deckPanel.showWidget(2);
    popupPanel.hide();
  }
};

A Command is called when the MenuItem is clicked.

Now the code to build the menu, link the commands, and handle the right-click:

private void createPopupMenu() {
  MenuBar popupMenuBar = new MenuBar(true);
  MenuItem alertItem = new MenuItem("Show alert", true, showAlertCommand);
  MenuItem imageItem = new MenuItem("Show Oliver ", true, showImageCommand);
  MenuItem sponserItem = new MenuItem("Show sponser ", true, showSponserCommand);
 
  popupPanel.setStyleName("popup");
  alertItem.addStyleName("popup-item");
  imageItem.addStyleName("popup-item");
  sponserItem.addStyleName("popup-item");
 
  popupMenuBar.addItem(alertItem);
  popupMenuBar.addItem(imageItem);
  popupMenuBar.addItem(sponserItem);
 
  popupMenuBar.setVisible(true);
  popupPanel.add(popupMenuBar);
}
 
public void onRightClick(Widget sender, Event event) {
  int x = DOM.eventGetClientX(event);
  int y = DOM.eventGetClientY(event);
  popupPanel.setPopupPosition(x, y);
  popupPanel.show();
}

Lastly, to make the menu actually look like a popup menu I modified the CSS like so:

.popup {
  background-color: gray;
  border-color: gray gray gray gray;
  border-width: 1px 3px 3px 1px;
  border-style: solid solid solid solid;
}
 
.popup-item {
  font-weight: normal;
  font-size: 80%;
}

What this does is makes it so the border isn’t the thick default GWT blue, and uses a thin border with the right and bottom borders a bit thicker.  This gives the popup that shadowed look.

A couple of books worth checking out are: GWT in Actionand GWT in Practice
Here’s the source and more links.


39 Comments

  1. zmo says:

    Great ! And the same way it’s possible to listen to double mouseclicks ?

  2. Nick says:

    zmo:
    yes, same. In fact, if you look in the switch() I left the “case Event.ONDBLCLICK:” in there. That’s where you put your code to happen when a double click occurs.

    To make it easy you could add a method to the AdvClickHandler class called:
    void onDoubleClick(Widget sender, Event event);

  3. Juancho says:

    Hi Nick,

    First of all, sorry for my english.

    I’m trying to do your example but I don’t understand the class structure of that. Where do you define the “listener” object that is called from AdvDeckPanel class in onBrowserEvent method.
    Also, in which class are you definining the “onRightClick” implementation?. I think it has to be in a class that implement AdvClickListener interface.

    I don’t undestand what do you mean with “AdvDeckPanel also has a reference to AdvClickListener which looks like”. What does “reference” means in the object context?. It implements AdvClickListener?.

    And final, How do you bind AdvClickListener Interface and AdvClickNotifier interface in the example?.

    Rergards,
    Juancho.

  4. Nick says:

    Juancho: Reference mean that the class AdvDeckPanel holds a member variable of type AdvClickListener. I figured the easiest way to understand is to look at the code, so I updated the post above with a link so you can get the source files. Once you see it, you’ll have a better understanding. Let me know if you have any other questions.
    -Nick

  5. wos1 says:

    Nick,
    Thanks a lot for the example. I’m trying to implement the same concept and hit a wall.
    It works fine under the GWT toolkit integrated environment, and in IE.
    But in Firefox, nothing seems to work. Neither the left nor the right click.
    I’m using GWT 1.5.3.

    Have you tried with GWT 1.5.x? I read at some places that the event processing has changed from 1.4.x…

    Thanks again
    Wos

  6. Nick says:

    Wos:
    I’m using GWT1.5.3 as well. Did you try and right-click on the example area above? The area with the big letters that say “Right-click here to see GWT context menu” ? Didn’t work?

    I know it works on FF 3.0.X (I currently use 3.0.5) and IE7. I think opera had some issues, but there shouldn’t be any on FF.

  7. wos1 says:

    Your site works fine on all browsers. It’s good to know that you’re using GWT 1.5.3 as well. It means something badly implemented on my side.
    Still digging, thanks for the input.
    Wos

  8. Ixionai says:

    Wow! Thanks for posting this. =)

  9. zeo says:

    Too bad it doesn’t work in opera. =(

  10. Nick says:

    yea, the problem is that you can’t (or at least I couldn’t figure out how to) override the right-click. So the default opera context menu pops up.

  11. Eric says:

    Hi Nick,

    Can this is same functionality be added to a tree. i was trying to get a right click work on a TreeItem.
    Let me know any pointers.

    Thanks,
    Eric

  12. Nick says:

    Eric, I don’t see why you couldn’t do this with a tree as well.
    In my example I used a DeckPanel, but you can instead use any widget that subclasses Widget (or implements EventListener), because thats where the onBrowserEvent() comes from. Look at my AdvDeckPanel class and with that create your own Tree subclass (AdvTree or whatever you want to call it). Take the onBrowserEvent() code from the AdvDeckPanel and paste it into your new tree-class and take my AdvClickListener and AdvClickNotifier and add those to the project. Don’t forget the code in your new tree class to handle the listener and notifier.

    Honestly, now thinking about it, maybe you can just change the “extends DeckPanel” to “extends Tree” and start there. That would be a good first step.

  13. Eric says:

    Hi Nick,

    I tried doing what you suggested but still have a few issues ..

    1. My tree has 4 TreeItems under it and when i click on any of the leafs i am getting the complete dom object. i am not able to get the clicked object.

    2.There are few functions like getselecteditem() which are giving me null value.

    I am trying to read the TreeItem selected and call the handlers.

    Thanks,
    Eric

  14. Nick says:

    Eric, I made some small changes to implement a Tree, can I send you a source zip to the gmail address you left on your comment?

    A couple of issues I saw were:
    1. The event wasn’t being propagated to the Tree because I was calling event.preventDefault(); so the tree nodes were not opening.
    2. The same context menu opens for all nodes. This I didn’t change, but you can build a custom menu for each node by changing my createPopupMenu() and maybe taking in a parameter, OR having a separate method for each node.

    Let me know.

  15. Eric says:

    Nick,

    I got the tree nodes to open to the first level .. but when i click on the child node(TreeItem) i am still getting the root(TreeItem) object that gets passed to the listener. and the tree structure closes..without opening the child tree.

    let me know if any thoughts ..

    you can send it to the id left on my comment .. will try let you know.

    Regards,
    Eric

  16. Magnus says:

    Code.
    deckPanel.showWidget(0);
    popupPanel.hide();

    Where and how do you declare deckPanel and popupPanel? I assume deckPanel would be something like
    private AdvDeckPanel deckPanel = new AdvDeckPanel(); in EntryPoint but it’s not obvious.

    The post is well written with code separated etc but why do you leave out parts? Many people would never be able to implement this in their own project.

    I really appreciate the post though! :) I hope the GWT-team add this to a future version.
    Magnus

  17. Nick says:

    Magnus, I added the few lines to the EntryPoint section, I probably left it out because it’s trivial and doesn’t pertain to handling the click events.

    If you want to see the full working source for the entire project just click on the link at the bottom of the post that says “Here’s the source”. There’s a 7z file and you can extract the project.

    Have fun!

  18. Magnus says:

    I’m keep getting [ERROR] Line 46: No source code is available for type com.nick.client.AdvClickListener; did you forget to inherit a required module?

    I have imported com.nick.client.*;

    Any ideas?

  19. Nick says:

    There are 4 java files in the 7z, make sure you have all 4.
    AdvClickListener.java
    AdvClickNotifier.java
    AdvDeckPanel.java
    RightClickEx.java

    I just downloaded and extracted, and only 1 file even has code on line 46, and it has nothing to do with the listener. Very odd. There’s nothing special that needs inherited. You should be able to create a new GWT project, call the project RightClickEx and make it under the com.nick.client package. There’s a change to the html and css noted in the post, but they wouldn’t affect compiling at all.

  20. Nauris says:

    i had problem with this in big View-s, when scrolling appeared (menu appeared in incorrect places):
    int x = DOM.eventGetClientX(event);
    int y = DOM.eventGetClientY(event);
    popupPanel.setPopupPosition(x, y);

    i fixed it by adding scrollTop like this:
    int x = DOM.eventGetClientX(event) + Window.getScrollLeft();
    int y = DOM.eventGetClientY(event) + Window.getScrollTop();

    btw, nice widget, tnx!

  21. Alton says:

    I don’t know if you still respond to this but here it goes.

    I’m doing a drag-and-drop interface, with the ability to right click label. I created an advlabel with the “onBrowserEvents” added to it. However, with this method (even if the method is blank), I can’t drag my objects, because onBrowserEvents is called not just for right clicks, but for left clicks and drags as well. If I remove the method, I can drag my objects, but of course, the right click functionality is removed. Is there any way to make it not call onBrowserEvents for left clicks/drags?

    Thanks.

  22. Nick says:

    Alton:
    I havent really dealt with DnD, but what I do know is that onBrowserEvents is going to get called on all clicks.

    It’s possible that because it’s getting called in your advlabel that it’s not propagating further to your code that handles the DnD. You might want to look at these two lines:
    event.cancelBubble(true);//This will stop the event from being propagated
    event.preventDefault();

    If that doesn’t help, then what you might want to figure out is how to separate the rightclick events from the leftclick events. Maybe just use Event.ONCONTEXTMENU for the rightclick. If you get a rightclick that opens the contextmenu then don’t propagate further. If it’s not a right click contextmenu event then you’ll need to propagate it to your DnD code.

    Not sure if this helps.

  23. Alton says:

    The DnD is a jar file I imported from here:

    http://code.google.com/p/gwt-dnd/

    The gist is that I just have to call the makedraggable(label) method, and a label is supposed to become draggable.

    If the onBrowserEvents is empty, it is still called, and drag and drop still does not work.

    What does sink events do? I tried to look it up but I did not really understand it.

  24. Nick says:

    You can think of sink events to be kind of like those are the events that is it listening for. So if you’re sinking:
    sinkEvents(Event.ONMOUSEUP | Event.ONDBLCLICK | Event.ONCONTEXTMENU);

    Then that UI component is listening for or is the end-point for those events.

    It’s possible that once onBrowserEvents is called in your right-click code, that it’s not getting called (behind the scenes of your draggable componenet) and so it’s not getting the click and so not dragging. This might be a question to ask on the GWT forums. How can I propagate an event to more than one onBrowserEvent() call?

  25. Alton says:

    Wait, since sinkEvents doesnt include ONMOUSDOWN, why does it call it while i’m still holding the mouse button to attempt to drag?

  26. Alton says:

    I got it working.

    In the diagram code, there was a demo, where it show how you can hold down select to perform another function, while still allowing dragging. Here is the code I used.

    Label label1 = new Label(s) {
    public void onBrowserEvent(Event event) {
    if( DOM.eventGetType(event) == Event.ONCLICK
    && DOM.eventGetCtrlKey(event) ){
    Window.alert(“CTRL was clicked.”);
    }
    if (DOM.eventGetButton(event) == Event.BUTTON_RIGHT) {
    GWT.log(“Event.BUTTON_RIGHT”, null);
    onRightClickBroker(this, event);
    }

    super.onBrowserEvent(event);
    }
    };
    label1.sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN);

    Thanks for your help and support.

  27. Nico says:

    You need

    even in the google chrome browser.

    Nico

  28. Nico says:

    I mean this:
    body oncontextmenu=”return false;

  29. Jayend says:

    Nick, i took your concept and did the same using handlers.

    I registered ContextMenuHandler, MouseupHandlers and also DoubleClickHandlers. I get the menu when i right click, but at the same time the browser contextMenu appears. i am not able to disable it. I am using FF 3.0.

    public void onContextMenu(ContextMenuEvent event) {
    event.stopPropagation();
    event.preventDefault();

    }

    @Override
    public void onMouseUp(MouseUpEvent event) {
    // Stop all default activities.
    event.stopPropagation();
    event.preventDefault();

    if (event.getNativeButton() == NativeEvent.BUTTON_RIGHT) {
    int x = event.getClientX();
    int y = event.getClientY();
    pp.setPopupPosition(x, y);
    pp.show();
    }

    }

    @Override
    public void onDoubleClick(DoubleClickEvent event) {
    event.stopPropagation();
    event.preventDefault();
    }

  30. Jayend says:

    Following solved my problem. Not sure whether it is the right way

    http://stackoverflow.com/questions/888917/suppress-control-click-context-menu-gwt-1-6

  31. Nick says:

    Very cool!
    My code is pre-1.6 and I’ve been thinking about redoing with handlers, but just haven’t had the time.

  32. Bipin says:

    It’s nice example. I have implemented this in my project. I have some issue with MenuItem. can i disable perticular menuitem? I have that type of requirement…

  33. Nick says:

    I’m not sure if you can do that right out of the box. If you need it you could probably subclass the MenuItem, add a “disabled” member, and change the style to match if (disabled == true).

  34. Moochy says:

    King Context Sir!

  35. Marc says:

    Nice sample. You mention 2 books about GWT. Which one do you prefer and why?

  36. Nick says:

    Hi Marc,
    Both are pretty close, but if I had to pick one I’d go with GWT in Action. I liked the structure and the order of topics a little better. I don’t think I actually finished GWT in Practice but it’s still on my desk and use it for reference.

    I’ve read a few of the “in Action” books lately and if anyone is looking at the Apache Wicket framework I’d highly recommend Wicket in Action.

    -Nick

  37. Bipin says:

    Hi,

    Thank you for your reply on my question for menu disable. Now i need to implement Focus and UnFocus functionality in this same class. I tried it out but focus and unfocus event is not picking up by onBrowserEvent.

    Can you please suggest, how i can implement this?

    Thank you,
    Bipin

  38. Bipin says:

    Hi Nick,

    I have done that disable thing by just adding action as null, and changing css for that perticular menu item.

    Thank you,
    Bipin Sutariya

Leave a Reply