|
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 comment has been minimized.