Monday, April 23, 2012

GWT Custom CellTable Column

I love GWT's Cell widgets. For example the CellTable is such a leap forward from displaying information than their stupid-ass FlexTable.  They do have some drawbacks, like not being able to bind click events of individual cells in the presenter which would be good when you want to have multiple buttons in each row.  And the other drawback is, where the hell is the documentation for creating a custom column in a CellTable? I'm looking at you Google!  So without an examples or documentation we are left to muddle through the source code, which might be fun to you but I have a wife who wants to spend time with me and I got shit to do.  Well now she's upset because I'm not giving her the emotional attention she requires, the dishes are all dirty, half the fish are starving and the other are floating upside-down, my kid's diaper is overflowing, I missed the Game of Thrones premier, and I had to get an extension on filing my taxes, dogs and cats living together! Mass hysteria!

It's actually not that hard to figure out but luckily I'm here to save you some time.  So let's say we have a custom widget that we would like to use in a CellTable.  In this example I will be using my StarRating widget from a previous post which you can find here: http://programmingfortherestofus.blogspot.com/2012/04/gwt-star-rating-widget-with-half-stars.html

First step we need to take is to create a class that extends AbstractCell and implements Cell so the CellTable knows how to draw the cell and what data type it will expect, as well as overrides the render method where we add our widget's HTML to an HtmlBuilder. And if we are allowing user interaction we first pass what events we want to capture to the super class, for example mouse click and the enter key press, and then we override the onBrowserEvent, onEnterKeyDown, and any other events we wanted to capture.

StarRatingCell.java
/**
 * 
 */
package com.yourproject.widgets;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;

/**
 * @author Austin_Rappa
 *
 */
public class StarRatingCell extends AbstractCell<StarRating> implements Cell<StarRating> {

    
    /**
     * Cell that contains a StarRating widget
     */
    public StarRatingCell() {
        super("click", "keydown");
    }


    
    /**
     * Render a cell as HTML into a SafeHtmlBuilder, suitable for passing to Element.setInnerHTML(String) on a container element. 
     */
    @Override
    public void render(com.google.gwt.cell.client.Cell.Context context, StarRating value, SafeHtmlBuilder sb) {
          sb.appendHtmlConstant("<div style=\"padding:0px;\">");
          if (value != null) {
              sb.appendHtmlConstant(value.toString());
          }
          sb.appendHtmlConstant("</div>");        
    }


    /**
     * Called when an event occurs in a rendered instance of this Cell. The     
     * parent element refers to the element that contains the rendered cell, NOT     
     * to the outermost element that the Cell rendered.     
     */    
    @Override    
    public void onBrowserEvent(Context context, Element parent, StarRating value, NativeEvent event, ValueUpdater<StarRating> valueUpdater) {               
        super.onBrowserEvent(context, parent, value, event, valueUpdater);         
        
        // Handle the click event.             
        if ("click".equals(event.getType())) {             
            // Ignore clicks that occur outside of the outermost element.            
            EventTarget eventTarget = event.getEventTarget();             
            if (parent.isOrHasChild(Element.as(eventTarget))) {                                
                // use this to get the selected element!!                
                Element el = Element.as(eventTarget);                 
                
                //Get the value, if it is clear then set it to zero
                if (el.getAttribute("title").equals("clear")) {
                    value.setRating(0);                    
                }
                else {
                    value.setRating(Integer.parseInt(el.getAttribute("title")));
                }
                 

                //Set the value of the StarRating
                setValue(context, parent, value);                                              
            }        
        }     
    };
        
                    

    /**     
     * onEnterKeyDown is called when the user presses the ENTER key will the     
     * Cell is selected. You are not required to override this method, but its a     
     * common convention that allows your cell to respond to key events.     
     */    
    @Override    
    protected void onEnterKeyDown(Context context, Element parent, StarRating value, NativeEvent event, ValueUpdater<StarRating> valueUpdater) {
        setValue(context, parent, value);                               
    }
}


I mean how much easier can it get?  Well I would like to introduce you to the next step, to create the column class that holds our StarRatingCell and overrides the getValue method to return a StarRating object. 

StarRatingColumn.java
package com.yourproject.widgets;

import com.google.gwt.user.cellview.client.Column;
    
/**
 * A column that displays its contents with a {@link StarRating} and does not make use of view data.
 *
 * @param <T> the row type
 * @author Austin_Rappa
 */
public abstract class StarRatingColumn<T> extends Column<T, StarRating> {

    /**
     * Construct a new StarRatingColumn.
     */
    public StarRatingColumn() {
        super(new StarRatingCell());
    }

    
     /**      
      * Return the passed-in object. 
      * @param object The value to get
      */    
    @Override    
    public StarRating getValue(T object) {    
        return null;         
    }  
}


And with this we are all ready to start puting this into a CellTable!  We instantiate the StarRatingColumn and define the type that contains our rating value, then override the getValue method, and finally set a field updater when allowing user interaction to update the html in the table when the value changes.

CellTable<ThingIWantToRate> cellTable = new CellTable<ThingIWantToRate>();
    
...
    
StarRatingColumn<ThingIWantToRate> ratingColumn = new StarRatingColumn<ThingIWantToRate>() {
    @Override    
    public StarRating getValue(ThingIWantToRate object) {
        return new StarRating(object.getRating(), 10, false);    
    }  
};
cellTable.addColumn(ratingColumn, "Rating");

//Update view
ratingColumn.setFieldUpdater(new FieldUpdater<ThingIWantToRate, StarRating>() {
    public void update(int index, ThingIWantToRate object, StarRating value) {
        //Set cell's value
        object.setRating(value.getRating());
                    
        //TODO: Update database here
                    
        //Redraw table
        cellTable.redraw();                    
    }                
});

GWT Star Rating Widget with Half Stars

One of the rages these days are fancy-star ratings. We see them everywhere from Amazon, Netflix, Google Shopping, and even Wikipedia. 

GWT does not come with a star rating widget built in, but there are some pretty good ones out there already like:
http://code.google.com/p/cobogw/

However they all have full rating images and I wanted something more like Amazon or Netflix with 2.5 or 4.5 stars, or with a little modification 3.75 stars.  So my version is based off of my HorizontalDivPanel class that fixes the table-based layout widgets with a better div-based approach.  So we would first need to get this code and place it in our project:
http://programmingfortherestofus.blogspot.com/2012/04/fixing-gwts-layout-widgets.html

Next find any image you would like to use for your rating image, I would recommend something smal and with an even number of pixels for its width. Now we are going to create 3 versions:
  1. selected: This is the "normal" image that will appear for stars 1 to the rating value.
  2. unselected: This is the default image used, and will appear for the images that a greater than the rating value.
  3. hover: This appears when a user hovers over a rating image.
Then take each of those images and crop them directly in half with the the left and right side added to the filename. Finally we need an image use to clear or reset rating value, like a red or gray X.  We should now have the following file names, which I prefixed with "star_" and placed them my project's war folder "images/starrating" but you could change them to whatever you wish either in the code or using one of the constructors:
  • clear.png
  • star_hover_left.png
  • star_hover_right.png
  • star_selected_left.png
  • star_selected_right.png
  • star_unselected_left.png
  • star_unselected_right.png
Great! Now on to the code. Just like creating any widget we extend Composite, and to get the behavior we want we extend ClickHandler, MouseOverHandler, MouseOutHandler, and also HasValue<Integer> for use in your MVP project.  Next we ad a HorizontalDivPanel (again from a previous blog of mine) to contain the images, and finally an Image uses to clear the rating value when the widget is not set to read only.  The 2 constructors I only use are Star() and Star(boolean read_only).  The read only value determines if the user can change the value of the widget or it is just there to display the rating value.

/**
 * 
 */
package com.yourproject.widgets;

import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Image;

/**
 * @author Austin_Rappa
 *
 */
public class StarRating extends Composite implements ClickHandler, MouseOverHandler, MouseOutHandler, HasValue<Integer> {

    private HorizontalDivPanel mainPanel = new HorizontalDivPanel();
    private Image clearImage = new Image();
    
    private int rating = 0;
    private int rating_max = 10;
    private int hover_index = 0;
    private boolean read_only = false;     
    private String star_selected_left_path = "images/starrating/star_selected_left.png";
    private String star_selected_right_path = "images/starrating/star_selected_right.png";
    private String star_unselected_left_path = "images/starrating/star_unselected_left.png";
    private String star_unselected_right_path = "images/starrating/star_unselected_right.png";
    private String star_hover_left_path = "images/starrating/star_hover_left.png";
    private String star_hover_right_path = "images/starrating/star_hover_right.png";
    private String clear_path = "images/starrating/clear.png";
    
    
    
    /**
     * Star rating widget
     */
    public StarRating() {
        this(0, 10, false);
    }
    
    
    /**
     * Star rating widget
     * @param read_only
     */
    public StarRating(boolean read_only){
        this(0, 10, read_only);
    }
    
    
    /**
     * Star rating widget
     * @param rating
     * @param rating_max
     */
    public StarRating(int rating, int rating_max){
        this(rating, rating_max, false);
    }
    
    
    /**
     * Star rating widget
     * @param rating
     * @param rating_max
     * @param read_only
     */
    public StarRating(int rating, int rating_max, boolean read_only){
        this.setRating(rating);
        this.setRatingMax(rating_max);
        this.setReadOnly(read_only);
        this.buildWidget();
    }
    

    
    
    
    /**
     * Star rating widget
     * @param rating
     * @param rating_max
     * @param read_only
     * @param star_selected_left
     * @param star_selected_right
     * @param star_unselected_left
     * @param star_unselected_right
     * @param star_hover_left
     * @param star_hover_right
     */
    public StarRating(int rating, int rating_max, boolean read_only, String star_selected_left, String star_selected_right, String star_unselected_left, String star_unselected_right, String star_hover_left, String star_hover_right){
        this.setRating(rating);
        this.setRatingMax(rating_max);
        this.setReadOnly(read_only);
        this.setStarSelectedLeft(star_selected_left);
        this.setStarSelectedRight(star_selected_right);
        this.setStarUnselectedLeft(star_unselected_left);
        this.setStarUnselectedRight(star_unselected_right);
        this.setStarHoverLeft(star_hover_left);
        this.setStarHoverRight(star_hover_right);        
        this.buildWidget();
    }
    
    
    
    
    /**
     * Builds the widget
     */
    private void buildWidget() {        
        Image.prefetch(this.getStarSelectedLeftPath());
        Image.prefetch(this.getStarSelectedRightPath());
        Image.prefetch(this.getStarUnselectedLeftPath());
        Image.prefetch(this.getStarUnselectedRightPath());
        Image.prefetch(this.getStarHoverLeftPath());
        Image.prefetch(this.getStarHoverRightPath());
        Image.prefetch(this.getClearPath());
        
        //Initialize
        initWidget(mainPanel); 
        mainPanel.setStyleName("starrating");
        this.addMouseOutHandler(this);
                
        //Stars
        for (int i = 0; i < this.getRatingMax(); i++) {
            Image image = new Image();
            
            //Settings
            image.setStyleName("star");
            image.setTitle("" + (i + 1));
            image.addClickHandler(this);
            image.addMouseOverHandler(this);
            
            mainPanel.add(image);
        }
        
        //If not readonly
        if (!this.isReadOnly()) {
            mainPanel.getElement().getStyle().setCursor(Style.Cursor.POINTER);
            
            //Clear image
            clearImage.setUrl(this.getClearPath());
clearImage.setTitle("clear"); clearImage.addClickHandler(this); clearImage.addMouseOverHandler(this); mainPanel.add(clearImage); } //Set the star images this.setStarImages(); } /** * * @param handler * @return */ private HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { return addDomHandler(handler, MouseOutEvent.getType()); } /** * Resets the button images */ private void setStarImages() { for (int i = 0; i < this.getRatingMax(); i++) { Image image = (Image)mainPanel.getWidget(i); image.setUrl(this.getImagePath(i)); } } /** * Gets the star image based on the index * @param index * @return */ private String getImagePath(int index) { String path = ""; if (index % 2 == 0) { if (index >= this.getHoverIndex()) { if (index >= this.getRating()) { path = this.getStarUnselectedLeftPath(); } else { path = this.getStarSelectedLeftPath(); } } else { path = this.getStarHoverLeftPath(); } } else { if (index >= this.getHoverIndex()) { if (index >= this.getRating()) { path = this.getStarUnselectedRightPath(); } else { path = this.getStarSelectedRightPath(); } } else { path = this.getStarHoverRightPath(); } } return path; } /** * @param rating the rating to set */ public void setRating(int rating) { this.rating = rating; } /** * @return the rating */ public int getRating() { return rating; } /** * @param rating_max the rating_max to set */ public void setRatingMax(int rating_max) { this.rating_max = rating_max; } /** * @return the rating_max */ public int getRatingMax() { return rating_max; } /** * @param hover_index the hover_index to set */ public void setHoverIndex(int hover_index) { this.hover_index = hover_index; } /** * @return the hover_index */ public int getHoverIndex() { return hover_index; } /** * @param read_only the read_only to set */ public void setReadOnly(boolean read_only) { this.read_only = read_only; } /** * @return the read_only */ public boolean isReadOnly() { return read_only; } /** * @param star_selected_left the star_selected_left to set */ public void setStarSelectedLeft(String star_selected_left) { this.star_selected_left_path = star_selected_left; } /** * @return the star_selected_left */ public String getStarSelectedLeftPath() { return star_selected_left_path; } /** * @param star_selected_right the star_selected_right to set */ public void setStarSelectedRight(String star_selected_right) { this.star_selected_right_path = star_selected_right; } /** * @return the star_selected_right */ public String getStarSelectedRightPath() { return star_selected_right_path; } /** * @param star_unselected_left the star_unselected_left to set */ public void setStarUnselectedLeft(String star_unselected_left) { this.star_unselected_left_path = star_unselected_left; } /** * @return the star_unselected_left */ public String getStarUnselectedLeftPath() { return star_unselected_left_path; } /** * @param star_unselected_right the star_unselected_right to set */ public void setStarUnselectedRight(String star_unselected_right) { this.star_unselected_right_path = star_unselected_right; } /** * @return the star_unselected_right */ public String getStarUnselectedRightPath() { return star_unselected_right_path; } /** * @param star_hover_left the star_hover_left to set */ public void setStarHoverLeft(String star_hover_left) { this.star_hover_left_path = star_hover_left; } /** * @return the star_hover_left */ public String getStarHoverLeftPath() { return star_hover_left_path; } /** * @param star_hover_right the star_hover_right to set */ public void setStarHoverRight(String star_hover_right) { this.star_hover_right_path = star_hover_right; } /** * @return the star_hover_right */ public String getStarHoverRightPath() { return star_hover_right_path; } /** * @param clear_path the clear_path to set */ public void setClearPath(String clear_path) { this.clear_path = clear_path; } /** * @return the clear_path */ public String getClearPath() { return clear_path; } /** * On mouse over event */ public void onMouseOver(MouseOverEvent event) { if (!this.isReadOnly()) { Image image = (Image)event.getSource(); if (image.equals(clearImage)) { this.setHoverIndex(0); } else { this.setHoverIndex(Integer.parseInt(image.getTitle())); } this.setStarImages(); } } /** * On click event */ public void onClick(ClickEvent event) { if (!this.isReadOnly()) { Image image = (Image)event.getSource(); if (image.equals(clearImage)) { this.setValue(0, true); } else { this.setValue(Integer.parseInt(image.getTitle()), true); } } } /** * On mouse out event */ public void onMouseOut(MouseOutEvent event) { this.setHoverIndex(0); this.setStarImages(); } /** * Adds a ValueChangehandler */ public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler) { return addHandler(handler, ValueChangeEvent.getType()); } /** * Get the rating value */ public Integer getValue() { return this.getRating(); } /** * Set the rating value * @param value the rating to set */ public void setValue(Integer value) { this.setRating(value); this.setStarImages(); } /** * Set the rating value * @param value the rating to set * @param fireEvents fire events */ public void setValue(Integer value, boolean fireEvents) { this.setValue(value); if (fireEvents) ValueChangeEvent.fire(this, value); } }

My first attempt was to do all of the image handling in CSS, because adding handlers to all of the images is just a ton of overhead. But in order to get the hover behavior to change the images from star 1 to whatever star is being hovered on, we sort of have to.  There is a lot to add, like a value to even use 1/4 stars or full stars, and to ensure the rating_max is divisible by that value. But you get the idea.

Now in a your View or other Widget you could instantiate the StarRating like so:

StarRating star1 = new StarRating();
        
StarRating star2 = new StarRating(true);
        
StarRating star3 = new StarRating(5, 10, false);

Wednesday, April 11, 2012

Fixing GWT's Layout Widgets - Horizontal and Vertical Div Panels

One of the biggest complaints with GWT is (rightfully so) its layout widgets VerticalPanel and HorizontalPanel, which use HTML tables to format its widgets.  Anyone this side of 1999 knows that using tables for your layout is highly inefficent and one of the reasons why serious web developers scoff at using it. So I've developed horizontal and vertical panels using more common div and unordered list elements and styled appropriately to do formatting.

The benefits are:
  • Less HTML. The code for a table is huge, this is super small making pages load faster.
  • How your webpage is supposed to be coded
  • Uses current standards where the styling is done in the CSS

VerticalDivPanel.java
package com.yourproject.widgets;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Widget;

public class HorizontalDivPanel extends ComplexPanel {
    
    UnorderedList ul = new UnorderedList();
    

     /**     
      * Creates an empty flow panel.     
      */    
    public HorizontalDivPanel() {
        final Element divElement = DOM.createDiv();          
        setElement(divElement);                    
        divElement.setClassName("horizontal");  
        
        super.add(ul, getElement());
    }
    
    
    /**
     * Adds a new child widget to the panel.     
     *      
     * @param w the widget to be added    
     */    
    public void add(Widget w) {         
        ListItem li = new ListItem();
        li.add(w);
        ul.add(li);         
    } 
    
    
    /**     
     * Inserts a widget before the specified index.     
     *      
     * @param w the widget to be inserted     
     * @param beforeIndex the index before which it will be inserted     
     * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of range    
     */    
    
    public void insert(Widget w, int beforeIndex) { 
        ListItem li = new ListItem();
        li.add(w);
        ul.add(li);
        ul.insert(li, beforeIndex);
    }


    /**
     * Gets the child widget at the specified index
     * @param index Index of the widget to retrieve
     * @return The child widget
     * @throws IndexOutOfBoundsException
     */
    public Widget getWidget(int index) {
        Widget widget = null;
        
        if (index < ul.getWidgetCount()) {
            ListItem li = (ListItem)ul.getWidget(index);
            if (li.getWidgetCount() > 0) {
                widget = li.getWidget(0);                
            }
        }
        else {
            throw new IndexOutOfBoundsException("The index value" + index + " cannot be greater than the number of widgets of " + ul.getWidgetCount());            
        }
        
        return widget;
    }
    
        
    /**
     * Gets the number of child widgets in the panel
     * @return The number of children
     */
    public int getWidgetCount() {
        return ul.getWidgetCount();
    }
    
    
    
    /**
     * Gets the index of the specified Widget
     * @param child The widget to be found
     * @return the widget's index, or -1 if it is not a child of this panel
     */
    public int getWidgetIndex(IsWidget child) {
        int index = -1;
        
        int i = 0;
        boolean found = false;
        while (i < ul.getWidgetCount() && !found) {
            ListItem li = (ListItem)ul.getWidget(i);
            if (li.getWidgetCount() > 0) {
                if (child.equals(li.getWidget(0))) {
                    found = true;
                    index = i;
                }
            }
            i++;
        }

        return index;
    }

    
    /**
     * Gets the index of the specified Widget
     * @param child The widget to be found
     * @return the widget's index, or -1 if it is not a child of this panel
     */
    public int getWidgetIndex(Widget child) {
        int index = -1;
        
        int i = 0;
        boolean found = false;
        while (i < ul.getWidgetCount() && !found) {
            ListItem li = (ListItem)ul.getWidget(i);
            if (li.getWidgetCount() > 0) {
                if (child.equals(li.getWidget(0))) {
                    found = true;
                    index = i;
                }
            }
            i++;
        }

        return index;
    }


    
    
    /**
     * Removes all child widgets
     */
    public void clear() {
        ul.clear();
    }
    
    
    
    /**
     * Adds a secondary or dependent style name to this object.
     * @param style the new style name
     */
    public void setStyleName(String style) {
        super.addStyleName(style);
    }


    

    
    /**
     * 
     * @author Austin_Rappa
     */
    public class OrderedList extends ComplexPanel {
        public OrderedList() {
            setElement(DOM.createElement("ol"));
        }

        public void add(Widget w) {
            super.add(w, getElement());
        }

        public void insert(Widget w, int beforeIndex) {
            super.insert(w, getElement(), beforeIndex, true);
        }
    }
    
    
    
    /**
     * 
     * @author Austin_Rappa
     */
    public class UnorderedList extends ComplexPanel {
        public UnorderedList() {
            setElement(DOM.createElement("ul"));
        }

        public void add(Widget w) {
            super.add(w, getElement());
        }

        public void insert(Widget w, int beforeIndex) {
            super.insert(w, getElement(), beforeIndex, true);
        }
    }

    
    /**
     * 
     * @author Austin_Rappa
     */
    public class ListItem extends ComplexPanel implements HasText {
        public ListItem() {
            setElement(DOM.createElement("li"));
        }

        public void add(Widget w) {
            super.add(w, getElement());
        }

        public void insert(Widget w, int beforeIndex) {
            super.insert(w, getElement(), beforeIndex, true);
        }

        public String getText() {
            return DOM.getInnerText(getElement());
        }

        public void setText(String text) {
            DOM.setInnerText(getElement(), (text == null) ? "" : text);
        }

    }
}



HorizontalDivPanel.java
package com.yourproject.widgets;

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.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;

/**
 * Creates a vertical panel
 * @author Austin_Rappa
 */
public class VerticalDivPanel extends ComplexPanel implements HasClickHandlers {
    
    private final UnorderedList ulElem = new UnorderedList();
    

     /**     
      * Creates an empty flow panel.     
      */    
    public VerticalDivPanel() {
        final Element divElement = DOM.createDiv();          
        setElement(divElement);                    
        divElement.setClassName("vertical");  
        
        super.add(ulElem, getElement());
    }
    
    
    /**
     * Adds a new child widget to the panel.     
     *      
     * @param w the widget to be added    
     */    
    public void add(Widget w) { 
        ListItem li = new ListItem();
        li.add(w);        
        ulElem.add(li);
    } 
    
    
    /**
     * Removes all child widgets
     */
    public void clear() {
        ulElem.clear();
    }
    
    
    
    /**
     * Adds a secondary or dependent style name to this object.
     * @param style the new style name
     */
    public void setStyleName(String style) {
        super.addStyleName(style);
    }
    
    
    
     * Gets the child widget at the specified index
     * @param index Index of the widget to retrieve
     * @return The child widget
     * @throws IndexOutOfBoundsException
     */
    public Widget getWidget(int index) {
        Widget widget = null;
        
        if (index < ul.getWidgetCount()) {
            ListItem li = (ListItem)ul.getWidget(index);
            if (li.getWidgetCount() > 0) {
                widget = li.getWidget(0);                
            }
        }
        else {
            throw new IndexOutOfBoundsException("The index value" + index + " cannot be greater than the number of widgets of " + ul.getWidgetCount());            
        }
        
        return widget;
    }
    
        
    /**
     * Gets the number of child widgets in the panel
     * @return The number of children
     */
    public int getWidgetCount() {
        return ul.getWidgetCount();
    }
    
    
    
    /**
     * Gets the index of the specified Widget
     * @param child The widget to be found
     * @return the widget's index, or -1 if it is not a child of this panel
     */
    public int getWidgetIndex(IsWidget child) {
        int index = -1;
        
        int i = 0;
        boolean found = false;
        while (i < ul.getWidgetCount() && !found) {
            ListItem li = (ListItem)ul.getWidget(i);
            if (li.getWidgetCount() > 0) {
                if (child.equals(li.getWidget(0))) {
                    found = true;
                    index = i;
                }
            }
            i++;
        }

        return index;
    }

    
    /**
     * Gets the index of the specified Widget
     * @param child The widget to be found
     * @return the widget's index, or -1 if it is not a child of this panel
     */
    public int getWidgetIndex(Widget child) {
        int index = -1;
        
        int i = 0;
        boolean found = false;
        while (i < ul.getWidgetCount() && !found) {
            ListItem li = (ListItem)ul.getWidget(i);
            if (li.getWidgetCount() > 0) {
                if (child.equals(li.getWidget(0))) {
                    found = true;
                    index = i;
                }
            }
            i++;
        }

        return index;
    }

        
    
    /**     
     * Inserts a widget before the specified index.     
     *      
     * @param w the widget to be inserted     
     * @param beforeIndex the index before which it will be inserted     
     * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of range    
     */    
    public void insert(Widget w, int beforeIndex) { 
        ListItem li = new ListItem();
        li.add(w);
        ulElem.insert(li, beforeIndex);
    }
    
    
    
    /**
     * 
     */
    public boolean remove(int index) {
        return ulElem.remove(index);        
    }
    
    
    
    /**
     * 
     */
    public boolean remove(Widget w) {
        return ulElem.remove(w);
    }
    
    
    /**
     * 
     */
    public boolean remove(IsWidget child) {
        return ulElem.remove(child);
    }

    
    
    /**
     * 
     * @param event
     * @return
     */
    public ListItem getItemForEvent(ClickEvent event) {
        Element li = getEventTargetItem(Event.as(event.getNativeEvent()));
        
        if (li == null) {
            return null;
        }
                
        return new ListItem(li);
    }
    
    
    
    /**
     * 
     * @param event
     * @return
     */
    protected Element getEventTargetItem(Event event) {
        Element li = DOM.eventGetTarget(event);

        for (; li != null; li = DOM.getParent(li)) {
         // If it's a LI, it might be the one we're looking for.
          if (DOM.getElementProperty(li, "tagName").equalsIgnoreCase("li")) {
            // Make sure it's directly a part of this table before returning it.
            Element ul = DOM.getParent(li);
            if (ul == ulElem.getElement()) {
              return li;
            }
          }
          // If we run into this table's body, we're out of options.
          if (li == ulElem.getElement()) {
            return null;
          }
        }
    
        return null;
    }
    
    

    /**
     * 
     */
    public HandlerRegistration addClickHandler(ClickHandler handler) {
        return addDomHandler(handler, ClickEvent.getType());
    }
    
    
    
    /**
     * 
     * @author Austin_Rappa
     */
    public class UnorderedList extends ComplexPanel {
        public UnorderedList() {
            setElement(DOM.createElement("ul"));
        }
        
        public UnorderedList(Element ul) {
            setElement(ul);
        }

        public void add(Widget w) {
            super.add(w, getElement());
        }

        public void insert(Widget w, int beforeIndex) {
            super.insert(w, getElement(), beforeIndex, true);
        }
    }

    
    /**
     * 
     * @author Austin_Rappa
     */
    public class ListItem extends ComplexPanel implements HasText {
        int index = -1;
        
        public ListItem() {
            setElement(DOM.createElement("li"));
        }
        
        public ListItem(Element li) {
            setElement(li);
            index = DOM.getChildIndex(DOM.getParent(li), li);
        }

        public void add(Widget w) {
            super.add(w, getElement());
        }

        public void insert(Widget w, int beforeIndex) {
            super.insert(w, getElement(), beforeIndex, true);
        }

        public String getText() {
            return DOM.getInnerText(getElement());
        }

        public void setText(String text) {
            DOM.setInnerText(getElement(), (text == null) ? "" : text);
        }

        public int getIndex() {
            if (this.getParent() == null) {
                return index;
            }
            return DOM.getChildIndex(this.getParent().getElement(), this.getElement());
        }
    }
}



Add to the beginning of your css file
/** --- DivPanels --- */
.vertical {vertical-align:top; margin:0px; padding:0px; width:100%;}
.vertical > ul {list-style:none; margin:0px; padding:0px; width:100%;}
.vertical > ul > li {display:block; text-decoration:none; margin:0px; padding:0px 0px; overflow:hidden;}
.horizontal {padding:0px; margin:0px; width:100%; height:inherit;}
.horizontal > ul {float:left; list-style:none; padding:0px; margin:0px; width:100%;}
.horizontal > ul > li {float:left; display:block; text-decoration:none; margin:0px; padding:0px;}