/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.view.component;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.commons.Link;
import org.eclipse.skalli.model.ext.linkgroups.LinkGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.EmptyValueException;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.terminal.UserError;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.AbstractSelect.NewItemHandler;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Field;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
public class LinkWindow extends Window {
private static final long serialVersionUID = 7764538915405149666L;
private static final Logger LOG = LoggerFactory.getLogger(LinkWindow.class);
private final ThemeResource ICON_BUTTON_OK = new ThemeResource("icons/button/ok.png"); //$NON-NLS-1$
private final Component parent;
private final Collection<String> knownGroups;
private final ILinkAddedHandler linkAddedHandler;
private final ILinkModifiedHandler linkModifiedHandler;
private final LinkGroup oldGroup;
private final Link link;
private VerticalLayout root;
/**
* @param parent the calling component
* @param linkGroups groups that are added to the combo box
* @param linkAddedHandler callback implementation
*/
public LinkWindow(Component parent, Collection<LinkGroup> linkGroups, ILinkAddedHandler linkAddedHandler) {
this.parent = parent;
this.knownGroups = new ArrayList<String>(linkGroups.size());
this.linkAddedHandler = linkAddedHandler;
this.linkModifiedHandler = null;
this.oldGroup = null;
this.link = null;
for (LinkGroup linkGroup : linkGroups) {
this.knownGroups.add(linkGroup.getCaption());
}
createContents("Add Link");
}
public LinkWindow(Component parent, Collection<LinkGroup> linkGroups, LinkGroup oldGroup, Link link,
ILinkModifiedHandler linkModifiedHandler) {
this.parent = parent;
this.knownGroups = new ArrayList<String>(linkGroups.size());
this.linkAddedHandler = null;
this.linkModifiedHandler = linkModifiedHandler;
this.oldGroup = oldGroup;
this.link = link;
for (LinkGroup linkGroup : linkGroups) {
this.knownGroups.add(linkGroup.getCaption());
}
createContents("Edit Link");
}
/**
* Render the window
*/
@SuppressWarnings("serial")
private void createContents(String title) {
setModal(true);
setCaption(title);
setWidth("400px"); //$NON-NLS-1$
setHeight("300px"); //$NON-NLS-1$
root = new VerticalLayout();
root.setMargin(true);
root.setSpacing(true);
final ComboBox cbLinkGroup = new ComboBox("Link Group");
cbLinkGroup.setInputPrompt("Enter a new group name or select from the list");
cbLinkGroup.setWidth("100%"); //$NON-NLS-1$
for (String groupName : knownGroups) {
cbLinkGroup.addItem(groupName);
}
if (oldGroup != null && knownGroups.contains(oldGroup.getCaption())) {
cbLinkGroup.select(oldGroup.getCaption());
}
cbLinkGroup.setImmediate(true);
cbLinkGroup.setNullSelectionAllowed(false);
cbLinkGroup.setNewItemsAllowed(true);
cbLinkGroup.setNewItemHandler(new NewItemHandler() {
@Override
public void addNewItem(String newGroupName) {
cbLinkGroup.removeAllItems();
for (String groupName : knownGroups) {
cbLinkGroup.addItem(groupName);
}
if (!cbLinkGroup.containsId(newGroupName)) {
cbLinkGroup.addItem(newGroupName);
}
cbLinkGroup.select(newGroupName);
}
});
cbLinkGroup.setRequired(true);
cbLinkGroup.addValidator(new StringValidator());
root.addComponent(cbLinkGroup);
final TextField tfLinkCaption = new TextField("Page Title");
tfLinkCaption.setInputPrompt("Enter a descriptive name for the page");
tfLinkCaption.setWidth("100%"); //$NON-NLS-1$
tfLinkCaption.setImmediate(true);
tfLinkCaption.setRequired(true);
tfLinkCaption.addValidator(new StringValidator());
if (link != null) {
tfLinkCaption.setValue(link.getLabel());
}
root.addComponent(tfLinkCaption);
final TextField tfLinkURL = new TextField("URL");
tfLinkURL.setInputPrompt("e.g. http://www.your-site.domain/path");
tfLinkURL.setWidth("100%"); //$NON-NLS-1$
tfLinkURL.setImmediate(true);
tfLinkURL.setRequired(true);
tfLinkURL.addValidator(new StringValidator());
tfLinkURL.addValidator(new URLValidator());
if (link != null) {
tfLinkURL.setValue(link.getUrl());
}
root.addComponent(tfLinkURL);
final Button okAndCloseButton = new Button("OK & Close");
okAndCloseButton.setIcon(ICON_BUTTON_OK);
okAndCloseButton.setDescription("Performs the action and closes the dialog.");
okAndCloseButton.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
validateInput(cbLinkGroup);
validateInput(tfLinkURL);
validateInput(tfLinkCaption);
if (cbLinkGroup.isValid() && tfLinkURL.isValid() && tfLinkCaption.isValid()) {
String groupName = String.valueOf(cbLinkGroup.getValue());
String linkLabel = String.valueOf(tfLinkCaption.getValue());
String linkUrl = String.valueOf(tfLinkURL.getValue());
if (linkAddedHandler != null) {
Link link = new Link(linkUrl, linkLabel);
linkAddedHandler.onLinkAdded(groupName, link);
close();
}
if (linkModifiedHandler != null) {
boolean linkModified = !link.getLabel().equals(linkLabel) || !link.getUrl().equals(linkUrl);
link.setLabel(linkLabel);
link.setUrl(linkUrl);
linkModifiedHandler.onLinkModified(oldGroup, groupName, link, linkModified);
close();
}
}
}
});
root.addComponent(okAndCloseButton);
root.setSizeFull();
setContent(root);
}
/**
* Display this window.
*
* Typically called from the outside when the window was instantiated.
*/
public void show() {
parent.getWindow().addWindow(this);
}
private void validateInput(Validatable validatable) {
try {
validatable.validate();
} catch (EmptyValueException e) {
final AbstractComponent component = (AbstractComponent) validatable;
component.setComponentError(new UserError("This field is required."));
((Field) component).addListener(new ValueNotLongerEmptyListener());
} catch (InvalidValueException e) {
// Automatically handled by validator implementations (onValueChange)
LOG.debug("Invalid value", e); //$NON-NLS-1$
}
}
/**
* Removes the (manually set) component error in case the property isn't blank anymore.
*/
private final class ValueNotLongerEmptyListener implements ValueChangeListener {
private static final long serialVersionUID = -1568052404052787122L;
@Override
public void valueChange(ValueChangeEvent event) {
Property property = event.getProperty();
if (property != null) {
Object value = property.getValue();
if (StringUtils.isNotBlank((String) value)) {
((AbstractComponent) property).setComponentError(null);
}
}
}
}
/**
* Checks for 'not empty' and no trailing/leading white spaces
*/
private final class StringValidator implements Validator {
private static final long serialVersionUID = 383201426094234832L;
@Override
public void validate(Object value) throws InvalidValueException {
String stringValue = (String) value;
if (StringUtils.isBlank(stringValue)) {
throw new InvalidValueException("This field must not be blank.");
}
if (StringUtils.trimToEmpty(stringValue).length() != stringValue.length()) {
throw new InvalidValueException("This field must not contain trailing or leading spaces.");
}
}
@Override
public boolean isValid(Object value) {
try {
validate(value);
} catch (InvalidValueException e) {
return false;
}
return true;
}
}
/**
* Checks for a valid URL pattern
*/
private final class URLValidator implements Validator {
private static final long serialVersionUID = -7840893696280942983L;
@Override
public void validate(Object value) throws InvalidValueException {
String stringValue = (String) value;
try {
new URL(stringValue);
} catch (MalformedURLException e) {
throw new InvalidValueException("The URL is malformed.");
}
}
@Override
public boolean isValid(Object value) {
try {
validate(value);
} catch (InvalidValueException e) {
return false;
}
return true;
}
}
/**
* Callback interface for components that open this window (case 'add').
*/
public interface ILinkAddedHandler {
public void onLinkAdded(String groupName, Link link);
}
/**
* Callback interface for components that open this window (case 'edit').
*/
public interface ILinkModifiedHandler {
public void onLinkModified(LinkGroup oldGroup, String newGroupName, Link link, boolean linkModified);
}
}