Hooking a GWT event onto an element in an external iframe

16,212

Solution 1

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!

Solution 2

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);

Solution 3

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.

Solution 4

Instead of using iframes i suggest you simply make a http request from GWT via com.google.gwt.http.client.RequestBuilder. Like so:

private void getHtml(String url) {
        RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);

        rb.setCallback(new RequestCallback() {

            @Override
            public void onResponseReceived(Request request, Response response) {
                HTMLPanel html = new HTMLPanel(response.getText());

                // Now you have a widget with the requested page
                // thus you may do whatever you want with it.
            }

            @Override
            public void onError(Request request, Throwable exception) {
                Log.error("error " + exception);
            }
        });

        try {
            rb.send();
        } catch (RequestException e) {
          Log.error("error " + e);
        }
    }

Solution 5

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

Share:
16,212
triggerNZ
Author by

triggerNZ

Software developer. Consultant. Polyglot. Functional fundamentalist. Mad scientist. Interested in elegant code, functional programming, scalability, machine learning, tech business and everything else.

Updated on June 05, 2022

Comments

  • triggerNZ
    triggerNZ almost 2 years

    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

  • triggerNZ
    triggerNZ almost 15 years
    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
    triggerNZ over 14 years
    Thank you! I knew there had to be a cleaner way :)
  • SorcyCat
    SorcyCat almost 13 years
    Thank you, thank you! This was exactly what I needed to get it to work in IE7!