Dynamically generated HTML and GWT

1,930 views
Skip to first unread message

Mike Dee

unread,
May 30, 2013, 12:58:40 AM5/30/13
to google-we...@googlegroups.com
Hi,

I'm hoping this can be done with relative ease in GWT.  I need to integrate a couple of features that involve HTML that is generated on the fly. Basically, the app gets some XML and sends it to a service which returns HTML (via an XSL transform).

There are two cases. 

1) The first is easy and it is working with GWT, but I have a question.  We just create an HtmlPanel and put the HTML in it.  But does the HTML need to be complete - should it have HTML, HEAD, and BODY tags or can it just be a blob of HTML?  Right now it does not have the HTML header and it works (the blob is wrapped in a DIV).  Just want to know what is "correct".

2) The second case is a little more tricky. It is similar to the first, but the HTML is a form.  So, it needs to display (which it does).  But how do I handle the submit button?

Thanks,
Mike

David

unread,
May 30, 2013, 3:06:53 AM5/30/13
to google-we...@googlegroups.com
Hi,

The HTMLPanel accepts HTML snippets, you should not put and entire HTML page in it since it is part of the window document.
So what you are doing is correct. Otherwise you will have to play with Frames (which you should avoid due to memory leak issues).

For the second question: the HTMLPanel has some methods that allow you to wrap a widget on existing DOM elements. So in your case, you could wrap the submit button. You just need to find the correct element in the generated DOM tree (using an ID would be the easiest).

To find the element by id you can use: 

To "wrap" the element you use the wrap method on SubmitButton:

One final note: You should use a deferred call before trying to find the submit button element because the setInnerHTML does not take effect immediately.

David

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-tool...@googlegroups.com.
To post to this group, send email to google-we...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Mike Dee

unread,
May 30, 2013, 11:43:57 AM5/30/13
to google-we...@googlegroups.com
Thanks for pointing me in the right direction.

One last question.  How is the form submission handled?  Is a servlet needed?  In this case, the form will simply save any changes (to a db) and redisplay itself.

Thanks,
Mike

David

unread,
May 31, 2013, 3:05:12 AM5/31/13
to google-we...@googlegroups.com
Form submission works like any form, so you will need a servlet to handle the post.
You will have to redirect the post to an internal IFrame otherwise your document will be replaced. This is what the GWT Form widget is doing.

David


Thanks,
Mike

--

Mike Dee

unread,
May 31, 2013, 5:05:20 PM5/31/13
to google-we...@googlegroups.com
Now, this has gone from fun to just downright frustrating.  I'm stuck at the point of finding and wrapping the submit button.  I get the ubiquitous, "A widget that has an existing parent widget may not be added to the detach list".  Looked through the dozens of posts regarding that message.  The most common response is "don't use wrap() the way are using it".  There's also, "that's not the use case for wrap()".  Maybe one of these is true in my case.

Code is fairly simple.

uibinder:
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui">
<g:ScrollPanel width="100%" height="100%" ui:field="mainScrollPanel"/>
</ui:UiBinder> 

The view class's onLoad() (this is an Activites and Places app that uses MVP).  So this view is on the right side of a splitterpanel.  The left side has a stackpanel (think of the mail example).  When the appropriate stack panel item is selected, this view appears on the right of the splitter.

public class ReviewView extends ResizeComposite
{
  ...

  @UiField ScrollPanel mainScrollPanel; 

private String html;  // Set in a separate method and called before onLoad().  This HTML comes from an external source.

  ...
public void onLoad()
{
mainScrollPanel.clear();
HTMLPanel htmlPanel = new HTMLPanel( html );
mainScrollPanel.add( htmlPanel );
Element submitButtonElement = htmlPanel.getElementById( "save" );    // This appears to work, without being deferred.  I tried it defered also.
Button submitButton = Button.wrap( submitButtonElement );                 // This is where the assert exception occurs.
}
}

Of course the HTMLPanel has a parent widget, the ReviewView. And it is nested inside of a ScrollPanel, which is nested in a SplitterLayoutPanel, which is inside a RootLayoutPanel.  There are probably widgets attached to all of those.  So, can this be done? One solution in the posts is to wrap() from the inside out.  With an A&P app this seems nearly impossible as Widgets are always embedded in Widgets.  What is the point of wrap() if it is so finicky()?  

I also tried deferred method, replacing HTMLPanel with MyHtmlPanel:

onLoad() looks like this:

MyHtmlPanel htmlPanel = new MyHtmlPanel( html );
mainScrollPanel.add( htmlPanel );

And MyHtmlPanel looks like this:

private class MyHtmlPanel extends HTMLPanel
{
public MyHtmlPanel( String html )
{
super( html );
}
protected void onAttach()
{
Element submitButtonElement = this.getElementById( "save" );    // Appears to work.
Button submitButton = Button.wrap( submitButtonElement );        // Same error occurs here.
}
protected void onDetach()
{
}
}




David

unread,
Jun 3, 2013, 3:39:08 AM6/3/13
to google-we...@googlegroups.com
Hi,
 
OK, I forgot about that part - I once had to go through these issues as well but forgot soon after.
 
You need to subclass HTMLPanel and SubmitButton to get what you want. The HTMLPanel only supports adding widgets to existing HTML elements, not just make an Element into a widget without detaching.
 
First you need to subclass the SubmitButton so that you gain access to the SubmitButton(Element) protected constructor. Use that constructor to create the SubmitButton instance.
 
You also need to subclass the HTMLPanel class, you need to add a method that contains something like this:

public attachWidget( Widget widget ) {
  // Logical attach.
  getChildren().add(widget);
  adopt(widget);
}

This method does not enforce any checks, so make sure that the widget is a wrapped one of an Element contained in the HTMLPanel.
 
Frankly I don't understand why the HTMLPanel and the Wrapping stuff does not allow you to do this. It is way more useful because you can bulk render and entire GUI and then later attach the widgets.
 
David
 

Thomas Broyer

unread,
Jun 3, 2013, 3:56:10 AM6/3/13
to google-we...@googlegroups.com


On Monday, June 3, 2013 9:39:08 AM UTC+2, stuckagain wrote:
Hi,
 
OK, I forgot about that part - I once had to go through these issues as well but forgot soon after.
 
You need to subclass HTMLPanel and SubmitButton to get what you want. The HTMLPanel only supports adding widgets to existing HTML elements, not just make an Element into a widget without detaching.
 
First you need to subclass the SubmitButton so that you gain access to the SubmitButton(Element) protected constructor. Use that constructor to create the SubmitButton instance.
 
You also need to subclass the HTMLPanel class, you need to add a method that contains something like this:

public attachWidget( Widget widget ) {
  // Logical attach.
  getChildren().add(widget);
  adopt(widget);
}

This method does not enforce any checks, so make sure that the widget is a wrapped one of an Element contained in the HTMLPanel.
 
Frankly I don't understand why the HTMLPanel and the Wrapping stuff does not allow you to do this. It is way more useful because you can bulk render and entire GUI and then later attach the widgets.

I believe this is what Renderable is supposed to add; but it's too easy to shoot yourself in the foot with this 2-step process. Should there be wrap(HTMLPanel, Element) overloads to "wrappable" widgets?
Feel free to propose a patch.

Jens

unread,
Jun 3, 2013, 4:11:30 AM6/3/13
to google-we...@googlegroups.com
I think you kind of misuse the *.wrap() method. They are meant to be used with static html pages that you want to enhance with GWT.

I think what you really want for now is to extend Widget and overwriting onBrowserEvent(), e.g.

MyServerFormWidget extends Widget {
  
  MyServerFormWidget() {
    setElement(Document.get().createDivElement());
    sinkEvents(Event.ONCLICK);
  }

  public void setServerGeneratedHtml(String html) {
    getElement().setInnerHTML(html);
  }

  @Override
  public void onBrowserEvent(Event event) {
    super.onBrowserEvent(event);
    int eventType = DOM.eventGetType(event);
    if(eventType == Event.ONCLICK) {
       //check event target element, if its the submit button
       if(formSubmitButtonHasBeenClicked(event)) {
          event.preventDefault();
          onSubmitClickIntercepted();
       }     
    }
  }

  private void onSubmitClickIntercepted() {
    //do your stuff, e.g. read form data and fire higher level events like fireEvent(new SubmitEvent(formData)); so you could use GWT-RPC/RequestFactory/RequestBuilder to send the form data to the server. That way you don't have to write Servlets directly.
  }

}


-- J.

David

unread,
Jun 3, 2013, 8:52:30 AM6/3/13
to google-we...@googlegroups.com
In my case I have no choice.
 
If you just need it for one widget I agree it is maybe over-the-top. But I want the rich behavior of custom widgets, without the cost of re-implementing all the workarounds and tricks in my own onBrowserEvent then wrapping is the way to go since all you do this way is to add the required listeners.
 
I am bulk rendering the HTML of an editor that is driven by the XSD schema of financial messages. There is such a huge amount of widgets needed that it takes minutes to get it on the screen using the "supported" way. By using the bulk render it takes a second or so for a few hundred of widgets which a lot of nice dynamic behavior.
 
By using this trick, I can make that static html into standards widgets without the huge cost. I can even delay converting to widgets based on mouse movements over the HTML.
 
Note that there might be a better solution nowadays, I started doing this a long time ago before Renderables or whatever features that were added later. GWT documentation did not really receive as much focus as the new code additions in the last years.
 
Maybe it is time that the section on making GWT GUIs more efficient a bit more detailed.
I found that there are many things missing (Elemental ? where is it described for example).
Some applications in the enterprise environment have a lot fields to enter, not everything can be simplified by a simple search box as Google is used to ;-)
 
David

 

--

Mike Dee

unread,
Jun 3, 2013, 10:23:03 AM6/3/13
to google-we...@googlegroups.com
Is my case considered static HTML?  The HTML is not known at run time.  It is received once the app is up and running.  It is not altered at all. The intent is to display it.  The HTML is generally always contains a form and there is an option to allow the user to alter some of the form fields and save the form.

From my experimentation with wrap() I can see very few circumstances in which case it is actually useful.  It appears to be useful where there is only one page.  The way nesting of widgets is handled would make it tricky in anything more complicated.

Mike Dee

unread,
Jun 3, 2013, 10:27:44 AM6/3/13
to google-we...@googlegroups.com
So, what is recommended - or what is good practice for a case where one has a page of HTML and needs to do something relatively simply?  Think of a form (HTML generated externally) and the form needs to be displayed in a GWT Composite-derived class.  The only thing desired is to allow the form fields to be altered and saved.

One additional question.  Would it be possible to do this with some simply Javascript in the external HTML.  JS is largely to validate fields.

Also, was exploring this further over the weekend.  Would the addAndReplace() method be useful?  Haven't tried it yet.

Mike



Mike Dee

unread,
Jun 3, 2013, 10:38:05 AM6/3/13
to google-we...@googlegroups.com
I guess what I would do for a fallback option is to simply show the form in the GWT app.  If the user wants to alter any form fields, they could click a link and the form could be showed in a JSP page (new window).   From a JSP we could get everything we need. But it would be nice to have this work right within the app.

This is how we handle printing.  Haven't found a good way to print with GWT - we mostly print reports (generated on the fly as HTML, generated externally).  A link pops up a (printer friendly) version of the page (without the GWT UI of the app) done in JSP.  Works well.

The reports are static and we can show them within the GWT app easily.  There is no user feedback.  However, on the form pages, the user can interact with the form (changing form fields and saving the form).  So, it is a little trickier than the static reports we display.

Mike



Jens

unread,
Jun 3, 2013, 11:26:44 AM6/3/13
to google-we...@googlegroups.com

From my experimentation with wrap() I can see very few circumstances in which case it is actually useful.  It appears to be useful where there is only one page.  The way nesting of widgets is handled would make it tricky in anything more complicated.

Yes, imagine a login application. Its UI is pretty easy and you could design it right in your host html page, so no RootPanel, no LayoutPanels, etc. If you want to do any useful with it you would get a handle to these DOM elements and now you can decide if you want to work with the low level element API or if you want the higher level Widget API by using any existing *.wrap() method to attach GWT widgets to these elements. These widgets created by *.wrap() do not have any parent widgets and will cleanup themselves once you leave the login page. So IMHO its indeed for simple use cases.

There are probably a good reasons why you can not wrap elements that are already managed by a widget. E.g. if you remove the widget, what would happen to the wrapped widget then?

-- J.

David

unread,
Jun 4, 2013, 3:31:57 AM6/4/13
to google-we...@googlegroups.com
Jens,

They way I use the widgets, maybe not supported, work perfectly. It is made part of the widget hierarchy. And when I remove the widget, everything is again cleaned up. I did a lot of memory leak investigating and it is just working the way it should. Even if the window close event is triggered, they get properly cleaned up just like the rest of the widget hierarchy.

The wrap methods on Widget assume and do too much and that makes them not usable beyond the scope of wrapping non nested widgets.

David

Jens

unread,
Jun 4, 2013, 4:23:37 AM6/4/13
to google-we...@googlegroups.com

They way I use the widgets, maybe not supported, work perfectly. It is made part of the widget hierarchy. And when I remove the widget, everything is again cleaned up. I did a lot of memory leak investigating and it is just working the way it should. Even if the window close event is triggered, they get properly cleaned up just like the rest of the widget hierarchy.

The wrap methods on Widget assume and do too much and that makes them not usable beyond the scope of wrapping non nested widgets.

Interesting, then they maybe decide to implement it the safe way so they don't have to think about that case when they want to change Widget internals.

-- J.

David

unread,
Jun 4, 2013, 6:55:40 AM6/4/13
to google-we...@googlegroups.com
Hi,

I'm using protected members, nothing private. So any change to those methods in the Widget API will break compatibility for a lot of projects. But regardless, if the widget API is ever changed or something better is created that would allow us to use Renderers and do it in a supported way then I would be glad to migrate my code.

I am not the only one who has moved to this direction. I am currently working on a second project that uses about the same approach, developed before I ever talked to the developers in that team/company.

In our case the root cause is that the GWT is always focusing on build time optimisations. I had big hopes for UiBinder - and I use it in some cases - but it turns out that this only targets build time. We are uploading schema's so we need to generate big UI's on the fly. Otherwise I would have created a generator that would spit out UiBinder compatible classes/files. 

But with pure usage of the widgets it just does not scale. Sure if we wait a few more years then everyone will be using IE10 or newer with GPU acceleration and god knows what else, and maybe we won't care that much anymore, but do not forget that mobile phones consume a lot of battery if you do not optimize.

GWT started doing similar bulk rendering with the Cell widgets, but there it is a much more narrow context. In tables you can get away with the rubber stamp approach and collect all events centrally. But with forms that becomes a bit more complicated, since I really need exactly the same behaviour as widgets already support, so why not share the code ?

David


-- J.

--

Mike Dee

unread,
Jun 4, 2013, 8:40:49 PM6/4/13
to google-we...@googlegroups.com
This looks more difficult than I first thought.  I think I'd have to somehow also attach to the form element.  I would need to set the action to go to a servlet.  I don't see a way to take some HTML and get the FORM element.

I may try a completely different way to do this.  I'll create a servlet to get the form, display it, and handle the form submission.  The entire thing will be put in an iframe (GWT frame).

I think this may also be the only way to get some of our forms working that have Javascript in them.  It looks like the Frame class treats the document as completely independent from the app (and the RootPanel).
Reply all
Reply to author
Forward
0 new messages