summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Wagner <wbadam@users.noreply.github.com>2017-07-03 13:36:17 +0200
committerHenri Sara <henri.sara@gmail.com>2017-07-03 14:36:17 +0300
commit6ecef502864ed201b468a68c00676beb401f21c7 (patch)
tree2f96341ebcb02bced9092e4d3196266ee2eb2740
parent4d085fd8b3589db4fbbdcd52643cee4b20f70e31 (diff)
downloadvaadin-framework-6ecef502864ed201b468a68c00676beb401f21c7.tar.gz
vaadin-framework-6ecef502864ed201b468a68c00676beb401f21c7.zip
Add criteria API to make it easier to set acceptance criteria for drag and drop (#9605)
Fixes #9600
-rw-r--r--client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java5
-rw-r--r--client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java42
-rw-r--r--server/src/main/java/com/vaadin/ui/dnd/DragSourceExtension.java83
-rw-r--r--server/src/main/java/com/vaadin/ui/dnd/DropTargetExtension.java148
-rw-r--r--server/src/main/java/com/vaadin/ui/dnd/event/DragEndEvent.java2
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java7
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java17
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/ComparisonOperator.java55
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java215
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Payload.java125
-rw-r--r--uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java28
11 files changed, 689 insertions, 38 deletions
diff --git a/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java
index 313c990074..b91b0aace0 100644
--- a/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java
+++ b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java
@@ -412,6 +412,11 @@ public class DragSourceExtensionConnector extends AbstractExtensionConnector {
for (String type : getState().types) {
orderedData.put(type, getState().data.get(type));
}
+
+ // Add payload for comparing against acceptance criteria
+ getState().payload.values().forEach(payload -> orderedData
+ .put(payload.getPayloadString(), payload.getValue()));
+
return orderedData;
}
diff --git a/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java b/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java
index 7e64b0d305..3791db53b4 100644
--- a/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java
+++ b/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java
@@ -17,8 +17,10 @@ package com.vaadin.client.extensions;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.DataTransfer;
@@ -34,6 +36,7 @@ import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.DropTargetRpc;
import com.vaadin.shared.ui.dnd.DropTargetState;
+import com.vaadin.shared.ui.dnd.criteria.Payload;
import com.vaadin.ui.dnd.DropTargetExtension;
import elemental.events.Event;
@@ -320,12 +323,43 @@ public class DropTargetExtensionConnector extends AbstractExtensionConnector {
// Currently Safari, Edge and IE don't follow the spec by allowing drop
// if those don't match
- if (getState().dropCriteria != null) {
- return executeScript(event, getState().dropCriteria);
+ // Allow by default when criteria not set
+ boolean allowed = true;
+
+ // Execute criteria script
+ if (getState().criteriaScript != null) {
+ allowed = executeScript(event, getState().criteriaScript);
+ }
+
+ // Execute criterion defined via API
+ if (allowed && getState().criteria != null && !getState().criteria
+ .isEmpty()) {
+
+ // Collect payload data types
+ Set<Payload> payloadSet = new HashSet<>();
+ JsArrayString typesJsArray = getTypes(event.getDataTransfer());
+ for (int i = 0; i < typesJsArray.length(); i++) {
+ String type = typesJsArray.get(i);
+
+ if (type.startsWith(Payload.ITEM_PREFIX)) {
+ payloadSet.add(Payload.parse(type));
+ }
+ }
+
+ // Compare payload against criteria
+ switch (getState().criteriaMatch) {
+ case ALL:
+ allowed = getState().criteria.stream()
+ .allMatch(criterion -> criterion.resolve(payloadSet));
+ break;
+ case ANY:
+ default:
+ allowed = getState().criteria.stream()
+ .anyMatch(criterion -> criterion.resolve(payloadSet));
+ }
}
- // Allow when criteria not set
- return true;
+ return allowed;
}
/**
diff --git a/server/src/main/java/com/vaadin/ui/dnd/DragSourceExtension.java b/server/src/main/java/com/vaadin/ui/dnd/DragSourceExtension.java
index 42c29f738a..d123700be9 100644
--- a/server/src/main/java/com/vaadin/ui/dnd/DragSourceExtension.java
+++ b/server/src/main/java/com/vaadin/ui/dnd/DragSourceExtension.java
@@ -23,10 +23,12 @@ import java.util.Objects;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Resource;
import com.vaadin.shared.Registration;
+import com.vaadin.shared.ui.dnd.criteria.ComparisonOperator;
import com.vaadin.shared.ui.dnd.DragSourceRpc;
import com.vaadin.shared.ui.dnd.DragSourceState;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.EffectAllowed;
+import com.vaadin.shared.ui.dnd.criteria.Payload;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.dnd.event.DragEndEvent;
import com.vaadin.ui.dnd.event.DragEndListener;
@@ -38,7 +40,7 @@ import com.vaadin.ui.dnd.event.DragStartListener;
* functionality.
*
* @param <T>
- * Type of the component to be extended.
+ * Type of the component to be extended.
* @author Vaadin Ltd
* @since 8.1
*/
@@ -58,7 +60,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* Extends {@code target} component and makes it a drag source.
*
* @param target
- * Component to be extended.
+ * Component to be extended.
*/
public DragSourceExtension(T target) {
super.extend(target);
@@ -125,7 +127,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* side. Fires the {@link DragEndEvent}.
*
* @param dropEffect
- * the drop effect on the dragend
+ * the drop effect on the dragend
*/
protected void onDragEnd(DropEffect dropEffect) {
DragEndEvent<T> event = new DragEndEvent<>(getParent(), dropEffect);
@@ -150,7 +152,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* equivalent to {@link EffectAllowed#ALL}.
*
* @param effect
- * Effects to allow for this draggable element. Cannot be {@code
+ * Effects to allow for this draggable element. Cannot be {@code
* null}.
*/
public void setEffectAllowed(EffectAllowed effect) {
@@ -294,6 +296,73 @@ public class DragSourceExtension<T extends AbstractComponent>
}
/**
+ * Sets payload for this drag source to use with acceptance criterion. The
+ * payload is transferred as data type in the data transfer object in the
+ * following format: {@code "v-item:string:key:value"}. The given value is
+ * compared to the criterion value when the drag source is dragged on top of
+ * a drop target that has the suitable criterion.
+ * <p>
+ * Note that setting payload in Internet Explorer 11 is not possible due to
+ * the browser's limitations.
+ *
+ * @param key
+ * key of the payload to be transferred
+ * @param value
+ * value of the payload to be transferred
+ * @see DropTargetExtension#setDropCriterion(String, String)
+ */
+ public void setPayload(String key, String value) {
+ setPayload(key, String.valueOf(value), Payload.ValueType.STRING);
+ }
+
+ /**
+ * Sets payload for this drag source to use with acceptance criterion. The
+ * payload is transferred as data type in the data transfer object in the
+ * following format: {@code "v-item:integer:key:value"}. The given value is
+ * compared to the criterion value when the drag source is dragged on top of
+ * a drop target that has the suitable criterion.
+ * <p>
+ * Note that setting payload in Internet Explorer 11 is not possible due to
+ * the browser's limitations.
+ *
+ * @param key
+ * key of the payload to be transferred
+ * @param value
+ * value of the payload to be transferred
+ * @see DropTargetExtension#setDropCriterion(String, ComparisonOperator,
+ * int)
+ */
+ public void setPayload(String key, int value) {
+ setPayload(key, String.valueOf(value), Payload.ValueType.INTEGER);
+ }
+
+ /**
+ * Sets payload for this drag source to use with acceptance criterion. The
+ * payload is transferred as data type in the data transfer object in the
+ * following format: {@code "v-item:double:key:value"}. The given value is
+ * compared to the criterion value when the drag source is dragged on top of
+ * a drop target that has the suitable criterion.
+ * <p>
+ * Note that setting payload in Internet Explorer 11 is not possible due to
+ * the browser's limitations.
+ *
+ * @param key
+ * key of the payload to be transferred
+ * @param value
+ * value of the payload to be transferred
+ * @see DropTargetExtension#setDropCriterion(String, ComparisonOperator,
+ * double)
+ */
+ public void setPayload(String key, double value) {
+ setPayload(key, String.valueOf(value), Payload.ValueType.DOUBLE);
+ }
+
+ private void setPayload(String key, String value,
+ Payload.ValueType valueType) {
+ getState().payload.put(key, new Payload(key, value, valueType));
+ }
+
+ /**
* Set server side drag data. This data is available in the drop event and
* can be used to transfer data between drag source and drop target if they
* are in the same UI.
@@ -322,7 +391,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* dragstart event happens on the client side.
*
* @param listener
- * Listener to handle dragstart event.
+ * Listener to handle dragstart event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragStartListener(DragStartListener<T> listener) {
@@ -337,7 +406,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* event happens on the client side.
*
* @param listener
- * Listener to handle dragend event.
+ * Listener to handle dragend event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragEndListener(DragEndListener<T> listener) {
@@ -349,7 +418,7 @@ public class DragSourceExtension<T extends AbstractComponent>
* Set a custom drag image for the current drag source.
*
* @param imageResource
- * Resource of the image to be displayed as drag image.
+ * Resource of the image to be displayed as drag image.
*/
public void setDragImage(Resource imageResource) {
setResource(DragSourceState.RESOURCE_DRAG_IMAGE, imageResource);
diff --git a/server/src/main/java/com/vaadin/ui/dnd/DropTargetExtension.java b/server/src/main/java/com/vaadin/ui/dnd/DropTargetExtension.java
index 1f5c675b21..4cff2545e8 100644
--- a/server/src/main/java/com/vaadin/ui/dnd/DropTargetExtension.java
+++ b/server/src/main/java/com/vaadin/ui/dnd/DropTargetExtension.java
@@ -15,6 +15,7 @@
*/
package com.vaadin.ui.dnd;
+import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -23,9 +24,11 @@ import java.util.Objects;
import com.vaadin.server.AbstractExtension;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
+import com.vaadin.shared.ui.dnd.criteria.ComparisonOperator;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.DropTargetRpc;
import com.vaadin.shared.ui.dnd.DropTargetState;
+import com.vaadin.shared.ui.dnd.criteria.Criterion;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.dnd.event.DropEvent;
import com.vaadin.ui.dnd.event.DropListener;
@@ -146,9 +149,13 @@ public class DropTargetExtension<T extends AbstractComponent>
/**
* Sets a criteria script in JavaScript to allow drop on this drop target.
* The script is executed when something is dragged on top of the target,
- * and the drop is not allowed in case the script returns {@code false}. If
- * no script is set, then the drop is always accepted, if the set
- * {@link #setDropEffect(DropEffect) dropEffect} matches the drag source.
+ * and the drop is not allowed in case the script returns {@code false}.
+ * <p>
+ * Drop will be allowed if it passes both this criteria script and the
+ * criteria set via any of {@code setDropCriterion()} or {@code
+ * setDropCriteria()} methods. If no criteria is set, then the drop is
+ * always accepted, if the set {@link #setDropEffect(DropEffect) dropEffect}
+ * matches the drag source.
* <p>
* <b>IMPORTANT:</b> Construct the criteria script carefully and do not
* include untrusted sources such as user input. Always keep in mind that
@@ -157,22 +164,23 @@ public class DropTargetExtension<T extends AbstractComponent>
* Example:
*
* <pre>
- * target.setDropCriteria(
- * // If dragged source contains a URL, allow it to be dropped
- * "if (event.dataTransfer.types.includes('text/uri-list')) {"
- * + " return true;" + "}" +
+ * target.setDropCriterion(
+ * // If dragged source contains a URL, allow it to be dropped
+ * "if (event.dataTransfer.types.includes('text/uri-list')) {" +
+ * " return true;" +
+ * "}" +
*
- * // Otherwise cancel the event"
- * "return false;");
+ * // Otherwise cancel the event
+ * "return false;");
* </pre>
*
* @param criteriaScript
* JavaScript to be executed when drop event happens or
* {@code null} to clear.
*/
- public void setDropCriteria(String criteriaScript) {
- if (!Objects.equals(getState(false).dropCriteria, criteriaScript)) {
- getState().dropCriteria = criteriaScript;
+ public void setDropCriteriaScript(String criteriaScript) {
+ if (!Objects.equals(getState(false).criteriaScript, criteriaScript)) {
+ getState().criteriaScript = criteriaScript;
}
}
@@ -182,10 +190,120 @@ public class DropTargetExtension<T extends AbstractComponent>
* allowed.
*
* @return JavaScript that executes when drop event happens.
- * @see #setDropCriteria(String)
+ * @see #setDropCriteriaScript(String)
+ */
+ public String getDropCriteriaScript() {
+ return getState(false).criteriaScript;
+ }
+
+ /**
+ * Set a drop criterion to allow drop on this drop target. When data is
+ * dragged on top of the drop target, the given value is compared to the
+ * drag source's payload with the same key. The drag passes this criterion
+ * if the value of the payload and the value given here are equal.
+ * <p>
+ * Note that calling this method will overwrite the previously set criteria.
+ * To set multiple criteria, call the {@link #setDropCriteria(Criterion.Match,
+ * Criterion...)} method.
+ * <p>
+ * To handle more complex criteria, define a custom script with {@link
+ * #setDropCriteriaScript(String)}. Drop will be allowed if both this
+ * criterion and the criteria script are passed.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param value
+ * value to be compared to the payload's value
+ * @see DragSourceExtension#setPayload(String, String)
+ */
+ public void setDropCriterion(String key, String value) {
+ setDropCriteria(Criterion.Match.ANY, new Criterion(key, value));
+ }
+
+ /**
+ * Set a drop criterion to allow drop on this drop target. When data is
+ * dragged on top of the drop target, the given value is compared to the
+ * drag source's payload with the same key. The drag passes this criterion
+ * if the value of the payload compared to the given value using the given
+ * operator holds.
+ * <p>
+ * Note that calling this method will overwrite the previously set criteria.
+ * To set multiple criteria, call the {@link #setDropCriteria(Criterion.Match,
+ * Criterion...)} method.
+ * <p>
+ * To handle more complex criteria, define a custom script with {@link
+ * #setDropCriteriaScript(String)}. Drop will be allowed if both this
+ * criterion and the criteria script are passed.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param operator
+ * comparison operator to be used
+ * @param value
+ * value to be compared to the payload's value
+ * @see DragSourceExtension#setPayload(String, int)
+ */
+ public void setDropCriterion(String key, ComparisonOperator operator,
+ int value) {
+ setDropCriteria(Criterion.Match.ANY,
+ new Criterion(key, operator, value));
+ }
+
+ /**
+ * Set a drop criterion to allow drop on this drop target. When data is
+ * dragged on top of the drop target, the given value is compared to the
+ * drag source's payload with the same key. The drag passes this criterion
+ * if the value of the payload compared to the given value using the given
+ * operator holds.
+ * <p>
+ * Note that calling this method will overwrite the previously set criteria.
+ * To set multiple criteria, call the {@link #setDropCriteria(Criterion.Match,
+ * Criterion...)} method.
+ * <p>
+ * To handle more complex criteria, define a custom script with {@link
+ * #setDropCriteriaScript(String)}. Drop will be allowed if both this
+ * criterion and the criteria script are passed.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param operator
+ * comparison operator to be used
+ * @param value
+ * value to be compared to the payload's value
+ * @see DragSourceExtension#setPayload(String, double)
+ */
+ public void setDropCriterion(String key, ComparisonOperator operator,
+ double value) {
+ setDropCriteria(Criterion.Match.ANY,
+ new Criterion(key, operator, value));
+ }
+
+ /**
+ * Sets multiple drop criteria to allow drop on this drop target. When data
+ * is dragged on top of the drop target, the value of the given criteria is
+ * compared to the drag source's payload with the same key.
+ * <p>
+ * The drag passes these criteria if, depending on {@code match}, any or all
+ * of the criteria matches the payload, that is the value of the payload
+ * compared to the value of the criterion using the criterion's operator
+ * holds.
+ * <p>
+ * Note that calling this method will overwrite the previously set
+ * criteria.
+ * <p>
+ * To handle more complex criteria, define a custom script with {@link
+ * #setDropCriteriaScript(String)}. Drop will be allowed if both this
+ * criterion and the criteria script are passed.
+ *
+ * @param match
+ * defines whether any or all of the given criteria should match to
+ * allow drop on this drop target
+ * @param criteria
+ * criteria to be compared to the payload
*/
- public String getDropCriteria() {
- return getState(false).dropCriteria;
+ public void setDropCriteria(Criterion.Match match, Criterion... criteria) {
+ getState().criteriaMatch = match;
+ getState().criteria = Arrays.asList(criteria);
}
/**
diff --git a/server/src/main/java/com/vaadin/ui/dnd/event/DragEndEvent.java b/server/src/main/java/com/vaadin/ui/dnd/event/DragEndEvent.java
index de0ba64a5e..df71ed0a08 100644
--- a/server/src/main/java/com/vaadin/ui/dnd/event/DragEndEvent.java
+++ b/server/src/main/java/com/vaadin/ui/dnd/event/DragEndEvent.java
@@ -64,7 +64,7 @@ public class DragEndEvent<T extends AbstractComponent> extends Component.Event {
* dragend event.
* @see DragSourceExtension#setEffectAllowed(EffectAllowed)
* @see DropTargetExtension#setDropEffect(DropEffect)
- * @see DropTargetExtension#setDropCriteria(String)
+ * @see DropTargetExtension#setDropCriteriaScript(String)
*/
public DropEffect getDropEffect() {
return dropEffect;
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java
index 0fefd05665..42e3bffc07 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import com.vaadin.shared.communication.SharedState;
+import com.vaadin.shared.ui.dnd.criteria.Payload;
/**
* State class containing parameters for DragSourceExtension.
@@ -75,4 +76,10 @@ public class DragSourceState extends SharedState {
* Used to store data in the {@code DataTransfer} object for the drag event.
*/
public Map<String, String> data = new HashMap<>();
+
+ /**
+ * Payload for comparing against acceptance criteria. Transferred in the
+ * {@code DataTransfer} object as data type.
+ */
+ public Map<String, Payload> payload = new HashMap<>();
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java
index c36d97f1dc..edd8beddbd 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java
@@ -15,7 +15,11 @@
*/
package com.vaadin.shared.ui.dnd;
+import java.util.ArrayList;
+import java.util.List;
+
import com.vaadin.shared.communication.SharedState;
+import com.vaadin.shared.ui.dnd.criteria.Criterion;
/**
* State class containing parameters for DropTargetExtension.
@@ -32,5 +36,16 @@ public class DropTargetState extends SharedState {
/**
* Criteria script to allow drop event on the element
*/
- public String dropCriteria;
+ public String criteriaScript;
+
+ /**
+ * List of criteria to compare against the payload.
+ */
+ public List<Criterion> criteria = new ArrayList<>();
+
+ /**
+ * Declares whether any or all of the given criteria should match the
+ * payload
+ */
+ public Criterion.Match criteriaMatch = Criterion.Match.ANY;
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/ComparisonOperator.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/ComparisonOperator.java
new file mode 100644
index 0000000000..d7ab6ff339
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/ComparisonOperator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.shared.ui.dnd.criteria;
+
+/**
+ * Comparison operator for drag and drop acceptance criterion.
+ *
+ * @author Vaadin Ltd.
+ * @since 8.1
+ */
+public enum ComparisonOperator {
+
+ /**
+ * Smaller than operator ("<").
+ */
+ SMALLER_THAN,
+
+ /**
+ * Smaller than or equals to operator ("<=").
+ */
+ SMALLER_THAN_OR_EQUALS,
+
+ /**
+ * Equals to operator ("==").
+ */
+ EQUALS,
+
+ /**
+ * Greater than or equals to operator (">=").
+ */
+ GREATER_THAN_OR_EQUALS,
+
+ /**
+ * Greater than operator (">").
+ */
+ GREATER_THAN,
+
+ /**
+ * Not equals operator ("!=").
+ */
+ NOT_EQUALS
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java
new file mode 100644
index 0000000000..c90637a6e0
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java
@@ -0,0 +1,215 @@
+/*
+ * 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.shared.ui.dnd.criteria;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Stores parameters for the drag and drop acceptance criterion defined using
+ * the criteria API.
+ * <p>
+ * When data is dragged over a drop target, the value here is compared to the
+ * payload added in DropTargetExtension with same key and value type.
+ *
+ * @author Vaadin Ltd
+ * @since 8.1
+ */
+public class Criterion implements Serializable {
+
+ /**
+ * Declares whether all or any of the given criteria should match when
+ * compared against the payload.
+ */
+ public enum Match {
+ /**
+ * When compared to the payload, the drop will be accepted if any of the
+ * criteria matches.
+ */
+ ANY,
+
+ /**
+ * When compared to the payload, the drop will be accepted only if all
+ * of the given criteria matches.
+ */
+ ALL
+ }
+
+ private String key;
+ private String value;
+ private Payload.ValueType valueType;
+ private ComparisonOperator operator;
+
+ /**
+ * Mandatory zero param constructor.
+ */
+ private Criterion() {
+
+ }
+
+ /**
+ * Creates a criterion object with the default comparison operator {@link
+ * ComparisonOperator#EQUALS}.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param value
+ * value of the payload to be compared
+ */
+ public Criterion(String key, String value) {
+ this(key, ComparisonOperator.EQUALS, value, Payload.ValueType.STRING);
+ }
+
+ /**
+ * Creates a criterion object.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param operator
+ * comparison operator
+ * @param value
+ * value of the payload to be compared
+ */
+ public Criterion(String key, ComparisonOperator operator, int value) {
+ this(key, operator, String.valueOf(value), Payload.ValueType.INTEGER);
+ }
+
+ /**
+ * Creates a criterion object.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param operator
+ * comparison operator
+ * @param value
+ * value of the payload to be compared
+ */
+ public Criterion(String key, ComparisonOperator operator, double value) {
+ this(key, operator, String.valueOf(value), Payload.ValueType.DOUBLE);
+ }
+
+ /**
+ * Creates a criterion object.
+ *
+ * @param key
+ * key of the payload to be compared
+ * @param operator
+ * comparison operator
+ * @param value
+ * value of the payload to be compared
+ * @param valueType
+ * type of the payload to be compared
+ */
+ private Criterion(String key, ComparisonOperator operator, String value,
+ Payload.ValueType valueType) {
+ this.key = key;
+ this.value = value;
+ this.valueType = valueType;
+ this.operator = operator;
+ }
+
+ /**
+ * Gets the key of the payload to be compared
+ *
+ * @return key of the payload to be compared
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the value of the payload to be compared
+ *
+ * @return value of the payload to be compared
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Gets the type of the payload value to be compared
+ *
+ * @return type of the payload value to be compared
+ */
+ public Payload.ValueType getValueType() {
+ return valueType;
+ }
+
+ /**
+ * Gets the comparison operator.
+ *
+ * @return operator to be used when comparing payload value with criterion
+ */
+ public ComparisonOperator getOperator() {
+ return operator;
+ }
+
+ /**
+ * Compares this criterion's value to the given payload's value and returns
+ * whether the result matches the criterion's operator. The comparison is
+ * done with the payload whose key and value type match the criterion's key
+ * and value type.
+ *
+ * @param payloadCollection
+ * collection of payloads to compare the criterion against
+ * @return {@code false} if there exists a payload in the collection with
+ * the same key and value type and it doesn't match the criterion, {@code
+ * true} otherwise
+ */
+ public boolean resolve(Collection<Payload> payloadCollection) {
+ Optional<Payload> payload = payloadCollection.stream()
+ .filter(p -> p.getKey().equals(key) && p.getValueType()
+ .equals(valueType)).findAny();
+
+ return payload.map(this::compareCriterionValue).orElse(true);
+ }
+
+ private boolean compareCriterionValue(Payload payload) {
+ int result;
+
+ switch (valueType) {
+ case STRING:
+ default:
+ result = value.compareTo(payload.getValue());
+ break;
+ case INTEGER:
+ result = Integer.valueOf(value)
+ .compareTo(Integer.valueOf(payload.getValue()));
+ break;
+ case DOUBLE:
+ result = Double.valueOf(value)
+ .compareTo(Double.valueOf(payload.getValue()));
+ break;
+ }
+
+ switch (operator) {
+ case SMALLER_THAN:
+ return result < 0;
+ case SMALLER_THAN_OR_EQUALS:
+ return result <= 0;
+ case EQUALS:
+ default:
+ return result == 0;
+ case GREATER_THAN_OR_EQUALS:
+ return result >= 0;
+ case GREATER_THAN:
+ return result > 0;
+ case NOT_EQUALS:
+ return result != 0;
+ }
+ }
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Payload.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Payload.java
new file mode 100644
index 0000000000..ffa3d0a1e6
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Payload.java
@@ -0,0 +1,125 @@
+/*
+ * 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.shared.ui.dnd.criteria;
+
+import java.io.Serializable;
+
+/**
+ * Stores key/value pairs and the value type. Payload is set in
+ * DragSourceExtension and is transferred during drag operation. It is used for
+ * comparing values to acceptance criteria.
+ */
+public class Payload implements Serializable {
+
+ /**
+ * Type of the payload's value.
+ */
+ public enum ValueType {
+ STRING, INTEGER, DOUBLE;
+ }
+
+ /**
+ * Prefix of the payload data type.
+ */
+ public static final String ITEM_PREFIX = "v-item";
+
+ private String key;
+ private String value;
+ private ValueType valueType;
+
+ /**
+ * Mandatory zero arg constructor.
+ */
+ private Payload() {
+
+ }
+
+ /**
+ * Creates a payload object.
+ *
+ * @param key
+ * key of the payload
+ * @param value
+ * value of the payload
+ * @param valueType
+ * type of the payload value
+ */
+ public Payload(String key, String value, ValueType valueType) {
+ this.key = key;
+ this.value = value;
+ this.valueType = valueType;
+ }
+
+ /**
+ * Gets the key of this payload.
+ *
+ * @return key identifying this payload
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the value of this payload.
+ *
+ * @return value of this payload
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Gets the value type of this payload.
+ *
+ * @return the type of the value of this payload
+ */
+ public ValueType getValueType() {
+ return valueType;
+ }
+
+ /**
+ * Returns the string representation of this payload. It is used as the data
+ * type in the {@code DataTransfer} object.
+ *
+ * @return the string representation of this payload
+ */
+ public String getPayloadString() {
+ return ITEM_PREFIX + ":" + valueType.name().toLowerCase() + ":" + key
+ + ":" + value;
+ }
+
+ /**
+ * Parses a payload string and returns a payload object represented by that
+ * string.
+ *
+ * @param payloadString
+ * string that represents a payload object
+ * @return a payload object represented by the given string
+ */
+ public static Payload parse(String payloadString) {
+ String[] parts = payloadString.split(":");
+
+ if (parts.length != 4 || !ITEM_PREFIX.equals(parts[0])) {
+ throw new IllegalArgumentException(
+ "Data type does not have a valid payload format");
+ }
+
+ // Create payload object of the given parts. Value type is converted to
+ // upper case to match the enum's case.
+ return new Payload(parts[2], parts[3],
+ ValueType.valueOf(parts[1].toUpperCase()));
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java b/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java
index 493b57226e..8f04ed1835 100644
--- a/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java
+++ b/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java
@@ -22,6 +22,7 @@ import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.Page;
import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.dnd.criteria.ComparisonOperator;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.tests.components.AbstractTestUIWithLog;
@@ -86,20 +87,20 @@ public class DragAndDropCardShuffle extends AbstractTestUIWithLog {
// Create UI and add extensions
ace.setStyleName("card");
- addDragSourceExtension(ace);
- addDropTargetExtension(ace);
+ addDragSourceExtension(ace, 14);
+ addDropTargetExtension(ace, 14);
jack.setStyleName("card");
- addDragSourceExtension(jack);
- addDropTargetExtension(jack);
+ addDragSourceExtension(jack, 11);
+ addDropTargetExtension(jack, 11);
queen.setStyleName("card");
- addDragSourceExtension(queen);
- addDropTargetExtension(queen);
+ addDragSourceExtension(queen, 12);
+ addDropTargetExtension(queen, 12);
king.setStyleName("card");
- addDragSourceExtension(king);
- addDropTargetExtension(king);
+ addDragSourceExtension(king, 13);
+ addDropTargetExtension(king, 13);
}
private void removeExtensions() {
@@ -116,11 +117,14 @@ public class DragAndDropCardShuffle extends AbstractTestUIWithLog {
king.removeExtension(king.getExtensions().iterator().next());
}
- private void addDragSourceExtension(Label source) {
+ private void addDragSourceExtension(Label source, int cardValue) {
// Create and attach extension
DragSourceExtension<Label> dragSource = new DragSourceExtension<>(
source);
+ // Set card value via criteria API for acceptance criteria
+ dragSource.setPayload("card_value", cardValue);
+
// Add listeners
dragSource.addDragStartListener(event -> {
log(event.getComponent().getValue() + " dragstart, effectsAllowed="
@@ -135,11 +139,15 @@ public class DragAndDropCardShuffle extends AbstractTestUIWithLog {
sources.add(dragSource);
}
- private void addDropTargetExtension(Label target) {
+ private void addDropTargetExtension(Label target, int cardValue) {
// Create and attach extension
DropTargetExtension<Label> dropTarget = new DropTargetExtension<>(
target);
+ // Cards can be dropped onto others with smaller value
+ dropTarget.setDropCriterion("card_value", ComparisonOperator.SMALLER_THAN,
+ cardValue);
+
// Add listener
dropTarget.addDropListener(event -> {
event.getDragSourceExtension().ifPresent(dragSource -> {