com.google.gwt.query.client.GQuery.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.query.client.GQuery.java

Source

/*
 * Copyright 2011, The gwtquery team.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.query.client;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayMixed;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.ButtonElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.HasCssName;
import com.google.gwt.dom.client.TextAreaElement;
import com.google.gwt.query.client.css.HasCssValue;
import com.google.gwt.query.client.css.TakesCssValue;
import com.google.gwt.query.client.css.TakesCssValue.CssSetter;
import com.google.gwt.query.client.impl.AttributeImpl;
import com.google.gwt.query.client.impl.DocumentStyleImpl;
import com.google.gwt.query.client.impl.SelectorEngine;
import com.google.gwt.query.client.js.JsCache;
import com.google.gwt.query.client.js.JsMap;
import com.google.gwt.query.client.js.JsNamedArray;
import com.google.gwt.query.client.js.JsNodeArray;
import com.google.gwt.query.client.js.JsObjectArray;
import com.google.gwt.query.client.js.JsRegexp;
import com.google.gwt.query.client.js.JsUtils;
import com.google.gwt.query.client.plugins.Effects;
import com.google.gwt.query.client.plugins.Events;
import com.google.gwt.query.client.plugins.Plugin;
import com.google.gwt.query.client.plugins.Widgets;
import com.google.gwt.query.client.plugins.ajax.Ajax;
import com.google.gwt.query.client.plugins.ajax.Ajax.Settings;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
import com.google.gwt.query.client.plugins.events.EventsListener;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.GqUi;
import com.google.gwt.user.client.ui.Widget;
import static com.google.gwt.query.client.plugins.QueuePlugin.Queue;

/**
 * GwtQuery is a GWT clone of the popular jQuery library.
 */
public class GQuery implements Lazy<GQuery, LazyGQuery> {

    private enum DomMan {
        AFTER, APPEND, BEFORE, PREPEND;
    }

    /**
     * A POJO used to store the top/left CSS positioning values of an element.
     */
    public static class Offset {
        public int left;
        public int top;

        public Offset(int left, int top) {
            this.left = left;
            this.top = top;
        }

        public Offset add(int left, int top) {
            return new Offset(this.left + left, this.top + top);
        }

        public String toString() {
            return top + "+" + left;
        }
    }

    /**
     * Class used internally to create DOM element  from html snippet
     */
    private static class TagWrapper {
        public static final TagWrapper DEFAULT = new TagWrapper(0, "", "");
        private String postWrap;
        private String preWrap;
        private int wrapDepth;

        public TagWrapper(int wrapDepth, String preWrap, String postWrap) {
            this.wrapDepth = wrapDepth;
            this.postWrap = postWrap;
            this.preWrap = preWrap;
        }
    }

    /**
     * Implementation class to modify attributes.
     */
    protected static AttributeImpl attributeImpl;

    /**
     * The body element in the current page.
     */
    public static final BodyElement body = Document.get().getBody();

    /**
     * Object to store element data.
     */
    protected static JsCache dataCache = null;

    /**
     * The document element in the current page.
     */
    public static final Document document = Document.get();

    /**
     * Static reference Effects plugin
     */
    public static Class<Effects> Effects = com.google.gwt.query.client.plugins.Effects.Effects;

    /**
     * Implementation engine used for CSS selectors.
     */
    protected static SelectorEngine engine;

    /**
     * Static reference Events plugin
     */
    public static Class<Events> Events = com.google.gwt.query.client.plugins.Events.Events;

    /**
     * A static reference to the GQuery class.
     */
    public static Class<GQuery> GQUERY = GQuery.class;

    private static final String OLD_DATA_PREFIX = "old-";

    private static JsMap<Class<? extends GQuery>, Plugin<? extends GQuery>> plugins;

    // Sizzle POS regex : usefull in some methods
    // TODO: Share this static with SelectorEngineSizzle
    private static final String POS_REGEX = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\))?(?=[^\\-]|$)";

    /**
     * Implementation class used for style manipulations.
     */
    private static DocumentStyleImpl styleImpl;

    private static JsRegexp tagNameRegex = new JsRegexp("<([\\w:]+)");

    /**
     * Static reference to the Widgets plugin
     */
    public static Class<Widgets> Widgets = com.google.gwt.query.client.plugins.Widgets.Widgets;

    /**
     * The window object.
     */
    public static final Element window = window();

    private static Element windowData = null;

    private static JsNamedArray<TagWrapper> wrapperMap;

    /**
     * Create an empty GQuery object.
     */
    public static GQuery $() {
        return new GQuery(JsNodeArray.create());
    }

    /**
     * Wrap a GQuery around an existing element.
     */
    public static GQuery $(Element element) {
        return new GQuery(element);
    }

    /**
     * Wrap a GQuery around an event's target element.
     */
    public static GQuery $(Event event) {
        return event == null ? $() : $((Element) event.getCurrentEventTarget().cast());
    }

    /**
     * Wrap a GQuery around the element of a Function callback.
     */
    public static GQuery $(Function f) {
        return $(f.getElement());
    }

    /**
     * Wrap a GQuery around an existing element, event, node or nodelist.
     */
    public static GQuery $(JavaScriptObject e) {
        return JsUtils.isWindow(e) ? GQuery.$(e.<Element>cast())
                : JsUtils.isElement(e) ? GQuery.$(e.<Element>cast())
                        : JsUtils.isEvent(e) ? GQuery.$(e.<Event>cast())
                                : JsUtils.isNodeList(e) ? GQuery.$(e.<NodeList<Element>>cast()) : $();
    }

    /**
     * Create a new GQuery given a list of nodes, elements or widgets
     */
    public static GQuery $(List<?> nodesOrWidgets) {
        JsNodeArray elms = JsNodeArray.create();
        if (nodesOrWidgets != null) {
            for (Object o : nodesOrWidgets) {
                if (o instanceof Node) {
                    elms.addNode((Node) o);
                } else if (o instanceof Widget) {
                    elms.addNode(((Widget) o).getElement());
                }
            }
        }
        return new GQuery(elms);
    }

    /**
     * Wrap a GQuery around an existing node.
     */
    public static GQuery $(Node n) {
        return $((Element) n);
    }

    /**
     * Wrap a GQuery around existing Elements.
     */
    public static GQuery $(NodeList<Element> elms) {
        return new GQuery(elms);
    }

    /**
     * This function accepts a string containing a CSS selector which is then used
     * to match a set of elements, or it accepts raw HTML creating a GQuery
     * element containing those elements.
     * Xpath selector is supported in browsers with native xpath engine.
     */
    public static GQuery $(String selectorOrHtml) {
        return $(selectorOrHtml, document);
    }

    /**
     * This function accepts a string containing a CSS selector which is then used
     * to match a set of elements, or it accepts raw HTML creating a GQuery
     * element containing those elements. The second parameter is is a class
     * reference to a plugin to be used.
     * 
     * Xpath selector is supported in browsers with native xpath engine.
     */
    public static <T extends GQuery> T $(String selector, Class<T> plugin) {
        return $(selector, document, plugin);
    }

    /**
     * This function accepts a string containing a CSS selector which is then used
     * to match a set of elements, or it accepts raw HTML creating a GQuery
     * element containing those elements. The second parameter is the context to
     * use for the selector, or the document where the new elements will be
     * created.
     * 
     * Xpath selector is supported in browsers with native xpath engine.
     */
    public static GQuery $(String selectorOrHtml, Node ctx) {
        String selector = null;
        if (selectorOrHtml == null || (selector = selectorOrHtml.trim()).length() == 0) {
            return $();
        }
        if (selector.startsWith("<")) {
            return innerHtml(selectorOrHtml, JsUtils.getOwnerDocument(ctx));
        }
        return new GQuery().select(selectorOrHtml, ctx);
    }

    /**
    * This function accepts a string containing a CSS selector which is then used
    * to match a set of elements, or it accepts raw HTML creating a GQuery
    * element containing those elements. The second parameter is the context to
    * use for the selector. The third parameter is the class plugin to use.
    * 
    * Xpath selector is supported in browsers with native xpath engine.
    */
    @SuppressWarnings("unchecked")
    public static <T extends GQuery> T $(String selector, Node context, Class<T> plugin) {
        try {
            if (plugins != null) {
                T gquery = (T) plugins.get(plugin).init(new GQuery().select(selector, context));
                return gquery;
            }
            throw new RuntimeException("No plugin for class " + plugin);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
    * This function accepts a string containing a CSS selector which is then used
    * to match a set of elements, or it accepts raw HTML creating a GQuery
    * element containing those elements. The second parameter is the context to
    * use for the selector, or the document where the new elements will be
    * created.
    * 
    * Xpath selector is supported in browsers with native xpath engine.
    */
    public static GQuery $(String selectorOrHtml, Widget context) {
        return $(selectorOrHtml, context.getElement());
    }

    /**
     * This function accepts a string containing a CSS selector which is then used
     * to match a set of elements, or it accepts raw HTML creating a GQuery
     * element containing those elements. The second parameter is the context to
     * use for the selector. The third parameter is the class plugin to use.
     *
     * Xpath selector is supported in browsers with native xpath engine.
     */
    public static <T extends GQuery> T $(String selector, Widget context, Class<T> plugin) {
        return $(selector, context.getElement(), plugin);
    }

    /**
    * wraps a GQuery or a plugin object
    */
    public static <T extends GQuery> T $(T gq) {
        return gq;
    }

    /**
     * Wrap a GQuery around one widget or an array of existing ones.
     */
    public static GQuery $(Widget... widgets) {
        return $(Arrays.asList(widgets));
    }

    /**
     * Wrap a JSON object.
     */
    public static Properties $$(String properties) {
        return Properties.create(properties);
    }

    /**
     * Perform an ajax request to the server.
     */
    public static void ajax(Properties p) {
        ajax(p);
    }

    /**
     * Perform an ajax request to the server.
     */
    public static void ajax(Settings settings) {
        Ajax.ajax(settings);
    }

    /**
     * Perform an ajax request to the server.
     */
    public static void ajax(String url, Settings settings) {
        Ajax.ajax(url, settings);
    }

    @SuppressWarnings("unchecked")
    protected static GQuery cleanHtmlString(String elem, Document doc) {

        String tag = tagNameRegex.exec(elem).get(1);

        if (tag == null) {
            throw new RuntimeException("HTML snippet doesn't contain any tag");
        }

        if (wrapperMap == null) {
            initWrapperMap();
        }

        TagWrapper wrapper = wrapperMap.get(tag.toLowerCase());

        if (wrapper == null) {
            wrapper = TagWrapper.DEFAULT;
        }

        // TODO: fix IE link tag serialization
        // TODO: fix IE <script> tag
        Element div = doc.createDivElement();
        div.setInnerHTML(wrapper.preWrap + elem.trim() + wrapper.postWrap);
        Node n = div;
        int depth = wrapper.wrapDepth;
        while (depth-- != 0) {
            n = n.getLastChild();
        }
        // TODO: add fixes for IE TBODY issue
        return $((NodeList<Element>) n.getChildNodes().cast());
    }

    /**
      * Return true if the element b is contained in a. 
      */
    public static boolean contains(Element a, Element b) {
        return engine.contains(a, b);
    }

    /**
      * Get the element data matching the key. 
      */
    public static Object data(Element e, String key) {
        return GQuery.data(e, key, null);
    }

    protected static <S> Object data(Element item, String name, S value) {
        if (dataCache == null) {
            windowData = JavaScriptObject.createObject().cast();
            dataCache = JavaScriptObject.createObject().cast();
        }
        item = item == window || item.getNodeName() == null ? windowData : item;
        if (item == null) {
            return value;
        }
        int id = item.hashCode();
        if (name != null && !dataCache.exists(id)) {
            dataCache.put(id, JsCache.createObject().cast());
        }

        JsCache d = dataCache.getCache(id);
        if (name != null && value != null) {
            d.put(name, value);
        }
        return name != null ? d.get(name) : id;
    }

    /**
     * Execute a function around each object
     */
    public static void each(JsArrayMixed objects, Function f) {
        for (int i = 0, l = objects.length(); i < l; i++) {
            f.f(i, objects.getObject(i));
        }
    }

    /**
      * Execute a function around each object
      */
    public static <T> void each(List<T> objects, Function f) {
        for (int i = 0, l = objects.size(); i < l; i++) {
            f.f(i, objects.get(i));
        }
    }

    /**
     * Execute a function around each object
     */
    public static <T> void each(T[] objects, Function f) {
        for (int i = 0, l = objects.length; i < l; i++) {
            f.f(i, objects[i]);
        }
    }

    /**
     * Perform an ajax request to the server using GET.
     */
    public static void get(String url, Properties data, final Function onSuccess) {
        Ajax.get(url, data, onSuccess);
    }

    /**
     * We will use the fact as GWT use the widget itself as EventListener ! If no
     * Widget associated with the element, this method returns null.
     */
    protected static Widget getAssociatedWidget(Element e) {
        try {
            EventListener listener = DOM.getEventListener((com.google.gwt.user.client.Element) e);
            // No listener attached to the element, so no widget exist for this element
            if (listener == null) {
                return null;
            }
            if (listener instanceof Widget) {
                // GWT uses the widget as event listener
                return (Widget) listener;
            } else if (listener instanceof EventsListener) {
                // GQuery replaces the gwt event listener and save it
                EventsListener gQueryListener = (EventsListener) listener;
                if (gQueryListener.getOriginalEventListener() != null
                        && gQueryListener.getOriginalEventListener() instanceof Widget) {
                    return (Widget) gQueryListener.getOriginalEventListener();
                }
            }
        } catch (Exception e2) {
            // Some times this code could raise an exception. 
            // We do not want GQuery to fail, but in dev-move we log the error.
            e2.printStackTrace();
        }
        return null;
    }

    private static AttributeImpl getAttributeImpl() {
        if (attributeImpl == null) {
            attributeImpl = GWT.create(AttributeImpl.class);
        }
        return attributeImpl;
    }

    /**
     * Perform an ajax request to the server using POST and
     * parsing the json response.
     */
    public static void getJSON(String url, Properties data, final Function onSuccess) {
        Ajax.getJSON(url, data, onSuccess);
    }

    /**
     * Perform an ajax request to the server using scripts tags and
     * parsing the json response. The request is not subject to the 
     * same origin policy restrictions.
     * 
     * Server side should accept a parameter to specify the callback
     * funcion name, and it must return a valid json object wrapped
     * this callback function.
     * 
     * Example:
     <pre>
      Client code:
      getJSONP("http://server.exampe.com/getData.php",$$("myCallback:'?', otherParameter='whatever'"), 
        new Function(){ public void f() {
    Properties p = getDataProperties();
    alert(p.getStr("k1");
      }});
         
      Server response:
      myCallback({"k1":"v1", "k2":"v2"});
     </pre>
     * 
     */
    public static void getJSONP(String url, Properties data, final Function onSuccess) {
        Ajax.getJSONP(url, data, onSuccess);
    }

    protected static DocumentStyleImpl getStyleImpl() {
        if (styleImpl == null) {
            styleImpl = GWT.create(DocumentStyleImpl.class);
        }
        return styleImpl;
    }

    /**
     * Return only the set of objects with match the predicate.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] grep(T[] objects, Predicate f) {
        ArrayList<Object> ret = new ArrayList<Object>();
        for (int i = 0, l = objects.length; i < l; i++) {
            if (f.f(objects[i], i)) {
                ret.add(objects[i]);
            }
        }
        return (T[]) ret.toArray(new Object[0]);
    }

    private static boolean hasClass(Element e, String clz) {
        return e.getClassName().matches("(^|.*\\s)" + clz + "(\\s.*|$)");
    }

    private static void initWrapperMap() {

        TagWrapper tableWrapper = new TagWrapper(1, "<table>", "</table>");
        TagWrapper selectWrapper = new TagWrapper(1, "<select multiple=\"multiple\">", "</select>");
        TagWrapper trWrapper = new TagWrapper(3, "<table><tbody><tr>", "</tr></tbody></table>");

        wrapperMap = JsNamedArray.create();
        wrapperMap.put("option", selectWrapper);
        wrapperMap.put("optgroup", selectWrapper);
        wrapperMap.put("legend", new TagWrapper(1, "<fieldset>", "</fieldset>"));
        wrapperMap.put("thead", tableWrapper);
        wrapperMap.put("tbody", tableWrapper);
        wrapperMap.put("tfoot", tableWrapper);
        wrapperMap.put("colgroup", tableWrapper);
        wrapperMap.put("caption", tableWrapper);
        wrapperMap.put("tr", new TagWrapper(2, "<table><tbody>", "</tbody></table>"));
        wrapperMap.put("td", trWrapper);
        wrapperMap.put("th", trWrapper);
        wrapperMap.put("col", new TagWrapper(2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"));
        wrapperMap.put("area", new TagWrapper(1, "<map>", "</map>"));

    }

    private static GQuery innerHtml(String html, Document doc) {
        return $(cleanHtmlString(html, doc));
    }

    protected static String[] jsArrayToString(JsArrayString array) {
        if (GWT.isScript()) {
            return jsArrayToString0(array);
        } else {
            String result[] = new String[array.length()];
            for (int i = 0, l = result.length; i < l; i++) {
                result[i] = array.get(i);
            }
            return result;
        }
    }

    private static native String[] jsArrayToString0(JsArrayString array) /*-{
                                                                         return array;
                                                                         }-*/;

    /**
     * Return a lazy version of the GQuery interface. Lazy function calls are
     * simply queued up and not executed immediately.
     */
    public static LazyGQuery<?> lazy() {
        return $().createLazy();
    }

    /**
     * Perform an ajax request to the server using POST.
     */
    public static void post(String url, Properties data, final Function onSuccess) {
        Ajax.post(url, data, onSuccess);
    }

    public static <T extends GQuery> Class<T> registerPlugin(Class<T> plugin, Plugin<T> pluginFactory) {
        if (plugins == null) {
            plugins = JsMap.createObject().cast();
        }

        plugins.put(plugin, pluginFactory);
        return plugin;
    }

    private static native void scrollIntoViewImpl(Node n) /*-{
                                                          if (n)
                                                          n.scrollIntoView()
                                                          }-*/;

    private static native void setElementValue(Element e, String value) /*-{
                                                                        e.value = value;
                                                                        }-*/;

    private static native Element window() /*-{
                                           return $wnd;
                                           }-*/;

    protected Node currentContext;

    protected String currentSelector;
    /**
     * Immutable array of matched elements, modify this using setArray
     */
    private Element[] elements = new Element[0];

    /**
     * The nodeList of matched elements, modify this using setArray
     */
    // TODO: remove this and use elements, change return type of get()
    private NodeList<Element> nodeList = JavaScriptObject.createArray().cast();

    private GQuery previousObject;

    private GQuery() {
    }

    private GQuery(Element element) {
        this(JsNodeArray.create(element));
    }

    protected GQuery(GQuery gq) {
        this(gq == null ? null : gq.get());
        currentSelector = gq.getSelector();
        currentContext = gq.getContext();
    }

    private GQuery(JsNodeArray nodes) {
        this(nodes.<NodeList<Element>>cast());
    }

    private GQuery(NodeList<Element> list) {
        setArray(list);
    }

    /**
     * Add elements to the set of matched elements if they are not included yet.
     * 
     * It construct a new GQuery object and does not modify the original ones.
     * 
     * It also update the selector appending the new one.
     */
    public GQuery add(GQuery elementsToAdd) {
        return pushStack(JsUtils.copyNodeList(nodeList, elementsToAdd.nodeList, true).<JsNodeArray>cast(), "add",
                getSelector() + "," + elementsToAdd.getSelector());
    }

    /**
     * Add elements to the set of matched elements if they are not included yet.
     */
    public GQuery add(String selector) {
        return add($(selector));
    }

    /**
     * Adds the specified classes to each matched element.
     */
    public GQuery addClass(String... classes) {
        for (Element e : elements) {
            //issue 81 : ensure that the element is an Element node.
            if (Element.is(e)) {
                for (String clz : classes) {
                    e.addClassName(clz);
                }
            }
        }
        return this;
    }

    /**
     * Insert content after each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element after
     * another if it's not in the page).
     */
    public GQuery after(GQuery query) {
        return domManip(query, DomMan.AFTER);
    }

    /**
     * Insert content after each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element after
     * another if it's not in the page).
     */
    public GQuery after(Node n) {
        return domManip($(n), DomMan.AFTER);
    }

    /**
     * Insert content after each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element after
     * another if it's not in the page).
     */
    public GQuery after(String html) {
        return domManip(html, DomMan.AFTER);
    }

    private void allNextSiblingElements(Element firstChildElement, JsNodeArray result, Element elem,
            String untilSelector) {
        while (firstChildElement != null) {

            if (untilSelector != null && $(firstChildElement).is(untilSelector)) {
                return;
            }

            if (firstChildElement != elem) {
                result.addNode(firstChildElement);
            }
            firstChildElement = firstChildElement.getNextSiblingElement();
        }
    }

    private void allPreviousSiblingElements(Element firstChildElement, JsNodeArray result, String untilSelector) {
        while (firstChildElement != null) {
            if (untilSelector != null && $(firstChildElement).is(untilSelector)) {
                return;
            }
            result.addNode(firstChildElement);
            firstChildElement = getPreviousSiblingElement(firstChildElement);
        }
    }

    /**
     * Add the previous selection to the current selection. Useful for traversing
     * elements, and then adding something that was matched before the last
     * traversal.
     */
    public GQuery andSelf() {
        return add(previousObject);
    }

    /**
     * 
     * The animate() method allows you to create animation effects on any numeric
     * Attribute, CSS property, or color CSS property.
     * 
     * Concerning to numeric properties, values are treated as a number of pixels
     * unless otherwise specified. The units em and % can be specified where
     * applicable.
     * 
     * By default animate considers css properties, if you wanted to animate element
     * attributes you should to prepend the symbol dollar to the attribute name.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to left:500px for 500ms
     *  $("#foo").animate("left:'500'");
     *  // Change the width attribute of a table
     *  $("table").animate("$width:'500'"), 400, Easing.LINEAR);
     * </pre>
     * 
     * In addition to numeric values, each property can take the strings 'show',
     * 'hide', and 'toggle'. These shortcuts allow for custom hiding and showing
     * animations that take into account the display type of the element. Animated
     * properties can also be relative. If a value is supplied with a leading +=
     * or -= sequence of characters, then the target value is computed by adding
     * or subtracting the given number from the current value of the property.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to 500px to the left for 500ms and
     *  // change the background color of the element at the end of the animation
     *  $("#foo").animate("left:'+=500'", new Function(){
     *                  
     *                 public void f(Element e){
     *                   $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED);
     *                 }
     *                 
     *              });
     * </pre>
     * 
     * The duration of the animation is 500ms.
     * 
     * For color css properties, values can be specified via hexadecimal or rgb or
     * literal values.
     * 
     * Example:
     * 
     * <pre class="code">
     *  $("#foo").animate("backgroundColor:'red', color:'#ffffff', borderColor:'rgb(129, 0, 70)'");
     * </pre>
     * 
     * @param prop the property to animate : "cssName:'value'"
     * @param funcs an array of {@link Function} called once the animation is
     *          complete
     */
    public GQuery animate(Object stringOrProperties, Function... funcs) {
        return as(Effects).animate(stringOrProperties, funcs);
    }

    /**
     * The animate() method allows you to create animation effects on any numeric
     * Attribute, CSS property, or color CSS property.
     * 
     * Concerning to numeric properties, values are treated as a number of pixels
     * unless otherwise specified. The units em and % can be specified where
     * applicable.
     * 
     * By default animate considers css properties, if you wanted to animate element
     * attributes you should to prepend the symbol dollar to the attribute name.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to the position top:500px and left:500px for 400ms.
     *  //use a swing easing function for the transition
     *  $("#foo").animate(Properties.create("{top:'500px',left:'500px'}"), 400, Easing.SWING);
     *  // Change the width and border attributes of a table
     *  $("table").animate(Properties.create("{$width: '500', $border: '10'}"), 400, Easing.LINEAR);
     * </pre>
     * 
     * In addition to numeric values, each property can take the strings 'show',
     * 'hide', and 'toggle'. These shortcuts allow for custom hiding and showing
     * animations that take into account the display type of the element. Animated
     * properties can also be relative. If a value is supplied with a leading +=
     * or -= sequence of characters, then the target value is computed by adding
     * or subtracting the given number from the current value of the property.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to 500px to the left and 5OOpx down for 400ms.
     *  //use a swing easing function for the transition
     *  $("#foo").animate(Properties.create("{top:'+=500px',left:'+=500px'}"), 400, Easing.SWING);
     * </pre>
     * 
     * For color css properties, values can be specified via hexadecimal or rgb or
     * literal values.
     * 
     * Example:
     * 
     * <pre class="code">
     *  $("#foo").animate("backgroundColor:'red', color:'#ffffff', borderColor:'rgb(129, 0, 70)'"), 400, Easing.SWING);
     * </pre>
     * 
     * @param stringOrProperties a String or a {@link Properties} object containing css properties to animate.
     * @param funcs an array of {@link Function} called once the animation is
     *          complete
     * @param duration the duration in milliseconds of the animation
     * @param easing the easing function to use for the transition
     */
    public GQuery animate(Object stringOrProperties, int duration, Easing easing, Function... funcs) {
        return as(Effects).animate(stringOrProperties, duration, easing, funcs);
    }

    /**
     * The animate() method allows you to create animation effects on any numeric
     * Attribute, CSS properties, or color CSS property.
     * 
     * Concerning to numeric property, values are treated as a number of pixels
     * unless otherwise specified. The units em and % can be specified where
     * applicable.
     * 
     * By default animate considers css properties, if you wanted to animate element
     * attributes you should to prepend the symbol dollar to the attribute name.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to left:500px for 2s
     *  $("#foo").animate("left:'500px'", 2000);
     *  // Change the width attribute of a table
     *  $("table").animate("$width:'500'"), 400);
     * </pre>
     * 
     * In addition to numeric values, each property can take the strings 'show',
     * 'hide', and 'toggle'. These shortcuts allow for custom hiding and showing
     * animations that take into account the display type of the element. Animated
     * properties can also be relative. If a value is supplied with a leading +=
     * or -= sequence of characters, then the target value is computed by adding
     * or subtracting the given number from the current value of the property.
     * 
     * Example:
     * 
     * <pre class="code">
     *  //move the element from its original position to 500px to the left for 1000ms and
     *  // change the background color of the element at the end of the animation
     *  $("#foo").animate("left:'+=500'", 1000, new Function(){
     *     public void f(Element e){
     *       $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED);
     *     }
     *  });
     * </pre>
     * 
     * 
     * For color css properties, values can be specified via hexadecimal or rgb or
     * literal values.
     * 
     * Example:
     * 
     * <pre class="code">
     *  $("#foo").animate("backgroundColor:'red', color:'#ffffff', borderColor:'rgb(129, 0, 70)', 1000");
     * </pre>
     * 
     * 
     * @param prop the property to animate : "cssName:'value'"
     * @param funcs an array of {@link Function} called once the animation is
     *          complete
     * @param duration the duration in milliseconds of the animation
     */
    public GQuery animate(Object stringOrProperties, int duration, Function... funcs) {
        return as(Effects).animate(stringOrProperties, duration, funcs);
    }

    /**
     * Append content to the inside of every matched element. This operation is
     * similar to doing an appendChild to all the specified elements, adding them
     * into the document.
     */
    public GQuery append(GQuery query) {
        return domManip(query, DomMan.APPEND);
    }

    /**
     * Append content to the inside of every matched element. This operation is
     * similar to doing an appendChild to all the specified elements, adding them
     * into the document.
     */
    public GQuery append(Node n) {
        return domManip($(n), DomMan.APPEND);
    }

    /**
     * Append content to the inside of every matched element. This operation is
     * similar to doing an appendChild to all the specified elements, adding them
     * into the document.
     */
    public GQuery append(String html) {
        return domManip(html, DomMan.APPEND);
    }

    /**
     * All of the matched set of elements will be inserted at the end of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).appendTo(B) is, essentially, the reverse of doing a
     * regular $(A).append(B), instead of appending B to A, you're appending A to
     * B.
     */
    public GQuery appendTo(GQuery other) {
        other.append(this);
        return this;
    }

    /**
     * All of the matched set of elements will be inserted at the end of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).appendTo(B) is, essentially, the reverse of doing a
     * regular $(A).append(B), instead of appending B to A, you're appending A to
     * B.
     */
    public GQuery appendTo(Node n) {
        GQuery a = $(n);
        GQuery b = this;
        a.append(b);
        return this;
    }

    /**
     * All of the matched set of elements will be inserted at the end of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).appendTo(B) is, essentially, the reverse of doing a
     * regular $(A).append(B), instead of appending B to A, you're appending A to
     * B.
     */
    public GQuery appendTo(String html) {
        $(html).append(this);
        return this;
    }

    /**
     * Convert to Plugin interface provided by Class literal.
     */
    @SuppressWarnings("unchecked")
    public <T extends GQuery> T as(Class<T> plugin) {
        // GQuery is not a plugin for itself
        if (plugin == GQUERY) {
            return (T) $(this);
        } else if (plugins != null) {

            Plugin<?> p = plugins.get(plugin);
            if (p != null) {
                return (T) p.init(this);
            }
        }
        throw new RuntimeException("No plugin registered for class " + plugin.getName());
    }

    /**
     * Set a key/value object as properties to all matched elements.
     * 
     * Example: $("img").attr(new
     * Properties("src: 'test.jpg', alt: 'Test Image'"))
     */
    public GQuery attr(Properties properties) {
        for (String name : properties.keys()) {
            attr(name, properties.getStr(name));
        }
        return this;
    }

    /**
     * Access a property on the first matched element. This method makes it easy
     * to retrieve a property value from the first matched element. If the element
     * does not have an attribute with such a name, empty string is returned.
     * Attributes include title, alt, src, href, width, style, etc.
     */
    public String attr(String name) {
        return isEmpty() ? "" : get(0).getAttribute(name);
    }

    /**
     * Set a single property to a computed value, on all matched elements.
     */
    public GQuery attr(String key, Function closure) {
        int i = 0;
        for (Element e : elements) {
            Object val = closure.f(e.<com.google.gwt.dom.client.Element>cast(), i++);
            $(e).attr(key, val);
        }
        return this;
    }

    /**
     * Set a single property to a value, on all matched elements.
     */
    public GQuery attr(String key, Object value) {
        assert key != null : "key cannot be null";
        assert !"$H".equalsIgnoreCase(
                key) : "$H is a GWT reserved attribute. Changing its value will break your application.";

        getAttributeImpl().setAttribute(this, key, value);

        return this;
    }

    /**
     * Insert content before each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element before
     * another if it's not in the page).
     */
    public GQuery before(GQuery query) {
        return domManip(query, DomMan.BEFORE);
    }

    /**
     * Insert content before each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element before
     * another if it's not in the page).
     */
    public GQuery before(Node n) {
        return domManip($(n), DomMan.BEFORE);
    }

    /**
     * Insert content before each of the matched elements. The elements must
     * already be inserted into the document (you can't insert an element before
     * another if it's not in the page).
     */
    public GQuery before(String html) {
        return domManip(html, DomMan.BEFORE);
    }

    /**
     * Binds a set of handlers to a particular Event for each matched element.
     * 
     * The event handlers are passed as Functions that you can use to prevent
     * default behavior. To stop both default action and event bubbling, the
     * function event handler has to return false.
     * 
     * You can pass an additional Object data to your Function as the second
     * parameter
     * 
     */
    public GQuery bind(int eventbits, final Object data, final Function... funcs) {
        return as(Events).bind(eventbits, data, funcs);
    }

    /**
     * Binds a set of handlers to a particular Event for each matched element.
     * 
     * The event handlers are passed as Functions that you can use to prevent
     * default behavior. To stop both default action and event bubbling, the
     * function event handler has to return false.
     * 
     * You can pass an additional Object data to your Function as the second
     * parameter
     * 
     */
    public GQuery bind(String eventType, final Object data, final Function... funcs) {
        return as(Events).bind(eventType, data, funcs);
    }

    /**
     * Bind Handlers or fire Events for each matched element.
     */
    private GQuery bindOrFire(int eventbits, final Object data, final Function... funcs) {
        if (funcs.length == 0) {
            return trigger(eventbits);
        } else {
            return bind(eventbits, data, funcs);
        }
    }

    /**
     * Bind a set of functions to the blur event of each matched element. 
     * Or trigger the blur event if no functions are provided.
     */
    public GQuery blur(Function... f) {
        bindOrFire(Event.ONBLUR, null, f);
        if (!isEmpty() && f.length == 0) {
            get(0).blur();
        }
        return this;
    }

    /**
     * Bind a set of functions to the change event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery change(Function... f) {
        return bindOrFire(Event.ONCHANGE, null, f);
    }

    /**
     * Get a set of elements containing all of the unique immediate children of
     * each of the matched set of elements. Also note: while parents() will look
     * at all ancestors, children() will only consider immediate child elements.
     */
    public GQuery children() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allNextSiblingElements(e.getFirstChildElement(), result, null, null);
        }
        return new GQuery(unique(result));
    }

    /**
     * Get a set of elements containing all of the unique children of each of the
     * matched set of elements. This set is filtered with the expressions that
     * will cause only elements matching any of the selectors to be collected.
     */
    public GQuery children(String... filters) {
        return children().filter(filters);
    }

    private void cleanGQData(Element... elements) {
        for (Element el : elements) {
            try {
                EventsListener.clean(el);
                removeData(el, null);
            } catch (Exception e) {
                // If for some reason event/data removal fails, do not break the app,
                // just log the error in dev-mode
                // e.g.: this happens when removing iframes which are no fully loaded.
                e.printStackTrace();
            }
        }
    }

    /**
     * Remove from the Effects queue all {@link Function} that have not yet been
     * run.
     */
    public GQuery clearQueue() {
        return as(Queue).clearQueue();
    }

    /**
     * Remove from the queue all {@link Function} that have not yet been run.
     */
    public GQuery clearQueue(String queueName) {
        return as(Queue).clearQueue(queueName);
    }

    /**
     * Bind a set of functions to the click event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery click(Function... f) {
        return bindOrFire(Event.ONCLICK, null, f);
    }

    /**
     * Clone matched DOM Elements and select the clones. This is useful for moving
     * copies of the elements to another location in the DOM.
     */
    public GQuery clone() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            result.addNode(e.cloneNode(true));
        }
        GQuery ret = new GQuery(result);
        ret.currentContext = currentContext;
        ret.currentSelector = currentSelector;
        return ret;
    }

    /**
     * Get the first ancestor element that matches the selector (for each matched
     * element), beginning at the current element and progressing up through the
     * DOM tree.
     * 
     * @param selector
     * @return
     */
    public GQuery closest(String selector) {
        return closest(selector, null);
    }

    /**
     * Get the first ancestor element that matches the selector (for each matched
     * element), beginning at the current element and progressing up through the
     * DOM tree until reach the <code>context</code> node.
     * 
     * If no context is passed in then the context of the gQuery object will be
     * used instead.
     * 
     */
    public GQuery closest(String selector, Node context) {
        assert selector != null;

        if (context == null) {
            context = currentContext;
        }

        GQuery pos = selector.matches(POS_REGEX) ? $(selector, context) : null;
        JsNodeArray result = JsNodeArray.create();

        for (Element e : elements) {
            Element current = e;
            while (current != null && current.getOwnerDocument() != null && current != context) {
                boolean match = pos != null ? pos.index(current) > -1 : $(current).is(selector);
                if (match) {
                    result.addNode(current);
                    break;
                } else {
                    current = current.getParentElement();
                }
            }
        }

        return $(unique(result));

    }

    /**
     * Returns a {@link Map} object as key a selector and as value the list of
     * ancestor elements matching this selectors, beginning at the first matched
     * element and progressing up through the DOM. This method allows retrieving
     * the list of ancestors matching many selectors by traversing the DOM only
     * one time.
     * 
     * @param selector
     * @return
     */
    public JsNamedArray<NodeList<Element>> closest(String[] selectors) {
        return closest(selectors, null);
    }

    /**
     * Returns a {@link Map} object as key a selector and as value the list of
     * ancestor elements matching this selectors, beginning at the first matched
     * element and progressing up through the DOM until reach the
     * <code>context</code> node.. This method allows retrieving the list of
     * ancestors matching many selectors by traversing the DOM only one time.
     * 
     * @param selector
     * @return
     */
    public JsNamedArray<NodeList<Element>> closest(String[] selectors, Node context) {
        JsNamedArray<NodeList<Element>> results = JsNamedArray.create();

        if (context == null) {
            context = currentContext;
        }

        Element first = get(0);
        if (first != null && selectors != null && selectors.length > 0) {
            JsNamedArray<GQuery> matches = JsNamedArray.create();
            for (String selector : selectors) {
                if (!matches.exists(selector)) {
                    matches.put(selector, selector.matches(POS_REGEX) ? $(selector, context) : null);
                }
            }

            Element current = first;
            while (current != null && current.getOwnerDocument() != null && current != context) {
                // for each selector, check if the current element match it.
                for (String selector : matches.keys()) {

                    GQuery pos = matches.get(selector);
                    boolean match = pos != null ? pos.index(current) > -1 : $(current).is(selector);

                    if (match) {
                        JsNodeArray elementsMatchingSelector = results.get(selector).cast();
                        if (elementsMatchingSelector == null) {
                            elementsMatchingSelector = JsNodeArray.create();
                            results.put(selector, elementsMatchingSelector);
                        }
                        elementsMatchingSelector.addNode(current);
                    }
                }

                current = current.getParentElement();
            }
        }
        return results;
    }

    /**
     * Filter the set of elements to those that contain the specified text.
     */
    public GQuery contains(String text) {
        JsNodeArray array = JsNodeArray.create();
        for (Element e : elements) {
            if ($(e).text().contains(text)) {
                array.addNode(e);
            }
        }
        return $(array);
    }

    /**
     * Find all the child nodes inside the matched elements (including text
     * nodes), or the content document, if the element is an iframe.
     */
    public GQuery contents() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            if (JsUtils.isWindow(e) || "iframe".equalsIgnoreCase(e.getTagName())) {
                result.addNode(styleImpl.getContentDocument(e));
            } else {
                NodeList<Node> children = e.getChildNodes();
                for (int i = 0, l = children.getLength(); i < l; i++) {
                    result.addNode(children.getItem(i));
                }
            }
        }
        return new GQuery(unique(result));
    }

    public LazyGQuery<?> createLazy() {
        return GWT.create(GQuery.class);
    }

    /**
     * Set CSS a single style property on every matched element using type-safe
     * enumerations.
     * 
     * The best way to use this method (i.e. to generate a CssSetter) is to take
     * the desired css property defined in {@link CSS} class and call the
     * {@link TakesCssValue#with(HasCssName)} method on it.
     * 
     * 
     * ex :
     * 
     * <pre class="code">
     * $("#myDiv").css(CSS.TOP.with(Length.cm(15)));
     * $("#myDiv").css(CSS.BACKGROUND.with(RGBColor.SILVER, ImageValue.url(""),
     *               BackgroundRepeat.NO_REPEAT, BackgroundAttachment.FIXED,
     *               BackgroundPosition.CENTER));
     * $("#myDiv").css(CSS.BACKGROUND_ATTACHMENT.with(BackgroundAttachment.FIXED));
     * 
     * </pre>
     * 
     */
    public GQuery css(CssSetter... cssSetter) {
        for (Element e : elements) {
            for (CssSetter s : cssSetter) {
                s.applyCss(e);
            }
        }
        return this;
    }

    /**
     * Return a style property on the first matched element using type-safe
     * enumerations.
     * 
     * Ex : $("#myId").css(CSS.BACKGROUND_COLOR);
     */
    public String css(HasCssValue property) {
        return css(property, true);
    }

    /**
     * Return a style property on the first matched element using type-safe
     * enumerations.
     * 
     * The parameter force has a special meaning here: - When force is false,
     * returns the value of the css property defined in the style attribute of the
     * element. - Otherwise it returns the real computed value.
     * 
     * For instance if you define 'display=none' not in the element style but in
     * the css stylesheet, it returns an empty string unless you pass the
     * parameter force=true.
     * 
     * Ex : $("#myId").css(CSS.WIDTH, true);
     */
    public String css(HasCssValue property, boolean force) {
        return css(property.getCssName(), force);
    }

    /**
     * Set a key/value object as style properties to all matched elements. This
     * serves as the best way to set a large number of style properties on all
     * matched elements. You can use either js maps or pure css syntax.
     * 
     * Example:
     * 
     * <pre class="code">
     *  $(".item").css(Properties.create("color: 'red', background:'blue'"))
     *  $(".item").css(Properties.create("color: red; background: blue;"))
     * </pre>
     */
    public GQuery css(Properties properties) {
        for (String property : properties.keys()) {
            css(property, properties.getStr(property));
        }
        return this;
    }

    /**
     * Return a style property on the first matched element.
     */
    public String css(String name) {
        return css(name, true);
    }

    /**
     * Return a style property on the first matched element.
     * 
     * The parameter force has a special meaning here: 
     * <ul>
     * <li>When force is false, returns the value of the css property 
     * defined in the style attribute of the element. 
     * <li>Otherwise it returns the real computed value.
     * </ul>
     * 
     * For instance if you don't define 'display=none'in the element style but in
     * the css stylesheet, it returns an empty string unless you pass the
     * parameter force=true.
     */
    public String css(String name, boolean force) {
        return isEmpty() ? "" : getStyleImpl().curCSS(get(0), name, force);
    }

    /**
     * Set a single style property to a value, on all matched elements.
     * 
     */
    public GQuery css(String prop, String val) {
        for (Element e : elements) {
            getStyleImpl().setStyleProperty(e, prop, val);
        }
        return this;
    }

    /**
     * Set CSS a single style property on every matched element using type-safe
     * enumerations. This method allows you to set manually the value or set
     * <i>inherit</i> value
     * 
     * ex :
     * 
     * <pre class="code">
     * $(#myId).css(CSS.TEXT_DECORATION, CSS.INHERIT);
     * </pre>
     */
    public GQuery css(TakesCssValue<?> cssProperty, String value) {
        return css(cssProperty.getCssName(), value);
    }

    /**
     * Returns the numeric value of a css property.
     */
    public double cur(String prop) {
        return cur(prop, false);
    }

    /**
     * Returns the numeric value of a css property.
     * 
     * The parameter force has a special meaning: - When force is false, returns
     * the value of the css property defined in the set of style attributes. -
     * When true returns the real computed value.
     */
    public double cur(String prop, boolean force) {
        return isEmpty() ? 0 : getStyleImpl().cur(get(0), prop, force);
    }

    /**
     * Returns value at named data store for the element, as set by data(name,
     * value).
     */
    public Object data(String name) {
        return isEmpty() ? null : data(get(0), name, null);
    }

    /**
     * Returns value at named data store for the element, as set by data(name,
     * value) with desired return type.
     * 
     * @param clz return type class literal
     */
    @SuppressWarnings("unchecked")
    public <T> T data(String name, Class<T> clz) {
        return isEmpty() ? null : (T) data(get(0), name, null);
    }

    /**
     * Stores the value in the named spot with desired return type.
     */
    public GQuery data(String name, Object value) {
        for (Element e : elements()) {
            data(e, name, value);
        }
        return this;
    }

    /**
     * Bind a set of functions to the dblclick event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery dblclick(Function... f) {
        return bindOrFire(Event.ONDBLCLICK, null, f);
    }

    /**
     * Insert a delay (in ms) in the GQuery queue, and optionally execute one o
     * more functions if provided when the delay finishes.
     * It uses the effects queue namespace, so you can stack any of the methods in the effects
     * plugin.
     * 
     * Example:
     * 
     * <pre class="code">
     * $("#foo").slideUp(300)
     *          .delay(800)
     *          .fadeIn(400); 
     * </pre>
     * 
     * When this statement is executed, the element slides up for 300 milliseconds
     * and then pauses for 800 milliseconds before fading in for 400 milliseconds.
     * Aditionally after those 800 milliseconds the element color is set to red.
     * 
     * NOTE that this methods affects only methods which uses the queue like effects.
     * So the following example is wrong:
     * 
     * <pre>
     * $("#foo").css(CSS.COLOR.with(RGBColor.RED)).delay(800).css(CSS.COLOR.with(RGBColor.BLACK)); 
     * </pre>
     * 
     * The code above will not insert a delay of 800 ms between the css() calls !
     * For this kind of behavior, you should execute these methods puting them in inline 
     * functions passed as argument to the delay() method, or adding them to the queue.
     * 
     * <pre>
     * $("#foo").css(CSS.COLOR.with(RGBColor.RED)).delay(800, lazy().css(CSS.COLOR.with(RGBColor.BLACK)).done()); 
     * $("#foo").css(CSS.COLOR.with(RGBColor.RED)).delay(800).queue(lazy().css(CSS.COLOR.with(RGBColor.BLACK)).dequeue().done()); 
     * </pre>
     */
    public GQuery delay(int milliseconds, Function... f) {
        return as(Queue).delay(milliseconds, f);
    }

    /**
     * Insert a delay (in ms) in the queue identified by the
     * <code>queueName</code> parameter, and optionally execute one o
     * more functions if provided when the delay finishes.
     * 
     * If <code>queueName</code> is null or
     * equats to 'fx', the delay will be inserted to the Effects queue.
     * 
     * Example :
     * 
     * <pre class="code">
     * $("#foo").queue("colorQueue", lazy().css(CSS.COLOR.with(RGBColor.RED)).dequeue("colorQueue").done())
     *          .delay(800, "colorQueue")
     *          .queue("colorQueue", lazy().css(CSS.COLOR.with(RGBColor.BLACK)).dequeue("colorQueue").done()); 
     * </pre>
     * 
     * When this statement is executed, the text color of the element changes to
     * red and then wait for 800 milliseconds before changes the text color to
     * black.
     * 
     */
    public GQuery delay(int milliseconds, String queueName, Function... f) {
        return as(Queue).delay(milliseconds, queueName, f);
    }

    /**
     * Attach <code>handlers</code> to one or more events for all elements that
     * match the <code>selector</code>, now or in the future, based on a specific
     * set of root elements. 
     * 
     * Example:
     * 
     * <pre>
     * $("table").delegate("td", Event.ONCLICK, new Function(){
     *  public void f(Element e){
     *  $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * 
     * This code above add an handler on click event on all cell (the existing
     * oneand the future cell) of all table. This code is equivalent to :
     * 
     * <pre>
     * $("table").each(new Function(){
     *  public void f(Element table){
     *   $("td", table).live(Event.ONCLICK, new Function(){
     *      public void f(Element e){
     *      $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *    }
     *  }
     * });
     *
     * </pre>
     * 
     * You can attach the handlers to many events by using the '|' operator
     * ex:
     * <pre>
     *  $("div.main").delegate(".subMain", Event.ONCLICK | Event.ONDBLCLICK, new Function(){...});
     * </pre>
     */
    public GQuery delegate(String selector, int eventbits, Function... handlers) {
        return delegate(selector, eventbits, null, handlers);
    }

    /**
     * Attach <code>handlers</code> to one or more events for all elements that match the <code>selector</code>, 
     * now or in the future, based on a specific set of root elements.
     * The <code>data</code> parameter allows us
     * to pass data to the handler.
     *
     * Example:
     * <pre>
     * $("table").delegate("td", "click", new Function(){
     *  public void f(Element e){
     *  $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * This code above add an handler on click event on all cell (the existing oneand the future cell) of all table.
     * This code is equivalent to :
     * <pre>
     * $("table").each(new Function(){
     *  public void f(Element table){
     *   $("td", table).live("click", new Function(){
     *      public void f(Element e){
     *      $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *    }
     *  }
     * });
     *
     * </pre>
     * 
     * You can pass attach the handlers to many events by using the '|' operator
     * ex:
     * <pre>
     *  $("div.main").delegate(".subMain", Event.ONCLICK | Event.ONDBLCLICK, new Function(){...});
     * </pre>
     */
    public GQuery delegate(String selector, int eventbits, Object data, Function... handlers) {

        for (Element e : elements) {
            $(selector, e).live(eventbits, data, handlers);
        }

        return this;
    }

    /**
     * Attach <code>handlers</code> to one or more events for all elements that
     * match the <code>selector</code>, now or in the future, based on a specific
     * set of root elements.
     * 
     * Example:
     * 
     * <pre>
     * $("table").delegate("td", "click", new Function(){
     *  public void f(Element e){
     *  $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * 
     * This code above add an handler on click event on all cell (the existing
     * oneand the future cell) of all table. This code is equivalent to :
     * 
     * <pre>
     * $("table").each(new Function(){
     *  public void f(Element table){
     *   $("td", table).live("click", new Function(){
     *      public void f(Element e){
     *      $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *    }
     *  }
     * });
     *
     * </pre>
     * 
     * You can pass attach the handlers to many events by specifying a String with espaced event type.
     * ex:
     * <pre>
     *  $("div.main").delegate(".subMain", "click dblclick", new Function(){...});
     * </pre>
     * </pre>
     */
    public GQuery delegate(String selector, String eventType, Function... handlers) {
        return delegate(selector, eventType, null, handlers);
    }

    /**
     * Attach <code>handlers</code> to one or more events for all elements that
     * match the <code>selector</code>, now or in the future, based on a specific
     * set of root elements.
     * 
     * Example:
     * 
     * <pre>
     * $("table").delegate("td", "click", new Function(){
     *  public void f(Element e){
     *  $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * 
     * This code above add an handler on click event on all cell (the existing
     * oneand the future cell) of all table. This code is equivalent to :
     * 
     * <pre>
     * $("table").each(new Function(){
     *  public void f(Element table){
     *   $("td", table).live("click", new Function(){
     *      public void f(Element e){
     *      $(e).css(CSS.BACKGROUND_COLOR.with(RGBColor.RED));
     *    }
     *  }
     * });
     *
     * You can pass attach the handlers to many events by specifying a String with espaced event type.
     * ex:
     * <pre>
     *  $("div.main").delegate(".subMain", "click dblclick", new Function(){...});
     * </pre>
     * </pre>
     */
    public GQuery delegate(String selector, String eventType, Object data, Function... handlers) {
        for (Element e : elements) {
            $(selector, e).live(eventType, data, handlers);
        }

        return this;
    }

    /**
     * Execute the next function on the Effects queue for the matched elements.
     * This method is usefull to tell when a function you add in the Effects queue
     * is ended and so the next function in the queue can start.
     * 
     * Note: you should be sure to call dequeue() in all functions of a queue chain,
     * otherwise the queue execution will be stopped.
     */
    public GQuery dequeue() {
        return as(Queue).dequeue();
    }

    /**
     * Execute the next function on the queue named as queueName for the matched elements. 
     * This method is usefull to tell when a function you add in the Effects queue is
     * ended and so the next function in the queue can start.
     */
    public GQuery dequeue(String queueName) {
        return as(Queue).dequeue(queueName);
    }

    /**
     * Detach all matched elements from the DOM. This method is the same than
     * {@link #remove()} method except all data and event handlers are not remove
     * from the element. This method is useful when removed elements are to be
     * reinserted into the DOM at a later time.
     */
    public GQuery detach() {
        return remove(null, false);
    }

    /**
     * Detach from the DOM all matched elements filtered by the
     * <code>filter</code>.. This method is the same than {@link #remove(String)}
     * method except all data and event handlers are not remove from the element.
     * This method is useful when removed elements are to be reinserted into the
     * DOM at a later time.
     */
    public GQuery detach(String filter) {
        return remove(filter, false);
    }

    /**
     * Remove all event handlers previously attached using
     * {@link #live(String, Function)}. In order for this method to function
     * correctly, the selector used with it must match exactly the selector
     * initially used with {@link #live(String, Function)}
     */
    public GQuery die() {
        return die(0);
    }

    /**
     * Remove an event handlers previously attached using
     * {@link #live(int, Function)} In order for this method to function
     * correctly, the selector used with it must match exactly the selector
     * initially used with {@link #live(int, Function)}
     */
    public GQuery die(int eventbits) {
        return as(Events).die(eventbits);
    }

    /**
     * Remove an event handlers previously attached using
     * {@link #live(String, Function)} In order for this method to function
     * correctly, the selector used with it must match exactly the selector
     * initially used with {@link #live(String, Function)}
     */
    public GQuery die(String eventName) {
        return as(Events).die(eventName);
    }

    private GQuery domManip(GQuery g, DomMan type, Element... elms) {
        JsNodeArray newNodes = JsNodeArray.create();
        if (elms.length == 0) {
            elms = elements;
        }
        for (int i = 0, l = elms.length; i < l; i++) {
            Element e = elms[i];
            if (e.getNodeType() == Node.DOCUMENT_NODE) {
                e = e.<Document>cast().getBody();
            }
            for (int j = 0, size = g.size(); j < size; j++) {
                // Widget w = getAssociatedWidget(g.get(j));
                // GqUi.detachWidget(w);

                Node n = g.get(j);
                // If an element selected is inserted elsewhere, it will be moved into the target (not cloned).
                // If there is more than one target element, however, cloned copies of the inserted element will be created for each target after the first
                if (i > 0) {
                    n = n.cloneNode(true);
                }
                switch (type) {
                case PREPEND:
                    newNodes.addNode(e.insertBefore(n, e.getFirstChild()));
                    break;
                case APPEND:
                    newNodes.addNode(e.appendChild(n));
                    break;
                case AFTER:
                    newNodes.addNode(e.getParentNode().insertBefore(n, e.getNextSibling()));
                    break;
                case BEFORE:
                    newNodes.addNode(e.getParentNode().insertBefore(n, e));
                    break;
                }
                EventsListener.rebind(n.<Element>cast());
                // GqUi.attachWidget(w);
            }
        }
        // TODO: newNodes.size() > g.size() makes testRebind fail
        if (newNodes.size() >= g.size()) {
            g.setArray(newNodes);
        }
        return this;
    }

    // TODO: this should be handled by the other domManip method
    private GQuery domManip(String htmlString, DomMan type) {
        JsMap<Document, GQuery> cache = JsMap.createObject().cast();
        for (Element e : elements) {
            Document d = JsUtils.getOwnerDocument(e);
            GQuery g = cache.get(d);
            if (g == null) {
                g = cleanHtmlString(htmlString, d);
                cache.put(d, g);
            }
            domManip(g.clone(), type, e);
        }
        return this;
    }

    /**
     * Run one or more Functions over each element of the GQuery. You have to
     * override one of these funcions: public void f(Element e) public String
     * f(Element e, int i)
     */
    public GQuery each(Function... f) {
        if (f != null) {
            for (Function f1 : f) {
                int i = 0;
                for (Element e : elements) {
                    f1.f(e.<com.google.gwt.dom.client.Element>cast(), i++);
                }
            }
        }
        return this;
    }

    /**
     * Returns the working set of nodes as a Java array. <b>Do NOT</b> attempt to
     * modify this array, e.g. assign to its elements, or call Arrays.sort()
     */
    public Element[] elements() {
        return elements;
    }

    /**
     * Remove all child nodes from the set of matched elements. In the case of a
     * document element, it removes all the content You should call this method
     * whenever you create a new iframe and you want to add dynamic content to it.
     */
    public GQuery empty() {
        for (Element e : elements) {
            if (e.getNodeType() == Element.DOCUMENT_NODE) {
                styleImpl.emptyDocument(e.<Document>cast());
            } else {
                Node c = e.getFirstChild();
                while (c != null) {
                    removeData(c.<Element>cast(), null);
                    GqUi.detachWidget(getAssociatedWidget(e));
                    EventsListener.clean(c.<Element>cast());
                    e.removeChild(c);
                    c = e.getFirstChild();
                }
            }
        }
        return this;
    }

    /**
     * Revert the most recent 'destructive' operation, changing the set of matched
     * elements to its previous state (right before the destructive operation).
     */
    public GQuery end() {
        return previousObject != null ? previousObject : new GQuery();
    }

    /**
     * Reduce GQuery to element in the specified position. This method accept
     * negative index. A negative index is counted from the end of the matched
     * set:
     * 
     * Example:
     * 
     * <pre>
     *  $("div").eq(0) will reduce the matched set to the first matched div
     *  $("div").eq(1) will reduce the matched set to the second matched div
     *  
     *  $("div").eq(-1) will reduce the matched set to the last matched div
     *  $("div").eq(-2) will reduce the matched set to the second-to-last matched div
     *  ...
     * </pre>
     */
    public GQuery eq(int pos) {
        return $(get(pos));
    }

    /**
     * Bind a set of functions to the error event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery error(Function... f) {
        return bindOrFire(Event.ONERROR, null, f);
    }

    /**
     * Fade in all matched elements by adjusting their opacity. The effect will
     * take 1000 milliseconds to complete
     */
    public GQuery fadeIn(Function... f) {
        return $(as(Effects).fadeIn(f));
    }

    /**
     * Fade in all matched elements by adjusting their opacity.
     */
    public GQuery fadeIn(int millisecs, Function... f) {
        return $(as(Effects).fadeIn(millisecs, f));
    }

    /**
     * Fade out all matched elements by adjusting their opacity. The effect will
     * take 1000 milliseconds to complete
     */
    public GQuery fadeOut(Function... f) {
        return $(as(Effects).fadeOut(f));
    }

    /**
     * Fade out all matched elements by adjusting their opacity.
     */
    public GQuery fadeOut(int millisecs, Function... f) {
        return as(Effects).fadeOut(millisecs, f);
    }

    /**
     * Toggle the visibility of all matched elements by adjusting their opacity and
     * firing an optional callback after completion. Only the opacity is adjusted for
     * this animation, meaning that all of the matched elements should already
     * have some form of height and width associated with them.
     */
    public Effects fadeToggle(int millisecs, Function... f) {
        return as(Effects).fadeToggle(millisecs, f);
    }

    /**
     * Removes all elements from the set of matched elements that do not match the
     * specified function. The function is called with a context equal to the
     * current element. If the function returns false, then the element is removed
     * - anything else and the element is kept.
     */
    public GQuery filter(Predicate filterFn) {
        JsNodeArray result = JsNodeArray.create();
        int i = 0;
        for (Element e : elements) {
            if (filterFn.f(e, i++)) {
                result.addNode(e);
            }
        }
        return pushStack(result, "filter", currentSelector);
    }

    /**
     * Removes all elements from the set of matched elements that do not pass the
     * specified css expression. This method is used to narrow down the results of
     * a search. Provide a comma-separated list of expressions to apply multiple
     * filters at once.
     */
    public GQuery filter(String... filters) {
        JsNodeArray array = JsNodeArray.create();
        for (String f : filters) {
            for (Element e : elements) {
                boolean ghostParent = false;

                if (e.getParentNode() == null) {
                    DOM.createDiv().appendChild(e);
                    ghostParent = true;
                }

                for (Element c : $(f, e.getParentNode()).elements) {
                    if (c == e) {
                        array.addNode(c);
                        break;
                    }
                }

                if (ghostParent) {
                    e.removeFromParent();
                }
            }
        }
        return pushStack(unique(array), "filter", filters[0]);
    }

    /**
     * Searches for all elements that match the specified css expression. This
     * method is a good way to find additional descendant elements with which to
     * process.
     * 
     * Provide a comma-separated list of expressions to apply multiple filters at
     * once.
     */
    public GQuery find(String... filters) {
        JsNodeArray array = JsNodeArray.create();
        for (String selector : filters) {
            for (Element e : elements) {
                for (Element c : $(selector, e).elements) {
                    array.addNode(c);
                }
            }
        }
        return pushStack(unique(array), "find", filters[0]);
    }

    /**
     * Reduce the set of matched elements to the first in the set.
     */
    public GQuery first() {
        return eq(0);
    }

    /**
     * Bind a set of functions to the focus event of each matched element. Or
     * trigger the event and move the input focus to the first element 
     * if no functions are provided.
     */
    public GQuery focus(Function... f) {
        bindOrFire(Event.ONFOCUS, null, f);
        if (!isEmpty() && f.length == 0) {
            get(0).focus();
        }
        return this;
    }

    /**
     * Return all elements matched in the GQuery as a NodeList. @see #elements()
     * for a method which returns them as an immutable Java array.
     */
    public NodeList<Element> get() {
        return nodeList;
    }

    /**
     * Return the ith element matched. This method accept negative index. A
     * negative index is counted from the end of the matched set.
     * 
     * Example:
     * 
     * <pre>
     *  $("div").get(0) will return the first matched div
     *  $("div").get(1) will return the second matched div
     *  
     *  $("div").get(-1) will return the last matched div
     *  $("div").get(-2) will return the secont-to-last matched div
     *  ...
     * </pre>
     */
    public Element get(int i) {
        int l = elements.length;
        if (i >= 0 && i < l) {
            return elements[i];
        }
        if (i < 0 && l + i >= 0) {
            return elements[l + i];
        }
        return null;
    }

    public Node getContext() {
        return currentContext;
    }

    /**
     * Return the previous set of matched elements prior to the last destructive
     * operation (e.g. query)
     */
    public GQuery getPreviousObject() {
        return previousObject;
    }

    private native Element getPreviousSiblingElement(Element elem) /*-{
                                                                   var sib = elem.previousSibling;
                                                                   while (sib && sib.nodeType != 1)
                                                                   sib = sib.previousSibling;
                                                                   return sib;
                                                                   }-*/;

    /**
     * Return the selector representing the current set of matched elements.
     */
    public String getSelector() {
        return currentSelector;
    }

    /**
     * Returns true any of the specified classes are present on any of the matched
     * Reduce the set of matched elements to all elements after a given position.
     * The position of the element in the set of matched elements starts at 0 and
     * goes to length - 1.
     */
    public GQuery gt(int pos) {
        return $(slice(pos + 1, -1));
    }

    /**
     * Reduce the set of matched elements to those that have a descendant 
     * that matches the Element.
     */
    public GQuery has(final Element elem) {
        return filter(new Predicate() {
            public boolean f(Element e, int index) {
                return engine.contains(e, elem);
            }
        });
    }

    /**
     * Reduce the set of matched elements to those that have a descendant 
     * that matches the selector.
     */
    public GQuery has(final String selector) {
        return filter(new Predicate() {
            public boolean f(Element e, int index) {
                return !$(selector, e).isEmpty();
            }
        });
    }

    /**
     * Returns true any of the specified classes are present on any of the matched
     * elements.
     */
    public boolean hasClass(String... classes) {
        for (Element e : elements) {
            for (String clz : classes) {
                if (hasClass(e, clz)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Get the current computed, pixel, height of the first matched element. It
     * does not include margin, padding nor border.
     */
    public int height() {
        return (int) cur("height", true);
    }

    /**
     * Set the height of every element in the matched set.
     */
    public GQuery height(int height) {
        for (Element e : elements) {
            e.getStyle().setPropertyPx("height", height);
        }
        return this;
    }

    /**
     * Set the height style property of every matched element. It's useful for
     * using 'percent' or 'em' units Example: $(".a").width("100%")
     */
    public GQuery height(String height) {
        return css("height", height);
    }

    /**
     * Make invisible all matched elements.
     */
    public GQuery hide() {
        for (Element e : elements) {
            String currentDisplay = e.getStyle().getDisplay();
            Object old = data(e, "oldDisplay", null);
            if (old == null && !"none".equals(currentDisplay)) {
                data(e, "oldDisplay", getStyleImpl().curCSS(e, "display", false));
            }
        }

        // set the display value in a separate for loop to avoid constant reflow
        // Broswer reflow is triggered each time we gonna set and after get (in styleImpl.curCSS(e, "display", false) method)
        // the diplay property. Reflows is very bad in performance point of view
        for (Element e : elements) {
            e.getStyle().setDisplay(Display.NONE);
        }

        return this;
    }

    /**
     * Bind a function to the mouseover event of each matched element. A method
     * for simulating hovering (moving the mouse on, and off, an object). This is
     * a custom method which provides an 'in' to a frequent task. Whenever the
     * mouse cursor is moved over a matched element, the first specified function
     * is fired. Whenever the mouse moves off of the element, the second specified
     * function fires.
     */
    public GQuery hover(Function fover, Function fout) {
        return bind(Event.ONMOUSEOVER, null, fover).bind(Event.ONMOUSEOUT, null, fout);
    }

    /**
     * Get the innerHTML of the first matched element.
     */
    public String html() {
        return isEmpty() ? "" : get(0).getInnerHTML();
    }

    /**
     * Set the innerHTML of every matched element.
     */
    public GQuery html(String html) {
        for (Element e : elements) {
            if (e.getNodeType() == Node.DOCUMENT_NODE) {
                e = e.<Document>cast().getBody();
            }
            e.setInnerHTML(html);
        }
        return this;
    }

    /**
     * Get the id of the first matched element.
     */
    public String id() {
        return attr("id");
    }

    /**
     * Set the id of the first matched element.
     */
    public GQuery id(String id) {
        return eq(0).attr("id", id);
    }

    /**
     * Find the index of the specified Element.
     */
    public int index(Element element) {
        int i = 0;
        for (Element e : elements) {
            if (e == element) {
                return i;
            }
            i++;
        }
        return -1;
    }

    /**
     * Returns the inner height of the first matched element, including padding
     * but not the vertical scrollbar height, border, or margin.
     */
    public int innerHeight() {
        return isEmpty() ? 0 : get(0).getClientHeight();
    }

    /**
     * Returns the inner width of the first matched element, including padding but
     * not the vertical scrollbar width, border, or margin.
     */
    public int innerWidth() {
        return isEmpty() ? 0 : get(0).getClientWidth();
    }

    /**
     * Insert all of the matched elements after another, specified, set of
     * elements.
     */
    public GQuery insertAfter(Element elem) {
        return insertAfter($(elem));
    }

    /**
     * Insert all of the matched elements after another, specified, set of
     * elements.
     */
    public GQuery insertAfter(GQuery query) {
        for (Element e : elements) {
            query.after(e);
        }
        return this;
    }

    /**
     * Insert all of the matched elements after another, specified, set of
     * elements.
     */
    public GQuery insertAfter(String selector) {
        return insertAfter($(selector));
    }

    /**
     * Insert all of the matched elements before another, specified, set of
     * elements.
     * 
     * The elements must already be inserted into the document (you can't insert
     * an element after another if it's not in the page).
     */
    public GQuery insertBefore(Element item) {
        return insertBefore($(item));
    }

    /**
     * Insert all of the matched elements before another, specified, set of
     * elements.
     * 
     * The elements must already be inserted into the document (you can't insert
     * an element after another if it's not in the page).
     */
    public GQuery insertBefore(GQuery query) {
        for (Element e : elements) {
            query.before(e);
        }
        return this;
    }

    /**
     * Insert all of the matched elements before another, specified, set of
     * elements.
     * 
     * The elements must already be inserted into the document (you can't insert
     * an element after another if it's not in the page).
     */
    public GQuery insertBefore(String selector) {
        return insertBefore($(selector));
    }

    /**
     * Checks the current selection against an expression and returns true, if at
     * least one element of the selection fits the given expression. Does return
     * false, if no element fits or the expression is not valid.
     */
    public boolean is(String... filters) {
        return !filter(filters).isEmpty();
    }

    /**
     * Returns true if the number of matched elements is 0. 
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Return true if the first element is visible.isVisible
     */
    public boolean isVisible() {
        return isEmpty() ? false : getStyleImpl().isVisible(get(0));
    }

    /**
     * Bind a set of functions to the keydown event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery keydown(Function... f) {
        return bindOrFire(Event.ONKEYDOWN, null, f);
    }

    /**
     * Trigger a keydown event passing the key pushed.
     */
    public GQuery keydown(int key) {
        return trigger(Event.ONKEYDOWN, key);
    }

    /**
     * Bind a set of functions to the keypress event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery keypress(Function... f) {
        return bindOrFire(Event.ONKEYPRESS, null, f);
    }

    /**
     * Trigger a keypress event passing the key pushed.
     */
    public GQuery keypress(int key) {
        return trigger(Event.ONKEYPRESS, key);
    }

    /**
     * Bind a set of functions to the keyup event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery keyup(Function... f) {
        return bindOrFire(Event.ONKEYUP, null, f);
    }

    /**
     * Trigger a keyup event passing the key pushed.
     */
    public GQuery keyup(int key) {
        return trigger(Event.ONKEYUP, key);
    }

    /**
     * Reduce the set of matched elements to the final one in the set.
     */
    public GQuery last() {
        return eq(size() - 1);
    }

    /**
     * Returns the computed left position of the first element matched.
     */
    public int left() {
        return (int) cur("left", true);
    }

    /**
     * Returns the number of elements currently matched. The size function will
     * return the same value.
     */
    public int length() {
        return size();
    }

    /**
     * Attach a handler for this event to all elements which match the current
     * selector, now and in the future.
     */
    public GQuery live(int eventbits, Function... funcs) {
        return as(Events).live(eventbits, null, funcs);
    }

    /**
     * Attach a handler for this event to all elements which match the current
     * selector, now and in the future.
     */
    public GQuery live(int eventbits, Object data, Function... funcs) {
        return as(Events).live(eventbits, data, funcs);
    }

    /**
     * <p>
     * Attach a handler for this event to all elements which match the current
     * selector, now and in the future.
     * <p>
     * <p>
     * Ex :
     * 
     * <pre>
     * $(".clickable").live("click", new Function(){
     *  public void f(Element e){
     *    $(e).css(CSS.COLOR.with(RGBColor.RED));
     *  }
     * });
     *  </pre>
     * 
     * With this code, all elements with class "clickable" present in the DOM or
     * added to the DOM in the future will be clickable. The text color will be
     * changed to red when they will be clicked. So if after in the code, you add
     * another element :
     * 
     * <pre>
     * $("body").append("<div class='clickable'>Click me and I will be red</div>");
     * </pre>
     * 
     * The click on this new element will also trigger the handler.
     * </p>
     * <p>
     * In the same way, if you add "clickable" class on some existing element,
     * these elements will be clickable also.
     * </p>
     * <p>
     * <h3>important remarks</h3>
     * <ul>
     * <li>
     * The live method should be always called after a selector</li>
     * <li>
     * Live events are bound to the context of the {@link GQuery} object :
     * 
     * <pre>
     * $(".clickable", myElement).live("click", new Function(){
     *  public void f(Element e){
     *    $(e).css(CSS.COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * The {@link Function} will be called only on elements having the class
     * "clickable" and being descendant of myElement.</li>
     * </ul>
     * </p>
     */
    public GQuery live(String eventName, Function... funcs) {
        return as(Events).live(eventName, null, funcs);
    }

    /**
     * <p>
     * Attach a handler for this event to all elements which match the current
     * selector, now and in the future. The <code>data</code> parameter allows us
     * to pass data to the handler.
     * <p>
     * <p>
     * Ex :
     * 
     * <pre>
     * $(".clickable").live("click", new Function(){
     *  public void f(Element e){
     *    $(e).css(CSS.COLOR.with(RGBColor.RED));
     *  }
     * });
     *  </pre>
     * 
     * With this code, all elements with class "clickable" present in the DOM or
     * added to the DOM in the future will be clickable. The text color will be
     * changed to red when they will be clicked. So if after in the code, you add
     * another element :
     * 
     * <pre>
     * $("body").append("<div class='clickable'>Click me and I will be red</div>");
     * </pre>
     * 
     * The click on this new element will also trigger the handler.
     * </p>
     * <p>
     * In the same way, if you add "clickable" class on some existing element,
     * these elements will be clickable also.
     * </p>
     * <p>
     * <h3>important remarks</h3>
     * <ul>
     * <li>
     * The live method should be always called after a selector</li>
     * <li>
     * Live events are bound to the context of the {@link GQuery} object :
     * 
     * <pre>
     * $(".clickable", myElement).live("click", new Function(){
     *  public void f(Element e){
     *    $(e).css(CSS.COLOR.with(RGBColor.RED));
     *  }
     * });
     * </pre>
     * The {@link Function} will be called only on elements having the class
     * "clickable" and being descendant of myElement.</li>
     * </ul>
     * </p>
     */
    public GQuery live(String eventName, Object data, Function... funcs) {
        return as(Events).live(eventName, data, funcs);
    }

    /**
     * Bind a function to the load event of each matched element.
     */
    @Deprecated
    public GQuery load(Function f) {
        return bind(Event.ONLOAD, null, f);
    }

    /**
     * Load data from the server and place the returned HTML into the matched element.
     * 
     * The url allows us to specify a portion of the remote document to be inserted. 
     * This is achieved with a special syntax for the url parameter. 
     * If one or more space characters are included in the string, the portion of 
     * the string following the first space is assumed to be a GQuery selector that 
     * determines the content to be loaded.
     * 
     */
    public GQuery load(String url) {
        return load(url, null, null);
    }

    /**
     * Load data from the server and place the returned HTML into the matched element.
     * 
     * The url allows us to specify a portion of the remote document to be inserted. 
     * This is achieved with a special syntax for the url parameter. 
     * If one or more space characters are included in the string, the portion of 
     * the string following the first space is assumed to be a GQuery selector that 
     * determines the content to be loaded.
     * 
     */
    public GQuery load(String url, Properties data, final Function onSuccess) {
        return as(Ajax.Ajax).load(url, data, onSuccess);
    }

    /**
     * Reduce the set of matched elements to all elements before a given position.
     * The position of the element in the set of matched elements starts at 0 and
     * goes to length - 1.
     */
    public GQuery lt(int pos) {
        return $(slice(0, pos));
    }

    /**
     * Pass each element in the current matched set through a function, producing
     * a new array containing the return values.
     * When the call to the function returns a null it is not added to the array.
     */
    public <W> List<W> map(Function f) {
        ArrayList<W> ret = new ArrayList<W>();
        int i = 0;
        for (Element e : elements) {
            @SuppressWarnings("unchecked")
            W o = (W) f.f(e.<com.google.gwt.dom.client.Element>cast(), i++);
            if (o != null) {
                ret.add(o);
            }
        }
        return ret;
    }

    /**
     * Bind a set of functions to the mousedown event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery mousedown(Function... f) {
        return bindOrFire(Event.ONMOUSEDOWN, null, f);
    }

    /**
     * Bind a set of functions to the mousemove event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery mousemove(Function... f) {
        return bindOrFire(Event.ONMOUSEMOVE, null, f);
    }

    /**
     * Bind a set of functions to the mouseout event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery mouseout(Function... f) {
        return bindOrFire(Event.ONMOUSEOUT, null, f);
    }

    /**
     * Bind a set of functions to the mouseover event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery mouseover(Function... f) {
        return bindOrFire(Event.ONMOUSEOVER, null, f);
    }

    /**
     * Bind a set of functions to the mouseup event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery mouseup(Function... f) {
        return bindOrFire(Event.ONMOUSEUP, null, f);
    }

    /**
     * Get a set of elements containing the unique next siblings of each of the
     * given set of elements. next only returns the very next sibling for each
     * element, not all next siblings see {#nextAll}.
     */
    public GQuery next() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Element next = e.getNextSiblingElement();
            if (next != null) {
                result.addNode(next);
            }
        }
        return pushStack(unique(result), "next", getSelector());
    }

    /**
     * Get a set of elements containing the unique next siblings of each of the
     * given set of elements filtered by 1 or more selectors. next only returns
     * the very next sibling for each element, not all next siblings see
     * {#nextAll}.
     */
    public GQuery next(String... selectors) {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Element next = e.getNextSiblingElement();
            if (next != null) {
                result.addNode(next);
            }
        }
        return pushStack(result, "next", selectors[0]).filter(selectors);
    }

    /**
     * Find all sibling elements after the current element.
     */
    public GQuery nextAll() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allNextSiblingElements(e.getNextSiblingElement(), result, null, null);
        }
        return pushStack(unique(result), "nextAll", getSelector());
    }

    /**
     * Get all following siblings of each element up to but not including the
     * element matched by the selector.
     * 
     * @param selector
     * @return
     */
    public GQuery nextUntil(String selector) {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allNextSiblingElements(e.getNextSiblingElement(), result, null, selector);
        }
        return pushStack(unique(result), "nextUntil", getSelector());
    }

    /**
     * Removes the specified Element from the set of matched elements. This method
     * is used to remove a single Element from a jQuery object.
     */
    public GQuery not(Element elem) {
        JsNodeArray array = JsNodeArray.create();
        for (Element e : elements) {
            if (e != elem) {
                array.addNode(e);
            }
        }
        return $(array);
    }

    /**
     * Removes any elements inside the passed set of elements from the set of
     * matched elements.
     */
    public GQuery not(GQuery gq) {
        GQuery ret = this;
        for (Element e : gq.elements) {
            ret = ret.not(e);
        }
        return ret;
    }

    /**
     * Removes elements matching the specified expression from the set of matched
     * elements.
     */
    public GQuery not(String... filters) {
        GQuery ret = this;
        for (String f : filters) {
            ret = ret.not($(f));
        }
        return ret;
    }

    /**
     * Get the current offset of the first matched element, in pixels, relative to
     * the document. The returned object contains two integer properties, top and
     * left. The method works only with visible elements.
     */
    public com.google.gwt.query.client.GQuery.Offset offset() {
        Element e = get(0);
        return e == null ? new Offset(0, 0) : new Offset(e.getAbsoluteLeft(), e.getAbsoluteTop());
    }

    /**
     * Returns a GQuery collection with the positioned parent of the first matched
     * element. This is the first parent of the element that has position (as in
     * relative or absolute). This method only works with visible elements.
     */
    public GQuery offsetParent() {
        if (isEmpty()) {
            return $();
        }
        Element offParent = JsUtils.or(get(0).getOffsetParent(), body);
        while (offParent != null && !"body".equalsIgnoreCase(offParent.getTagName())
                && !"html".equalsIgnoreCase(offParent.getTagName())
                && "static".equals(getStyleImpl().curCSS(offParent, "position", true))) {
            offParent = offParent.getOffsetParent();
        }
        return new GQuery(offParent);
    }

    /**
     * Binds a handler to a particular Event (like Event.ONCLICK) for each matched
     * element. The handler is executed only once for each element.
     * 
     * The event handler is passed as a Function that you can use to prevent
     * default behavior. To stop both default action and event bubbling, the
     * function event handler has to return false.
     * 
     * You can pass an additional Object data to your Function as the second
     * parameter
     */
    public GQuery one(int eventbits, final Object data, final Function f) {
        return as(Events).one(eventbits, data, f);
    }

    /**
     * Get the current computed height for the first element in the set of matched
     * elements, including padding, border, but not the margin.
     */
    public int outerHeight() {
        return outerHeight(false);
    }

    /**
     * Get the current computed height for the first element in the set of matched
     * elements, including padding, border, and optionally margin.
     */
    public int outerHeight(boolean includeMargin) {
        if (isEmpty()) {
            return 0;
        }
        // height including padding and border
        int outerHeight = get(0).getOffsetHeight();
        if (includeMargin) {
            outerHeight += cur("marginTop", true) + cur("marginBottom", true);
        }
        return outerHeight;
    }

    /**
     * Get the current computed width for the first element in the set of matched
     * elements, including padding, border, but not the margin.
     */
    public int outerWidth() {
        return outerWidth(false);
    }

    /**
     * Get the current computed width for the first element in the set of matched
     * elements, including padding and border and optionally margin.
     */
    public int outerWidth(boolean includeMargin) {
        if (isEmpty()) {
            return 0;
        }
        // width including padding and border
        int outerWidth = get(0).getOffsetWidth();
        if (includeMargin) {
            outerWidth += cur("marginRight", true) + cur("marginLeft", true);
        }
        return outerWidth;
    }

    /**
     * Get a set of elements containing the unique parents of the matched set of
     * elements.
     */
    public GQuery parent() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Element p = e.getParentElement();
            if (p != null) {
                result.addNode(p);
            }
        }
        return new GQuery(unique(result));
    }

    /**
     * Get a set of elements containing the unique parents of the matched set of
     * elements. You may use an optional expressions to filter the set of parent
     * elements that will match one of them.
     */
    public GQuery parent(String... filters) {
        return parent().filter(filters);
    }

    /**
     * Get a set of elements containing the unique ancestors of the matched set of
     * elements (except for the root element).
     */
    public GQuery parents() {
        return parentsUntil(null);
    }

    /**
     * Get a set of elements containing the unique ancestors of the matched set of
     * elements (except for the root element). The matched elements are filtered,
     * returning those that match any of the filters.
     */
    public GQuery parents(String... filters) {
        return parents().filter(filters);
    }

    /**
     * Get the ancestors of each element in the current set of matched elements,
     * up to but not including the element matched by the selector.
     *
     */
    public GQuery parentsUntil(String selector) {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Node par = e.getParentNode();
            while (par != null && par != document) {
                if (selector != null && $(par).is(selector)) {
                    break;
                }
                result.addNode(par);
                par = par.getParentNode();
            }
        }
        return new GQuery(unique(result));
    }

    /**
     * Gets the top and left position of an element relative to its offset parent.
     * The returned object contains two Integer properties, top and left. For
     * accurate calculations make sure to use pixel values for margins, borders
     * and padding. This method only works with visible elements.
     */
    public com.google.gwt.query.client.GQuery.Offset position() {
        if (isEmpty()) {
            return new Offset(0, 0);
        }
        Element element = get(0);
        // Get *real* offsetParent
        Element offsetParent = get(0).getOffsetParent();
        // Get correct offsets
        Offset offset = offset();
        Offset parentOffset = null;
        if (offsetParent == body || offsetParent == (Node) document) {
            parentOffset = new Offset(0, 0);
        } else {
            parentOffset = $(offsetParent).offset();
        }

        // Subtract element margins
        int topMargin = (int) getStyleImpl().cur(element, "marginTop", true);
        // TODO: move this check to getStyleImpl()
        // When margin-left = auto, Safari and chrome return a value while IE and
        // Firefox return 0
        // force the margin-left to 0 if margin-left = auto.
        int leftMargin = 0;
        if (!"auto".equals(element.getStyle().getMarginLeft())) {
            leftMargin = (int) getStyleImpl().cur(element, "marginLeft", true);
        }

        offset = offset.add(-leftMargin, -topMargin);

        // Add offsetParent borders
        int parentOffsetBorderTop = (int) getStyleImpl().cur(offsetParent, "borderTopWidth", true);
        int parentOffsetBorderLeft = (int) getStyleImpl().cur(offsetParent, "borderLeftWidth", true);
        parentOffset = parentOffset.add(parentOffsetBorderLeft, parentOffsetBorderTop);

        // Subtract the two offsets
        return offset.add(-parentOffset.left, -parentOffset.top);
    }

    /**
     * Prepend content to the inside of every matched element. This operation is
     * the best way to insert elements inside, at the beginning, of all matched
     * elements.
     */
    public GQuery prepend(GQuery query) {
        return domManip(query, DomMan.PREPEND);
    }

    /**
     * Prepend content to the inside of every matched element. This operation is
     * the best way to insert elements inside, at the beginning, of all matched
     * elements.
     */
    public GQuery prepend(Node n) {
        return domManip($(n), DomMan.PREPEND);
    }

    /**
     * Prepend content to the inside of every matched element. This operation is
     * the best way to insert elements inside, at the beginning, of all matched
     * elements.
     */
    public GQuery prepend(String html) {
        return domManip(html, DomMan.PREPEND);
    }

    /**
     * All of the matched set of elements will be inserted at the beginning of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).prependTo(B) is, essentially, the reverse of doing a
     * regular $(A).prepend(B), instead of prepending B to A, you're prepending A
     * to B.
     */
    public GQuery prependTo(GQuery other) {
        other.prepend(this);
        return this;
    }

    /**
     * All of the matched set of elements will be inserted at the beginning of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).prependTo(B) is, essentially, the reverse of doing a
     * regular $(A).prepend(B), instead of prepending B to A, you're prepending A
     * to B.
     */
    public GQuery prependTo(Node n) {
        $(n).prepend(this);
        return this;
    }

    /**
     * All of the matched set of elements will be inserted at the beginning of the
     * element(s) specified by the parameter other.
     * 
     * The operation $(A).prependTo(B) is, essentially, the reverse of doing a
     * regular $(A).prepend(B), instead of prepending B to A, you're prepending A
     * to B.
     */
    public GQuery prependTo(String html) {
        $(html).prepend(this);
        return this;
    }

    /**
     * Get a set of elements containing the unique previous siblings of each of
     * the matched set of elements. Only the immediately previous sibling is
     * returned, not all previous siblings.
     */
    public GQuery prev() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Element next = getPreviousSiblingElement(e);
            if (next != null) {
                result.addNode(next);
            }
        }
        return new GQuery(unique(result));
    }

    /**
     * Get a set of elements containing the unique previous siblings of each of
     * the matched set of elements filtered by selector. Only the immediately
     * previous sibling is returned, not all previous siblings.
     */
    public GQuery prev(String... selectors) {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            Element next = getPreviousSiblingElement(e);
            if (next != null) {
                result.addNode(next);
            }
        }
        return new GQuery(unique(result)).filter(selectors);
    }

    /**
     * Find all sibling elements in front of the current element.
     */
    public GQuery prevAll() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allPreviousSiblingElements(getPreviousSiblingElement(e), result, null);
        }
        return pushStack(unique(result), "prevAll", getSelector());
    }

    /**
     * Find all sibling elements in front of the current element.
     */
    public GQuery prevUntil(String selector) {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allPreviousSiblingElements(getPreviousSiblingElement(e), result, selector);
        }
        return pushStack(unique(result), "prevUntil", getSelector());
    }

    /**
     * Accesses a boolean property on the first matched element.
     * 
     * @param key the name of the boolean property to be accessed
     * 
     * @return <code>true</code> if at least one element is matched and the
     *         specified boolean property is set to <code>true</code> on the first
     *         matched element; <code>false</code> otherwise
     * 
     */
    public boolean prop(String key) {
        assert key != null : "Key is null";

        return !isEmpty() && get(0).getPropertyBoolean(key);
    }

    /**
     * Sets a boolean property to a value on all matched elements.
     * 
     * @param key the name of the boolean property to be set
     * @param value the value the specified boolean property should be set to
     * 
     * @return this <code>GQuery</code> object
     * 
     */
    public GQuery prop(String key, boolean value) {
        assert key != null : "Key is null";

        for (final Element element : elements) {
            element.setPropertyBoolean(key, value);
        }

        return this;
    }

    /**
     * Sets a boolean property to a computed value on all matched elements.
     * 
     * @param key the name of the boolean property to be set
     * @param closure the closure to be used to compute the value the specified
     *          boolean property should be set to; the <code>closure</code> is
     *          {@linkplain Function#f(com.google.gwt.dom.client.Element, int)
     *          passed} the target element and its index as arguments and is
     *          expected to return either a <code>Boolean</code> value or an
     *          object whose textual representation is converted to a
     *          <code>Boolean</code> value; <code>null</code> return values are
     *          ignored
     * 
     * @return this <code>GQuery</code> object
     * 
     */
    public GQuery prop(String key, Function closure) {
        assert key != null : "Key is null";
        assert closure != null : "Closure is null";

        int i = 0;
        for (Element e : elements) {
            Object value = closure.f(e, i++);
            if (value != null) {
                e.setPropertyBoolean(key,
                        value instanceof Boolean ? (Boolean) value : Boolean.valueOf(value.toString()));
            }
        }

        return this;
    }

    protected GQuery pushStack(JsNodeArray elts, String name, String selector) {
        GQuery g = new GQuery(elts);
        g.setPreviousObject(this);
        g.setSelector(selector);
        g.currentContext = currentContext;
        return g;
    }

    /**
     * Show the number of functions in the efects queue to be executed on the
     * first matched element.
     */
    public int queue() {
        return as(Queue).queue();
    }

    /**
     * Put a set of {@link Function} at the end of the Effects queue.
     * 
     * Example:
     * 
     * <pre class="code">
     * $("#foo").animate("left:'+=500'", 400)
     *      .queue(new Function(){
     *          public void f(Element e){
     *             $(e).css(CSS.BACKGROUNG_COLOR.with(RGBColor.RED));
     *             $(e).dequeue();     
     *          }
     *        })
     *       .animate("left:'-=500'", 400)
     *       .queue(lazy().css("color", "yellow");
     *       
     * </pre>
     * 
     * When this statement is executed, the element move to 500 px to left for 400
     * ms, then its background color is changed to red and then move to 500px to
     * right for 400ms, and finally its color is set to yellow.
     * 
     * Please note that {@link #dequeue()} function is needed at the end of your
     * function to start the next function in the queue. In lazy() methods you should
     * call dequeue() just before the done() call.
     * {@see #dequeue()}
     */
    public GQuery queue(Function... f) {
        return as(Queue).queue(f);
    }

    /**
     * Show the number of functions in the queued named as queueName to be
     * executed on the first matched element.
     */
    public int queue(String queueName) {
        return as(Queue).queue();
    }

    /**
     * Put a set of {@link Function} at the end of a queue.
     * 
     * Example:
     * 
     * <pre class="code">
     * $("#foo").queue("myQueue", new Function(){
     *          public void f(Element e){
     *             $(e).css(CSS.BACKGROUNG_COLOR.with(RGBColor.RED));
     *             dequeue("myQueue");
     *          }
     *        })
     *        .delay(500, "myQueue")
     *        .queue("myQueue", lazy().css(CSS.COLOR.with(RGBColor.YELLOW)).dequeue("myQueue").done());
     * </pre>
     * 
     * When this statement is executed, the background color of the element is set
     * to red, then wait 500ms before to set the text color of the element to
     * yellow. right for 400ms.
     * 
     * Please note that {@link #dequeue()} function is needed at the end of your
     * function to start the next function in the queue. In lazy() methods you should
     * call dequeue() just before the done() call.
     * {@see #dequeue()}
     */
    public GQuery queue(String queueName, Function... f) {
        return as(Queue).queue(queueName, f);
    }

    /**
     * Removes all matched elements from the DOM.
     */
    public GQuery remove() {
        return remove(null, true);
    }

    /**
     * Removes from the DOM all matched elements filtered by the
     * <code>filter</code>.
     */
    public GQuery remove(String filter) {
        return remove(filter, true);
    }

    /**
     * Removes all matched elements from the DOM and cleans their data and bound
     * events if the value of <code>clean</code> parameter is set to true. The
     * <code> filter</code> parameter allows to filter the matched set to remove.
     */
    protected GQuery remove(String filter, boolean clean) {
        for (Element e : elements) {
            if (filter == null || $(e).filter(filter).length() == 1) {
                if (clean) {
                    // clean data linked to the children
                    cleanGQData($("*", e).elements());
                    // clean data linked to the element itself
                    cleanGQData(e);
                }
                Widget w = getAssociatedWidget(e);
                if (w != null) {
                    w.removeFromParent();
                } else {
                    e.removeFromParent();
                }
            }
        }
        return this;
    }

    /**
     * Remove the named attribute from every element in the matched set.
     */
    public GQuery removeAttr(String key) {
        getAttributeImpl().removeAttribute(this, key);
        return this;
    }

    /**
     * Removes the specified classes to each matched element.
     */
    public GQuery removeClass(String... classes) {
        for (Element e : elements) {
            if (Element.is(e)) {
                for (String clz : classes) {
                    e.removeClassName(clz);
                }
            }
        }
        return this;
    }

    private void removeData(Element item, String name) {
        if (dataCache == null) {
            windowData = JavaScriptObject.createObject().cast();
            dataCache = JavaScriptObject.createObject().cast();
        }
        item = item == window || item.getNodeName() == null ? windowData : item;
        int id = item.hashCode();
        if (name != null) {
            if (dataCache.exists(id)) {
                dataCache.getCache(id).delete(name);
            }
            if (dataCache.getCache(id).isEmpty()) {
                removeData(item, null);
            }
        } else {
            dataCache.delete(id);
        }
    }

    /**
     * Removes named data store from an element.
     */
    public GQuery removeData(String name) {
        for (Element e : elements) {
            removeData(e, name);
        }
        return this;
    }

    /**
     * Replaces the element <code>elem</code> by the specified selector with the
     * matched elements. This function is the complement to replaceWith() which
     * does the same task with the parameters reversed.
     * 
     * @return a {@link GQuery} object containing the new elements.
     */
    public GQuery replaceAll(Element elem) {
        return replaceAll($(elem));
    }

    /**
     * Replaces the elements matched by the target with the selected elements.
     * This function is the complement to replaceWith() which does the same task
     * with the parameters reversed.
     * 
     * @return a {@link GQuery} object containing the new elements.
     */
    public GQuery replaceAll(GQuery target) {
        // if there is only one element and it is not attached to the dom, we have
        // to clone it to be reused on each element of target (if target contains
        // more than one element)
        boolean mustBeCloned = length() == 1 && parents().filter("body").length() == 0;

        List<Element> newElements = new ArrayList<Element>();
        for (int i = 0, l = target.size(); i < l; i++) {
            GQuery _this = (i > 0 && mustBeCloned) ? this.clone() : this;
            $(target.get(i)).replaceWith(_this);
            newElements.addAll(Arrays.asList(_this.elements));
        }
        return $(newElements);
    }

    /**
     * Replaces the elements matched by the specified selector with the matched
     * elements. This function is the complement to replaceWith() which does the
     * same task with the parameters reversed.
     * 
     * @return a {@link GQuery} object containing the new elements.
     */
    public GQuery replaceAll(String selector) {
        return replaceAll($(selector));
    }

    /**
     * Replaces all matched elements with the specified element.
     * 
     * @return the GQuery element that was just replaced, which has been removed
     *         from the DOM and not the new element that has replaced it.
     */
    public GQuery replaceWith(Element elem) {
        return replaceWith($(elem));
    }

    /**
     * Replaces all matched elements with elements selected by <code>target</code>
     * .
     * 
     * @return the GQuery element that was just replaced, which has been removed
     *         from the DOM and not the new element that has replaced it.
     */
    public GQuery replaceWith(GQuery target) {
        for (Element el : elements) {
            Element nextSibling = el.getNextSiblingElement();

            if (nextSibling != null) {
                $(nextSibling).before(target);
            } else {
                Element parent = el.getParentElement();
                $(parent).append(target);
            }
            $(el).remove();
        }
        return this;

    }

    /**
     * Replaces all matched elements with the specified HTML.
     * 
     * @return the GQuery element that was just replaced, which has been removed
     *         from the DOM and not the new element that has replaced it.
     */
    public GQuery replaceWith(String html) {
        for (Element el : elements) {
            Element nextSibling = el.getNextSiblingElement();

            if (nextSibling != null) {
                $(nextSibling).before(html);
            } else {
                Element parent = el.getParentElement();
                $(parent).append(html);
            }
            $(el).remove();
        }
        return this;
    }

    /**
     * Bind a set of functions to the resize event of each matched element, or
     * tigger the resize event if no functions are provided.
     * 
     * Note that although all elements can be configured to handle resize
     * events, by default only window will trigger it when it is resized, 
     * for an arbitrary element you have to trigger the event after resizing 
     * the object. 
     * 
     */
    public GQuery resize(Function... f) {
        return bindOrFire(EventsListener.ONRESIZE, null, f);
    }

    /**
     * Bind an event handler to the "resize" JavaScript event, or trigger that event on an element. 
     */
    public GQuery resize(final Function f) {
        return bindOrFire(EventsListener.ONRESIZE, null, f);
    }

    /**
     * Save a set of Css properties of every matched element.
     */
    public void restoreCssAttrs(String... cssProps) {
        for (Element e : elements) {
            for (String a : cssProps) {
                getStyleImpl().setStyleProperty(e, a, (String) data(e, OLD_DATA_PREFIX + a, null));
            }
        }
    }

    /**
     * Restore a set of previously saved Css properties in every matched element.
     */
    public void saveCssAttrs(String... cssProps) {
        for (Element e : elements) {
            for (String a : cssProps) {
                data(OLD_DATA_PREFIX + a, getStyleImpl().curCSS(e, a, false));
            }
        }
    }

    /**
     * Bind a set of functions to the scroll event of each matched element. Or
     * trigger the event if no functions are provided.
     */
    public GQuery scroll(Function... f) {
        return bindOrFire(Event.ONSCROLL, null, f);
    }

    /**
     * Scrolls the first matched element into view.
     */
    public GQuery scrollIntoView() {
        if (!isEmpty())
            scrollIntoViewImpl(get(0));
        return this;
    }

    /**
     * Scrolls the first matched element into view.
     * 
     * If ensure == true, it crawls up the DOM hierarchy, adjusting the scrollLeft
     * and scrollTop properties of each scroll-able element to ensure that the
     * specified element is completely in view. It adjusts each scroll position by
     * the minimum amount necessary.
     */
    public GQuery scrollIntoView(boolean ensure) {
        if (!isEmpty() && ensure) {
            DOM.scrollIntoView((com.google.gwt.user.client.Element) get(0));
        } else {
            scrollIntoView();
        }
        return this;
    }

    /**
     * Gets the scroll left offset of the first matched element. This method works
     * for both visible and hidden elements.
     */
    public int scrollLeft() {
        Element e = get(0);
        if (e == null) {
            return 0;
        }
        if (e == window || e.getNodeName() == null) {
            return Window.getScrollLeft();
        } else if (e == (Node) document) {
            return document.getScrollLeft();
        } else {
            return e.getScrollLeft();
        }
    }

    /**
     * The scroll left offset is set to the passed value on all matched elements.
     * This method works for both visible and hidden elements.
     */
    public GQuery scrollLeft(int left) {
        for (Element e : elements) {
            if (e == window || e.getNodeName() == null || e == (Node) document) {
                Window.scrollTo(left, $(e).scrollTop());
            } else {
                e.setPropertyInt("scrollLeft", left);
            }
        }
        return this;
    }

    /**
     * 
     * Scrolls the contents of all matched elements to the specified co-ordinate
     * becoming the top left corner of the viewable area.
     * 
     * This method is only useful where there are areas of the document not
     * viewable within the current viewable area of the window and the visible
     * property of the window's scrollbar must be set to true.
     * 
     */
    public GQuery scrollTo(int left, int top) {
        scrollLeft(left).scrollTop(top);
        return this;
    }

    /**
     * Gets the scroll top offset of the first matched element. This method works
     * for both visible and hidden elements.
     */
    public int scrollTop() {
        Element e = get(0);
        if (e == null) {
            return 0;
        }
        if (e == window || e.getNodeName() == null) {
            return Window.getScrollTop();
        } else if (e == (Node) document) {
            return document.getScrollTop();
        } else {
            return e.getScrollTop();
        }
    }

    /**
     * The scroll top offset is set to the passed value on all matched elements.
     * This method works for both visible and hidden elements.
     */
    public GQuery scrollTop(int top) {
        for (Element e : elements) {
            if (e == window || e.getNodeName() == null || e == (Node) document) {
                Window.scrollTo($(e).scrollLeft(), top);
            } else {
                e.setPropertyInt("scrollTop", top);
            }
        }
        return this;
    }

    public GQuery select() {
        return as(Events).triggerHtmlEvent("select");
    }

    private GQuery select(String selector, Node context) {
        if (engine == null) {
            engine = new SelectorEngine();
        }

        NodeList<Element> n = engine.select(selector, context == null ? document : context);
        currentSelector = selector;
        currentContext = context != null ? context : document;
        return setArray(n);
    }

    /**
     * Force the current matched set of elements to become the specified array of
     * elements.
     */
    public GQuery setArray(NodeList<Element> list) {
        if (list != null) {
            nodeList.<JsCache>cast().clear();
            int l = list.getLength();
            elements = new Element[l];
            for (int i = 0; i < l; i++) {
                elements[i] = list.getItem(i);
                nodeList.<JsObjectArray>cast().add(list.getItem(i));
            }
        }
        return this;
    }

    public void setPreviousObject(GQuery previousObject) {
        this.previousObject = previousObject;
    }

    public GQuery setSelector(String selector) {
        this.currentSelector = selector;
        return this;
    }

    /**
     * Make all matched elements visible
     */
    public GQuery show() {
        for (Element e : elements) {
            String currentDisplay = e.getStyle().getDisplay();
            String oldDisplay = (String) data(e, "oldDisplay", null);

            //reset the display
            if (oldDisplay == null && "none".equals(currentDisplay)) {
                getStyleImpl().setStyleProperty(e, "display", "");
                currentDisplay = "";
            }

            //check if the stylesheet impose display: none. If it is the case, determine 
            //the default display for the tag and store it at the element level
            if ("".equals(currentDisplay) && !getStyleImpl().isVisible(e)) {
                data(e, "oldDisplay", getStyleImpl().defaultDisplay(e.getNodeName()));
            }
        }

        // set the display value in a separate for loop to avoid constant reflow
        // because broswer reflow is triggered each time we gonna set and after get (in isVisibleProperty() method)
        // the diplay property. Reflows is very bad in performance point of view
        for (Element e : elements) {
            String currentDisplay = e.getStyle().getDisplay();
            if ("".equals(currentDisplay) || "none".equals(currentDisplay)) {
                getStyleImpl().setStyleProperty(e, "display", JsUtils.or((String) data(e, "oldDisplay", null), ""));
            }
        }
        return this;
    }

    /**
     * Get a set of elements containing all of the unique siblings of each of the
     * matched set of elements.
     */
    public GQuery siblings() {
        JsNodeArray result = JsNodeArray.create();
        for (Element e : elements) {
            allNextSiblingElements(e.getParentElement().getFirstChildElement(), result, e, null);
        }
        return new GQuery(unique(result));
    }

    /**
     * Get a set of elements containing all of the unique siblings of each of the
     * matched set of elements filtered by the provided set of selectors.
     */
    public GQuery siblings(String... selectors) {
        return siblings().filter(selectors);
    }

    /**
     * Return the number of elements in the matched set.
     */
    public int size() {
        return elements.length;
    }

    /**
     * Selects a subset of the matched elements.
     */
    public GQuery slice(int start, int end) {
        JsNodeArray slice = JsNodeArray.create();
        int l = size();
        if (end == -1 || end > l) {
            end = l;
        }
        for (int i = start; i < end; i++) {
            slice.addNode(get(i));
        }
        return new GQuery(slice);
    }

    /**
     * Reveal all matched elements by adjusting their height and firing an
     * optional callback after completion.
     */
    public Effects slideDown(Function... f) {
        return as(Effects).slideDown(f);
    }

    /**
     * Reveal all matched elements by adjusting their height and firing an
     * optional callback after completion.
     */
    public Effects slideDown(int millisecs, Function... f) {
        return as(Effects).slideDown(millisecs, f);
    }

    /**
     * Toggle the visibility of all matched elements by adjusting their height and
     * firing an optional callback after completion. Only the height is adjusted
     * for this animation, causing all matched elements to be hidden or shown in a
     * "sliding" manner
     */
    public Effects slideToggle(int millisecs, Function... f) {
        return as(Effects).slideToggle(millisecs, f);
    }

    /**
     * Hide all matched elements by adjusting their height and firing an optional
     * callback after completion.
     */
    public Effects slideUp(Function... f) {
        return as(Effects).slideUp(f);
    }

    /**
     * Hide all matched elements by adjusting their height and firing an optional
     * callback after completion.
     */
    public Effects slideUp(int millisecs, Function... f) {
        return as(Effects).slideUp(millisecs, f);
    }

    /**
     * When .stop() is called on an element, the currently-running animation (if any) 
     * is immediately stopped. If, for instance, an element is being hidden with .slideUp() 
     * when .stop() is called, the element will now still be displayed, but will be 
     * a fraction of its previous height. Callback functions are not called but
     * the next animation in the queue begins immediately. 
     */
    public GQuery stop() {
        return stop(false);
    }

    /**
     * When .stop() is called on an element, the currently-running animation (if any) 
     * is immediately stopped. If, for instance, an element is being hidden with .slideUp() 
     * when .stop() is called, the element will now still be displayed, but will be 
     * a fraction of its previous height. Callback functions are not called but
     * the next animation in the queue begins immediately. 
     * 
     * If the clearQueue parameter is provided with a value of true, then the rest of the 
     * animations in the queue are removed and never run.
     */
    public GQuery stop(boolean clearQueue) {
        return stop(clearQueue, false);
    }

    /**
     * When .stop() is called on an element, the currently-running animation (if any) 
     * is immediately stopped. If, for instance, an element is being hidden with .slideUp() 
     * when .stop() is called, the element will now still be displayed, but will be 
     * a fraction of its previous height. Callback functions are not called but
     * the next animation in the queue begins immediately. 
     * 
     * If the clearQueue parameter is provided with a value of true, then the rest of the 
     * animations in the queue are removed and never run.
     * 
     * If the jumpToEnd property is provided with a value of true, the current animation stops, 
     * but the element is immediately given its target values for each CSS property.
     * The callback functions are then immediately called, if provided.
     */
    public GQuery stop(boolean clearQueue, boolean jumpToEnd) {
        return as(Queue).stop(clearQueue, jumpToEnd);
    }

    /**
     * Bind a set of functions to the submit event of each matched element. 
     * Or submit a form if no functions are provided.
     */
    public GQuery submit(Function... funcs) {
        return bindOrFire(EventsListener.ONSUBMIT, null, funcs);
    }

    /**
     * Return the concatened text contained in the matched elements.
     */
    public String text() {
        String result = "";
        for (Element e : elements) {
            result += JsUtils.text(e);
        }
        return result;
    }

    /**
     * Set the innerText of every matched element.
     */
    public GQuery text(String txt) {
        for (Element e : elements) {
            e.setInnerText(txt);
        }
        return this;
    }

    /**
     * Toggle visibility of elements.
     */
    public GQuery toggle() {
        for (Element e : elements) {
            if (getStyleImpl().isVisible(e)) {
                $(e).hide();
            } else {
                $(e).show();
                e.getStyle().setDisplay(Display.BLOCK);
            }
        }
        return this;
    }

    /**
     * Toggle among two or more function calls every other click.
     */
    public GQuery toggle(final Function... fn) {
        for (Element e : elements) {
            $(e).click(new Function() {
                int click = 0;

                public boolean f(Event e) {
                    int n = fn.length == 1 ? 0 : (click++ % fn.length);
                    return fn[n].f(e);
                }
            });
        }
        return this;
    }

    /**
     * Adds or removes the specified classes to each matched element depending on
     * the class's presence.
     */
    public GQuery toggleClass(String... classes) {
        for (Element e : elements) {
            for (String clz : classes) {
                if (hasClass(e, clz)) {
                    e.removeClassName(clz);
                } else {
                    e.addClassName(clz);
                }
            }
        }
        return this;
    }

    /**
     * Adds or removes the specified classes to each matched element depending on
     * the value of the switch argument.
     * 
     * if addOrRemove is true, the class is added and in the case of false it is
     * removed.
     */
    public GQuery toggleClass(String clz, boolean addOrRemove) {
        if (addOrRemove) {
            addClass(clz);
        } else {
            removeClass(clz);
        }
        return this;
    }

    /**
     * Returns the computed top position of the first element matched.
     */
    public int top() {
        return (int) cur("top", true);
    }

    /**
     * Produces a string representation of the matched elements.
     */
    public String toString() {
        return toString(false);
    }

    /**
     * Produces a string representation of the matched elements.
     */
    public String toString(boolean pretty) {
        String r = "";
        for (Element e : elements) {
            if (window.equals(e)) {
                continue;
            }
            String elStr;
            try {
                elStr = JsUtils.isXML(e) ? JsUtils.XML2String(e) : e.getString();
            } catch (Exception e2) {
                elStr = "< " + (e == null ? "null" : e.getNodeName())
                        + "(gquery, error getting the element string representation: " + e2.getMessage() + ")/>";
            }
            r += (pretty && r.length() > 0 ? "\n " : "") + elStr;
        }
        return r;
    }

    /**
     * Trigger a set of events on each matched element.
     * 
     * For keyboard events you can pass a second parameter which represents the
     * key-code of the pushed key.
     * 
     * Example: fire(Event.ONCLICK | Event.ONFOCUS) Example: fire(Event.ONKEYDOWN.
     * 'a');
     */
    public GQuery trigger(int eventbits, int... keys) {
        return as(Events).trigger(eventbits, keys);
    }

    /**
     * Removes all events that match the eventbits.
     */
    public GQuery unbind(int eventbits) {
        return as(Events).unbind(eventbits);
    }

    /**
     * Removes the function passed from the set of events which match 
     * the eventbits.
     */
    public GQuery unbind(int eventbits, Function f) {
        return as(Events).unbind(eventbits, null, f);
    }

    /**
     * Removes all events that match the eventList.
     */
    public GQuery unbind(String eventList) {
        return unbind(eventList, null);
    }

    /**
     * Removes all events that match the eventList.
     */
    public GQuery unbind(String eventList, Function f) {
        return as(Events).unbind(eventList, f);
    }

    /**
     * Remove all event delegation that have been bound using
     * {@link #delegate(String, int, Function...)} {@link #live(int, Function...)} methods
     */
    public GQuery undelegate() {
        return as(Events).undelegate();
    }

    /**
     * Undelegate is a way of removing event handlers that have been bound using
     * {@link #delegate(String, int, Function...)} method
     */
    public GQuery undelegate(String selector) {
        for (Element e : elements) {
            $(selector, e).die();
        }

        return this;
    }

    /**
     * Undelegate is a way of removing event handlers that have been bound using
     * {@link #delegate(String, int, Function...)} method
     */
    public GQuery undelegate(String selector, int eventBit) {
        for (Element e : elements) {
            $(selector, e).die(eventBit);
        }

        return this;
    }

    /**
     * Undelegate is a way of removing event handlers that have been bound using
     * {@link #delegate(String, int, Function...)} method
     */
    public GQuery undelegate(String selector, String eventName) {
        for (Element e : elements) {
            $(selector, e).die(eventName);
        }

        return this;
    }

    /**
     * Remove all duplicate elements from an array of elements. Note that this
     * only works on arrays of DOM elements, not strings or numbers.
     */
    public JsNodeArray unique(NodeList<Element> result) {
        return JsUtils.unique(result.<JsArray<Element>>cast()).cast();
    }

    /**
     * This method removes the element's parent. The matched elements replaces
     * their parents within the DOM structure. It is the inverse of
     * {@link GQuery#wrap(GQuery)} method
     * 
     * @return
     */
    public GQuery unwrap() {

        for (Element parent : parent().elements) {
            if (!"body".equalsIgnoreCase(parent.getTagName())) {
                GQuery $parent = $(parent);
                $parent.replaceWith($parent.children());
            }
        }
        return this;
    }

    /**
     * Gets the content of the value attribute of the first matched element,
     * returns only the first value even if it is a multivalued element. To get an
     * array of all values in multivalues elements use vals()
     * 
     * When the first element is a radio-button and is not checked, then it looks
     * for the first checked radio-button that has the same name in the list of
     * matched elements.
     * 
     * When there are not matched elements it returns null.
     */
    public String val() {
        if (isEmpty()) {
            return null;
        }
        String[] v = vals();
        return v == null ? null : v.length > 0 ? v[0] : "";
    }

    /**
     * Sets the value attribute of every matched element based in the return
     * value of the function evaluated for this element.
     * 
     * NOTE: in jquery the function receives the arguments in different
     * way, first index and them the actual value, but we use the normal way
     * in gquery Function, first the element and second the index.
     */
    public GQuery val(Function f) {
        for (int i = 0; i < size(); i++) {
            eq(i).val(f.f(get(i), i).toString());
        }
        return this;
    }

    /**
     * Sets the 'value' attribute of every matched element, but
     * does not set the checked flag to checkboxes or radiobuttons.
     * 
     * If you wanted to set values in collections of checkboxes o radiobuttons 
     * use val(String[]) instead
     */
    public GQuery val(String value) {
        for (Element e : elements) {
            setElementValue(e, value);
        }
        return this;
    }

    /**
     * Sets the value of every matched element.
     * 
     * There is a different behaviour depending on the element type:
     * <ul>
     *  <li>select multiple: options whose value match any of the passed values will be set.
     *  <li>select single: the last option whose value matches any of the passed values will be set.
     *  <li>input radio: the last input whose value matches any of the passed values will be set.
     *  <li>input checkbox: inputs whose value match any of the passed values will be set.
     *  <li>textarea, button, and other input: value will set to a string result of joining with coma, all passed values 
     * </ul>
     * 
     * NOTE: if you wanted call this function with just one parameter, you have to
     * pass an array signature to avoid call the overloaded val(String) method:
     * 
     * $(...).val(new String[]{"value"});
     */
    public GQuery val(String... values) {
        String value = values.length > 0 ? values[0] : "";
        for (int i = 1; i < values.length; i++) {
            value += "," + values[i];
        }
        for (Element e : elements) {
            String name = e.getNodeName();
            if ("select".equalsIgnoreCase(name)) {
                SelectElement s = SelectElement.as(e);
                s.setSelectedIndex(-1);
                for (String v : values) {
                    if (s.isMultiple()) {
                        for (int i = 0, l = s.getOptions().getLength(); i < l; i++) {
                            if (v.equals(s.getOptions().getItem(i).getValue())) {
                                s.getOptions().getItem(i).setSelected(true);
                            }
                        }
                    } else {
                        s.setValue(v);
                    }
                }
            } else if ("input".equalsIgnoreCase(name)) {
                InputElement ie = InputElement.as(e);
                String type = ie.getType();
                if ("radio".equalsIgnoreCase((type)) || "checkbox".equalsIgnoreCase(type)) {
                    ie.setChecked(false);
                    for (String v : values) {
                        if (ie.getValue().equals(v)) {
                            ie.setChecked(true);
                            break;
                        }
                    }
                } else {
                    ie.setValue(value);
                }
            } else {
                setElementValue(e, value);
            }
        }
        return this;
    }

    /**
     * Gets the content of the value attribute of the first matched element,
     * returns more than one value if it is a multiple select.
     * 
     * When the first element is a radio-button and is not checked, then it looks
     * for a the first checked radio-button that has the same name in the list of
     * matched elements.
     * 
     * This method always returns an array. If no valid value can be determined
     * the array will be empty, otherwise it will contain one or more values.
     */
    public String[] vals() {
        if (!isEmpty()) {
            Element e = get(0);
            if (e.getNodeName().equalsIgnoreCase("select")) {
                SelectElement se = SelectElement.as(e);
                if (se.isMultiple()) {
                    JsArrayString result = JsArrayString.createArray().cast();
                    for (int i = 0, l = se.getOptions().getLength(); i < l; i++) {
                        OptionElement oe = se.getOptions().getItem(i);
                        if (oe.isSelected()) {
                            result.set(result.length(), oe.getValue());
                        }
                    }
                    return result.length() > 0 ? jsArrayToString(result) : null;
                } else if (se.getSelectedIndex() >= 0) {
                    return new String[] { se.getOptions().getItem(se.getSelectedIndex()).getValue() };
                }
            } else if (e.getNodeName().equalsIgnoreCase("input")) {
                InputElement ie = InputElement.as(e);
                return new String[] { ie.getValue() };
            } else if (e.getNodeName().equalsIgnoreCase("textarea")) {
                return new String[] { TextAreaElement.as(e).getValue() };
            } else if (e.getNodeName().equalsIgnoreCase("button")) {
                return new String[] { ButtonElement.as(e).getValue() };
            }
        }
        return new String[0];
    }

    @Deprecated
    public boolean visible() {
        return isVisible();
    }

    /**
     * Return the first non null attached widget from the matched elements or null
     * if there isn't any.
     */
    @SuppressWarnings("unchecked")
    public <W extends Widget> W widget() {
        return (W) widget(0);
    }

    /**
     * Return the nth non null attached widget from the matched elements or null
     * if there isn't any.
     */
    public <W extends Widget> W widget(int n) {
        for (Element e : elements) {
            @SuppressWarnings("unchecked")
            W w = (W) getAssociatedWidget(e);
            if (w != null) {
                if (n == 0) {
                    return w;
                }
                n--;
            }
        }
        return null;
    }

    /**
     * return the list of attached widgets matching the query
     */
    public List<Widget> widgets() {
        List<Widget> widgets = new ArrayList<Widget>();
        for (Element e : elements) {
            Widget w = getAssociatedWidget(e);
            if (w != null) {
                widgets.add(w);
            }
        }
        return widgets;
    }

    /**
     * Return the list of attached widgets instance of the provided class matching
     * the query.
     * 
     * This method is very useful for decoupled views, so as we can access widgets
     * from other views without maintaining methods which export them.
     * 
     */
    @SuppressWarnings("unchecked")
    public <W extends Widget> List<W> widgets(Class<W> clazz) {
        List<W> ret = new ArrayList<W>();
        for (Widget w : widgets()) {
            // isAssignableFrom does not work in gwt.
            Class<?> c = w.getClass();
            do {
                if (c.equals(clazz)) {
                    ret.add((W) w);
                    break;
                }
                c = c.getSuperclass();
            } while (c != null);
        }
        return ret;
    }

    /**
     * Get the current computed, pixel, width of the first matched element. It
     * does not include margin, padding nor border.
     */
    public int width() {
        return (int) cur("width", true);
    }

    /**
     * Set the width of every matched element.
     */
    public GQuery width(int width) {
        for (Element e : elements) {
            e.getStyle().setPropertyPx("width", width);
        }
        return this;
    }

    /**
     * Wrap each matched element with the specified HTML content. This wrapping
     * process is most useful for injecting additional structure into a document,
     * without ruining the original semantic qualities of a document. This works
     * by going through the first element provided (which is generated, on the
     * fly, from the provided HTML) and finds the deepest descendant element
     * within its structure -- it is that element that will enwrap everything
     * else.
     */
    public GQuery wrap(Element elem) {
        return wrap($(elem));
    }

    /**
     * Wrap each matched element with the specified HTML content. This wrapping
     * process is most useful for injecting additional structure into a document,
     * without ruining the original semantic qualities of a document. This works
     * by going through the first element provided (which is generated, on the
     * fly, from the provided HTML) and finds the deepest descendant element
     * within its structure -- it is that element that will enwrap everything
     * else.
     */
    public GQuery wrap(GQuery query) {
        for (Element e : elements) {
            $(e).wrapAll(query);
        }
        return this;
    }

    /**
     * Wrap each matched element with the specified HTML content. This wrapping
     * process is most useful for injecting additional structure into a document,
     * without ruining the original semantic qualities of a document. This works
     * by going through the first element provided (which is generated, on the
     * fly, from the provided HTML) and finds the deepest descendant element
     * within its structure -- it is that element that will enwrap everything
     * else.
     */
    public GQuery wrap(String html) {
        return wrap($(html));
    }

    /**
     * Wrap all the elements in the matched set into a single wrapper element.
     * This is different from .wrap() where each element in the matched set would
     * get wrapped with an element. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document.
     * 
     * This works by going through the first element provided (which is generated,
     * on the fly, from the provided HTML) and finds the deepest descendant
     * element within its structure -- it is that element that will enwrap
     * everything else.
     */
    public GQuery wrapAll(Element elem) {
        return wrapAll($(elem));
    }

    /**
     * Wrap all the elements in the matched set into a single wrapper element.
     * This is different from .wrap() where each element in the matched set would
     * get wrapped with an element. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document.
     * 
     * This works by going through the first element provided (which is generated,
     * on the fly, from the provided HTML) and finds the deepest descendant
     * element within its structure -- it is that element that will enwrap
     * everything else.
     */
    public GQuery wrapAll(GQuery query) {
        if (!isEmpty()) {
            GQuery wrap = query.clone();
            if (get(0).getParentNode() != null) {
                wrap.insertBefore(get(0));
            }
            for (Element e : wrap.elements) {
                Node n = e;
                while (n.getFirstChild() != null && n.getFirstChild().getNodeType() == Node.ELEMENT_NODE) {
                    n = n.getFirstChild();
                }
                $((Element) n).append(this);
            }
        }
        return this;
    }

    /**
     * Wrap all the elements in the matched set into a single wrapper element.
     * This is different from .wrap() where each element in the matched set would
     * get wrapped with an element. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document.
     * 
     * This works by going through the first element provided (which is generated,
     * on the fly, from the provided HTML) and finds the deepest descendant
     * element within its structure -- it is that element that will enwrap
     * everything else.
     */
    public GQuery wrapAll(String html) {
        return wrapAll($(html));
    }

    /**
     * Wrap the inner child contents of each matched element (including text
     * nodes) with an HTML structure. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document. This works by going through the
     * first element provided (which is generated, on the fly, from the provided
     * HTML) and finds the deepest ancestor element within its structure -- it is
     * that element that will enwrap everything else.
     */
    public GQuery wrapInner(Element elem) {
        return wrapInner($(elem));
    }

    /**
     * Wrap the inner child contents of each matched element (including text
     * nodes) with an HTML structure. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document. This works by going through the
     * first element provided (which is generated, on the fly, from the provided
     * HTML) and finds the deepest ancestor element within its structure -- it is
     * that element that will enwrap everything else.
     */
    public GQuery wrapInner(GQuery query) {
        for (Element e : elements) {
            $(e).contents().wrapAll(query);
        }
        return this;
    }

    /**
     * Wrap the inner child contents of each matched element (including text
     * nodes) with an HTML structure. This wrapping process is most useful for
     * injecting additional structure into a document, without ruining the
     * original semantic qualities of a document. This works by going through the
     * first element provided (which is generated, on the fly, from the provided
     * HTML) and finds the deepest ancestor element within its structure -- it is
     * that element that will enwrap everything else.
     */
    public GQuery wrapInner(String html) {
        return wrapInner($(html));
    }
}