com.watopi.chosen.client.ChosenImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.watopi.chosen.client.ChosenImpl.java

Source

/*
 * Copyright (C) 2012 Julien Dramaix
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.watopi.chosen.client;

import static com.google.gwt.query.client.GQuery.$;
import static com.google.gwt.query.client.GQuery.document;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
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.StyleInjector;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.js.JsObjectArray;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.watopi.chosen.client.SelectParser.GroupItem;
import com.watopi.chosen.client.SelectParser.OptionItem;
import com.watopi.chosen.client.SelectParser.SelectItem;
import com.watopi.chosen.client.event.ChosenChangeEvent;
import com.watopi.chosen.client.event.ChosenEvent;
import com.watopi.chosen.client.event.HidingDropDownEvent;
import com.watopi.chosen.client.event.MaxSelectedEvent;
import com.watopi.chosen.client.event.ReadyEvent;
import com.watopi.chosen.client.event.ShowingDropDownEvent;
import com.watopi.chosen.client.event.UpdatedEvent;
import com.watopi.chosen.client.resources.ChozenCss;
import com.watopi.chosen.client.resources.Resources;

public class ChosenImpl {

    public static interface ChozenTemplate extends SafeHtmlTemplates {
        public ChozenTemplate templates = GWT.create(ChozenTemplate.class);

        @Template("<li class=\"{1}\" id=\"{0}\"><span>{2}</span><a href=\"javascript:void(0)\" class=\"{3}\" rel=\"{4}\"></a></li>")
        SafeHtml choice(String id, String searchChoiceClass, String content, String searchChoiceCloseClass,
                String rel);

        @Template("<div id=\"{0}\" class=\"{1}\"></div>")
        SafeHtml container(String id, String cssClasses);

        @Template("<ul class=\"{0}\"><li class=\"{1}\"><input type=\"text\" value=\"{2}\" class=\"{3}\" autocomplete=\"off\" style=\"width:25px;\"/></li></ul><div class=\"{4}\" style=\"{6}:-9000px;\"><ul class=\"{5}\"></ul></div>")
        SafeHtml contentMultiple(String chznChoicesClass, String chznSearchFieldClass, String defaultText,
                String defaultClass, String chznDropClass, String chznResultClass, String rightOrLeft);

        @Template("<a href=\"javascript:void(0)\" class=\"{0} {1}\"><span>{2}</span><div><b></b></div></a><div class=\"{3}\" style=\"{6}:-9000px;\"><div class=\"{4}\"><input type=\"text\" autocomplete=\"off\" /></div><ul class=\"{5}\"></ul></div>")
        SafeHtml contentSingle(String chznSingleClass, String chznDefaultClass, String defaultText,
                String dropClass, String chznSearchClass, String chznResultClass, String rightOrLeft);

        @Template("<li id=\"{0}\" class=\"{1}\">{2}</li>")
        SafeHtml group(String id, String groupResultClass, String content);

        @Template("<li class=\"{0}\">{1}\"<span></span>\"</li>")
        SafeHtml noResults(String noResultsClass, String content);

        @Template("<li id=\"{0}\" class=\"{1}\" style=\"{2}\">{3}</li>")
        SafeHtml option(String id, String groupResultClass, String style, String content);

    }

    private static final RegExp containerIdRegExp = RegExp.compile("[^\\w]", "g");
    private static boolean cssInjected = false;

    private static final String DEFAULT_CONTAINER_ID = "chozen_container__";
    private static int idCounter = 0;
    private static final RegExp regExpChars = RegExp.compile("[-[\\]{}()*+?.,\\\\^$|#\\s]", "g");
    private static final String TABINDEX_PROPERTY = "tabindex";

    private GQuery $selectElement;
    private Function activateAction;
    private boolean activeField = false;
    private boolean allowSingleDeselect = false;
    private int backstrokeLength;
    private int choices;
    private Function clickTestAction;
    private GQuery container;
    private String containerId;
    private ChozenCss css;
    private String currentValue;
    private String defaultText;
    private GQuery dropdown;
    private EventBus eventBus;
    private int fWidth;
    private boolean isDisabled;
    private boolean isMultiple;
    private boolean isRTL;
    private boolean mouseOnContainer = false;
    private ChosenOptions options;
    private GQuery pendingBackstroke;
    private boolean pendingDestroyClick;
    private GQuery resultHighlight;
    private GQuery resultSingleSelected;
    private String resultsNoneFound;
    private boolean resultsShowing = false;
    private GQuery searchChoices;
    private GQuery searchContainer;
    private GQuery searchField;
    private GQuery searchResults;
    private GQuery selectedItem;
    private SelectElement selectElement;
    private JsObjectArray<SelectItem> selectItems;
    private HandlerRegistration updateEventHandlerRegistration;

    public GQuery getContainer() {
        return container;
    }

    public String getCurrentValue() {
        return currentValue;
    }

    public ChosenOptions getOptions() {
        return options;
    }

    public SelectElement getSelectElement() {
        return selectElement;
    }

    /**
     * Is the plugin support the current broxser ?
     * 
     * @return
     */
    public boolean isSupported() {
        return true;
    }

    protected void init(SelectElement element, ChosenOptions options, EventBus eventBus) {
        this.selectElement = element;
        this.options = options;
        this.eventBus = eventBus;

        this.$selectElement = $(selectElement);

        setDefaultValues();

        this.isMultiple = selectElement.isMultiple();

        setDefaultText();

        setup();

        bind();

        finishSetup();

    }

    protected void update() {
        if (!isMultiple) {
            resultsResetCleanup();
        }

        resultClearHighlight();
        resultSingleSelected = null;
        resultsBuild();
    }

    protected void release() {
        if (updateEventHandlerRegistration != null) {
            updateEventHandlerRegistration.removeHandler();
            updateEventHandlerRegistration = null;
        }
        // empty the searchResult to speed up the container.remove()
        if (searchResults != null) {
            searchResults.html("");
        }

        // remove method clean listener and cie
        container.remove();

        $selectElement.removeClass(css.chznDone(), "chzn-done").show();
    }

    private boolean activateField(Event e) {
        if (!isMultiple && !activeField) {
            searchField.attr(TABINDEX_PROPERTY, selectedItem.attr(TABINDEX_PROPERTY));
            selectedItem.attr(TABINDEX_PROPERTY, -1);
        }

        container.addClass(css.chznContainerActive());
        activeField = true;

        searchField.val(searchField.val());
        searchField.focus();
        return false;
    }

    private void bind() {
        container.mousedown(new Function() {
            @Override
            public boolean f(Event e) {
                return containerMouseDown(e);

            }

        });

        container.mouseup(new Function() {
            @Override
            public boolean f(Event e) {
                return containerMouseUp(e);
            }

        });

        container.mouseenter(new Function() {
            @Override
            public boolean f(Event e) {
                mouseOnContainer = true;
                return false;
            }
        });

        container.mouseleave(new Function() {
            @Override
            public boolean f(Event e) {
                mouseOnContainer = false;
                return false;
            }
        });

        searchResults.mouseup(new Function() {
            @Override
            public boolean f(Event e) {
                return searchResultsMouseUp(e);

            }
        });

        searchResults.mouseover(new Function() {
            @Override
            public boolean f(Event e) {
                return searchResultsMouseOver(e);
            }
        });

        searchResults.mouseout(new Function() {
            @Override
            public boolean f(Event e) {
                return searchResultsMouseOut(e);

            }
        });

        if (eventBus != null) {
            updateEventHandlerRegistration = eventBus.addHandler(UpdatedEvent.getType(),
                    new UpdatedEvent.UpdatedHandler() {
                        public void onUpdated(UpdatedEvent event) {
                            update();
                        }
                    });
        }

        searchField.blur(new Function() {
            @Override
            public boolean f(Event e) {
                return inputBlur(e);
            }
        });

        searchField.keyup(new Function() {
            @Override
            public boolean f(Event e) {
                return keyupChecker(e);
            }

        });

        searchField.keydown(new Function() {
            @Override
            public boolean f(Event e) {
                return keydownChecker(e);
            }

        });

        if (isMultiple) {
            searchChoices.click(new Function() {
                @Override
                public boolean f(Event e) {
                    return choicesClick(e);
                }

            });
            searchField.focus(new Function() {
                @Override
                public boolean f(Event e) {
                    return inputFocus(e);

                }

            });

        } else {
            container.click(new Function() {
                @Override
                public boolean f(Event e) {
                    e.preventDefault();
                    return false;
                }
            });
        }
    }

    private void blurTest() {
        if (!activeField && container.hasClass(css.chznContainerActive())) {
            closeField();
        }
    }

    private String buildContainerId() {
        String id = null;
        String selectElementId = selectElement.getId();

        if (selectElementId != null && selectElementId.length() > 0) {
            id = containerIdRegExp.replace(selectElementId, "_");
        } else {
            id = generateContainerId();
            selectElement.setId(id);
        }

        id += "_chzn";

        return id;
    }

    private void choiceBuild(OptionItem option) {
        if (isMultiple && maxSelectedOptionsReached()) {
            fireEvent(new MaxSelectedEvent(this));
            return;
        }

        String choiceId = containerId + "_c_" + option.getArrayIndex();
        choices++;
        searchContainer.before(ChozenTemplate.templates.choice(choiceId, css.searchChoice(), option.getHtml(),
                css.searchChoiceClose(), "" + option.getArrayIndex()).asString());
        $('#' + choiceId).find("a").click(new Function() {
            public boolean f(final Event e) {
                choiceDestroyLinkClick(e);
                return false;
            }

        });
    }

    private void choiceDestroy(GQuery link) {
        choices--;
        showSearchFieldDefault();
        if (isMultiple && choices > 0 && searchField.val() != null && searchField.val().length() < 1) {
            resultsHide();
        }

        resultDeselect(Integer.parseInt(link.attr("rel")));
        link.parents("li").first().remove();
    }

    private void choiceDestroyLinkClick(Event e) {
        e.preventDefault();
        if (!isDisabled) {
            pendingDestroyClick = true;
            Element target = e.getEventTarget().cast();
            choiceDestroy($(target));
        } else {
            e.stopPropagation();
        }

    }

    private boolean choicesClick(Event e) {
        e.preventDefault();

        Element target = e.getEventTarget().cast();
        GQuery $e = $(target);

        if (activeField && !($e.hasClass(css.searchChoice()) || !$e.parents("." + css.searchChoice()).isEmpty())
                && !resultsShowing) {
            resultsShow();
        }
        return false;
    }

    private void clearBackstroke() {
        if (pendingBackstroke != null) {
            pendingBackstroke.removeClass(css.searchChoiceFocus());
        }
        pendingBackstroke = null;

    }

    private void closeField() {
        $(document).unbind("click", clickTestAction);

        if (!isMultiple) {
            selectedItem.attr(TABINDEX_PROPERTY, searchField.attr(TABINDEX_PROPERTY));
            searchField.attr(TABINDEX_PROPERTY, "-1");
        }

        activeField = false;

        resultsHide();

        container.removeClass(css.chznContainerActive());
        winnowResultsClear();
        clearBackstroke();

        showSearchFieldDefault();
        searchFieldScale();

    }

    private boolean containerMouseDown(Event e) {
        if (isDisabled) {
            return true;
        }
        Element target = e.getEventTarget().cast();
        GQuery $e = $(target);

        boolean targetCloseLink = $e.hasClass(css.searchChoiceClose());

        if (!resultsShowing) {
            e.stopPropagation();
        }

        if (!pendingDestroyClick && !targetCloseLink) {
            if (!activeField) {
                if (isMultiple) {
                    searchField.val("");
                }
                $(document).click(clickTestAction);
                resultsShow();
            } else if (!isMultiple && !$e.isEmpty()
                    && ($e.get(0) == selectedItem.get(0) || $e.parents("a." + css.chznSingle()).length() > 0)) {
                e.preventDefault();
                resultsToggle();
            }

            activateField(e);

        } else {
            pendingDestroyClick = false;
        }

        return false;
    }

    private boolean containerMouseUp(Event e) {
        Element target = e.getEventTarget().cast();
        GQuery $e = $(target);

        if (!$e.isEmpty() && "ABBR".equalsIgnoreCase($e.get(0).getNodeName()) && !isDisabled) {
            resultsReset(e);
            return false;
        }

        return true;
    }

    private void finishSetup() {
        $selectElement.addClass(css.chznDone(), "chzn-done");

    }

    private void fireEvent(ChosenEvent<?> event) {
        if (eventBus != null) {
            eventBus.fireEvent(event);
        }

    }

    private String generateContainerId() {
        String id = DEFAULT_CONTAINER_ID + idCounter++;

        while (!$("#" + id).isEmpty()) {
            idCounter++;
            id = DEFAULT_CONTAINER_ID + idCounter;
        }
        return id;
    }

    private int getSideBorderPadding(GQuery elmt, boolean isHidden) {
        if (isHidden) {
            //bug in gquery when one parent of the element is hidden
            return (int) (elmt.cur("padding-left", true) + elmt.cur("padding-right", true)
                    + elmt.cur("border-left-width", true) + elmt.cur("border-right-width", true));
        }
        return elmt.outerWidth() - elmt.width();
    }

    private boolean inputBlur(Event e) {
        if (!mouseOnContainer) {
            activeField = false;
            Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
                public boolean execute() {
                    blurTest();
                    return false;
                }
            }, 100);
        }
        return false;
    }

    private boolean inputFocus(final Event e) {
        if (!activeField) {
            Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
                public boolean execute() {
                    containerMouseDown(e);
                    return false;
                }
            }, 50);
        }
        return false;
    }

    private void keydownArrow() {
        if (resultHighlight == null) {
            GQuery firstActive = searchResults.find("li." + css.activeResult()).first();
            if (firstActive != null) {
                resultDoHighlight(firstActive);
            }
        } else if (resultsShowing) {
            // TODO should be replaced by :
            // GQuery nextSibling = resultHighlight.nextAll("li."+css.activeResult()).first();
            // but performance is bad... See http://code.google.com/p/gwtquery/issues/detail?id=146

            GQuery nextSibling = resultHighlight.next();

            while (!nextSibling.isEmpty() && !nextSibling.is("li." + css.activeResult())) {
                nextSibling = nextSibling.next();
            }

            resultDoHighlight(nextSibling);

        }

        if (!resultsShowing) {
            resultsShow();
        }
    }

    private void keydownBackstroke() {
        if (pendingBackstroke != null) {
            choiceDestroy(pendingBackstroke.find("a").first());
            clearBackstroke();
        } else {
            pendingBackstroke = searchContainer.siblings("li." + css.searchChoice()).last();
            if (options.isSingleBackstrokeDelete()) {
                keydownBackstroke();
            } else {
                pendingBackstroke.addClass(css.searchChoiceFocus());
            }
        }

    }

    private boolean keydownChecker(Event e) {
        int stroke = e.getKeyCode();
        searchFieldScale();

        if (stroke != 8 && pendingBackstroke != null) {
            clearBackstroke();
        }

        switch (stroke) {
        case 8: // backspace
            backstrokeLength = searchField.val().length();
            break;

        case 9: // tab
            if (resultsShowing && !isMultiple) {
                resultSelect(e);
            }
            mouseOnContainer = false;
            break;

        case 13: // enter
            if (resultsShowing) {
                e.preventDefault();
                return false;
            }
            return true;

        case 38: // up arrow
            e.preventDefault();
            keyupArrow();
            return false;

        case 40: // down arrow
            this.keydownArrow();
            return false;
        }

        return true;

    }

    private void keyupArrow() {
        if (!resultsShowing && !isMultiple) {
            resultsShow();
        } else if (resultHighlight != null) {
            // TODO should be replaced by :
            // GQuery prevSibs = resultHighlight.prevAll("li." + css.activeResult());
            // but performance is bad... See http://code.google.com/p/gwtquery/issues/detail?id=146

            GQuery prevSibling = resultHighlight.prev();

            while (!prevSibling.isEmpty() && !prevSibling.is("li." + css.activeResult())) {
                prevSibling = prevSibling.prev();
            }

            if (prevSibling.length() > 0) {
                resultDoHighlight(prevSibling.first());
            } else {
                if (choices > 0) {
                    resultsHide();
                }

                resultClearHighlight();
            }
        }

    }

    private boolean keyupChecker(Event e) {
        int stroke = e.getKeyCode();

        searchFieldScale();

        switch (stroke) {
        case 8: // backspace
            if (isMultiple && backstrokeLength < 1 && choices > 0) {
                keydownBackstroke();
            } else if (pendingBackstroke == null) {
                resultClearHighlight();
                resultsSearch();
            }
            break;

        case 13: // enter
            if (resultsShowing) {
                resultSelect(e);
            }
            return true;
        case 27: // escape
            if (resultsShowing) {
                resultsHide();
            }
            return false;
        case 9:
        case 38:
        case 40:
        case 16:
        case 91:
        case 17:
            // do nothing
            break;
        default:
            resultsSearch();
            break;
        }

        return true;
    }

    private boolean maxSelectedOptionsReached() {
        return options.getMaxSelectedOptions() != -1 && options.getMaxSelectedOptions() <= choices;
    }

    private void noResultClear() {
        searchResults.find("." + css.noResults()).remove();
    }

    private void noResults(String terms) {
        GQuery noResults = $(ChozenTemplate.templates.noResults(css.noResults(), resultsNoneFound).asString());
        noResults.find("span").html(terms);

        searchResults.append(noResults);

    }

    private void resultActivate(GQuery query) {
        query.addClass(css.activeResult());

    }

    private SafeHtml resultAddGroup(GroupItem group) {
        if (!group.isDisabled()) {
            group.domId = containerId + "_g_" + group.getArrayIndex();
            return ChozenTemplate.templates.group(group.domId, css.groupResult(), group.getLabel());
        } else {
            return null;
        }
    }

    private SafeHtml resultAddOption(OptionItem option) {
        if (!option.isDisabled()) {
            option.domId = containerId + "_o_" + option.getArrayIndex();

            StringBuilder classes = new StringBuilder();

            if (!(option.isSelected() && isMultiple)) {
                classes.append(css.activeResult()).append(" ");
            }

            if (option.isSelected()) {
                classes.append(css.resultSelected()).append(" ");
            }

            if (option.getGroupArrayIndex() != -1) {
                classes.append(css.groupOption()).append(" ");
            }

            if (option.getClasses() != null) {
                classes.append(option.getClasses());
            }

            String style = option.getStyle();

            return ChozenTemplate.templates.option(option.getDomId(), classes.toString().trim(), style,
                    option.getText());
        }
        return null;
    }

    private void resultClearHighlight() {
        if (resultHighlight != null) {
            resultHighlight.removeClass(css.highlighted());
            resultHighlight = null;
        }

    }

    private void resultDeactivate(GQuery query) {
        query.removeClass(css.activeResult());

    }

    private void resultDeselect(int index) {
        OptionItem item = (OptionItem) selectItems.get(index);

        item.setSelected(false);

        // select option in original element
        OptionElement option = selectElement.getOptions().getItem(item.getOptionsIndex());
        option.setSelected(false);

        $("#" + containerId + "_o_" + index).removeClass(css.resultSelected()).addClass(css.activeResult()).show();

        resultClearHighlight();
        winnowResults();

        fireEvent(new ChosenChangeEvent(option.getValue(), false, this));

        searchFieldScale();
    }

    private void resultDoHighlight(GQuery el) {
        if (el == null || el.length() == 0) {
            return;
        }

        resultClearHighlight();

        resultHighlight = el;

        resultHighlight.addClass(css.highlighted());

        int maxHeight = (int) searchResults.cur("maxHeight", true);
        int visibleTop = searchResults.scrollTop();
        int visibleBottom = maxHeight + visibleTop;

        int highTop = resultHighlight.position().top + searchResults.scrollTop();
        int highBottom = highTop + resultHighlight.outerHeight();

        if (highBottom >= visibleBottom) {
            int toScroll = highBottom - maxHeight;
            searchResults.scrollTop(toScroll > 0 ? toScroll : 0);
        } else if (highTop < visibleTop) {
            searchResults.scrollTop(highTop);
        }

    }

    private void resultsBuild() {

        selectItems = new SelectParser().parse(selectElement);

        if (isMultiple && choices > 0) {
            searchChoices.find("li." + css.searchChoice()).remove();
            choices = 0;
        } else if (!isMultiple) {
            selectedItem.addClass(css.chznDefault()).find("span").text(defaultText);

            if (selectElement.getOptions().getLength() <= options.getDisableSearchThreshold()) {
                container.addClass(css.chznContainerSingleNoSearch());
            } else {
                container.removeClass(css.chznContainerSingleNoSearch());
            }
        }

        SafeHtmlBuilder content = new SafeHtmlBuilder();

        for (int i = 0; i < selectItems.length(); i++) {
            SelectItem item = selectItems.get(i);

            if (item.isGroup()) {
                SafeHtml result = resultAddGroup((GroupItem) item);
                if (result != null) {
                    content.append(result);
                }
            } else {
                OptionItem optionItem = (OptionItem) item;

                if (optionItem.isEmpty()) {
                    continue;
                }

                SafeHtml optionHtml = resultAddOption(optionItem);
                if (optionHtml != null) {
                    content.append(optionHtml);
                }

                if (optionItem.isSelected() && isMultiple) {
                    choiceBuild(optionItem);
                } else if (optionItem.isSelected() && !isMultiple) {
                    selectedItem.removeClass(css.chznDefault()).find("span").text(optionItem.getText());
                    if (allowSingleDeselect) {
                        singleDeselectControlBuild();
                    }
                }
            }
        }
        searchFieldDisabled();
        showSearchFieldDefault();
        searchFieldScale();

        searchResults.html(content.toSafeHtml().asString());

    }

    private void resultSelect(Event e) {
        if (resultHighlight != null) {
            GQuery high = resultHighlight;
            String highId = high.attr("id");

            resultClearHighlight();

            if (isMultiple) {
                resultDeactivate(high);
            } else {
                searchResults.find("." + css.resultSelected()).removeClass(css.resultSelected());
                resultSingleSelected = high;
                selectedItem.removeClass(css.chznDefault());
            }

            high.addClass(css.resultSelected());

            int position = Integer.parseInt(highId.substring(highId.lastIndexOf("_") + 1));
            OptionItem item = (OptionItem) selectItems.get(position);
            item.setSelected(true);
            selectElement.getOptions().getItem(item.getOptionsIndex()).setSelected(true);

            if (isMultiple) {
                choiceBuild(item);
            } else {
                selectedItem.find("span").text(item.getText());
                if (allowSingleDeselect) {
                    singleDeselectControlBuild();
                }
            }

            if (!e.getMetaKey() || !isMultiple) {
                resultsHide();
            }

            searchField.val("");

            if (isMultiple || currentValue == null || !currentValue.equals($selectElement.val())) {
                String value = selectElement.getOptions().getItem(item.getOptionsIndex()).getValue();
                fireEvent(new ChosenChangeEvent(value, this));
            }

            currentValue = $selectElement.val();

            searchFieldScale();
        }

    }

    private void resultsHide() {
        if (!resultsShowing) {
            return;
        }

        if (!isMultiple) {
            selectedItem.removeClass(css.chznSingleWithDrop());
        }

        resultClearHighlight();

        fireEvent(new HidingDropDownEvent(this));

        dropdown.css(isRTL ? "right" : "left", "-9000px");
        resultsShowing = false;

    }

    private void resultsReset(Event e) {
        OptionElement firstoption = selectElement.getOptions().getItem(0);
        if (firstoption != null) {
            firstoption.setSelected(true);
        }

        selectedItem.find("span").text(defaultText);
        if (!isMultiple) {
            selectedItem.addClass(css.chznDefault());
        }

        showSearchFieldDefault();
        resultsResetCleanup();

        fireEvent(new ChosenChangeEvent(null, this));
        if (activeField) {
            resultsHide();
        }

    }

    private void resultsResetCleanup() {
        currentValue = $selectElement.val();
        selectedItem.find("abbr").remove();
    }

    private void resultsSearch() {
        if (resultsShowing) {
            winnowResults();
        } else {
            resultsShow();
        }

    }

    private boolean resultsShow() {
        if (!isMultiple) {
            selectedItem.addClass(css.chznSingleWithDrop());
            if (resultSingleSelected != null) {
                resultDoHighlight(resultSingleSelected);
            }
        } else if (maxSelectedOptionsReached()) {
            fireEvent(new MaxSelectedEvent(this));
            return false;
        }

        int ddTop = isMultiple ? container.height() : container.height() - 1;

        fireEvent(new ShowingDropDownEvent(this));

        dropdown.css("top", ddTop + "px").css(isRTL ? "right" : "left", "0");
        resultsShowing = true;

        searchField.focus();
        searchField.val(searchField.val());

        winnowResults();

        return true;
    }

    private void resultsToggle() {
        if (resultsShowing) {
            resultsHide();
        } else {
            resultsShow();
        }
    }

    private void searchFieldDisabled() {
        isDisabled = selectElement.isDisabled();
        if (isDisabled) {
            container.addClass(css.chznDisabled());
            InputElement.as(searchField.get(0)).setDisabled(true);
            if (!isMultiple) {
                selectedItem.unbind("focus", activateAction);
            }

        } else {
            container.removeClass(css.chznDisabled());
            InputElement.as(searchField.get(0)).setDisabled(false);
            if (!isMultiple) {
                selectedItem.bind("focus", activateAction);
            }

        }
    }

    private void searchFieldScale() {
        if (!isMultiple) {
            return;
        }

        StringBuilder styleBlock = new StringBuilder(
                "position:absolute; " + (isRTL ? "right" : "left") + ": -1000px; top: -1000px; visibility:hidden;");
        String[] styleToCopy = { "font-size", "font-style", "font-weight", "font-family", "line-height",
                "text-transform", "letter-spacing" };

        for (String style : styleToCopy) {
            styleBlock.append(style).append(':').append(searchField.css(style));
        }

        GQuery div = $("<div />").attr("style", styleBlock.toString()).text(searchField.val());
        $("body").append(div);

        int w = div.width() + 25;
        div.remove();

        if (w > fWidth - 10) {
            w = fWidth - 10;
        }

        searchField.css("width", w + "px");

        int ddTop = container.height();
        dropdown.css("top", ddTop + "px");
    }

    private boolean searchResultsMouseOut(Event e) {
        Element targetEl = e.getEventTarget().cast();
        GQuery $e = $(targetEl);

        if ($e.hasClass(css.activeResult()) || $e.parents("." + css.activeResult()).length() > 0) {
            resultClearHighlight();
        }

        return false;
    }

    private boolean searchResultsMouseOver(Event e) {
        Element targetEl = e.getEventTarget().cast();
        GQuery $e = $(targetEl);

        GQuery target = $e.hasClass(css.activeResult()) ? $e : $e.parents("." + css.activeResult()).first();
        if (!target.isEmpty()) {
            resultDoHighlight(target);
        }

        return false;
    }

    private boolean searchResultsMouseUp(Event e) {
        Element targetEvent = e.getEventTarget().cast();
        GQuery $e = $(targetEvent);

        GQuery target = $e.hasClass(css.activeResult()) ? $e : $e.parents("." + css.activeResult()).first();
        if (!target.isEmpty()) {
            resultHighlight = target;
            resultSelect(e);
        }
        return false;
    }

    private void setDefaultText() {

        String dataPlaceHolder = selectElement.getAttribute("data-placeholder");

        if (dataPlaceHolder != null && dataPlaceHolder.length() > 0) {
            defaultText = dataPlaceHolder;
        } else if (isMultiple) {
            if (options.getPlaceholderTextMultiple() != null) {
                defaultText = options.getPlaceholderTextMultiple();
            } else if (options.getPlaceholderText() != null) {
                defaultText = options.getPlaceholderText();
            } else {
                defaultText = "Select Some Options";
            }
        } else {
            if (options.getPlaceholderTextSingle() != null) {
                defaultText = options.getPlaceholderTextSingle();
            } else if (options.getPlaceholderText() != null) {
                defaultText = options.getPlaceholderText();
            } else {
                defaultText = "Select an Option";
            }
        }

        String dataNoResultsText = selectElement.getAttribute("data-no_results_text");
        if (dataNoResultsText != null && dataNoResultsText.length() > 0) {
            resultsNoneFound = dataNoResultsText;
        } else if (options.getNoResultsText() != null) {
            resultsNoneFound = options.getNoResultsText();
        } else {
            resultsNoneFound = "No results match";
        }

    }

    private void setDefaultValues() {
        clickTestAction = new Function() {
            @Override
            public boolean f(Event e) {
                return testActiveClick(e);
            }
        };

        activateAction = new Function() {
            @Override
            public boolean f(Event e) {
                return activateField(e);
            }
        };

        activeField = false;
        mouseOnContainer = false;
        resultsShowing = false;

        NodeList<OptionElement> optionsList = selectElement.getOptions();
        allowSingleDeselect = options.isAllowSingleDeselect() && optionsList.getLength() > 0
                && "".equals(optionsList.getItem(0).getText());

        choices = 0;

        if (options.getResources() != null) {
            css = options.getResources().css();
        } else {
            css = GWT.<Resources>create(Resources.class).css();
        }

        // Force the injection the first time only 
        // If you want to use different css file for different GwtChosen component
        // please register your css files (css.ensureInject()) before the first call of the plugin
        if (!cssInjected) {
            StyleInjector.inject(css.getText(), true);
            cssInjected = true;
        }

    }

    private void setTabIndex() {
        String tabIndexProperty = $selectElement.attr(TABINDEX_PROPERTY);
        if (tabIndexProperty != null && tabIndexProperty.length() > 0) {
            $selectElement.attr(TABINDEX_PROPERTY, -1);

            if (isMultiple) {
                searchField.attr(TABINDEX_PROPERTY, tabIndexProperty);
            } else {
                selectedItem.attr(TABINDEX_PROPERTY, tabIndexProperty);
                searchField.attr(TABINDEX_PROPERTY, -1);
            }
        }

    }

    private void setup() {
        boolean isHidden = false;
        containerId = buildContainerId();
        fWidth = $selectElement.outerWidth();

        //Temporary fix. IIf the select element is inside a hidden container
        //GQuery cannot get the size of the select element.
        if (fWidth == 0) {
            $("body").append("<div id='gwt_chosen_temp_div' style='display:block;position:absolute;"
                    + (isRTL ? "right" : "left") + ":-9000px; visibility:hidden'> </div>");
            GQuery tempDiv = $("#gwt_chosen_temp_div");
            tempDiv.append($selectElement.clone());

            fWidth = tempDiv.children("select").outerWidth();

            tempDiv.remove();
            isHidden = fWidth > 0;

        }

        isRTL = LocaleInfo.getCurrentLocale().isRTL() || $selectElement.hasClass("chzn-rtl");

        String cssClasses = isRTL ? css.chznContainer() + " " + css.chznRtl() : css.chznContainer();

        GQuery containerTemp = $(ChozenTemplate.templates.container(containerId, cssClasses).asString())
                .width(fWidth);

        if (isMultiple) {
            containerTemp.html(ChozenTemplate.templates.contentMultiple(css.chznChoices(), css.searchField(),
                    defaultText, css.defaultClass(), css.chznDrop(), css.chznResults(), (isRTL ? "right" : "left"))
                    .asString());
        } else {
            containerTemp.html(ChozenTemplate.templates.contentSingle(css.chznSingle(), css.chznDefault(),
                    defaultText, css.chznDrop(), css.chznSearch(), css.chznResults(), (isRTL ? "right" : "left"))
                    .asString());
        }

        // insert container after the select elements
        $selectElement.hide().after(containerTemp);
        container = $("#" + containerId);
        container.addClass(isMultiple ? css.chznContainerMulti() : css.chznContainerSingle());

        dropdown = container.find("div." + css.chznDrop()).first();
        int ddTop = container.height();
        int ddWidth = fWidth - getSideBorderPadding(dropdown, isHidden);
        dropdown.css(Properties.create("{\"width\": " + ddWidth + "px, \"top\": " + ddTop + "px}"));

        searchField = container.find("input").first();
        searchResults = container.find("ul." + css.chznResults()).first();
        searchFieldScale();

        if (isMultiple) {
            searchChoices = container.find("ul." + css.chznChoices()).first();
            searchContainer = container.find("li." + css.searchField()).first();
        } else {
            searchContainer = container.find("div." + css.chznSearch()).first();
            selectedItem = container.find("." + css.chznSingle()).first();
            int searchFieldWidth = ddWidth - getSideBorderPadding(searchContainer, isHidden)
                    - getSideBorderPadding(searchField, isHidden);
            searchField.css("width", searchFieldWidth + "px");
        }

        resultsBuild();

        setTabIndex();

        fireEvent(new ReadyEvent(this));

    }

    private void showSearchFieldDefault() {
        if (isMultiple && choices < 1 && !activeField) {
            searchField.val(defaultText);
            searchField.addClass(css.defaultClass());
        } else {
            searchField.val("");
            searchField.removeClass(css.defaultClass());
        }
    }

    private void singleDeselectControlBuild() {
        if (allowSingleDeselect && selectedItem.find("abbr").isEmpty()) {
            selectedItem.find("span").first().after("<abbr class=\"" + css.searchChoiceClose() + "\"></abbr>");
        }

    }

    private boolean testActiveClick(Event e) {

        Element target = e.getEventTarget().cast();
        GQuery $e = $(target);

        if ($e.parents("#" + containerId).length() > 0) {
            activeField = true;
        } else {
            closeField();
        }
        return false;
    }

    private void winnowResults() {

        noResultClear();

        int results = 0;

        String searchText = defaultText.equals(searchField.val()) ? "" : searchField.val().trim();
        searchText = SafeHtmlUtils.htmlEscape(searchText);

        String regexAnchor = options.isSearchContains() ? "" : "^";
        // escape reg exp special chars
        String escapedSearchText = regExpChars.replace(searchText, "\\$&");
        String test2 = "test";
        test2.substring(1);
        RegExp regex = RegExp.compile(regexAnchor + escapedSearchText, "i");
        RegExp zregex = RegExp.compile("(" + escapedSearchText + ")", "i");

        for (int i = 0; i < selectItems.length(); i++) {
            SelectItem item = selectItems.get(i);

            if (item.isDisabled() || item.isEmpty()) {
                continue;
            }

            if (item.isGroup()) {
                $('#' + item.getDomId()).css("display", "none");
            } else {
                OptionItem option = (OptionItem) item;

                if (!(isMultiple && option.isSelected())) {
                    boolean found = false;
                    String resultId = option.getDomId();
                    GQuery result = $("#" + resultId);
                    String optionContent = option.getHtml();

                    if (regex.test(optionContent)) {
                        found = true;
                        results++;
                    } else if (optionContent.indexOf(" ") >= 0 || optionContent.indexOf("[") == 0) {
                        String[] parts = optionContent.replaceAll("\\[|\\]", "").split(" ");
                        for (String part : parts) {
                            if (regex.test(part)) {
                                found = true;
                                results++;
                            }
                        }
                    }

                    if (found) {
                        String text;
                        if (searchText.length() > 0) {
                            text = zregex.replace(optionContent, "<em>$1</em>");
                        } else {
                            text = optionContent;
                        }

                        result.html(text);
                        resultActivate(result);

                        if (option.getGroupArrayIndex() != -1) {
                            $("#" + selectItems.get(option.getGroupArrayIndex()).getDomId()).css("display",
                                    "list-item");
                        }
                    } else {
                        if (resultHighlight != null && resultId.equals(resultHighlight.attr("id"))) {
                            resultClearHighlight();
                        }
                        resultDeactivate(result);
                    }

                }

            }

        }

        if (results < 1 && searchText.length() > 0) {
            noResults(searchText);
        } else {
            winnowResultsSetHighlight();
        }
    }

    private void winnowResultsClear() {
        searchField.val("");
        GQuery lis = searchResults.find("li");

        for (Element li : lis.elements()) {
            GQuery $li = $(li);
            if ($li.hasClass(css.groupResult())) {
                $li.css("display", "");
            } else if (!isMultiple || !$li.hasClass(css.resultSelected())) {
                resultActivate($li);
            }
        }

    }

    private void winnowResultsSetHighlight() {
        if (resultHighlight == null) {
            GQuery selectedResults = !isMultiple
                    ? searchResults.find("." + css.resultSelected() + "." + css.activeResult())
                    : null;

            GQuery doHigh = selectedResults != null && selectedResults.length() > 0 ? selectedResults.first()
                    : searchResults.find("." + css.activeResult()).first();

            if (doHigh != null) {
                resultDoHighlight(doHigh);
            }
        }

    }

}