Skip to content

Instantly share code, notes, and snippets.

@branflake2267
Created April 2, 2019 21:08
Show Gist options
  • Save branflake2267/97c92fe470aa7b03c6667c0263ac97ee to your computer and use it in GitHub Desktop.
Save branflake2267/97c92fe470aa7b03c6667c0263ac97ee to your computer and use it in GitHub Desktop.
GXT 4.0.3 - ListViewSelectionModel selection fix, when you click on the scroll bar and use shift to persist the selection.
package com.sencha.gxt.widget.core.client;
import java.util.Collections;
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.KeyCodes;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.gestures.PointerEventsSupport;
import com.sencha.gxt.core.client.gestures.TapGestureRecognizer;
import com.sencha.gxt.core.client.gestures.TouchData;
import com.sencha.gxt.core.client.util.KeyNav;
import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.event.RefreshEvent;
import com.sencha.gxt.widget.core.client.event.RefreshEvent.RefreshHandler;
import com.sencha.gxt.widget.core.client.event.XEvent;
import com.sencha.gxt.widget.core.client.selection.AbstractStoreSelectionModel;
/**
* ListView selection model.
*
* @param <M> the model type
*/
public class ListViewSelectionModel<M> extends AbstractStoreSelectionModel<M> {
protected class Handler implements MouseDownHandler, MouseUpHandler, ClickHandler, RefreshHandler {
@Override
public void onClick(ClickEvent event) {
onMouseClick(event);
}
@Override
public void onMouseDown(MouseDownEvent event) {
ListViewSelectionModel.this.onMouseDown(event);
}
@Override
public void onMouseUp(MouseUpEvent event) {
ListViewSelectionModel.this.onMouseUp(event);
}
@Override
public void onRefresh(RefreshEvent event) {
ListViewSelectionModel.this.onRefresh(event);
}
}
protected boolean enableNavKeys = true;
protected KeyNav keyNav = new KeyNav() {
@Override
public void onDown(NativeEvent e) {
if (isVertical) {
ListViewSelectionModel.this.onKeyDown(e);
}
}
@Override
public void onKeyPress(NativeEvent ce) {
ListViewSelectionModel.this.onKeyPress(ce);
}
@Override
public void onLeft(NativeEvent e) {
if (!isVertical) {
ListViewSelectionModel.this.onKeyUp(e);
}
}
@Override
public void onRight(NativeEvent e) {
if (!isVertical) {
ListViewSelectionModel.this.onKeyDown(e);
}
}
@Override
public void onUp(NativeEvent e) {
if (isVertical) {
ListViewSelectionModel.this.onKeyUp(e);
}
}
};
protected ListStore<M> listStore;
protected ListView<M, ?> listView;
/**
* True to deselect a selected item on click (defaults to {@code true).
*/
protected boolean deselectOnSimpleClick = true;
protected boolean isVertical = true;
protected Handler handler = new Handler();
protected GroupingHandlerRegistration handlerRegistration = new GroupingHandlerRegistration();
/**
* Track the selection index, when the shift combined with and so then this is the starting point of the selection.
*/
protected int indexOnSelectNoShift;
/**
* Binds the list view to the selection model.
*
* @param listView the list view
*/
public void bindList(ListView<M, ?> listView) {
if (this.listView != null) {
handlerRegistration.removeHandler();
keyNav.bind(null);
this.listStore = null;
bind(null);
}
this.listView = listView;
if (listView != null) {
if (handlerRegistration == null) {
handlerRegistration = new GroupingHandlerRegistration();
}
handlerRegistration.add(listView.addDomHandler(handler, MouseDownEvent.getType()));
handlerRegistration.add(listView.addDomHandler(handler, MouseUpEvent.getType()));
handlerRegistration.add(listView.addDomHandler(handler, ClickEvent.getType()));
handlerRegistration.add(listView.addRefreshHandler(handler));
keyNav.bind(listView);
bind(listView.getStore());
this.listStore = listView.getStore();
listView.addGestureRecognizer(new TapGestureRecognizer() {
@Override
protected void onTap(TouchData touchData) {
super.onTap(touchData);
ListViewSelectionModel.this.onTap(touchData);
}
});
}
}
/**
* Returns the currently bound list view.
*
* @return the list view
*/
public ListView<M, ?> getListView() {
return listView;
}
/**
* Returns {@code true} if up and down arrow keys are used for navigation. Else left and right arrow keys are used.
*
* @return the isVertical
*/
public boolean isVertical() {
return isVertical;
}
/**
* Sets if up and down arrow keys or left and right arrow keys should be used (defaults to {@code true}).
*
* @param isVertical the isVertical to set
*/
public void setVertical(boolean isVertical) {
this.isVertical = isVertical;
}
/**
* since 4.0.3
*/
protected void onRefresh(RefreshEvent event) {
refresh();
if (getLastFocused() != null) {
listView.onHighlightRow(listStore.indexOf(getLastFocused()), true);
}
}
protected void onMouseClick(ClickEvent ce) {
onMouseClick(ce.getNativeEvent());
}
protected void onMouseClick(NativeEvent event) {
if (fireSelectionChangeOnClick) {
fireSelectionChange();
fireSelectionChangeOnClick = false;
}
}
protected void onMouseDown(MouseDownEvent event) {
onMouseDown(event.getNativeEvent());
}
protected void onMouseDown(NativeEvent event) {
if (PointerEventsSupport.impl.isSupported()) {
return;
}
if (!processSelectOnMouseUp) {
onSelect(event);
}
}
/**
* Returns if the click is on the scroll bar.
*
* @param event - native event
* @return true or false
* @since 4.0.4
*/
protected boolean isScrollBarEvent(NativeEvent event) {
Element target = event.getEventTarget().cast();
if (target == null) {
return false;
}
int selIndex = listView.findElementIndex(target);
// Figure if the event is on the scroll bar
if (selIndex == -1) {
return true;
}
return false;
}
/**
* @since 4.0.3
*/
protected void onMouseUp(MouseUpEvent event) {
onMouseUp(event.getNativeEvent());
}
/**
* @since 4.0.3
*/
protected void onMouseUp(NativeEvent event) {
if (processSelectOnMouseUp) {
onSelect(event);
}
}
/**
* @since 4.0.3
*/
protected void onTap(TouchData touchData) {
onSelect(touchData.getLastNativeEvent());
}
/**
* @since 4.0.3
*/
protected void onSelect(NativeEvent nativeEvent) {
// Ignore scroll bar events
if (isScrollBarEvent(nativeEvent)) {
return;
}
XEvent event = nativeEvent.<XEvent>cast();
XElement target = event.getEventTargetEl();
int selIndex = listView.findElementIndex(target);
if (isLocked() || isInput(target)) {
return;
}
if (selIndex == -1) {
deselectAll();
return;
}
mouseDown = true;
if (event.isRightClick()) {
if (selectionMode != SelectionMode.SINGLE && isSelected(listStore.get(selIndex))) {
onRightClick(nativeEvent, selIndex);
return;
}
select(selIndex, false);
listView.focusItem(selIndex);
} else {
M selectedModel = listStore.get(selIndex);
if (selectedModel == null) {
return;
}
boolean isSelected = isSelected(selectedModel);
boolean isMeta = event.getCtrlOrMetaKey();
boolean isShift = event.getShiftKey();
switch (selectionMode) {
case SIMPLE:
listView.focusItem(selIndex);
if (!isSelected) {
select(selectedModel, true);
} else if (isSelected && deselectOnSimpleClick) {
deselect(selectedModel);
}
break;
case SINGLE:
if (isMeta && isSelected) {
deselect(selectedModel);
} else if (!isSelected) {
listView.focusItem(selIndex);
select(selectedModel, false);
}
break;
case MULTI:
if (isMeta && isSelected) {
// reset the starting location of the click
indexOnSelectNoShift = selIndex;
doDeselect(Collections.singletonList(selectedModel), false);
} else if (isMeta) {
// reset the starting location of the click
indexOnSelectNoShift = selIndex;
doSelect(Collections.singletonList(selectedModel), true, false);
listView.focusItem(selIndex);
} else if (isSelected && !isShift && !isMeta && selected.size() > 1) {
// reset the starting location of the click
indexOnSelectNoShift = selIndex;
doSelect(Collections.singletonList(selectedModel), false, false);
listView.focusItem(selIndex);
} else if (isShift && lastSelected != null) {
int start;
int end;
// This deals with flipping directions
if (indexOnSelectNoShift < selIndex) {
start = indexOnSelectNoShift;
end = selIndex;
} else {
start = selIndex;
end = indexOnSelectNoShift;
}
select(start, end, false);
listView.focusItem(end);
} else if (!isSelected) {
// reset the starting location of multi select
indexOnSelectNoShift = selIndex;
listView.focusItem(selIndex);
doSelect(Collections.singletonList(selectedModel), false, false);
}
break;
}
}
mouseDown = false;
}
/**
* @since 4.0.3
*/
protected void onRightClick(NativeEvent event, int selectedIndex) {
}
protected boolean isInput(Element target) {
String tag = target.getTagName();
return "INPUT".equals(tag) || "TEXTAREA".equals(tag);
}
protected void onKeyDown(NativeEvent event) {
XEvent e = event.<XEvent>cast();
if (!e.getCtrlOrMetaKey() && selected.size() == 0 && getLastFocused() == null) {
select(0, false);
} else {
int idx = listStore.indexOf(getLastFocused());
if (idx >= 0 && (idx + 1) < listStore.size()) {
if (e.getCtrlOrMetaKey() || (e.getShiftKey() && isSelected(listStore.get(idx + 1)))) {
if (!e.getCtrlOrMetaKey()) {
deselect(idx);
}
M lF = listStore.get(idx + 1);
if (lF != null) {
setLastFocused(lF);
listView.focusItem(idx + 1);
}
} else {
if (e.getShiftKey() && lastSelected != getLastFocused()) {
select(listStore.indexOf(lastSelected), idx + 1, true);
listView.focusItem(idx + 1);
} else {
if (idx + 1 < listStore.size()) {
select(idx + 1, e.getShiftKey());
listView.focusItem(idx + 1);
}
}
}
}
}
e.preventDefault();
}
protected void onKeyPress(NativeEvent event) {
XEvent e = event.<XEvent>cast();
if (lastSelected != null && enableNavKeys) {
int kc = e.getKeyCode();
if (kc == KeyCodes.KEY_PAGEUP || kc == KeyCodes.KEY_HOME) {
e.stopEvent();
select(0, false);
listView.focusItem(0);
} else if (kc == KeyCodes.KEY_PAGEDOWN || kc == KeyCodes.KEY_END) {
e.stopEvent();
int idx = listStore.indexOf(listStore.get(listStore.size() - 1));
select(idx, false);
listView.focusItem(idx);
}
}
// if space bar is pressed
if (e.getKeyCode() == 32) {
if (getLastFocused() != null) {
if (e.getShiftKey() && lastSelected != null) {
int last = listStore.indexOf(lastSelected);
int i = listStore.indexOf(getLastFocused());
select(last, i, e.getCtrlOrMetaKey());
listView.focusItem(i);
} else {
if (isSelected(getLastFocused())) {
deselect(getLastFocused());
} else {
select(getLastFocused(), true);
listView.focusItem(listStore.indexOf(getLastFocused()));
}
}
}
}
}
protected void onKeyUp(NativeEvent event) {
XEvent e = event.<XEvent>cast();
int idx = listStore.indexOf(getLastFocused());
if (idx >= 1) {
if (e.getCtrlOrMetaKey() || (e.getShiftKey() && isSelected(listStore.get(idx - 1)))) {
if (!e.getCtrlOrMetaKey()) {
deselect(idx);
}
M lF = listStore.get(idx - 1);
if (lF != null) {
setLastFocused(lF);
listView.focusItem(idx - 1);
}
} else {
if (e.getShiftKey() && lastSelected != getLastFocused()) {
select(listStore.indexOf(lastSelected), idx - 1, true);
listView.focusItem(idx - 1);
} else {
if (idx > 0) {
select(idx - 1, e.getShiftKey());
listView.focusItem(idx - 1);
}
}
}
}
e.preventDefault();
}
@Override
protected void onLastFocusChanged(M oldFocused, M newFocused) {
int i;
if (oldFocused != null) {
i = listStore.indexOf(oldFocused);
if (i >= 0) {
listView.onHighlightRow(i, false);
}
}
if (newFocused != null) {
i = listStore.indexOf(newFocused);
if (i >= 0) {
listView.onHighlightRow(i, true);
}
}
}
@Override
protected void onSelectChange(M model, boolean select) {
listView.onSelectChange(model, select);
}
void onRowUpdated(M model) {
if (isSelected(model)) {
onSelectChange(model, true);
}
if (getLastFocused() == model) {
setLastFocused(getLastFocused());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment