![JAR search and dependency download from the Maven repository](/logo.png)
com.sksamuel.jqm4gwt.form.elements.JQMSelect Maven / Gradle / Ivy
package com.sksamuel.jqm4gwt.form.elements;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
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.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasFocusHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.uibinder.client.UiChild;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.sksamuel.jqm4gwt.DataIcon;
import com.sksamuel.jqm4gwt.HasCorners;
import com.sksamuel.jqm4gwt.HasIcon;
import com.sksamuel.jqm4gwt.HasMini;
import com.sksamuel.jqm4gwt.HasNative;
import com.sksamuel.jqm4gwt.HasPreventFocusZoom;
import com.sksamuel.jqm4gwt.HasText;
import com.sksamuel.jqm4gwt.IconPos;
import com.sksamuel.jqm4gwt.JQMCommon;
import com.sksamuel.jqm4gwt.JQMPage;
import com.sksamuel.jqm4gwt.JQMWidget;
import com.sksamuel.jqm4gwt.button.JQMButton;
import com.sksamuel.jqm4gwt.events.HasTapHandlers;
import com.sksamuel.jqm4gwt.events.JQMComponentEvents;
import com.sksamuel.jqm4gwt.events.JQMHandlerRegistration;
import com.sksamuel.jqm4gwt.events.JQMHandlerRegistration.WidgetHandlerCounter;
import com.sksamuel.jqm4gwt.events.TapEvent;
import com.sksamuel.jqm4gwt.events.TapHandler;
import com.sksamuel.jqm4gwt.form.JQMFieldContainer;
import com.sksamuel.jqm4gwt.html.FormLabel;
/**
* @author Stephen K Samuel [email protected] 5 May 2011 10:58:58
*
An implementation of a jQuery mobile select element.
*
See Select menu
*/
public class JQMSelect extends JQMFieldContainer implements HasNative, HasText,
HasFocusHandlers, HasChangeHandlers, HasClickHandlers, HasTapHandlers, HasValue,
JQMFormWidget, HasIcon, HasPreventFocusZoom, HasCorners,
HasMini, Focusable {
/** JQMSelect non-native popup window has close button on the left side,
* but it could be changed globally to the right side.
**/
public static boolean closeBtnPosRight = false;
public static class Option {
private String value;
private String text;
private String filterText;
private boolean placeholder;
private boolean selected;
private boolean disabled;
// Icons are supported by JQMSelectWithIcons
private DataIcon icon;
private String customIcon;
public Option() {}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public boolean isPlaceholder() {
return placeholder;
}
public void setPlaceholder(boolean placeholder) {
this.placeholder = placeholder;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public DataIcon getIcon() {
return icon;
}
public void setIcon(DataIcon icon) {
this.icon = icon;
}
public String getCustomIcon() {
return customIcon;
}
public void setCustomIcon(String customIcon) {
this.customIcon = customIcon;
}
public String getFilterText() {
return filterText;
}
public void setFilterText(String filterText) {
this.filterText = filterText;
}
}
public static interface ChangeOptionsAction {
/**
* Something like that:
*
* if (!select.isEmpty()) select.clear();
* select.beginAddOptions();
* try {
* select.addOption(...);
* return true;
* } finally {
* select.endAddOptions();
* }
*
*
* @return - must return true in case options were changed
*/
boolean doChange(JQMSelect select);
}
private static final String SELECT_STYLENAME = "jqm4gwt-select";
protected class ListBoxEx extends ListBox {
public ListBoxEx() {
}
@Override
public void setOptionText(OptionElement option, String text, Direction dir) {
super.setOptionText(option, text, dir);
}
@Override
public String getOptionText(OptionElement option) {
return super.getOptionText(option);
}
}
protected final ListBoxEx select;
protected Integer mandatorySelIdx; // See checkSelectedIndex()
/** Unique search index: value, index in select */
protected final Map selectIdx = new HashMap(); // search index
private boolean nonUniqueFindFirst;
protected final FormLabel label;
private String menuStyleNames;
private static boolean jsServed;
private boolean valueChangeHandlerInitialized;
private boolean blurHandlerAdded;
private boolean created;
private boolean transparent = true;
private Element transparentPrevPage;
private boolean transparentPrevPageClearCache;
private boolean transparentDoPrevPageLifecycle;
/** See {@link JQMSelect#getDelayedValue()} */
private String delayedValue;
private Boolean delayedFireEvents;
private boolean addingOptions;
private ArrayList addingOptionList;
private String multiValueSeparator = ",";
/**
* Creates a new {@link JQMSelect} with no label text.
*/
public JQMSelect() {
this(null);
}
/**
* Creates a new {@link JQMSelect} with the given label text.
*/
public JQMSelect(String text) {
String id = Document.get().createUniqueId();
label = new FormLabel();
label.setFor(id);
add(label);
select = new ListBoxEx();
select.getElement().setId(id);
add(select);
addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
if (!isMultiple()) mandatorySelIdx = select.getSelectedIndex();
fireValueChange(getValue());
}
});
setText(text);
addStyleName(SELECT_STYLENAME);
}
private void fireValueChange(String value) {
if (valueChangeHandlerInitialized) {
ValueChangeEvent.fire(this, value);
}
if (isAttached() && created && blurHandlerAdded && !isNative()) {
// TODO: could be fixed in next versions of jqm, but in 1.4.5 this problem exists
// For example to rerun JQMForm's validation for this dropdown
DomEvent.fireNativeEvent(Document.get().createBlurEvent(), select);
}
}
@Override
public HandlerRegistration addBlurHandler(BlurHandler handler) {
blurHandlerAdded = true;
return select.addBlurHandler(handler);
}
@Override
public HandlerRegistration addChangeHandler(ChangeHandler handler) {
return select.addChangeHandler(handler);
}
@Override
public HandlerRegistration addClickHandler(final ClickHandler handler) {
if (handler == null) return null;
return isNative() ? select.addClickHandler(handler)
: flow.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
EventTarget target = event.getNativeEvent().getEventTarget();
if (target != null) {
Element elt = target.cast();
Element uiSel = findUiSelect();
if (uiSel != null) {
Element btn = JQMCommon.findChild(uiSel, "ui-btn");
if (btn != null) {
while (elt != null) { // filter out non-button related elements (label for instance)
if (elt == btn) {
handler.onClick(event);
return;
}
elt = elt.getParentElement();
}
}
}
}
}
}, ClickEvent.getType());
}
@Override
public HandlerRegistration addTapHandler(TapHandler handler) {
// this is not a native browser event so we will have to manage it via JS
return JQMHandlerRegistration.registerJQueryHandler(new WidgetHandlerCounter() {
@Override
public int getHandlerCountForWidget(Type> type) {
return getHandlerCount(type);
}
}, this, handler, JQMComponentEvents.TAP_EVENT, TapEvent.getType());
}
@Override
public Label addErrorLabel() {
return null;
}
@Override
public HandlerRegistration addFocusHandler(FocusHandler handler) {
return addDomHandler(handler, FocusEvent.getType());
}
@UiChild
public void addOption(Option option) {
addOption(option.value, option.text, option.filterText,
option.isPlaceholder(), option.isSelected(), option.isDisabled(),
option.getIcon(), option.getCustomIcon());
}
/**
* Adds an option with the given text. The text is also used as the value.
* The option is added at the end of the list of options.
*
* If you want to specify a value diferent from the display text, then
* invoke addOption(String, String).
*/
public void addOption(String text) {
addOption(text, text);
}
public void addOptions(String... texts) {
if (texts != null) {
for (String s : texts) addOption(s, s);
}
}
/**
* @param values - comma separated list of option values, i.e. dropdown values. Useful for UiBinder.
*/
public void setOptionValues(String values) {
if (values == null || values.isEmpty()) {
clear();
} else {
String[] arr = values.split(",");
for (int i = 0; i < arr.length; i++) {
addOption(arr[i], arr[i]);
}
}
}
private static void prepareOption(OptionElement opt, String value, String filterText,
boolean placeholder, boolean selected, boolean disabled,
DataIcon icon, String customIcon) {
if (value == null) JQMCommon.setAttribute(opt, "value", null);
if (filterText != null && !filterText.isEmpty()) {
JQMCommon.setFilterText(opt, filterText);
}
if (placeholder) JQMCommon.setAttribute(opt, "data-placeholder", "true");
if (selected) JQMCommon.setAttribute(opt, "selected", "selected");
if (disabled) JQMCommon.setAttribute(opt, "disabled", "disabled");
if (icon != null) JQMCommon.setIcon(opt, icon);
else if (customIcon != null) JQMCommon.setIcon(opt, customIcon);
}
private void addToSelectIdx(String key, Integer value) {
if (nonUniqueFindFirst) {
if (!selectIdx.containsKey(key)) { // preserves first non-unique item
selectIdx.put(key, value);
}
} else { // overwrites already existing, i.e. last one has higher priority than first one.
selectIdx.put(key, value);
}
}
public void addOption(String value, String text, String filterText,
boolean placeholder, boolean selected,
boolean disabled, DataIcon icon, String customIcon) {
if (addingOptions) {
OptionElement opt = Document.get().createOptionElement();
select.setOptionText(opt, text, null/*dir*/);
opt.setValue(value);
prepareOption(opt, value, filterText, placeholder, selected, disabled, icon, customIcon);
addingOptionList.add(opt);
} else {
select.addItem(text, value);
final int i;
if (value == null || (filterText != null && !filterText.isEmpty())
|| placeholder || selected || disabled || icon != null || customIcon != null) {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
i = opts.getLength() - 1;
OptionElement opt = opts.getItem(i);
prepareOption(opt, value, filterText, placeholder, selected, disabled, icon, customIcon);
addToSelectIdx(opt.getValue(), i);
} else {
i = select.getItemCount() - 1;
String v = select.getValue(i);
addToSelectIdx(v, i);
}
if (i == 0) {
if (delayedValue == null) {
if (!isMultiple()) internSetSelectedIndex(0); // for compatibility with previous versions
} else {
checkSelectedIndex();
}
} else {
checkSelectedIndex();
}
if (delayedValue != null) tryResolveDelayed();
}
}
protected void rebuildSearchIndex() {
selectIdx.clear();
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
for (int i = 0; i < opts.getLength(); i++) {
String v = opts.getItem(i).getValue();
addToSelectIdx(v, i);
}
}
/**
* Improves performance in case of mass/long options list population, always use with try..finally block.
*/
public void beginAddOptions(int initialCapacity) {
addingOptions = true;
addingOptionList = initialCapacity < 0 ? new ArrayList()
: new ArrayList(initialCapacity);
}
public void beginAddOptions() {
beginAddOptions(-1);
}
public void endAddOptions() {
addingOptions = false;
if (!addingOptionList.isEmpty()) {
SelectElement selElt = select.getElement().cast();
int i = selElt.getOptions().getLength();
for (OptionElement opt : addingOptionList) {
selElt.add(opt, null/*to tail*/);
addToSelectIdx(opt.getValue(), i);
i++;
}
checkSelectedIndex();
}
addingOptionList = null;
if (delayedValue != null) tryResolveDelayed();
}
/** May be needed in ChangeOptionsAction.doChange() implementation */
public SelectElement getSelectElt() {
return select.getElement().cast();
}
public void massChangeOptions(ChangeOptionsAction action) {
if (action == null) return;
String v = getValue();
final String val = v != null ? v : getDelayedValue();
boolean changed = action.doChange(this);
if (!changed) return;
rebuildSearchIndex();
if (val != null) {
if (!val.equals(getValue())) {
setValue(val, false/*fireEvents*/);
}
} else {
setSelectedIndex(-1); // any addOption() call resets selectedIndex from -1 to 0, so we have to fix that
}
}
public void addOption(String value, String text, String filterText,
boolean placeholder, boolean selected, boolean disabled) {
addOption(value, text, filterText, placeholder, selected, disabled,
null/*icon*/, null/*customIcon*/);
}
public void addOption(String value, String text, String filterText) {
addOption(value, text, filterText, false/*placeholder*/, false/*selected*/, false/*disabled*/);
}
/**
* Adds an option with the given value and text. The option is added at
* the end of the list of options.
*/
public void addOption(String value, String text) {
addOption(value, text, null/*filterText*/);
}
public JQMSelect withOption(String value, String text) {
addOption(value, text);
return this;
}
public int getOptionCount() {
return select.getItemCount();
}
public String getOptionText(int index) {
return select.getItemText(index);
}
public String getOptionValue(int index) {
return select.getValue(index);
}
public String getOptionIcon(String optValue) {
if (optValue == null) return null;
List opts = getOptions();
for (OptionElement opt : opts) {
if (optValue.equals(opt.getValue())) {
return JQMCommon.getCustomIcon(opt);
}
}
return null;
}
public List getOptions() {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
List rslt = new ArrayList(opts != null ? opts.getLength() : 0);
if (opts != null) {
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
rslt.add(opt);
}
}
return rslt;
}
public OptionElement getOption(int index) {
if (index < 0) return null;
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
return index < opts.getLength() ? opts.getItem(index) : null;
}
@Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) {
if (!valueChangeHandlerInitialized) valueChangeHandlerInitialized = true;
return addHandler(handler, ValueChangeEvent.getType());
}
@Override
public IconPos getIconPos() {
return JQMCommon.getIconPos(select);
}
/**
* Sets the position of the icon.
* @param pos - if null means set default value, i.e. RIGHT.
**/
@Override
public void setIconPos(IconPos pos) {
JQMCommon.setIconPos(select, pos);
final String prefix = JQMCommon.STYLE_UI_BTN_ICONPOS;
String newPos = pos != null ? prefix + pos.getJqmValue() : prefix + IconPos.RIGHT.getJqmValue();
refreshIconPos(getElement(), prefix, newPos);
}
public void setIconPosNone(boolean value) {
if (!value) {
setIconPos(null);
return;
}
JQMCommon.setAttribute(select, JQMCommon.DATA_ICONPOS, "");
refreshIconPos(getElement(), JQMCommon.STYLE_UI_BTN_ICONPOS, "");
}
public boolean isIconPosNone() {
if (!select.getElement().hasAttribute(JQMCommon.DATA_ICONPOS)) return false;
String v = JQMCommon.getAttribute(select, JQMCommon.DATA_ICONPOS);
return "".equals(v);
}
/** Sets the position of the icon. */
@Override
public JQMSelect withIconPos(IconPos pos) {
setIconPos(pos);
return this;
}
@Override
public boolean isMini() {
return JQMCommon.isMini(select);
}
/** If set to true then renders a smaller version of the standard-sized element. */
@Override
public void setMini(boolean mini) {
JQMCommon.setMini(select, mini);
}
/** If set to true then renders a smaller version of the standard-sized element. */
@Override
public JQMSelect withMini(boolean mini) {
setMini(mini);
return this;
}
/**
* Returns the index of the currently selected option
*/
public int getSelectedIndex() {
checkSelectedIndex();
return select.getSelectedIndex();
}
/**
* @return - true if selectedIndex has been fixed, refresh() must be called manually in that case.
*
*
Unfortunately jqm likes to change selectedIndex from -1 to 0 (for example it happens
* on initialization, i.e. selectmenucreate). Also this behavior is browser specific, so
* we need this guard method to make sure that null value is supported properly.
*/
protected boolean checkSelectedIndex() {
if (mandatorySelIdx == null) return false;
int i = select.getSelectedIndex();
if (mandatorySelIdx != i) {
if (isMultiple()) return false;
select.setSelectedIndex(mandatorySelIdx);
return true;
} else {
return false;
}
}
public String getSelectedValue() {
return getValue();
}
@Override
public int getTabIndex() {
return select.getTabIndex();
}
@Override
public String getText() {
return label.getText();
}
/**
* Returns the currently selected value
*/
@Override
public String getValue() {
if (!isMultiple()) {
return getValue(getSelectedIndex());
} else {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
StringBuilder sb = null;
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
if (opt.isSelected()) {
String v = opt.getValue();
if (sb == null) {
sb = new StringBuilder();
sb.append(v);
} else {
sb.append(multiValueSeparator);
sb.append(v);
}
}
}
if (sb == null) return null;
else {
String rslt = sb.toString();
if (rslt.isEmpty()) rslt = null;
return rslt;
}
}
}
/**
* Returns the currently selected text (makes sense if options are populated by pairs: value & text.
*/
public String getSelectedText() {
if (!isMultiple()) {
int idx = getSelectedIndex();
return idx == -1 ? null : getOptionText(idx);
} else {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
StringBuilder sb = null;
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
if (opt.isSelected()) {
String v = select.getOptionText(opt);
if (sb == null) {
sb = new StringBuilder();
sb.append(v);
} else {
sb.append(multiValueSeparator);
sb.append(v);
}
}
}
if (sb == null) return null;
else {
String rslt = sb.toString();
if (rslt.isEmpty()) rslt = null;
return rslt;
}
}
}
private boolean isMultiValNotEmpty() {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
if (opt.isSelected()) return true;
}
return false;
}
/**
* Returns the value at the given index
*/
public String getValue(int index) {
return index == -1 ? null : select.getValue(index);
}
/**
* setValue() can be called before options are populated (or in the middle of their population).
* For example: asynchronous options loading or data binding scenarios.
*
if value != null and cannot be resolved immediately through current options,
* it will be memorized and probably resolved later, when more options are added.
*
On successful resolution regular setValue() will be called (and events fired if it was requested).
*
Calling clear() resets delayedValue processing.
*/
public String getDelayedValue() {
return delayedValue;
}
/**
* Returns the index of the last option (or first, see {@link JQMSelect#setNonUniqueFindFirst(boolean)})
* that matches the given value or -1 if no such option exists.
*/
public int indexOf(String value) {
if (value == null) return -1;
Integer i = selectIdx.get(value);
return i == null ? -1 : i;
}
public boolean isNonUniqueFindFirst() {
return nonUniqueFindFirst;
}
/** if select contains non-unique items, this property controls how indexOf() search will be working. */
public void setNonUniqueFindFirst(boolean value) {
if (this.nonUniqueFindFirst != value) {
this.nonUniqueFindFirst = value;
if (!selectIdx.isEmpty()) rebuildSearchIndex();
}
}
@Override
public boolean isCorners() {
return JQMCommon.isCorners(select);
}
@Override
public void setCorners(boolean corners) {
JQMCommon.setCorners(select, corners);
}
@Override
public JQMSelect withCorners(boolean corners) {
setCorners(corners);
return this;
}
public boolean isMultiple() {
String s = JQMCommon.getAttribute(select, "multiple");
return s != null && !s.isEmpty() && "multiple".equals(s);
}
/**
* Sets this select to allow multiple selections.
*/
public void setMultiple(boolean value) {
JQMCommon.setAttribute(select, "multiple", value ? "multiple" : null);
}
/**
* Returns true if the select is set to render in native mode.
*/
@Override
public boolean isNative() {
// an element is native by default. So this returns true if native is not set to false.
String s = JQMCommon.getAttribute(select, "data-native-menu");
if (s == null || s.isEmpty()) return true;
return !"false".equals(s);
}
@Override
public void setNative(boolean value) {
JQMCommon.setAttribute(select, "data-native-menu", value ? null : "false");
}
@Override
public JQMSelect withNative(boolean value) {
setNative(value);
return this;
}
@Override
public boolean isPreventFocusZoom() {
return "true".equals(JQMCommon.getAttribute(select, "data-prevent-focus-zoom"));
}
/**
* This option disables page zoom temporarily when a custom select is focused,
* which prevents iOS devices from zooming the page into the select.
* By default, iOS often zooms into form controls, and the behavior is often
* unnecessary and intrusive in mobile-optimized layouts.
*/
@Override
public void setPreventFocusZoom(boolean b) {
JQMCommon.setAttribute(select, "data-prevent-focus-zoom", b ? "true" : null);
}
public boolean isShadow() {
String s = JQMCommon.getAttribute(select, "data-shadow");
if (s == null || s.isEmpty()) return true;
return !"false".equals(s);
}
public void setShadow(boolean value) {
JQMCommon.setAttribute(select, "data-shadow", value ? null : "false");
}
/** Programmatically close an open select menu */
public void close() {
close(select.getElement());
}
private static native void close(Element elt) /*-{
$wnd.$(elt).selectmenu('close');
}-*/;
/** Programmatically open a select menu */
public void open() {
open(select.getElement());
}
private static native void open(Element elt) /*-{
$wnd.$(elt).selectmenu('open');
}-*/;
/**
* Refreshes the select after a programmatic options change has taken place.
*/
public void refresh() {
refresh(select.getElement());
}
private static native void refresh(Element elt) /*-{
var w = $wnd.$(elt);
if (w.data('mobile-selectmenu') !== undefined) {
w.selectmenu('refresh');
}
}-*/;
/**
* Remove the first option that matches the given value
*/
public void removeOption(String value) {
int indexOf = indexOf(value);
if (indexOf >= 0) {
int i = getSelectedIndex();
if (i == indexOf) internSetSelectedIndex(-1);
select.removeItem(indexOf);
}
}
/**
* @return - true when there are no value and no options defined.
*/
public boolean isEmpty() {
return getSelectedIndex() == -1 && getOptionCount() == 0;
}
/**
* Clears all (options too). Then refresh() should be called to update visual state.
*/
public void clear() {
clear(false/*delayValue*/);
}
public void clear(boolean delayValue) {
if (!delayValue) clearDelayed();
else {
String v = getValue();
if (v != null) {
delayedValue = v;
delayedFireEvents = false;
}
// else - delayedValue is not cleared
}
select.clear();
selectIdx.clear();
internSetSelectedIndex(-1);
}
private void clearDelayed() {
delayedValue = null;
delayedFireEvents = null;
}
@Override
public String getTheme() {
return JQMCommon.getTheme(select);
}
private Element findUiSelect() {
// In case of manual JQMContext.render() call, created could be still false,
// but ui-select is already created, so set all needed properties before render() call.
if (!created) return null;
Element uiSel = JQMCommon.findChild(getElement(), "ui-select");
return uiSel;
}
@Override
public void setTheme(String themeName) {
JQMCommon.applyTheme(select, themeName);
Element uiSel = findUiSelect();
if (uiSel != null) {
Element btn = JQMCommon.findChild(uiSel, "ui-btn");
if (btn != null) JQMButton.setTheme(btn, themeName);
}
}
@Override
public JQMWidget withTheme(String themeName) {
setTheme(themeName);
return this;
}
@Override
public void setAccessKey(char key) {
select.setAccessKey(key);
}
@Override
public void setFocus(boolean focused) {
select.setFocus(focused);
}
@Override
public JQMSelect removeIcon() {
String oldIcon = JQMCommon.getCustomIcon(select.getElement());
oldIcon = oldIcon == null ? "" : JQMCommon.STYLE_UI_ICON + oldIcon;
JQMCommon.setIcon(select, null);
refreshIcon(getElement(), oldIcon, "");
return this;
}
/**
* Sets the icon used by this button. See {@link DataIcon}.
*/
@Override
public void setBuiltInIcon(DataIcon icon) {
String oldIcon = JQMCommon.getCustomIcon(select.getElement());
oldIcon = oldIcon == null ? "" : JQMCommon.STYLE_UI_ICON + oldIcon;
JQMCommon.setIcon(select, icon);
String newIcon = JQMCommon.getCustomIcon(select.getElement());
newIcon = newIcon == null ? "" : JQMCommon.STYLE_UI_ICON + newIcon;
refreshIcon(getElement(), oldIcon, newIcon);
}
@Override
public void setIconURL(String src) {
String oldIcon = JQMCommon.getCustomIcon(select.getElement());
oldIcon = oldIcon == null ? "" : JQMCommon.STYLE_UI_ICON + oldIcon;
JQMCommon.setIcon(select.getElement(), src);
String newIcon = JQMCommon.getCustomIcon(select.getElement());
newIcon = newIcon == null ? "" : JQMCommon.STYLE_UI_ICON + newIcon;
refreshIcon(getElement(), oldIcon, newIcon);
}
private static native void refreshIcon(Element elt, String oldIcon, String newIcon) /*-{
if ($wnd.$ === undefined || $wnd.$ === null) return; // jQuery is not loaded
$wnd.$(elt).children().find('.ui-btn').removeClass(oldIcon).addClass(newIcon);
}-*/;
private static native void refreshIconPos(Element elt, String iconPosPrefix, String newIconPos) /*-{
if ($wnd.$ === undefined || $wnd.$ === null) return; // jQuery is not loaded
var s = '(^|\\s)' + iconPosPrefix + '\\S+';
var regex = new RegExp(s, 'g');
$wnd.$(elt).children().find('.ui-btn').removeClass(function(index, className) {
return (className.match(regex) || []).join(' ');
}).addClass(newIconPos);
}-*/;
/**
* Sets the icon used by this button. See {@link DataIcon}.
*/
@Override
public JQMSelect withBuiltInIcon(DataIcon icon) {
setBuiltInIcon(icon);
return this;
}
@Override
public JQMSelect withIconURL(String src) {
setIconURL(src);
return this;
}
public boolean isIconNoDisc() {
return JQMCommon.isIconNoDisc(this);
}
public void setIconNoDisc(boolean value) {
JQMCommon.setIconNoDisc(this, value);
}
public boolean isIconAlt() {
return JQMCommon.isIconAlt(this);
}
/**
* @param value - if true "white vs. black" icon style will be used
*/
public void setIconAlt(boolean value) {
JQMCommon.setIconAlt(this, value);
}
public String getOverlayTheme() {
return JQMCommon.getAttribute(select, "data-overlay-theme");
}
/**
* Sets the color of the overlay layer for the dialog-based custom select menus
* and the outer border of the smaller custom menus.
*/
public void setOverlayTheme(String theme) {
JQMCommon.setAttribute(select, "data-overlay-theme", theme);
}
/**
* Change the selection to the option at the given index, refresh() is called automatically.
*
Setting the selected index programmatically does NOT
* cause the {@link ChangeHandler#onChange(ChangeEvent)}
* nor {@link ValueChangeHandler#onValueChange(ValueChangeEvent)}
* events to be fired.
*
Call {@link JQMSelect#setValue(String, boolean)} with true if you need them raised.
*/
public void setSelectedIndex(int index) {
clearDelayed();
internSetSelectedIndex(index);
refresh();
}
protected void internSetSelectedIndex(int index) {
select.setSelectedIndex(index);
mandatorySelIdx = index;
}
public void setSelectedValue(String value, boolean ignoreCase) {
if (value == null) {
setSelectedIndex(-1);
return;
}
if (!ignoreCase) {
int i = indexOf(value);
setSelectedIndex(i);
return;
} else {
int i = indexOf(value);
if (i >= 0) {
setSelectedIndex(i);
return;
}
}
// Can be optimized with search index as well, but it's expensive to support additional index
for (int k = 0; k < select.getItemCount(); k++) {
String v = select.getValue(k);
boolean eq = value.equalsIgnoreCase(v);
if (eq) {
setSelectedIndex(k);
return;
}
}
}
@Override
public void setTabIndex(int index) {
select.setTabIndex(index);
}
/**
* Set the text of the label element.
*/
@Override
public void setText(String text) {
label.setText(text);
}
@Override
public JQMSelect withText(String text) {
setText(text);
return this;
}
/**
* Sets the selected value to the given value. If no option matches the
* given value then the selected is removed.
*/
@Override
public void setValue(String value) {
setValue(value, false);
}
/**
* Sets the selected value to the given value. If no option matches the
* given value then the selected is removed.
*/
@Override
public void setValue(String value, boolean fireEvents) {
if (!isMultiple()) {
int newIdx = value == null ? -1 : indexOf(value);
setNewSelectedIndex(newIdx, fireEvents);
if (newIdx == -1 && value != null) {
delayedValue = value;
delayedFireEvents = fireEvents;
}
} else {
if (value == null || value.isEmpty() || value.trim().isEmpty()) {
boolean oldValNotEmpty = fireEvents ? isMultiValNotEmpty() : false;
boolean changed = unselectAllOptions();
clearDelayed();
if (changed) refresh();
if (fireEvents && oldValNotEmpty) fireValueChange(null);
} else {
if (getOptionCount() == 0) {
delayedValue = value;
delayedFireEvents = fireEvents;
} else {
Set newVals = new HashSet();
if (!verifyMultiValue(value, newVals)) {
boolean oldValNotEmpty = fireEvents ? isMultiValNotEmpty() : false;
boolean changed = unselectAllOptions();
delayedValue = value;
delayedFireEvents = fireEvents;
if (changed) refresh();
if (fireEvents && oldValNotEmpty) fireValueChange(null);
return;
}
setNewMultiVals(newVals, fireEvents);
}
}
}
}
private void setNewSelectedIndex(int newIdx, boolean fireEvents) {
int oldIdx = fireEvents ? getSelectedIndex() : -1;
setSelectedIndex(newIdx);
if (fireEvents) {
newIdx = getSelectedIndex();
if (oldIdx != newIdx) {
fireValueChange(getValue(newIdx));
}
}
}
private void setNewMultiVals(Set newVals, boolean fireEvents) {
StringBuilder sb = fireEvents ? new StringBuilder() : null;
boolean changed = selectOptions(newVals, sb);
if (changed) refresh();
if (fireEvents && changed && sb != null) {
String newVal = sb.toString();
if (newVal.isEmpty()) newVal = null;
fireValueChange(newVal);
}
}
private boolean verifyMultiValue(String value, Set newVals) {
if (!newVals.isEmpty()) newVals.clear();
String[] arr = value.split(multiValueSeparator);
for (int i = 0; i < arr.length; i++) {
String s = arr[i].trim();
if (s.isEmpty()) continue;
int idx = indexOf(s);
if (idx == -1) {
return false;
}
newVals.add(s);
}
return true;
}
private boolean unselectAllOptions() {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
boolean changed = false;
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
if (opt.isSelected()) {
opt.setSelected(false);
changed = true;
}
}
return changed;
}
private boolean selectOptions(Set selected, StringBuilder sbSelectedValues) {
SelectElement selElt = select.getElement().cast();
NodeList opts = selElt.getOptions();
boolean changed = false;
for (int i = 0; i < opts.getLength(); i++) {
OptionElement opt = opts.getItem(i);
String v = opt.getValue();
boolean sel = selected.contains(v);
if (opt.isSelected() != sel) {
opt.setSelected(sel);
changed = true;
}
if (sbSelectedValues != null && sel) {
if (sbSelectedValues.length() > 0) sbSelectedValues.append(multiValueSeparator);
sbSelectedValues.append(v);
}
}
return changed;
}
private void tryResolveDelayed() {
if (delayedValue == null) return;
if (!isMultiple()) {
int newIdx = indexOf(delayedValue);
if (newIdx == -1) return;
boolean fireEvents = delayedFireEvents != null ? delayedFireEvents : false;
setNewSelectedIndex(newIdx, fireEvents);
} else {
Set newVals = new HashSet();
if (!verifyMultiValue(delayedValue, newVals)) return;
boolean fireEvents = delayedFireEvents != null ? delayedFireEvents : false;
clearDelayed();
setNewMultiVals(newVals, fireEvents);
}
}
public String getCloseText() {
return JQMCommon.getAttribute(select, "data-close-text");
}
/**
* Customizes the text of the close button which is helpful for translating this
* into different languages. The close button is displayed as an icon-only button
* by default so the text isn't visible on-screen, but is read by screen readers
* so this is an important accessibility feature.
*/
public void setCloseText(String value) {
JQMCommon.setAttribute(select, "data-close-text", value);
}
public boolean getSelectInline() {
return JQMCommon.isInline(select);
}
/** If set to true, this will make the select/dropdown act like an inline widget
* so the width is determined by the widget's text.
*/
public void setSelectInline(boolean value) {
JQMCommon.setInline(select, value);
Element uiSel = findUiSelect();
if (uiSel != null) {
JQMCommon.setInlineEx(uiSel, value, JQMCommon.STYLE_UI_BTN_INLINE);
}
}
public boolean isHidePlaceholderMenuItems() {
String s = JQMCommon.getAttribute(select, "data-hide-placeholder-menu-items");
if (s == null || s.isEmpty()) return true;
return !"false".equals(s);
}
/**
* Default is true.
*
Sets whether placeholder menu items are hidden.
* When true, the menu item used as the placeholder for the select menu widget
* will not appear in the list of choices.
*/
public void setHidePlaceholderMenuItems(boolean value) {
JQMCommon.setAttribute(select, "data-hide-placeholder-menu-items", value ? null : "false");
}
@Override
protected Widget getDataFilterWidget() {
return select;
}
private static native void bindCreated(Element elt, JQMSelect sel) /*-{
$wnd.$(elt).on( 'selectmenucreate', function( event, ui ) {
[email protected]::created()();
});
}-*/;
private static native void unbindCreated(Element elt) /*-{
$wnd.$(elt).off( 'selectmenucreate' );
}-*/;
private void created() {
created = true;
if (checkSelectedIndex()) refresh();
}
@Override
protected void onLoad() {
super.onLoad();
Element elt = select.getElement();
bindCreated(elt, this);
if (!jsServed) {
jsServed = true;
serveSelects();
}
}
@Override
protected void onUnload() {
Element elt = select.getElement();
unbindCreated(elt);
super.onUnload();
}
private static native boolean isPageSelectDialog(String pageId) /*-{
var isDialog = false;
var SEL = "." + @com.sksamuel.jqm4gwt.form.elements.JQMSelect::SELECT_STYLENAME
+ " .ui-select > select";
$wnd.$(SEL).each(function() {
var id = $wnd.$(this).attr("id");
if (id) {
var s = id + "-dialog";
if (s === pageId) {
isDialog = true;
return false;
}
}
});
return isDialog;
}-*/;
protected static JQMSelect findCombo(Element elt) {
Widget w = JQMCommon.findWidget(elt);
while (w != null) {
if (w instanceof JQMSelect) return (JQMSelect) w;
w = w.getParent();
}
return null;
}
private static native void serveSelects() /*-{
if ($wnd.$ === undefined || $wnd.$ === null) return; // jQuery is not loaded
var SEL = "." + @com.sksamuel.jqm4gwt.form.elements.JQMSelect::SELECT_STYLENAME
+ " .ui-select > select";
$wnd.$.mobile.document
// Upon creation of the select menu, we want to make use of the fact that the ID of the
// listview it generates starts with the ID of the select menu itself, plus the suffix "-menu".
.on( "selectmenucreate", SEL, function( event ) {
// process additional CSS styling classes
var combo = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::findCombo(Lcom/google/gwt/dom/client/Element;)
(event.target);
var addnl = [email protected]::getMenuStyleNames()();
if (addnl) {
var selectmenu = $wnd.$( event.target ),
id = selectmenu.attr( "id" ),
lb = $wnd.$( "#" + id + "-listbox" );
lb.addClass( addnl );
}
if (@com.sksamuel.jqm4gwt.form.elements.JQMSelect::closeBtnPosRight) {
var selectmenu = $wnd.$( event.target ),
id = selectmenu.attr( "id" ),
lb = $wnd.$( "#" + id + "-listbox" );
lb.children(".ui-header").first()
.children(".ui-btn.ui-btn-left.ui-icon-delete").first()
.removeClass("ui-btn-left").addClass("ui-btn-right");
}
})
// The custom select list may show up as either a popup or a dialog, depending on how much
// vertical room there is on the screen. If it shows up as a dialog, then we have to
// process transparent property.
.on( "pagecontainerbeforeshow", function( event, data ) {
var pageId = data.toPage && data.toPage.attr("id");
// We only handle the appearance of a dialog generated by a selectmenu
var isDlg = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::isPageSelectDialog(Ljava/lang/String;)
(pageId);
if (!isDlg) return;
var dialog = data.toPage;
if (@com.sksamuel.jqm4gwt.form.elements.JQMSelect::closeBtnPosRight) {
dialog.children(".ui-dialog-contain").first()
.children(".ui-header").first()
.children(".ui-btn.ui-btn-left.ui-icon-delete").first()
.removeClass("ui-btn-left").addClass("ui-btn-right");
}
var comboId = pageId.replace("-dialog", "");
var combo = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::findCombo(Lcom/google/gwt/dom/client/Element;)
($wnd.$("#" + comboId)[0]);
var addnl = [email protected]::getMenuStyleNames()();
if (addnl) dialog.addClass( addnl );
[email protected]::doDlgBeforeShow(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Element;)
(dialog[0], data.prevPage.get(0));
})
// After the dialog is closed, we have to process transparent property as well.
.on( "pagecontainerhide", function( event, data ) {
var pageId = data.prevPage && data.prevPage.attr("id");
// We only handle the disappearance of a dialog generated by a filterable selectmenu
var isDlg = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::isPageSelectDialog(Ljava/lang/String;)
(pageId);
if (!isDlg) return;
var dialog = data.prevPage;
var comboId = pageId.replace("-dialog", "");
var combo = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::findCombo(Lcom/google/gwt/dom/client/Element;)
($wnd.$("#" + comboId)[0]);
[email protected]::doDlgHide(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Element;)
(dialog[0], data.toPage.get(0));
});
}-*/;
/**
* @param dialog - select list's dialog, which is showing
* @param prevPage - actually the page on which select widget is placed
*/
protected void doDlgBeforeShow(Element dialog, Element prevPage) {
if (transparent && prevPage != null) {
transparentPrevPage = prevPage;
prevPage.addClassName(JQMPage.UI_DIALOG_BACKGROUND);
String s = prevPage.getAttribute(JQMPage.DATA_DOM_CACHE);
if ("true".equals(s)) {
transparentPrevPageClearCache = false;
} else {
transparentPrevPageClearCache = true;
prevPage.setAttribute(JQMPage.DATA_DOM_CACHE, "true");
}
if (!transparentDoPrevPageLifecycle) {
JQMPage prev = JQMPage.findPage(transparentPrevPage);
if (prev != null) prev.unbindLifecycleEvents();
}
//Element dlgContain = JQMCommon.findChild(dialog, JQMPage.UI_DIALOG_CONTAIN);
//if (dlgContain != null) dlgContain.addClassName(JQMPage.UI_BODY_INHERIT);
} else {
transparentPrevPage = null;
transparentPrevPageClearCache = false;
//Element dlgContain = JQMCommon.findChild(dialog, JQMPage.UI_DIALOG_CONTAIN);
//if (dlgContain != null) dlgContain.removeClassName(JQMPage.UI_BODY_INHERIT);
}
if (transparent) dialog.addClassName(JQMPage.JQM4GWT_DLG_TRANSPARENT);
else dialog.removeClassName(JQMPage.JQM4GWT_DLG_TRANSPARENT);
}
/**
* @param dialog - select list's dialog, which is hiding
* @param nextPage - actually the page on which select widget is placed
*/
protected void doDlgHide(Element dialog, Element nextPage) {
if (transparentPrevPage != null) {
transparentPrevPage.removeClassName(JQMPage.UI_DIALOG_BACKGROUND);
if (transparentPrevPageClearCache) {
transparentPrevPage.removeAttribute(JQMPage.DATA_DOM_CACHE);
}
if (!transparentDoPrevPageLifecycle) {
final JQMPage prev = JQMPage.findPage(transparentPrevPage);
if (prev != null) {
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
prev.bindLifecycleEvents();
}
});
}
}
transparentPrevPage = null;
transparentPrevPageClearCache = false;
}
}
public boolean isDlgTransparent() {
return transparent;
}
/**
* @param transparent - select list may show up as either a popup or a dialog,
* depending how much vertical room there is on the screen.
* If it shows up as a dialog then true means show faded previous page under dialog window
* and don't bother prev page with lifecycle events (show, hide, ...).
*/
public void setDlgTransparent(boolean transparent) {
this.transparent = transparent;
}
public boolean isDlgTransparentDoPrevPageLifecycle() {
return transparentDoPrevPageLifecycle;
}
/**
* By default all lifecycle events (show, hide, ...) are blocked on previous page
* when dlgTransparent is true for this select widget. This behavior can be disabled by setting
* this property to true, so lifecycle events will be called for previous page.
*/
public void setDlgTransparentDoPrevPageLifecycle(boolean transparentDoPrevPageLifecycle) {
this.transparentDoPrevPageLifecycle = transparentDoPrevPageLifecycle;
}
public String getMultiValueSeparator() {
return multiValueSeparator;
}
public void setMultiValueSeparator(String multiValueSeparator) {
this.multiValueSeparator = multiValueSeparator;
}
public String getMenuStyleNames() {
return menuStyleNames;
}
/**
* For non-native mode (popup or dialog with listview), you can style listview by defining rule:
*
.ui-selectmenu .ui-selectmenu-list { ... }
*
For additional flexibility you can specify custom classes to be added together with ui-selectmenu
* @param menuStyleNames - space separated custom classes
*/
public void setMenuStyleNames(String menuStyleNames) {
this.menuStyleNames = menuStyleNames;
}
/** Should be used in case we want to move select to another parent panel. */
public void moveTo(Panel panel) {
if (panel == null) return;
Widget p = getParent();
if (p == panel) return;
if (created) { // on iOS moving JQMSelect from one panel to another is making it "stuck"
String v = getValue();
panel.add(this);
refresh();
setValue(v);
} else {
panel.add(this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy