Присоединение события GWT к элементу во внешнем iframe

Я пишу приложение GWT, которое предполагает взаимодействие с внешним документом в iframe. В качестве доказательства концепции я пытаюсь привязать обработчик кликов к кнопке.

Следующие работы в 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!");
};

Пытаясь сделать эквивалент в GWT, я сделал следующее:

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;
}-*/;

Ниже приведен класс ElementWrapper:

public class ElementWrapper extends Widget implements HasClickHandlers {

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

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


}

Код для поиска кнопки работает нормально, но обработчик события клика не получает вызова. Раньше у кого-то была аналогичная проблема, и как вы ее разрешили?

Спасибо заранее,

Олово

Ответ 1

Хильбранд прав насчет того, что метод GWT onAttach() не был вызван.

Я внедрил ваше оригинальное решение, добавив следующий метод в ElementWrapper:

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

И вызвал добавленный wrapper.onAttach() после создания ElementWrapper. Работает как шарм!

Ответ 2

Я ожидаю, что проблема заключается в том, что метод GWT onAttach() не вызывается, когда вы используете упаковку, как в первом примере. Вы можете попробовать использовать статический метод wrap для виджета Button. Хотя для использования этого параметра input должен иметь тип button. Или посмотрите на реализацию метода wrap. Вот модифицированный код при использовании метода wrap:

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

Ответ 3

После изучения этого далее я обнаружил, что iframe не имеет значения. Такое же поведение не работает на обычной кнопке на главной странице.

Я в основном исправил это, используя JSNI для репликации части механизма обработки событий GWT. Следующие работы:

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

Где CustomElementWrapper:

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

Наконец, ClickEventManager, где происходит фактическая работа:

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 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()();
        }
}-*/;
}

Лично я ненавижу это решение, потому что я, кажется, дублирую обработку событий GWT и, вполне возможно, вводя неприятные утечки памяти в JavaScript. Любые идеи о том, почему мой первый пост не работает (помня, что аспект iframe является красной селедкой), будет оценено.

Спасибо,

Олово

Ответ 4

Вы можете счесть это полезным:

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

Затем вы сможете создавать произвольные контейнеры в контейнерах виджетов:

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

И привяжите события к произвольным элементам:

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

В частности, попробуйте следующее:

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

Меня всегда озадачило, что GWT делает это настолько трудным и плохо документированным.

Ответ 5

Вместо использования iframe я предлагаю вам просто сделать HTTP-запрос от GWT через com.google.gwt.http.client.RequestBuilder. Например:

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

Ответ 6

Вы можете использовать JSNI для повторного использования кода JavaScript. Ваш javascript-код вызовет метод gwt для объекта, который будет отбрасывать его от имени кнопки в iframe.

Что касается того, почему код GWT не работает, я думаю, это потому, что они используют некоторый слой поверх обычных событий браузера, которые, вероятно, не могут охватывать более одного кадра. Впрочем, это только предположение. Вы можете указать это как запрос функции/ошибки снова в команде GWT. Если я прав, ваш код выглядит просто.

Ответ 7

См. мой предыдущий ответ. Небольшая модификация вашего оригинального решения заставит его работать.