Last active
May 11, 2022 15:25
Star
You must be signed in to star a gist
GXT combo with multiple checkbox for GXT 4.0.3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sencha.gxt.test.client.combo_multi_checkselect4; | |
import com.google.gwt.cell.client.AbstractCell; | |
import com.google.gwt.cell.client.ValueUpdater; | |
import com.google.gwt.dom.client.Element; | |
import com.google.gwt.dom.client.InputElement; | |
import com.google.gwt.dom.client.NativeEvent; | |
import com.google.gwt.safehtml.shared.SafeHtmlBuilder; | |
import com.google.gwt.safehtml.shared.SafeHtmlUtils; | |
import com.sencha.gxt.core.client.dom.XElement; | |
/** | |
* A simple native input checkbox. | |
* @since 4.0.3 | |
*/ | |
public class InputCheckboxCell extends AbstractCell<Boolean> { | |
public InputCheckboxCell() { | |
super("click", "touchstart"); | |
} | |
/** | |
* Allow selection when clicked. | |
*/ | |
@Override | |
public boolean handlesSelection() { | |
return true; | |
} | |
@Override | |
public void onBrowserEvent(Context context, Element parent, Boolean value, NativeEvent event, | |
ValueUpdater<Boolean> valueUpdater) { | |
super.onBrowserEvent(context, parent, value, event, valueUpdater); | |
XElement targetEl = event.getEventTarget().cast(); | |
if (targetEl != null && targetEl.getTagName().equalsIgnoreCase("input")) { | |
InputElement input = targetEl.cast(); | |
if (input != null) { | |
valueUpdater.update(input.isChecked()); | |
} | |
} | |
} | |
@Override | |
public void render(Context context, Boolean value, SafeHtmlBuilder sb) { | |
String checked = ""; | |
if (value) { | |
checked = "checked"; | |
} | |
String s = "<input type='checkbox' " + checked + " style='" + getStyle(context, value) + "' class='" | |
+ getStyleName(context, value) + "' />"; | |
sb.append(SafeHtmlUtils.fromTrustedString(s)); | |
} | |
/** | |
* Provide the input with a style attribute. | |
* @param value the checked value | |
* @param context the cell context | |
* | |
* @return styles | |
*/ | |
protected String getStyle(Context context, Boolean value) { | |
return ""; | |
} | |
/** | |
* Provide the input with a class attribute. | |
* @param value the checked value | |
* @param context the cell context | |
* | |
* @return the class attribute | |
*/ | |
protected String getStyleName(Context context, Boolean value) { | |
return ""; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import com.google.gwt.user.client.Event; | |
import com.sencha.gxt.data.shared.LabelProvider; | |
import com.sencha.gxt.data.shared.ListStore; | |
import com.sencha.gxt.widget.core.client.form.TriggerField; | |
public class MultiSelectComboBox<T> extends TriggerField<List<T>> { | |
/** | |
* Create an instance of the multiple select combo. | |
* | |
* @param listStore the possible values for selection. | |
* @param labelProvider the item label renderer. | |
*/ | |
public MultiSelectComboBox(ListStore<T> listStore, LabelProvider<T> labelProvider) { | |
this(new MultiSelectComboBoxCell<T>(listStore, labelProvider)); | |
} | |
/** | |
* Create an instance of the mutlti select combo with your own cell and property editor. | |
* | |
* @param cell a multi select combobox cell. | |
* @param propertyEditor a multi select combobox property editor. | |
*/ | |
public MultiSelectComboBox(MultiSelectComboBoxCell<T> cell) { | |
super(cell, cell.getPropertyEditor()); | |
} | |
@Override | |
protected void onResize(int width, int height) { | |
super.onResize(width, height); | |
getCell().syncListViewWidth(getElement()); | |
} | |
@Override | |
public void onBrowserEvent(Event event) { | |
super.onBrowserEvent(event); | |
// on key down | |
} | |
/** | |
* Sets the panel's expand state. | |
* | |
* @param expand true to expand | |
*/ | |
public void setExpanded(boolean expand) { | |
if (expand) { | |
getCell().expand(getElement(), getValue()); | |
} else { | |
getCell().collapse(); | |
} | |
} | |
@Override | |
public MultiSelectComboBoxCell<T> getCell() { | |
return (MultiSelectComboBoxCell<T>) super.getCell(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sencha.gxt.test.client.combo_multi_checkselect4; | |
import java.text.ParseException; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import com.google.gwt.cell.client.Cell; | |
import com.google.gwt.cell.client.CompositeCell; | |
import com.google.gwt.cell.client.FieldUpdater; | |
import com.google.gwt.cell.client.HasCell; | |
import com.google.gwt.cell.client.TextCell; | |
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.safehtml.shared.SafeHtmlBuilder; | |
import com.google.gwt.user.client.Event; | |
import com.google.gwt.user.client.Event.NativePreviewEvent; | |
import com.google.gwt.user.client.ui.RootPanel; | |
import com.sencha.gxt.cell.core.client.form.TriggerFieldCell; | |
import com.sencha.gxt.core.client.IdentityValueProvider; | |
import com.sencha.gxt.core.client.Style.Anchor; | |
import com.sencha.gxt.core.client.Style.AnchorAlignment; | |
import com.sencha.gxt.core.client.Style.HideMode; | |
import com.sencha.gxt.core.client.Style.SelectionMode; | |
import com.sencha.gxt.core.client.ValueProvider; | |
import com.sencha.gxt.core.client.dom.XDOM; | |
import com.sencha.gxt.core.client.dom.XElement; | |
import com.sencha.gxt.core.client.util.BaseEventPreview; | |
import com.sencha.gxt.data.shared.LabelProvider; | |
import com.sencha.gxt.data.shared.ListStore; | |
import com.sencha.gxt.widget.core.client.ListView; | |
import com.sencha.gxt.widget.core.client.ListViewSelectionModel; | |
import com.sencha.gxt.widget.core.client.event.XEvent; | |
import com.sencha.gxt.widget.core.client.form.PropertyEditor; | |
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent; | |
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler; | |
public class MultiSelectComboBoxCell<T> extends TriggerFieldCell<List<T>> { | |
/** | |
* Render and parse values to and from the input. | |
*/ | |
public static class MultiComboPropertyEditor<T> extends PropertyEditor<List<T>> { | |
protected MultiSelectComboBoxCell<T> cell; | |
protected LabelProvider<T> labelProvider; | |
public MultiComboPropertyEditor(MultiSelectComboBoxCell<T> cell, LabelProvider<T> labelProvider) { | |
this.cell = cell; | |
this.labelProvider = labelProvider; | |
} | |
/** | |
* Used to render the values into the input | |
*/ | |
@Override | |
public String render(List<T> objects) { | |
if (objects == null) { | |
return ""; | |
} | |
StringBuffer sb = new StringBuffer(); | |
for (int i = 0; i < objects.size(); i++) { | |
T item = objects.get(i); | |
String label = labelProvider.getLabel(item); | |
sb.append(label); | |
if (i < objects.size() - 1) { | |
sb.append(getItemSeparator()); | |
} | |
} | |
return sb.toString(); | |
} | |
/** | |
* Used to parse the values from the inputs. Instead use the cells selected list values. | |
*/ | |
@Override | |
public List<T> parse(CharSequence text) throws ParseException { | |
return cell.getValues(); | |
} | |
protected String getItemSeparator() { | |
return ", "; | |
} | |
} | |
public class ItemCheckBoxCellHasCell<T> implements HasCell<T, Boolean> { | |
private ListViewSelectionModel<T> selectionModel; | |
public ItemCheckBoxCellHasCell(ListViewSelectionModel<T> selectionModel) { | |
this.selectionModel = selectionModel; | |
} | |
@Override | |
public Cell<Boolean> getCell() { | |
return new InputCheckboxCell(); | |
} | |
@Override | |
public FieldUpdater<T, Boolean> getFieldUpdater() { | |
return new FieldUpdater<T, Boolean>() { | |
@Override | |
public void update(int index, T object, Boolean value) { | |
if (value) { | |
selectionModel.select(object, true); | |
} else { | |
selectionModel.deselect(object); | |
} | |
} | |
}; | |
} | |
@Override | |
public Boolean getValue(T item) { | |
return selectionModel.isSelected(item); | |
} | |
} | |
public class ItemTextHasCell<T> implements HasCell<T, String> { | |
protected TextCell cell = new TextCell(); | |
protected LabelProvider<T> labelProvider; | |
public ItemTextHasCell(LabelProvider<T> labelProvider) { | |
this.labelProvider = labelProvider; | |
} | |
@Override | |
public Cell<String> getCell() { | |
return cell; | |
} | |
@Override | |
public FieldUpdater<T, String> getFieldUpdater() { | |
return new FieldUpdater<T, String>() { | |
@Override | |
public void update(int index, T object, String value) { | |
} | |
}; | |
} | |
@Override | |
public String getValue(T object) { | |
return labelProvider.getLabel(object); | |
} | |
} | |
public static class ListViewSelectionModelExt<T> extends ListViewSelectionModel<T> { | |
public ListViewSelectionModelExt() { | |
super(); | |
setSelectionMode(SelectionMode.MULTI); | |
} | |
@Override | |
protected void doSelect(List<T> models, boolean keepExisting, boolean suppressEvent) { | |
// always keep existing | |
super.doSelect(models, true, suppressEvent); | |
} | |
@Override | |
protected void onMouseDown(NativeEvent event) { | |
XEvent e = event.<XEvent>cast(); | |
XElement target = e.getEventTargetEl(); | |
int selIndex = listView.findElementIndex(target); | |
if (selIndex == -1 || isLocked()) { | |
return; | |
} | |
T sel = listStore.get(selIndex); | |
if (sel == null) { | |
return; | |
} | |
boolean isSelected = isSelected(sel); | |
if (isSelected) { | |
doDeselect(Collections.singletonList(sel), false); | |
selected.remove(sel); | |
} else { | |
doSelect(Collections.singletonList(sel), true, false); | |
} | |
} | |
} | |
public static class ListViewExt<T, N> extends ListView<T, N> { | |
public ListViewExt(ListStore<T> store, ValueProvider<? super T, N> valueProvider, Cell<N> cell) { | |
super(store, valueProvider, cell); | |
} | |
@Override | |
public void onBrowserEvent(Event event) { | |
super.onBrowserEvent(event); | |
switch (event.getTypeInt()) { | |
case Event.ONCLICK: | |
onMouseDown(event); | |
break; | |
} | |
} | |
@Override | |
protected void onMouseOver(Event ce) { | |
// don't highlight on mouse over | |
// super.onMouseOver(ce); | |
} | |
} | |
protected ListViewExt<T, T> listView; | |
protected ListStore<T> store; | |
protected LabelProvider<T> labelProvider; | |
protected BaseEventPreview basePreviewEvent; | |
protected int listViewMaxHeight = 300; | |
protected ListViewSelectionModelExt<T> selectionModel; | |
protected List<T> showListSelectedValues; | |
protected boolean expanded; | |
public MultiSelectComboBoxCell(ListStore<T> store, LabelProvider<T> labelProvider) { | |
super(GWT.<TriggerFieldAppearance>create(TriggerFieldAppearance.class)); | |
this.store = store; | |
this.labelProvider = labelProvider; | |
this.propertyEditor = initPropertyEditor(labelProvider); | |
} | |
protected PropertyEditor<List<T>> initPropertyEditor(LabelProvider<T> labelProvider) { | |
return new MultiComboPropertyEditor<T>(this, labelProvider); | |
} | |
@Override | |
protected void onTriggerClick(Context context, XElement parent, NativeEvent event, List<T> values, | |
ValueUpdater<List<T>> updater) { | |
super.onTriggerClick(context, parent, event, values, updater); | |
if (!expanded) { | |
saveContext(context, parent, event, updater, values); | |
expand(parent, values); | |
} else { | |
collapse(); | |
} | |
} | |
/** | |
* Show the list view with the selected values. | |
* | |
* @param parent widget consuming this cell. | |
* @param values to be selected. | |
*/ | |
public void expand(XElement parent, List<T> values) { | |
this.showListSelectedValues = values; | |
if (!expanded) { | |
configureListView(); | |
showListView(); | |
} | |
} | |
/** | |
* Set the selected values. | |
* | |
* @param values to be selected. | |
*/ | |
public void setValues(List<T> values) { | |
selectionModel.setSelection(values); | |
} | |
/** | |
* Returns the selected values. | |
* | |
* @return the selected values. | |
*/ | |
public List<T> getValues() { | |
return selectionModel.getSelection(); | |
} | |
/** | |
* Sets the max height of the expanded list view. | |
* | |
* @param listViewMaxHeight sets the max height of the view. | |
*/ | |
public void setListViewMaxHeight(int listViewMaxHeight) { | |
this.listViewMaxHeight = listViewMaxHeight; | |
} | |
/** | |
* Sync width to a parent widget. | |
* | |
* @param syncWidthToParentWidgetElement the widget to sync the width to. | |
*/ | |
public void syncListViewWidth(XElement syncWidthToParentWidgetElement) { | |
if (!expanded) { | |
return; | |
} | |
listView.setWidth(getListViewWidth(syncWidthToParentWidgetElement)); | |
} | |
/** | |
* Initialize the list view. | |
*/ | |
protected void configureListView() { | |
if (listView != null) { | |
return; | |
} | |
selectionModel = new ListViewSelectionModelExt<>(); | |
selectionModel.addSelectionChangedHandler(new SelectionChangedHandler<T>() { | |
@Override | |
public void onSelectionChanged(SelectionChangedEvent<T> event) { | |
listView.refresh(); | |
List<T> selectedItems = event.getSelection(); | |
updateSelectedItems(selectedItems); | |
} | |
}); | |
listView = initListView(); | |
basePreviewEvent = new BaseEventPreview() { | |
@Override | |
protected boolean onPreview(NativePreviewEvent event) { | |
Element target = event.getNativeEvent().getEventTarget().cast(); | |
switch (event.getTypeInt()) { | |
case Event.ONSCROLL: | |
case Event.ONMOUSEWHEEL: | |
case Event.ONMOUSEDOWN: | |
// collapse if the mouse down is not on list or parent widget | |
if (listView != null && lastParent != null && !listView.getElement().isOrHasChild(target) && !lastParent.isOrHasChild(target)) { | |
collapse(); | |
} | |
break; | |
} | |
return true; | |
} | |
}; | |
basePreviewEvent.setAutoHide(false); | |
} | |
protected void updateSelectedItems(List<T> selectedItems) { | |
if (lastValueUpdater == null) { | |
return; | |
} | |
lastValueUpdater.update(selectedItems); | |
String renderedValues = getPropertyEditor().render(selectedItems); | |
setText(lastParent, renderedValues); | |
} | |
protected ListViewExt<T, T> initListView() { | |
List<HasCell<T, ?>> cells = new ArrayList<HasCell<T, ?>>(); | |
cells.add(initCheckBoxCell(selectionModel)); | |
cells.add(initTextCell(labelProvider)); | |
// add two cells to layout a checkbox and label - [] label | |
CompositeCell<T> listViewCell = initCompositeCell(cells); | |
listView = new ListViewExt<T, T>(store, new IdentityValueProvider<T>(XDOM.getUniqueId()), listViewCell) { | |
@Override | |
protected void onAfterFirstAttach() { | |
super.onAfterFirstAttach(); | |
// This can cause a lock up if called over and over | |
selectionModel.select(showListSelectedValues, false); | |
} | |
}; | |
listView.setSelectionModel(selectionModel); | |
listView.setHideMode(HideMode.OFFSETS); | |
listView.setSelectOnOver(false); | |
listView.setVisible(false); | |
listView.setBorders(true); | |
return listView; | |
} | |
protected CompositeCell<T> initCompositeCell(List<HasCell<T, ?>> cells) { | |
return new CompositeCell<T>(cells) { | |
@Override | |
protected <X> void render(Context context, T value, SafeHtmlBuilder sb, HasCell<T, X> hasCell) { | |
// draw the cells in a line | |
Cell<X> cell = hasCell.getCell(); | |
sb.appendHtmlConstant("<span style='display: inline-block; vertical-align:middle;'>"); | |
cell.render(context, hasCell.getValue(value), sb); | |
sb.appendHtmlConstant("</span> "); | |
} | |
}; | |
} | |
protected HasCell<T, ?> initCheckBoxCell(ListViewSelectionModelExt<T> selectionModel) { | |
return new ItemCheckBoxCellHasCell<T>(selectionModel); | |
} | |
protected HasCell<T, ?> initTextCell(LabelProvider<T> labelProvider) { | |
return new ItemTextHasCell<T>(labelProvider); | |
} | |
protected void showListView() { | |
expanded = true; | |
RootPanel.get().add(listView); | |
listView.getElement().getStyle().setProperty("maxHeight", getListViewMaxHeight(lastParent)); | |
listView.setWidth(getListViewWidth(lastParent)); | |
listView.getElement().makePositionable(true); | |
listView.setVisible(true); | |
basePreviewEvent.add(); | |
AnchorAlignment anchorAlignment = new AnchorAlignment(Anchor.TOP_LEFT, Anchor.BOTTOM_LEFT, true); | |
listView.getElement().alignTo(lastParent, anchorAlignment, 0, 0); | |
} | |
protected int getListViewWidth(XElement parent) { | |
return parent.getOffsetWidth(); | |
} | |
protected String getListViewMaxHeight(XElement parent) { | |
return listViewMaxHeight + "px"; | |
} | |
protected void collapse() { | |
doTriggerBlur(); | |
basePreviewEvent.remove(); | |
listView.setVisible(false); | |
clearContext(); | |
RootPanel.get().remove(listView); | |
expanded = false; | |
} | |
protected void doTriggerBlur() { | |
doTriggerBlur(lastContext, lastParent, lastValue, lastValueUpdater); | |
} | |
protected void doTriggerBlur(Context context, XElement parent, List<T> value, ValueUpdater<List<T>> valueUpdater) { | |
triggerBlur(context, parent, value, valueUpdater); | |
onBlur(context, parent, value, null, valueUpdater); | |
} | |
@Override | |
protected void saveContext(Context context, Element parent, NativeEvent event, ValueUpdater<List<T>> valueUpdater, | |
List<T> value) { | |
super.saveContext(context, parent, event, valueUpdater, value); | |
} | |
@Override | |
protected void clearContext() { | |
if (expanded) { | |
return; | |
} | |
super.clearContext(); | |
} | |
@Override | |
protected void onFocus(Context context, XElement parent, List<T> value, NativeEvent event, | |
ValueUpdater<List<T>> valueUpdater) { | |
super.onFocus(context, parent, value, event, valueUpdater); | |
saveContext(context, parent, event, valueUpdater, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayList; | |
import java.util.List; | |
import com.google.gwt.core.client.EntryPoint; | |
import com.google.gwt.user.client.ui.RootPanel; | |
import com.sencha.gxt.core.client.util.Margins; | |
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.test.client.combo_multi_checkselect2.Data; | |
import com.sencha.gxt.widget.core.client.button.TextButton; | |
import com.sencha.gxt.widget.core.client.container.BorderLayoutContainer; | |
import com.sencha.gxt.widget.core.client.container.BorderLayoutContainer.BorderLayoutData; | |
import com.sencha.gxt.widget.core.client.container.SimpleContainer; | |
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer; | |
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData; | |
import com.sencha.gxt.widget.core.client.container.Viewport; | |
import com.sencha.gxt.widget.core.client.event.SelectEvent; | |
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler; | |
import com.sencha.gxt.widget.core.client.info.Info; | |
public class MultiSelectComboTest4 implements EntryPoint { | |
@Override | |
public void onModuleLoad() { | |
List<Data> values = new ArrayList<Data>(); | |
values.add(new Data("0", "1")); | |
//values.add(new Data("5", "102")); | |
//values.add(new Data("7", "234")); | |
List<Data> selectionValues = new ArrayList<Data>(); | |
selectionValues.add(new Data("0", "1")); | |
selectionValues.add(new Data("1", "2")); | |
selectionValues.add(new Data("2", "3")); | |
selectionValues.add(new Data("3", "4")); | |
selectionValues.add(new Data("4", "101")); | |
selectionValues.add(new Data("5", "102")); | |
selectionValues.add(new Data("6", "103")); | |
selectionValues.add(new Data("7", "234")); | |
selectionValues.add(new Data("8", "235")); | |
selectionValues.add(new Data("9", "236")); | |
for (int i = 1000; i < 1500; i++) { | |
selectionValues.add(new Data(i + "", "Item: " + i)); | |
} | |
ListStore<Data> listStore = new ListStore<Data>(new ModelKeyProvider<Data>() { | |
@Override | |
public String getKey(Data item) { | |
return item.getId().toString(); | |
} | |
}); | |
listStore.addAll(selectionValues); | |
LabelProvider<Data> labelProvider = new LabelProvider<Data>() { | |
@Override | |
public String getLabel(Data item) { | |
return item.getName(); | |
} | |
}; | |
final MultiSelectComboBox<Data> combo = new MultiSelectComboBox<Data>(listStore, labelProvider); | |
combo.setValue(values); | |
final TextButton button = new TextButton("Get Values"); | |
button.addSelectHandler(new SelectHandler() { | |
@Override | |
public void onSelect(SelectEvent event) { | |
List<Data> values = combo.getValue(); | |
Info.display("", "values=" + values.size() + " values=" + values); | |
} | |
}); | |
VerticalLayoutContainer vlc = new VerticalLayoutContainer(); | |
vlc.getElement().getStyle().setProperty("border", "1px solid green"); | |
vlc.add(combo, new VerticalLayoutData(1, 50, new Margins(10))); | |
vlc.add(button, new VerticalLayoutData(1, -1, new Margins(10))); | |
BorderLayoutData layoutData = new BorderLayoutData(100); | |
BorderLayoutContainer blc = new BorderLayoutContainer(); | |
blc.setNorthWidget(new SimpleContainer(), layoutData); | |
blc.setEastWidget(new SimpleContainer(), layoutData); | |
blc.setWestWidget(new SimpleContainer(), layoutData); | |
blc.setWidget(vlc); | |
Viewport vp = new Viewport(); | |
vp.add(blc); | |
RootPanel.get().add(vp); | |
} | |
} |
Author
branflake2267
commented
Aug 10, 2017
Hello, dear Brandon. Please, help me with your MultiSelectComboBox. How can I attach SelectionHandler, or order Handler to handle Selection Events to this MultiSelectComboBox ? I need to handle selection events, but all handlers i tryed with this component had no effect. Can you explain in some words possibilityes of selection handling in MultiSelectComboBox ?
Looks like the list of the selected items is not always well refreshed.
If I do some select/clear/select cycles the text is correct, but when you open the combobox list nothing is elected, even if the getValue list returns the right values. 😔
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment