views:

4005

answers:

6

Hi everyone,

I am writing a GWT app that involves interacting with an external document in an iframe. As a proof of concept, I am trying to attach a click handler to a button.

The following works in javascript

var iframe = document.getElementById("rawJSIFrame");
var doc = iframe.contentDocument;
var body = doc.body;
var button = doc.getElementsByTagName("input").namedItem("submit");
button.onclick = function() {
    alert("Clicked!");
};

Trying to do the equivalent in GWT, I did the following:

public void addClickHandlerToSubmitButton(String buttonElementName, ClickHandler clickHandler) {
    IFrameElement iframe = IFrameElement.as(frame.getElement());
    Document frameDocument = getIFrameDocument(iframe);
    if (frameDocument != null) {
     Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
     ElementWrapper wrapper = new ElementWrapper(buttonElement);
     HandlerRegistration handlerRegistration = wrapper.addClickHandler(clickHandler);
    }
}

private native Document getIFrameDocument(IFrameElement iframe)/*-{
        return iframe.contentDocument;
}-*/;

The following is the ElementWrapper class:

public class ElementWrapper extends Widget implements HasClickHandlers {

    public ElementWrapper(Element theElement) {
     setElement(theElement);
    }

    public HandlerRegistration addClickHandler(ClickHandler handler) {
     return addDomHandler(handler, ClickEvent.getType());
    }


}

The code to find the button works fine but the actual click event handler is not getting invoked. Has anybody had a similar issue before, and how did you resolve it?

Thanks in advance,

Tin

A: 

You could use JSNI to reuse your JavaScript piece of code. Your javascript code would call a gwt method on an object that would throw it on behalf of the button in the iframe.

As to why GWT code does not work -- I guess that is because they use some layer on top of regular browser events that probably cannot span more than 1 frame. That's just a guess though. You could file this as a feature/bug request agains GWT team. If I am right your code looks just fine.

Marcin
Cool, thanks for the reply. I guess I'll explore the JSNI path, though I want to stay away from writing custom javascript as much as possible.
triggerNZ
A: 

After researching this further, I found that the iframe is irrelevant. The same behaviour doesn't work on a normal button on the host page.

I basically fixed it by using JSNI to replicate part of GWT's event handling mechanism. The following works:

Element buttonElement = DOM.getElementById("externalButton");
new CustomElementWrapper(buttonElement).addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
     Window.alert("GWT hooked into button");
    }
});

Where CustomElementWrapper is:

public class CustomElementWrapper extends Widget implements HasClickHandlers {
    private ClickEventManager clickEventManager;

    public CustomElementWrapper(Element theElement) {
     setElement(theElement);
     clickEventManager = new ClickEventManager(theElement);
    }

    public HandlerRegistration addClickHandler(ClickHandler handler) {
     //The 'right' way of doing this would be the code below. However, this doesn't work
     // A bug in GWT?
     //  
     //    return addDomHandler(handler, ClickEvent.getType());
     return clickEventManager.registerClickHandler(handler);
    }


    void invokeClickHandler() {
     clickEventManager.invokeClickHandler();
    }

    public boolean isClickHandlerRegistered() {
     return clickEventManager.isClickHandlerRegistered();
    }
}

Finally, the ClickEventManager, where the actual work happens is:

public class ClickEventManager {
private boolean clickHandlerRegistered = false;
private ClickHandler clickHandler;
private Element element;

public ClickEventManager(Element element) {
 this.element = element;
}

public void invokeClickHandler() {
 //This shouldn't really be null but we are bypassing GWT's native event mechanism
 //so we can't create an event
 clickHandler.onClick(null);
}

public boolean isClickHandlerRegistered() {
 return clickHandlerRegistered;
}

HandlerRegistration registerClickHandler(ClickHandler handler) {
 clickHandler = handler;

 if (!clickHandlerRegistered) {
  registerClickHandlerInJS(element);
  clickHandlerRegistered = true;
 }
 return new HandlerRegistration() {
  public void removeHandler() {
   //For now, we don't support the removal of handlers
   throw new UnsupportedOperationException();
  }
 };
}
private native void registerClickHandlerInJS(Element element)/*-{
 element.__clickManager = this;
 element.onclick 
  = function() {
   var cm = this.__clickManager; 
   [email protected]::invokeClickHandler()();
  }
}-*/;
}

Personally, I hate this solution because I appear to be duplicating GWT's event handling and quite possibly introducing nasty javascript memory leaks. Any ideas on why my first post doesn't work (remembering that the iframe aspect is a red herring), would be appreciated.

Thanks,

Tin

triggerNZ
A: 

I expect the problem is that the GWT method onAttach() is not called when you use the wrapping as in your first example. You can try to use the static wrap method on the Button widget. Although to use this the input must be of type button. Or have a look at the implementation of the wrap method. Here is the modified code when using the wrap method:

Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
Button button = Button.wrap(buttonElement);
HandlerRegistration handlerRegistration = button.addClickHandler(clickHandler);
Hilbrand
+4  A: 

Hilbrand is right about the problem being that the GWT method onAttach() was not called.

I implemented your original solution, adding the following method to ElementWrapper:

  public void onAttach() {
    super.onAttach();
  }

And called added wrapper.onAttach() after the ElementWrapper is created. Works like a charm!

Tina
Thank you! I knew there had to be a cleaner way :)
triggerNZ
A: 

Please see my previous answer. A slight modification to your original solution will make it work.

Tina
A: 

You may find this helpful:

import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.AbsolutePanel;

public class DirectPanel extends AbsolutePanel implements HasClickHandlers {
        public DirectPanel(Element elem) {
                super(elem.<com.google.gwt.user.client.Element> cast());
                onAttach();
        }

        @Override
        public HandlerRegistration addClickHandler(ClickHandler handler) {
                return addDomHandler(handler, ClickEvent.getType());
        }
}

You will then be able to make arbitrary containers into widget containers:

  Element root = Document.get().getElementById("target");
  DirectPanel p = new DirectPanel(root);
  Button register = new Button("Register");
  register.addClickHandler(new ClickHandler() {
    @Override
    public void onClick(ClickEvent event) {
      // ...
    }
  });
  p.add(register);

And bind events to arbitrary elements:

  Element root = Document.get().getElementById("target");
  DirectPanel p = new DirectPanel(root);
  p.addClickHandler(new ClickHandler() {
    @Override
    public void onClick(ClickEvent event) {
      // ...
    }
  });

Specifically in your case, try this:

IFrameElement frm = Document.get().createIFrameElement();
Document d = frm.getContentDocument();
NodeList<Element> inputs = d.getElementsByTagName("input");
InputElement target = null;
for(int i = 0; i < inputs.getLength(); ++i) {
  Element e = inputs.getItem(0);
  if (e.getNodeName().equals("submit")) {
    target = InputElement.as(e);
    break;
  }
}
if (target != null) {
  DirectPanel p = new DirectPanel(target);
  p.addClickHandler(new ClickHandler() {
    @Override
    public void onClick(ClickEvent event) {
      // TODO Auto-generated method stub
    }
  });
}

It's always mystified me that GWT makes doing this so difficult and poorly documented.

Doug