/*
* Copyright 2015 Explicatis GmbH <ext-token-field@explicatis.com>
*
* Author: Florian Schmitt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ui;
import java.time.LocalDate;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.explicatis.ext_token_field.ExtTokenField;
import com.explicatis.ext_token_field.SimpleTokenizable;
import com.explicatis.ext_token_field.Tokenizable;
import com.explicatis.ext_token_field.TokenizableAction;
import com.explicatis.ext_token_field.events.TokenAddedEvent;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
import com.vaadin.data.Binder;
import com.vaadin.data.BinderValidationStatus;
import com.vaadin.data.HasValue.ValueChangeListener;
import com.vaadin.data.ValidationResult;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.server.Page;
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
@SuppressWarnings("serial")
@Theme(ValoTheme.THEME_NAME)
@Widgetset(value = "widgetset.WidgetSet")
@SpringUI
public class TestUI extends UI
{
private static String[] LANGUAGES = {"PHP", "Java", "JavaScript", "Scala", "Python", "C", "Ruby", "C++"};
private VerticalLayout notes;
private VerticalLayout mainLayout;
private Registration valueChangeListenerRegistration;
private Set<Registration> tokenChangeListeners;
@Override
protected void init(VaadinRequest vaadinRequest)
{
mainLayout = new VerticalLayout();
Label heading = new Label("ExtTokenField");
heading.addStyleName(ValoTheme.LABEL_HUGE);
Label subheading = new Label(String.format("%d - Explicatis GmbH, Florian Schmitt", LocalDate.now().getYear()));
subheading.addStyleName(ValoTheme.LABEL_TINY);
VerticalLayout head = new VerticalLayout(heading, subheading);
head.setMargin(new MarginInfo(false, true));
head.setSpacing(false);
notes = new VerticalLayout();
notes.setSizeFull();
notes.setMargin(new MarginInfo(false, true));
notes.setSpacing(false);
addNote("Keyboard controls (arrow-left, arrow-right & delete)");
addNote("ComboBox or Button input (TextField support is planned)");
addNote("Custom actions can be defined, if the setInheritsReadOnlyAndEnabled setting is false, they will be still be usable, when the ExtTokenField is set to read only or is not enabled");
addNote("trims long token captions so that token is clickable to expose full caption (planned to be configurable, as to trimming length)");
addNote("implement <b>Tokenizable</b> interface in your bean or entity class to be able to set the fields value as a List of these objects");
mainLayout.addComponent(head);
mainLayout.addComponent(notes);
mainLayout.addComponent(new ConfigurableLayout());
setContent(mainLayout);
}
private void addNote(String note)
{
Label l1 = new Label();
l1.setIcon(VaadinIcons.CIRCLE);
Label l2 = new Label(note, ContentMode.HTML);
HorizontalLayout hl = new HorizontalLayout(l1, l2);
hl.setMargin(false);
hl.setHeight(10, Unit.PIXELS);
hl.setComponentAlignment(l1, Alignment.MIDDLE_LEFT);
hl.setComponentAlignment(l2, Alignment.MIDDLE_LEFT);
notes.addComponent(hl);
}
private static long tokenCollectionIdDummy = 0;
private static Collection<SimpleTokenizable> initTokenCollection()
{
tokenCollectionIdDummy = 0;
return Stream.of(LANGUAGES)//
.sorted()//
.map(name -> new SimpleTokenizable(tokenCollectionIdDummy++, name))//
.collect(Collectors.toList());
}
private static ComboBox<SimpleTokenizable> buildComboBox()
{
ComboBox<SimpleTokenizable> result = new ComboBox<>("", initTokenCollection());
result.setItemCaptionGenerator(SimpleTokenizable::getStringValue);
result.setPlaceholder("Type here to add");
return result;
}
private Button buildAddButton()
{
Button result = new Button();
result.setCaption("add element");
result.setIcon(VaadinIcons.PLUS_CIRCLE);
result.addStyleName(ValoTheme.BUTTON_BORDERLESS);
result.addClickListener(event -> notificate("add clicked"));
return result;
}
private ValueChangeListener<SimpleTokenizable> getComboBoxValueChange(ExtTokenField extTokenField)
{
return event -> {
SimpleTokenizable value = event.getValue();
if (value != null)
{
extTokenField.addTokenizable(value);
event.getSource().setValue(null);
}
};
}
private void addValueChangeListeners(ExtTokenField extTokenField)
{
valueChangeListenerRegistration = extTokenField.addValueChangeListener(event -> notificate("Value change: " + event.getValue()));
}
private void addTokenListeners(ExtTokenField extTokenField)
{
if (tokenChangeListeners == null)
{
tokenChangeListeners = new HashSet<>();
}
tokenChangeListeners.add(extTokenField.addTokenAddedListener(event -> notificate("Token added: " + event.getTokenizable().getStringValue())));
tokenChangeListeners.add(extTokenField.addTokenRemovedListener(event -> notificate("Token removed: " + event.getTokenizable().getStringValue())));
tokenChangeListeners.add(extTokenField.addTokenReorderedListener(
event -> notificate(String.format("Token reordered: source=%s target=%s type=%s", event.getSourceTokenizable().getStringValue(), event.getTargetTokenizable().getStringValue(), event.getDropTargetType().toString()))));
}
private void removeValueChangeListener(ExtTokenField extTokenField)
{
if (valueChangeListenerRegistration != null)
{
valueChangeListenerRegistration.remove();
valueChangeListenerRegistration = null;
}
}
private void removeValueAddedAndRemovedListeners(ExtTokenField extTokenField)
{
if (tokenChangeListeners != null)
{
tokenChangeListeners.forEach(Registration::remove);
tokenChangeListeners = null;
}
}
protected void notificate(String msg)
{
Notification notification = new Notification(msg);
notification.setDelayMsec(5000);
notification.show(Page.getCurrent());
}
private class ConfigurableLayout extends VerticalLayout
{
private ExtTokenField tokenField = new ExtTokenField();
private CheckBox delete = new CheckBox("activate or deactivate delete action", true);
private CheckBox comboBoxOrButton = new CheckBox("ComboBox or Button");
private CheckBox readOnly = new CheckBox("read only");
private CheckBox required = new CheckBox("required", true);
private CheckBox enabled = new CheckBox("enabled", true);
private CheckBox addCustomAction = new CheckBox("add or remove custom action");
private CheckBox readOnlyIgnoringCustomAction = new CheckBox("should the custom action ignore read only");
private CheckBox activateTokenListeners = new CheckBox("add or remove TokenAddedListener & TokenRemovedListener & TokenReorderedListener");
private CheckBox activateValueChangeListener = new CheckBox("add or remove ValueChangeListener");
private CheckBox enableDragDrop = new CheckBox("enable drag and drop reordering");
private ComboBox<SimpleTokenizable> comboBox = TestUI.buildComboBox();
private Button addButton = buildAddButton();
private Binder<DemoBean> binder = new Binder<>(DemoBean.class);
public ConfigurableLayout()
{
binder.setBean(new DemoBean());
initTokenField();
bindTokenField();
setSampleTokenizableValue();
comboBox.addValueChangeListener(getComboBoxValueChange(tokenField));
FormLayout formLayout = new FormLayout(tokenField);
formLayout.setSizeFull();
setMargin(new MarginInfo(false, true));
addComponent(formLayout);
FormLayout configLayout = new FormLayout(readOnly, enabled, required, delete, comboBoxOrButton, addCustomAction, readOnlyIgnoringCustomAction, activateValueChangeListener, activateTokenListeners, enableDragDrop);
configLayout.setCaption("modify settings");
configLayout.setSizeFull();
addComponent(configLayout);
setupCheckBoxes();
initTestButtons();
}
private void bindTokenField()
{
binder.forField(tokenField)
.asRequired("a value is required")
.bind("tokens");
}
private void initTestButtons()
{
Button focusTestButton = initFocusTestButton();
Button validateTestButton = initValidateTestButton();
HorizontalLayout btnLayout = new HorizontalLayout(focusTestButton, validateTestButton);
btnLayout.setSpacing(true);
addComponent(btnLayout);
}
private void initTokenField()
{
tokenField.setCaption("Tokens");
tokenField.setInputField(comboBox);
tokenField.setEnableDefaultDeleteTokenAction(delete.getValue());
}
private void setSampleTokenizableValue()
{
List<SimpleTokenizable> list = buildSampleTokenizableList();
binder.setBean(new DemoBean(list));
}
private List<SimpleTokenizable> buildSampleTokenizableList()
{
List<SimpleTokenizable> result = new LinkedList<>();
List<String> list = Stream.of(LANGUAGES)//
.limit(LANGUAGES.length - 2)//
.sorted()//
.collect(Collectors.toList());
for (int i = 0; i < list.size(); i++)
{
result.add(new SimpleTokenizable(i, list.get(i)));
}
return result;
}
private void setupCheckBoxes()
{
delete.addValueChangeListener(event -> tokenField.setEnableDefaultDeleteTokenAction(delete.getValue()));
readOnly.addValueChangeListener(event -> tokenField.setReadOnly(!tokenField.isReadOnly()));
enabled.addValueChangeListener(event -> tokenField.setEnabled(!tokenField.isEnabled()));
comboBoxOrButton.addValueChangeListener(event -> {
if (tokenField.hasInputField())
tokenField.setInputButton(addButton);
else
tokenField.setInputField(comboBox);
});
TokenizableAction tokenizableAction = new TokenizableAction("id1", VaadinIcons.VAADIN_V)
{
@Override
public void onClick(Tokenizable token)
{
notificate("clicked " + token.getStringValue());
};
};
addCustomAction.addValueChangeListener(event -> {
if (tokenField.hasTokenizableAction(tokenizableAction))
{
tokenField.removeTokenizableAction(tokenizableAction);
}
else
{
tokenizableAction.setInheritsReadOnlyAndEnabled(!readOnlyIgnoringCustomAction.getValue());
tokenField.addTokenAction(tokenizableAction);
}
});
readOnlyIgnoringCustomAction.addValueChangeListener(event -> {
if (tokenField.hasTokenizableAction(tokenizableAction))
{
tokenField.removeTokenizableAction(tokenizableAction);
tokenizableAction.setInheritsReadOnlyAndEnabled(!readOnlyIgnoringCustomAction.getValue());
tokenField.addTokenAction(tokenizableAction);
}
});
activateTokenListeners.addValueChangeListener(event -> {
if (tokenField.getListeners(TokenAddedEvent.class).isEmpty())
{
addTokenListeners(tokenField);
}
else
{
removeValueAddedAndRemovedListeners(tokenField);
}
});
activateValueChangeListener.addValueChangeListener(event -> {
if (valueChangeListenerRegistration == null)
{
addValueChangeListeners(tokenField);
}
else
{
removeValueChangeListener(tokenField);
}
});
required.addValueChangeListener(e -> tokenField.setRequiredIndicatorVisible(required.getValue()));
enableDragDrop.addValueChangeListener(e -> tokenField.setTokenDragDropEnabled(enableDragDrop.getValue()));
}
private Button initFocusTestButton()
{
Button result = new Button("focus field", this::focusField);
return result;
}
private Button initValidateTestButton()
{
Button result = new Button("validate field", this::validateField);
return result;
}
private void focusField(ClickEvent event)
{
tokenField.focus();
}
private void validateField(ClickEvent event)
{
BinderValidationStatus<DemoBean> status = binder.validate();
if (status.hasErrors())
{
List<ValidationResult> errors = status.getValidationErrors();
String msg = errors.stream()//
.map(ValidationResult::getErrorMessage)//
.collect(Collectors.joining(","));
notificate("Error: " + msg);
}
else
{
notificate("Success");
}
}
}
}