diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2017-05-11 14:36:27 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-11 14:36:27 +0300 |
commit | f72ac12fd257e218c370a4d686be4cc99c2a22d6 (patch) | |
tree | df7937c1835dcd6517717332147cae383a27c05c /server | |
parent | e2e3058a497f43f34f2fcfadf6b63de9211be659 (diff) | |
download | vaadin-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')
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 |