我正在尝试使用多选功能实现一个comboBox。将有一个与每个字符串相关联的复选框,用户可以从列表中选择一个或多个。 在GXT 2中,我们使用CheckBoxListView,这使事情变得更容易。
我有实现它的想法。
使用一个Grid,它可以有一列作为CheckBox,其他列作为我要显示的字符串,然后将此网格的存储添加到CheckBoxStore。但是,由于Grid和ComboBoxes的ListStore不相同,我尝试了但没有成功,因为两个商店都不同并且接受了不同的属性。
应该有另一种方式,比如使用ListView.But,我没有得到如何在ListView中使用CheckBox
需要帮助
答案 0 :(得分:2)
这是我的代码..不完美,但有点临时解决方法..
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayString;
import client.grid.model.IComboBoxProperties;
import client.model.ComboBoxModel;
import com.sencha.gxt.cell.core.client.form.CheckBoxCell;
import com.sencha.gxt.cell.core.client.form.ComboBoxCell.TriggerAction;
import com.sencha.gxt.core.client.Style.HideMode;
import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.Window;
import com.sencha.gxt.widget.core.client.event.ExpandEvent;
import com.sencha.gxt.widget.core.client.event.HideEvent;
import com.sencha.gxt.widget.core.client.event.HideEvent.HideHandler;
import com.sencha.gxt.widget.core.client.form.ComboBox;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
public class MultiSelectBox extends ComboBox<ComboBoxModel>{
private Window checkBoxListHolder;
private Grid<ComboBoxModel> checkBoxGrid;
private ListStore<ComboBoxModel> listStore;
private boolean readOnly;
private ColumnConfig<ComboBoxModel, String> cc1;
private ColumnConfig<ComboBoxModel, Boolean> cc2;
private ComboBoxModel setData;
IComboBoxProperties props = GWT.create(IComboBoxProperties.class);
public MultiSelectBox(ListStore<ComboBoxModel> store,
LabelProvider<? super ComboBoxModel> labelProvider) {
super(store, labelProvider);
this.listStore=getStore();
this.checkBoxGrid=getCheckBoxGrid();
checkBoxListHolder = new Window(){
};
checkBoxListHolder.setClosable(false);
checkBoxListHolder.setHeaderVisible(false);
checkBoxListHolder.setResizable(false);
checkBoxListHolder.setAutoHide(true);
checkBoxListHolder.getButtonBar().setVisible(false);
checkBoxGrid.setVisible(true);
checkBoxGrid.focus();
checkBoxListHolder.add(checkBoxGrid);
checkBoxListHolder.addHideHandler(new HideHandler() {
@Override
public void onHide(HideEvent event) {
checkBoxGrid.getView().refresh(false);
checkBoxGrid.getStore().commitChanges();
clear();
setValue(parseCheckedValues(checkBoxGrid));
getCell().setTriggerAction(TriggerAction.ALL);
}
});
addExpandHandler(new ExpandEvent.ExpandHandler() {
@Override
public void onExpand(ExpandEvent event) {
if (checkBoxListHolder.isVisible()) {
checkBoxGrid.getView().refresh(false);
checkBoxGrid.getStore().commitChanges();
checkBoxListHolder.hide();
} else {
if(getValue()!=null)
{
setData=getValue();
updateGrid(setData);
}
else
{
updateGridStore(getStore());
}
checkBoxGrid.getStore().commitChanges();
checkBoxGrid.getView().refresh(false);
checkBoxListHolder.setPosition(getAbsoluteLeft(), getAbsoluteTop()+getOffsetHeight());
checkBoxListHolder.setSize(String.valueOf(getOffsetWidth()), "200");
checkBoxListHolder.show();
}
collapse();
}
});
}
private Grid<ComboBoxModel> getCheckBoxGrid()
{
ListStore<ComboBoxModel> gridStore = new ListStore<ComboBoxModel>(props.key());
for(int i=0;i<listStore.size();i++)
{
gridStore.add(new ComboBoxModel(listStore.get(i).getKey(),listStore.get(i).getValue(),false));
}
gridStore.commitChanges();
List<ColumnConfig<ComboBoxModel,?>> configs = getColumnConfig();
ColumnModel<ComboBoxModel> cm = new ColumnModel<ComboBoxModel>(configs);
final Grid<ComboBoxModel> grid = new Grid<ComboBoxModel>(gridStore, cm){
};
grid.setBorders(false);
grid.getView().setStripeRows(false);
grid.getView().setAutoFill(true);
grid.getView().setColumnLines(false);
grid.setHideHeaders(true);
return grid;
}
private List<ColumnConfig<ComboBoxModel,?>> getColumnConfig(){
List<ColumnConfig<ComboBoxModel,?>> columns = new ArrayList<ColumnConfig<ComboBoxModel,?>>();
cc2 =new ColumnConfig<ComboBoxModel,Boolean>(props.checked(),20,"Select");
cc2.setCell(new CheckBoxCell(){
});
columns.add(cc2);
cc1 = new ColumnConfig<ComboBoxModel,String>(props.getValue(),50,"Choose Properties");
columns.add(cc1);
return columns;
}
private ComboBoxModel parseCheckedValues(Grid<ComboBoxModel> grid) {
ListStore<ComboBoxModel> list = grid.getStore();
ComboBoxModel m = new ComboBoxModel();
String buf="";
String keys="";
if (list != null) {
for(int i=0;i<list.size();i++){
if(list.get(i).getChecked().booleanValue())
{
buf=buf+list.get(i).getValue();
buf=buf+",";
keys=keys+list.get(i).getKey();
keys=keys+",";
}
}
if (buf.length() > 0 && buf.charAt(buf.length()-1)==',') {
buf = buf.substring(0, buf.length()-1);
}
if (keys.length() > 0 && keys.charAt(keys.length()-1)==',') {
keys = keys.substring(0, keys.length()-1);
}
}
m.setKey(keys);
m.setValue(buf);
return m;
}
public JsArrayString getSelectedItems() {
JsArrayString result = (JsArrayString) JsArrayString.createArray();
ListStore<ComboBoxModel> store=checkBoxGrid.getStore();
if (store != null){
for(int i=0;i<store.size();i++){
if(store.get(i).getChecked().booleanValue())
{
result.push(store.get(i).getKey());
}
}
}
return result;
}
public List<ComboBoxModel> getSelectedItemCombos() {
List<ComboBoxModel> list = new ArrayList<ComboBoxModel>();
ListStore<ComboBoxModel> store=checkBoxGrid.getStore();
if(store!=null){
for(int i=0;i<store.size();i++){
if(store.get(i).getChecked().booleanValue())
{
list.add(store.get(i));
}
}
}
return list;
}
public void setCheckedItems(List<ComboBoxModel> list){
ListStore<ComboBoxModel> liststore = checkBoxGrid.getStore();
for(int i=0;i<liststore.size();i++)
{
for(int j=0;j<list.size();j++)
{
if(checkBoxGrid.getStore().get(i).getKey().equals(list.get(j).getKey()) && checkBoxGrid.getStore().get(i).getValue().equals(list.get(j).getValue()))
{
checkBoxGrid.getStore().get(i).setChecked(true);
break;
}
else
checkBoxGrid.getStore().get(i).setChecked(false);
}
}
checkBoxGrid.getStore().commitChanges();
setValue(parseCheckedValues(checkBoxGrid));
}
public void clearCheckedItems(){
for (int i =0; i< checkBoxGrid.getStore().size(); i++){
checkBoxGrid.getStore().get(i).setChecked(false);
}
}
private ArrayList<ComboBoxModel> getSelectedValues(ComboBoxModel model)
{
ArrayList<ComboBoxModel> list = new ArrayList<ComboBoxModel>();
String [] values=model.getValue().split(",");
String [] keys = model.getKey().split(",");
int i=0;
int len=values.length;
for(i=0;i<len;i++)
{
list.add(new ComboBoxModel(keys[i],values[i],true));
}
return list;
}
private void updateGrid(ComboBoxModel model)
{
String [] values=model.getValue().split(",");
String [] keys = model.getKey().split(",");
int i=0;
int len=values.length;
ListStore<ComboBoxModel> list = checkBoxGrid.getStore();
for(i=0;i<list.size();i++)
{
for(int j=0;j<len;j++)
{
if(checkBoxGrid.getStore().get(i).getKey().equals(keys[j]) && checkBoxGrid.getStore().get(i).getValue().equals(values[j]))
{
checkBoxGrid.getStore().get(i).setChecked(true);
break;
}
else
checkBoxGrid.getStore().get(i).setChecked(false);
}
}
checkBoxGrid.getStore().commitChanges();
}
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public ListStore<ComboBoxModel> getListStore() {
return listStore;
}
public void setListStore(ListStore<ComboBoxModel> listStore) {
this.listStore = listStore;
}
private void updateGridStore(ListStore<ComboBoxModel> store)
{
checkBoxGrid.getStore().clear();
for(int i=0;i<store.size();i++)
{
checkBoxGrid.getStore().add(newComboBoxModel(listStore.get(i).getKey(),listStore.get(i).getValue(),false));
}
checkBoxGrid.getStore().commitChanges();
}
}
答案 1 :(得分:1)
我已经为GXT 3.X版本实现了几乎所有函数的MultiSelectComboBox。
这是我的代码:
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.event.RowClickEvent;
import com.sencha.gxt.widget.core.client.event.RowMouseDownEvent;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CheckBoxSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.tips.QuickTip;
import java.util.ArrayList;
import java.util.List;
public class MultiSelectComboBox<T> extends HorizontalPanel implements HasKeyDownHandlers {
private static final Icons ICONS = GWT.create(Icons.class);
private static final int SINGLE_ENTRY_HEIGHT = 22;
private static final int IMAGE_WIDTH = 17;
private TextField textField;
private Menu menu;
private Grid<SimpleValue> grid;
private ModelKeyProvider<T> keyProvider;
private LabelProvider<T> labelProvider;
private List<T> values;
private ListStore<SimpleValue> store;
private GridClickHandler clickHandler;
private CheckBoxSelectionModel<SimpleValue> sm;
private String noSelectionLabel;
private int listWidth;
private int width;
private MultiSelectComboBox(final ListStore<SimpleValue> store, ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider, List<T> values, String noSelectionLabel, int width, int listWidth) {
this.store = store;
this.keyProvider = keyProvider;
this.labelProvider = labelProvider;
this.values = values;
this.noSelectionLabel = noSelectionLabel;
this.width = width;
this.listWidth = listWidth;
init();
}
public List<T> getValues() {
List<T> selectedItems = new ArrayList<>();
for (SimpleValue data : sm.getSelectedItems()) {
selectedItems.add(findRealValueInternal(data.getKey()));
}
return selectedItems;
}
public void setValue(final T value) {
setValues(new ArrayList<T>() {{add(value);}});
}
public void setValues(List<T> values) {
for (T value : values) {
boolean alreadyExists = findRealValueInternal(keyProvider.getKey(value)) != null;
if (!alreadyExists) {
values.add(value);
grid.getStore().add(SimpleValue.create(value, keyProvider, labelProvider));
}
}
for (T value : values) {
SimpleValue simpleValue = findSimpleValueInternal(keyProvider.getKey(value));
if (!sm.isSelected(simpleValue)) {
sm.select(simpleValue, true);
}
}
}
public void addAll(List<T> list) {
for (T v : list) {
add(v);
}
grid.setHeight(values.size() * SINGLE_ENTRY_HEIGHT);
}
public void add(T value) {
boolean alreadyExists = findRealValueInternal(keyProvider.getKey(value)) != null;
if (!alreadyExists) {
values.add(value);
grid.getStore().add(SimpleValue.create(value, keyProvider, labelProvider));
}
}
public void clearStore() {
values.clear();
grid.getStore().clear();
}
private SimpleValue findSimpleValueInternal(String key) {
SimpleValue value = null;
for (SimpleValue v : grid.getStore().getAll()) {
if (v.getKey().equals(key)) {
value = v;
break;
}
}
return value;
}
public boolean isValid() {
return textField.isValid();
}
@SuppressWarnings("GWTStyleCheck")
private void init() {
menu = new Menu();
initGrid();
menu.add(grid);
textField = new TextField();
textField.setReadOnly(true);
textField.setValue(noSelectionLabel);
textField.setWidth(width);
textField.addStyleName("multiComboTextFieldStyle");
textField.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
textField.finishEditing();
textField.sinkEvents(KeyCodes.KEY_ENTER);
}
}
});
Image image = new Image(ICONS.arrowDown());
image.addStyleName("multiComboButtonStyle");
image.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
menu.showAt(textField.getAbsoluteLeft(), textField.getAbsoluteTop() + textField.getOffsetHeight());
}
});
add(textField);
add(image);
}
@SuppressWarnings({"unchecked", "GWTStyleCheck"})
private void initGrid() {
IdentityValueProvider<SimpleValue> identity = new IdentityValueProvider<>();
sm = new CheckBoxSelectionModel<SimpleValue>(identity) {
@Override
protected void onRowClick(RowClickEvent event) {}
@Override
protected void onRowMouseDown(RowMouseDownEvent event) {
boolean left = event.getEvent().getButton() == Event.BUTTON_LEFT;
XElement target = event.getEvent().getEventTarget().cast();
if (left && this.getAppearance().isRowChecker(target)) {
controlSelection(listStore.get(event.getRowIndex()));
}
}
};
sm.setSelectionMode(Style.SelectionMode.MULTI);
sm.addSelectionChangedHandler(new SelectionChangedEvent.SelectionChangedHandler<SimpleValue>() {
@Override
public void onSelectionChanged(SelectionChangedEvent<SimpleValue> simpleValueSelectionChangedEvent) {
setValuesToTextField();
}
});
ColumnModel columnModel = new ColumnModel(getConfigs());
grid = new Grid<>(store, columnModel, new ZGridView<SimpleValue>());
grid.setColumnReordering(true);
grid.getView().setColumnLines(true);
grid.setSelectionModel(sm);
grid.setWidth(listWidth);
grid.setHeight(values.size() * SINGLE_ENTRY_HEIGHT);
grid.setHideHeaders(true);
grid.getView().setColumnLines(false);
grid.addStyleName("z-grid-style");
new QuickTip(grid);
clickHandler = new GridClickHandler() {
@Override
public void onClick(Cell.Context context, Object value) {
controlSelection((SimpleValue)((Object[])value)[0]);
}
};
}
@SuppressWarnings("unchecked")
private List<ColumnConfig> getConfigs() {
List<ColumnConfig> configs = new ArrayList<>();
ColumnConfig<SimpleValue, String> columnConfig = new ColumnConfig(new ZEmptyValueProvider(), listWidth - sm.getColumn().getWidth());
columnConfig.setCell(new AbstractCell<String>("click") {
@Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
String htmlValue = store.get(context.getIndex()).getLabel();
sb.append(SafeHtmlUtils.fromTrustedString(htmlValue != null ? "<div style=\"font-size: 12px;\">" + htmlValue + "</div>": ""));
}
@Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (("click").equals(event.getType())) {
if (clickHandler != null) {
Object[] objects = new Object[1];
objects[0] = store.get(context.getIndex());
clickHandler.onClick(context, objects);
}
}
}
});
columnConfig.setColumnStyle(new SafeStyles() {
@Override
public String asString() {
return "cursor: pointer;";
}
});
columnConfig.setColumnStyle(new SafeStyles() {
@Override
public String asString() {
return "border: none;";
}
});
configs.add(sm.getColumn());
configs.add(columnConfig);
return configs;
}
private void controlSelection(SimpleValue model) {
if (model != null) {
if (sm.isSelected(model)) {
sm.deselect(model);
} else {
sm.select(model, true);
}
}
}
private void setValuesToTextField() {
if (sm.getSelectedItems().size() > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sm.getSelectedItems().size(); i++) {
sb.append(sm.getSelectedItems().get(i).getLabel());
if (i != sm.getSelectedItems().size() - 1) {
sb.append(", ");
}
}
textField.setValue(sb.toString());
textField.setToolTip(sb.toString());
} else {
textField.setValue(noSelectionLabel);
textField.removeToolTip();
}
}
private T findRealValueInternal(String key) {
T value = null;
for (T v : values) {
if (key.equals(keyProvider.getKey(v))) {
value = v;
break;
}
}
return value;
}
@Override
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return addDomHandler(handler, KeyDownEvent.getType());
}
public void setEnabled(boolean enabled) {
textField.setEnabled(enabled);
}
public void setToolTip(String tooltip) {
textField.setToolTip(tooltip);
}
public static class Builder<T> {
private ModelKeyProvider<T> keyProvider;
private LabelProvider<T> labelProvider;
private String noSelectionLabel;
private List<T> values = new ArrayList<>();
private int listWidth;
private int width = 130;
public Builder<T> keyProvider(ModelKeyProvider<T> keyProvider) {
this.keyProvider = keyProvider;
return this;
}
public Builder labelProvider(LabelProvider<T> labelProvider) {
this.labelProvider = labelProvider;
return this;
}
public Builder noSelectionLabel(String noSelectionLabel) {
this.noSelectionLabel = noSelectionLabel;
return this;
}
public Builder values(List<T> values) {
this.values = values;
return this;
}
public Builder listWidth(int listWidth) {
this.listWidth = listWidth;
return this;
}
public Builder width(int width) {
this.width = width;
return this;
}
@SuppressWarnings("unchecked")
public MultiSelectComboBox build() {
if (keyProvider == null) {
throw new IllegalStateException("KeyProvider is required");
}
if (labelProvider == null) {
throw new IllegalStateException("LabelProvider is required");
}
if (noSelectionLabel == null) {
noSelectionLabel = "";
}
ListStore<SimpleValue> store = new ListStore<>(new ModelKeyProvider<SimpleValue>() {
@Override
public String getKey(SimpleValue item) {
return item.getKey();
}
});
if (values.size() > 0) {
for (T obj : values) {
store.add(SimpleValue.create(obj, keyProvider, labelProvider));
}
}
return new MultiSelectComboBox(store, keyProvider, labelProvider, values, noSelectionLabel, width - IMAGE_WIDTH, listWidth == 0 ? width : listWidth - IMAGE_WIDTH);
}
}
public static class SimpleValue {
private String key;
private String label;
public SimpleValue() {}
public static <T> SimpleValue create(T obj, ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider) {
SimpleValue v = new SimpleValue();
v.setKey(keyProvider.getKey(obj));
v.setLabel(labelProvider.getLabel(obj));
return v;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
}
用法:
MultiSelectComboBox<String> combo = new MultiSelectComboBox.Builder<String>()
.values(Arrays.asList(Enum.values()))
.noSelectionLabel("All Values")
.width(150)
.listWidth(180)
.keyProvider(new ModelKeyProvider<String>() {
@Override
public String getKey(String s) {
return s;
}
})
.labelProvider(new LabelProvider<String>() {
@Override
public String getLabel(String s) {
return s;
}
})
.build();
答案 2 :(得分:0)
查看GXT-showcase的ListBindingExample。 http://www.sencha.com/examples/#ExamplePlace:listviewbinding
答案 3 :(得分:0)
FWIW我为工具栏实现了一些东西。
MultiSelectButton是一个TextButton,其菜单中包含多个复选框,摘要放在按钮标签中(EG:&#34;已选择:4/6&#34;)。
答案 4 :(得分:0)
我已经从David Lekishvili的版本开始实现了我自己的版本,并尝试在某些方面进行改进(使用GXT-3.1.4和GWT-2.7.0进行测试)。
以下是代码:
MultiSelectComboBox.java
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.Store;
import com.sencha.gxt.widget.core.client.event.FocusEvent;
import com.sencha.gxt.widget.core.client.event.RowClickEvent;
import com.sencha.gxt.widget.core.client.event.RowMouseDownEvent;
import com.sencha.gxt.widget.core.client.event.TriggerClickEvent;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.*;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.tips.QuickTip;
import java.util.ArrayList;
import java.util.List;
/**
* Custom GXT component allowing selection of several elements in a combobox along with filtering of values
* Custom rendering of values can be defined by providing a custom AbstractCell
*/
public class MultiSelectComboBox<T> extends HorizontalPanel {
/**
* Number of values that will be displayed in the drop down list before adding a vertical scrollbar
*/
private static final int NB_VALUES_WITHOUT_SCROLL = 15;
private static final int SINGLE_ENTRY_HEIGHT = 22;
/**
* Field displaying label of every checked values
*/
private MultiComboBoxTriggerField selectedItemsField = new MultiComboBoxTriggerField();
/**
* Menu contains the "select all checkbox" and the filtering text field
*/
private Menu menu = new Menu();
/**
* Grid with only one column containing the combobox elements
*/
private Grid<T> grid;
private CheckBox selectAllCheckBox = new CheckBox();
private ModelKeyProvider<T> keyProvider;
private LabelProvider<T> labelProvider;
private CheckBoxSelectionModel<T> sm;
private ColumnConfig<T, String> columnConfig;
/**
* Cell used for rendering of values in the combobox drop down list
*/
private AbstractCell cell;
/**
* Label to display in case no element is selected in the drop down list
*/
private String noSelectionLabel = new String("");
/**
* Combobox field default width
*/
private int selectedFieldWidth = 150;
/**
* Combobox drop down list default width
*/
private int listWidth = 185;
/**
* This default cell is the one used in case no custom cell has been specified in constructor
*/
private AbstractCell defaultCell = new AbstractCell() {
@Override
public void render(Cell.Context context, Object value, SafeHtmlBuilder sb) {
String label = labelProvider.getLabel(grid.getStore().get(context.getIndex()));
sb.append(SafeHtmlUtils.fromTrustedString(label != null ? "<div style=\"font-size: 12px;\">" + label + "</div>" : ""));
}
};
/**
* Default constructor
*
* @param keyProvider
* @param labelProvider
*/
public MultiSelectComboBox(ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider) {
this.keyProvider = keyProvider;
this.labelProvider = labelProvider;
cell = defaultCell;
init();
}
/**
* Constructor with a custom width for the drop down list
*
* @param keyProvider
* @param labelProvider
*/
public MultiSelectComboBox(ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider, int listWidth) {
this.keyProvider = keyProvider;
this.labelProvider = labelProvider;
this.listWidth = listWidth;
cell = defaultCell;
init();
}
/**
* Constructor with a custom cell defined for values rendering
*
* @param keyProvider
* @param labelProvider
* @param customCell
*/
public MultiSelectComboBox(ModelKeyProvider<T> keyProvider, LabelProvider<T> labelProvider, AbstractCell customCell) {
this.keyProvider = keyProvider;
this.labelProvider = labelProvider;
cell = customCell;
init();
}
private void init() {
StyleResources.INSTANCE.multiSelectComboStyle().ensureInjected();
initGrid();
// Filter field
final TextField filterField = new TextField();
filterField.setEmptyText(IpWebParameters.INSTANCE.filterLabel());
final Store.StoreFilter<T> filter = new Store.StoreFilter<T>() {
@Override
public boolean select(Store<T> store, T parent, T item) {
return labelProvider.getLabel(item).toUpperCase().contains(filterField.getText().toUpperCase());
}
};
// Filter values when the user fill in the filter field
filterField.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
if (filterField.getText().length() > 1) {
grid.getStore().removeFilters();
grid.getStore().addFilter(filter);
grid.getStore().setEnableFilters(true);
} else {
grid.getStore().removeFilters();
}
}
});
selectAllCheckBox.setStyleName(StyleResources.INSTANCE.multiSelectComboStyle().selectAllCheckbox());
selectAllCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
if (sm.isSelectAllChecked()) {
sm.setSelectAllChecked(false);
} else {
sm.setSelectAllChecked(true);
}
}
});
HorizontalPanel filterPanel = new HorizontalPanel();
filterPanel.add(selectAllCheckBox);
filterPanel.add(filterField);
// Menu contains filter fields and checkable items
menu.add(filterPanel);
menu.add(grid);
selectedItemsField.addStyleName(StyleResources.INSTANCE.multiSelectComboStyle().multiComboTextField());
// selectedItemsField.addStyleOnOver(selectedItemsField.getElement(), StyleResources.INSTANCE.multiSelectComboStyle().multiComboTextField());
selectedItemsField.setReadOnly(false);
selectedItemsField.setValue(noSelectionLabel);
selectedItemsField.setWidth(selectedFieldWidth);
selectedItemsField.addTriggerClickHandler(new TriggerClickEvent.TriggerClickHandler() {
@Override
public void onTriggerClick(TriggerClickEvent event) {
menu.showAt(selectedItemsField.getAbsoluteLeft(), selectedItemsField.getAbsoluteTop() + selectedItemsField.getOffsetHeight());
}
});
selectedItemsField.addFocusHandler(new FocusEvent.FocusHandler() {
@Override
public void onFocus(FocusEvent event) {
menu.showAt(selectedItemsField.getAbsoluteLeft(), selectedItemsField.getAbsoluteTop() + selectedItemsField.getOffsetHeight());
}
});
add(selectedItemsField);
}
/**
* Creation of the grid object required for rendering of values in the drop down list
*/
private void initGrid() {
IdentityValueProvider<T> identity = new IdentityValueProvider<>();
// Check elements when clicking on the checkbox or on the row
sm = new CheckBoxSelectionModel<T>(identity) {
@Override
protected void onRowClick(RowClickEvent event) {
boolean left = event.getEvent().getButton() == Event.BUTTON_LEFT;
XElement target = event.getEvent().getEventTarget().cast();
if (left && !this.getAppearance().isRowChecker(target)) {
controlSelection(listStore.get(event.getRowIndex()));
}
}
@Override
protected void onRowMouseDown(RowMouseDownEvent event) {
boolean left = event.getEvent().getButton() == Event.BUTTON_LEFT;
XElement target = event.getEvent().getEventTarget().cast();
if (left && this.getAppearance().isRowChecker(target)) {
controlSelection(listStore.get(event.getRowIndex()));
}
}
};
// allow the user to select multiple values
sm.setSelectionMode(Style.SelectionMode.MULTI);
// Manage selectAll check box depending on already selected values
sm.addSelectionChangedHandler(new SelectionChangedEvent.SelectionChangedHandler<T>() {
@Override
public void onSelectionChanged(SelectionChangedEvent<T> simpleValueSelectionChangedEvent) {
// automatically check or uncheck the "select all checkbox" depending on current user selection
if (selectAllCheckBox.getValue() && !isAllSelected()) {
selectAllCheckBox.setValue(false);
} else if (!selectAllCheckBox.getValue() && isAllSelected()) {
selectAllCheckBox.setValue(true);
}
setValuesToTextField();
}
});
ColumnModel columnModel = new ColumnModel(getColumnConfig());
ListStore<T> store = new ListStore<>(keyProvider);
grid = new Grid<>(store, columnModel, new GridView<T>());
grid.setSelectionModel(sm);
grid.setAllowTextSelection(false);
grid.setHideHeaders(true);
grid.setBorders(false);
// Define grid view properties
grid.getView().setColumnLines(false);
grid.getView().setAdjustForHScroll(false);
grid.getView().setTrackMouseOver(false);
refreshGridHeight();
new QuickTip(grid);
}
/**
* @return the list of currently selected items
*/
public List<T> getSelectedItems() {
return sm.getSelectedItems();
}
/**
* Add a list of checkable item in the drop down list
*
* @param list
*/
public void addAll(List<T> list) {
for (T v : list) {
add(v);
}
}
/**
* Add a checkable item in the drop down list
*
* @param item the item to add
*/
public void add(T item) {
boolean alreadyExists = false;
if (!alreadyExists) {
grid.getStore().add(item);
}
refreshGridHeight();
refreshColumnWidth();
}
/**
* @return all items contained in the store
*/
public List<T> getAllItems() {
return grid.getStore().getAll();
}
public void clearStore() {
grid.getStore().clear();
}
private List<ColumnConfig> getColumnConfig() {
List<ColumnConfig> configs = new ArrayList<>();
// Width depends on the presence of a scrollbar
columnConfig = new ColumnConfig(new IdentityValueProvider<T>());
columnConfig.setCell(cell);
columnConfig.setColumnStyle(new SafeStyles() {
@Override
public String asString() {
return "border: none;";
}
});
configs.add(sm.getColumn());
configs.add(columnConfig);
return configs;
}
/**
* Adjust drop down list height depending on the number of elements in the list. A scrollbar is added if this number exceeds NB_VALUES_WITHOUT_SCROLL
*/
private void refreshGridHeight() {
if (getAllItems().size() > NB_VALUES_WITHOUT_SCROLL) {
grid.setHeight(NB_VALUES_WITHOUT_SCROLL * SINGLE_ENTRY_HEIGHT);
} else {
grid.setHeight(getAllItems().size() * SINGLE_ENTRY_HEIGHT);
}
}
/**
* Refresh width of columns. Width depends on the presence of a scrollbar
*/
private void refreshColumnWidth() {
int columnWidth = listWidth - sm.getColumn().getWidth() - 25;
if (getAllItems().size() < NB_VALUES_WITHOUT_SCROLL) {
columnWidth = listWidth - sm.getColumn().getWidth();
}
columnConfig.setWidth(columnWidth);
}
/**
* Switch the selection status of the given item
*
* @param item
*/
private void controlSelection(T item) {
if (item != null) {
if (sm.isSelected(item)) {
sm.deselect(item);
} else {
sm.select(item, true);
}
}
}
/**
* Manage the label to display in the "selected item field". Label of each selected values is displayed or "All" if all elements are selected
*/
private void setValuesToTextField() {
if (sm.getSelectedItems().size() > 0) {
if (isAllSelected()) {
selectedItemsField.setValue(IpWebParameters.INSTANCE.all());
selectedItemsField.setToolTip(IpWebParameters.INSTANCE.all());
} else {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sm.getSelectedItems().size(); i++) {
sb.append(labelProvider.getLabel(sm.getSelectedItems().get(i)));
if (i != sm.getSelectedItems().size() - 1) {
sb.append(", ");
}
}
selectedItemsField.setValue(sb.toString());
selectedItemsField.setToolTip(sb.toString());
}
} else {
selectedItemsField.setValue(noSelectionLabel);
selectedItemsField.removeToolTip();
}
}
public void setEnabled(boolean enabled) {
selectedItemsField.setEnabled(enabled);
}
/**
* Set the matching model item as checked
*
* @param item the item to check
*/
public void select(T item) {
if (!sm.isSelected(item)) {
sm.select(true, item);
}
}
/**
* Set the matching model items as checked
*
* @param items the item to check
*/
public void select(List<T> items) {
for (T item : items) {
this.select(item);
}
}
/**
* Check all items in the drop down list
*/
public void selectAll() {
for (T item : getAllItems()) {
this.select(item);
}
}
/**
* deselect all items in the drop down list
*/
public void deselectAll() {
sm.deselectAll();
}
/**
* @return true if all items are selected
*/
public boolean isAllSelected() {
return sm.getSelection().size() == getAllItems().size();
}
public interface ComboStyle extends CssResource {
String multiComboTextField();
String selectAllCheckbox();
}
public interface StyleResources extends ClientBundle {
StyleResources INSTANCE = GWT.create(StyleResources.class);
@Source("multiselectcombo.css")
ComboStyle multiSelectComboStyle();
}
}
MultiComboBoxTriggerField.java
import com.google.gwt.event.shared.HandlerRegistration;
import com.sencha.gxt.cell.core.client.form.TriggerFieldCell;
import com.sencha.gxt.widget.core.client.event.CollapseEvent;
import com.sencha.gxt.widget.core.client.event.ExpandEvent;
import com.sencha.gxt.widget.core.client.form.PropertyEditor;
import com.sencha.gxt.widget.core.client.form.TriggerField;
/**
* Trigger Field for multi select combo box
*/
public class MultiComboBoxTriggerField extends TriggerField<String> implements ExpandEvent.HasExpandHandlers, CollapseEvent.HasCollapseHandlers {
public MultiComboBoxTriggerField() {
this(new TriggerFieldCell<String>(), new MultiComboBoxPropertyEditor());
}
protected MultiComboBoxTriggerField(TriggerFieldCell<String> cell, PropertyEditor<String> propertyEditor) {
super(cell, propertyEditor);
}
@Override
public HandlerRegistration addExpandHandler(ExpandEvent.ExpandHandler handler) {
return addHandler(handler, ExpandEvent.getType());
}
@Override
public HandlerRegistration addCollapseHandler(CollapseEvent.CollapseHandler handler) {
return addHandler(handler, CollapseEvent.getType());
}
}
MultiComboBoxPropertyEditor.java
import com.sencha.gxt.widget.core.client.form.PropertyEditor;
import java.text.ParseException;
/**
* Property editor for multi select combo box
*/
public class MultiComboBoxPropertyEditor extends PropertyEditor<String> {
@Override
public String parse(CharSequence text) throws ParseException {
return text.toString();
}
@Override
public String render(String object) {
return object;
}
}