Saturday, December 27, 2008

The GWT rendering process

The project I'm currently working on uses GWT and GXT and so I decided to dig into the frameworks to figure out how the both perform their magic of turning Java components into HTML elements. Since I've done the work, I thought I may as well share it incase anybody else is curious!

GWT Rendering

In order for GWT to render any component it MUST be added to the RootPanel, which is just a standard Panel component that wraps an actual existing element on the browsers HTML DOM; this can be see from the following hierarchy:

The RootPanel.get(String id) method, which will look up the specified element by id and return a RootPanel that wraps it, OR it will return a DefaultRootPanel which simply wraps the Body element (which is what you get if you call the RootPanel.get() method). The following code snippet from RootPanel.get(String) shows how all this is done:

    // Find the element that this RootPanel will wrap.
    Element elem = null;
    if (id != null) {
      if (null == (elem = DOM.getElementById(id))) {
        return null;
      }
    }

    // SNIP SNIP

    // Create the panel and put it in the map.
    if (elem == null) {
      // 'null' means use document's body element.
      rp = new DefaultRootPanel();
    } else {
      // Otherwise, wrap the existing element.
      rp = new RootPanel(elem);
    }

The process by which elements are added to this RootPanel is now the same for any standard panel, but before we get into that, we must understand two concepts in GWT regarding attachment, i.e the process by which a Widget becomes attached to the HTML DOM.

  • Logical Attachment : A component is said to be Logically Attached if it has been created (or even added to a parent), but has not been added to the physical DOM.
  • Physical Attachment : A component is physically attached when it has been added to the underlying DOM (and thus will be rendered by the browser)

Components are created using Logical Attachment (by, for example, creating and setting up new Panel objects), and then physically attached by adding them to a RootPanel. GXT extends on this idea to allow components to be lazily rendered as we'll see later. For now lets have a look at how this all works using the simple GWT Label widget.

Widget Creation

Since GWT is Java, creating a new Widget is as simple as instantiating it's constructor .i.e.

Label label = new Label("My Label");

And the constructor looks like so :

 public Label() {
    setElement(Document.get().createDivElement());
    setStyleName("gwt-Label");
  }

As we can see from the following code snippet, the label constructor calls into the special GWT DOM object in order to create a DIV element, this will be the actual element that will represent this Widget in the browser. Remember though that at this time the Element has not been physically attached to the underlying DOM, you could create thousands of these Label objects and none would currently appear in the browser.

The constructor calls the setElement method in order to inform the Widget what element it is going to use. This really calls down to the UIObject class which is the base of all, UI Objects in GWT. The element is simply registered for later use :

  protected void setElement(com.google.gwt.user.client.Element elem) {
    assert (element == null) : SETELEMENT_TWICE_ERROR;
    this.element = elem;
  }

Setting up the Widget

At this point in the process we have an empty label which isn't going to look very interesting on an HTML page so now we need to give it some text. This is done via the aptly named setText method. If we have a look at Widget.setText we can see it calls the standard setInnerText the DIV element created earlier :

  public void setText(String text) {
    getElement().setInnerText(text);
  }

Rendering the Widget

We now have a label with some text which we know is actually a DIV element with some inner text, which remember is still only Logically attached; as far as the browser is concerned our Widget doesn't currently exist. As said before, in order to physically attach the element we need to add it to a RootPanel, lets now look at the process our Label goes through when it's added.

  1. RootPanel.add is called to add the Label to the underlying browser DOM
  2. ComplexPanel.add gets called to add the Label as a child of this panel (RootPanels are after all normal panels around a well known DOM element). This logically attaches the Label as a child of the panel, then physically attaches the Label as a child of the panels DOM Element.
  3. ComplexPanel.adopt is called which calls Label.setParent() to inform the label it now has a parent
  4. Label.setParent will call the onAttach method if this parent has been physically attached to the DOM (which it will have been since the panel wraps an existing DOM element)
  5. In the case of the Label there is no onAttach implementation, the DIV element was created when the Label was created and the innerText of the DIV is set as soon as setLabel is called on the Label; therefore in the case of our simple Label example, the rendering process is complete.

The following diagram shows (or at least attempts to show), how this all hangs together:

Click for bigger image

GXT Rendering

GXT or (EXT for GWT), is the widget framework I'm working with on my current project, which changes the rendering process to provide lazy rendering of the components. GXT doesn't provide a Label component, but it does provide an Html component, so lets look at how GXT handles the rendering process.

  1. When the Html component is created, unlike the GWT Label, it DOESN'T create a DOM element, instead it acts just like a simple Java Object
  2. Html.setHtml(String) can be called at any time and, if the component hasn't been attached (rendered in GXT speak), the html is simply stored in a String field.
  3. A GXT Component extends the GWT Widget class and therefore can be added to a standard RootPanel
  4. The rendering process is the same as above, however the GXT Component.onAttach overrides the Widget.onAttach in order to perform the GXT rendering process. It's worth looking at the onAttach method in a little more detail because it solves a problem with lazy rendering of components.
  5. If you look at the previous diagram you'll see that AbsolutePanel.add(Widget) retrieves the DOM element of the component (with getElement()) and passes it to the ComplexPanel.add(Widget, Element) method. Unfortunately in GXT we don't want to create the DOM element until the onAttach method which is lower down the call stack. GXT solves this by adding a dummy DOM element if getElement is called on the widget before it's been rendered. The following code shows the important part of the getElement method:
         if (!rendered) {
          if (dummy == null) dummy = DOM.createDiv();
          return dummy;
        }
    
    The onAttach method then removes this dummy DOM element (which it calls a proxy) and calls the components render method.
  6. The components render method is the work horse of the framework and performs the following :
    • calls beforeRender()
    • Intializes plugins
    • calls createStyles()
    • calls onRender()
    • If events have been added, registers to revieve
    • Adds the base style name
    • calls afterRender()
    • Fires the Events.Render event
    Out of all of these, it's the onRender method that should be overriden by components. A component onRender method MUST call setElement otherwise on returning from the onRender method an exception will be thrown.

From this it can be seen that GXT makes the rendering process a little simpler by only requiring a component to override onRender. What's more there are times when you don't actually know what type of DOM element will represent the component until it's time to actually render it, the default GWT render process does not support this.

As an example, lets compare the GWT HTML widget with the GXT Html (camel case!) component. The GWT version needs to know the DOM component type up front, simply because setElement MUST be called during widget creation, which means the GWT HTML component is ALWAYS a DIV with the HTML as the innerHtml. GXT however allows components to defer the decision on the underlying DOM Element until render time and so the GXT Html component allows the actual parent tag type to be set at any time (before rendering) using the setTagName() method. At render time the correct component type is created and rendered.

5 comments:

scwong said...

nice n useful for those gwt newbie like me. Thanks for sharing ! =)

Rangio B. said...

Thanks for sharing! It's very useful and well explained.

I have a question for you.

What if i need to re-render physically my object?

That is, to change the DOM elements of an already rendered RootPanel.
It can be done 'logically' by the various methods add(), remove(), but this is not reflected correctly 'physically'.

Thanks for the help

Unknown said...

@alessaless Make sure you call the layout() method on any LayoutContainer after elements have been added or removed. Cheers!

Anonymous said...

Can I force the rendering process? I have a TabPanel with multiple TabItems and I’m looking for a method to attach/render all of the TabItems right after construction. GXT renders/attaches only the selected TabItem till the user clicks on another tab. Did you find a mechanism to achieve this?

David Lucky said...

thanks for sharing! I'm currently working on GWT 2.8.2 and I wonder how to improve rendering speed, maybe integrate with React and calculate diff in shadow dom