CustomComboBox.java
/*
** Module : CustomComboBox.java
** Abstract : CustomComboBox is a combo box that is used data grid as its model.
**
** Copyright (c) 2017-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 SBI 20170601 Created initial version.
** 002 CA 20180607 Changed to allow HTML table cells.
** 003 SBI 20231117 Reused GridHandle as the grid data model for this combobox.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.admin.client.widget;
import java.lang.reflect.Array;
import java.util.*;
import java.util.logging.*;
import com.google.common.base.Predicate;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.*;
import com.google.gwt.uibinder.client.*;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.*;
import com.google.gwt.dom.client.*;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.*;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.view.client.*;
import com.google.web.bindery.event.shared.*;
import org.gwtbootstrap3.client.ui.TextBox;
/**
*
* Grid based combo box. It displays a grid drop down.
*
* @param <TRowObject>
* The grid row object type
*/
public class CustomComboBox<TRowObject>
extends Composite
{
/** The logger */
private static Logger logger = Logger.getLogger("CustomComboBox");
/** The GWT UI creator */
interface Binder extends UiBinder<Widget, CustomComboBox>
{}
/** The instance of the GWT UI creator */
private static final Binder binder = GWT.create(Binder.class);
/** The widget container */
@UiField
HorizontalPanel container;
/** The text box */
@UiField
TextBox entry;
/** The dropdown button*/
@UiField
Button dropdown;
/** The dropdown panel */
private PopupPanel popup;
/** The selection handler */
private HandlerRegistration selectionModelHandler;
/** The parser for text entry presentation values */
private EntryValue<TRowObject> entryTextParser;
/** The grid handle that manages the grid placed on the dropdown panel */
private GridHandle<TRowObject> gridHandle;
/**
* Creates the instance of the grid combobox.
*
* @param popupWidth
* The css width
* @param popupHeight
* The css height
*/
@UiConstructor
public CustomComboBox(String popupWidth, String popupHeight)
{
initWidget(binder.createAndBindUi(this));
entry.setReadOnly(true);
entry.addKeyDownHandler(new KeyDownHandler()
{
@Override
public void onKeyDown(KeyDownEvent event)
{
NativeEvent nativeEvent = event.getNativeEvent();
int keyCode = nativeEvent.getKeyCode();
if (keyCode == KeyCodes.KEY_ENTER)
{
togglePopup();
}
}
});
popup = new PopupPanel(true)
{
@Override
protected void onPreviewNativeEvent(NativePreviewEvent event)
{
super.onPreviewNativeEvent(event);
Event nativeEvent = Event.as(event.getNativeEvent());
int type = nativeEvent.getTypeInt();
switch (type) {
case Event.ONKEYDOWN:
int keyCode = nativeEvent.getKeyCode();
if (keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_ESCAPE)
{
hidePopup();
}
break;
case Event.ONDBLCLICK:
hidePopup();
break;
default:
break;
}
}
};
popup.addStyleName("custom-combobox-popup");
popup.setHeight(popupHeight);
popup.setWidth(popupWidth);
popup.addAutoHidePartner(dropdown.getElement());
popup.addCloseHandler(new CloseHandler<PopupPanel>()
{
@Override
public void onClose(CloseEvent<PopupPanel> event)
{
toggleDropdownStyle();
}
});
}
/**
* Defines the presentation method to parse a row into its presentation value.
*
* @param <TRowObject>
* The row type
*/
public interface EntryValue<TRowObject>
{
/**
* Returns the presentation value of the set of selected rows.
*
* @param rows
* The given set of rows
*
* @return The row's presentation value
*/
String toEntryValue(Set<TRowObject> rows);
}
/**
* Returns the combobox text entry parser.
*
* @return The text entry parser
*/
public EntryValue<TRowObject> getEntryTextParser()
{
return entryTextParser;
}
/**
* Sets the combobox text entry parser.
*
* @param entryTextParser
* The text entry parser
*/
public void setEntryTextParser(EntryValue<TRowObject> entryTextParser)
{
this.entryTextParser = entryTextParser;
}
public void setGridHandle(GridHandle<TRowObject> gridHandle)
{
GridHandle<TRowObject> oldGridHandle = this.gridHandle;
this.gridHandle = gridHandle;
//clear data
if (oldGridHandle != null && oldGridHandle != gridHandle)
{
oldGridHandle.clearGrid();
oldGridHandle.clearSelection();
}
if (this.selectionModelHandler != null)
{
this.selectionModelHandler.removeHandler();
}
selectionModelHandler = this.gridHandle.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler()
{
@Override
public void onSelectionChange(SelectionChangeEvent event)
{
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
Set<TRowObject> selectedRows = CustomComboBox.this.gridHandle.getSelected();
String value;
if (!selectedRows.isEmpty())
{
value = getEntryTextParser().toEntryValue(selectedRows);
}
else
{
value = "";
}
entry.setValue(value);
}
});
}
});
//place the grid on the popup panel
DataGrid grid = this.gridHandle.getGrid();
popup.add(grid);
popup.addAutoHidePartner(grid.getElement());
grid.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.BOUND_TO_SELECTION);
}
/**
* Set data.
*
* @param data
* The table rows
*/
public void setData(TRowObject[] data)
{
gridHandle.setRows(data);
}
/**
* Gets the selected set.
*
* @return The selected values
*/
public Set<TRowObject> getValue()
{
if (gridHandle != null)
{
return gridHandle.getSelected();
}
return null;
}
/**
* Sets the selected value.
*
* @param row
* The selected row
*/
public void setValue(TRowObject row)
{
if (gridHandle != null)
{
gridHandle.select(row);
}
}
/**
* Resets the current selection
*/
public void resetValue()
{
//clear selection model
if (gridHandle != null)
{
this.gridHandle.clearSelection();
}
}
/**
* Defines the dropdown button handler.
*
* @param event
* The click event
*/
@UiHandler("dropdown")
void onDropDown(ClickEvent event)
{
togglePopup();
}
/**
* Sets the selected value by item selector.
*
* @param itemSelector
* The item selector
*/
public void selectItem(Predicate<TRowObject> itemSelector)
{
ListDataProvider<TRowObject> dataProvider = getDataProvider();
for (TRowObject value : dataProvider.getList())
{
if (itemSelector.apply(value))
{
resetValue();
setValue(value);
break;
}
}
}
/**
* Returns the data grid.
*
* @return The data grid.
*/
public DataGrid<TRowObject> getDataGrid()
{
if (gridHandle != null)
{
return (DataGrid<TRowObject>) gridHandle.getGrid();
}
return null;
}
/**
* Returns the selection model.
*
* @return The selection model
*/
public SelectionModel<TRowObject> getSelectionModel()
{
if (gridHandle != null)
{
return gridHandle.getSelectionModel();
}
return null;
}
/**
* Returns the data provider.
*
* @return The data provider
*/
public ListDataProvider<TRowObject> getDataProvider()
{
if (gridHandle != null)
{
return gridHandle.getDataProvider();
}
return null;
}
/**
* Requests the input focus for its data grid.
*/
private void focusDataGrid()
{
DataGrid<TRowObject> grid = getDataGrid();
if (grid != null)
{
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
logger.info("grid is in focus");
grid.setFocus(true);
}
});
}
}
/**
* Displays the dropdown.
*/
private void displayPopup()
{
popup.setPopupPositionAndShow(new PopupPanel.PositionCallback()
{
@Override
public void setPosition(int offsetWidth, int offsetHeight)
{
int left = container.getAbsoluteLeft();
int top = container.getAbsoluteTop() + container.getOffsetHeight();
popup.setPopupPosition(left, top);
}
});
toggleDropdownStyle();
}
/**
* Switches the dropdown to be visible or hidden.
*/
private void togglePopup()
{
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
if (hasElement(Document.get().getBody(), popup.getElement()))
{
popup.hide();
}
else
{
displayPopup();
focusDataGrid();
}
}
});
}
/**
* Hides the dropdown.
*/
private void hidePopup()
{
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
if (hasElement(Document.get().getBody(), popup.getElement()))
{
popup.hide();
}
}
});
}
/**
* Changes the presentation icon for the dropdown button.
*/
private void toggleDropdownStyle()
{
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
toggleClass(dropdown.getElement(), "glyphicon-triangle-bottom");
toggleClass(dropdown.getElement(), "glyphicon-triangle-top");
}
});
}
/**
* Imports the jQuery toggleClass function.
*
* @param e
* The html element
* @param style
* The css class
*/
private native void toggleClass(final Element e, final String style) /*-{
$wnd.jQuery(e).toggleClass(style);
}-*/;
/**
* Tests if the given container holds the target element.
*
* @param container
* The given container
* @param e
* The target element
*
* @return The true if the given container has the target element, otherwise false.
*/
private native boolean hasElement(final Element container, final Element e) /*-{
return $wnd.jQuery.contains(container, e);
}-*/;
}