org.openelis.gwt.widget.AutoComplete.java Source code

Java tutorial

Introduction

Here is the source code for org.openelis.gwt.widget.AutoComplete.java

Source

/** Exhibit A - UIRF Open-source Based Public Software License.
* 
* The contents of this file are subject to the UIRF Open-source Based
* Public Software License(the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
* openelis.uhl.uiowa.edu
* 
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* 
* The Original Code is OpenELIS code.
* 
* The Initial Developer of the Original Code is The University of Iowa.
* Portions created by The University of Iowa are Copyright 2006-2008. All
* Rights Reserved.
* 
* Contributor(s): ______________________________________.
* 
* Alternatively, the contents of this file marked
* "Separately-Licensed" may be used under the terms of a UIRF Software
* license ("UIRF Software License"), in which case the provisions of a
* UIRF Software License are applicable instead of those above. 
*/
package org.openelis.gwt.widget;

import java.util.ArrayList;

import org.openelis.ui.common.data.QueryData;
import org.openelis.gwt.event.BeforeGetMatchesEvent;
import org.openelis.gwt.event.BeforeGetMatchesHandler;
import org.openelis.gwt.event.GetMatchesEvent;
import org.openelis.gwt.event.GetMatchesHandler;
import org.openelis.gwt.event.HasBeforeGetMatchesHandlers;
import org.openelis.gwt.event.HasGetMatchesHandlers;
import org.openelis.gwt.screen.TabHandler;
import org.openelis.gwt.widget.table.TableDataRow;
import org.openelis.gwt.widget.table.TableRenderer;
import org.openelis.gwt.widget.table.TableView;
import org.openelis.gwt.widget.table.TableView.VerticalScroll;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasMouseOutHandlers;
import com.google.gwt.event.dom.client.HasMouseOverHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.HorizontalPanel;

/**
 * This widget class implements a textbox that listens to keystrokes and will fire a GetMatchesEvent to a
 * registered handler that will return a list of suggestions that are displayed in a dropdown window for the
 * user to choose from.
 * 
 * @author tschmidt
 *
 * @param <T>
 *        generic parameter used to define the Type of Key to used by the widget.  
 */
public class AutoComplete<T> extends DropdownWidget implements FocusHandler, BlurHandler, HasValue<T>,
        HasBeforeGetMatchesHandlers, HasGetMatchesHandlers, HasMouseOutHandlers, HasMouseOverHandlers {

    private AutoCompleteListener listener = new AutoCompleteListener(this);
    private Field<T> field;
    private boolean enabled;
    IconContainer icon = new IconContainer();
    public String dropwidth;
    public int minWidth;
    HorizontalPanel hp;
    private int delay = 350;

    private class AutoCompleteListener implements KeyUpHandler, ClickHandler {

        private AutoComplete widget;

        public AutoCompleteListener(AutoComplete widget) {
            this.widget = widget;
        }

        /**
         * Catches key events from the AutoComplete widget and determines if getMatches should be called.  We set a delay so
         * that if multiple key strokes are entered quickly we don't call getMathces with calls that will most likely be never seen by the 
         * user. 
         */
        public void onKeyUp(KeyUpEvent event) {
            if (widget.queryMode)
                return;
            if (!widget.textbox.isReadOnly()) {
                if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN || event.getNativeKeyCode() == KeyCodes.KEY_UP
                        || event.getNativeKeyCode() == KeyCodes.KEY_TAB
                        || event.getNativeKeyCode() == KeyCodes.KEY_LEFT
                        || event.getNativeKeyCode() == KeyCodes.KEY_RIGHT
                        || event.getNativeKeyCode() == KeyCodes.KEY_ALT
                        || event.getNativeKeyCode() == KeyCodes.KEY_CTRL
                        || event.getNativeKeyCode() == KeyCodes.KEY_SHIFT
                        || event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE)
                    return;
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER && !widget.popup.isShowing()
                        && !widget.itemSelected && widget.focused) {
                    widget.showTable();
                    return;
                }
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER && widget.itemSelected) {
                    widget.itemSelected = false;
                    return;
                }
                String text = widget.textbox.getText();
                if (text.length() > 0 && !text.endsWith("*")) {
                    widget.setDelay(text, delay);
                } else {
                    widget.hideTable();
                }
            }
        }

        /**
         * Call getMatches with whatever is in the the textbox to show the model
         * If empty then will return the top of the listed sorted alphabetically
         */
        public void onClick(ClickEvent event) {
            try {
                getMatches(textbox.getText());
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    private class Handler implements MouseOutHandler, MouseOverHandler {

        AutoComplete<T> source;

        public Handler(AutoComplete<T> source) {
            this.source = source;
        }

        public void onMouseOut(MouseOutEvent event) {
            //MouseOutEvent.fireNativeEvent(event.getNativeEvent(), source);

        }

        public void onMouseOver(MouseOverEvent event) {
            //MouseOverEvent.fireNativeEvent(event.getNativeEvent(), source);

        }

    }

    Handler mouseHandler = new Handler(this);

    /**
     * Default No-Arg constructor
     */
    public AutoComplete() {

    }

    /**
     * This method is called to setup the widget after all options are set into the widget
     * That is why this code is not located in a constructor.
     */
    public void init() {
        if (maxRows == 0)
            maxRows = 10;
        renderer = new TableRenderer(this);
        view = new TableView(this, showScroll);
        view.setWidth(width);
        keyboardHandler = this;
        hp = new HorizontalPanel();
        hp.add(textbox);
        hp.add(icon);
        setWidget(hp);
        hp.setWidth(width);
        setStyleName("AutoDropdown");
        textbox.setStyleName("TextboxUnselected");
        textbox.addFocusHandler(this);
        textbox.addBlurHandler(this);
        popup.setStyleName("DropdownPopup");
        popup.setWidget(view);
        popup.addCloseHandler(this);
        textbox.addKeyUpHandler(listener);
        textbox.setReadOnly(!enabled);
        textbox.addMouseOutHandler(mouseHandler);
        textbox.addMouseOverHandler(mouseHandler);
        icon.setStyleName("AutoDropdownButton");
        icon.addClickHandler(listener);
        icon.addFocusHandler(this);
        addDomHandler(keyboardHandler, KeyDownEvent.getType());
        addDomHandler(keyboardHandler, KeyUpEvent.getType());
    }

    /**
     * Sinks a KeyPressEvent for this widget attaching a TabHandler that will override the default
     * browser tab order for the tab order defined by the screen for this widget.
     * 
     * @param handler
     *        Instance of TabHandler that controls tabing logic for widget.
     */
    public void addTabHandler(TabHandler handler) {
        addDomHandler(handler, KeyPressEvent.getType());
    }

    /**
     * Call this function with true to enable the widget and false to disable.
     * 
     * @param enabled
     */
    public void enable(boolean enabled) {
        this.enabled = enabled;
        textbox.setReadOnly(!enabled);
        icon.enable(enabled);
        //super.enable(enabled);
    }

    /**
     * Method that returns true if the widget is currently enabled for use.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     *  Overrides the getMatches method from the super DropdownWidget to fire the getMatches events.
     *  
     *  If a handler is registered, a BeforeGetMatchesEvent will be fired and the the cancel flag will
     *  be checked before firing the GetMatchesEvent.
     *  
     *  Will throw an exception if no handler is registered to the widget.
     */
    protected void getMatches(final String text) throws Exception {
        if (!field.queryMode) {
            try {
                if (getHandlerCount(BeforeGetMatchesEvent.getType()) > 0) {
                    BeforeGetMatchesEvent event = BeforeGetMatchesEvent.fire(this, text);
                    if (event.isCancelled())
                        return;
                }
                if (getHandlerCount(GetMatchesEvent.getType()) > 0)
                    GetMatchesEvent.fire(this, text);
                else
                    throw new Exception("No GetMatchesHandler registered to AutoComplete Widget");

            } catch (Exception e) {
                e.printStackTrace();
                Window.alert(e.getMessage());
            }
        }
    }

    /**
     * Displays the suggestions supplied from a GetMatchesEvent Handler.
     * 
     * @param data
     *        A model for the DropdownWidget to display for matching suggestions.
     */
    public void showAutoMatches(ArrayList<TableDataRow> data) {
        selectedRow = -1;
        selectedCol = -1;
        if (data == null || data.size() == 0) {
            data = new ArrayList<TableDataRow>();
            data.add(new TableDataRow(null, ""));
        }
        load(data);
        if (textbox.getStyleName().indexOf("Focus") > -1) {
            selectRow(0);
            view.setHeight(Math.min(maxRows, data.size()) * cellHeight);
            showTable();
        } else if (data != null && data.size() > 0) {
            setValue((T) data.get(0).key, true);
            field.checkValue(this);
            field.drawExceptions(this);
        } else {
            setSelection(null, "");
            field.checkValue(this);
            field.drawExceptions(this);
            if (fireEvents)
                ValueChangeEvent.fire(this, null);
        }

    }

    /**
     * Loads a default model to the widget to be used for displaying an initial value before the user
     * has used the widget.
     * @param model
     */
    public void setModel(ArrayList<TableDataRow> model) {
        this.load(model);
    }

    /**
     * Returns the key value for the selected option in the current model of suggestions for the widget.
     */
    public T getValue() {
        if (getSelectedRow() > -1)
            return (T) getRow(getSelectedRow()).key;
        else
            return null;
    }

    /**
     * Sets the current value of the widget.  Does not fire ValueChangeEvent.
     */
    public void setValue(T value) {
        setValue(value, false);
    }

    /**
     * Sets the current value of the widget and fires a ValueChangeEvent to any registered handlers
     * if the fireEvents param is passed as true.
     */
    public void setValue(T value, boolean fireEvents) {
        T old = field.getValue();
        setSelection(value);
        field.setValue(value);
        if (fireEvents)
            ValueChangeEvent.fireIfNotEqual(this, old, value);
    }

    /**
     * This method takes a key, value pair and will create a default model with a TableDataRow with those values
     * and sets the value the widget to that row.  Used when displaying data returned from a fetch call.
     * 
     * @param key
     *       Key value for the selected row 
     * @param display
     *       Display text for the selected row
     */
    public void setSelection(T key, String display) {
        ArrayList<TableDataRow> model = new ArrayList<TableDataRow>();
        model.add(new TableDataRow(key, display));
        setModel(model);
        setSelection(key);
        field.setValue(key);
    }

    /**
     * This method takes a TableDataRow and will create a default model with that TableDataRow 
     * and sets the value of he widget to that row.  Used mostly when displaying values when the widget 
     * is used in a table.
     * 
     */
    public void setSelection(TableDataRow row) {
        ArrayList<TableDataRow> model = new ArrayList<TableDataRow>();
        model.add(row);
        setModel(model);
        setSelection(row.key);
        field.setValue((T) row.key);
    }

    /**
     * Registers ValueChangeHandler to the widget
     */
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    /**
     * Registers a handler to widgets field to get notified when its field value changes.
     */
    public HandlerRegistration addFieldValueChangeHandler(ValueChangeHandler handler) {
        return addValueChangeHandler(handler);
    }

    /**
     * Adds a an error to the widget.  An error style is added to the widget and on MouseOver a popup will 
     * be displayed with errors.
     */
    public void addException(Exception error) {
        field.addException(error);
        field.drawExceptions(this);
    }

    /**
     * Clears the error list and removes the error style from the widget
     */
    public void clearExceptions() {
        field.clearExceptions(this);
    }

    /**
     * Returns the Field used by this widget to hold errors and validate data
     */
    public Field getField() {
        return field;
    }

    /**
     * Sets the Field to be used by this widget to display erorros, validate data, and format the
     * output of the widget if necessary.
     */
    public void setField(Field field) {
        this.field = field;
        addBlurHandler(field);
        addMouseOutHandler(field);
        addMouseOverHandler(field);
    }

    /**
     * Receives notice when this widget is focused and adds the Focus style.
     */
    public void onFocus(FocusEvent event) {
        if (isEnabled())
            textbox.addStyleName("Focus");
    }

    /**
     * Receives notice when this widget loses focus.  Removes the focus style and fires ValuChangeEvent to handlers.
     */
    public void onBlur(BlurEvent event) {
        textbox.removeStyleName("Focus");
        if (!showingOptions && isEnabled() && !field.queryMode) {
            if ("".equals(textbox.getText()) && field.getValue() != null) {
                setSelection(null, "");
                ValueChangeEvent.fire(this, null);
            } else {
                setValue(getValue(), true);
            }
            checkValue();
        }
    }

    @Override
    public void setFocus(boolean focus) {
        textbox.setFocus(focus);
        if (getSelection() != null) {
            if (getSelection().display != null) {
                if (columns.get(0).getColumnWidget() instanceof Dropdown) {
                    ((Dropdown) columns.get(0).getColumnWidget()).setValue(getSelection().getCells().get(0));
                    textbox.setText(((Dropdown) columns.get(0).getColumnWidget()).getTextBoxDisplay());
                } else {
                    String textValue = "";
                    textValue = (String) getSelection().getCells().get(0) + (!"".equals(textValue) ? "|" : "")
                            + textValue;
                    textbox.setText(textValue);
                }
            }
        }
    }

    @Override
    public void setWidth(String width) {
        if (hp != null && width != null)
            hp.setWidth(width);
        int index = width.indexOf("px");
        int wid = 0;
        if (index > 0)
            wid = Integer.parseInt(width.substring(0, index)) - 16;
        else
            wid = Integer.parseInt(width) - 16;
        if (wid + 16 > minWidth)
            dropwidth = (wid + 16) + "px";
        else
            dropwidth = minWidth + "px";
        //view.setWidth(dropwidth);
        super.setWidth(wid + "px");
    }

    /**
     * Puts the field into Query Mode to be used to accept query strings instead of normal data.
     */
    public void setQueryMode(boolean query) {
        if (query == field.queryMode)
            return;
        load(null);
        field.setQueryMode(query);

    }

    /**
     * called to validate the current value set to the widget and will display error if necessary.
     */
    @Override
    public void checkValue() {
        if (!field.queryMode)
            field.checkValue(this);
    }

    /**
     * If the widget is in Query mode and the queryString value is not null, a QueryData object will be created 
     * and added to the list passed into the method.  
     */
    public void getQuery(ArrayList list, String key) {
        if (!field.queryMode)
            return;
        if (textbox.getText() != null && !textbox.getText().equals("")) {
            QueryData qd = new QueryData();
            qd.setKey(key);
            qd.setQuery(textbox.getText());
            qd.setType(QueryData.Type.STRING);
            list.add(qd);
        }
    }

    /**
     * The current list of error for this widget.
     */
    @Override
    public ArrayList<Exception> getExceptions() {
        return field.exceptions;
    }

    /**
     * Returns the curent value in the field for this widget.
     */
    public Object getFieldValue() {
        return getValue();
    }

    /**
     * Sets the value of the field to the object passed in the method.
     */
    public void setFieldValue(Object value) {
        if (field.queryMode)
            field.setStringValue((String) value);
        else
            setValue((T) value);
    }

    @Override
    /**
     * Sets the value of the widget to the chosen value by the user in the dropdown 
     * and closes the popup.
     */
    public void complete() {
        super.complete();
        //field.setValue(getValue());
        clearExceptions();
        //ValueChangeEvent.fire(this, getValue());
        //checkValue();
        textbox.setFocus(true);

    }

    /**
     * Registers a BeforeGetMatchesHandler to the widget.
     */
    public HandlerRegistration addBeforeGetMatchesHandler(BeforeGetMatchesHandler handler) {
        return addHandler(handler, BeforeGetMatchesEvent.getType());
    }

    /**
     * Registers a GetMatchesHandler to the widget.
     */
    public HandlerRegistration addGetMatchesHandler(GetMatchesHandler handler) {
        return addHandler(handler, GetMatchesEvent.getType());
    }

    @Override
    public void addExceptionStyle(String style) {
        textbox.addStyleName(style);
    }

    @Override
    public void removeExceptionStyle(String style) {
        textbox.removeStyleName(style);
    }

    @Override
    public Object getWidgetValue() {
        return getValue();
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }
}