summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorPekka Hyvönen <pekka@vaadin.com>2017-05-11 14:36:27 +0300
committerGitHub <noreply@github.com>2017-05-11 14:36:27 +0300
commitf72ac12fd257e218c370a4d686be4cc99c2a22d6 (patch)
treedf7937c1835dcd6517717332147cae383a27c05c /server
parente2e3058a497f43f34f2fcfadf6b63de9211be659 (diff)
downloadvaadin-framework-f72ac12fd257e218c370a4d686be4cc99c2a22d6.tar.gz
vaadin-framework-f72ac12fd257e218c370a4d686be4cc99c2a22d6.zip
Add mobile html5 dnd support using polyfill (#9282)
First step of mobile DND support. - Add mobile html5 dnd support using polyfill - Adds a switch for enabling mobile html5 dnd support - Adds polyfill only when needed - Ignore native Android Chrome drag start because doesn't work properly (no dragend event fired) - Add documentation on enabling mobile HTML5 DnD support - Add mention of drag-drop-polyfill license - Fixed issue in polyfill when not using "snapback" - Add mention of forked polyfill Fixes #9174
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/server/Page.java46
-rw-r--r--server/src/main/java/com/vaadin/server/communication/UidlWriter.java7
-rw-r--r--server/src/main/java/com/vaadin/ui/UI.java74
-rw-r--r--server/src/main/resources/VAADIN/drag-drop-polyfill.min.js4
4 files changed, 128 insertions, 3 deletions
diff --git a/server/src/main/java/com/vaadin/server/Page.java b/server/src/main/java/com/vaadin/server/Page.java
index 1cebe42802..385aa0bf9a 100644
--- a/server/src/main/java/com/vaadin/server/Page.java
+++ b/server/src/main/java/com/vaadin/server/Page.java
@@ -20,11 +20,15 @@ import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.EventObject;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import com.vaadin.annotations.HtmlImport;
+import com.vaadin.annotations.StyleSheet;
import com.vaadin.event.EventRouter;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.BorderStyle;
@@ -33,6 +37,7 @@ import com.vaadin.shared.ui.ui.PageState;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIState;
import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.ui.Dependency;
import com.vaadin.ui.JavaScript;
import com.vaadin.ui.LegacyWindow;
import com.vaadin.ui.Link;
@@ -560,6 +565,8 @@ public class Page implements Serializable {
private String newPushState;
private String newReplaceState;
+ private List<Dependency> pendingDependencies;
+
public Page(UI uI, PageState state) {
this.uI = uI;
this.state = state;
@@ -1426,4 +1433,43 @@ public class Page implements Serializable {
private boolean hasEventRouter() {
return eventRouter != null;
}
+
+ /**
+ * Add a dependency that should be added to the current page.
+ * <p>
+ * These dependencies are always added before the dependencies included by
+ * using the annotations {@link HtmlImport}, {@link JavaScript} and
+ * {@link StyleSheet} during the same request.
+ * <p>
+ * Please note that these dependencies are always sent to the client side
+ * and not filtered out by any {@link DependencyFilter}.
+ *
+ * @param dependency
+ * the dependency to add
+ * @since 8.1
+ */
+ public void addDependency(Dependency dependency) {
+ if (pendingDependencies == null) {
+ pendingDependencies = new ArrayList<>();
+ }
+ pendingDependencies.add(dependency);
+ }
+
+ /**
+ * Returns all pending dependencies.
+ * <p>
+ * For internal use only, calling this method will clear the pending
+ * dependencies.
+ *
+ * @return the pending dependencies to the current page
+ * @since 8.1
+ */
+ public Collection<Dependency> getPendingDependencies() {
+ ArrayList<Dependency> copy = new ArrayList<>();
+ if (pendingDependencies != null) {
+ copy.addAll(pendingDependencies);
+ }
+ pendingDependencies = null;
+ return copy;
+ }
}
diff --git a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java
index 7b5f83000c..7511ccd7ec 100644
--- a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java
+++ b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java
@@ -283,9 +283,10 @@ public class UidlWriter implements Serializable {
}
});
- List<Dependency> dependencies = Dependency
- .findAndFilterDependencies(newConnectorTypes, manager,
- new FilterContext(session));
+ List<Dependency> dependencies = new ArrayList<>();
+ dependencies.addAll(ui.getPage().getPendingDependencies());
+ dependencies.addAll(Dependency.findAndFilterDependencies(
+ newConnectorTypes, manager, new FilterContext(session)));
// Include dependencies in output if there are any
if (!dependencies.isEmpty()) {
diff --git a/server/src/main/java/com/vaadin/ui/UI.java b/server/src/main/java/com/vaadin/ui/UI.java
index 536b2603dd..1928aabe87 100644
--- a/server/src/main/java/com/vaadin/ui/UI.java
+++ b/server/src/main/java/com/vaadin/ui/UI.java
@@ -45,6 +45,7 @@ import com.vaadin.event.UIEvents.PollEvent;
import com.vaadin.event.UIEvents.PollListener;
import com.vaadin.event.UIEvents.PollNotifier;
import com.vaadin.event.dnd.DragSourceExtension;
+import com.vaadin.event.dnd.DropTargetExtension;
import com.vaadin.navigator.Navigator;
import com.vaadin.server.ClientConnector;
import com.vaadin.server.ComponentSizeValidator;
@@ -63,6 +64,7 @@ import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
import com.vaadin.server.VaadinSession.State;
import com.vaadin.server.communication.PushConnection;
+import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.Connector;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
@@ -71,12 +73,14 @@ import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.WindowOrderRpc;
import com.vaadin.shared.ui.ui.DebugWindowClientRpc;
import com.vaadin.shared.ui.ui.DebugWindowServerRpc;
+import com.vaadin.shared.ui.ui.PageClientRpc;
import com.vaadin.shared.ui.ui.ScrollClientRpc;
import com.vaadin.shared.ui.ui.UIClientRpc;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIServerRpc;
import com.vaadin.shared.ui.ui.UIState;
import com.vaadin.ui.Component.Focusable;
+import com.vaadin.ui.Dependency.Type;
import com.vaadin.ui.Window.WindowOrderChangeListener;
import com.vaadin.ui.declarative.Design;
import com.vaadin.util.ConnectorHelper;
@@ -658,6 +662,8 @@ public abstract class UI extends AbstractSingleComponentContainer
private String embedId;
+ private boolean mobileHtml5DndPolyfillLoaded;
+
/**
* This method is used by Component.Focusable objects to request focus to
* themselves. Focus renders must be handled at window level (instead of
@@ -1834,6 +1840,74 @@ public abstract class UI extends AbstractSingleComponentContainer
}
/**
+ * Returns whether HTML5 DnD extensions {@link DragSourceExtension} and
+ * {@link DropTargetExtension} and alike should be enabled for mobile
+ * devices.
+ * <p>
+ * By default, it is disabled.
+ *
+ * @return {@code true} if enabled, {@code false} if not
+ * @since 8.1
+ * @see #setMobileHtml5DndEnabled(boolean)
+ */
+ public boolean isMobileHtml5DndEnabled() {
+ return getState(false).enableMobileHTML5DnD;
+ }
+
+ /**
+ * Enable or disable HTML5 DnD for mobile devices.
+ * <p>
+ * Usually you should enable the support in the {@link #init(VaadinRequest)}
+ * method. By default, it is disabled. This operation is NOOP when the user
+ * is not on a mobile device.
+ * <p>
+ * Changing this will effect all {@link DragSourceExtension} and
+ * {@link DropTargetExtension} (and subclasses) that have not yet been
+ * attached to the UI on the client side.
+ * <p>
+ * <em>NOTE: When disabling this after it has been enabled, it will not
+ * affect {@link DragSourceExtension} and {@link DropTargetExtension} (and
+ * subclasses) that have been previously added. Those extensions should be
+ * explicitly removed to make sure user cannot perform DnD operations
+ * anymore.</em>
+ *
+ * @param enabled
+ * {@code true} if enabled, {@code false} if not
+ * @since 8.1
+ */
+ public void setMobileHtml5DndEnabled(boolean enabled) {
+ if (getState(false).enableMobileHTML5DnD != enabled) {
+ getState().enableMobileHTML5DnD = enabled;
+
+ if (isMobileHtml5DndEnabled()) {
+ loadMobileHtml5DndPolyfill();
+ }
+ }
+ }
+
+ /**
+ * Load and initialize the mobile drag-drop-polyfill if needed and not yet
+ * done so.
+ */
+ private void loadMobileHtml5DndPolyfill() {
+ if (mobileHtml5DndPolyfillLoaded) {
+ return;
+ }
+ if (!getPage().getWebBrowser().isTouchDevice()) {
+ return;
+ }
+ mobileHtml5DndPolyfillLoaded = true;
+
+ String vaadinLocation = getSession().getService().getStaticFileLocation(
+ VaadinService.getCurrentRequest()) + "/VAADIN/";
+
+ getPage().addDependency(new Dependency(Type.JAVASCRIPT,
+ vaadinLocation + ApplicationConstants.MOBILE_DND_POLYFILL_JS));
+
+ getRpcProxy(PageClientRpc.class).initializeMobileHtml5DndPolyfill();
+ }
+
+ /**
* Event which is fired when the ordering of the windows is updated.
* <p>
* The other way to listen window position for specific window is
diff --git a/server/src/main/resources/VAADIN/drag-drop-polyfill.min.js b/server/src/main/resources/VAADIN/drag-drop-polyfill.min.js
new file mode 100644
index 0000000000..00d8ba0a98
--- /dev/null
+++ b/server/src/main/resources/VAADIN/drag-drop-polyfill.min.js
@@ -0,0 +1,4 @@
+/*! drag-drop-polyfill 2.0.0-beta.2 | Copyright (c) 2016 Tim Ruffles | BSD 2 License */
+/* Forked from original with this change: https://github.com/timruffles/ios-html5-drag-drop-shim/pull/99 */
+var DEBUG,DragDropPolyfill;!function(a){function b(){var a={dragEvents:"ondragstart"in document.documentElement,draggable:"draggable"in document.documentElement,touchEvents:"ontouchstart"in document.documentElement,userAgentSupportingNativeDnD:void 0},b=!!window.chrome||/chrome/i.test(navigator.userAgent);return a.userAgentSupportingNativeDnD=!(/iPad|iPhone|iPod|Android/.test(navigator.userAgent)||b&&a.touchEvents),DEBUG&&Object.keys(a).forEach(function(a){}),a}function c(a){if(a&&Object.keys(a).forEach(function(b){t[b]=a[b]}),!t.forceApply){var c=b();if(c.userAgentSupportingNativeDnD&&c.draggable&&c.dragEvents)return}document.addEventListener("touchstart",d)}function d(a){if(!u){var b=e(a);if(b)try{u=new C(a,t,b,f)}catch(c){throw f(t,a,3),c}}}function e(a){var b=a.target;do if(b.draggable!==!1&&b.getAttribute&&"true"===b.getAttribute("draggable"))return b;while((b=b.parentNode)&&b!==document.body)}function f(a,b,c){if(0===c&&a.defaultActionOverride)try{a.defaultActionOverride(b),b.defaultPrevented}catch(d){}u=null}function g(a){return 0===a.length?0:a.reduce(function(a,b){return b+a},0)/a.length}function h(a){return a&&a.tagName}function i(a,b){for(var c=0;c<a.changedTouches.length;c++){var d=a.changedTouches[c];if(d.identifier===b)return!0}return!1}function j(a,b,c,d,e,f,g){void 0===g&&(g=null);var h=b.changedTouches[0],i=new Event(c,{bubbles:!0,cancelable:d});i.dataTransfer=f,i.relatedTarget=g,i.screenX=h.screenX,i.screenY=h.screenY,i.clientX=h.clientX,i.clientY=h.clientY,i.pageX=h.pageX,i.pageY=h.pageY;var j=a.getBoundingClientRect();return i.offsetX=i.clientX-j.left,i.offsetY=i.clientY-j.top,i}function k(a,b,c){for(var d=[],e=[],f=0;f<b.touches.length;f++){var h=b.touches[f];d.push(h[a+"X"]),e.push(h[a+"Y"])}c.x=g(d),c.y=g(e)}function l(a,b){if(1===a.nodeType){for(var c=getComputedStyle(a),d=0;d<c.length;d++){var e=c[d];b.style.setProperty(e,c.getPropertyValue(e),c.getPropertyPriority(e))}b.style.pointerEvents="none",b.removeAttribute("id"),b.removeAttribute("class"),b.removeAttribute("draggable")}if(a.hasChildNodes())for(var d=0;d<a.childNodes.length;d++)l(a.childNodes[d],b.childNodes[d])}function m(a){var b=a.cloneNode(!0);return l(a,b),b.style.position="absolute",b.style.left="0px",b.style.top="0px",b.style.zIndex="999999",b.classList.add(z),b.classList.add(B),b}function n(a){return x.map(function(b){var c=a.style[b+"transform"];return c&&"none"!==c?c.replace(/translate\(\D*\d+[^,]*,\D*\d+[^,]*\)\s*/g,""):""})}function o(a,b,c,d,e){void 0===e&&(e=!0);var f=b.x,g=b.y;d&&(f+=d.x,g+=d.y),e&&(f-=parseInt(a.offsetWidth,10)/2,g-=parseInt(a.offsetHeight,10)/2);for(var h="translate3d("+f+"px,"+g+"px, 0)",i=0;i<x.length;i++){var j=x[i]+"transform";a.style[j]=h+" "+c[i]}}function p(a,b,c,d){var e=getComputedStyle(a);if("hidden"===e.visibility||"none"===e.display)return void d();var f=a.getBoundingClientRect(),g={x:f.left,y:f.top};g.x+=document.body.scrollLeft||document.documentElement.scrollLeft,g.y+=document.body.scrollTop||document.documentElement.scrollTop,g.x-=parseInt(e.marginLeft,10),g.y-=parseInt(e.marginTop,10),b.classList.add(A);var h=getComputedStyle(b);var i=parseFloat(h.transitionDuration);if(i===NaN ||i===0)return void d();var j=parseFloat(h.transitionDelay),k=Math.round(1e3*(i+j));o(b,g,c,void 0,!1),setTimeout(d,k)}function q(a,b){return a?a===v[0]?w[0]:0===a.indexOf(v[1])||a===v[7]?w[1]:0===a.indexOf(v[4])?w[3]:a===v[6]?w[2]:w[1]:3===b.nodeType&&"A"===b.tagName?w[3]:w[1]}function r(a,b,c,d,e,f,g){if(void 0===f&&(f=!0),void 0===g&&(g=null),DEBUG){var h=y+"debug",i=y+"event-target",k=y+"event-related-target";b.classList.add(h),b.classList.add(i),g&&(g.classList.add(h),g.classList.add(k))}var l=j(b,c,a,f,document.defaultView,e,g),m=!b.dispatchEvent(l);return d.g=0,DEBUG&&(b.classList.remove(i),g&&g.classList.remove(k)),m}function s(a,b){if(!a||a===v[7])return b;if(b===w[1]){if(0===a.indexOf(w[1]))return w[1]}else if(b===w[3]){if(0===a.indexOf(w[3])||a.indexOf("Link")>-1)return w[3]}else if(b===w[2]&&(0===a.indexOf(w[2])||a.indexOf("Move")>-1))return w[2];return w[0]}var t={iterationInterval:150};a.Initialize=c;var u,v=["none","copy","copyLink","copyMove","link","linkMove","move","all"],w=["none","copy","move","link"],x=["","-webkit-"],y="dnd-poly-",z=y+"drag-image",A=y+"snapback",B=y+"icon",C=function(){function a(a,b,c,d){this.h=a,this.i=b,this.j=c,this.k=d,this.l=0,this.m=null,this.o=null,this.p=a,this.q=a.changedTouches[0],this.s=this.t.bind(this),this.u=this.v.bind(this),document.addEventListener("touchmove",this.s),document.addEventListener("touchend",this.u),document.addEventListener("touchcancel",this.u)}return a.prototype.A=function(){var a=this;this.l=1,this.B=w[0],this.C={D:{},F:void 0,g:3,G:[]},this.H={x:null,y:null},this.I={x:null,y:null};var b=this.j;if(this.J=new D(this.C,function(c,d,e){b=c,"number"!=typeof d&&"number"!=typeof e||(a.K={x:d||0,y:e||0})}),this.C.g=2,this.J.dropEffect=w[0],r("dragstart",this.j,this.p,this.C,this.J))return this.l=3,this.L(),!1;if(k("page",this.p,this.I),this.M=m(b),this.N=n(this.M),!this.K)if(this.i.dragImageOffset)this.K={x:this.i.dragImageOffset.x,y:this.i.dragImageOffset.y};else if(this.i.dragImageCenterOnTouch)this.K={x:0,y:0};else{var c=b.getBoundingClientRect(),d=getComputedStyle(b);this.K={x:c.left-this.q.clientX-parseInt(d.marginLeft,10),y:c.top-this.q.clientY-parseInt(d.marginTop,10)}}return o(this.M,this.I,this.N,this.K,this.i.dragImageCenterOnTouch),document.body.appendChild(this.M),this.O=setInterval(function(){a.P||(a.P=!0,a.R(),a.P=!1)},this.i.iterationInterval),!0},a.prototype.L=function(){this.O&&(clearInterval(this.O),this.O=null),document.removeEventListener("touchmove",this.s),document.removeEventListener("touchend",this.u),document.removeEventListener("touchcancel",this.u),this.M&&(this.M.parentNode.removeChild(this.M),this.M=null),this.k(this.i,this.p,this.l)},a.prototype.t=function(a){var b=this;if(i(a,this.q.identifier)!==!1){if(this.p=a,0===this.l){var c=void 0;if(this.i.dragStartConditionOverride)try{c=this.i.dragStartConditionOverride(a)}catch(d){c=!1}else c=1===a.touches.length;return c?void(this.A()===!0&&(this.h.preventDefault(),a.preventDefault())):void this.L()}if(a.preventDefault(),k("client",a,this.H),k("page",a,this.I),this.i.dragImageTranslateOverride)try{var e=!1;if(this.i.dragImageTranslateOverride(a,{x:this.H.x,y:this.H.y},this.m,function(a,c){b.M&&(e=!0,b.H.x+=a,b.H.y+=c,b.I.x+=a,b.I.y+=c,o(b.M,b.I,b.N,b.K,b.i.dragImageCenterOnTouch))}),e)return}catch(d){}o(this.M,this.I,this.N,this.K,this.i.dragImageCenterOnTouch)}},a.prototype.v=function(a){if(i(a,this.q.identifier)!==!1){if(this.i.dragImageTranslateOverride)try{this.i.dragImageTranslateOverride(void 0,void 0,void 0,function(){})}catch(b){}if(0===this.l)return void this.L();a.preventDefault(),this.l="touchcancel"===a.type?3:2}},a.prototype.R=function(){var a=this;if(DEBUG)var b=y+"debug",c=y+"immediate-user-selection",d=y+"current-drop-target";var e=this.B;this.C.g=3,this.J.dropEffect=w[0];var f=r("drag",this.j,this.p,this.C,this.J);if(f&&(this.B=w[0]),f||2===this.l||3===this.l){var g=this.S(this.l);return g?void p(this.j,this.M,this.N,function(){a.T()}):void this.T()}var i=document.elementFromPoint(this.H.x,this.H.y),j=this.o;i!==this.m&&i!==this.o&&(DEBUG&&(this.m&&this.m.classList.remove(c),i&&(i.classList.add(b),i.classList.add(c))),this.m=i,null!==this.o&&(this.C.g=3,this.J.dropEffect=w[0],r("dragexit",this.o,this.p,this.C,this.J,!1)),null===this.m?this.o=this.m:(this.C.g=3,this.J.dropEffect=q(this.C.F,this.j),r("dragenter",this.m,this.p,this.C,this.J)?(this.o=this.m,this.B=s(this.J.effectAllowed,this.J.dropEffect)):this.m!==document.body&&(this.o=document.body))),j!==this.o&&h(j)&&(DEBUG&&j.classList.remove(d),this.C.g=3,this.J.dropEffect=w[0],r("dragleave",j,this.p,this.C,this.J,!1,this.o)),h(this.o)&&(DEBUG&&(this.o.classList.add(b),this.o.classList.add(d)),this.C.g=3,this.J.dropEffect=q(this.C.F,this.j),r("dragover",this.o,this.p,this.C,this.J)===!1?this.B=w[0]:this.B=s(this.J.effectAllowed,this.J.dropEffect)),e!==this.B&&this.M.classList.remove(y+e);var k=y+this.B;this.M.classList.contains(k)===!1&&this.M.classList.add(k)},a.prototype.S=function(a){if(DEBUG){var b=y+"immediate-user-selection",c=y+"current-drop-target";this.o&&this.o.classList.remove(c),this.m&&this.m.classList.remove(b)}var d=this.B===w[0]||null===this.o||3===a;return d?h(this.o)&&(this.C.g=3,this.J.dropEffect=w[0],r("dragleave",this.o,this.p,this.C,this.J,!1)):h(this.o)&&(this.C.g=1,this.J.dropEffect=this.B,r("drop",this.o,this.p,this.C,this.J)===!0?this.B=this.J.dropEffect:this.B=w[0]),d},a.prototype.T=function(){this.C.g=3,this.J.dropEffect=this.B,r("dragend",this.j,this.p,this.C,this.J,!1),this.l=2,this.L()},a}(),D=function(){function a(a,b){this.U=a,this.V=b,this.W=w[0]}return Object.defineProperty(a.prototype,"types",{get:function(){if(0!==this.U.g)return Object.freeze(this.U.G)},enumerable:!0,configurable:!0}),a.prototype.setData=function(a,b){if(2===this.U.g){if(a.indexOf(" ")>-1)throw new Error("illegal arg: type contains space");this.U.D[a]=b,this.U.G.indexOf(a)===-1&&this.U.G.push(a)}},a.prototype.getData=function(a){if(1===this.U.g||2===this.U.g)return this.U.D[a]||""},a.prototype.clearData=function(a){if(2===this.U.g){if(a&&this.U.D[a]){delete this.U.D[a];var b=this.U.G.indexOf(a);return void(b>-1&&this.U.G.splice(b,1))}this.U.D={},this.U.G=[]}},a.prototype.setDragImage=function(a,b,c){2===this.U.g&&this.V(a,b,c)},Object.defineProperty(a.prototype,"effectAllowed",{get:function(){return this.U.F},set:function(a){2===this.U.g&&v.indexOf(a)>-1&&(this.U.F=a)},enumerable:!0,configurable:!0}),Object.defineProperty(a.prototype,"dropEffect",{get:function(){return this.W},set:function(a){0!==this.U.g&&v.indexOf(a)>-1&&(this.W=a)},enumerable:!0,configurable:!0}),a}()}(DragDropPolyfill||(DragDropPolyfill={}));
+//# sourceMappingURL=drag-drop-polyfill.min.js.map