Creating a GWT Wrapper for the JQuery UI Slider

by Bess Siegal on December 23, 2010


Today my wonderful and amazing colleague Bess Siegal dropped by to talk with me about wrapping JQuery controls in GWT.  You might remember Bess from her last article, Creating A Multi-Valued Auto-Complete Field Using GWT SuggestBox And REST.

JavaScript can dynamically zoom photos, develop mobile applications, and create complex animations without flash.  And GWT can’t.  Well… not easily.  

There’s a world of JQuery plugins you can use easily and safely in your GWT project.  All it takes is a little planning and a little practice.  

This article walks you through creating a GWT wrapper for the JQuery UI Slider which has more features, testing, and customizability than any slider available in GWT.

    

Default Slider

    

    

Range Slider

        

    

Snap to increments

    

    

Slider with multiple anchors

    

Importing JQuery libraries

We’ll start by adding a few libraries.  Download the minified files for JQuery and JQuery UI and add them to your WAR.  Then reference them in your HTML file like this:

<link media="all" type="text/css" href="jquery-ui.css" rel="stylesheet"> 
<script src="jquery-1.4.4.min.js" type="text/javascript" charset="utf-8"></script> 
<script src="jquery-ui.min.js" type="text/javascript" charset="utf-8"></script> 

You can also have Google host them for you like this:

<link media="all" type="text/css" 
        href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/themes/base/jquery-ui.css" rel="stylesheet"> 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js" 
        type="text/javascript" charset="utf-8"></script> 
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/jquery-ui.min.js" 
        type="text/javascript" charset="utf-8"></script> 

Let’s add some code

Get the
source code

Now that you’ve imported the JQuery libraries, let’s start using them.  The JQuery UI Slider attaches to a DIV element in your page so our GWT Slider wrapper extends Widget.  

Picking the correct base class is an important part of creating a fully featured GWT control.  Our slider is just a DIV.  If your control uses a text field you could extend TextBox or any other GWT widget.

public class Slider extends Widget
    
    /**
     * Create a slider with the specified ID.  The ID is required
     * because the slider needs a specific ID to connect to.
     * @param id - id of the element to create
     * @param options - JSONObject of any possible option, can be null 
     *                  for defaults
     */
    public Slider(String id, JSONObject options)
    {           
        super();
        Element divEle = DOM.createDiv();
        setElement(divEle);
        divEle.setId(id); 1
        
        m_defaultOptions = options;
        if (m_defaultOptions == null) {
            m_defaultOptions = getOptions(0, 100, new int[]{0});2
        }        
    }

A DIV element is created and set as the element in the constructor 1.

Extending widget works well for the Slider, but it isn’t always the best choice.  When we wrapped the JQuery UI Date Picker we used TextBox as our base class.  Choosing the right base class let’s your JQuery wrapper act like a first class GWT widget.

Getting your slider’s options

getOptions2 is a static method to assist in creating the JSONObject
of options.  

    
    /**
     * A convenient way to create an options JSONObject.  Use SliderOption 
     * for keys.
     *
     * @param min - default minimum of the slider
     * @param max - default maximum of the slider
     * @param defaultValues - default points of each anchor
     * @return a JSONObject of Slider options
     */
    public static JSONObject getOptions(int min, int max, int[] defaultValues) 
    {
        JSONObject options = new JSONObject();
        options.put(SliderOption.MIN.toString(), new JSONNumber(min));
        options.put(SliderOption.MAX.toString(), new JSONNumber(max));
        JSONArray vals = intArrayToJSONArray(defaultValues);
        options.put(SliderOption.VALUES.toString(), vals);
        return options;
    }

    private static JSONArray intArrayToJSONArray(int[] values)
    {
        JSONArray vals = new JSONArray(); 
        for (int i = 0, len = values.length; i < len; i++) {
            vals.set(i, new JSONNumber(values[i]));
        }
        return vals;
    }

More options

There are also other constructors for the most common parameter options set during initialization. 

/**
 * Create a slider with the specified ID.  The ID is required
 * because the slider needs a specific ID to connect to.
 * @param id - id of the element
 * @param min - default minimum of the slider
 * @param max - default maximum of the slider
 * @param defaultValue - default point of a single anchor
 */
public Slider(String id, int min, int max, int defaultValue)
{
    this(id, min, max, new int[]{defaultValue});
}
    
/**
 * Create a slider with the specified ID.  The ID is required
 * because the slider needs a specific ID to connect to.
 * @param id - id of the element
 * @param min - default minimum of the slider
 * @param max - default maximum of the slider
 * @param defaultValues - default points of each anchor
 */
public Slider(String id, int min, int max, int[] defaultValues)
{           
    this(id, getOptions(min, max, defaultValues));
}

The SliderOption enumeration helps with additional slider options.  The Javadoc comments for each option is copied directly from JQuery UI Slider documentation.

Initialization

Constructing your GWT object is only the first step.  You also have to bind it to JQuery4 with the JavaScript Native Interface.  The best place to do this is the onLoad method.

We need this method because GWT causes a timing problem.  With a regular JQuery page you would just call the binding when your page loads.  GWT uses a delayed loading mechanism which loads the correct GWT code for your browser and language.  The onLoad3 method gives us a good place to bind to JQuery after our GWT object has been loaded into the page.

    @Override
    protected void onLoad()3
    {
        if (m_defaultOptions == null) {
            m_defaultOptions = new JSONObject();
        }
        
        createSliderJS(this, getElement().getId(), m_defaultOptions.getJavaScriptObject());
        super.onLoad();
    }
    
    private native void createSliderJS(Slider x, String id, JavaScriptObject options) /*-{
        options.start = function(event, ui) {
            x.@com.example.slider.client.widget.slider.Slider::fireOnStartEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
        };
        options.slide = function(event, ui) {
            return x.@com.example.slider.client.widget.slider.Slider::fireOnSlideEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
        };
        options.change = function(event, ui) {
            var has = event.originalEvent ? true : false;
            x.@com.example.slider.client.widget.slider.Slider::fireOnChangeEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;Z)(event, ui.values, has);                
        };
        options.stop = function(event, ui) {
            x.@com.example.slider.client.widget.slider.Slider::fireOnStopEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
        };5
        
        $wnd.$("#" + id).slider(options);4
    }-*/;
    

In addition to the options set in the constructor, the createSliderJS method also maps the slider’s events to corresponding fireOnXEvent Java methods using the GWT technique for passing JavaScript values into Java code.  The values we pass are the native event and the values selected by the slider. 5.  Thus we have our cornerstone for event handling.

Event handling

When we’re done with our Slider it will look just like a standard GWT widget from the outside.  That way we can switch implementations and never worry about our choice of JavaScript library leaking out to other code in our project.  We’ll continue that strong encapsulation by creating a SliderEvent object and SliderListener interface to allow other code to get events from the Slider.

    private void fireOnStartEvent(Event evt, JsArrayInteger values)
    {
        int[] vals = jsArrayIntegerToIntArray(values);
        SliderEvent e = new SliderEvent(evt, this, vals);
        
        for (SliderListener l : m_listeners) {
            l.onStart(e);
        }
    }
    
    private boolean fireOnSlideEvent(Event evt, JsArrayInteger values)
    {
        int[] vals = jsArrayIntegerToIntArray(values);
        SliderEvent e = new SliderEvent(evt, this, vals);
        
        for (SliderListener l : m_listeners) {
            l.onStart(e);
        }
        
        boolean ret = true;
        
        for (SliderListener l : m_listeners) {
            if (!l.onSlide(e)) {
                //if any of the listeners returns false, return false,
                //but let them all do their thing
                ret = false;
            }
        }
        
        return ret;6
    }
    
    private void fireOnChangeEvent(Event evt, JsArrayInteger values, 
                                   boolean hasOriginalEvent)
    {
        int[] vals = jsArrayIntegerToIntArray(values);        
        SliderEvent e = new SliderEvent(evt, this, vals, hasOriginalEvent);
        
        for (SliderListener l : m_listeners) {
            l.onChange(e);
        }
    }
    
    private void fireOnStopEvent(Event evt, JsArrayInteger values)
    {
        int[] vals = jsArrayIntegerToIntArray(values);
        SliderEvent e = new SliderEvent(evt, this, vals);
        
        for (SliderListener l : m_listeners) {
            l.onStop(e);
        }
    }

Each fire event method notifies all listeners and passes the native event and an int[] of values. The onSlide event allows for cancellation of the action by returning false6.  

Changing options after initialization

You may change a slider’s options after initialization.  There are get/setIntOption, get/setBooleanOption and get/setStringOption methods to maintain type-safety.  Each of these methods calls a corresponding JSNI method to get or set the option.  Getting and setting the slider’s values is a little different, so they have their own methods.

    /**
     * Convenience method for only 1 anchor
     * @return Returns the value.
     */
    public int getValue()
    {
        return getValueAtIndex(0);
    }
    
    /**
     * Sets the value of each anchor
     * @param values - int array of values
     */
    public void setValues(int[] values)
    {
        JSONArray vals = intArrayToJSONArray(values);
        setValuesJS(getElement().getId(), vals.getJavaScriptObject());
    } 
        
    /**
     * Gets the value of a anchor at the specified index
     * 
     * @param index  the index to retrieve the value for
     * 
     * @return the value
     */
    public int getValueAtIndex(int index)
    {
        return getValueJS(getElement().getId(), index);
    }
    
    
    private native void setValuesJS(String id, JavaScriptObject values) /*-{
        $wnd.$("#" + id).slider("option", "values", values);
    }-*/;
    
    
    private native int getValueJS(String id, int index) /*-{
        return $wnd.$("#" + id).slider("values", index);
    }-*/;

Extending the Slider

Because a slider that uses the range option is unique in that the values are limited to 2, a RangeSlider subclass was created to only allow get and set of 2 values.  You could extend the slider for any of the specific options required by your application.

Cleanup

The widget will cleanup after itself by calling destroy on the slider during onUnload, which is called immediately before a widget will be detached from the browser’s document.

    @Override
    protected void onUnload()
    {
        destroySliderJS(this, getElement().getId());
        super.onUnload();        
    }
    
    private native void destroySliderJS(Slider x, String id) /*-{
        $wnd.$("#" + id).slider("destroy");
    }-*/;

The Takeaway

For our web application projects at Novell, we like to use GWT for maintainability.  For all GWT’s benefits, we understand that it’s standard widgets are limited, especially when compared to its corresponding JQuery widget.  Fortunately, we’ve developed a repeatable method for wrapping JQuery UI controls, and we hope you can employ it with this slider as an example.  You may also feel free to use the slider code in your application.  Either way, please let us know if you do.

The code in this example is free and released under the Apache 2.0 license. The other programs needed to run this example are also free, but some of them may use different licenses. Make sure to read and understand each license before using a tool.

Bess Siegal is a software engineer at Novell.  She enjoys her one-minute commute so she can spend more time with her husband and 3 daughters.

  • http://profiles.google.com/sankalp.acharya Sankalp Acharya

    Thank you for this post – it came in handy, but in my app, I wanted to use a UiBinder-ized version of this widget wrapper. I extended your example by adding a UiBinder template for the Slider and then did some minor refactoring to the Java code. I’m happy to share if you’d like – let me know if this is something you’d be interested in!

  • http://www.zackgrossbart.com Zack Grossbart

    Hey Sankalp,nnThat sounds super cool. We would love to see the code.

  • Roger Studner

    I’d really like to see a UIBinder version of this.. please please post!

  • Bess Siegal

    The Slider has been released as a part of Spiffy UI!u00a0 Find out more about all that Spiffy UI has to offer, including REST, security as well as other widgets at http://www.spiffyui.org

  • Pingback: Developer Sharing June Edition « GWT Buzz

  • John

    I think you just saved me some time and effort…Thanks!

  • Rene Dohan

    gwtquery-ui

  • an solas

    could we see this slider in the design view ?

  • http://www.whatsthesequency.com/cookie.php Mohene1

    Too many acronyms are jargon. I just want to get things working I am not a developer. wrapper, and GWT are dangling terms they could mean anything.

  • http://www.ventatextil.es/ colchones

    Can i get a download source example? thanks!

  • http://www.zackgrossbart.com Zack Grossbart
  • Marcel Hess

    Thanks for that! Great stuff!

  • Peter Dungel

    Thank you for you work, i’ve downloaded your sample code and it worked fine for my project. I only have one issue, where i’m not sure if it’s my fault, which i believe, or it is not working with gwt. I wanted to create a range-min slider like here (
    http://jqueryui.com/demos/slider/#rangemin) but i can’t get it work within your example project, i tried to set the RANGE propertie for a “simple” and a “range” slider to min, which has the effekt that a range-min div is created in my webpage, but no matter how much i use the slider the range-min div does not change its size, the width of it stays 0 for a horizontal slider, maybe someone has an idea?  

  • http://www.zackgrossbart.com Zack Grossbart

    Thanks for using the code. What you’re describing should work. I would check your CSS to make sure there isn’t a style preventing the slider from growing. Other than that it’s tough to say what’s going on unless you can share you code.

    Good luck,
    Zack

  • Peter Dungel

    Thanks for your quick reply. For example i’ve downloaded your sourcecode and just added to the stepslider the RANGE min property by entering this line:
     options.put(SliderOption.RANGE.toString(), new JSONString(“min”));

    adding this line to the code the created slider has a new div:

     which should show the the range between the minimum of the slider and the handle, but no matter to which position i drag the handle nothing happens.

    the range slider in your demo app, shows the range between the two handles as here on your site, so i guess this should not be a css problem, my guess would be there is something wrong with the code which generates the range-min div because other than at the range slider there is no inline style which sets the width of the div.

    yours peter

  • Dianadm

    Thank you, I’m using your code in my project, but  it doesn’t work properly in IE – it change position only by clicking. Is there any solution for this problem?

  • http://www.zackgrossbart.com Zack Grossbart

    Does the sample in this article work on IE for you?  What version of IE are you using?  I’m not sure what “change position on by clicking” means.  Can you give me a few more details.

    Thanks,
    Zack

  • Peter Dungel

    maybe you have to take a newer version of the js library, i also encountered a problem with the example and ie, updating to a newer version solved that problem

  • Peter Dungel

    i think i found out what the problem is, in your example when creating a slider you always pass an array of int values for the handles even if there is only one present. using the parameter values instead of value seems to disable the range min property.

  • Dianadm

    The sample in this article also doesn’t work on IE for me. I mean that I have to click somewhere on the ax to move slider.
    but today I took newest version of jquery-ui and it works good! Thanks

  • http://www.zackgrossbart.com Zack Grossbart

    Thanks for finding the issue guys.  The version of jQuery in this article is a little old and they’ve had a lot of time to fix bugs since then.  

  • http://profile.yahoo.com/S4NCDHKSCZB3LLNXDR5DQ3BCPA Manasa

    I am trying to get a range slider with fixed minimum in gwt. It should work just like step slider but highlight the selected range. Is there any suggestion what I need to do??

    I tried

    options.put(SliderOption.RANGE.toString(), new JSONString(“min”));

    but that’s not working

  • http://www.zackgrossbart.com Zack Grossbart

    I’m not sure I understand what you’re looking for. Can you please tell me a little more about what you’re trying to do.

    Thanks,
    Zack

  • Pingback: Erstellen einer TimeSlider Animation mit GWT | TIGER TECH TALK