com.extjs.gxt.ui.client.widget.form.ComboBox Maven / Gradle / Ivy
Show all versions of gxt Show documentation
/*
* Ext GWT 2.2.0 - Ext for GWT
* Copyright(c) 2007-2010, Ext JS, LLC.
* [email protected]
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.form;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style.Scroll;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.core.XTemplate;
import com.extjs.gxt.ui.client.data.BaseLoader;
import com.extjs.gxt.ui.client.data.BasePagingLoadConfig;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.PagingLoadConfig;
import com.extjs.gxt.ui.client.data.PagingLoader;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.DomEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.ListViewEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.PreviewEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedListener;
import com.extjs.gxt.ui.client.event.SelectionProvider;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.store.StoreListener;
import com.extjs.gxt.ui.client.util.BaseEventPreview;
import com.extjs.gxt.ui.client.util.DelayedTask;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Util;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.ListView;
import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.RootPanel;
/**
* A combobox component.
*
*
* When not forcing a selection ({@link #setForceSelection(boolean)})
* {@link #getValue()} can return null event if the user has typed text into the
* field if that text cannot be tied to a model from from the combo's store. In
* this case, you can use {@link #getRawValue()} to get the fields string value.
*
*
* Combo uses a XTemplate
to render it's drop down list. A custom
* template can be specified to customize the display of the drop down list. See
* {@link #setTemplate(XTemplate)}.
*
*
* A custom PropertyEditor
can be used to "format" the value that
* is displayed in the combo's text field. For example:
*
*
* combo.setPropertyEditor(new ListModelPropertyEditor<State>() {
* public String getStringValue(State value) {
* return value.getAbbr() + " " + value.getName();
* }
* });
*
*
* A ModelProcessor
can be used to "format" the values in the drop
* down list:
*
*
* combo.getView().setModelProcessor(new ModelProcessor<State>() {
* public State prepareData(State model) {
* model.set("test", model.getAbbr() + " " + model.getName());
* return model;
* }
* });
*
*
*
* - Events:
* - Expand : FieldEvent(field)
* Fires when the dropdown list is expanded.
*
* - field : this
*
*
*
* - Collapse : FieldEvent(field)
* Fires when the dropdown list is collapsed.
*
* - field : this
*
*
*
* - BeforeSelect : FieldEvent(field)
* Fires before a list item is selected. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.
*
* - field : this
*
*
* - Select : FieldEvent(field)
* Fires when a list item is selected.
*
* - field : this
*
*
*
* - BeforeQuery : FieldEvent(field, value)
* Fires before all queries are processed. Listeners can cancel the action
* by calling {@link BaseEvent#setCancelled(boolean)}.
*
* - field : this
* - value : query
*
*
*
*
*
* - Inherited Events:
* - Field Focus
* - Field Blur
* - Field Change
* - Field Invalid
* - Field Valid
* - Field KeyPress
* - Field SpecialKey
* - TriggerField TriggerClick
*
*
* @param the model data type
*/
public class ComboBox extends TriggerField implements SelectionProvider {
/**
* ComboBox error messages.
*/
public class ComboBoxMessages extends TextFieldMessages {
private String loadingText = GXT.MESSAGES.loadMask_msg();
private String valueNoutFoundText;
/**
* Returns the loading text.
*
* @return the loading text
*/
public String getLoadingText() {
return loadingText;
}
/**
* Returns the value not found error text.
*
* @return the error text
*/
public String getValueNoutFoundText() {
return valueNoutFoundText;
}
/**
* Sets the loading text.
*
* @param loadingText the loading text
*/
public void setLoadingText(String loadingText) {
this.loadingText = loadingText;
}
/**
* When using a name/value combo, if the value passed to setValue is not
* found in the store, valueNotFoundText will be displayed as the field text
* if defined.
*
* @param valueNoutFoundText
*/
public void setValueNoutFoundText(String valueNoutFoundText) {
this.valueNoutFoundText = valueNoutFoundText;
}
}
/**
* TriggerAction enum.
*/
public enum TriggerAction {
ALL, QUERY;
}
protected boolean autoComplete = false;
protected boolean delayedCheck;
protected String lastQuery;
protected ListStore store;
private String allQuery = "";
private BaseEventPreview eventPreview;
private boolean expanded;
private El footer;
private boolean forceSelection;
private InputElement hiddenInput;
private String itemSelector;
private String lastSelectionText;
private boolean lazyRender = true, initialized;
private LayoutContainer list;
private String listAlign = "tl-bl?";
private String listStyle = "x-combo-list";
private ListView listView;
private int maxHeight = 300;
private int minChars = 4;
private int minListWidth = 70;
private String mode = "remote";
private int pageSize;
private PagingToolBar pageTb;
private int queryDelay = 500;
private D selectedItem;
private String selectedStyle = "x-combo-selected";
private StoreListener storeListener;
private DelayedTask taTask, dqTask;
private XTemplate template;
private TriggerAction triggerAction = TriggerAction.QUERY;
private boolean typeAhead;
private int typeAheadDelay = 250;
private boolean useQueryCache = true;
private String valueField;
/**
* Creates a combo box.
*/
public ComboBox() {
messages = new ComboBoxMessages();
listView = new ListView();
setPropertyEditor(new ListModelPropertyEditor());
monitorWindowResize = true;
windowResizeDelay = 0;
initComponent();
}
public void addSelectionChangedListener(SelectionChangedListener listener) {
addListener(Events.SelectionChange, listener);
}
@Override
public void clear() {
getStore().clearFilters();
boolean f = forceSelection;
forceSelection = false;
super.clear();
forceSelection = f;
}
/**
* Clears any text/value currently set in the field.
*/
public void clearSelections() {
setRawValue("");
lastSelectionText = "";
applyEmptyText();
value = null;
}
/**
* Hides the dropdown list if it is currently expanded. Fires the
* Collapse event on completion.
*/
public void collapse() {
if (!expanded) {
return;
}
eventPreview.remove();
expanded = false;
list.hide();
RootPanel.get().remove(list);
if (GXT.isAriaEnabled()) {
// inspect 32 is keeping focus on hidden list item in dropdown
input.blur();
input.focus();
}
fireEvent(Events.Collapse, new FieldEvent(this));
}
/**
* Execute a query to filter the dropdown list. Fires the BeforeQuery event
* prior to performing the query allowing the query action to be canceled if
* needed.
*
* @param q the query
* @param forceAll true to force the query to execute even if there are
* currently fewer characters in the field than the minimum specified
* by the minChars config option. It also clears any filter
* previously saved in the current store
*/
public void doQuery(String q, boolean forceAll) {
if (q == null) {
q = "";
}
FieldEvent fe = new FieldEvent(this);
fe.setValue(q);
if (!fireEvent(Events.BeforeQuery, fe)) {
return;
}
if (forceAll || q.length() >= minChars) {
if (!useQueryCache || !q.equals(lastQuery)) {
lastQuery = q;
if (mode.equals("local")) {
selectedItem = null;
store.filter(getDisplayField(), q);
onLoad(null);
} else {
expand();
store.getLoader().load(getParams(q));
}
} else {
selectedItem = null;
onLoad(null);
}
}
}
/**
* Expands the dropdown list if it is currently hidden. Fires the
* expand event on completion.
*/
public void expand() {
if (expanded || !hasFocus) {
return;
}
if (!initialized) {
createList(false);
} else {
RootPanel.get().add(list);
}
list.show();
list.layout();
list.el().updateZIndex(0);
restrict();
eventPreview.add();
expanded = true;
fireEvent(Events.Expand, new FieldEvent(this));
}
/**
* Returns the all query.
*
* @return the all query
*/
public String getAllQuery() {
return allQuery;
}
/**
* Returns the display field.
*
* @return the display field
*/
public String getDisplayField() {
return getPropertyEditor().getDisplayProperty();
}
/**
* Returns true if the field's value is forced to one of the value in the
* list.
*
* @return the force selection state
*/
public boolean getForceSelection() {
return forceSelection;
}
/**
* Returns the item selector.
*
* @return the item selector
*/
public String getItemSelector() {
return itemSelector;
}
/**
* Returns the list's list align value.
*
* @return the list align value
*/
public String getListAlign() {
return listAlign;
}
/**
* Returns the list style.
*
* @return the list style
*/
public String getListStyle() {
return listStyle;
}
/**
* Returns the combo's list view.
*
* @return the view
*/
public ListView getListView() {
return listView;
}
/**
* Returns the loading text.
*
* @return the loading text
*/
public String getLoadingText() {
return getMessages().getLoadingText();
}
/**
* Returns the dropdown list's max height.
*
* @return the max height
*/
public int getMaxHeight() {
return maxHeight;
}
@SuppressWarnings("unchecked")
@Override
public ComboBoxMessages getMessages() {
return (ComboBoxMessages) messages;
}
/**
* Returns the min characters used for autocompete and typeahead.
*
* @return the minimum number of characters
*/
public int getMinChars() {
return minChars;
}
/**
* Returns the dropdown list's min width.
*
* @return the min width
*/
public int getMinListWidth() {
return minListWidth;
}
/**
* Returns the page size.
*
* @return the page size
*/
public int getPageSize() {
return pageSize;
}
/**
* Returns the combo's paging tool bar.
*
* @return the tool bar
*/
public PagingToolBar getPagingToolBar() {
return pageTb;
}
@Override
public ListModelPropertyEditor getPropertyEditor() {
return (ListModelPropertyEditor) propertyEditor;
}
/**
* Returns the query delay.
*
* @return the query delay
*/
public int getQueryDelay() {
return queryDelay;
}
/**
* Returns the selected style.
*
* @return the selected style
*/
public String getSelectedStyle() {
return selectedStyle;
}
public List getSelection() {
List sel = new ArrayList();
D v = getValue();
if (v != null) {
sel.add(v);
}
return sel;
}
/**
* Returns the combo's store.
*
* @return the store
*/
public ListStore getStore() {
return store;
}
/**
* Returns the custom template.
*
* @return the template
*/
public XTemplate getTemplate() {
return template;
}
/**
* Returns the trigger action.
*
* @return the trigger action
*/
public TriggerAction getTriggerAction() {
return triggerAction;
}
/**
* Returns the type ahead delay in milliseconds.
*
* @return the type ahead delay
*/
public int getTypeAheadDelay() {
return typeAheadDelay;
}
@Override
public D getValue() {
if (!initialized) {
return value;
}
if (store != null) {
getPropertyEditor().setList(store.getModels());
}
doForce();
return super.getValue();
}
/**
* Returns the value field name.
*
* @return the value field name
*/
public String getValueField() {
return valueField;
}
/**
* Returns the combo's list view.
*
* @return the view
*/
public ListView getView() {
return listView;
}
/**
* Returns true
if the panel is expanded.
*
* @return the expand state
*/
public boolean isExpanded() {
return expanded;
}
/**
* Returns true if lazy rendering is enabled.
*
* @return true of lazy rendering
*/
public boolean isLazyRender() {
return lazyRender;
}
/**
* Returns true if type ahead is enabled.
*
* @return the type ahead state
*/
public boolean isTypeAhead() {
return typeAhead;
}
/**
* Returns the state if the query cache is used or not.
*
* @return the useQueryCache state
*/
public boolean isUseQueryCache() {
return useQueryCache;
}
public void removeSelectionListener(SelectionChangedListener listener) {
removeListener(Events.SelectionChange, listener);
}
@Override
public void reset() {
getStore().clearFilters();
boolean f = forceSelection;
forceSelection = false;
super.reset();
forceSelection = f;
}
public void select(D sel) {
if (listView != null && sel != null) {
int index = store.indexOf(sel);
selectedItem = sel;
if (index < listView.getElements().size()) {
listView.getSelectionModel().select(sel, false);
fly(listView.getElement(index)).scrollIntoView(listView.getElement(), false);
}
}
}
/**
* Select an item in the dropdown list by its numeric index in the list. This
* function does NOT cause the select event to fire. The list must expanded
* for this function to work, otherwise use #setValue.
*
* @param index the index of the item to select
*/
public void select(int index) {
select(store.getAt(index));
}
/**
* The text query to send to the server to return all records for the list
* with no filtering (defaults to '').
*
* @param allQuery the all query
*/
public void setAllQuery(String allQuery) {
this.allQuery = allQuery;
}
/**
* The underlying data field name to bind to this ComboBox (defaults to
* 'text').
*
* @param displayField the display field
*/
public void setDisplayField(String displayField) {
getPropertyEditor().setDisplayProperty(displayField);
}
/**
* Sets the panel's expand state.
*
* @param expand true true to expand
*/
public void setExpanded(boolean expand) {
this.expanded = expand;
if (isRendered()) {
if (expand) {
expand();
} else {
collapse();
}
}
}
/**
* Sets whether the combo's value is restricted to one of the values in the
* list, false to allow the user to set arbitrary text into the field
* (defaults to false).
*
* @param forceSelection true to force selection
*/
public void setForceSelection(boolean forceSelection) {
this.forceSelection = forceSelection;
}
/**
* This setting is required if a custom XTemplate has been specified.
*
* @param itemSelector the item selector
*/
public void setItemSelector(String itemSelector) {
this.itemSelector = itemSelector;
}
/**
* True to lazily render the combo's drop down list (default to true,
* pre-render).
*
* @param lazyRender true to lazy render the drop down list
*/
public void setLazyRender(boolean lazyRender) {
this.lazyRender = lazyRender;
}
/**
* Sets a valid anchor position value. See {@link El#alignTo} for details on
* supported anchor positions (defaults to 'tl-bl?').
*
* @param listAlign the new list align value
*/
public void setListAlign(String listAlign) {
this.listAlign = listAlign;
}
/**
* Sets the style for the drop down list (defaults to 'x-combo-list');
*
* @param listStyle the list style
*/
public void setListStyle(String listStyle) {
this.listStyle = listStyle;
}
/**
* Sets the loading text.
*
* @param loadingText the loading text
*/
public void setLoadingText(String loadingText) {
getMessages().setLoadingText(loadingText);
}
/**
* Sets the maximum height in pixels of the dropdown list before scrollbars
* are shown (defaults to 300).
*
* @param maxHeight the max hieght
*/
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
/**
* Sets the minimum number of characters the user must type before
* autocomplete and typeahead active (defaults to 4 if remote, or 0 if local).
*
* @param minChars
*/
public void setMinChars(int minChars) {
this.minChars = minChars;
}
/**
* Sets the minimum width of the dropdown list in pixels (defaults to 70, will
* be ignored if listWidth has a higher value).
*
* @param minListWidth the min width
*/
public void setMinListWidth(int minListWidth) {
this.minListWidth = minListWidth;
}
/**
* Sets the page size. Only applies when using a paging toolbar.
*
* @param pageSize the page size
*/
public void setPageSize(int pageSize) {
assertPreRender();
this.pageSize = pageSize;
if (pageSize > 0) {
if (pageTb != null) {
pageTb.setPageSize(pageSize);
} else {
pageTb = new PagingToolBar(pageSize);
}
} else {
pageTb = null;
}
}
@Override
public void setPropertyEditor(PropertyEditor propertyEditor) {
assert propertyEditor instanceof ListModelPropertyEditor> : "PropertyEditor must be a ListModelPropertyEditor instance";
super.setPropertyEditor(propertyEditor);
}
/**
* The length of time in milliseconds to delay between the start of typing and
* sending the query to filter the dropdown list.
*
* @param queryDelay the query delay
*/
public void setQueryDelay(int queryDelay) {
this.queryDelay = queryDelay;
}
@Override
public void setRawValue(String text) {
if (rendered) {
if (text == null) {
String msg = getMessages().getValueNoutFoundText();
text = msg != null ? msg : "";
}
getInputEl().setValue(text);
}
}
/**
* Sets the CSS style name to apply to the selected item in the dropdown list
* (defaults to 'x-combo-selected').
*
* @param selectedStyle the selected style
*/
public void setSelectedStyle(String selectedStyle) {
this.selectedStyle = selectedStyle;
}
public void setSelection(List selection) {
if (selection.size() > 0) {
setValue(selection.get(0));
} else {
setValue(null);
}
}
/**
* Sets the template fragment to be used for the text of each combo list item.
*
*
*
* <code> combo.setSimpleTemplate("{abbr} {name}"); </code>
*
*
*
* @param html the html used only for the text of each item in the list
*/
public void setSimpleTemplate(String html) {
assertPreRender();
html = "" + html + " ";
template = XTemplate.create(html);
}
/**
* Sets the combo's store.
*
* @param store the store
*/
public void setStore(ListStore store) {
this.store = store;
if (rendered) {
bindStore(store);
}
}
/**
* Sets the custom template used to render the combo's drop down list.Use this
* to create custom UI layouts for items in the list.
*
* If you wish to preserve the default visual look of list items, add the CSS
* class name 'x-combo-list-item' to the template's container element.
*
* @param html the html
*/
public void setTemplate(String html) {
assertPreRender();
template = XTemplate.create(html);
}
/**
* Sets the custom template used to render the combo's drop down list.
*
* @param template the template
*/
public void setTemplate(XTemplate template) {
assertPreRender();
this.template = template;
}
/**
* The action to execute when the trigger field is activated. Use
* {@link TriggerAction#ALL} to run the query specified by the allQuery config
* option (defaults to {@link TriggerAction#QUERY}).
*
* @param triggerAction the trigger action
*/
public void setTriggerAction(TriggerAction triggerAction) {
this.triggerAction = triggerAction;
}
/**
* True to populate and autoselect the remainder of the text being typed after
* a configurable delay ({@link #typeAheadDelay}) if it matches a known value
* (defaults to false)
*
* @param typeAhead
*/
public void setTypeAhead(boolean typeAhead) {
this.typeAhead = typeAhead;
if (rendered) {
if (typeAhead && taTask == null) {
taTask = new DelayedTask(new Listener() {
public void handleEvent(BaseEvent be) {
onTypeAhead();
}
});
} else if (!typeAhead && taTask != null) {
taTask.cancel();
taTask = null;
}
}
}
/**
* The length of time in milliseconds to wait until the typeahead text is
* displayed if typeAhead = true (defaults to 250).
*
* @param typeAheadDelay the type ahead delay
*/
public void setTypeAheadDelay(int typeAheadDelay) {
this.typeAheadDelay = typeAheadDelay;
}
/**
* Set this to false to disable the last query cache (defaults to true).
*
* When set to false the store gets queried on each expand for the data that
* should get displayed in the list. If you use a loader, than each time the
* ComboBox gets expanded, the server gets asked for the data.
*
* You want to do this for example, if you filter the content of this ComboBox
* against some selection in another field.
*
* @param useQueryCache the useQueryCache to set
*/
public void setUseQueryCache(boolean useQueryCache) {
this.useQueryCache = useQueryCache;
}
@Override
public void setValue(D value) {
D oldValue = this.value;
super.setValue(value);
updateHiddenValue();
this.lastSelectionText = getRawValue();
if (!Util.equalWithNull(oldValue, value)) {
SelectionChangedEvent se = new SelectionChangedEvent(this, getSelection());
fireEvent(Events.SelectionChange, se);
}
}
/**
* Sets the model field used to retrieve the "value" from the model. If
* specified, a hidden form field will contain the value. The hidden form
* field name will be the combo's field name plus "-hidden".
*
* @param valueField the value field name
*/
public void setValueField(String valueField) {
this.valueField = valueField;
}
/**
* Sets the combo's view.
*
* @param view the view
*/
public void setView(ListView view) {
this.listView = view;
}
protected void collapseIf(PreviewEvent pe) {
if (!list.el().isOrHasChild(pe.getTarget()) && !el().isOrHasChild(pe.getTarget())) {
collapse();
}
}
protected void doForce() {
if (forceSelection) {
boolean f = forceSelection;
forceSelection = false;
String rv = getRawValue();
if (getAllowBlank() && (rv == null || rv.equals(""))) {
forceSelection = f;
return;
}
if (getValue() == null) {
if (lastSelectionText != null && !"".equals(lastSelectionText)) {
setRawValue(lastSelectionText);
if (mode.equals("local")) {
// we need to filter the store here, so that the store only contains
// the items needed.
// we adjust the lastQuery, so doLoad does not use an old value
lastQuery = lastSelectionText;
store.filter(getDisplayField(), getRawValue());
}
} else {
applyEmptyText();
}
}
forceSelection = f;
}
}
protected D findModel(String property, String value) {
if (value == null) return null;
for (D model : store.getModels()) {
if (value.equals(getPropertyEditor().getStringValue(model))) {
return model;
}
}
return null;
}
protected void fireKey(FieldEvent fe) {
if (fe.isNavKeyPress() && !isExpanded() && !delayedCheck) {
fireEvent(Events.SpecialKey, fe);
}
}
@Override
protected El getFocusEl() {
return input;
}
protected PagingLoadConfig getParams(String query) {
PagingLoadConfig config = null;
if (store.getLoader() instanceof BaseLoader> && ((BaseLoader>) store.getLoader()).isReuseLoadConfig()) {
config = (PagingLoadConfig) ((BaseLoader>) store.getLoader()).getLastConfig();
} else {
config = new BasePagingLoadConfig();
}
config.setLimit(pageSize);
config.setOffset(0);
config.set("query", query);
return config;
}
protected boolean hasFocus() {
return hasFocus || expanded;
}
@SuppressWarnings("rawtypes")
protected void initComponent() {
storeListener = new StoreListener() {
@Override
public void storeBeforeDataChanged(StoreEvent se) {
onBeforeLoad(se);
}
@Override
public void storeDataChanged(StoreEvent se) {
onLoad(se);
}
@Override
public void storeUpdate(StoreEvent se) {
onUpdate(se);
}
};
eventPreview = new BaseEventPreview() {
@Override
protected boolean onPreview(PreviewEvent pe) {
switch (pe.getType().getEventCode()) {
case Event.ONSCROLL:
case Event.ONMOUSEWHEEL:
case Event.ONMOUSEDOWN:
collapseIf(pe);
break;
case Event.ONKEYPRESS:
if (expanded && pe.getKeyCode() == KeyCodes.KEY_ENTER) {
pe.stopEvent();
onViewClick(pe, false);
}
break;
}
return true;
}
};
eventPreview.setAutoHide(false);
new KeyNav(this) {
@Override
public void onDown(ComponentEvent ce) {
if (!isReadOnly()) {
ce.cancelBubble();
if (!isExpanded()) {
onTriggerClick(ce);
} else {
selectNext();
}
}
}
@Override
public void onEnter(ComponentEvent ce) {
if (expanded) {
ce.cancelBubble();
onViewClick(ce, false);
delayedCheck = true;
unsetDelayCheck();
}
}
@Override
public void onEsc(ComponentEvent ce) {
if (expanded) {
ce.cancelBubble();
collapse();
}
}
@Override
public void onTab(ComponentEvent ce) {
if (expanded) {
onViewClick(ce, false);
}
}
@Override
public void onUp(ComponentEvent ce) {
if (expanded) {
ce.cancelBubble();
selectPrev();
}
}
};
}
@SuppressWarnings("rawtypes")
protected void initList() {
if (listView == null) {
listView = new ListView();
}
String style = listStyle;
listView.setStyleAttribute("overflowX", "hidden");
listView.addStyleName(style + "-inner");
listView.setStyleAttribute("padding", "0px");
listView.setItemSelector(itemSelector != null ? itemSelector : "." + style + "-item");
listView.setSelectOnOver(true);
listView.setBorders(false);
listView.setLoadingText(getMessages().getLoadingText());
listView.getSelectionModel().addListener(Events.SelectionChange, new Listener>() {
public void handleEvent(SelectionChangedEvent se) {
selectedItem = listView.getSelectionModel().getSelectedItem();
if (GXT.isAriaEnabled()) {
Element e = listView.getElement(listView.getStore().indexOf(selectedItem));
ComboBox.this.setAriaState("aria-activedescendant", e.getId());
}
}
});
listView.addListener(Events.Select, new Listener>() {
public void handleEvent(ListViewEvent le) {
onViewClick(le, true);
}
});
if (template == null) {
String html = "{" + getDisplayField()
+ "} ";
template = XTemplate.create(html);
}
if (pageTb != null) {
pageTb.bind((PagingLoader) store.getLoader());
}
list = new LayoutContainer() {
@Override
protected void doAttachChildren() {
super.doAttachChildren();
ComponentHelper.doAttach(ComboBox.this.pageTb);
}
@Override
protected void doDetachChildren() {
super.doDetachChildren();
ComponentHelper.doDetach(ComboBox.this.pageTb);
}
@Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
eventPreview.getIgnoreList().add(getElement());
setAriaRole("presentation");
if (pageTb != null) {
footer = list.el().createChild("");
pageTb.setBorders(false);
pageTb.render(footer.dom);
}
}
};
list.setScrollMode(Scroll.NONE);
list.setShim(true);
list.setShadow(true);
list.setBorders(true);
list.setStyleName(style);
list.hide();
list.addStyleName("x-ignore");
assert store != null : "ComboBox needs a store";
list.add(listView);
listView.setTemplate(template);
listView.setSelectStyle(selectedStyle);
bindStore(store);
if (!lazyRender) {
createList(true);
}
}
protected void onBeforeLoad(StoreEvent se) {
if (!hasFocus()) {
return;
}
if (expanded) {
restrict();
}
}
@Override
protected void onDetach() {
collapse();
super.onDetach();
if (eventPreview != null) {
eventPreview.remove();
}
}
protected void onEmptyResults() {
collapse();
}
@Override
protected void onKeyDown(FieldEvent fe) {
if (fe.getKeyCode() == KeyCodes.KEY_TAB) {
if (expanded) {
onViewClick(fe, false);
}
}
super.onKeyDown(fe);
}
@Override
protected void onKeyUp(FieldEvent fe) {
super.onKeyUp(fe);
if (!isReadOnly() && isEditable()
&& (!fe.isSpecialKey() || fe.getKeyCode() == KeyCodes.KEY_BACKSPACE || fe.getKeyCode() == 46)) {
// last key
dqTask.delay(queryDelay);
}
}
protected void onLoad(StoreEvent se) {
if (!isAttached() || !hasFocus()) {
return;
}
if (store.getCount() > 0) {
if (expanded) {
restrict();
} else {
expand();
}
if (lastQuery != null && lastQuery.equals(allQuery)) {
if (isEditable()) {
selectAll();
}
} else {
if (typeAhead) {
taTask.delay(typeAheadDelay);
}
}
if (!selectByValue(getRawValue())) {
select(0);
}
} else {
onEmptyResults();
}
}
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
initList();
if (!autoComplete) {
getInputEl().dom.setAttribute("autocomplete", "off");
}
if (mode.equals("local")) {
minChars = 0;
}
dqTask = new DelayedTask(new Listener() {
public void handleEvent(BaseEvent be) {
initQuery();
}
});
if (valueField != null) {
hiddenInput = Document.get().createHiddenInputElement().cast();
hiddenInput.setName(getName() + "-hidden");
getElement().appendChild(hiddenInput);
}
if (typeAhead) {
taTask = new DelayedTask(new Listener() {
public void handleEvent(BaseEvent be) {
onTypeAhead();
}
});
}
eventPreview.getIgnoreList().add(getElement());
setAriaState("aria-owns", listView.getId());
setAriaRole("combobox");
}
protected void onSelect(D model, int index) {
FieldEvent fe = new FieldEvent(this);
if (fireEvent(Events.BeforeSelect, fe)) {
setValue(model);
collapse();
fireEvent(Events.Select, fe);
}
}
protected void onTriggerClick(ComponentEvent ce) {
super.onTriggerClick(ce);
if (expanded) {
collapse();
} else {
onFocus(null);
if (triggerAction == TriggerAction.ALL) {
doQuery(allQuery, true);
} else {
doQuery(getRawValue(), true);
}
}
getInputEl().focus();
}
protected void onTypeAhead() {
if (store.getCount() > 0) {
D m = store.getAt(0);
String newValue = propertyEditor.getStringValue(m);
int len = newValue.length();
int selStart = getRawValue().length();
if (selStart != len) {
setRawValue(newValue);
select(selStart, newValue.length());
}
}
}
protected void onUpdate(StoreEvent se) {
// handle the case when the selected model's display property is updated
if (!getRawValue().equals("") && getValue() == null && forceSelection) {
setValue(null);
store.clearFilters();
setValue(se.getModel());
}
}
protected void onViewClick(DomEvent de, boolean focus) {
int idx = -1;
// when testing in selenium the items will not be selected as the mouse
// is not moved during the test
Element elem = listView.findElement(de.getTarget());
if (elem != null) {
idx = listView.indexOf(elem);
} else {
D sel = listView.getSelectionModel().getSelectedItem();
if (sel != null) {
idx = store.indexOf(sel);
}
}
if (idx != -1) {
D sel = store.getAt(idx);
onSelect(sel, idx);
}
if (focus) {
DeferredCommand.addCommand(new Command() {
public void execute() {
focus();
}
});
}
}
protected void onWindowResize(int width, int height) {
collapse();
}
protected boolean selectByValue(String value) {
D r = findModel(getDisplayField(), value);
if (r != null) {
select(r);
return true;
}
return false;
}
@Override
protected void triggerBlur(ComponentEvent ce) {
doForce();
super.triggerBlur(ce);
}
protected void unsetDelayCheck() {
DeferredCommand.addCommand(new Command() {
public void execute() {
delayedCheck = false;
}
});
}
@Override
protected boolean validateBlur(DomEvent e, Element target) {
return list == null || (list != null && !list.isVisible() && !list.getElement().isOrHasChild(target));
}
@Override
protected boolean validateValue(String value) {
if (forceSelection) {
boolean f = forceSelection;
forceSelection = false;
if (getValue() == null) {
forceSelection = f;
String rv = getRawValue();
if (getAllowBlank() && (rv == null || rv.equals(""))) {
return true;
}
markInvalid(getMessages().getBlankText());
return false;
}
forceSelection = f;
}
return super.validateValue(value);
}
private void bindStore(ListStore store) {
if (this.store != null) {
this.store.removeStoreListener(storeListener);
this.store = null;
if (listView != null) {
listView.setStore(null);
}
}
if (store != null) {
this.store = store;
mode = store.getLoader() == null ? "local" : "remote";
if (listView != null) {
listView.setStore(store);
}
store.addStoreListener(storeListener);
}
}
private void createList(boolean remove) {
RootPanel.get().add(list);
initialized = true;
if (remove) {
RootPanel.get().remove(list);
}
}
private void initQuery() {
doQuery(getRawValue(), false);
}
private void restrict() {
list.el().setVisibility(false);
listView.setHeight("auto");
list.setHeight("auto");
int w = Math.max(getWidth(), minListWidth);
int fh = footer != null ? footer.getHeight() : 0;
int fw = list.el().getFrameWidth("tb") + fh;
int h = listView.getHeight() + fw;
h = Math.min(h, maxHeight - fw);
list.setSize(w, h);
list.el().alignTo(getElement(), listAlign, null);
h -= fh;
int width = w - list.el().getFrameWidth("lr");
listView.syncSize();
listView.setSize(width, h - list.el().getFrameWidth("tb"));
if (pageTb != null) {
pageTb.setWidth(width);
}
int y = list.el().getY();
int b = y + h;
int vh = XDOM.getViewportSize().height + XDOM.getBodyScrollTop();
if (b > vh) {
y = y - (b - vh) - 5;
list.el().setTop(y);
}
list.el().setVisibility(true);
}
private void selectNext() {
int count = store.getCount();
if (count > 0) {
int selectedIndex = store.indexOf(selectedItem);
if (selectedIndex == -1) {
select(0);
} else if (selectedIndex < count - 1) {
select(selectedIndex + 1);
}
}
}
private void selectPrev() {
int count = store.getCount();
if (count > 0) {
int selectedIndex = store.indexOf(selectedItem);
if (selectedIndex == -1) {
select(0);
} else if (selectedIndex != 0) {
select(selectedIndex - 1);
}
}
}
private void updateHiddenValue() {
if (hiddenInput != null) {
String v = "";
D val = getValue();
if (val != null && val.get(valueField) != null) {
v = ((Object) val.get(valueField)).toString();
}
hiddenInput.setValue(v);
}
}
}