org.gwtbootstrap3.extras.select.client.ui.SelectBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gwtbootstrap3-extras Show documentation
Show all versions of gwtbootstrap3-extras Show documentation
Extra, third-party widgets/components for GwtBootstrap3
package org.gwtbootstrap3.extras.select.client.ui;
/*
* #%L
* GwtBootstrap3
* %%
* Copyright (C) 2016 GwtBootstrap3
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.CONTAINER;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.DROPDOWN_ALIGN_RIGHT;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.DROPUP_AUTO;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.HEADER;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.HIDE_DISABLED;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_NORMALIZE;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_PLACEHOLDER;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.LIVE_SEARCH_STYLE;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.MOBILE;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SELECT_ON_TAB;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_CONTENT;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_ICON;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SHOW_SUBTEXT;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.SIZE;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.STYLE;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.WIDTH;
import static org.gwtbootstrap3.extras.select.client.ui.SelectOptions.WINDOW_PADDING;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.gwtbootstrap3.client.ui.base.ComplexWidget;
import org.gwtbootstrap3.client.ui.base.HasSize;
import org.gwtbootstrap3.client.ui.base.HasType;
import org.gwtbootstrap3.client.ui.base.mixin.AttributeMixin;
import org.gwtbootstrap3.client.ui.base.mixin.EnabledMixin;
import org.gwtbootstrap3.client.ui.constants.ButtonSize;
import org.gwtbootstrap3.client.ui.constants.ButtonType;
import org.gwtbootstrap3.client.ui.constants.Styles;
import org.gwtbootstrap3.extras.select.client.ui.constants.DropdownAlignRight;
import org.gwtbootstrap3.extras.select.client.ui.constants.LiveSearchStyle;
import org.gwtbootstrap3.extras.select.client.ui.constants.MenuSize;
import org.gwtbootstrap3.extras.select.client.ui.constants.SelectStyles;
import org.gwtbootstrap3.extras.select.client.ui.constants.SelectWidth;
import org.gwtbootstrap3.extras.select.client.ui.event.HasAllSelectHandlers;
import org.gwtbootstrap3.extras.select.client.ui.event.HiddenEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.HiddenHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.HideEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.HideHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.LoadedEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.LoadedHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.RefreshedEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.RefreshedHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.RenderedEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.RenderedHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.ShowEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.ShowHandler;
import org.gwtbootstrap3.extras.select.client.ui.event.ShownEvent;
import org.gwtbootstrap3.extras.select.client.ui.event.ShownHandler;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayNumber;
import com.google.gwt.core.client.JsonUtils;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
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.editor.client.IsEditor;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.client.adapters.TakesValueEditor;
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.ui.Focusable;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;
/**
* Bootstrap select widget base
*
* @param select value type
*
* @see http://silviomoreto.github.io/bootstrap-select/
* @author Xiaodong Sun
*/
public abstract class SelectBase extends ComplexWidget implements HasValue, HasEnabled, Focusable,
HasType, HasSize, IsEditor>, HasAllSelectHandlers {
private LeafValueEditor editor;
private ButtonType type;
private ButtonSize size;
/**
* Default language: {@link SelectLanguage#EN}
*/
private static final SelectLanguage DEFAULT_LANGUAGE = SelectLanguage.EN;
/**
* Language; defaults to {@value #DEFAULT_LANGUAGE}
*/
private SelectLanguage language = DEFAULT_LANGUAGE;
protected final SelectElement selectElement;
protected final Map itemMap = new HashMap<>(0);
protected final AttributeMixin> attrMixin = new AttributeMixin<>(this);
private final EnabledMixin> enabledMixin = new EnabledMixin<>(this);
private final FocusImpl focusImpl = FocusImpl.getFocusImplForWidget();
/**
* Initialize options
*/
protected SelectOptions options = SelectOptions.newOptions();
protected SelectBase() {
this.selectElement = Document.get().createSelectElement();
setElement(selectElement);
setStyleName(SelectStyles.SELECT_PICKER);
addStyleName(Styles.FORM_CONTROL);
}
/**
* Returns true
if multiple selection is allowed.
*
* @return true
if multiple selection is allowed
*/
public abstract boolean isMultiple();
@Override
protected void onLoad() {
super.onLoad();
// Inject the language JS is necessary
if (language.getJs() != null) {
ScriptInjector.fromString(language.getJs().getText())
.setWindow(ScriptInjector.TOP_WINDOW).inject();
}
initialize(getElement(), options);
bindSelectEvents(getElement());
}
@Override
protected void onUnload() {
super.onUnload();
unbindSelectEvents(getElement());
command(getElement(), SelectCommand.DESTROY);
}
@Override
public void add(Widget child) {
super.add(child);
updateItemMap(child, true);
}
@Override
public void insert(Widget child, int beforeIndex) {
super.insert(child, beforeIndex);
updateItemMap(child, true);
}
@Override
public boolean remove(Widget w) {
boolean removed = super.remove(w);
if (removed) {
updateItemMap(w, false);
}
return removed;
}
void updateItemMap(Widget widget, boolean toAdd) {
// Option ==> update with this option
if (widget instanceof Option) {
Option option = (Option) widget;
if (toAdd)
itemMap.put(option.getSelectElement(), option);
else
itemMap.remove(option.getSelectElement());
} else if (widget instanceof OptGroup) {
// OptGroup ==> update with all optGroup options
OptGroup optGroup = (OptGroup) widget;
if (toAdd)
itemMap.putAll(optGroup.getItemMap());
else
for (Entry entry : optGroup.getItemMap().entrySet()) {
OptionElement optElem = entry.getKey();
itemMap.remove(optElem);
}
}
}
/**
* Set the select language.
*
* @param language
*/
public void setLanguage(final SelectLanguage language) {
this.language = (language == null) ? DEFAULT_LANGUAGE : language;
}
/**
* Returns the select language.
*
* @return
*/
public SelectLanguage getLanguage() {
return language;
}
/**
* Sets a container to which the select will be appended.
*
* @param container specific element or selector, e.g., "body", ".my-container"
*/
public void setContainer(final String container) {
if (container != null)
attrMixin.setAttribute(CONTAINER, container);
else
attrMixin.removeAttribute(CONTAINER);
}
/**
* Sets the handler to get the text displayed when selectedTextFormat
* is count
or count > #
, or null
* to use the default text: X of Y selected
.
*
* @param handler
*/
public void setCountSelectedTextHandler(final CountSelectedTextHandler handler) {
options.setCountSelectedTextHandler(handler);
}
/**
* Sets the drop-down menu right alignment.
*
* Defaults to {@link DropdownAlignRight#FALSE}.
*
* @param dropdownAlignRight
* @see DropdownAlignRight
*/
public void setDropdownAlignRight(final DropdownAlignRight dropdownAlignRight) {
if (dropdownAlignRight != null)
attrMixin.setAttribute(DROPDOWN_ALIGN_RIGHT, dropdownAlignRight.getValue());
else
attrMixin.removeAttribute(DROPDOWN_ALIGN_RIGHT);
}
/**
* Checks to see which has more room, above or below.
* If the drop-up has enough room to fully open normally,
* but there is more room above, the drop-up still opens
* normally. Otherwise, it becomes a drop-up.
* If dropupAuto is set to false
, drop-ups
* must be called manually.
*
* Defaults to true
.
*
* @param dropupAuto
*/
public void setDropupAuto(final boolean dropupAuto) {
if (!dropupAuto)
attrMixin.setAttribute(DROPUP_AUTO, Boolean.toString(false));
else
attrMixin.removeAttribute(DROPUP_AUTO);
}
/**
* If drop-up auto is set to false
, force to make
* the select a drop-up menu if set to true
.
*
* @param forceDropup
* @see #setDropupAuto(boolean)
*/
public void setForceDropup(final boolean forceDropup) {
if (forceDropup) {
addStyleName(SelectStyles.DROPUP);
} else {
removeStyleName(SelectStyles.DROPUP);
}
}
/**
* Adds a header to the top of the menu; includes
* a close button by default.
*
* No header by default.
*
* @param header
*/
public void setHeader(final String header) {
if (header != null)
attrMixin.setAttribute(HEADER, header);
else
attrMixin.removeAttribute(HEADER);
}
/**
* Removes disabled options and optgroups from the menu
*
* Defaults to false
.
*
* @param hideDisabled
*/
public void setHideDisabled(final boolean hideDisabled) {
if (hideDisabled)
attrMixin.setAttribute(HIDE_DISABLED, Boolean.toString(true));
else
attrMixin.removeAttribute(HIDE_DISABLED);
}
/**
* When set to true
, adds a search box to the
* top of the select picker drop-down.
*
* Defaults to false
.
*
* @param liveSearch
*/
public void setLiveSearch(final boolean liveSearch) {
if (liveSearch)
attrMixin.setAttribute(LIVE_SEARCH, Boolean.toString(true));
else
attrMixin.removeAttribute(LIVE_SEARCH);
}
/**
* Setting liveSearchNormalize to true
allows for
* accent-insensitive searching.
*
* Defaults to false
.
*
* @param liveSearchNormalize
*/
public void setLiveSearchNormalize(final boolean liveSearchNormalize) {
if (liveSearchNormalize)
attrMixin.setAttribute(LIVE_SEARCH_NORMALIZE, Boolean.toString(true));
else
attrMixin.removeAttribute(LIVE_SEARCH_NORMALIZE);
}
/**
* Set live search style.
*
* Defaults to {@link LiveSearchStyle#CONTAINS}.
*
* @param liveSearchStyle
* @see LiveSearchStyle
*/
public void setLiveSearchStyle(final LiveSearchStyle liveSearchStyle) {
if (liveSearchStyle != null)
attrMixin.setAttribute(LIVE_SEARCH_STYLE, liveSearchStyle.getValue());
else
attrMixin.removeAttribute(LIVE_SEARCH_STYLE);
}
/**
* Set a placeholder to the live search input.
*
* Defaults to null
.
*
* @param liveSearchPlaceholder
*/
public void setLiveSearchPlaceholder(final String liveSearchPlaceholder) {
if (liveSearchPlaceholder != null)
attrMixin.setAttribute(LIVE_SEARCH_PLACEHOLDER, liveSearchPlaceholder);
else
attrMixin.removeAttribute(LIVE_SEARCH_PLACEHOLDER);
}
/**
* When set to true
, enables the device's native
* menu for select menus.
*
* Defaults to false
.
*
* @param mobile
*/
public void setMobile(final boolean mobile) {
if (mobile)
attrMixin.setAttribute(MOBILE, Boolean.toString(true));
else
attrMixin.removeAttribute(MOBILE);
}
/**
* When set to true
, treats the tab character like the
* enter
or Space
characters within the
* select picker drop-down.
*
* Defaults to false
.
*
* @param selectOnTab
*/
public void setSelectOnTab(final boolean selectOnTab) {
if (selectOnTab)
attrMixin.setAttribute(SELECT_ON_TAB, Boolean.toString(true));
else
attrMixin.removeAttribute(SELECT_ON_TAB);
}
/**
* When set to true
, display custom HTML associated with
* selected option(s) in the button. When set to false
,
* the option value will be displayed instead.
*
* Defaults to true
.
*
* @param showContent
*/
public void setShowContent(final boolean showContent) {
if (!showContent)
attrMixin.setAttribute(SHOW_CONTENT, Boolean.toString(false));
else
attrMixin.removeAttribute(SHOW_CONTENT);
}
/**
* When set to true
, display icon(s) associated with
* selected option(s) in the button.
*
* Defaults to true
.
*
* @param showIcon
*/
public void setShowIcon(final boolean showIcon) {
if (!showIcon)
attrMixin.setAttribute(SHOW_ICON, Boolean.toString(false));
else
attrMixin.removeAttribute(SHOW_ICON);
}
/**
* When set to true
, display sub-text associated with a
* selected option in the button.
*
* Defaults to false
.
*
* @param showSubtext
*/
public void setShowSubtext(final boolean showSubtext) {
if (showSubtext)
attrMixin.setAttribute(SHOW_SUBTEXT, Boolean.toString(true));
else
attrMixin.removeAttribute(SHOW_SUBTEXT);
}
/**
* Specifies the number of visible menu items.
*
* Defaults to {@link MenuSize#AUTO}.
*
* @param size
* @see MenuSize
*/
public void setMenuSize(final MenuSize size) {
if (size != null)
attrMixin.setAttribute(SIZE, size.getValue());
else
attrMixin.removeAttribute(SIZE);
}
/**
* The menu will show the given number of items, even if
* the drop-down is cut off.
*
* Defaults to {@link MenuSize#AUTO}.
*
* @param size
*/
public void setFixedMenuSize(final int size) {
attrMixin.setAttribute(SIZE, Integer.toString(size));
}
/**
* Sets the {@link ButtonType} of the select.
*
* IMPORTANT: This method will override the style set by
* {@link #setStyle(String)}.
*/
@Override
public void setType(final ButtonType type) {
this.type = type;
updateStyle();
}
/**
* Returns the {@link ButtonType} of the select; may be null
.
*
* @return the {@link ButtonType} of the select
*/
@Override
public ButtonType getType() {
return type;
}
/**
* Sets the {@link ButtonSize} of the select.
*
* IMPORTANT: This method will override the style set by
* {@link #setStyle(String)}.
*/
@Override
public void setSize(final ButtonSize size) {
this.size = size;
updateStyle();
}
/**
* Returns the {@link ButtonSize} of the select; may be null
.
*
* @return the {@link ButtonSize} of the select
*/
@Override
public ButtonSize getSize() {
return size;
}
private void updateStyle() {
StringBuilder sb = new StringBuilder();
if (type != null) {
sb.append(type.getCssName());
}
if (size != null) {
if (!sb.toString().isEmpty()) {
sb.append(" ");
}
sb.append(size.getCssName());
}
setStyle(sb.toString());
}
/**
* Set the customized style name to the select.
*
* Defaults to null
.
*
* IMPORTANT: This method will override the style set by
* {@link #setType(ButtonType)} and/or {@link #setSize(ButtonSize)}.
*
* @param styleName
*/
public void setStyle(final String styleName) {
if (styleName != null)
attrMixin.setAttribute(STYLE, styleName);
else
attrMixin.removeAttribute(STYLE);
}
/**
* Returns the style name applied to the select.
*
* @return
*/
public String getStyle() {
return attrMixin.getAttribute(STYLE);
}
/**
* Set the default placeholder text when nothing is selected.
* This works for both multiple and standard select boxes.
*
* Defaults to null
.
*
* @param title
* @see #setTitle(String)
*/
public void setPlaceholder(final String placeholder) {
setTitle(placeholder);
}
/**
* Set the specified width to the select.
*
* Defaults to {@link SelectWidth#NONE}.
*
* @param width
* @see #setWidth(String)
* @see SelectWidth
*/
public void setSelectWidth(final SelectWidth width) {
setWidth((width != null) ? width.getValue() : null);
}
/**
* Set the select width witch is forced inline to the given value.
*
* @param cssWidth a CSS width with units, e.g. 100px
*/
@Override
public void setWidth(final String cssWidth) {
if (cssWidth != null)
attrMixin.setAttribute(WIDTH, cssWidth);
else
attrMixin.removeAttribute(WIDTH);
}
/**
* Sets the window padding to all sides. This is useful in cases where
* the window has areas that the drop-down menu should not cover - for
* instance a fixed header.
*
* @param padding
*/
public void setWindowPadding(final int padding) {
attrMixin.setAttribute(WINDOW_PADDING, Integer.toString(padding));
}
/**
* Sets the window padding to top, right, bottom, and right sides. This
* is useful in cases where the window has areas that the drop-down menu
* should not cover - for instance a fixed header.
*
* @param top
* @param right
* @param bottom
* @param left
*/
public void setWindowPaddingTopRightBottomLeft(final int top, final int right,
final int bottom, final int left) {
JsArrayNumber array = JavaScriptObject.createArray(4).cast();
array.push(top);
array.push(right);
array.push(bottom);
array.push(left);
attrMixin.setAttribute(WINDOW_PADDING, JsonUtils.stringify(array));
}
/**
* Set to true
to add the Bootstrap menu arrow.
*
* @param showMenuArrow
*/
public void setShowMenuArrow(final boolean showMenuArrow) {
if (showMenuArrow) {
addStyleName(SelectStyles.SHOW_MENU_ARROW);
} else {
removeStyleName(SelectStyles.SHOW_MENU_ARROW);
}
}
@Override
public LeafValueEditor asEditor() {
if (editor == null) {
editor = TakesValueEditor.of(this);
}
return editor;
}
@Override
public void setValue(final T value) {
setValue(value, false);
}
@Override
public void setValue(final T value, final boolean fireEvents) {
T oldValue = fireEvents ? getValue() : null;
setSelectedValue(value);
if (fireEvents) {
T newValue = getValue();
ValueChangeEvent.fireIfNotEqual(this, oldValue, newValue);
}
}
/**
* Fires {@link ValueChangeEvent} with the current value.
*/
private void onValueChange() {
T newValue = getValue();
ValueChangeEvent.fire(this, newValue);
}
/**
* Selects the given value. If the value is null
* or does not match any option, no option will be selected.
*
* @param value
*/
protected abstract void setSelectedValue(final T value);
@Override
public void setEnabled(boolean enabled) {
enabledMixin.setEnabled(enabled);
refresh();
}
@Override
public boolean isEnabled() {
return enabledMixin.isEnabled();
}
@Override
public int getTabIndex() {
return focusImpl.getTabIndex(getFocusElement());
}
@Override
public void setAccessKey(char key) {
focusImpl.setAccessKey(getFocusElement(), key);
}
@Override
public void setFocus(boolean focused) {
if (focused) {
focusImpl.focus(getFocusElement());
} else {
focusImpl.blur(getFocusElement());
}
}
@Override
public void setTabIndex(int index) {
focusImpl.setTabIndex(getFocusElement(), index);
}
private Element getFocusElement() {
if (!isAttached()) {
return selectElement;
}
return getElement().getParentElement().getFirstChildElement();
}
/**
* Returns the number of items present in the select.
*
* @return the number of items
*/
public int getItemCount() {
return selectElement.getOptions().getLength();
}
/**
* Returns the item list.
*
* @return the item list
*/
public List