package au.com.vaadinutils.ui;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.vaadin.peter.contextmenu.ContextMenu;
import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuClosedEvent;
import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuClosedListener;
import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuItem;
import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuItemClickEvent;
import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuItemClickListener;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.ui.TextField;
public class AutoCompleteTextField<E> extends TextField
{
private static final long serialVersionUID = 1L;
private AutoCompeleteQueryListener<E> listener;
private Map<E, String> options = new LinkedHashMap<>();
private AutoCompleteOptionSelected<E> optionListener;
private ContextMenu contextMenu;
private boolean isContextMenuOpen = false;
/**
* <pre>
* {@code
* sample usage
*
* AutoCompleteTextFieldV2<PostCode> suburb = new AutoCompleteTextFieldV2<>();
*
* suburb.setQueryListener(new AutoCompeleteQueryListener<PostCode>()
* {
*
* @Override
* public void handleQuery(AutoCompleteTextFieldV2<PostCode> field,String queryText)
* {
* field.addOption(new PostCode(3241),"Title");
* }
* });
*
* suburb.setOptionSelectionListener(new AutoCompleteOptionSelected<PostCode>()
* {
*
* @Override
* public void optionSelected(AutoCompleteTextFieldV2<PostCode> field, PostCode option)
* {
* field.setValue(option.getSuburb());
* }
* });
* }
* </pre>
*
* The ContextMenu is removed from the parent after each interaction to
* allow the default context menu to still work for copy/paste. However
* there is an issue with the ContextMenuClosedEvent firing before the
* ContextMenuItemClickEvent. This means that the ContextMenu can't be
* removed from the parent in onContextMenuClosed, otherwise
* contextMenuItemClicked is never entered. The effect of this is that the
* default context menu stops working if the user clicks out of the auto
* complete context menu without selecting anything.
*/
public AutoCompleteTextField(final String caption)
{
this();
setCaption(caption);
}
public AutoCompleteTextField()
{
setTextChangeEventMode(TextChangeEventMode.LAZY);
setImmediate(true);
addTextChangeListener(new TextChangeListener()
{
private static final long serialVersionUID = 1L;
@Override
public void textChange(final TextChangeEvent event)
{
options.clear();
if (listener != null)
{
listener.handleQuery(AutoCompleteTextField.this, event.getText());
}
if (!options.isEmpty())
{
createContextMenu();
showOptionMenu();
}
}
});
}
private void createContextMenu()
{
// Don't create a new context menu if the existing one is still open
if (isContextMenuOpen)
{
return;
}
// Create a new ContextMenu as each instance can only be added and
// removed from a parent once
contextMenu = new ContextMenu();
contextMenu.setAsContextMenuOf(this);
contextMenu.setOpenAutomatically(false);
contextMenu.addContextMenuCloseListener(new ContextMenuClosedListener()
{
@Override
public void onContextMenuClosed(ContextMenuClosedEvent event)
{
isContextMenuOpen = false;
}
});
}
private void showOptionMenu()
{
contextMenu.removeAllItems();
contextMenu.open(this);
isContextMenuOpen = true;
for (final Entry<E, String> option : options.entrySet())
{
ContextMenuItem menuItem = contextMenu.addItem(option.getValue());
menuItem.addItemClickListener(new ContextMenuItemClickListener()
{
@Override
public void contextMenuItemClicked(ContextMenuItemClickEvent event)
{
optionListener.optionSelected(AutoCompleteTextField.this, option.getKey());
// Remove the context menu when it gets closed to allow the
// default context menu to still be available
contextMenu.remove();
}
});
}
}
public void setOptionSelectionListener(AutoCompleteOptionSelected<E> listener)
{
this.optionListener = listener;
}
public void removeOptionSelectionListener()
{
optionListener = null;
}
public void setQueryListener(AutoCompeleteQueryListener<E> listener)
{
this.listener = listener;
}
public void removeQueryListener()
{
listener = null;
}
public void addOption(E option, String optionLabel)
{
options.put(option, optionLabel);
}
public void hideAutoComplete()
{
if (contextMenu != null)
{
contextMenu.hide();
}
}
}