Blog

Creating a Custom Ext GWT Component

November 23, 2009 | Darrell Meyer

Creating custom Web components can prove challenging without a good foundation. Fortunately, the Ext GWT framework contains a Component API that provides the ability to quickly and easily create custom components while using Java. This post will walk you through the steps needed to create our ContentScroller - an Ext GWT UX component.

The ContentScroller

ContentScroller

The ContentScroller component will allow any content to be vertically scrolled by placing a clickable navigation bar above and below the content. When the navigation bars are clicked the content is scrolled either up or down. The finished example can be viewed here.

Our UX User Interface

Our user extension will display two navigation bars. These bars must be styled which includes the HTML markup, CSS, and images.

Images

There are two images used for the component: the bar background, and the up and down arrows. All the arrow icons are in a single file. CSS offsets are used to display the appropriate part of the image. As an alternative to this method, the GWT ImageBundle could have been used rather than creating the CSS and offsets manually. Here are the two images used by the component: bar and arrows.

HTML Markup

For the HTML markup, a HTML TABLE element is used for each bar. The table has a single row with 3 cells: left, center, and right. The left and right will show the rounded ends of the bar, while the center region will show the center section of the bar. The width of the left and right cells are fixed, with the center width being dynamic based on the available width.
 
    <div unselectable="on" class=" x-cscroller-bar x-unselectable " id="x-auto-0">
      <table cellpadding="0" cellspacing="0" width="100%">
        <tbody>
          <tr>
            <td class="x-cscroller-left"></td>
            <td class="x-cscroller-center">
              <div>&nbsp;
              </div></td>
            <td class="x-cscroller-right"></td>
          </tr>
        </tbody>
      </table>
    </div>
 
 

Given the images and HTML markup, the following CSS styles are used.

 
.x-cscroller-left {
  width: 20px;
  height: 22px;
  background: url(bar.gif);
}
.x-cscroller-right {
  width: 20px;
  background: url(bar.gif) top right;
}
.x-cscroller-center {
  background: url(bar.gif) top center;
}
.x-cscroller-center div {
  background: url(arrows.gif) no-repeat center -9px;
  height: 8px;
  overflow: hidden;
}
.x-cscroller-top-disabled .x-cscroller-center div {
  background-position: center 0px;
}
.x-cscroller-bottom .x-cscroller-center div {
  background-position: center -16px;
}
.x-cscroller-bottom-disabled .x-cscroller-center div {
  background-position: center -24px;
}
.x-cscroller .container {
  overflow: hidden;
  position: relative;
  margin: 6 0px;
}
.x-cscroller .scroller {
  position: relative;
}
 

Create The Custom Component

Extending Component

The first decision for our custom component is to decide which base class to extend. The two primary choices are Component and BoxComponent. BoxComponent should be used when the custom component can be “sized” which is the case for our custom component.

The component will essentially have three components: the top bar, the content area, and the bottom bar.

NavigationBar

For the navigation bars a custom component is defined as an inner class of ContentScroller. This will encapsulate the rendering and logic of each of the navigation bars. Since the bars do not need to be sized they can extend Component, rather than BoxComponent. Ext GWT renders its components “lazily”. That is, the DOM elements are not created when the component is initially constructed. Rather, the DOM elements are created right before a component is added to the DOM. This is different than GWT widgets which create the DOM elements at construction time. Lazy rendering ensures that DOM elements are not created until they are actually needed.

The first step when creating a custom component is to override the onRender method and supply the components root DOM element. For this component, the component is represented as a HTML DIV element.

Next, we need to construct the HTML table structure for the bar. Rather than manually creating the elements using the DOM API, the HTML structure is created using a StringBuffer which is then used to create the elements. This approach is much faster than doing manual DOM operations.

We are going to need to know when the bars are clicked so we must set this up. We call sinkEvents to specify what events the component is interested in being notified about.

After sinking the events, we need to override onComponentEvent to add our code that needs to be executed when the clicks occur. When working with Ext GWT, we do not override onBrowserEvent as done with straight GWT development.

We are also using a ClickRepeater. This utility class allows an action to be repeated as the user holds the mouse down over the bars. This allows the content to be scrolled quickly with a single mouse click.

 
    @Override
    protected void onRender(Element target, int index) {
      setElement(DOM.createDiv(), target, index);
 
      StringBuffer sb = new StringBuffer();
      sb.append("<table width='100%' cellpadding='0' cellspacing='0'><tr>");
      sb.append("<td class='x-cscroller-left'></td>");
      sb.append("<td class='x-cscroller-center'><div>&nbsp;</div></td>");
      sb.append("<td class='x-cscroller-right'></td>");
      sb.append("</tr></table>");
 
      el().setInnerHtml(sb.toString());
 
      ClickRepeater repeater = new ClickRepeater(el());
      repeater.addListener(Events.OnClick, new Listener<BaseEvent>() {
        public void handleEvent(BaseEvent be) {
          if (isTop) {
            scrollUp();
          } else {
            scrollDown();
          }
        }
      });
      addAttachable(repeater);
 
      disableTextSelection(true);
 
      sinkEvents(Event.ONCLICK);
    }
  }
 

ContentScroller

Now that we have the code for the navigation bars we can look at the onRender method of ContentScroller. You will see that we create two instances of the new bar component. In addition, we set up the elements needed to contain and scroll the content. This is accomplished with two DIV elements. The first will be sized and have its CSS overflow property set to "hidden". The nested DIV will be the “scroller” element whose CSS top property is adjusted to move the content up and down.

 
 @Override
  protected void onRender(Element target, int index) {
    setElement(DOM.createDiv(), target, index);
 
    top = new Bar(true);
    top.addStyleName("x-cscroller-top-disabled");
 
    bottom = new Bar(false);
    bottom.addStyleName("x-cscroller-bottom");
 
    top.render(getElement());
 
    getElement().appendChild(XDOM.create("<div class='container'><div class='scroller'></div></div>"));
 
    container = el().selectNode(".container");
    scroller = el().selectNode(".scroller");
 
    if (content.isRendered()) {
      scroller.appendChild(content.getElement());
    } else {
      content.render(scroller.dom);
    }
 
    // fixes firefox issue with bleeding content
    content.el().setStyleAttribute("overflow", "hidden");
 
    bottom.render(getElement());
 
    // make div focusable for keyboard events
    el().setTabIndex(0);
    // hide the focus outline
    el().setElementAttribute("hideFocus", "true");
 
    new KeyNav<ComponentEvent>(this) {
      @Override
      public void onUp(ComponentEvent ce) {
        scrollUp();
      }
      @Override
      public void onDown(ComponentEvent ce) {
        scrollDown();
      }
    };
  }
 

We override the onResize method so that we can make any size adjustments needed when the component is resized. All we need to do is set the height of our container element to be the current height of the component minus the heights of the two bars. The bar’s heights are fixed, so we do not need to retrieve the bar height dynamically.

 
  @Override
  protected void onResize(int width, int height) {
    super.onResize(width, height);
    container.setHeight(height - 44);
  }
 

Attach / Detach

Since we are not using a Container or Panel for the component, we are responsible for attaching and detaching our child components. So we override onAttach and onDetach and manually attach and detach all three child components. Without this code, our child components would not receive any events.

 
  @Override
  protected void onAttach() {
    super.onAttach();
    ComponentHelper.doAttach(top);
    ComponentHelper.doAttach(bottom);
    ComponentHelper.doAttach(content);
  }
 
  @Override
  protected void onDetach() {
    super.onDetach();
    ComponentHelper.doDetach(top);
    ComponentHelper.doDetach(bottom);
    ComponentHelper.doDetach(content);
  }
 

Scrolling

The last bit of code is the code that executes when the bars are clicked. To move the content up and down, we adjust the CSS top property of the scroller element. In addition, we change the bar’s active state based on the current scroll position. By adding and removing CSS styles to the bar, the arrow colors are changed.

 
    private void scrollDown() {
      int contentHeight = content.el().getHeight() - container.getHeight();
      int top = scroller.getTop();
      top = -(Math.abs(top) + scrollDistance);
      top = Math.max(-contentHeight, top);
      scroller.setTop(top);
      update(top);
    }
 
    private void scrollUp() {
      int top = scroller.getTop();
      top = -(Math.abs(top) - scrollDistance);
      top = Math.min(0, top);
      scroller.setTop(top);
      update(top);
    }
 
    private void update(int scrollTop) {
      top.el().setStyleName("x-cscroller-top-disabled", scrollTop == 0);
      int contentHeight = content.el().getHeight() - container.getHeight();
      bottom.el().setStyleName("x-cscroller-bottom-disabled", scrollTop == - contentHeight);
    }
 

Keyboard Support

In addition to the navigation bar, the content can be scrolled via the up and down arrow keys. We first make the component "focusable" by giving the root element a tab index. This is needed to allow the component to receive the keyboard events. Next, we use the KeyNav utility class that makes it trivial to add support for key events within your component.

Summary

This tutorial demonstrated how a custom component can be designed and implemented with Ext GWT. I hope this tutorial inspires you to create some cool components using Ext GWT.

The source code for the example is available for download.

Written by Darrell Meyer
Darrell Meyer leads the Ext GWT product team at Sencha. Before joining the company, Darrell was the creator of the popular open source MyGWT Widget Library for Google Web Toolkit (GWT). Darrell brings his expert Java and GWT knowledge to Sencha. With 10+ year’s experience building enterprise web applications, Darrell is equally well versed as both a software architect and user interface expert.
Follow Darrell on Twitter

Share this post:
Leave a reply

There are 21 responses. Add yours.

Tweets that mention Ext JS - Blog -- Topsy.com

2 years ago

[...] This post was mentioned on Twitter by extjs, Open4G Media. Open4G Media said: Creating a Custom Ext GWT Component - http://bit.ly/5rXF3M [...]

Michael

2 years ago

Outstanding write up.  Can be easily ported to regular JS components.  Thanks for the overview.  You guys rock.

Kirt

2 years ago

Great work Darrell.  I always wanted to learn how to create a component using GWT, just never knew where to start.  This will help tremendously.

alldevnet.com

2 years ago

Creating a Custom Ext GWT Component…

Creating custom Web components can prove challenging without a good foundation. Fortunately, the Ext GWT framework contains a Component API that provides the ability to quickly and easily create custom components while using Java. This post will walk y…

Asad

2 years ago

Great write up.  What’s great about using Box component as the base class is that it can participate in layouts.  I just incorporated a ContentScroller into a border layout and it worked perfectly.  Very nice work!

Vinícius Rabelo

2 years ago

Excelent, helped me to understand very much…

Cristina Mayorga

2 years ago

Hi, exelent work, i really like EXT-GWT.
I was wondering if you are going to integrate some print functionality in future release. We are developing a huge project and the users are asking for this kind of function for the grids.
Thank you.
Greetings from Mexico.

triathlon

2 years ago

Excellent work !

Jonathan Janisch

2 years ago

Nice article Darrell.  I wrote something similar for our internal blog here.  One thing which I was surprised worked was using tabIndex for focus.  When I wrote my article, in both chrome and safari only INPUT elements could receive focus AND act as tab stops (but I did try your example and it seemed to work fine).  The GXT Component class also has protected variable “focusable” which when true appends an input element specifically for webkit browsers.  I found this never worked for me because the input field must have a width and height greater than 0 which is what I do for our app here.  It may be helpful to explain more about these focus issues as tab navigation is really important.

matt nathanson music fan

2 years ago

Hey, thanks for this!

Diyet

2 years ago

HTML Markup
For the HTML markup, a HTML TABLE element is used for each bar. The table has a single row with 3 cells: left, center, and right. The left and right will show the rounded ends of the bar, while the center region will show the center section of the bar. The width of the left and right cells are fixed, with the center width being dynamic based on the available width.

 
   
     
     
       
       
           
         
       
     
     
   

Zay?flama

2 years ago

   
     
     
       
       
           
         
       
     
     
   

H?zl? Zay?flama

2 years ago

div unselectable=“on” class=” x-cscroller-bar x-unselectable ” id=“x-auto-0”>
   
     
     
       
       
           
         
       
     
     
   
  </div

oya-dantel

2 years ago

I wrote something similar for our internal blog here. One thing which I was surprised worked was using tabIndex for focus. When I wrote my article, in both chrome and safari only INPUT elements could receive focus AND act as tab stops (but I did try your example and it seemed to work fine). The GXT Component class also has protected variable “focusable” which when true appends an input element specifically for webkit browsers. I found this never worked for me because the input field must have a width and height greater than 0 which is what I do for our app here. It may be helpful to explain more about these focus issues as tab navigation is really important

Dantel Modelleri

2 years ago

All plugins must implement a method named init which is called by the Component, passing itself as the sole parameter at initialization time right at the beginning of the Component’s lifecycle, before it has been rendered.

Upon gaining control, a plugin must initialize itself to add capabilities and processing to its client Component.

JenniferB

2 years ago

Thanks for the info~

??

2 years ago

It may be helpful to explain more about these focus issues as tab.

Siki?

2 years ago

Excellent work !

rocky lemon

2 years ago

Amazing, I didn’t knew this, thankyou.

Kad?n

1 year ago

The GXT Component class also has protected variable “focusable” which when true appends an input element specifically for webkit browsers. I found this never worked for me because the input field must have a width and height greater than 0 which is what I do for our app here. It may be helpful to explain more about these focus issues as tab navigation is really important
Amazing, I didn’t knew this, thankyou

Akvaryum

1 year ago

The GXT Component class also has protected variable “focusable” which when true appends an input element specifically for webkit browsers. I found this never worked for me because the input field must have a width and height greater than 0 which is what I do for our app here. It may be helpful to explain more about these focus issues as tab navigation is really important

Comments are Gravatar enabled. Your email address will not be shown.

Commenting is not available in this channel entry.