From c6b44ac8adc9b2ffd6290c98643a633f405dd6c6 Mon Sep 17 00:00:00 2001
From: Artur Signell
+ * A class representing a selection of items the user has selected in a UI. The
+ * set of choices is presented as a set of {@link com.com.vaadin.v7.data.Item}s
+ * in a {@link com.com.vaadin.v7.data.Container}.
+ *
+ * A true
if caption is rendered as HTML,
+ * false
otherwise
+ * @deprecated as of , use {@link #setCaptionAsHtml(boolean)} instead
+ */
+ @Deprecated
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ setCaptionAsHtml(htmlContentAllowed);
+ }
+
+ /**
+ * Return HTML rendering setting
+ *
+ * @return true
if the caption text is to be rendered as HTML,
+ * false
otherwise
+ * @deprecated as of , use {@link #isCaptionAsHtml()} instead
+ */
+ @Deprecated
+ public boolean isHtmlContentAllowed() {
+ return isCaptionAsHtml();
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+
+ Attributes attributes = design.attributes();
+ if (design.hasAttr("color")) {
+ // Ignore the # character
+ String hexColor = DesignAttributeHandler
+ .readAttribute("color", attributes, String.class)
+ .substring(1);
+ setColor(new Color(Integer.parseInt(hexColor, 16)));
+ }
+ if (design.hasAttr("popup-style")) {
+ setPopupStyle(PopupStyle.valueOf(
+ "POPUP_" + attributes.get("popup-style").toUpperCase()));
+ }
+ if (design.hasAttr("position")) {
+ String[] position = attributes.get("position").split(",");
+ setPosition(Integer.parseInt(position[0]),
+ Integer.parseInt(position[1]));
+ }
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+
+ Attributes attribute = design.attributes();
+ DesignAttributeHandler.writeAttribute("color", attribute,
+ color.getCSS(), Color.WHITE.getCSS(), String.class);
+ DesignAttributeHandler.writeAttribute("popup-style", attribute,
+ (popupStyle == PopupStyle.POPUP_NORMAL ? "normal" : "simple"),
+ "normal", String.class);
+ DesignAttributeHandler.writeAttribute("position", attribute,
+ positionX + "," + positionY, "0,0", String.class);
+ }
+
+ @Override
+ protected CollectionSelect
component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
+ *
+ * Setting the id to a existing property implicitly sets the item caption
+ * mode to ITEM_CAPTION_MODE_PROPERTY
. If the object is in
+ * ITEM_CAPTION_MODE_PROPERTY
mode, setting caption property id
+ * null resets the item caption mode to
+ * ITEM_CAPTION_EXPLICIT_DEFAULTS_ID
.
+ *
+ * Note that the type of the property used for caption must be String + *
+ *+ * Setting the property id to null disables this feature. The id is null by + * default + *
+ * . + * + * @param propertyId + * the id of the property. + * + */ + public void setItemCaptionPropertyId(Object propertyId) { + if (propertyId != null) { + itemCaptionPropertyId = propertyId; + setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY); + markAsDirty(); + } else { + itemCaptionPropertyId = null; + if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY) { + setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID); + } + markAsDirty(); + } + } + + /** + * Gets the item caption property. + * + * @return the Id of the property used as item caption source. + */ + public Object getItemCaptionPropertyId() { + return itemCaptionPropertyId; + } + + /** + * Sets the item icon property. + * + *+ * If the property id is set to a valid value, each item is given an icon + * got from the given property of the items. The type of the property must + * be assignable to Resource. + *
+ * + *
+ * Note : The icons set with setItemIcon
function override the
+ * icons from the property.
+ *
+ * Setting the property id to null disables this feature. The id is null by + * default + *
+ * . + * + * @param propertyId + * the id of the property that specifies icons for items or null + * @throws IllegalArgumentException + * If the propertyId is not in the container or is not of a + * valid type + */ + public void setItemIconPropertyId(Object propertyId) + throws IllegalArgumentException { + if (propertyId == null) { + itemIconPropertyId = null; + } else if (!getContainerPropertyIds().contains(propertyId)) { + throw new IllegalArgumentException( + "Property id not found in the container"); + } else if (Resource.class.isAssignableFrom(getType(propertyId))) { + itemIconPropertyId = propertyId; + } else { + throw new IllegalArgumentException( + "Property type must be assignable to Resource"); + } + markAsDirty(); + } + + /** + * Gets the item icon property. + * + *+ * If the property id is set to a valid value, each item is given an icon + * got from the given property of the items. The type of the property must + * be assignable to Icon. + *
+ * + *
+ * Note : The icons set with setItemIcon
function override the
+ * icons from the property.
+ *
+ * Setting the property id to null disables this feature. The id is null by + * default + *
+ * . + * + * @return the Id of the property containing the item icons. + */ + public Object getItemIconPropertyId() { + return itemIconPropertyId; + } + + /** + * Tests if an item is selected. + * + *+ * In single select mode testing selection status of the item identified by + * {@link #getNullSelectionItemId()} returns true if the value of the + * property is null. + *
+ * + * @param itemId + * the Id the of the item to be tested. + * @see #getNullSelectionItemId() + * @see #setNullSelectionItemId(Object) + * + */ + public boolean isSelected(Object itemId) { + if (itemId == null) { + return false; + } + if (isMultiSelect()) { + return ((Set>) getValue()).contains(itemId); + } else { + final Object value = getValue(); + return itemId + .equals(value == null ? getNullSelectionItemId() : value); + } + } + + /** + * Selects an item. + * + *+ * In single select mode selecting item identified by + * {@link #getNullSelectionItemId()} sets the value of the property to null. + *
+ * + * @param itemId + * the identifier of Item to be selected. + * @see #getNullSelectionItemId() + * @see #setNullSelectionItemId(Object) + * + */ + public void select(Object itemId) { + if (!isMultiSelect()) { + setValue(itemId); + } else if (!isSelected(itemId) && itemId != null + && items.containsId(itemId)) { + final SetsetNullSelectionItemId()
. This way you can for
+ * instance set an icon and caption for the null selection item.
+ *
+ * @param nullSelectionAllowed
+ * whether or not to allow empty selection
+ * @see #setNullSelectionItemId(Object)
+ * @see #isNullSelectionAllowed()
+ */
+ public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
+ if (nullSelectionAllowed != this.nullSelectionAllowed) {
+ this.nullSelectionAllowed = nullSelectionAllowed;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Checks if null empty selection is allowed by the user.
+ *
+ * @return whether or not empty selection is allowed
+ * @see #setNullSelectionAllowed(boolean)
+ */
+ public boolean isNullSelectionAllowed() {
+ return nullSelectionAllowed;
+ }
+
+ /**
+ * Returns the item id that represents null value of this select in single
+ * select mode.
+ *
+ * + * Data interface does not support nulls as item ids. Selecting the item + * identified by this id is the same as selecting no items at all. This + * setting only affects the single select mode. + *
+ * + * @return the Object Null value item id. + * @see #setNullSelectionItemId(Object) + * @see #isSelected(Object) + * @see #select(Object) + */ + public Object getNullSelectionItemId() { + return nullSelectionItemId; + } + + /** + * Sets the item id that represents null value of this select. + * + *+ * Data interface does not support nulls as item ids. Selecting the item + * identified by this id is the same as selecting no items at all. This + * setting only affects the single select mode. + *
+ * + * @param nullSelectionItemId + * the nullSelectionItemId to set. + * @see #getNullSelectionItemId() + * @see #isSelected(Object) + * @see #select(Object) + */ + public void setNullSelectionItemId(Object nullSelectionItemId) { + if (nullSelectionItemId != null && isMultiSelect()) { + throw new IllegalStateException( + "Multiselect and NullSelectionItemId can not be set at the same time."); + } + this.nullSelectionItemId = nullSelectionItemId; + } + + /** + * Notifies the component that it is connected to an application. + * + * @see com.vaadin.v7.ui.AbstractField#attach() + */ + @Override + public void attach() { + super.attach(); + } + + /** + * Detaches the component from application. + * + * @see com.vaadin.ui.AbstractComponent#detach() + */ + @Override + public void detach() { + getCaptionChangeListener().clear(); + super.detach(); + } + + // Caption change listener + protected CaptionChangeListener getCaptionChangeListener() { + if (captionChangeListener == null) { + captionChangeListener = new CaptionChangeListener(); + } + return captionChangeListener; + } + + /** + * This is a listener helper for Item and Property changes that should cause + * a repaint. It should be attached to all items that are displayed, and the + * default implementation does this in paintContent(). Especially + * "lazyloading" components should take care to add and remove listeners as + * appropriate. Call addNotifierForItem() for each painted item (and + * remember to clear). + * + * NOTE: singleton, use getCaptionChangeListener(). + * + */ + protected class CaptionChangeListener implements + Item.PropertySetChangeListener, Property.ValueChangeListener { + + // TODO clean this up - type is either Item.PropertySetChangeNotifier or + // Property.ValueChangeNotifier + HashSet+ * This accept criterion is currently usable in Tree and Table + * implementations. + */ + public static class VerticalLocationIs extends TargetDetailIs { + public static VerticalLocationIs TOP = new VerticalLocationIs( + VerticalDropLocation.TOP); + public static VerticalLocationIs BOTTOM = new VerticalLocationIs( + VerticalDropLocation.BOTTOM); + public static VerticalLocationIs MIDDLE = new VerticalLocationIs( + VerticalDropLocation.MIDDLE); + + private VerticalLocationIs(VerticalDropLocation l) { + super("detail", l.name()); + } + } + + /** + * Implement this interface and pass it to Tree.setItemDescriptionGenerator + * or Table.setItemDescriptionGenerator to generate mouse over descriptions + * ("tooltips") for the rows and cells in Table or for the items in Tree. + */ + public interface ItemDescriptionGenerator extends Serializable { + + /** + * Called by Table when a cell (and row) is painted or a item is painted + * in Tree + * + * @param source + * The source of the generator, the Tree or Table the + * generator is attached to + * @param itemId + * The itemId of the painted cell + * @param propertyId + * The propertyId of the cell, null when getting row + * description + * @return The description or "tooltip" of the item. + */ + public String generateDescription(Component source, Object itemId, + Object propertyId); + } + + @Override + public void readDesign(Element design, DesignContext context) { + // handle default attributes + super.readDesign(design, context); + // handle children specifying selectable items (
+ * + *+ * The {@link Handler#handleAction(Action, Object, Object)} parameters + * depend on what the context menu is called upon: + *
+ * If set to true, the captions are rendered in the browser as HTML and the + * developer is responsible for ensuring no harmful HTML is used. If set to + * false, the caption is rendered in the browser as plain text. + *
+ * The default is false, i.e. to render that caption as plain text. + * + * @param captionAsHtml + * true if the captions are rendered as HTML, false if rendered + * as plain text + */ + public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) { + getState().eventCaptionAsHtml = eventCaptionAsHtml; + } + + /** + * Checks whether event captions are rendered as HTML + *
+ * The default is false, i.e. to render that caption as plain text.
+ *
+ * @return true if the captions are rendered as HTML, false if rendered as
+ * plain text
+ */
+ public boolean isEventCaptionAsHtml() {
+ return getState(false).eventCaptionAsHtml;
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+
+ Attributes attr = design.attributes();
+ if (design.hasAttr("time-format")) {
+ setTimeFormat(TimeFormat.valueOf(
+ "Format" + design.attr("time-format").toUpperCase()));
+ }
+
+ if (design.hasAttr("start-date")) {
+ setStartDate(DesignAttributeHandler.readAttribute("start-date",
+ attr, Date.class));
+ }
+ if (design.hasAttr("end-date")) {
+ setEndDate(DesignAttributeHandler.readAttribute("end-date", attr,
+ Date.class));
+ }
+ };
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+
+ if (currentTimeFormat != null) {
+ design.attr("time-format",
+ (currentTimeFormat == TimeFormat.Format12H ? "12h"
+ : "24h"));
+ }
+ if (startDate != null) {
+ design.attr("start-date", df_date.format(getStartDate()));
+ }
+ if (endDate != null) {
+ design.attr("end-date", df_date.format(getEndDate()));
+ }
+ if (!getTimeZone().equals(TimeZone.getDefault())) {
+ design.attr("time-zone", getTimeZone().getID());
+ }
+ }
+
+ @Override
+ protected Collection
+ * A date editor component that can be bound to any {@link Property} that is
+ * compatible with
+ * Since
+ * A
+ * There are also some static helper methods available for custom built field
+ * factories.
+ *
+ */
+public class DefaultFieldFactory implements TableFieldFactory {
+
+ private static final DefaultFieldFactory instance = new DefaultFieldFactory();
+
+ /**
+ * Singleton method to get an instance of DefaultFieldFactory.
+ *
+ * @return an instance of DefaultFieldFactory
+ */
+ public static DefaultFieldFactory get() {
+ return instance;
+ }
+
+ protected DefaultFieldFactory() {
+ }
+
+ @Override
+ public Field createField(Container container, Object itemId,
+ Object propertyId, Component uiContext) {
+ Property containerProperty = container.getContainerProperty(itemId,
+ propertyId);
+ Class> type = containerProperty.getType();
+ Field> field = createFieldByPropertyType(type);
+ field.setCaption(createCaptionByPropertyId(propertyId));
+ return field;
+ }
+
+ /**
+ * If name follows method naming conventions, convert the name to spaced
+ * upper case text. For example, convert "firstName" to "First Name"
+ *
+ * @param propertyId
+ * @return the formatted caption string
+ */
+ public static String createCaptionByPropertyId(Object propertyId) {
+ return SharedUtil.propertyIdToHumanFriendly(propertyId);
+ }
+
+ /**
+ * Creates fields based on the property type.
+ *
+ * The default field type is {@link TextField}. Other field types generated
+ * by this method:
+ *
+ * Boolean: {@link CheckBox}.
+ *
+ * @param type
+ * the type of the property
+ * @return the most suitable generic {@link Field} for given type
+ */
+ public static Field> createFieldByPropertyType(Class> type) {
+ // Null typed properties can not be edited
+ if (type == null) {
+ return null;
+ }
+
+ // Date field
+ if (Date.class.isAssignableFrom(type)) {
+ final DateField df = new DateField();
+ df.setResolution(DateField.RESOLUTION_DAY);
+ return df;
+ }
+
+ // Boolean field
+ if (Boolean.class.isAssignableFrom(type)) {
+ return new CheckBox();
+ }
+
+ return new TextField();
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java
new file mode 100644
index 0000000000..0a5e5b40a4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java
@@ -0,0 +1,7355 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.v7.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import com.vaadin.data.sort.Sort;
+import com.vaadin.data.sort.SortOrder;
+import com.vaadin.event.ContextClickEvent;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
+import com.vaadin.event.SelectionEvent;
+import com.vaadin.event.SelectionEvent.SelectionListener;
+import com.vaadin.event.SelectionEvent.SelectionNotifier;
+import com.vaadin.event.SortEvent;
+import com.vaadin.event.SortEvent.SortListener;
+import com.vaadin.event.SortEvent.SortNotifier;
+import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.server.EncodeResult;
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.Extension;
+import com.vaadin.server.JsonCodec;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.data.sort.SortDirection;
+import com.vaadin.shared.ui.grid.EditorClientRpc;
+import com.vaadin.shared.ui.grid.EditorServerRpc;
+import com.vaadin.shared.ui.grid.GridClientRpc;
+import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.shared.ui.grid.GridConstants;
+import com.vaadin.shared.ui.grid.GridConstants.Section;
+import com.vaadin.shared.ui.grid.GridServerRpc;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.GridStaticCellType;
+import com.vaadin.shared.ui.grid.GridStaticSectionState;
+import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
+import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
+import com.vaadin.shared.ui.grid.HeightMode;
+import com.vaadin.shared.ui.grid.ScrollDestination;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
+import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.ui.AbstractFocusable;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.SelectiveRenderer;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
+import com.vaadin.util.ReflectTools;
+import com.vaadin.v7.data.Container;
+import com.vaadin.v7.data.Container.Indexed;
+import com.vaadin.v7.data.Container.ItemSetChangeEvent;
+import com.vaadin.v7.data.Container.ItemSetChangeListener;
+import com.vaadin.v7.data.Container.ItemSetChangeNotifier;
+import com.vaadin.v7.data.Container.PropertySetChangeEvent;
+import com.vaadin.v7.data.Container.PropertySetChangeListener;
+import com.vaadin.v7.data.Container.PropertySetChangeNotifier;
+import com.vaadin.v7.data.Container.Sortable;
+import com.vaadin.v7.data.Item;
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.Validator.InvalidValueException;
+import com.vaadin.v7.data.fieldgroup.DefaultFieldGroupFieldFactory;
+import com.vaadin.v7.data.fieldgroup.FieldGroup;
+import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException;
+import com.vaadin.v7.data.fieldgroup.FieldGroupFieldFactory;
+import com.vaadin.v7.data.util.IndexedContainer;
+import com.vaadin.v7.data.util.converter.Converter;
+import com.vaadin.v7.data.util.converter.ConverterUtil;
+import com.vaadin.v7.server.communication.data.DataGenerator;
+import com.vaadin.v7.server.communication.data.RpcDataProviderExtension;
+import com.vaadin.v7.ui.renderers.HtmlRenderer;
+import com.vaadin.v7.ui.renderers.Renderer;
+import com.vaadin.v7.ui.renderers.TextRenderer;
+
+import elemental.json.Json;
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * A grid component for displaying tabular data.
+ *
+ * Grid is always bound to a {@link Container.Indexed}, but is not a
+ * {@code Container} of any kind in of itself. The contents of the given
+ * Container is displayed with the help of {@link Renderer Renderers}.
+ *
+ *
+ *
+ *
+ *
+ * Each column has its own {@link Renderer} that displays data into something
+ * that can be displayed in the browser. That data is first converted with a
+ * {@link com.vaadin.v7.data.util.converter.Converter Converter} into
+ * something that the Renderer can process. This can also be an implicit step -
+ * if a column has a simple data type, like a String, no explicit assignment is
+ * needed.
+ *
+ * Usually a renderer takes some kind of object, and converts it into a
+ * HTML-formatted string.
+ *
+ *
+ * The data is accessed as it is needed by Grid and not any sooner. In other
+ * words, if the given Container is huge, but only the first few rows are
+ * displayed to the user, only those (and a few more, for caching purposes) are
+ * accessed.
+ *
+ *
+ * Grid supports three selection {@link SelectionMode modes} (single,
+ * multi, none), and comes bundled with one {@link SelectionModel
+ * model} for each of the modes. The distinction between a selection mode
+ * and selection model is as follows: a mode essentially says whether
+ * you can have one, many or no rows selected. The model, however, has the
+ * behavioral details of each. A single selection model may require that the
+ * user deselects one row before selecting another one. A variant of a
+ * multiselect might have a configurable maximum of rows that may be selected.
+ * And so on.
+ *
+ *
+ * Note: If a component gets generated, it may not be manually
+ * attached anywhere. The same details component can not be displayed
+ * for multiple different rows.
+ *
+ * @param rowReference
+ * the reference for the row for which to generate details
+ * @return the details for the given row, or
+ * This method attaches created components to the parent {@link Grid}.
+ *
+ * @param itemId
+ * the item id for which to create the details component.
+ * @throws IllegalStateException
+ * if the current details generator provides a component
+ * that was manually attached.
+ */
+ private void createDetails(Object itemId) throws IllegalStateException {
+ assert itemId != null : "itemId was null";
+
+ if (itemIdToDetailsComponent.containsKey(itemId)
+ || emptyDetails.contains(itemId)) {
+ // Don't overwrite existing components
+ return;
+ }
+
+ RowReference rowReference = new RowReference(getParentGrid());
+ rowReference.set(itemId);
+
+ DetailsGenerator detailsGenerator = getParentGrid()
+ .getDetailsGenerator();
+ Component details = detailsGenerator.getDetails(rowReference);
+ if (details != null) {
+ if (details.getParent() != null) {
+ String name = detailsGenerator.getClass().getName();
+ throw new IllegalStateException(
+ name + " generated a details component that already "
+ + "was attached. (itemId: " + itemId
+ + ", component: " + details + ")");
+ }
+
+ itemIdToDetailsComponent.put(itemId, details);
+
+ addComponentToGrid(details);
+
+ assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
+ + "itemId is empty even though we just created a "
+ + "component for it (" + itemId + ")";
+ } else {
+ emptyDetails.add(itemId);
+ }
+
+ }
+
+ /**
+ * Destroys a details component correctly.
+ *
+ * This method will detach the component from parent {@link Grid}.
+ *
+ * @param itemId
+ * the item id for which to destroy the details component
+ */
+ private void destroyDetails(Object itemId) {
+ emptyDetails.remove(itemId);
+
+ Component removedComponent = itemIdToDetailsComponent
+ .remove(itemId);
+ if (removedComponent == null) {
+ return;
+ }
+
+ removeComponentFromGrid(removedComponent);
+ }
+
+ /**
+ * Recreates all visible details components.
+ */
+ public void refreshDetails() {
+ Set
+ * The currently opened row details will be re-rendered.
+ *
+ * @param detailsGenerator
+ * the details generator to set
+ * @throws IllegalArgumentException
+ * if detailsGenerator is
+ * Passing one of these enums into
+ * {@link Grid#setSelectionMode(SelectionMode)} is equivalent to calling
+ * {@link Grid#setSelectionModel(SelectionModel)} with one of the built-in
+ * implementations of {@link SelectionModel}.
+ *
+ * @see Grid#setSelectionMode(SelectionMode)
+ * @see Grid#setSelectionModel(SelectionModel)
+ */
+ public enum SelectionMode {
+ /** A SelectionMode that maps to {@link SingleSelectionModel} */
+ SINGLE {
+ @Override
+ protected SelectionModel createModel() {
+ return new SingleSelectionModel();
+ }
+
+ },
+
+ /** A SelectionMode that maps to {@link MultiSelectionModel} */
+ MULTI {
+ @Override
+ protected SelectionModel createModel() {
+ return new MultiSelectionModel();
+ }
+ },
+
+ /** A SelectionMode that maps to {@link NoSelectionModel} */
+ NONE {
+ @Override
+ protected SelectionModel createModel() {
+ return new NoSelectionModel();
+ }
+ };
+
+ protected abstract SelectionModel createModel();
+ }
+
+ /**
+ * The server-side interface that controls Grid's selection state.
+ * SelectionModel should extend {@link AbstractGridExtension}.
+ */
+ public interface SelectionModel extends Serializable, Extension {
+ /**
+ * Checks whether an item is selected or not.
+ *
+ * @param itemId
+ * the item id to check for
+ * @return
+ * Note: This method should not be called manually.
+ *
+ * @param grid
+ * the Grid in which the SelectionModel currently is, or
+ *
+ * Most often this means that the selection state is cleared, but
+ * implementations are free to interpret the "initial state" as they
+ * wish. Some, for example, may want to keep the first selected item as
+ * selected.
+ */
+ void reset();
+
+ /**
+ * A SelectionModel that supports multiple selections to be made.
+ *
+ * This interface has a contract of having the same behavior, no matter
+ * how the selection model is interacted with. In other words, if
+ * something is forbidden to do in e.g. the user interface, it must also
+ * be forbidden to do in the server-side and client-side APIs.
+ */
+ public interface Multi extends SelectionModel {
+
+ /**
+ * Marks items as selected.
+ *
+ * This method does not clear any previous selection state, only
+ * adds to it.
+ *
+ * @param itemIds
+ * the itemId(s) to mark as selected
+ * @return
+ * This method does not clear any previous selection state, only
+ * adds to it.
+ *
+ * @param itemIds
+ * the itemIds to mark as selected
+ * @return
+ * This interface has a contract of having the same behavior, no matter
+ * how the selection model is interacted with. In other words, if
+ * something is forbidden to do in e.g. the user interface, it must also
+ * be forbidden to do in the server-side and client-side APIs.
+ */
+ public interface Single extends SelectionModel {
+
+ /**
+ * Marks an item as selected.
+ *
+ * @param itemId
+ * the itemId to mark as selected;
+ * This interface has a contract of having the same behavior, no matter
+ * how the selection model is interacted with. In other words, if the
+ * developer is unable to select something programmatically, it is not
+ * allowed for the end-user to select anything, either.
+ */
+ public interface None extends SelectionModel {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always
+ * Note that this is only a helper method, and routes the call all the
+ * way to Grid. A {@link SelectionModel} is not a
+ * {@link SelectionNotifier}
+ *
+ * @param oldSelection
+ * the complete {@link Collection} of the itemIds that were
+ * selected before this event happened
+ * @param newSelection
+ * the complete {@link Collection} of the itemIds that are
+ * selected after this event happened
+ */
+ protected void fireSelectionEvent(final Collection
+ * If an item is selected, it will become deselected.
+ */
+ @Override
+ public void reset() {
+ deselect(getSelectedRow());
+ }
+
+ @Override
+ public void setDeselectAllowed(boolean deselectAllowed) {
+ getState().deselectAllowed = deselectAllowed;
+ }
+
+ @Override
+ public boolean isDeselectAllowed() {
+ return getState().deselectAllowed;
+ }
+
+ @Override
+ protected SingleSelectionModelState getState() {
+ return (SingleSelectionModelState) super.getState();
+ }
+ }
+
+ /**
+ * A default implementation for a {@link SelectionModel.None}
+ */
+ public static class NoSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.None {
+
+ @Override
+ public boolean isSelected(final Object itemId) {
+ return false;
+ }
+
+ @Override
+ public Collection
+ * Effectively a no-op.
+ */
+ @Override
+ public void reset() {
+ // NOOP
+ }
+ }
+
+ /**
+ * A default implementation of a {@link SelectionModel.Multi}
+ */
+ public static class MultiSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.Multi {
+
+ /**
+ * The default selection size limit.
+ *
+ * @see #setSelectionLimit(int)
+ */
+ public static final int DEFAULT_MAX_SELECTIONS = 1000;
+
+ private int selectionLimit = DEFAULT_MAX_SELECTIONS;
+
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+ registerRpc(new MultiSelectionModelServerRpc() {
+
+ @Override
+ public void select(List
+ * All items might not be selected if the limit set using
+ * {@link #setSelectionLimit(int)} is exceeded.
+ */
+ @Override
+ public boolean select(final Collection> itemIds)
+ throws IllegalArgumentException {
+ return select(itemIds, true);
+ }
+
+ protected boolean select(final Collection> itemIds, boolean refresh) {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds may not be null");
+ }
+
+ // Sanity check
+ checkItemIdsExist(itemIds);
+
+ final boolean selectionWillChange = !selection.containsAll(itemIds)
+ && selection.size() < selectionLimit;
+ if (selectionWillChange) {
+ final HashSet
+ * Old selections are not discarded if the current number of selected
+ * row exceeds the new limit.
+ *
+ * The default limit is {@value #DEFAULT_MAX_SELECTIONS} rows.
+ *
+ * @param selectionLimit
+ * the non-negative selection limit to set
+ * @throws IllegalArgumentException
+ * if the limit is negative
+ */
+ public void setSelectionLimit(int selectionLimit) {
+ if (selectionLimit < 0) {
+ throw new IllegalArgumentException(
+ "The selection limit must be non-negative");
+ }
+ this.selectionLimit = selectionLimit;
+ }
+
+ /**
+ * Gets the selection limit.
+ *
+ * @see #setSelectionLimit(int)
+ *
+ * @return the selection limit
+ */
+ public int getSelectionLimit() {
+ return selectionLimit;
+ }
+
+ @Override
+ public boolean deselect(final Object... itemIds)
+ throws IllegalArgumentException {
+ if (itemIds != null) {
+ // deselect will fire the event
+ return deselect(Arrays.asList(itemIds));
+ } else {
+ throw new IllegalArgumentException(
+ "Vararg array of itemIds may not be null");
+ }
+ }
+
+ @Override
+ public boolean deselect(final Collection> itemIds)
+ throws IllegalArgumentException {
+ return deselect(itemIds, true);
+ }
+
+ protected boolean deselect(final Collection> itemIds,
+ boolean refresh) {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds may not be null");
+ }
+
+ final boolean hasCommonElements = !Collections.disjoint(itemIds,
+ selection);
+ if (hasCommonElements) {
+ final HashSet
+ * The returned Collection is in order of selection
+ * – the item that was first selected will be first in the
+ * collection, and so on. Should an item have been selected twice
+ * without being deselected in between, it will have remained in its
+ * original position.
+ */
+ @Override
+ public Collection
+ * Equivalent to calling {@link #deselectAll()}
+ */
+ @Override
+ public void reset() {
+ deselectAll();
+ }
+
+ @Override
+ public boolean setSelected(Collection> itemIds)
+ throws IllegalArgumentException {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds may not be null");
+ }
+
+ checkItemIdsExist(itemIds);
+
+ boolean changed = false;
+ Set
+ * Since this class follows the
+ * Since this class follows the
+ * The default value is
+ * NOTE: setting this to empty string might cause the hiding
+ * toggle to not render correctly.
+ *
+ * @since 7.5.0
+ * @param hidingToggleCaption
+ * the text to show in the column hiding toggle
+ * @return the column itself
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public Column setHidingToggleCaption(String hidingToggleCaption)
+ throws IllegalStateException {
+ checkColumnIsAttached();
+ state.hidingToggleCaption = hidingToggleCaption;
+ grid.markAsDirty();
+ return this;
+ }
+
+ /**
+ * Returns the width (in pixels). By default a column is 100px wide.
+ *
+ * @return the width in pixels of the column
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public double getWidth() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.width;
+ }
+
+ /**
+ * Sets the width (in pixels).
+ *
+ * This overrides any configuration set by any of
+ * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
+ * {@link #setMaximumWidth(double)}.
+ *
+ * @param pixelWidth
+ * the new pixel width of the column
+ * @return the column itself
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ * @throws IllegalArgumentException
+ * thrown if pixel width is less than zero
+ */
+ public Column setWidth(double pixelWidth)
+ throws IllegalStateException, IllegalArgumentException {
+ checkColumnIsAttached();
+ if (pixelWidth < 0) {
+ throw new IllegalArgumentException(
+ "Pixel width should be greated than 0 (in " + toString()
+ + ")");
+ }
+ if (state.width != pixelWidth) {
+ state.width = pixelWidth;
+ grid.markAsDirty();
+ grid.fireColumnResizeEvent(this, false);
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether this column has an undefined width.
+ *
+ * @since 7.6
+ * @return whether the width is undefined
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public boolean isWidthUndefined() {
+ checkColumnIsAttached();
+ return state.width < 0;
+ }
+
+ /**
+ * Marks the column width as undefined. An undefined width means the
+ * grid is free to resize the column based on the cell contents and
+ * available space in the grid.
+ *
+ * @return the column itself
+ */
+ public Column setWidthUndefined() {
+ checkColumnIsAttached();
+ if (!isWidthUndefined()) {
+ state.width = -1;
+ grid.markAsDirty();
+ grid.fireColumnResizeEvent(this, false);
+ }
+ return this;
+ }
+
+ /**
+ * Checks if column is attached and throws an
+ * {@link IllegalStateException} if it is not
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ protected void checkColumnIsAttached() throws IllegalStateException {
+ if (grid.getColumnByColumnId(state.id) == null) {
+ throw new IllegalStateException("Column no longer exists.");
+ }
+ }
+
+ /**
+ * Sets this column as the last frozen column in its grid.
+ *
+ * @return the column itself
+ *
+ * @throws IllegalArgumentException
+ * if the column is no longer attached to any grid
+ * @see Grid#setFrozenColumnCount(int)
+ */
+ public Column setLastFrozenColumn() {
+ checkColumnIsAttached();
+ grid.setFrozenColumnCount(
+ grid.getState(false).columnOrder.indexOf(getState().id)
+ + 1);
+ return this;
+ }
+
+ /**
+ * Sets the renderer for this column.
+ *
+ * If a suitable converter isn't defined explicitly, the session
+ * converter factory is used to find a compatible converter.
+ *
+ * @param renderer
+ * the renderer to use
+ * @return the column itself
+ *
+ * @throws IllegalArgumentException
+ * if no compatible converter could be found
+ *
+ * @see VaadinSession#getConverterFactory()
+ * @see ConverterUtil#getConverter(Class, Class, VaadinSession)
+ * @see #setConverter(Converter)
+ */
+ public Column setRenderer(Renderer> renderer) {
+ if (!internalSetRenderer(renderer)) {
+ throw new IllegalArgumentException(
+ "Could not find a converter for converting from the model type "
+ + getModelType()
+ + " to the renderer presentation type "
+ + renderer.getPresentationType() + " (in "
+ + toString() + ")");
+ }
+ return this;
+ }
+
+ /**
+ * Sets the renderer for this column and the converter used to convert
+ * from the property value type to the renderer presentation type.
+ *
+ * @param renderer
+ * the renderer to use, cannot be null
+ * @param converter
+ * the converter to use
+ * @return the column itself
+ *
+ * @throws IllegalArgumentException
+ * if the renderer is already associated with a grid column
+ */
+ public
+ * Note: it is possible to sort by this column programmatically
+ * using the Grid#sort methods regardless of the returned value.
+ *
+ * @return {@code true} if the column is sortable by the user,
+ * {@code false} otherwise
+ */
+ public boolean isSortable() {
+ return state.sortable;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[propertyId:"
+ + grid.getPropertyIdByColumnId(state.id) + "]";
+ }
+
+ /**
+ * Sets the ratio with which the column expands.
+ *
+ * By default, all columns expand equally (treated as if all of them had
+ * an expand ratio of 1). Once at least one column gets a defined expand
+ * ratio, the implicit expand ratio is removed, and only the defined
+ * expand ratios are taken into account.
+ *
+ * If a column has a defined width ({@link #setWidth(double)}), it
+ * overrides this method's effects.
+ *
+ * Example: A grid with three columns, with expand ratios 0, 1
+ * and 2, respectively. The column with a ratio of 0 is exactly
+ * as wide as its contents requires. The column with a ratio of
+ * 1 is as wide as it needs, plus a third of any excess
+ * space, because we have 3 parts total, and this column
+ * reserves only one of those. The column with a ratio of 2, is as wide
+ * as it needs to be, plus two thirds of the excess
+ * width.
+ *
+ * @param expandRatio
+ * the expand ratio of this column. {@code 0} to not have it
+ * expand at all. A negative number to clear the expand
+ * value.
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ * @see #setWidth(double)
+ */
+ public Column setExpandRatio(int expandRatio)
+ throws IllegalStateException {
+ checkColumnIsAttached();
+
+ getState().expandRatio = expandRatio;
+ grid.markAsDirty();
+ return this;
+ }
+
+ /**
+ * Returns the column's expand ratio.
+ *
+ * @return the column's expand ratio
+ * @see #setExpandRatio(int)
+ */
+ public int getExpandRatio() {
+ return getState().expandRatio;
+ }
+
+ /**
+ * Clears the expand ratio for this column.
+ *
+ * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public Column clearExpandRatio() throws IllegalStateException {
+ return setExpandRatio(-1);
+ }
+
+ /**
+ * Sets the minimum width for this column.
+ *
+ * This defines the minimum guaranteed pixel width of the column
+ * when it is set to expand.
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ * @see #setExpandRatio(int)
+ */
+ public Column setMinimumWidth(double pixels)
+ throws IllegalStateException {
+ checkColumnIsAttached();
+
+ final double maxwidth = getMaximumWidth();
+ if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
+ throw new IllegalArgumentException("New minimum width ("
+ + pixels + ") was greater than maximum width ("
+ + maxwidth + ")");
+ }
+ getState().minWidth = pixels;
+ grid.markAsDirty();
+ return this;
+ }
+
+ /**
+ * Return the minimum width for this column.
+ *
+ * @return the minimum width for this column
+ * @see #setMinimumWidth(double)
+ */
+ public double getMinimumWidth() {
+ return getState().minWidth;
+ }
+
+ /**
+ * Sets the maximum width for this column.
+ *
+ * This defines the maximum allowed pixel width of the column when
+ * it is set to expand.
+ *
+ * @param pixels
+ * the maximum width
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ * @see #setExpandRatio(int)
+ */
+ public Column setMaximumWidth(double pixels) {
+ checkColumnIsAttached();
+
+ final double minwidth = getMinimumWidth();
+ if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
+ throw new IllegalArgumentException("New maximum width ("
+ + pixels + ") was less than minimum width (" + minwidth
+ + ")");
+ }
+
+ getState().maxWidth = pixels;
+ grid.markAsDirty();
+ return this;
+ }
+
+ /**
+ * Returns the maximum width for this column.
+ *
+ * @return the maximum width for this column
+ * @see #setMaximumWidth(double)
+ */
+ public double getMaximumWidth() {
+ return getState().maxWidth;
+ }
+
+ /**
+ * Sets whether the properties corresponding to this column should be
+ * editable when the item editor is active. By default columns are
+ * editable.
+ *
+ * Values in non-editable columns are currently not displayed when the
+ * editor is active, but this will probably change in the future. They
+ * are not automatically assigned an editor field and, if one is
+ * manually assigned, it is not used. Columns that cannot (or should
+ * not) be edited even in principle should be set non-editable.
+ *
+ * @param editable
+ * {@code true} if this column should be editable,
+ * {@code false} otherwise
+ * @return this column
+ *
+ * @throws IllegalStateException
+ * if the editor is currently active
+ *
+ * @see Grid#editItem(Object)
+ * @see Grid#isEditorActive()
+ */
+ public Column setEditable(boolean editable) {
+ checkColumnIsAttached();
+ if (grid.isEditorActive()) {
+ throw new IllegalStateException(
+ "Cannot change column editable status while the editor is active");
+ }
+ getState().editable = editable;
+ grid.markAsDirty();
+ return this;
+ }
+
+ /**
+ * Returns whether the properties corresponding to this column should be
+ * editable when the item editor is active.
+ *
+ * @return {@code true} if this column is editable, {@code false}
+ * otherwise
+ *
+ * @see Grid#editItem(Object)
+ * @see #setEditable(boolean)
+ */
+
+ public boolean isEditable() {
+ return getState().editable;
+ }
+
+ /**
+ * Sets the field component used to edit the properties in this column
+ * when the item editor is active. If an item has not been set, then the
+ * binding is postponed until the item is set using
+ * {@link #editItem(Object)}.
+ *
+ * Setting the field to
+ * When {@link #editItem(Object) editItem} is called, fields are
+ * automatically created and bound for any unbound properties.
+ *
+ * Getting a field before the editor has been opened depends on special
+ * support from the {@link FieldGroup} in use. Using this method with a
+ * user-provided
+ * Note: the column can be programmatically hidden using
+ * {@link #setHidden(boolean)} regardless of the returned value.
+ *
+ * @since 7.5.0
+ * @return
+ * Note: the column can be programmatically resized using
+ * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
+ * of the returned value.
+ *
+ * @since 7.6
+ * @return {@code true} if this column is resizable, {@code false}
+ * otherwise
+ */
+ public boolean isResizable() {
+ return getState().resizable;
+ }
+
+ /**
+ * Writes the design attributes for this column into given element.
+ *
+ * @since 7.5.0
+ *
+ * @param design
+ * Element to write attributes into
+ *
+ * @param designContext
+ * the design context
+ */
+ protected void writeDesign(Element design,
+ DesignContext designContext) {
+ Attributes attributes = design.attributes();
+ GridColumnState def = new GridColumnState();
+
+ DesignAttributeHandler.writeAttribute("property-id", attributes,
+ getPropertyId(), null, Object.class);
+
+ // Sortable is a special attribute that depends on the container.
+ DesignAttributeHandler.writeAttribute("sortable", attributes,
+ isSortable(), null, boolean.class);
+ DesignAttributeHandler.writeAttribute("editable", attributes,
+ isEditable(), def.editable, boolean.class);
+ DesignAttributeHandler.writeAttribute("resizable", attributes,
+ isResizable(), def.resizable, boolean.class);
+
+ DesignAttributeHandler.writeAttribute("hidable", attributes,
+ isHidable(), def.hidable, boolean.class);
+ DesignAttributeHandler.writeAttribute("hidden", attributes,
+ isHidden(), def.hidden, boolean.class);
+ DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
+ attributes, getHidingToggleCaption(), null, String.class);
+
+ DesignAttributeHandler.writeAttribute("width", attributes,
+ getWidth(), def.width, Double.class);
+ DesignAttributeHandler.writeAttribute("min-width", attributes,
+ getMinimumWidth(), def.minWidth, Double.class);
+ DesignAttributeHandler.writeAttribute("max-width", attributes,
+ getMaximumWidth(), def.maxWidth, Double.class);
+ DesignAttributeHandler.writeAttribute("expand", attributes,
+ getExpandRatio(), def.expandRatio, Integer.class);
+ }
+
+ /**
+ * Reads the design attributes for this column from given element.
+ *
+ * @since 7.5.0
+ * @param design
+ * Element to read attributes from
+ * @param designContext
+ * the design context
+ */
+ protected void readDesign(Element design, DesignContext designContext) {
+ Attributes attributes = design.attributes();
+
+ if (design.hasAttr("sortable")) {
+ setSortable(DesignAttributeHandler.readAttribute("sortable",
+ attributes, boolean.class));
+ }
+ if (design.hasAttr("editable")) {
+ setEditable(DesignAttributeHandler.readAttribute("editable",
+ attributes, boolean.class));
+ }
+ if (design.hasAttr("resizable")) {
+ setResizable(DesignAttributeHandler.readAttribute("resizable",
+ attributes, boolean.class));
+ }
+
+ if (design.hasAttr("hidable")) {
+ setHidable(DesignAttributeHandler.readAttribute("hidable",
+ attributes, boolean.class));
+ }
+ if (design.hasAttr("hidden")) {
+ setHidden(DesignAttributeHandler.readAttribute("hidden",
+ attributes, boolean.class));
+ }
+ if (design.hasAttr("hiding-toggle-caption")) {
+ setHidingToggleCaption(DesignAttributeHandler.readAttribute(
+ "hiding-toggle-caption", attributes, String.class));
+ }
+
+ // Read size info where necessary.
+ if (design.hasAttr("width")) {
+ setWidth(DesignAttributeHandler.readAttribute("width",
+ attributes, Double.class));
+ }
+ if (design.hasAttr("min-width")) {
+ setMinimumWidth(DesignAttributeHandler
+ .readAttribute("min-width", attributes, Double.class));
+ }
+ if (design.hasAttr("max-width")) {
+ setMaximumWidth(DesignAttributeHandler
+ .readAttribute("max-width", attributes, Double.class));
+ }
+ if (design.hasAttr("expand")) {
+ if (design.attr("expand").isEmpty()) {
+ setExpandRatio(1);
+ } else {
+ setExpandRatio(DesignAttributeHandler.readAttribute(
+ "expand", attributes, Integer.class));
+ }
+ }
+ }
+ }
+
+ /**
+ * An abstract base class for server-side
+ * {@link com.vaadin.v7.ui.renderers.Renderer Grid renderers}. This class
+ * currently extends the AbstractExtension superclass, but this fact should
+ * be regarded as an implementation detail and subject to change in a future
+ * major or minor Vaadin revision.
+ *
+ * @param
+ * This is a helper method that can be invoked by an
+ * {@link #encode(Object) encode(T)} override if serializing a value of
+ * type other than {@link #getPresentationType() the presentation type}
+ * is desired. For instance, a {@code Renderer
+ * Note: If the extension is an instance of {@link DataGenerator} it will
+ * automatically register itself to {@link RpcDataProviderExtension} of
+ * extended Grid. On remove this registration is automatically removed.
+ *
+ * @since 7.5
+ */
+ public static abstract class AbstractGridExtension
+ extends AbstractExtension {
+
+ /**
+ * Constructs a new Grid extension.
+ */
+ public AbstractGridExtension() {
+ super();
+ }
+
+ /**
+ * Constructs a new Grid extension and extends given Grid.
+ *
+ * @param grid
+ * a grid instance
+ */
+ public AbstractGridExtension(Grid grid) {
+ super();
+ extend(grid);
+ }
+
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+
+ if (this instanceof DataGenerator) {
+ getParentGrid().datasourceExtension
+ .addDataGenerator((DataGenerator) this);
+ }
+ }
+
+ @Override
+ public void remove() {
+ if (this instanceof DataGenerator) {
+ getParentGrid().datasourceExtension
+ .removeDataGenerator((DataGenerator) this);
+ }
+
+ super.remove();
+ }
+
+ /**
+ * Gets the item id for a row key.
+ *
+ * A key is used to identify a particular row on both a server and a
+ * client. This method can be used to get the item id for the row key
+ * that the client has sent.
+ *
+ * @param rowKey
+ * the row key for which to retrieve an item id
+ * @return the item id corresponding to {@code key}
+ */
+ protected Object getItemId(String rowKey) {
+ return getParentGrid().getKeyMapper().get(rowKey);
+ }
+
+ /**
+ * Gets the column for a column id.
+ *
+ * An id is used to identify a particular column on both a server and a
+ * client. This method can be used to get the column for the column id
+ * that the client has sent.
+ *
+ * @param columnId
+ * the column id for which to retrieve a column
+ * @return the column corresponding to {@code columnId}
+ */
+ protected Column getColumn(String columnId) {
+ return getParentGrid().getColumnByColumnId(columnId);
+ }
+
+ /**
+ * Gets the parent Grid of the renderer.
+ *
+ * @return parent grid
+ * @throws IllegalStateException
+ * if parent is not Grid
+ */
+ protected Grid getParentGrid() {
+ if (getParent() instanceof Grid) {
+ Grid grid = (Grid) getParent();
+ return grid;
+ } else if (getParent() == null) {
+ throw new IllegalStateException(
+ "Renderer is not attached to any parent");
+ } else {
+ throw new IllegalStateException(
+ "Renderers can be used only with Grid. Extended "
+ + getParent().getClass().getSimpleName()
+ + " instead");
+ }
+ }
+
+ /**
+ * Resends the row data for given item id to the client.
+ *
+ * @since 7.6
+ * @param itemId
+ * row to refresh
+ */
+ protected void refreshRow(Object itemId) {
+ getParentGrid().datasourceExtension.updateRowData(itemId);
+ }
+
+ /**
+ * Informs the parent Grid that this Extension wants to add a child
+ * component to it.
+ *
+ * @since 7.6
+ * @param c
+ * component
+ */
+ protected void addComponentToGrid(Component c) {
+ getParentGrid().addComponent(c);
+ }
+
+ /**
+ * Informs the parent Grid that this Extension wants to remove a child
+ * component from it.
+ *
+ * @since 7.6
+ * @param c
+ * component
+ */
+ protected void removeComponentFromGrid(Component c) {
+ getParentGrid().removeComponent(c);
+ }
+ }
+
+ /**
+ * The data source attached to the grid
+ */
+ private Container.Indexed datasource;
+
+ /**
+ * Property id to column instance mapping
+ */
+ private final Map
+ *
+ * Note Grid columns are based on properties and try to
+ * detect a correct converter for the data type. The columns are not
+ * reinitialized automatically if the container is changed, and if the same
+ * properties are present after container change, the columns are reused.
+ * Properties with same names, but different data types will lead to
+ * unpredictable behaviour.
+ *
+ * @param container
+ * The container data source. Cannot be null.
+ * @throws IllegalArgumentException
+ * if the data source is null
+ */
+ public void setContainerDataSource(Container.Indexed container) {
+ defaultContainer = false;
+ internalSetContainerDataSource(container);
+ }
+
+ private void internalSetContainerDataSource(Container.Indexed container) {
+ if (container == null) {
+ throw new IllegalArgumentException(
+ "Cannot set the datasource to null");
+ }
+ if (datasource == container) {
+ return;
+ }
+
+ // Remove old listeners
+ if (datasource instanceof PropertySetChangeNotifier) {
+ ((PropertySetChangeNotifier) datasource)
+ .removePropertySetChangeListener(propertyListener);
+ }
+
+ if (datasourceExtension != null) {
+ removeExtension(datasourceExtension);
+ }
+
+ // Remove old DetailComponentManager
+ if (detailComponentManager != null) {
+ detailComponentManager.remove();
+ }
+
+ resetEditor();
+
+ datasource = container;
+
+ //
+ // Adjust sort order
+ //
+
+ if (container instanceof Container.Sortable) {
+
+ // If the container is sortable, go through the current sort order
+ // and match each item to the sortable properties of the new
+ // container. If the new container does not support an item in the
+ // current sort order, that item is removed from the current sort
+ // order list.
+ Collection> sortableProps = ((Container.Sortable) getContainerDataSource())
+ .getSortableContainerPropertyIds();
+
+ Iterator
+ * Note that adding a new property is only done for the default container
+ * that Grid sets up with the default constructor.
+ *
+ * @param propertyId
+ * the property id of the new column
+ * @return the new column
+ *
+ * @throws IllegalStateException
+ * if column for given property already exists in this grid
+ */
+
+ public Column addColumn(Object propertyId) throws IllegalStateException {
+ if (datasource.getContainerPropertyIds().contains(propertyId)
+ && !columns.containsKey(propertyId)) {
+ appendColumn(propertyId);
+ } else if (defaultContainer) {
+ addColumnProperty(propertyId, String.class, "");
+ } else {
+ if (columns.containsKey(propertyId)) {
+ throw new IllegalStateException(
+ "A column for property id '" + propertyId.toString()
+ + "' already exists in this grid");
+ } else {
+ throw new IllegalStateException(
+ "Property id '" + propertyId.toString()
+ + "' does not exist in the container");
+ }
+ }
+
+ // Inform the data provider of this new column.
+ Column column = getColumn(propertyId);
+ List
+ * Default value for the new property is 0 if type is Integer, Double and
+ * Float. If type is String, default value is an empty string. For all other
+ * types the default value is null.
+ *
+ * Note that adding a new property is only done for the default container
+ * that Grid sets up with the default constructor.
+ *
+ * @param propertyId
+ * the property id of the new column
+ * @param type
+ * the data type for the new property
+ * @return the new column
+ *
+ * @throws IllegalStateException
+ * if column for given property already exists in this grid or
+ * property already exists in the container with wrong type
+ */
+ public Column addColumn(Object propertyId, Class> type) {
+ addColumnProperty(propertyId, type, null);
+ return getColumn(propertyId);
+ }
+
+ protected void addColumnProperty(Object propertyId, Class> type,
+ Object defaultValue) throws IllegalStateException {
+ if (!defaultContainer) {
+ throw new IllegalStateException(
+ "Container for this Grid is not a default container from Grid() constructor");
+ }
+
+ if (!columns.containsKey(propertyId)) {
+ if (!datasource.getContainerPropertyIds().contains(propertyId)) {
+ datasource.addContainerProperty(propertyId, type, defaultValue);
+ } else {
+ Property> containerProperty = datasource.getContainerProperty(
+ datasource.firstItemId(), propertyId);
+ if (containerProperty.getType() == type) {
+ appendColumn(propertyId);
+ } else {
+ throw new IllegalStateException(
+ "DataSource already has the given property "
+ + propertyId + " with a different type");
+ }
+ }
+ } else {
+ throw new IllegalStateException(
+ "Grid already has a column for property " + propertyId);
+ }
+ }
+
+ /**
+ * Removes all columns from this Grid.
+ */
+ public void removeAllColumns() {
+ List
+ * The default value is 0.
+ *
+ * @param numberOfColumns
+ * the number of columns that should be frozen
+ *
+ * @throws IllegalArgumentException
+ * if the column count is < 0 or > the number of visible columns
+ */
+ public void setFrozenColumnCount(int numberOfColumns) {
+ if (numberOfColumns < -1 || numberOfColumns > columns.size()) {
+ throw new IllegalArgumentException(
+ "count must be between -1 and the current number of columns ("
+ + columns.size() + "): " + numberOfColumns);
+ }
+
+ getState().frozenColumnCount = numberOfColumns;
+ }
+
+ /**
+ * Gets the number of frozen columns in this grid. 0 means that no data
+ * columns will be frozen, but the built-in selection checkbox column will
+ * still be frozen if it's in use. -1 means that not even the selection
+ * column is frozen.
+ *
+ * NOTE: this count includes {@link Column#isHidden() hidden
+ * columns} in the count.
+ *
+ * @see #setFrozenColumnCount(int)
+ *
+ * @return the number of frozen columns
+ */
+ public int getFrozenColumnCount() {
+ return getState(false).frozenColumnCount;
+ }
+
+ /**
+ * Scrolls to a certain item, using {@link ScrollDestination#ANY}.
+ *
+ * If the item has visible details, its size will also be taken into
+ * account.
+ *
+ * @param itemId
+ * id of item to scroll to.
+ * @throws IllegalArgumentException
+ * if the provided id is not recognized by the data source.
+ */
+ public void scrollTo(Object itemId) throws IllegalArgumentException {
+ scrollTo(itemId, ScrollDestination.ANY);
+ }
+
+ /**
+ * Scrolls to a certain item, using user-specified scroll destination.
+ *
+ * If the item has visible details, its size will also be taken into
+ * account.
+ *
+ * @param itemId
+ * id of item to scroll to.
+ * @param destination
+ * value specifying desired position of scrolled-to row.
+ * @throws IllegalArgumentException
+ * if the provided id is not recognized by the data source.
+ */
+ public void scrollTo(Object itemId, ScrollDestination destination)
+ throws IllegalArgumentException {
+
+ int row = datasource.indexOfId(itemId);
+
+ if (row == -1) {
+ throw new IllegalArgumentException(
+ "Item with specified ID does not exist in data source");
+ }
+
+ GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
+ clientRPC.scrollToRow(row, destination);
+ }
+
+ /**
+ * Scrolls to the beginning of the first data row.
+ */
+ public void scrollToStart() {
+ GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
+ clientRPC.scrollToStart();
+ }
+
+ /**
+ * Scrolls to the end of the last data row.
+ */
+ public void scrollToEnd() {
+ GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
+ clientRPC.scrollToEnd();
+ }
+
+ /**
+ * Sets the number of rows that should be visible in Grid's body, while
+ * {@link #getHeightMode()} is {@link HeightMode#ROW}.
+ *
+ * If Grid is currently not in {@link HeightMode#ROW}, the given value is
+ * remembered, and applied once the mode is applied.
+ *
+ * @param rows
+ * The height in terms of number of rows displayed in Grid's
+ * body. If Grid doesn't contain enough rows, white space is
+ * displayed instead. If
+ * Note: This method will change the widget's size in the browser
+ * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
+ *
+ * @see #setHeightMode(HeightMode)
+ */
+ @Override
+ public void setHeight(float height, Unit unit) {
+ super.setHeight(height, unit);
+ }
+
+ /**
+ * Defines the mode in which the Grid widget's height is calculated.
+ *
+ * If {@link HeightMode#CSS} is given, Grid will respect the values given
+ * via a {@code setHeight}-method, and behave as a traditional Component.
+ *
+ * If {@link HeightMode#ROW} is given, Grid will make sure that the body
+ * will display as many rows as {@link #getHeightByRows()} defines.
+ * Note: If headers/footers are inserted or removed, the widget
+ * will resize itself to still display the required amount of rows in its
+ * body. It also takes the horizontal scrollbar into account.
+ *
+ * @param heightMode
+ * the mode in to which Grid should be set
+ */
+ public void setHeightMode(HeightMode heightMode) {
+ /*
+ * This method is a workaround for the fact that Vaadin re-applies
+ * widget dimensions (height/width) on each state change event. The
+ * original design was to have setHeight and setHeightByRow be equals,
+ * and whichever was called the latest was considered in effect.
+ *
+ * But, because of Vaadin always calling setHeight on the widget, this
+ * approach doesn't work.
+ */
+
+ getState().heightMode = heightMode;
+ }
+
+ /**
+ * Returns the current {@link HeightMode} the Grid is in.
+ *
+ * Defaults to {@link HeightMode#CSS}.
+ *
+ * @return the current HeightMode
+ */
+ public HeightMode getHeightMode() {
+ return getState(false).heightMode;
+ }
+
+ /* Selection related methods: */
+
+ /**
+ * Takes a new {@link SelectionModel} into use.
+ *
+ * The SelectionModel that is previously in use will have all its items
+ * deselected.
+ *
+ * If the given SelectionModel is already in use, this method does nothing.
+ *
+ * @param selectionModel
+ * the new SelectionModel to use
+ * @throws IllegalArgumentException
+ * if {@code selectionModel} is
+ * Grid supports three selection modes: multiselect, single select and no
+ * selection, and this is a convenience method for choosing between one of
+ * them.
+ *
+ * Technically, this method is a shortcut that can be used instead of
+ * calling {@code setSelectionModel} with a specific SelectionModel
+ * instance. Grid comes with three built-in SelectionModel classes, and the
+ * {@link SelectionMode} enum represents each of them.
+ *
+ * Essentially, the two following method calls are equivalent:
+ *
+ *
+ * This method is a shorthand that delegates to the
+ * {@link #getSelectionModel() selection model}.
+ *
+ * @return a collection of all the currently selected itemIds
+ */
+ // keep this javadoc in sync with SelectionModel.getSelectedRows
+ public Collection
+ * This method is a shorthand that delegates to the
+ * {@link #getSelectionModel() selection model}. Only
+ * {@link SelectionModel.Single} is supported.
+ *
+ * @return the item id of the currently selected item, or
+ * This method is a shorthand that delegates to the
+ * {@link #getSelectionModel() selection model}. Only
+ * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
+ * supported.
+ *
+ * @param itemId
+ * the itemId to mark as selected
+ * @return
+ * This method is a shorthand that delegates to the
+ * {@link #getSelectionModel() selection model}. Only
+ * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
+ * supported.
+ *
+ * @param itemId
+ * the itemId to remove from being selected
+ * @return
+ * This method is a shorthand that delegates to the
+ * {@link #getSelectionModel() selection model}. Only
+ * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
+ * supported.
+ *
+ * @return
+ * Note: This is not a method that should be called by
+ * application logic. This method is publicly accessible only so that
+ * {@link SelectionModel SelectionModels} would be able to inform Grid of
+ * these events.
+ *
+ * @param newSelection
+ * the selection that was added by this event
+ * @param oldSelection
+ * the selection that was removed by this event
+ */
+ public void fireSelectionEvent(Collection
+ * Note: Sorting by a property that has no column in Grid will hide
+ * all possible sorting indicators.
+ *
+ * @param s
+ * a sort instance
+ *
+ * @throws IllegalStateException
+ * if container is not sortable (does not implement
+ * Container.Sortable)
+ * @throws IllegalArgumentException
+ * if trying to sort by non-existing property
+ */
+ public void sort(Sort s) {
+ setSortOrder(s.build());
+ }
+
+ /**
+ * Sort this Grid in ascending order by a specified property.
+ *
+ * Note: Sorting by a property that has no column in Grid will hide
+ * all possible sorting indicators.
+ *
+ * @param propertyId
+ * a property ID
+ *
+ * @throws IllegalStateException
+ * if container is not sortable (does not implement
+ * Container.Sortable)
+ * @throws IllegalArgumentException
+ * if trying to sort by non-existing property
+ */
+ public void sort(Object propertyId) {
+ sort(propertyId, SortDirection.ASCENDING);
+ }
+
+ /**
+ * Sort this Grid in user-specified {@link SortOrder} by a property.
+ *
+ * Note: Sorting by a property that has no column in Grid will hide
+ * all possible sorting indicators.
+ *
+ * @param propertyId
+ * a property ID
+ * @param direction
+ * a sort order value (ascending/descending)
+ *
+ * @throws IllegalStateException
+ * if container is not sortable (does not implement
+ * Container.Sortable)
+ * @throws IllegalArgumentException
+ * if trying to sort by non-existing property
+ */
+ public void sort(Object propertyId, SortDirection direction) {
+ sort(Sort.by(propertyId, direction));
+ }
+
+ /**
+ * Clear the current sort order, and re-sort the grid.
+ */
+ public void clearSortOrder() {
+ sortOrder.clear();
+ sort(false);
+ }
+
+ /**
+ * Sets the sort order to use.
+ *
+ * Note: Sorting by a property that has no column in Grid will hide
+ * all possible sorting indicators.
+ *
+ * @param order
+ * a sort order list.
+ *
+ * @throws IllegalStateException
+ * if container is not sortable (does not implement
+ * Container.Sortable)
+ * @throws IllegalArgumentException
+ * if order is null or trying to sort by non-existing property
+ */
+ public void setSortOrder(List
+ * Please note that it's generally only safe to use this method during
+ * initialization. After Grid has been initialized and the visible column
+ * order might have been changed, it's better to instead add items directly
+ * to the underlying container and use {@link Item#getItemProperty(Object)}
+ * to make sure each value is assigned to the intended property.
+ *
+ * @param values
+ * the cell values of the new row, in the same order as the
+ * visible column order, not
+ * Note: This is a pass-through call to the backing field group.
+ *
+ * @throws CommitException
+ * If the commit was aborted
+ *
+ * @see FieldGroup#commit()
+ */
+ public void saveEditor() throws CommitException {
+ editorFieldGroup.commit();
+ }
+
+ /**
+ * Cancels the currently active edit if any. Hides the editor and discards
+ * possible unsaved changes in the editor fields.
+ */
+ public void cancelEditor() {
+ if (isEditorActive()) {
+ getEditorRpc()
+ .cancel(getContainerDataSource().indexOfId(editedItemId));
+ doCancelEditor();
+ }
+ }
+
+ protected void doCancelEditor() {
+ editedItemId = null;
+ editorActive = false;
+ editorFieldGroup.discard();
+ editorFieldGroup.setItemDataSource(null);
+
+ if (datasource instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) datasource)
+ .removeItemSetChangeListener(editorClosingItemSetListener);
+ }
+
+ // Mark Grid as dirty so the client side gets to know that the editors
+ // are no longer attached
+ markAsDirty();
+ }
+
+ void resetEditor() {
+ if (isEditorActive()) {
+ /*
+ * Simply force cancel the editing; throwing here would just make
+ * Grid.setContainerDataSource semantics more complicated.
+ */
+ cancelEditor();
+ }
+ for (Field> editor : getEditorFields()) {
+ editor.setParent(null);
+ }
+
+ editedItemId = null;
+ editorActive = false;
+ editorFieldGroup = new CustomFieldGroup();
+ }
+
+ /**
+ * Gets a collection of all fields bound to the item editor of this grid.
+ *
+ * When {@link #editItem(Object) editItem} is called, fields are
+ * automatically created and bound to any unbound properties.
+ *
+ * @return a collection of all the fields bound to the item editor
+ */
+ Collection
+ * Note: This is a pass-through call to the backing field group.
+ *
+ * @param fieldFactory
+ * The field factory to use
+ */
+ public void setEditorFieldFactory(FieldGroupFieldFactory fieldFactory) {
+ editorFieldGroup.setFieldFactory(fieldFactory);
+ }
+
+ /**
+ * Sets the error handler for the editor.
+ *
+ * The error handler is called whenever there is an exception in the editor.
+ *
+ * @param editorErrorHandler
+ * The editor error handler to use
+ * @throws IllegalArgumentException
+ * if the error handler is null
+ */
+ public void setEditorErrorHandler(EditorErrorHandler editorErrorHandler)
+ throws IllegalArgumentException {
+ if (editorErrorHandler == null) {
+ throw new IllegalArgumentException(
+ "The error handler cannot be null");
+ }
+ this.editorErrorHandler = editorErrorHandler;
+ }
+
+ /**
+ * Gets the error handler used for the editor
+ *
+ * @see #setErrorHandler(com.vaadin.server.ErrorHandler)
+ * @return the editor error handler, never null
+ */
+ public EditorErrorHandler getEditorErrorHandler() {
+ return editorErrorHandler;
+ }
+
+ /**
+ * Gets the field factory for the {@link FieldGroup}. The field factory is
+ * only used when {@link FieldGroup} creates a new field.
+ *
+ * Note: This is a pass-through call to the backing field group.
+ *
+ * @return The field factory in use
+ */
+ public FieldGroupFieldFactory getEditorFieldFactory() {
+ return editorFieldGroup.getFieldFactory();
+ }
+
+ /**
+ * Sets the caption on the save button in the Grid editor.
+ *
+ * @param saveCaption
+ * the caption to set
+ * @throws IllegalArgumentException
+ * if {@code saveCaption} is {@code null}
+ */
+ public void setEditorSaveCaption(String saveCaption)
+ throws IllegalArgumentException {
+ if (saveCaption == null) {
+ throw new IllegalArgumentException("Save caption cannot be null");
+ }
+ getState().editorSaveCaption = saveCaption;
+ }
+
+ /**
+ * Gets the current caption of the save button in the Grid editor.
+ *
+ * @return the current caption of the save button
+ */
+ public String getEditorSaveCaption() {
+ return getState(false).editorSaveCaption;
+ }
+
+ /**
+ * Sets the caption on the cancel button in the Grid editor.
+ *
+ * @param cancelCaption
+ * the caption to set
+ * @throws IllegalArgumentException
+ * if {@code cancelCaption} is {@code null}
+ */
+ public void setEditorCancelCaption(String cancelCaption)
+ throws IllegalArgumentException {
+ if (cancelCaption == null) {
+ throw new IllegalArgumentException("Cancel caption cannot be null");
+ }
+ getState().editorCancelCaption = cancelCaption;
+ }
+
+ /**
+ * Gets the current caption of the cancel button in the Grid editor.
+ *
+ * @return the current caption of the cancel button
+ */
+ public String getEditorCancelCaption() {
+ return getState(false).editorCancelCaption;
+ }
+
+ /**
+ * Sets the buffered editor mode. The default mode is buffered (
+ *
+ * In most cases Grid will know when column widths need to be recalculated
+ * but this method can be used to force recalculation in situations when
+ * grid does not recalculate automatically.
+ *
+ * @since 7.4.1
+ */
+ public void recalculateColumnWidths() {
+ getRpcProxy(GridClientRpc.class).recalculateColumnWidths();
+ }
+
+ /**
+ * Registers a new column visibility change listener
+ *
+ * @since 7.5.0
+ * @param listener
+ * the listener to register
+ */
+ public void addColumnVisibilityChangeListener(
+ ColumnVisibilityChangeListener listener) {
+ addListener(ColumnVisibilityChangeEvent.class, listener,
+ COLUMN_VISIBILITY_METHOD);
+ }
+
+ /**
+ * Removes a previously registered column visibility change listener
+ *
+ * @since 7.5.0
+ * @param listener
+ * the listener to remove
+ */
+ public void removeColumnVisibilityChangeListener(
+ ColumnVisibilityChangeListener listener) {
+ removeListener(ColumnVisibilityChangeEvent.class, listener,
+ COLUMN_VISIBILITY_METHOD);
+ }
+
+ private void fireColumnVisibilityChangeEvent(Column column, boolean hidden,
+ boolean isUserOriginated) {
+ fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
+ isUserOriginated));
+ }
+
+ /**
+ * Sets a new details generator for row details.
+ *
+ * The currently opened row details will be re-rendered.
+ *
+ * @since 7.5.0
+ * @param detailsGenerator
+ * the details generator to set
+ * @throws IllegalArgumentException
+ * if detailsGenerator is
+ * A date entry component, which displays the actual date selector inline.
+ *
+ *
- * A date editor component that can be bound to any {@link Property} that is
- * compatible with
- * Since
- * A
- * A date entry component, which displays the actual date selector inline.
- *
- *
- * A date entry component, which displays the actual date selector as a popup.
- *
- *
+ * A date entry component, which displays the actual date selector as a popup.
+ *
+ *
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ *
+ * The default value is string 'null'.
+ *
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ *
+ * By default this setting is false
+ *
+ * The null-valued strings are represented on the user interface by
+ * replacing the null value with this string. If the null representation is
+ * set null (not 'null' string), painting null value throws exception.
+ *
+ * The default value is string 'null'
+ *
+ * If this property is true, writing null-representation string to text
+ * field always sets the field value to real null. If this property is
+ * false, null setting is not made, but the null values are maintained.
+ * Maintenance of null-values is made by only converting the textfield
+ * contents to real null, if the text field matches the null-string
+ * representation and the current value of the field is null.
+ *
+ * By default this setting is false.
+ *
+ * A class representing a selection of items the user has selected in a UI. The
+ * set of choices is presented as a set of {@link com.vaadin.v7.data.Item}s in a
+ * {@link com.vaadin.v7.data.Container}.
+ *
+ * A
+ *
+ * Scalability of the Table is largely dictated by the container. A table does
+ * not have a limit for the number of items and is just as fast with hundreds of
+ * thousands of items as with just a few. The current GWT implementation with
+ * scrolling however limits the number of rows to around 500000, depending on
+ * the browser and the pixel height of rows.
+ *
+ * Components in a Table will not have their caption nor icon rendered.
+ *
+ * This is the default behavior.
+ */
+ EXPLICIT_DEFAULTS_ID
+ }
+
+ /**
+ * @deprecated As of 7.0, use {@link ColumnHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_HIDDEN = ColumnHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated As of 7.0, use {@link ColumnHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_ID = ColumnHeaderMode.ID;
+
+ /**
+ * @deprecated As of 7.0, use {@link ColumnHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT = ColumnHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated As of 7.0, use {@link ColumnHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final ColumnHeaderMode COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = ColumnHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ public enum RowHeaderMode {
+ /**
+ * Row caption mode: The row headers are hidden. This is the default
+ * mode.
+ */
+ HIDDEN(null),
+ /**
+ * Row caption mode: Items Id-objects toString is used as row caption.
+ */
+ ID(ItemCaptionMode.ID),
+ /**
+ * Row caption mode: Item-objects toString is used as row caption.
+ */
+ ITEM(ItemCaptionMode.ITEM),
+ /**
+ * Row caption mode: Index of the item is used as item caption. The
+ * index mode can only be used with the containers implementing the
+ * {@link com.vaadin.data.Container.Indexed} interface.
+ */
+ INDEX(ItemCaptionMode.INDEX),
+ /**
+ * Row caption mode: Item captions are explicitly specified, but if the
+ * caption is missing, the item id objects
+ * The columns are show in the order of their appearance in this array.
+ *
+ * The columns are show in the order of their appearance in this array.
+ *
+ * The headers match the property id:s given by the set visible column
+ * headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString().
+ *
+ * The headers match the property id:s given by the set visible column
+ * headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
+ * headers. In the defaults mode any nulls in the headers array are replaced
+ * with id.toString() outputs when rendering.
+ *
+ * The icons in headers match the property id:s given by the set visible
+ * column headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers
+ * with icons.
+ *
+ * The icons in headers match the property id:s given by the set visible
+ * column headers. The table must be set in either
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT} or
+ * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers
+ * with icons.
+ *
+ * The items in the array must match the properties identified by
+ * {@link #getVisibleColumns()}. The possible values for the alignments
+ * include:
+ *
+ * The amount of items in the array must match the amount of properties
+ * identified by {@link #getVisibleColumns()}. The possible values for the
+ * alignments include:
+ *
+ * Column can either have a fixed width or expand ratio. The latter one set
+ * is used. See @link {@link #setColumnExpandRatio(Object, float)}.
+ *
+ * @param propertyId
+ * columns property id
+ * @param width
+ * width to be reserved for columns content
+ * @since 4.0.3
+ */
+ public void setColumnWidth(Object propertyId, int width) {
+ if (propertyId == null) {
+ // Since propertyId is null, this is the row header. Use the magic
+ // id to store the width of the row header.
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+
+ // Setting column width should remove any expand ratios as well
+ columnExpandRatios.remove(propertyId);
+
+ if (width < 0) {
+ columnWidths.remove(propertyId);
+ } else {
+ columnWidths.put(propertyId, width);
+ }
+ markAsDirty();
+ }
+
+ /**
+ * Sets the column expand ratio for given column.
+ *
+ * Expand ratios can be defined to customize the way how excess space is
+ * divided among columns. Table can have excess space if it has its width
+ * defined and there is horizontally more space than columns consume
+ * naturally. Excess space is the space that is not used by columns with
+ * explicit width (see {@link #setColumnWidth(Object, int)}) or with natural
+ * width (no width nor expand ratio).
+ *
+ *
+ * By default (without expand ratios) the excess space is divided
+ * proportionally to columns natural widths.
+ *
+ *
+ * Only expand ratios of visible columns are used in final calculations.
+ *
+ *
+ * Column can either have a fixed width or expand ratio. The latter one set
+ * is used.
+ *
+ *
+ * A column with expand ratio is considered to be minimum width by default
+ * (if no excess space exists). The minimum width is defined by terminal
+ * implementation.
+ *
+ *
+ * If terminal implementation supports re-sizable columns the column becomes
+ * fixed width column if users resizes the column.
+ *
+ * @param propertyId
+ * columns property id
+ * @param expandRatio
+ * the expandRatio used to divide excess space for this column
+ */
+ public void setColumnExpandRatio(Object propertyId, float expandRatio) {
+ if (propertyId == null) {
+ // Since propertyId is null, this is the row header. Use the magic
+ // id to store the width of the row header.
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+
+ // Setting the column expand ratio should remove and defined column
+ // width
+ columnWidths.remove(propertyId);
+
+ if (expandRatio < 0) {
+ columnExpandRatios.remove(propertyId);
+ } else {
+ columnExpandRatios.put(propertyId, expandRatio);
+ }
+
+ requestRepaint();
+ }
+
+ /**
+ * Gets the column expand ratio for a column. See
+ * {@link #setColumnExpandRatio(Object, float)}
+ *
+ * @param propertyId
+ * columns property id
+ * @return the expandRatio used to divide excess space for this column
+ */
+ public float getColumnExpandRatio(Object propertyId) {
+ final Float width = columnExpandRatios.get(propertyId);
+ if (width == null) {
+ return -1;
+ }
+ return width.floatValue();
+ }
+
+ /**
+ * Gets the pixel width of column
+ *
+ * @param propertyId
+ * @return width of column or -1 when value not set
+ */
+ public int getColumnWidth(Object propertyId) {
+ if (propertyId == null) {
+ // Since propertyId is null, this is the row header. Use the magic
+ // id to retrieve the width of the row header.
+ propertyId = ROW_HEADER_FAKE_PROPERTY_ID;
+ }
+ final Integer width = columnWidths.get(propertyId);
+ if (width == null) {
+ return -1;
+ }
+ return width.intValue();
+ }
+
+ /**
+ * Gets the page length.
+ *
+ *
+ * Setting page length 0 disables paging.
+ *
+ * Setting page length 0 disables paging. The page length defaults to 15.
+ *
+ * If Table has height set ({@link #setHeight(float, Unit)} ) the client
+ * side may update the page length automatically the correct value.
+ *
+ * Table component may fetch and render some rows outside visible area. With
+ * complex tables (for example containing layouts and components), the
+ * client side may become unresponsive. Setting the value lower, UI will
+ * become more responsive. With higher values scrolling in client will hit
+ * server less frequently.
+ *
+ *
+ * The amount of cached rows will be cacheRate multiplied with pageLength (
+ * {@link #setPageLength(int)} both below and above visible area..
+ *
+ * @param cacheRate
+ * a value over 0 (fastest rendering time). Higher value will
+ * cache more rows on server (smoother scrolling). Default value
+ * is 2.
+ */
+ public void setCacheRate(double cacheRate) {
+ if (cacheRate < 0) {
+ throw new IllegalArgumentException(
+ "cacheRate cannot be less than zero");
+ }
+ if (this.cacheRate != cacheRate) {
+ this.cacheRate = cacheRate;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * @see #setCacheRate(double)
+ *
+ * @return the current cache rate value
+ */
+ public double getCacheRate() {
+ return cacheRate;
+ }
+
+ /**
+ * Getter for property currentPageFirstItem.
+ *
+ * @return the Value of property currentPageFirstItem.
+ */
+ public Object getCurrentPageFirstItemId() {
+
+ // Prioritise index over id if indexes are supported
+ if (items instanceof Container.Indexed) {
+ final int index = getCurrentPageFirstItemIndex();
+ Object id = null;
+ if (index >= 0 && index < size()) {
+ id = getIdByIndex(index);
+ }
+ if (id != null && !id.equals(currentPageFirstItemId)) {
+ currentPageFirstItemId = id;
+ }
+ }
+
+ // If there is no item id at all, use the first one
+ if (currentPageFirstItemId == null) {
+ currentPageFirstItemId = firstItemId();
+ }
+
+ return currentPageFirstItemId;
+ }
+
+ /**
+ * Returns the item ID for the item represented by the index given. Assumes
+ * that the current container implements {@link Container.Indexed}.
+ *
+ * See {@link Container.Indexed#getIdByIndex(int)} for more information
+ * about the exceptions that can be thrown.
+ *
+ * @param index
+ * the index for which the item ID should be fetched
+ * @return the item ID for the given index
+ *
+ * @throws ClassCastException
+ * if container does not implement {@link Container.Indexed}
+ * @throws IndexOutOfBoundsException
+ * thrown by {@link Container.Indexed#getIdByIndex(int)} if the
+ * index is invalid
+ */
+ protected Object getIdByIndex(int index) {
+ return ((Container.Indexed) items).getIdByIndex(index);
+ }
+
+ /**
+ * Setter for property currentPageFirstItemId.
+ *
+ * @param currentPageFirstItemId
+ * the New value of property currentPageFirstItemId.
+ */
+ public void setCurrentPageFirstItemId(Object currentPageFirstItemId) {
+
+ // Gets the corresponding index
+ int index = -1;
+ if (items instanceof Container.Indexed) {
+ index = indexOfId(currentPageFirstItemId);
+ } else {
+ // If the table item container does not have index, we have to
+ // calculates the index by hand
+ Object id = firstItemId();
+ while (id != null && !id.equals(currentPageFirstItemId)) {
+ index++;
+ id = nextItemId(id);
+ }
+ if (id == null) {
+ index = -1;
+ }
+ }
+
+ // If the search for item index was successful
+ if (index >= 0) {
+ /*
+ * The table is not capable of displaying an item in the container
+ * as the first if there are not enough items following the selected
+ * item so the whole table (pagelength) is filled.
+ */
+ int maxIndex = size() - pageLength;
+ if (maxIndex < 0) {
+ maxIndex = 0;
+ }
+
+ if (index > maxIndex) {
+ // Note that we pass index, not maxIndex, letting
+ // setCurrentPageFirstItemIndex handle the situation.
+ setCurrentPageFirstItemIndex(index);
+ return;
+ }
+
+ this.currentPageFirstItemId = currentPageFirstItemId;
+ currentPageFirstItemIndex = index;
+ }
+
+ // Assures the visual refresh
+ refreshRowCache();
+
+ }
+
+ protected int indexOfId(Object itemId) {
+ return ((Container.Indexed) items).indexOfId(itemId);
+ }
+
+ /**
+ * Gets the icon Resource for the specified column.
+ *
+ * @param propertyId
+ * the propertyId identifying the column.
+ * @return the icon for the specified column; null if the column has no icon
+ * set, or if the column is not visible.
+ */
+ public Resource getColumnIcon(Object propertyId) {
+ return columnIcons.get(propertyId);
+ }
+
+ /**
+ * Sets the icon Resource for the specified column.
+ *
+ * Throws IllegalArgumentException if the specified column is not visible.
+ *
+ * Throws IllegalArgumentException if the alignment is not one of the
+ * following: {@link Align#LEFT}, {@link Align#CENTER} or
+ * {@link Align#RIGHT}
+ *
+ * The table is not selectable until it's explicitly set as selectable or at
+ * least one {@link ValueChangeListener} is added.
+ *
+ * The table is not selectable until it's explicitly set as selectable via
+ * this method or alternatively at least one {@link ValueChangeListener} is
+ * added.
+ *
+ * Contains all exceptions which occurred during the cache update. The first
+ * occurred exception is set as the cause of this exception. All occurred
+ * exceptions can be accessed using {@link #getCauses()}.
+ *
+ * The mode can be one of the following ones:
+ *
+ * Do not call this e.g. if you have updated the data model through a
+ * Property. These types of changes are automatically propagated to the
+ * Table.
+ *
+ * A typical case when this is needed is if you update a generator (e.g.
+ * CellStyleGenerator) and want to ensure that the rows are redrawn with new
+ * styles.
+ *
+ * Note that calling this method is not cheap so avoid calling it
+ * unnecessarily.
+ *
+ * @since 6.7.2
+ */
+ public void refreshRowCache() {
+ resetPageBuffer();
+ refreshRenderedCells();
+ }
+
+ /**
+ * Sets the Container that serves as the data source of the viewer. As a
+ * side-effect the table's selection value is set to null as the old
+ * selection might not exist in new Container.
+ * Keeps propertyValueConverters if the corresponding id exists in the new
+ * data source and is of a compatible type.
+ *
+ * Keeps propertyValueConverters if the corresponding id exists in the new
+ * data source and is of a compatible type.
+ *
+ * A generated column is a column that exists only in the Table, not as a
+ * property in the underlying Container. It shows up just as a regular
+ * column.
+ *
+ * A generated column will override a property with the same id, so that the
+ * generated column is shown instead of the column representing the
+ * property. Note that getContainerProperty() will still get the real
+ * property.
+ *
+ * Table will not listen to value change events from properties overridden
+ * by generated columns. If the content of your generated column depends on
+ * properties that are not directly visible in the table, attach value
+ * change listener to update the content on all depended properties.
+ * Otherwise your UI might not get updated as expected.
+ *
+ * Also note that getVisibleColumns() will return the generated columns,
+ * while getContainerPropertyIds() will not.
+ *
+ * Note, that some due to historical reasons the name of the method is bit
+ * misleading. Some items may be partly or totally out of the viewport of
+ * the table's scrollable area. Actually detecting rows which can be
+ * actually seen by the end user may be problematic due to the client server
+ * architecture. Using {@link #getCurrentPageFirstItemId()} combined with
+ * {@link #getPageLength()} may produce good enough estimates in some
+ * situations.
+ *
+ * @see com.vaadin.v7.ui.Select#getVisibleItemIds()
+ */
+
+ @Override
+ public Collection> getVisibleItemIds() {
+
+ final LinkedList
+ * Note that the {@link #isSortEnabled()} state affects what this method
+ * returns. Disabling sorting causes this method to always return an empty
+ * collection.
+ *
+ * Setting this to false disallows sorting by the user. It is still possible
+ * to call {@link #sort()}.
+ *
+ * Note, that on some clients the mode may not be respected. E.g. on touch
+ * based devices CTRL/SHIFT base selection method is invalid, so touch based
+ * browsers always use the {@link MultiSelectMode#SIMPLE}.
+ *
+ * @param mode
+ * The select mode of the table
+ */
+ public void setMultiSelectMode(MultiSelectMode mode) {
+ multiSelectMode = mode;
+ markAsDirty();
+ }
+
+ /**
+ * Returns the select mode in which multi-select is used.
+ *
+ * @return The multi select mode
+ */
+ public MultiSelectMode getMultiSelectMode() {
+ return multiSelectMode;
+ }
+
+ /**
+ * Lazy loading accept criterion for Table. Accepted target rows are loaded
+ * from server once per drag and drop operation. Developer must override one
+ * method that decides on which rows the currently dragged data can be
+ * dropped.
+ *
+ *
+ * Initially pretty much no data is sent to client. On first required
+ * criterion check (per drag request) the client side data structure is
+ * initialized from server and no subsequent requests requests are needed
+ * during that drag and drop operation.
+ */
+ public static abstract class TableDropCriterion
+ extends ServerSideCriterion {
+
+ private Table table;
+
+ private Set
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ *
+ * The listener will receive events which contain information about which
+ * column was clicked and some details about the mouse event.
+ *
+ * The footer can be used to add column related data like sums to the bottom
+ * of the Table using setColumnFooter(Object propertyId, String footer).
+ *
+ * The GeneratedRow data object contains the text that should be
+ * rendered in the row. The itemId in the container thus works only as a
+ * placeholder.
+ *
+ * If GeneratedRow.setSpanColumns(true) is used, there will be one
+ * String spanning all columns (use setText("Spanning text")). Otherwise
+ * you can define one String per visible column.
+ *
+ * If GeneratedRow.setRenderAsHtml(true) is used, the strings can
+ * contain HTML markup, otherwise all strings will be rendered as text
+ * (the default).
+ *
+ * A "v-table-generated-row" CSS class is added to all generated rows.
+ * For custom styling of a generated row you can combine a RowGenerator
+ * with a CellStyleGenerator.
+ *
+ *
+ * @param table
+ * The Table that is being painted
+ * @param itemId
+ * The itemId for the row
+ * @return A GeneratedRow describing how the row should be painted or
+ * null to paint the row with the contents from the container
+ */
+ public GeneratedRow generateRow(Table table, Object itemId);
+ }
+
+ public static class GeneratedRow implements Serializable {
+ private boolean htmlContentAllowed = false;
+ private boolean spanColumns = false;
+ private String[] text = null;
+
+ /**
+ * Creates a new generated row. If only one string is passed in, columns
+ * are automatically spanned.
+ *
+ * @param text
+ */
+ public GeneratedRow(String... text) {
+ setHtmlContentAllowed(false);
+ setSpanColumns(text == null || text.length == 1);
+ setText(text);
+ }
+
+ /**
+ * Pass one String if spanColumns is used, one String for each visible
+ * column otherwise
+ */
+ public void setText(String... text) {
+ if (text == null || (text.length == 1 && text[0] == null)) {
+ text = new String[] { "" };
+ }
+ this.text = text;
+ }
+
+ protected String[] getText() {
+ return text;
+ }
+
+ protected Object getValue() {
+ return getText();
+ }
+
+ protected boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ /**
+ * If set to true, all strings passed to {@link #setText(String...)}
+ * will be rendered as HTML.
+ *
+ * @param htmlContentAllowed
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ }
+
+ protected boolean isSpanColumns() {
+ return spanColumns;
+ }
+
+ /**
+ * If set to true, only one string will be rendered, spanning the entire
+ * row.
+ *
+ * @param spanColumns
+ */
+ public void setSpanColumns(boolean spanColumns) {
+ this.spanColumns = spanColumns;
+ }
+ }
+
+ /**
+ * Assigns a row generator to the table. The row generator will be able to
+ * replace rows in the table when it is rendered.
+ *
+ * @param generator
+ * the new row generator
+ */
+ public void setRowGenerator(RowGenerator generator) {
+ rowGenerator = generator;
+ refreshRowCache();
+ }
+
+ /**
+ * @return the current row generator
+ */
+ public RowGenerator getRowGenerator() {
+ return rowGenerator;
+ }
+
+ /**
+ * Sets a converter for a property id.
+ *
+ * The converter is used to format the the data for the given property id
+ * before displaying it in the table.
+ * null
if null selection is allowed.
+ * @return The style name to add to this item. (the CSS class name will
+ * be v-filterselect-item-[style name]
+ */
+ public String getStyle(ComboBox source, Object itemId);
+ }
+
+ private ComboBoxServerRpc rpc = new ComboBoxServerRpc() {
+ @Override
+ public void createNewItem(String itemValue) {
+ if (isNewItemsAllowed()) {
+ // New option entered (and it is allowed)
+ if (itemValue != null && itemValue.length() > 0) {
+ getNewItemHandler().addNewItem(itemValue);
+ // rebuild list
+ filterstring = null;
+ prevfilterstring = null;
+ }
+ }
+ }
+
+ @Override
+ public void setSelectedItem(String item) {
+ if (item == null) {
+ setValue(null, true);
+ } else {
+ final Object id = itemIdMapper.get(item);
+ if (id != null && id.equals(getNullSelectionItemId())) {
+ setValue(null, true);
+ } else {
+ setValue(id, true);
+ }
+ }
+ }
+
+ @Override
+ public void requestPage(String filter, int page) {
+ filterstring = filter;
+ if (filterstring != null) {
+ filterstring = filterstring.toLowerCase(getLocale());
+ }
+ currentPage = page;
+
+ // TODO this should trigger a data-only update instead of a full
+ // repaint
+ requestRepaint();
+ }
+ };
+
+ FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(
+ this) {
+ @Override
+ protected void fireEvent(Component.Event event) {
+ ComboBox.this.fireEvent(event);
+ }
+ };
+
+ // Current page when the user is 'paging' trough options
+ private int currentPage = -1;
+
+ private String filterstring;
+ private String prevfilterstring;
+
+ /**
+ * Number of options that pass the filter, excluding the null item if any.
+ */
+ private int filteredSize;
+
+ /**
+ * Cache of filtered options, used only by the in-memory filtering system.
+ */
+ private ListITEM_CAPTION_MODE_PROPERTY
mode.
+ *
+ * Note that the client side implementation expects the filter string to
+ * apply to the item caption string it sees, so changing the behavior of
+ * this method can cause problems.
+ *
+ * @param filterString
+ * @param filteringMode
+ * @return
+ */
+ protected Filter buildFilter(String filterString,
+ FilteringMode filteringMode) {
+ Filter filter = null;
+
+ if (null != filterString && !"".equals(filterString)) {
+ switch (filteringMode) {
+ case OFF:
+ break;
+ case STARTSWITH:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, true);
+ break;
+ case CONTAINS:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, false);
+ break;
+ }
+ }
+ return filter;
+ }
+
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ if (!isPainting) {
+ super.containerItemSetChange(event);
+ }
+ }
+
+ /**
+ * Makes correct sublist of given list of options.
+ *
+ * If paint is not an option request (affected by page or filter change),
+ * page will be the one where possible selection exists.
+ *
+ * Detects proper first and last item in list to return right page of
+ * options. Also, if the current page is beyond the end of the list, it will
+ * be adjusted.
+ *
+ * @param options
+ * @param needNullSelectOption
+ * flag to indicate if nullselect option needs to be taken into
+ * consideration
+ */
+ private List> sanitetizeList(List> options,
+ boolean needNullSelectOption) {
+
+ if (getPageLength() != 0 && options.size() > getPageLength()) {
+
+ int indexToEnsureInView = -1;
+
+ // if not an option request (item list when user changes page), go
+ // to page with the selected item after filtering if accepted by
+ // filter
+ Object selection = getValue();
+ if (isScrollToSelectedItem() && !optionRequest
+ && selection != null) {
+ // ensure proper page
+ indexToEnsureInView = options.indexOf(selection);
+ }
+
+ int size = options.size();
+ currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
+ indexToEnsureInView, size);
+ int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
+ size);
+ int last = getLastItemIndexOnCurrentPage(needNullSelectOption, size,
+ first);
+ return options.subList(first, last + 1);
+ } else {
+ return options;
+ }
+ }
+
+ /**
+ * Returns the index of the first item on the current page. The index is to
+ * the underlying (possibly filtered) contents. The null item, if any, does
+ * not have an index but takes up a slot on the first page.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @return first item to show on the UI (index to the filtered list of
+ * options, not taking the null item into consideration if any)
+ */
+ private int getFirstItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size) {
+ // Not all options are visible, find out which ones are on the
+ // current "page".
+ int first = currentPage * getPageLength();
+ if (needNullSelectOption && currentPage > 0) {
+ first--;
+ }
+ return first;
+ }
+
+ /**
+ * Returns the index of the last item on the current page. The index is to
+ * the underlying (possibly filtered) contents. If needNullSelectOption is
+ * true, the null item takes up the first slot on the first page,
+ * effectively reducing the first page size by one.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @param first
+ * index in the filtered view of the first item of the page
+ * @return index in the filtered view of the last item on the page
+ */
+ private int getLastItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size, int first) {
+ // page length usable for non-null items
+ int effectivePageLength = getPageLength()
+ - (needNullSelectOption && (currentPage == 0) ? 1 : 0);
+ return Math.min(size - 1, first + effectivePageLength - 1);
+ }
+
+ /**
+ * Adjusts the index of the current page if necessary: make sure the current
+ * page is not after the end of the contents, and optionally go to the page
+ * containg a specific item. There are no side effects but the adjusted page
+ * index is returned.
+ *
+ * @param page
+ * page number to use as the starting point
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param indexToEnsureInView
+ * index of an item that should be included on the page (in the
+ * data set, not counting the null item if any), -1 for none
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ */
+ private int adjustCurrentPage(int page, boolean needNullSelectOption,
+ int indexToEnsureInView, int size) {
+ if (indexToEnsureInView != -1) {
+ int newPage = (indexToEnsureInView + (needNullSelectOption ? 1 : 0))
+ / getPageLength();
+ page = newPage;
+ }
+ // adjust the current page if beyond the end of the list
+ if (page * getPageLength() > size) {
+ page = (size + (needNullSelectOption ? 1 : 0)) / getPageLength();
+ }
+ return page;
+ }
+
+ /**
+ * Filters the options in memory and returns the full filtered list.
+ *
+ * This can be less efficient than using container filters, so use
+ * {@link #getOptionsWithFilter(boolean)} if possible (filterable container
+ * and suitable item caption mode etc.).
+ *
+ * @return
+ */
+ protected List> getFilteredOptions() {
+ if (!isFilteringNeeded()) {
+ prevfilterstring = null;
+ filteredOptions = new LinkedListnull
to not
+ * use any custom item styles
+ * @since 7.5.6
+ */
+ public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
+ this.itemStyleGenerator = itemStyleGenerator;
+ markAsDirty();
+ }
+
+ /**
+ * Gets the currently used item style generator.
+ *
+ * @return the itemStyleGenerator the currently used item style generator,
+ * or null
if no generator is used
+ * @since 7.5.6
+ */
+ public ItemStyleGenerator getItemStyleGenerator() {
+ return itemStyleGenerator;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java
new file mode 100644
index 0000000000..b02e7b87a4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/DateField.java
@@ -0,0 +1,1010 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.v7.ui;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.logging.Logger;
+
+import org.jsoup.nodes.Element;
+
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.shared.ui.datefield.DateFieldConstants;
+import com.vaadin.shared.ui.datefield.Resolution;
+import com.vaadin.shared.ui.datefield.TextualDateFieldState;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.LegacyComponent;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.Validator;
+import com.vaadin.v7.data.Validator.InvalidValueException;
+import com.vaadin.v7.data.util.converter.Converter;
+import com.vaadin.v7.data.validator.DateRangeValidator;
+
+/**
+ * java.util.Date
.
+ * DateField
extends LegacyAbstractField
it
+ * implements the {@link com.vaadin.v7.data.Buffered}interface.
+ * DateField
is in write-through mode by default, so
+ * {@link com.vaadin.v7.ui.AbstractField#setWriteThrough(boolean)}must
+ * be called to enable buffering.
+ * DateField
with no caption.
+ */
+ public DateField() {
+ }
+
+ /**
+ * Constructs an empty DateField
with caption.
+ *
+ * @param caption
+ * the caption of the datefield.
+ */
+ public DateField(String caption) {
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new DateField
that's bound to the specified
+ * Property
and has the given caption String
.
+ *
+ * @param caption
+ * the caption String
for the editor.
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public DateField(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new DateField
that's bound to the specified
+ * Property
and has no caption.
+ *
+ * @param dataSource
+ * the Property to be edited with this editor.
+ */
+ public DateField(Property dataSource)
+ throws IllegalArgumentException {
+ if (!Date.class.isAssignableFrom(dataSource.getType())) {
+ throw new IllegalArgumentException(
+ "Can't use " + dataSource.getType().getName()
+ + " typed property as datasource");
+ }
+
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new DateField
with the given caption and
+ * initial text contents. The editor constructed this way will not be bound
+ * to a Property unless
+ * {@link com.vaadin.v7.data.Property.Viewer#setPropertyDataSource(Property)}
+ * is called to bind it.
+ *
+ * @param caption
+ * the caption String
for the editor.
+ * @param value
+ * the Date value.
+ */
+ public DateField(String caption, Date value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ /* Component basic features */
+
+ /*
+ * Paints this component. Don't add a JavaDoc comment here, we use the
+ * default documentation from implemented interface.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Adds the locale as attribute
+ final Locale l = getLocale();
+ if (l != null) {
+ target.addAttribute("locale", l.toString());
+ }
+
+ if (getDateFormat() != null) {
+ target.addAttribute("format", dateFormat);
+ }
+
+ if (!isLenient()) {
+ target.addAttribute("strict", true);
+ }
+
+ target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
+ isShowISOWeekNumbers());
+ target.addAttribute("parsable", uiHasValidDateString);
+ /*
+ * TODO communicate back the invalid date string? E.g. returning back to
+ * app or refresh.
+ */
+
+ // Gets the calendar
+ final Calendar calendar = getCalendar();
+ final Date currentDate = getValue();
+
+ // Only paint variables for the resolution and up, e.g. Resolution DAY
+ // paints DAY,MONTH,YEAR
+ for (Resolution res : Resolution
+ .getResolutionsHigherOrEqualTo(resolution)) {
+ int value = -1;
+ if (currentDate != null) {
+ value = calendar.get(res.getCalendarField());
+ if (res == Resolution.MONTH) {
+ // Calendar month is zero based
+ value++;
+ }
+ }
+ target.addVariable(this, variableNameForResolution.get(res), value);
+ }
+ }
+
+ @Override
+ protected boolean shouldHideErrors() {
+ return super.shouldHideErrors() && uiHasValidDateString;
+ }
+
+ @Override
+ protected TextualDateFieldState getState() {
+ return (TextualDateFieldState) super.getState();
+ }
+
+ @Override
+ protected TextualDateFieldState getState(boolean markAsDirty) {
+ return (TextualDateFieldState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Sets the start range for this component. If the value is set before this
+ * date (taking the resolution into account), the component will not
+ * validate. If startDate
is set to null
, any
+ * value before endDate
will be accepted by the range
+ *
+ * @param startDate
+ * - the allowed range's start date
+ */
+ public void setRangeStart(Date startDate) {
+ if (startDate != null && getState().rangeEnd != null
+ && startDate.after(getState().rangeEnd)) {
+ throw new IllegalStateException(
+ "startDate cannot be later than endDate");
+ }
+
+ // Create a defensive copy against issues when using java.sql.Date (and
+ // also against mutable Date).
+ getState().rangeStart = startDate != null
+ ? new Date(startDate.getTime()) : null;
+ updateRangeValidator();
+ }
+
+ /**
+ * Sets the current error message if the range validation fails.
+ *
+ * @param dateOutOfRangeMessage
+ * - Localizable message which is shown when value (the date) is
+ * set outside allowed range
+ */
+ public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
+ this.dateOutOfRangeMessage = dateOutOfRangeMessage;
+ updateRangeValidator();
+ }
+
+ /**
+ * Gets the end range for a certain resolution. The range is inclusive, so
+ * if rangeEnd is set to zero milliseconds past year n and resolution is set
+ * to YEAR, any date in year n will be accepted. Resolutions lower than DAY
+ * will be interpreted on a DAY level. That is, everything below DATE is
+ * cleared
+ *
+ * @param forResolution
+ * - the range conforms to the resolution
+ * @return
+ */
+ private Date getRangeEnd(Resolution forResolution) {
+ // We need to set the correct resolution for the dates,
+ // otherwise the range validator will complain
+
+ Date rangeEnd = getState(false).rangeEnd;
+ if (rangeEnd == null) {
+ return null;
+ }
+
+ Calendar endCal = Calendar.getInstance();
+ endCal.setTime(rangeEnd);
+
+ if (forResolution == Resolution.YEAR) {
+ // Adding one year (minresolution) and clearing the rest.
+ endCal.set(endCal.get(Calendar.YEAR) + 1, 0, 1, 0, 0, 0);
+ } else if (forResolution == Resolution.MONTH) {
+ // Adding one month (minresolution) and clearing the rest.
+ endCal.set(endCal.get(Calendar.YEAR),
+ endCal.get(Calendar.MONTH) + 1, 1, 0, 0, 0);
+ } else {
+ endCal.set(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
+ endCal.get(Calendar.DATE) + 1, 0, 0, 0);
+ }
+ // removing one millisecond will now get the endDate to return to
+ // current resolution's set time span (year or month)
+ endCal.set(Calendar.MILLISECOND, -1);
+ return endCal.getTime();
+ }
+
+ /**
+ * Gets the start range for a certain resolution. The range is inclusive, so
+ * if rangeStart
is set to one millisecond before year n and
+ * resolution is set to YEAR, any date in year n - 1 will be accepted.
+ * Lowest supported resolution is DAY.
+ *
+ * @param forResolution
+ * - the range conforms to the resolution
+ * @return
+ */
+ private Date getRangeStart(Resolution forResolution) {
+ if (getState(false).rangeStart == null) {
+ return null;
+ }
+ Calendar startCal = Calendar.getInstance();
+ startCal.setTime(getState(false).rangeStart);
+
+ if (forResolution == Resolution.YEAR) {
+ startCal.set(startCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
+ } else if (forResolution == Resolution.MONTH) {
+ startCal.set(startCal.get(Calendar.YEAR),
+ startCal.get(Calendar.MONTH), 1, 0, 0, 0);
+ } else {
+ startCal.set(startCal.get(Calendar.YEAR),
+ startCal.get(Calendar.MONTH), startCal.get(Calendar.DATE),
+ 0, 0, 0);
+ }
+
+ startCal.set(Calendar.MILLISECOND, 0);
+ return startCal.getTime();
+ }
+
+ private void updateRangeValidator() {
+ if (currentRangeValidator != null) {
+ removeValidator(currentRangeValidator);
+ currentRangeValidator = null;
+ }
+ if (getRangeStart() != null || getRangeEnd() != null) {
+ currentRangeValidator = new DateRangeValidator(
+ dateOutOfRangeMessage, getRangeStart(resolution),
+ getRangeEnd(resolution), null);
+ addValidator(currentRangeValidator);
+ }
+ }
+
+ /**
+ * Sets the end range for this component. If the value is set after this
+ * date (taking the resolution into account), the component will not
+ * validate. If endDate
is set to null
, any value
+ * after startDate
will be accepted by the range.
+ *
+ * @param endDate
+ * - the allowed range's end date (inclusive, based on the
+ * current resolution)
+ */
+ public void setRangeEnd(Date endDate) {
+ if (endDate != null && getState().rangeStart != null
+ && getState().rangeStart.after(endDate)) {
+ throw new IllegalStateException(
+ "endDate cannot be earlier than startDate");
+ }
+
+ // Create a defensive copy against issues when using java.sql.Date (and
+ // also against mutable Date).
+ getState().rangeEnd = endDate != null ? new Date(endDate.getTime())
+ : null;
+ updateRangeValidator();
+ }
+
+ /**
+ * Returns the precise rangeStart used.
+ *
+ * @param startDate
+ *
+ */
+ public Date getRangeStart() {
+ return getState(false).rangeStart;
+ }
+
+ /**
+ * Returns the precise rangeEnd used.
+ *
+ * @param startDate
+ */
+ public Date getRangeEnd() {
+ return getState(false).rangeEnd;
+ }
+
+ /*
+ * Invoked when a variable of the component changes. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void changeVariables(Object source, MapCalendar.getInstance
+ * is used.
+ *
+ * @return the Calendar.
+ * @see #setCalendar(Calendar)
+ */
+ private Calendar getCalendar() {
+
+ // Makes sure we have an calendar instance
+ if (calendar == null) {
+ calendar = Calendar.getInstance();
+ // Start by a zeroed calendar to avoid having values for lower
+ // resolution variables e.g. time when resolution is day
+ int min, field;
+ for (Resolution r : Resolution
+ .getResolutionsLowerThan(resolution)) {
+ field = r.getCalendarField();
+ min = calendar.getActualMinimum(field);
+ calendar.set(field, min);
+ }
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+
+ // Clone the instance
+ final Calendar newCal = (Calendar) calendar.clone();
+
+ final TimeZone currentTimeZone = getTimeZone();
+ if (currentTimeZone != null) {
+ newCal.setTimeZone(currentTimeZone);
+ }
+
+ final Date currentDate = getValue();
+ if (currentDate != null) {
+ newCal.setTime(currentDate);
+ }
+ return newCal;
+ }
+
+ /**
+ * Sets formatting used by some component implementations. See
+ * {@link SimpleDateFormat} for format details.
+ *
+ * By default it is encouraged to used default formatting defined by Locale,
+ * but due some JVM bugs it is sometimes necessary to use this method to
+ * override formatting. See Vaadin issue #2200.
+ *
+ * @param dateFormat
+ * the dateFormat to set
+ *
+ * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
+ */
+ public void setDateFormat(String dateFormat) {
+ this.dateFormat = dateFormat;
+ markAsDirty();
+ }
+
+ /**
+ * Returns a format string used to format date value on client side or null
+ * if default formatting from {@link Component#getLocale()} is used.
+ *
+ * @return the dateFormat
+ */
+ public String getDateFormat() {
+ return dateFormat;
+ }
+
+ /**
+ * Specifies whether or not date/time interpretation in component is to be
+ * lenient.
+ *
+ * @see Calendar#setLenient(boolean)
+ * @see #isLenient()
+ *
+ * @param lenient
+ * true if the lenient mode is to be turned on; false if it is to
+ * be turned off.
+ */
+ public void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ markAsDirty();
+ }
+
+ /**
+ * Returns whether date/time interpretation is to be lenient.
+ *
+ * @see #setLenient(boolean)
+ *
+ * @return true if the interpretation mode of this calendar is lenient;
+ * false otherwise.
+ */
+ public boolean isLenient() {
+ return lenient;
+ }
+
+ @Override
+ public void addFocusListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeFocusListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ @Override
+ public void addBlurListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeBlurListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ /**
+ * Checks whether ISO 8601 week numbers are shown in the date selector.
+ *
+ * @return true if week numbers are shown, false otherwise.
+ */
+ public boolean isShowISOWeekNumbers() {
+ return showISOWeekNumbers;
+ }
+
+ /**
+ * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
+ * 8601 defines that a week always starts with a Monday so the week numbers
+ * are only shown if this is the case.
+ *
+ * @param showWeekNumbers
+ * true if week numbers should be shown, false otherwise.
+ */
+ public void setShowISOWeekNumbers(boolean showWeekNumbers) {
+ showISOWeekNumbers = showWeekNumbers;
+ markAsDirty();
+ }
+
+ /**
+ * Validates the current value against registered validators if the field is
+ * not empty. Note that DateField is considered empty (value == null) and
+ * invalid if it contains text typed in by the user that couldn't be parsed
+ * into a Date value.
+ *
+ * @see com.vaadin.v7.ui.AbstractField#validate()
+ */
+ @Override
+ public void validate() throws InvalidValueException {
+ /*
+ * To work properly in form we must throw exception if there is
+ * currently a parsing error in the datefield. Parsing error is kind of
+ * an internal validator.
+ */
+ if (!uiHasValidDateString) {
+ throw new UnparsableDateString(currentParseErrorMessage);
+ }
+ super.validate();
+ }
+
+ /**
+ * Return the error message that is shown if the user inputted value can't
+ * be parsed into a Date object. If
+ * {@link #handleUnparsableDateString(String)} is overridden and it throws a
+ * custom exception, the message returned by
+ * {@link Exception#getLocalizedMessage()} will be used instead of the value
+ * returned by this method.
+ *
+ * @see #setParseErrorMessage(String)
+ *
+ * @return the error message that the DateField uses when it can't parse the
+ * textual input from user to a Date object
+ */
+ public String getParseErrorMessage() {
+ return defaultParseErrorMessage;
+ }
+
+ /**
+ * Sets the default error message used if the DateField cannot parse the
+ * text input by user to a Date field. Note that if the
+ * {@link #handleUnparsableDateString(String)} method is overridden, the
+ * localized message from its exception is used.
+ *
+ * @see #getParseErrorMessage()
+ * @see #handleUnparsableDateString(String)
+ * @param parsingErrorMessage
+ */
+ public void setParseErrorMessage(String parsingErrorMessage) {
+ defaultParseErrorMessage = parsingErrorMessage;
+ }
+
+ /**
+ * Sets the time zone used by this date field. The time zone is used to
+ * convert the absolute time in a Date object to a logical time displayed in
+ * the selector and to convert the select time back to a Date object.
+ *
+ * If no time zone has been set, the current default time zone returned by
+ * {@code TimeZone.getDefault()} is used.
+ *
+ * @see #getTimeZone()
+ * @param timeZone
+ * the time zone to use for time calculations.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ markAsDirty();
+ }
+
+ /**
+ * Gets the time zone used by this field. The time zone is used to convert
+ * the absolute time in a Date object to a logical time displayed in the
+ * selector and to convert the select time back to a Date object.
+ *
+ * If {@code null} is returned, the current default time zone returned by
+ * {@code TimeZone.getDefault()} is used.
+ *
+ * @return the current time zone
+ */
+ public TimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ public static class UnparsableDateString
+ extends Validator.InvalidValueException {
+
+ public UnparsableDateString(String message) {
+ super(message);
+ }
+
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+ if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
+ Date date = DesignAttributeHandler.getFormatter()
+ .parse(design.attr("value"), Date.class);
+ // formatting will return null if it cannot parse the string
+ if (date == null) {
+ Logger.getLogger(DateField.class.getName()).info(
+ "cannot parse " + design.attr("value") + " as date");
+ }
+ this.setValue(date, false, true);
+ }
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+ if (getValue() != null) {
+ design.attr("value",
+ DesignAttributeHandler.getFormatter().format(getValue()));
+ }
+ }
+
+ /**
+ * Returns current date-out-of-range error message.
+ *
+ * @see #setDateOutOfRangeMessage(String)
+ * @since 7.4
+ * @return Current error message for dates out of range.
+ */
+ public String getDateOutOfRangeMessage() {
+ return dateOutOfRangeMessage;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java
new file mode 100644
index 0000000000..53035ba087
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/DefaultFieldFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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 com.vaadin.v7.ui;
+
+import java.text.Normalizer.Form;
+import java.util.Date;
+
+import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.ui.Component;
+import com.vaadin.v7.data.Container;
+import com.vaadin.v7.data.Property;
+
+/**
+ * This class contains a basic implementation for {@link TableFieldFactory}. The
+ * class is singleton, use {@link #get()} method to get reference to the
+ * instance.
+ *
+ *
+ * Date: {@link DateField}(resolution: day).
+ * Item: {@link Form}.
+ * default field type: {@link TextField}.
+ * Headers and Footers
+ * Converters and Renderers
+ *
+ *
+ *
+ * Grid grid = new Grid(myContainer);
+ * Column column = grid.getColumn(STRING_DATE_PROPERTY);
+ * column.setConverter(new StringToDateConverter());
+ * column.setRenderer(new MyColorfulDateRenderer());
+ *
Lazy Loading
+ * Selection Modes and Models
+ *
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class Grid extends AbstractFocusable implements SelectionNotifier,
+ SortNotifier, SelectiveRenderer, ItemClickNotifier {
+
+ /**
+ * An event listener for column visibility change events in the Grid.
+ *
+ * @since 7.5.0
+ */
+ public interface ColumnVisibilityChangeListener extends Serializable {
+ /**
+ * Called when a column has become hidden or unhidden.
+ *
+ * @param event
+ */
+ void columnVisibilityChanged(ColumnVisibilityChangeEvent event);
+ }
+
+ /**
+ * An event that is fired when a column's visibility changes.
+ *
+ * @since 7.5.0
+ */
+ public static class ColumnVisibilityChangeEvent extends Component.Event {
+
+ private final Column column;
+ private final boolean userOriginated;
+ private final boolean hidden;
+
+ /**
+ * Constructor for a column visibility change event.
+ *
+ * @param source
+ * the grid from which this event originates
+ * @param column
+ * the column that changed its visibility
+ * @param hidden
+ *
+ * Grid grid = new Grid(myContainer);
+ *
+ * // uses the bundled SingleSelectionModel class
+ * grid.setSelectionMode(SelectionMode.SINGLE);
+ *
+ * // changes the behavior to a custom selection model
+ * grid.setSelectionModel(new MyTwoSelectionModel());
+ *
true
if the column was hidden,
+ * false
if it became visible
+ * @param isUserOriginated
+ * true
iff the event was triggered by an UI
+ * interaction
+ */
+ public ColumnVisibilityChangeEvent(Grid source, Column column,
+ boolean hidden, boolean isUserOriginated) {
+ super(source);
+ this.column = column;
+ this.hidden = hidden;
+ userOriginated = isUserOriginated;
+ }
+
+ /**
+ * Gets the column that became hidden or visible.
+ *
+ * @return the column that became hidden or visible.
+ * @see Column#isHidden()
+ */
+ public Column getColumn() {
+ return column;
+ }
+
+ /**
+ * Was the column set hidden or visible.
+ *
+ * @return true
if the column was hidden false
+ * if it was set visible
+ */
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ /**
+ * Returns true
if the column reorder was done by the user,
+ * false
if not and it was triggered by server side code.
+ *
+ * @return true
if event is a result of user interaction
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
+ }
+
+ /**
+ * A callback interface for generating details for a particular row in Grid.
+ *
+ * @since 7.5.0
+ * @author Vaadin Ltd
+ * @see DetailsGenerator#NULL
+ */
+ public interface DetailsGenerator extends Serializable {
+
+ /** A details generator that provides no details */
+ public DetailsGenerator NULL = new DetailsGenerator() {
+ @Override
+ public Component getDetails(RowReference rowReference) {
+ return null;
+ }
+ };
+
+ /**
+ * This method is called for whenever a details row needs to be shown on
+ * the client. Grid removes all of its references to details components
+ * when they are no longer displayed on the client-side and will
+ * re-request once needed again.
+ * null
to leave
+ * the details empty.
+ */
+ Component getDetails(RowReference rowReference);
+ }
+
+ /**
+ * A class that manages details components by calling
+ * {@link DetailsGenerator} as needed. Details components are attached by
+ * this class when the {@link RpcDataProviderExtension} is sending data to
+ * the client. Details components are detached and forgotten when client
+ * informs that it has dropped the corresponding item.
+ *
+ * @since 7.6.1
+ */
+ public final static class DetailComponentManager
+ extends AbstractGridExtension implements DataGenerator {
+
+ /**
+ * The user-defined details generator.
+ *
+ * @see #setDetailsGenerator(DetailsGenerator)
+ */
+ private DetailsGenerator detailsGenerator;
+
+ /**
+ * This map represents all details that are currently visible on the
+ * client. Details components get destroyed once they scroll out of
+ * view.
+ */
+ private final Mapnull
from DetailsGenerator when
+ * {@link DetailsGenerator#getDetails(RowReference)} was called.
+ */
+ private final Settrue
if visible; false
if not
+ */
+ public void setDetailsVisible(Object itemId, boolean visible) {
+ if ((visible && openDetails.contains(itemId))
+ || (!visible && !openDetails.contains(itemId))) {
+ return;
+ }
+
+ if (visible) {
+ openDetails.add(itemId);
+ refreshRow(itemId);
+ } else {
+ openDetails.remove(itemId);
+ destroyDetails(itemId);
+ refreshRow(itemId);
+ }
+ }
+
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ // DetailComponentManager should not send anything if details
+ // generator is the default null version.
+ if (openDetails.contains(itemId)
+ && !detailsGenerator.equals(DetailsGenerator.NULL)) {
+ // Double check to be sure details component exists.
+ createDetails(itemId);
+
+ Component detailsComponent = itemIdToDetailsComponent
+ .get(itemId);
+ rowData.put(GridState.JSONKEY_DETAILS_VISIBLE,
+ (detailsComponent != null
+ ? detailsComponent.getConnectorId() : ""));
+ }
+ }
+
+ @Override
+ public void destroyData(Object itemId) {
+ if (openDetails.contains(itemId)) {
+ destroyDetails(itemId);
+ }
+ }
+
+ /**
+ * Sets a new details generator for row details.
+ * null
;
+ */
+ public void setDetailsGenerator(DetailsGenerator detailsGenerator)
+ throws IllegalArgumentException {
+ if (detailsGenerator == null) {
+ throw new IllegalArgumentException(
+ "Details generator may not be null");
+ } else if (detailsGenerator == this.detailsGenerator) {
+ return;
+ }
+
+ this.detailsGenerator = detailsGenerator;
+
+ refreshDetails();
+ }
+
+ /**
+ * Gets the current details generator for row details.
+ *
+ * @return the detailsGenerator the current details generator
+ */
+ public DetailsGenerator getDetailsGenerator() {
+ return detailsGenerator;
+ }
+
+ /**
+ * Checks whether details are visible for the given item.
+ *
+ * @param itemId
+ * the id of the item for which to check details visibility
+ * @return true
iff the details are visible
+ */
+ public boolean isDetailsVisible(Object itemId) {
+ return openDetails.contains(itemId);
+ }
+ }
+
+ /**
+ * Custom field group that allows finding property types before an item has
+ * been bound.
+ */
+ private final class CustomFieldGroup extends FieldGroup {
+
+ public CustomFieldGroup() {
+ setFieldFactory(EditorFieldFactory.get());
+ }
+
+ @Override
+ protected Class> getPropertyType(Object propertyId)
+ throws BindException {
+ if (getItemDataSource() == null) {
+ return datasource.getType(propertyId);
+ } else {
+ return super.getPropertyType(propertyId);
+ }
+ }
+
+ @Override
+ protected null
if header or footer
+ */
+ public Object getItemId() {
+ return itemId;
+ }
+
+ /**
+ * Returns property id of clicked column.
+ *
+ * @return property id
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ /**
+ * Return the clicked section of Grid.
+ *
+ * @return section of grid
+ */
+ public Section getSection() {
+ return section;
+ }
+
+ /**
+ * Returns the clicked row index relative to Grid section. In the body
+ * of the Grid the index is the item index in the Container. Header and
+ * Footer rows for index can be fetched with
+ * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
+ *
+ * @return row index in section
+ */
+ public int getRowIndex() {
+ return rowIndex;
+ }
+
+ @Override
+ public Grid getComponent() {
+ return (Grid) super.getComponent();
+ }
+ }
+
+ /**
+ * An event which is fired when saving the editor fails
+ */
+ public static class CommitErrorEvent extends Component.Event {
+
+ private CommitException cause;
+
+ private Settrue
if event is a result of user
+ * interaction, false
if from API call
+ */
+ public ColumnReorderEvent(Grid source, boolean userOriginated) {
+ super(source);
+ this.userOriginated = userOriginated;
+ }
+
+ /**
+ * Returns true
if the column reorder was done by the user,
+ * false
if not and it was triggered by server side code.
+ *
+ * @return true
if event is a result of user interaction
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
+
+ }
+
+ /**
+ * An event listener for column resize events in the Grid.
+ *
+ * @since 7.6
+ */
+ public interface ColumnResizeListener extends Serializable {
+
+ /**
+ * Called when the columns of the grid have been resized.
+ *
+ * @param event
+ * An event providing more information
+ */
+ void columnResize(ColumnResizeEvent event);
+ }
+
+ /**
+ * An event that is fired when a column is resized, either programmatically
+ * or by the user.
+ *
+ * @since 7.6
+ */
+ public static class ColumnResizeEvent extends Component.Event {
+
+ private final Column column;
+ private final boolean userOriginated;
+
+ /**
+ *
+ * @param source
+ * the grid where the event originated from
+ * @param userOriginated
+ * true
if event is a result of user
+ * interaction, false
if from API call
+ */
+ public ColumnResizeEvent(Grid source, Column column,
+ boolean userOriginated) {
+ super(source);
+ this.column = column;
+ this.userOriginated = userOriginated;
+ }
+
+ /**
+ * Returns the column that was resized.
+ *
+ * @return the resized column.
+ */
+ public Column getColumn() {
+ return column;
+ }
+
+ /**
+ * Returns true
if the column resize was done by the user,
+ * false
if not and it was triggered by server side code.
+ *
+ * @return true
if event is a result of user interaction
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
+
+ }
+
+ /**
+ * Interface for an editor event listener
+ */
+ public interface EditorListener extends Serializable {
+
+ public static final Method EDITOR_OPEN_METHOD = ReflectTools.findMethod(
+ EditorListener.class, "editorOpened", EditorOpenEvent.class);
+ public static final Method EDITOR_MOVE_METHOD = ReflectTools.findMethod(
+ EditorListener.class, "editorMoved", EditorMoveEvent.class);
+ public static final Method EDITOR_CLOSE_METHOD = ReflectTools
+ .findMethod(EditorListener.class, "editorClosed",
+ EditorCloseEvent.class);
+
+ /**
+ * Called when an editor is opened
+ *
+ * @param e
+ * an editor open event object
+ */
+ public void editorOpened(EditorOpenEvent e);
+
+ /**
+ * Called when an editor is reopened without closing it first
+ *
+ * @param e
+ * an editor move event object
+ */
+ public void editorMoved(EditorMoveEvent e);
+
+ /**
+ * Called when an editor is closed
+ *
+ * @param e
+ * an editor close event object
+ */
+ public void editorClosed(EditorCloseEvent e);
+
+ }
+
+ /**
+ * Base class for editor related events
+ */
+ public static abstract class EditorEvent extends Component.Event {
+
+ private Object itemID;
+
+ protected EditorEvent(Grid source, Object itemID) {
+ super(source);
+ this.itemID = itemID;
+ }
+
+ /**
+ * Get the item (row) for which this editor was opened
+ */
+ public Object getItem() {
+ return itemID;
+ }
+
+ }
+
+ /**
+ * This event gets fired when an editor is opened
+ */
+ public static class EditorOpenEvent extends EditorEvent {
+
+ public EditorOpenEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
+ * This event gets fired when an editor is opened while another row is being
+ * edited (i.e. editor focus moves elsewhere)
+ */
+ public static class EditorMoveEvent extends EditorEvent {
+
+ public EditorMoveEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
+ * This event gets fired when an editor is dismissed or closed by other
+ * means.
+ */
+ public static class EditorCloseEvent extends EditorEvent {
+
+ public EditorCloseEvent(Grid source, Object itemID) {
+ super(source, itemID);
+ }
+ }
+
+ /**
+ * Default error handler for the editor
+ *
+ */
+ public class DefaultEditorErrorHandler implements EditorErrorHandler {
+
+ @Override
+ public void commitError(CommitErrorEvent event) {
+ Maptrue
iff the item is selected
+ */
+ boolean isSelected(Object itemId);
+
+ /**
+ * Returns a collection of all the currently selected itemIds.
+ *
+ * @return a collection of all the currently selected itemIds
+ */
+ Collectionnull
when a selection model is being detached
+ * from a Grid.
+ */
+ void setGrid(Grid grid);
+
+ /**
+ * Resets the SelectiomModel to an initial state.
+ * true
if the selection state changed.
+ * false
if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if the itemIds
varargs array is
+ * null
or given itemIds don't exist in the
+ * container of Grid
+ * @see #deselect(Object...)
+ */
+ boolean select(Object... itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks items as selected.
+ * true
if the selection state changed.
+ * false
if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if itemIds
is null
or given
+ * itemIds don't exist in the container of Grid
+ * @see #deselect(Collection)
+ */
+ boolean select(Collection> itemIds)
+ throws IllegalArgumentException;
+
+ /**
+ * Marks items as deselected.
+ *
+ * @param itemIds
+ * the itemId(s) to remove from being selected
+ * @return true
if the selection state changed.
+ * false
if none the given itemIds were
+ * selected previously
+ * @throws IllegalArgumentException
+ * if the itemIds
varargs array is
+ * null
+ * @see #select(Object...)
+ */
+ boolean deselect(Object... itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks items as deselected.
+ *
+ * @param itemIds
+ * the itemId(s) to remove from being selected
+ * @return true
if the selection state changed.
+ * false
if none the given itemIds were
+ * selected previously
+ * @throws IllegalArgumentException
+ * if itemIds
is null
+ * @see #select(Collection)
+ */
+ boolean deselect(Collection> itemIds)
+ throws IllegalArgumentException;
+
+ /**
+ * Marks all the items in the current Container as selected
+ *
+ * @return true
iff some items were previously not
+ * selected
+ * @see #deselectAll()
+ */
+ boolean selectAll();
+
+ /**
+ * Marks all the items in the current Container as deselected
+ *
+ * @return true
iff some items were previously selected
+ * @see #selectAll()
+ */
+ boolean deselectAll();
+
+ /**
+ * Marks items as selected while deselecting all items not in the
+ * given Collection.
+ *
+ * @param itemIds
+ * the itemIds to mark as selected
+ * @return true
if the selection state changed.
+ * false
if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if itemIds
is null
or given
+ * itemIds don't exist in the container of Grid
+ */
+ boolean setSelected(Collection> itemIds)
+ throws IllegalArgumentException;
+
+ /**
+ * Marks items as selected while deselecting all items not in the
+ * varargs array.
+ *
+ * @param itemIds
+ * the itemIds to mark as selected
+ * @return true
if the selection state changed.
+ * false
if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if the itemIds
varargs array is
+ * null
or given itemIds don't exist in the
+ * container of Grid
+ */
+ boolean setSelected(Object... itemIds)
+ throws IllegalArgumentException;
+ }
+
+ /**
+ * A SelectionModel that supports for only single rows to be selected at
+ * a time.
+ * null
for
+ * deselect
+ * @return true
if the selection state changed.
+ * false
if the itemId already was selected
+ * @throws IllegalStateException
+ * if the selection was illegal. One such reason might
+ * be that the given id was null, indicating a deselect,
+ * but implementation doesn't allow deselecting.
+ * re-selecting something
+ * @throws IllegalArgumentException
+ * if given itemId does not exist in the container of
+ * Grid
+ */
+ boolean select(Object itemId)
+ throws IllegalStateException, IllegalArgumentException;
+
+ /**
+ * Gets the item id of the currently selected item.
+ *
+ * @return the item id of the currently selected item, or
+ * null
if nothing is selected
+ */
+ Object getSelectedRow();
+
+ /**
+ * Sets whether it's allowed to deselect the selected row through
+ * the UI. Deselection is allowed by default.
+ *
+ * @param deselectAllowed
+ * true
if the selected row can be
+ * deselected without selecting another row instead;
+ * otherwise false
.
+ */
+ public void setDeselectAllowed(boolean deselectAllowed);
+
+ /**
+ * Sets whether it's allowed to deselect the selected row through
+ * the UI.
+ *
+ * @return true
if deselection is allowed; otherwise
+ * false
+ */
+ public boolean isDeselectAllowed();
+ }
+
+ /**
+ * A SelectionModel that does not allow for rows to be selected.
+ * false
.
+ */
+ @Override
+ public boolean isSelected(Object itemId);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always an empty collection.
+ */
+ @Override
+ public CollectionFlyweight
-pattern any instance
+ * of this object is subject to change without the user knowing it and so
+ * should not be stored anywhere outside of the method providing these
+ * instances.
+ */
+ public static class RowReference implements Serializable {
+ private final Grid grid;
+
+ private Object itemId;
+
+ /**
+ * Creates a new row reference for the given grid.
+ *
+ * @param grid
+ * the grid that the row belongs to
+ */
+ public RowReference(Grid grid) {
+ this.grid = grid;
+ }
+
+ /**
+ * Sets the identifying information for this row
+ *
+ * @param itemId
+ * the item id of the row
+ */
+ public void set(Object itemId) {
+ this.itemId = itemId;
+ }
+
+ /**
+ * Gets the grid that contains the referenced row.
+ *
+ * @return the grid that contains referenced row
+ */
+ public Grid getGrid() {
+ return grid;
+ }
+
+ /**
+ * Gets the item id of the row.
+ *
+ * @return the item id of the row
+ */
+ public Object getItemId() {
+ return itemId;
+ }
+
+ /**
+ * Gets the item for the row.
+ *
+ * @return the item for the row
+ */
+ public Item getItem() {
+ return grid.getContainerDataSource().getItem(itemId);
+ }
+ }
+
+ /**
+ * A data class which contains information which identifies a cell in a
+ * {@link Grid}.
+ * Flyweight
-pattern any instance
+ * of this object is subject to change without the user knowing it and so
+ * should not be stored anywhere outside of the method providing these
+ * instances.
+ */
+ public static class CellReference implements Serializable {
+ private final RowReference rowReference;
+
+ private Object propertyId;
+
+ public CellReference(RowReference rowReference) {
+ this.rowReference = rowReference;
+ }
+
+ /**
+ * Sets the identifying information for this cell
+ *
+ * @param propertyId
+ * the property id of the column
+ */
+ public void set(Object propertyId) {
+ this.propertyId = propertyId;
+ }
+
+ /**
+ * Gets the grid that contains the referenced cell.
+ *
+ * @return the grid that contains referenced cell
+ */
+ public Grid getGrid() {
+ return rowReference.getGrid();
+ }
+
+ /**
+ * @return the property id of the column
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ /**
+ * @return the property for the cell
+ */
+ public Property> getProperty() {
+ return getItem().getItemProperty(propertyId);
+ }
+
+ /**
+ * Gets the item id of the row of the cell.
+ *
+ * @return the item id of the row
+ */
+ public Object getItemId() {
+ return rowReference.getItemId();
+ }
+
+ /**
+ * Gets the item for the row of the cell.
+ *
+ * @return the item for the row
+ */
+ public Item getItem() {
+ return rowReference.getItem();
+ }
+
+ /**
+ * Gets the value of the cell.
+ *
+ * @return the value of the cell
+ */
+ public Object getValue() {
+ return getProperty().getValue();
+ }
+ }
+
+ /**
+ * A callback interface for generating custom style names for Grid rows.
+ *
+ * @see Grid#setRowStyleGenerator(RowStyleGenerator)
+ */
+ public interface RowStyleGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a style name for a row.
+ *
+ * @param row
+ * the row to generate a style for
+ * @return the style name to add to this row, or {@code null} to not set
+ * any style
+ */
+ public String getStyle(RowReference row);
+ }
+
+ /**
+ * A callback interface for generating custom style names for Grid cells.
+ *
+ * @see Grid#setCellStyleGenerator(CellStyleGenerator)
+ */
+ public interface CellStyleGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a style name for a column.
+ *
+ * @param cell
+ * the cell to generate a style for
+ * @return the style name to add to this cell, or {@code null} to not
+ * set any style
+ */
+ public String getStyle(CellReference cell);
+ }
+
+ /**
+ * A callback interface for generating optional descriptions (tooltips) for
+ * Grid rows. If a description is generated for a row, it is used for all
+ * the cells in the row for which a {@link CellDescriptionGenerator cell
+ * description} is not generated.
+ *
+ * @see Grid#setRowDescriptionGenerator
+ *
+ * @since 7.6
+ */
+ public interface RowDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a description (tooltip) for a row. The
+ * description may contain HTML which is rendered directly; if this is
+ * not desired the returned string must be escaped by the implementing
+ * method.
+ *
+ * @param row
+ * the row to generate a description for
+ * @return the row description or {@code null} for no description
+ */
+ public String getDescription(RowReference row);
+ }
+
+ /**
+ * A callback interface for generating optional descriptions (tooltips) for
+ * Grid cells. If a cell has both a {@link RowDescriptionGenerator row
+ * description}Â and a cell description, the latter has precedence.
+ *
+ * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator)
+ *
+ * @since 7.6
+ */
+ public interface CellDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Grid to generate a description (tooltip) for a cell. The
+ * description may contain HTML which is rendered directly; if this is
+ * not desired the returned string must be escaped by the implementing
+ * method.
+ *
+ * @param cell
+ * the cell to generate a description for
+ * @return the cell description or {@code null} for no description
+ */
+ public String getDescription(CellReference cell);
+ }
+
+ /**
+ * Class for generating all row and cell related data for the essential
+ * parts of Grid.
+ */
+ private class RowDataGenerator implements DataGenerator {
+
+ private void put(String key, String value, JsonObject object) {
+ if (value != null && !value.isEmpty()) {
+ object.put(key, value);
+ }
+ }
+
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ RowReference row = new RowReference(Grid.this);
+ row.set(itemId);
+
+ if (rowStyleGenerator != null) {
+ String style = rowStyleGenerator.getStyle(row);
+ put(GridState.JSONKEY_ROWSTYLE, style, rowData);
+ }
+
+ if (rowDescriptionGenerator != null) {
+ String description = rowDescriptionGenerator
+ .getDescription(row);
+ put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData);
+
+ }
+
+ JsonObject cellStyles = Json.createObject();
+ JsonObject cellData = Json.createObject();
+ JsonObject cellDescriptions = Json.createObject();
+
+ CellReference cell = new CellReference(row);
+
+ for (Column column : getColumns()) {
+ cell.set(column.getPropertyId());
+
+ writeData(cell, cellData);
+ writeStyles(cell, cellStyles);
+ writeDescriptions(cell, cellDescriptions);
+ }
+
+ if (cellDescriptionGenerator != null
+ && cellDescriptions.keys().length > 0) {
+ rowData.put(GridState.JSONKEY_CELLDESCRIPTION,
+ cellDescriptions);
+ }
+
+ if (cellStyleGenerator != null && cellStyles.keys().length > 0) {
+ rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
+ }
+
+ rowData.put(GridState.JSONKEY_DATA, cellData);
+ }
+
+ private void writeStyles(CellReference cell, JsonObject styles) {
+ if (cellStyleGenerator != null) {
+ String style = cellStyleGenerator.getStyle(cell);
+ put(columnKeys.key(cell.getPropertyId()), style, styles);
+ }
+ }
+
+ private void writeDescriptions(CellReference cell,
+ JsonObject descriptions) {
+ if (cellDescriptionGenerator != null) {
+ String description = cellDescriptionGenerator
+ .getDescription(cell);
+ put(columnKeys.key(cell.getPropertyId()), description,
+ descriptions);
+ }
+ }
+
+ private void writeData(CellReference cell, JsonObject data) {
+ Column column = getColumn(cell.getPropertyId());
+ Converter, ?> converter = column.getConverter();
+ Renderer> renderer = column.getRenderer();
+
+ Item item = cell.getItem();
+ Object modelValue = item.getItemProperty(cell.getPropertyId())
+ .getValue();
+
+ data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer
+ .encodeValue(modelValue, renderer, converter, getLocale()));
+ }
+
+ @Override
+ public void destroyData(Object itemId) {
+ // NO-OP
+ }
+ }
+
+ /**
+ * Abstract base class for Grid header and footer sections.
+ *
+ * @since 7.6
+ * @param null
, even
+ * if model and renderer aren't compatible.
+ */
+ private boolean isFirstConverterAssignment = true;
+
+ /**
+ * Internally used constructor.
+ *
+ * @param grid
+ * The grid this column belongs to. Should not be null.
+ * @param state
+ * the shared state of this column
+ * @param propertyId
+ * the backing property id for this column
+ */
+ Column(Grid grid, GridColumnState state, Object propertyId) {
+ this.grid = grid;
+ this.state = state;
+ this.propertyId = propertyId;
+ internalSetRenderer(new TextRenderer());
+ }
+
+ /**
+ * Returns the serializable state of this column that is sent to the
+ * client side connector.
+ *
+ * @return the internal state of the column
+ */
+ GridColumnState getState() {
+ return state;
+ }
+
+ /**
+ * Returns the property id for the backing property of this Column
+ *
+ * @return property id
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ /**
+ * Returns the caption of the header. By default the header caption is
+ * the property id of the column.
+ *
+ * @return the text in the default row of header.
+ *
+ * @throws IllegalStateException
+ * if the column no longer is attached to the grid
+ */
+ public String getHeaderCaption() throws IllegalStateException {
+ checkColumnIsAttached();
+
+ return state.headerCaption;
+ }
+
+ /**
+ * Sets the caption of the header. This caption is also used as the
+ * hiding toggle caption, unless it is explicitly set via
+ * {@link #setHidingToggleCaption(String)}.
+ *
+ * @param caption
+ * the text to show in the caption
+ * @return the column itself
+ *
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public Column setHeaderCaption(String caption)
+ throws IllegalStateException {
+ checkColumnIsAttached();
+ if (caption == null) {
+ caption = ""; // Render null as empty
+ }
+ state.headerCaption = caption;
+
+ HeaderRow row = grid.getHeader().getDefaultRow();
+ if (row != null) {
+ row.getCell(grid.getPropertyIdByColumnId(state.id))
+ .setText(caption);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the caption of the hiding toggle for this column.
+ *
+ * @since 7.5.0
+ * @see #setHidingToggleCaption(String)
+ * @return the caption for the hiding toggle for this column
+ * @throws IllegalStateException
+ * if the column is no longer attached to any grid
+ */
+ public String getHidingToggleCaption() throws IllegalStateException {
+ checkColumnIsAttached();
+ return state.hidingToggleCaption;
+ }
+
+ /**
+ * Sets the caption of the hiding toggle for this column. Shown in the
+ * toggle for this column in the grid's sidebar when the column is
+ * {@link #isHidable() hidable}.
+ * null
, and in that case the column's
+ * {@link #getHeaderCaption() header caption} is used.
+ * null
clears any previously set
+ * field, causing a new field to be created the next time the item
+ * editor is opened.
+ *
+ * @param editor
+ * the editor field
+ * @return this column
+ */
+ public Column setEditorField(Field> editor) {
+ grid.setEditorField(getPropertyId(), editor);
+ return this;
+ }
+
+ /**
+ * Returns the editor field used to edit the properties in this column
+ * when the item editor is active. Returns null if the column is not
+ * {@link Column#isEditable() editable}.
+ * FieldGroup
might cause
+ * {@link com.vaadin.v7.data.fieldgroup.FieldGroup.BindException
+ * BindException} to be thrown.
+ *
+ * @return the bound field; or null
if the respective
+ * column is not editable
+ *
+ * @throws IllegalArgumentException
+ * if there is no column for the provided property id
+ * @throws FieldGroup.BindException
+ * if no field has been configured and there is a problem
+ * building or binding
+ */
+ public Field> getEditorField() {
+ return grid.getEditorField(getPropertyId());
+ }
+
+ /**
+ * Hides or shows the column. By default columns are visible before
+ * explicitly hiding them.
+ *
+ * @since 7.5.0
+ * @param hidden
+ * true
to hide the column, false
+ * to show
+ * @return this column
+ */
+ public Column setHidden(boolean hidden) {
+ if (hidden != getState().hidden) {
+ getState().hidden = hidden;
+ grid.markAsDirty();
+ grid.fireColumnVisibilityChangeEvent(this, hidden, false);
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether this column is hidden. Default is {@code false}.
+ *
+ * @since 7.5.0
+ * @return true
if the column is currently hidden,
+ * false
otherwise
+ */
+ public boolean isHidden() {
+ return getState().hidden;
+ }
+
+ /**
+ * Sets whether this column can be hidden by the user. Hidable columns
+ * can be hidden and shown via the sidebar menu.
+ *
+ * @since 7.5.0
+ * @param hidable
+ * true
iff the column may be hidable by the
+ * user via UI interaction
+ * @return this column
+ */
+ public Column setHidable(boolean hidable) {
+ if (hidable != getState().hidable) {
+ getState().hidable = hidable;
+ grid.markAsDirty();
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether this column can be hidden by the user. Default is
+ * {@code false}.
+ * true
if the user can hide the column,
+ * false
if not
+ */
+ public boolean isHidable() {
+ return getState().hidable;
+ }
+
+ /**
+ * Sets whether this column can be resized by the user.
+ *
+ * @since 7.6
+ * @param resizable
+ * {@code true} if this column should be resizable,
+ * {@code false} otherwise
+ */
+ public Column setResizable(boolean resizable) {
+ if (resizable != getState().resizable) {
+ getState().resizable = resizable;
+ grid.markAsDirty();
+ }
+ return this;
+ }
+
+ /**
+ * Returns whether this column can be resized by the user. Default is
+ * {@code true}.
+ * null
+ * after the constructor has been run.
+ */
+ private SelectionModel selectionModel;
+
+ /**
+ * Used to know whether selection change events originate from the server or
+ * the client so the selection change handler knows whether the changes
+ * should be sent to the client.
+ */
+ private boolean applyingSelectionFromClient;
+
+ private final Header header = new Header(this);
+ private final Footer footer = new Footer(this);
+
+ private Object editedItemId = null;
+ private boolean editorActive = false;
+ private FieldGroup editorFieldGroup = new CustomFieldGroup();
+
+ private CellStyleGenerator cellStyleGenerator;
+ private RowStyleGenerator rowStyleGenerator;
+
+ private CellDescriptionGenerator cellDescriptionGenerator;
+ private RowDescriptionGenerator rowDescriptionGenerator;
+
+ /**
+ * true
if Grid is using the internal IndexedContainer created
+ * in Grid() constructor, or false
if the user has set their
+ * own Container.
+ *
+ * @see #setContainerDataSource(Indexed)
+ * @see #LegacyGrid()
+ */
+ private boolean defaultContainer = true;
+
+ private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler();
+
+ private DetailComponentManager detailComponentManager = null;
+
+ private Setnull
if not found
+ */
+ public Column getColumn(Object propertyId) {
+ return columns.get(propertyId);
+ }
+
+ /**
+ * Returns a copy of currently configures columns in their current visual
+ * order in this Grid.
+ *
+ * @return unmodifiable copy of current columns in visual order
+ */
+ public Listnull
if not found
+ */
+ Column getColumnByColumnId(String columnId) {
+ Object propertyId = getPropertyIdByColumnId(columnId);
+ return getColumn(propertyId);
+ }
+
+ /**
+ * Used internally by the {@link Grid} to get a property id by referencing
+ * the columns generated state id.
+ *
+ * @param columnId
+ * The state id of the column
+ * @return The column instance or null if not found
+ */
+ Object getPropertyIdByColumnId(String columnId) {
+ return columnKeys.get(columnId);
+ }
+
+ /**
+ * Returns whether column reordering is allowed. Default value is
+ * false
.
+ *
+ * @since 7.5.0
+ * @return true if reordering is allowed
+ */
+ public boolean isColumnReorderingAllowed() {
+ return getState(false).columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether or not column reordering is allowed. Default value is
+ * false
.
+ *
+ * @since 7.5.0
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ if (isColumnReorderingAllowed() != columnReorderingAllowed) {
+ getState().columnReorderingAllowed = columnReorderingAllowed;
+ }
+ }
+
+ @Override
+ protected GridState getState() {
+ return (GridState) super.getState();
+ }
+
+ @Override
+ protected GridState getState(boolean markAsDirty) {
+ return (GridState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Creates a new column based on a property id and appends it as the last
+ * column.
+ *
+ * @param datasourcePropertyId
+ * The property id of a property in the datasource
+ */
+ private Column appendColumn(Object datasourcePropertyId) {
+ if (datasourcePropertyId == null) {
+ throw new IllegalArgumentException("Property id cannot be null");
+ }
+ assert datasource.getContainerPropertyIds().contains(
+ datasourcePropertyId) : "Datasource should contain the property id";
+
+ GridColumnState columnState = new GridColumnState();
+ columnState.id = columnKeys.key(datasourcePropertyId);
+
+ Column column = new Column(this, columnState, datasourcePropertyId);
+ columns.put(datasourcePropertyId, column);
+
+ getState().columns.add(columnState);
+ getState().columnOrder.add(columnState.id);
+ header.addColumn(datasourcePropertyId);
+ footer.addColumn(datasourcePropertyId);
+
+ String humanFriendlyPropertyId = SharedUtil.propertyIdToHumanFriendly(
+ String.valueOf(datasourcePropertyId));
+ column.setHeaderCaption(humanFriendlyPropertyId);
+
+ if (datasource instanceof Sortable
+ && ((Sortable) datasource).getSortableContainerPropertyIds()
+ .contains(datasourcePropertyId)) {
+ column.setSortable(true);
+ }
+
+ return column;
+ }
+
+ /**
+ * Removes a column from Grid based on a property id.
+ *
+ * @param propertyId
+ * The property id of column to be removed
+ *
+ * @throws IllegalArgumentException
+ * if there is no column for given property id in this grid
+ */
+ public void removeColumn(Object propertyId)
+ throws IllegalArgumentException {
+ if (!columns.keySet().contains(propertyId)) {
+ throw new IllegalArgumentException(
+ "There is no column for given property id " + propertyId);
+ }
+
+ Listnull
is given, then Grid's
+ * height is undefined
+ * @throws IllegalArgumentException
+ * if {@code rows} is zero or less
+ * @throws IllegalArgumentException
+ * if {@code rows} is {@link Double#isInfinite(double) infinite}
+ * @throws IllegalArgumentException
+ * if {@code rows} is {@link Double#isNaN(double) NaN}
+ */
+ public void setHeightByRows(double rows) {
+ if (rows <= 0.0d) {
+ throw new IllegalArgumentException(
+ "More than zero rows must be shown.");
+ } else if (Double.isInfinite(rows)) {
+ throw new IllegalArgumentException(
+ "Grid doesn't support infinite heights");
+ } else if (Double.isNaN(rows)) {
+ throw new IllegalArgumentException("NaN is not a valid row count");
+ }
+
+ getState().heightByRows = rows;
+ }
+
+ /**
+ * Gets the amount of rows in Grid's body that are shown, while
+ * {@link #getHeightMode()} is {@link HeightMode#ROW}.
+ *
+ * @return the amount of rows that are being shown in Grid's body
+ * @see #setHeightByRows(double)
+ */
+ public double getHeightByRows() {
+ return getState(false).heightByRows;
+ }
+
+ /**
+ * {@inheritDoc}
+ * null
+ */
+ public void setSelectionModel(SelectionModel selectionModel)
+ throws IllegalArgumentException {
+ if (selectionModel == null) {
+ throw new IllegalArgumentException(
+ "Selection model may not be null");
+ }
+
+ if (this.selectionModel != selectionModel) {
+ // this.selectionModel is null on init
+ if (this.selectionModel != null) {
+ this.selectionModel.remove();
+ }
+
+ this.selectionModel = selectionModel;
+ selectionModel.setGrid(this);
+ }
+ }
+
+ /**
+ * Returns the currently used {@link SelectionModel}.
+ *
+ * @return the currently used SelectionModel
+ */
+ public SelectionModel getSelectionModel() {
+ return selectionModel;
+ }
+
+ /**
+ * Sets the Grid's selection mode.
+ *
+ *
+ *
+ * @param selectionMode
+ * the selection mode to switch to
+ * @return The {@link SelectionModel} instance that was taken into use
+ * @throws IllegalArgumentException
+ * if {@code selectionMode} is
+ * grid.setSelectionMode(SelectionMode.MULTI);
+ * grid.setSelectionModel(new MultiSelectionMode());
+ *
null
+ * @see SelectionModel
+ */
+ public SelectionModel setSelectionMode(final SelectionMode selectionMode)
+ throws IllegalArgumentException {
+ if (selectionMode == null) {
+ throw new IllegalArgumentException(
+ "selection mode may not be null");
+ }
+ final SelectionModel newSelectionModel = selectionMode.createModel();
+ setSelectionModel(newSelectionModel);
+ return newSelectionModel;
+ }
+
+ /**
+ * Checks whether an item is selected or not.
+ *
+ * @param itemId
+ * the item id to check for
+ * @return true
iff the item is selected
+ */
+ // keep this javadoc in sync with SelectionModel.isSelected
+ public boolean isSelected(Object itemId) {
+ return selectionModel.isSelected(itemId);
+ }
+
+ /**
+ * Returns a collection of all the currently selected itemIds.
+ * null
+ * if nothing is selected
+ * @throws IllegalStateException
+ * if the selection model does not implement
+ * {@code SelectionModel.Single}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.getSelectedRow
+ public Object getSelectedRow() throws IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ return ((SelectionModel.Single) selectionModel).getSelectedRow();
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ throw new IllegalStateException("Cannot get unique selected row: "
+ + "Grid is in multiselect mode "
+ + "(the current selection model is "
+ + selectionModel.getClass().getName() + ").");
+ } else if (selectionModel instanceof SelectionModel.None) {
+ throw new IllegalStateException(
+ "Cannot get selected row: " + "Grid selection is disabled "
+ + "(the current selection model is "
+ + selectionModel.getClass().getName() + ").");
+ } else {
+ throw new IllegalStateException("Cannot get selected row: "
+ + "Grid selection model does not implement "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + "(the current model is "
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Marks an item as selected.
+ * true
if the selection state changed,
+ * false
if the itemId already was selected
+ * @throws IllegalArgumentException
+ * if the {@code itemId} doesn't exist in the currently active
+ * Container
+ * @throws IllegalStateException
+ * if the selection was illegal. One such reason might be that
+ * the implementation already had an item selected, and that
+ * needs to be explicitly deselected before re-selecting
+ * something.
+ * @throws IllegalStateException
+ * if the selection model does not implement
+ * {@code SelectionModel.Single} or {@code SelectionModel.Multi}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.select
+ public boolean select(Object itemId)
+ throws IllegalArgumentException, IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ return ((SelectionModel.Single) selectionModel).select(itemId);
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ return ((SelectionModel.Multi) selectionModel).select(itemId);
+ } else if (selectionModel instanceof SelectionModel.None) {
+ throw new IllegalStateException("Cannot select row '" + itemId
+ + "': Grid selection is disabled "
+ + "(the current selection model is "
+ + selectionModel.getClass().getName() + ").");
+ } else {
+ throw new IllegalStateException("Cannot select row '" + itemId
+ + "': Grid selection model does not implement "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + "(the current model is "
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Marks an item as unselected.
+ * true
if the selection state changed,
+ * false
if the itemId was already selected
+ * @throws IllegalArgumentException
+ * if the {@code itemId} doesn't exist in the currently active
+ * Container
+ * @throws IllegalStateException
+ * if the deselection was illegal. One such reason might be that
+ * the implementation requires one or more items to be selected
+ * at all times.
+ * @throws IllegalStateException
+ * if the selection model does not implement
+ * {@code SelectionModel.Single} or {code SelectionModel.Multi}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.deselect
+ public boolean deselect(Object itemId) throws IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ if (isSelected(itemId)) {
+ return ((SelectionModel.Single) selectionModel).select(null);
+ }
+ return false;
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ return ((SelectionModel.Multi) selectionModel).deselect(itemId);
+ } else if (selectionModel instanceof SelectionModel.None) {
+ throw new IllegalStateException("Cannot deselect row '" + itemId
+ + "': Grid selection is disabled "
+ + "(the current selection model is "
+ + selectionModel.getClass().getName() + ").");
+ } else {
+ throw new IllegalStateException("Cannot deselect row '" + itemId
+ + "': Grid selection model does not implement "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + "(the current model is "
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Marks all items as unselected.
+ * true
if the selection state changed,
+ * false
if the itemId was already selected
+ * @throws IllegalStateException
+ * if the deselection was illegal. One such reason might be that
+ * the implementation requires one or more items to be selected
+ * at all times.
+ * @throws IllegalStateException
+ * if the selection model does not implement
+ * {@code SelectionModel.Single} or {code SelectionModel.Multi}
+ */
+ public boolean deselectAll() throws IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ if (getSelectedRow() != null) {
+ return deselect(getSelectedRow());
+ }
+ return false;
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ return ((SelectionModel.Multi) selectionModel).deselectAll();
+ } else if (selectionModel instanceof SelectionModel.None) {
+ throw new IllegalStateException(
+ "Cannot deselect all rows" + ": Grid selection is disabled "
+ + "(the current selection model is "
+ + selectionModel.getClass().getName() + ").");
+ } else {
+ throw new IllegalStateException("Cannot deselect all rows:"
+ + " Grid selection model does not implement "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + "(the current model is "
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Fires a selection change event.
+ * null
to
+ * remove a previously set generator
+ */
+ public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
+ this.cellStyleGenerator = cellStyleGenerator;
+ datasourceExtension.refreshCache();
+ }
+
+ /**
+ * Gets the style generator that is used for generating styles for cells
+ *
+ * @return the cell style generator, or null
if no generator is
+ * set
+ */
+ public CellStyleGenerator getCellStyleGenerator() {
+ return cellStyleGenerator;
+ }
+
+ /**
+ * Sets the style generator that is used for generating styles for rows
+ *
+ * @param rowStyleGenerator
+ * the row style generator to set, or null
to remove
+ * a previously set generator
+ */
+ public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) {
+ this.rowStyleGenerator = rowStyleGenerator;
+ datasourceExtension.refreshCache();
+ }
+
+ /**
+ * Gets the style generator that is used for generating styles for rows
+ *
+ * @return the row style generator, or null
if no generator is
+ * set
+ */
+ public RowStyleGenerator getRowStyleGenerator() {
+ return rowStyleGenerator;
+ }
+
+ /**
+ * Adds a row to the underlying container. The order of the parameters
+ * should match the current visible column order.
+ * null
.
+ * @return the item id of the new row
+ * @throws IllegalArgumentException
+ * if values is null
+ * @throws IllegalArgumentException
+ * if its length does not match the number of visible columns
+ * @throws IllegalArgumentException
+ * if a parameter value is not an instance of the corresponding
+ * property type
+ * @throws UnsupportedOperationException
+ * if the container does not support adding new items
+ */
+ public Object addRow(Object... values) {
+ if (values == null) {
+ throw new IllegalArgumentException("Values cannot be null");
+ }
+
+ Indexed dataSource = getContainerDataSource();
+ Listtrue
to enable the feature, false
+ * otherwise
+ * @throws IllegalStateException
+ * if an item is currently being edited
+ *
+ * @see #getEditedItemId()
+ */
+ public void setEditorEnabled(boolean isEnabled)
+ throws IllegalStateException {
+ if (isEditorActive()) {
+ throw new IllegalStateException(
+ "Cannot disable the editor while an item ("
+ + getEditedItemId() + ") is being edited");
+ }
+ if (isEditorEnabled() != isEnabled) {
+ getState().editorEnabled = isEnabled;
+ }
+ }
+
+ /**
+ * Checks whether the item editor UI is enabled for this grid.
+ *
+ * @return true
iff the editor is enabled for this grid
+ *
+ * @see #setEditorEnabled(boolean)
+ * @see #getEditedItemId()
+ */
+ public boolean isEditorEnabled() {
+ return getState(false).editorEnabled;
+ }
+
+ /**
+ * Gets the id of the item that is currently being edited.
+ *
+ * @return the id of the item that is currently being edited, or
+ * null
if no item is being edited at the moment
+ */
+ public Object getEditedItemId() {
+ return editedItemId;
+ }
+
+ /**
+ * Gets the field group that is backing the item editor of this grid.
+ *
+ * @return the backing field group
+ */
+ public FieldGroup getEditorFieldGroup() {
+ return editorFieldGroup;
+ }
+
+ /**
+ * Sets the field group that is backing the item editor of this grid.
+ *
+ * @param fieldGroup
+ * the backing field group
+ *
+ * @throws IllegalStateException
+ * if the editor is currently active
+ */
+ public void setEditorFieldGroup(FieldGroup fieldGroup) {
+ if (isEditorActive()) {
+ throw new IllegalStateException(
+ "Cannot change field group while an item ("
+ + getEditedItemId() + ") is being edited");
+ }
+ editorFieldGroup = fieldGroup;
+ }
+
+ /**
+ * Returns whether an item is currently being edited in the editor.
+ *
+ * @return true iff the editor is open
+ */
+ public boolean isEditorActive() {
+ return editorActive;
+ }
+
+ private void checkColumnExists(Object propertyId) {
+ if (getColumn(propertyId) == null) {
+ throw new IllegalArgumentException(
+ "There is no column with the property id " + propertyId);
+ }
+ }
+
+ private Field> getEditorField(Object propertyId) {
+ checkColumnExists(propertyId);
+
+ if (!getColumn(propertyId).isEditable()) {
+ return null;
+ }
+
+ Field> editor = editorFieldGroup.getField(propertyId);
+
+ try {
+ if (editor == null) {
+ editor = editorFieldGroup.buildAndBind(propertyId);
+ }
+ } finally {
+ if (editor == null) {
+ editor = editorFieldGroup.getField(propertyId);
+ }
+
+ if (editor != null && editor.getParent() != Grid.this) {
+ assert editor.getParent() == null;
+ editor.setParent(this);
+ }
+ }
+ return editor;
+ }
+
+ /**
+ * Opens the editor interface for the provided item. Scrolls the Grid to
+ * bring the item to view if it is not already visible.
+ *
+ * Note that any cell content rendered by a WidgetRenderer will not be
+ * visible in the editor row.
+ *
+ * @param itemId
+ * the id of the item to edit
+ * @throws IllegalStateException
+ * if the editor is not enabled or already editing an item in
+ * buffered mode
+ * @throws IllegalArgumentException
+ * if the {@code itemId} is not in the backing container
+ * @see #setEditorEnabled(boolean)
+ */
+ public void editItem(Object itemId)
+ throws IllegalStateException, IllegalArgumentException {
+ if (!isEditorEnabled()) {
+ throw new IllegalStateException("Item editor is not enabled");
+ } else if (isEditorBuffered() && editedItemId != null) {
+ throw new IllegalStateException("Editing item " + itemId
+ + " failed. Item editor is already editing item "
+ + editedItemId);
+ } else if (!getContainerDataSource().containsId(itemId)) {
+ throw new IllegalArgumentException("Item with id " + itemId
+ + " not found in current container");
+ }
+ editedItemId = itemId;
+ getEditorRpc().bind(getContainerDataSource().indexOfId(itemId));
+ }
+
+ protected void doEditItem() {
+ Item item = getContainerDataSource().getItem(editedItemId);
+
+ editorFieldGroup.setItemDataSource(item);
+
+ for (Column column : getColumns()) {
+ column.getState().editorConnector = getEditorField(
+ column.getPropertyId());
+ }
+
+ editorActive = true;
+ // Must ensure that all fields, recursively, are sent to the client
+ // This is needed because the fields are hidden using isRendered
+ for (Field> f : getEditorFields()) {
+ f.markAsDirtyRecursive();
+ }
+
+ if (datasource instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) datasource)
+ .addItemSetChangeListener(editorClosingItemSetListener);
+ }
+ }
+
+ private void setEditorField(Object propertyId, Field> field) {
+ checkColumnExists(propertyId);
+
+ Field> oldField = editorFieldGroup.getField(propertyId);
+ if (oldField != null) {
+ editorFieldGroup.unbind(oldField);
+ oldField.setParent(null);
+ }
+
+ if (field != null) {
+ field.setParent(this);
+ editorFieldGroup.bind(field, propertyId);
+ }
+ }
+
+ /**
+ * Saves all changes done to the bound fields.
+ * true
).
+ *
+ * @since 7.6
+ * @param editorBuffered
+ * true
to enable buffered editor,
+ * false
to disable it
+ * @throws IllegalStateException
+ * If editor is active while attempting to change the buffered
+ * mode.
+ */
+ public void setEditorBuffered(boolean editorBuffered)
+ throws IllegalStateException {
+ if (isEditorActive()) {
+ throw new IllegalStateException(
+ "Can't change editor unbuffered mode while editor is active.");
+ }
+ getState().editorBuffered = editorBuffered;
+ editorFieldGroup.setBuffered(editorBuffered);
+ }
+
+ /**
+ * Gets the buffered editor mode.
+ *
+ * @since 7.6
+ * @return true
if buffered editor is enabled,
+ * false
otherwise
+ */
+ public boolean isEditorBuffered() {
+ return getState(false).editorBuffered;
+ }
+
+ @Override
+ public void addItemClickListener(ItemClickListener listener) {
+ addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener, ItemClickEvent.ITEM_CLICK_METHOD);
+ }
+
+ @Override
+ @Deprecated
+ public void addListener(ItemClickListener listener) {
+ addItemClickListener(listener);
+ }
+
+ @Override
+ public void removeItemClickListener(ItemClickListener listener) {
+ removeListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener);
+ }
+
+ @Override
+ @Deprecated
+ public void removeListener(ItemClickListener listener) {
+ removeItemClickListener(listener);
+ }
+
+ /**
+ * Requests that the column widths should be recalculated.
+ * null
;
+ */
+ public void setDetailsGenerator(DetailsGenerator detailsGenerator)
+ throws IllegalArgumentException {
+ detailComponentManager.setDetailsGenerator(detailsGenerator);
+ }
+
+ /**
+ * Gets the current details generator for row details.
+ *
+ * @since 7.5.0
+ * @return the detailsGenerator the current details generator
+ */
+ public DetailsGenerator getDetailsGenerator() {
+ return detailComponentManager.getDetailsGenerator();
+ }
+
+ /**
+ * Shows or hides the details for a specific item.
+ *
+ * @since 7.5.0
+ * @param itemId
+ * the id of the item for which to set details visibility
+ * @param visible
+ * true
to show the details, or false
+ * to hide them
+ */
+ public void setDetailsVisible(Object itemId, boolean visible) {
+ detailComponentManager.setDetailsVisible(itemId, visible);
+ }
+
+ /**
+ * Checks whether details are visible for the given item.
+ *
+ * @since 7.5.0
+ * @param itemId
+ * the id of the item for which to check details visibility
+ * @return true
iff the details are visible
+ */
+ public boolean isDetailsVisible(Object itemId) {
+ return detailComponentManager.isDetailsVisible(itemId);
+ }
+
+ private static SelectionMode getDefaultSelectionMode() {
+ return SelectionMode.SINGLE;
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext context) {
+ super.readDesign(design, context);
+
+ Attributes attrs = design.attributes();
+ if (attrs.hasKey("editable")) {
+ setEditorEnabled(DesignAttributeHandler.readAttribute("editable",
+ attrs, boolean.class));
+ }
+ if (attrs.hasKey("rows")) {
+ setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
+ double.class));
+ setHeightMode(HeightMode.ROW);
+ }
+ if (attrs.hasKey("selection-mode")) {
+ setSelectionMode(DesignAttributeHandler.readAttribute(
+ "selection-mode", attrs, SelectionMode.class));
+ }
+
+ if (design.children().size() > 0) {
+ if (design.children().size() > 1
+ || !design.child(0).tagName().equals("table")) {
+ throw new DesignException(
+ "Grid needs to have a table element as its only child");
+ }
+ Element table = design.child(0);
+
+ Elements colgroups = table.getElementsByTag("colgroup");
+ if (colgroups.size() != 1) {
+ throw new DesignException(
+ "Table element in declarative Grid needs to have a"
+ + " colgroup defining the columns used in Grid");
+ }
+
+ int i = 0;
+ for (Element col : colgroups.get(0).getElementsByTag("col")) {
+ String propertyId = DesignAttributeHandler.readAttribute(
+ "property-id", col.attributes(), "property-" + i,
+ String.class);
+ addColumn(propertyId, String.class).readDesign(col, context);
+ ++i;
+ }
+
+ for (Element child : table.children()) {
+ if (child.tagName().equals("thead")) {
+ header.readDesign(child, context);
+ } else if (child.tagName().equals("tbody")) {
+ for (Element row : child.children()) {
+ Elements cells = row.children();
+ Object[] data = new String[cells.size()];
+ for (int c = 0; c < cells.size(); ++c) {
+ data[c] = cells.get(c).html();
+ }
+ addRow(data);
+ }
+
+ // Since inline data is used, set HTML renderer for columns
+ for (Column c : getColumns()) {
+ c.setRenderer(new HtmlRenderer());
+ }
+ } else if (child.tagName().equals("tfoot")) {
+ footer.readDesign(child, context);
+ }
+ }
+ }
+
+ // Read frozen columns after columns are read.
+ if (attrs.hasKey("frozen-columns")) {
+ setFrozenColumnCount(DesignAttributeHandler
+ .readAttribute("frozen-columns", attrs, int.class));
+ }
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext context) {
+ super.writeDesign(design, context);
+
+ Attributes attrs = design.attributes();
+ Grid def = context.getDefaultInstance(this);
+
+ DesignAttributeHandler.writeAttribute("editable", attrs,
+ isEditorEnabled(), def.isEditorEnabled(), boolean.class);
+
+ DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
+ getFrozenColumnCount(), def.getFrozenColumnCount(), int.class);
+
+ if (getHeightMode() == HeightMode.ROW) {
+ DesignAttributeHandler.writeAttribute("rows", attrs,
+ getHeightByRows(), def.getHeightByRows(), double.class);
+ }
+
+ SelectionMode selectionMode = null;
+
+ if (selectionModel.getClass().equals(SingleSelectionModel.class)) {
+ selectionMode = SelectionMode.SINGLE;
+ } else if (selectionModel.getClass()
+ .equals(MultiSelectionModel.class)) {
+ selectionMode = SelectionMode.MULTI;
+ } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
+ selectionMode = SelectionMode.NONE;
+ }
+
+ assert selectionMode != null : "Unexpected selection model "
+ + selectionModel.getClass().getName();
+
+ DesignAttributeHandler.writeAttribute("selection-mode", attrs,
+ selectionMode, getDefaultSelectionMode(), SelectionMode.class);
+
+ if (columns.isEmpty()) {
+ // Empty grid. Structure not needed.
+ return;
+ }
+
+ // Do structure.
+ Element tableElement = design.appendElement("table");
+ Element colGroup = tableElement.appendElement("colgroup");
+
+ Listjava.util.Date
.
- * DateField
extends LegacyAbstractField
it
- * implements the {@link com.vaadin.data.Buffered}interface.
- * DateField
is in write-through mode by default, so
- * {@link com.vaadin.v7.ui.LegacyAbstractField#setWriteThrough(boolean)}must
- * be called to enable buffering.
- * DateField
with no caption.
- */
- public LegacyDateField() {
- }
-
- /**
- * Constructs an empty DateField
with caption.
- *
- * @param caption
- * the caption of the datefield.
- */
- public LegacyDateField(String caption) {
- setCaption(caption);
- }
-
- /**
- * Constructs a new DateField
that's bound to the specified
- * Property
and has the given caption String
.
- *
- * @param caption
- * the caption String
for the editor.
- * @param dataSource
- * the Property to be edited with this editor.
- */
- public LegacyDateField(String caption, Property dataSource) {
- this(dataSource);
- setCaption(caption);
- }
-
- /**
- * Constructs a new DateField
that's bound to the specified
- * Property
and has no caption.
- *
- * @param dataSource
- * the Property to be edited with this editor.
- */
- public LegacyDateField(Property dataSource)
- throws IllegalArgumentException {
- if (!Date.class.isAssignableFrom(dataSource.getType())) {
- throw new IllegalArgumentException(
- "Can't use " + dataSource.getType().getName()
- + " typed property as datasource");
- }
-
- setPropertyDataSource(dataSource);
- }
-
- /**
- * Constructs a new DateField
with the given caption and
- * initial text contents. The editor constructed this way will not be bound
- * to a Property unless
- * {@link com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)}
- * is called to bind it.
- *
- * @param caption
- * the caption String
for the editor.
- * @param value
- * the Date value.
- */
- public LegacyDateField(String caption, Date value) {
- setValue(value);
- setCaption(caption);
- }
-
- /* Component basic features */
-
- /*
- * Paints this component. Don't add a JavaDoc comment here, we use the
- * default documentation from implemented interface.
- */
- @Override
- public void paintContent(PaintTarget target) throws PaintException {
-
- // Adds the locale as attribute
- final Locale l = getLocale();
- if (l != null) {
- target.addAttribute("locale", l.toString());
- }
-
- if (getDateFormat() != null) {
- target.addAttribute("format", dateFormat);
- }
-
- if (!isLenient()) {
- target.addAttribute("strict", true);
- }
-
- target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
- isShowISOWeekNumbers());
- target.addAttribute("parsable", uiHasValidDateString);
- /*
- * TODO communicate back the invalid date string? E.g. returning back to
- * app or refresh.
- */
-
- // Gets the calendar
- final Calendar calendar = getCalendar();
- final Date currentDate = getValue();
-
- // Only paint variables for the resolution and up, e.g. Resolution DAY
- // paints DAY,MONTH,YEAR
- for (Resolution res : Resolution
- .getResolutionsHigherOrEqualTo(resolution)) {
- int value = -1;
- if (currentDate != null) {
- value = calendar.get(res.getCalendarField());
- if (res == Resolution.MONTH) {
- // Calendar month is zero based
- value++;
- }
- }
- target.addVariable(this, variableNameForResolution.get(res), value);
- }
- }
-
- @Override
- protected boolean shouldHideErrors() {
- return super.shouldHideErrors() && uiHasValidDateString;
- }
-
- @Override
- protected TextualDateFieldState getState() {
- return (TextualDateFieldState) super.getState();
- }
-
- @Override
- protected TextualDateFieldState getState(boolean markAsDirty) {
- return (TextualDateFieldState) super.getState(markAsDirty);
- }
-
- /**
- * Sets the start range for this component. If the value is set before this
- * date (taking the resolution into account), the component will not
- * validate. If startDate
is set to null
, any
- * value before endDate
will be accepted by the range
- *
- * @param startDate
- * - the allowed range's start date
- */
- public void setRangeStart(Date startDate) {
- if (startDate != null && getState().rangeEnd != null
- && startDate.after(getState().rangeEnd)) {
- throw new IllegalStateException(
- "startDate cannot be later than endDate");
- }
-
- // Create a defensive copy against issues when using java.sql.Date (and
- // also against mutable Date).
- getState().rangeStart = startDate != null
- ? new Date(startDate.getTime()) : null;
- updateRangeValidator();
- }
-
- /**
- * Sets the current error message if the range validation fails.
- *
- * @param dateOutOfRangeMessage
- * - Localizable message which is shown when value (the date) is
- * set outside allowed range
- */
- public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
- this.dateOutOfRangeMessage = dateOutOfRangeMessage;
- updateRangeValidator();
- }
-
- /**
- * Gets the end range for a certain resolution. The range is inclusive, so
- * if rangeEnd is set to zero milliseconds past year n and resolution is set
- * to YEAR, any date in year n will be accepted. Resolutions lower than DAY
- * will be interpreted on a DAY level. That is, everything below DATE is
- * cleared
- *
- * @param forResolution
- * - the range conforms to the resolution
- * @return
- */
- private Date getRangeEnd(Resolution forResolution) {
- // We need to set the correct resolution for the dates,
- // otherwise the range validator will complain
-
- Date rangeEnd = getState(false).rangeEnd;
- if (rangeEnd == null) {
- return null;
- }
-
- Calendar endCal = Calendar.getInstance();
- endCal.setTime(rangeEnd);
-
- if (forResolution == Resolution.YEAR) {
- // Adding one year (minresolution) and clearing the rest.
- endCal.set(endCal.get(Calendar.YEAR) + 1, 0, 1, 0, 0, 0);
- } else if (forResolution == Resolution.MONTH) {
- // Adding one month (minresolution) and clearing the rest.
- endCal.set(endCal.get(Calendar.YEAR),
- endCal.get(Calendar.MONTH) + 1, 1, 0, 0, 0);
- } else {
- endCal.set(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
- endCal.get(Calendar.DATE) + 1, 0, 0, 0);
- }
- // removing one millisecond will now get the endDate to return to
- // current resolution's set time span (year or month)
- endCal.set(Calendar.MILLISECOND, -1);
- return endCal.getTime();
- }
-
- /**
- * Gets the start range for a certain resolution. The range is inclusive, so
- * if rangeStart
is set to one millisecond before year n and
- * resolution is set to YEAR, any date in year n - 1 will be accepted.
- * Lowest supported resolution is DAY.
- *
- * @param forResolution
- * - the range conforms to the resolution
- * @return
- */
- private Date getRangeStart(Resolution forResolution) {
- if (getState(false).rangeStart == null) {
- return null;
- }
- Calendar startCal = Calendar.getInstance();
- startCal.setTime(getState(false).rangeStart);
-
- if (forResolution == Resolution.YEAR) {
- startCal.set(startCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
- } else if (forResolution == Resolution.MONTH) {
- startCal.set(startCal.get(Calendar.YEAR),
- startCal.get(Calendar.MONTH), 1, 0, 0, 0);
- } else {
- startCal.set(startCal.get(Calendar.YEAR),
- startCal.get(Calendar.MONTH), startCal.get(Calendar.DATE),
- 0, 0, 0);
- }
-
- startCal.set(Calendar.MILLISECOND, 0);
- return startCal.getTime();
- }
-
- private void updateRangeValidator() {
- if (currentRangeValidator != null) {
- removeValidator(currentRangeValidator);
- currentRangeValidator = null;
- }
- if (getRangeStart() != null || getRangeEnd() != null) {
- currentRangeValidator = new LegacyDateRangeValidator(
- dateOutOfRangeMessage, getRangeStart(resolution),
- getRangeEnd(resolution), null);
- addValidator(currentRangeValidator);
- }
- }
-
- /**
- * Sets the end range for this component. If the value is set after this
- * date (taking the resolution into account), the component will not
- * validate. If endDate
is set to null
, any value
- * after startDate
will be accepted by the range.
- *
- * @param endDate
- * - the allowed range's end date (inclusive, based on the
- * current resolution)
- */
- public void setRangeEnd(Date endDate) {
- if (endDate != null && getState().rangeStart != null
- && getState().rangeStart.after(endDate)) {
- throw new IllegalStateException(
- "endDate cannot be earlier than startDate");
- }
-
- // Create a defensive copy against issues when using java.sql.Date (and
- // also against mutable Date).
- getState().rangeEnd = endDate != null ? new Date(endDate.getTime())
- : null;
- updateRangeValidator();
- }
-
- /**
- * Returns the precise rangeStart used.
- *
- * @param startDate
- *
- */
- public Date getRangeStart() {
- return getState(false).rangeStart;
- }
-
- /**
- * Returns the precise rangeEnd used.
- *
- * @param startDate
- */
- public Date getRangeEnd() {
- return getState(false).rangeEnd;
- }
-
- /*
- * Invoked when a variable of the component changes. Don't add a JavaDoc
- * comment here, we use the default documentation from implemented
- * interface.
- */
- @Override
- public void changeVariables(Object source, MapCalendar.getInstance
- * is used.
- *
- * @return the Calendar.
- * @see #setCalendar(Calendar)
- */
- private Calendar getCalendar() {
-
- // Makes sure we have an calendar instance
- if (calendar == null) {
- calendar = Calendar.getInstance();
- // Start by a zeroed calendar to avoid having values for lower
- // resolution variables e.g. time when resolution is day
- int min, field;
- for (Resolution r : Resolution
- .getResolutionsLowerThan(resolution)) {
- field = r.getCalendarField();
- min = calendar.getActualMinimum(field);
- calendar.set(field, min);
- }
- calendar.set(Calendar.MILLISECOND, 0);
- }
-
- // Clone the instance
- final Calendar newCal = (Calendar) calendar.clone();
-
- final TimeZone currentTimeZone = getTimeZone();
- if (currentTimeZone != null) {
- newCal.setTimeZone(currentTimeZone);
- }
-
- final Date currentDate = getValue();
- if (currentDate != null) {
- newCal.setTime(currentDate);
- }
- return newCal;
- }
-
- /**
- * Sets formatting used by some component implementations. See
- * {@link SimpleDateFormat} for format details.
- *
- * By default it is encouraged to used default formatting defined by Locale,
- * but due some JVM bugs it is sometimes necessary to use this method to
- * override formatting. See Vaadin issue #2200.
- *
- * @param dateFormat
- * the dateFormat to set
- *
- * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
- */
- public void setDateFormat(String dateFormat) {
- this.dateFormat = dateFormat;
- markAsDirty();
- }
-
- /**
- * Returns a format string used to format date value on client side or null
- * if default formatting from {@link Component#getLocale()} is used.
- *
- * @return the dateFormat
- */
- public String getDateFormat() {
- return dateFormat;
- }
-
- /**
- * Specifies whether or not date/time interpretation in component is to be
- * lenient.
- *
- * @see Calendar#setLenient(boolean)
- * @see #isLenient()
- *
- * @param lenient
- * true if the lenient mode is to be turned on; false if it is to
- * be turned off.
- */
- public void setLenient(boolean lenient) {
- this.lenient = lenient;
- markAsDirty();
- }
-
- /**
- * Returns whether date/time interpretation is to be lenient.
- *
- * @see #setLenient(boolean)
- *
- * @return true if the interpretation mode of this calendar is lenient;
- * false otherwise.
- */
- public boolean isLenient() {
- return lenient;
- }
-
- @Override
- public void addFocusListener(FocusListener listener) {
- addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
- FocusListener.focusMethod);
- }
-
- @Override
- public void removeFocusListener(FocusListener listener) {
- removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
- }
-
- @Override
- public void addBlurListener(BlurListener listener) {
- addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
- BlurListener.blurMethod);
- }
-
- @Override
- public void removeBlurListener(BlurListener listener) {
- removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
- }
-
- /**
- * Checks whether ISO 8601 week numbers are shown in the date selector.
- *
- * @return true if week numbers are shown, false otherwise.
- */
- public boolean isShowISOWeekNumbers() {
- return showISOWeekNumbers;
- }
-
- /**
- * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
- * 8601 defines that a week always starts with a Monday so the week numbers
- * are only shown if this is the case.
- *
- * @param showWeekNumbers
- * true if week numbers should be shown, false otherwise.
- */
- public void setShowISOWeekNumbers(boolean showWeekNumbers) {
- showISOWeekNumbers = showWeekNumbers;
- markAsDirty();
- }
-
- /**
- * Validates the current value against registered validators if the field is
- * not empty. Note that DateField is considered empty (value == null) and
- * invalid if it contains text typed in by the user that couldn't be parsed
- * into a Date value.
- *
- * @see com.vaadin.v7.ui.LegacyAbstractField#validate()
- */
- @Override
- public void validate() throws InvalidValueException {
- /*
- * To work properly in form we must throw exception if there is
- * currently a parsing error in the datefield. Parsing error is kind of
- * an internal validator.
- */
- if (!uiHasValidDateString) {
- throw new UnparsableDateString(currentParseErrorMessage);
- }
- super.validate();
- }
-
- /**
- * Return the error message that is shown if the user inputted value can't
- * be parsed into a Date object. If
- * {@link #handleUnparsableDateString(String)} is overridden and it throws a
- * custom exception, the message returned by
- * {@link Exception#getLocalizedMessage()} will be used instead of the value
- * returned by this method.
- *
- * @see #setParseErrorMessage(String)
- *
- * @return the error message that the DateField uses when it can't parse the
- * textual input from user to a Date object
- */
- public String getParseErrorMessage() {
- return defaultParseErrorMessage;
- }
-
- /**
- * Sets the default error message used if the DateField cannot parse the
- * text input by user to a Date field. Note that if the
- * {@link #handleUnparsableDateString(String)} method is overridden, the
- * localized message from its exception is used.
- *
- * @see #getParseErrorMessage()
- * @see #handleUnparsableDateString(String)
- * @param parsingErrorMessage
- */
- public void setParseErrorMessage(String parsingErrorMessage) {
- defaultParseErrorMessage = parsingErrorMessage;
- }
-
- /**
- * Sets the time zone used by this date field. The time zone is used to
- * convert the absolute time in a Date object to a logical time displayed in
- * the selector and to convert the select time back to a Date object.
- *
- * If no time zone has been set, the current default time zone returned by
- * {@code TimeZone.getDefault()} is used.
- *
- * @see #getTimeZone()
- * @param timeZone
- * the time zone to use for time calculations.
- */
- public void setTimeZone(TimeZone timeZone) {
- this.timeZone = timeZone;
- markAsDirty();
- }
-
- /**
- * Gets the time zone used by this field. The time zone is used to convert
- * the absolute time in a Date object to a logical time displayed in the
- * selector and to convert the select time back to a Date object.
- *
- * If {@code null} is returned, the current default time zone returned by
- * {@code TimeZone.getDefault()} is used.
- *
- * @return the current time zone
- */
- public TimeZone getTimeZone() {
- return timeZone;
- }
-
- public static class UnparsableDateString
- extends Validator.InvalidValueException {
-
- public UnparsableDateString(String message) {
- super(message);
- }
-
- }
-
- @Override
- public void readDesign(Element design, DesignContext designContext) {
- super.readDesign(design, designContext);
- if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
- Date date = DesignAttributeHandler.getFormatter()
- .parse(design.attr("value"), Date.class);
- // formatting will return null if it cannot parse the string
- if (date == null) {
- Logger.getLogger(LegacyDateField.class.getName()).info(
- "cannot parse " + design.attr("value") + " as date");
- }
- this.setValue(date, false, true);
- }
- }
-
- @Override
- public void writeDesign(Element design, DesignContext designContext) {
- super.writeDesign(design, designContext);
- if (getValue() != null) {
- design.attr("value",
- DesignAttributeHandler.getFormatter().format(getValue()));
- }
- }
-
- /**
- * Returns current date-out-of-range error message.
- *
- * @see #setDateOutOfRangeMessage(String)
- * @since 7.4
- * @return Current error message for dates out of range.
- */
- public String getDateOutOfRangeMessage() {
- return dateOutOfRangeMessage;
- }
-
-}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/LegacyInlineDateField.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/LegacyInlineDateField.java
deleted file mode 100644
index 4e1ad7e997..0000000000
--- a/compatibility-server/src/main/java/com/vaadin/v7/ui/LegacyInlineDateField.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * 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 com.vaadin.v7.ui;
-
-import java.util.Date;
-
-import com.vaadin.data.Property;
-
-/**
- * RichTextArea
with no caption.
+ */
+ public RichTextArea() {
+ setValue("");
+ }
+
+ /**
+ *
+ * Constructs an empty RichTextArea
with the given caption.
+ *
+ * @param caption
+ * the caption for the editor.
+ */
+ public RichTextArea(String caption) {
+ this();
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new RichTextArea
that's bound to the specified
+ * Property
and has no caption.
+ *
+ * @param dataSource
+ * the data source for the editor value
+ */
+ public RichTextArea(Property dataSource) {
+ setPropertyDataSource(dataSource);
+ }
+
+ /**
+ * Constructs a new RichTextArea
that's bound to the specified
+ * Property
and has the given caption.
+ *
+ * @param caption
+ * the caption for the editor.
+ * @param dataSource
+ * the data source for the editor value
+ */
+ public RichTextArea(String caption, Property dataSource) {
+ this(dataSource);
+ setCaption(caption);
+ }
+
+ /**
+ * Constructs a new RichTextArea
with the given caption and
+ * initial text contents.
+ *
+ * @param caption
+ * the caption for the editor.
+ * @param value
+ * the initial text content of the editor.
+ */
+ public RichTextArea(String caption, String value) {
+ setValue(value);
+ setCaption(caption);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (selectAll) {
+ target.addAttribute("selectAll", true);
+ selectAll = false;
+ }
+
+ // Adds the content as variable
+ String value = getValue();
+ if (value == null) {
+ value = getNullRepresentation();
+ }
+ if (value == null) {
+ throw new IllegalStateException(
+ "Null values are not allowed if the null-representation is null");
+ }
+ target.addVariable(this, "text", value);
+
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ // IE6 cannot support multi-classname selectors properly
+ // TODO Can be optimized now that support for I6 is dropped
+ if (readOnly) {
+ addStyleName("v-richtextarea-readonly");
+ } else {
+ removeStyleName("v-richtextarea-readonly");
+ }
+ }
+
+ /**
+ * Selects all text in the rich text area. As a side effect, focuses the
+ * rich text area.
+ *
+ * @since 6.5
+ */
+ public void selectAll() {
+ /*
+ * Set selection range functionality is currently being
+ * planned/developed for GWT RTA. Only selecting all is currently
+ * supported. Consider moving selectAll and other selection related
+ * functions to AbstractTextField at that point to share the
+ * implementation. Some third party components extending
+ * AbstractTextField might however not want to support them.
+ */
+ selectAll = true;
+ focus();
+ markAsDirty();
+ }
+
+ @Override
+ public void changeVariables(Object source, MapSelect
component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
+ * Table
is used for representing data or components in a pageable
+ * and selectable table.
+ * toString()
is
+ * used instead.
+ */
+ EXPLICIT_DEFAULTS_ID(ItemCaptionMode.EXPLICIT_DEFAULTS_ID),
+ /**
+ * Row caption mode: Item captions are explicitly specified.
+ */
+ EXPLICIT(ItemCaptionMode.EXPLICIT),
+ /**
+ * Row caption mode: Only icons are shown, the captions are hidden.
+ */
+ ICON_ONLY(ItemCaptionMode.ICON_ONLY),
+ /**
+ * Row caption mode: Item captions are read from property specified with
+ * {@link #setItemCaptionPropertyId(Object)} .
+ */
+ PROPERTY(ItemCaptionMode.PROPERTY);
+
+ ItemCaptionMode mode;
+
+ private RowHeaderMode(ItemCaptionMode mode) {
+ this.mode = mode;
+ }
+
+ public ItemCaptionMode getItemCaptionMode() {
+ return mode;
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#HIDDEN} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_HIDDEN = RowHeaderMode.HIDDEN;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#ID} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ID = RowHeaderMode.ID;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#ITEM} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ITEM = RowHeaderMode.ITEM;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#INDEX} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_INDEX = RowHeaderMode.INDEX;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = RowHeaderMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_EXPLICIT = RowHeaderMode.EXPLICIT;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#ICON_ONLY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_ICON_ONLY = RowHeaderMode.ICON_ONLY;
+
+ /**
+ * @deprecated As of 7.0, use {@link RowHeaderMode#PROPERTY} instead
+ */
+ @Deprecated
+ public static final RowHeaderMode ROW_HEADER_MODE_PROPERTY = RowHeaderMode.PROPERTY;
+
+ /**
+ * The default rate that table caches rows for smooth scrolling.
+ */
+ private static final double CACHE_RATE_DEFAULT = 2;
+
+ private static final String ROW_HEADER_COLUMN_KEY = "0";
+ private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new UniqueSerializable() {
+ };
+
+ /**
+ * How layout manager should behave when measuring Table's child components
+ */
+ private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS;
+
+ /* Private table extensions to Select */
+
+ /**
+ * True if column collapsing is allowed.
+ */
+ private boolean columnCollapsingAllowed = false;
+
+ /**
+ * True if reordering of columns is allowed on the client side.
+ */
+ private boolean columnReorderingAllowed = false;
+
+ /**
+ * Keymapper for column ids.
+ */
+ private final KeyMapper
+ *
+ * The alignments default to {@link Align#LEFT}: any null values are
+ * rendered as align lefts.
+ *
+ *
+ * The alignments default to {@link Align#LEFT}
+ * true
, the column can only be actually collapsed (via
+ * UI or with {@link #setColumnCollapsed(Object, boolean)
+ * setColumnCollapsed()}) if {@link #isColumnCollapsingAllowed()} is also
+ * true.
+ *
+ * @return true if the column can be collapsed; false otherwise.
+ */
+ public boolean isColumnCollapsible(Object propertyId) {
+ return !noncollapsibleColumns.contains(propertyId);
+ }
+
+ /**
+ * Checks if column reordering is allowed.
+ *
+ * @return true if columns can be reordered; false otherwise.
+ */
+ public boolean isColumnReorderingAllowed() {
+ return columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering is allowed or not.
+ *
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed.
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ if (columnReorderingAllowed != this.columnReorderingAllowed) {
+ this.columnReorderingAllowed = columnReorderingAllowed;
+ markAsDirty();
+ }
+ }
+
+ /*
+ * Arranges visible columns according to given columnOrder. Silently ignores
+ * colimnId:s that are not visible columns, and keeps the internal order of
+ * visible columns left out of the ordering (trailing). Silently does
+ * nothing if columnReordering is not allowed.
+ */
+ private void setColumnOrder(Object[] columnOrder) {
+ if (columnOrder == null || !isColumnReorderingAllowed()) {
+ return;
+ }
+ final LinkedList
+ *
+ * The default value is {@link #ROW_HEADER_MODE_HIDDEN}
+ * toString()
+ * is used as row caption.
+ * toString()
+ * is used as row caption.
+ * toString()
is used as row header. If caption is explicitly
+ * specified, it overrides the id-caption.
+ * Container.Indexed
interface.
+ *
+ * All rows and columns are generated as visible using this method. If the
+ * new container contains properties that are not meant to be shown you
+ * should use {@link Table#setContainerDataSource(Container, Collection)}
+ * instead, especially if the table is editable.
+ * ItemId
from the Container.
+ *
+ * @see com.vaadin.v7.data.Container#removeItem(Object)
+ */
+
+ @Override
+ public boolean removeItem(Object itemId) {
+ final Object nextItemId = nextItemId(itemId);
+ final boolean ret = super.removeItem(itemId);
+ if (ret && (itemId != null)
+ && (itemId.equals(currentPageFirstItemId))) {
+ currentPageFirstItemId = nextItemId;
+ }
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a Property specified by the given Property ID from the Container.
+ *
+ * @see com.vaadin.v7.data.Container#removeContainerProperty(Object)
+ */
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ // If a visible property is removed, remove the corresponding column
+ visibleColumns.remove(propertyId);
+ columnAlignments.remove(propertyId);
+ columnIcons.remove(propertyId);
+ columnHeaders.remove(propertyId);
+ columnFooters.remove(propertyId);
+ // If a propertyValueConverter was defined for the property, remove it.
+ propertyValueConverters.remove(propertyId);
+
+ return super.removeContainerProperty(propertyId);
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the property.
+ * @param type
+ * the class of the property.
+ * @param defaultValue
+ * the default value given for all existing items.
+ * @see com.vaadin.v7.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ boolean visibleColAdded = false;
+ if (!visibleColumns.contains(propertyId)) {
+ visibleColumns.add(propertyId);
+ visibleColAdded = true;
+ }
+
+ if (!super.addContainerProperty(propertyId, type, defaultValue)) {
+ if (visibleColAdded) {
+ visibleColumns.remove(propertyId);
+ }
+ return false;
+ }
+ if (!(items instanceof Container.PropertySetChangeNotifier)) {
+ refreshRowCache();
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new property to the table and show it as a visible column.
+ *
+ * @param propertyId
+ * the Id of the property
+ * @param type
+ * the class of the property
+ * @param defaultValue
+ * the default value given for all existing items
+ * @param columnHeader
+ * the Explicit header of the column. If explicit header is not
+ * needed, this should be set null.
+ * @param columnIcon
+ * the Icon of the column. If icon is not needed, this should be
+ * set null.
+ * @param columnAlignment
+ * the Alignment of the column. Null implies align left.
+ * @throws UnsupportedOperationException
+ * if the operation is not supported.
+ * @see com.vaadin.v7.data.Container#addContainerProperty(Object, Class,
+ * Object)
+ */
+ public boolean addContainerProperty(Object propertyId, Class> type,
+ Object defaultValue, String columnHeader, Resource columnIcon,
+ Align columnAlignment) throws UnsupportedOperationException {
+ if (!this.addContainerProperty(propertyId, type, defaultValue)) {
+ return false;
+ }
+ setColumnAlignment(propertyId, columnAlignment);
+ setColumnHeader(propertyId, columnHeader);
+ setColumnIcon(propertyId, columnIcon);
+ return true;
+ }
+
+ /**
+ * Adds a generated column to the Table.
+ * true
if ascending, false
if descending.
+ */
+ public boolean isSortAscending() {
+ return sortAscending;
+ }
+
+ /**
+ * Sets the table in ascending order.
+ *
+ * @param ascending
+ * true
if ascending, false
if
+ * descending.
+ */
+ public void setSortAscending(boolean ascending) {
+ setSortAscending(ascending, true);
+ }
+
+ /**
+ * Internal method to set sort ascending. With doSort flag actual sort can
+ * be bypassed.
+ *
+ * @param ascending
+ * @param doSort
+ */
+ private void setSortAscending(boolean ascending, boolean doSort) {
+ if (sortAscending != ascending) {
+ sortAscending = ascending;
+ if (doSort) {
+ sort();
+ // Assures the visual refresh. This should not be necessary as
+ // sort() calls refreshRowCache
+ refreshRenderedCells();
+ }
+ }
+ }
+
+ /**
+ * Is sorting disabled altogether.
+ *
+ * True iff no sortable columns are given even in the case where data source
+ * would support this.
+ *
+ * @return True iff sorting is disabled.
+ * @deprecated As of 7.0, use {@link #isSortEnabled()} instead
+ */
+ @Deprecated
+ public boolean isSortDisabled() {
+ return !isSortEnabled();
+ }
+
+ /**
+ * Checks if sorting is enabled.
+ *
+ * @return true if sorting by the user is allowed, false otherwise
+ */
+ public boolean isSortEnabled() {
+ return sortEnabled;
+ }
+
+ /**
+ * Disables the sorting by the user altogether.
+ *
+ * @param sortDisabled
+ * True iff sorting is disabled.
+ * @deprecated As of 7.0, use {@link #setSortEnabled(boolean)} instead
+ */
+ @Deprecated
+ public void setSortDisabled(boolean sortDisabled) {
+ setSortEnabled(!sortDisabled);
+ }
+
+ /**
+ * Enables or disables sorting.
+ * element");
+ }
+ Element tr = elem.child(0);
+ Elements elems = tr.children();
+ Collection> propertyIds = visibleColumns;
+ if (elems.size() != propertyIds.size()) {
+ throw new DesignException(
+ "Table header and footer should contain as many items as there"
+ + " are columns in the Table.");
+ }
+ Iterator> propertyIt = propertyIds.iterator();
+ for (Element e : elems) {
+ String columnValue = DesignFormatter
+ .decodeFromTextNode(e.html());
+ Object propertyId = propertyIt.next();
+ if (header) {
+ setColumnHeader(propertyId, columnValue);
+ if (e.hasAttr("icon")) {
+ setColumnIcon(propertyId,
+ DesignAttributeHandler.readAttribute("icon",
+ e.attributes(), Resource.class));
+ }
+ } else {
+ setColumnFooter(propertyId, columnValue);
+ }
+ }
+ }
+ }
+
+ protected void readBody(Element design, DesignContext context) {
+ Element tbody = design.select("> table > tbody").first();
+ if (tbody == null) {
+ return;
+ }
+
+ Set