GWT ScrollPanel for Touch Screens

Getting this working is easier than you think. Basically the only tricky bit is that the touch object allows for multiple fingers they are represented in the array ‘touches[]‘. Since we are only scrolling we only need to worry about the first finger which is why I use touches[0].screenY. Enjoy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.peterfranza.gwt.mobileui.client.widgets;
 
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.ui.ScrollPanel;
 
/**
 * 
 * @author peter.franza
 *
 */
public class TouchScrollPanel extends ScrollPanel {
 
	private int initialTouchX = -1;
	private int initialTouchY = -1;
	private int initialHorizontalOffset;
	private int initialVerticalOffset;
	private boolean moved = false;
 
	{
		attachTouch(getElement());
	}
 
	public TouchScrollPanel(VerticalPanel body) {
		super(body);
	}
 
	private native void attachTouch(JavaScriptObject ele) /*-{	
		var ref = this;
		ele.ontouchstart = function(evt){
	  		evt.preventDefault();	
	  		ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);
		}	
		ele.ontouchmove = function(evt){
	  		evt.preventDefault();	
	  		ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::doScroll(II)(evt.touches[0].screenX, evt.touches[0].screenY);
		}		
		ele.ontouchend = function(evt){
			evt.preventDefault();
			ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::setEndTouch(II)(evt.pageX, evt.pageY);
		}		
	}-*/;
 
	private native void fireClick(int x, int y) /*-{	
		var theTarget = $doc.elementFromPoint(x, y);
		if (theTarget.nodeType == 3) theTarget = theTarget.parentNode;
 
		var theEvent = $doc.createEvent('MouseEvents');
		theEvent.initEvent('click', true, true);
		theTarget.dispatchEvent(theEvent);
	}-*/;
 
 
	@SuppressWarnings("unused")
	private void setInitialTouch(int x, int y) {
		initialVerticalOffset = getScrollPosition();
		initialHorizontalOffset = getHorizontalScrollPosition();
 
		initialTouchX = x;
		initialTouchY = y;
		moved = false;
 
	}
 
	@SuppressWarnings("unused")
	private void doScroll(int x, int y) {
		if (initialTouchY != -1) {
			moved = true;
			int vDelta = initialTouchY - y;
			int hDelta = initialTouchX - x;
			setScrollPosition(vDelta + initialVerticalOffset);
			setHorizontalScrollPosition(hDelta + initialHorizontalOffset);
		}
	}
 
	@SuppressWarnings("unused")
	private void setEndTouch(int x, int y) {
		if (!moved) {
			fireClick(x, y);
		}
		initialTouchX = -1;
		initialTouchY = -1;
	} 
}


Did you enjoy this post? Why not leave a comment below and continue the conversation, or subscribe to my feed and get articles like this delivered automatically to your feed reader.

Comments

I’ve updated this post so the scroll will propagate click events if the touch doesn’t move

Reply

Updated to also allow horizontal scrolling.
— Contributed by b.sciacchitano

Reply

“ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);”

?

Reply

Hi Peter,

This looks promising, but I have the same question as Jim. How do I get “ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);” to compile?

Thanks!
Todd

Reply

Todd,

I discovered that the “ref.@…” statement is the real magic of this post. This allows us to refer to the Java code in the TouchScrollPanel class (which will ultimately be compiled into javascript).

The example above should refer to the class com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel for this to work (not com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel).

Reply

Sorry the code examples were taken from a real program so I had missed the copy paste for the package name, but jim is correct.

To bad blogs aren’t refactor safe.

Reply

Peter,
For some reason this is not propagating the click event to the widget inside the TouchScrollPanel. Any ideas on why this might be? I’m using UiBinder, and that layout generally looks like this:

<g:DockLayoutPanel unit=’EM’>
<g:center>
<ipad:TouchScrollPanel>
<g:FlexTable ui:field=’orderList’ />
</ipad:TouchScrollPanel>
</g:center>
</g:DockLayoutPanel>

But my FlexTable is not receiving any click events on the iPhone/iPad. I’m not a Javascript guru — any help is appreciated.

Reply

pfranza Reply:

I haven’t used the class with the UIBinder stuff, but what I think might be happening is that the UIBinder isn’t passing the body panel in through the constructor of the ScrollPanel. If you were using the class programmatically you would first construct your oversize panel, then pass it into the constructor of the ScrollPanel and then add that to your root panel (or whatever the parent of the scroll is supposed to be).

I think the UIBinder might be creating the scrollpanel and then adding items to it via an add(…) method.

If you load the screen using a desktop browser (firefox/chrome/IE …) do you see scrollbars around your panel?

I have seen this work on ipad and android so I think its probably just a difference in the way UIBinder initializes the object graph.

I look forward to seeing the resolution and any modifications/improvements you might have.

Thanks,

Reply

Jeff Reply:

Desktop browsers apparently work just fine, showing scrollbars and allowing clicks.

I do believe that Widget.add(Widget) is being called; do you think I should call attachTouch(getElement()) after the widget has been added?

Reply

Jeff Reply:

I’m having the same trouble even if I don’t use UIBinder and programmatically add the TouchScrollPanel to the window… I think it has something to do with the target being a table.

Reply

pfranza Reply:

Have you tried wrapping the table in a Panel like the SimplePanel?

Reply

Jeff Reply:

Yes, its in a VerticalPanel.

Reply

Jeff Reply:

Peter, this doesn’t work for me at all. I’ve produced a simple example of a TouchScrollPane that is placed inside a DockLayoutPanel. The TouchScrollPane wraps a VerticalPanel, which contains a Button and a FlexTable, each of which have ClickHandlers. In any browser other than mobile Safari, the button and table both react to mouse clicks, and show scrollbars which work fine.

On the iPad, the one-finger scrolling works, but no clicks are received by either the Button or FlexTable. If I print out the Node returned by
var theTarget = $doc.elementFromPoint(x, y);
it returns the DIV that represents the DockLayoutPanel.

I can send you the source of the example if you would like to see it.

Reply

pfranza Reply:

Sure I’ll take a look

Reply

Jeff Reply:

Should I post the source here, or email you? If email, to what address?

Reply

Alan Wecker Reply:

I too have this problem any solution?

It works for me:

fireClick(initialTouchX, initialTouchY);

Reply

Frank Reply:

also

evt.touches[0].clientX, evt.touches[0].clientY

in the functions ontouchstart and ontouchmove.

Reply

Frank Reply:

alternative would also suffice:

evt.changedTouches[0].clientX, evt.changedTouches[0].clientY

then the first two changes not required

Reply

Frank Reply:

evt.changedTouches[0].clientX, evt.changedTouches[0].clientY in the function ontouchend

Reply

Thanks Frank for your reply. I can get the clicks to work now, but I have a problem with a ListBox that I have inside of my touchscrollpanel. The ListBox won’t work with click events. Any ideas?

Reply

Frank Reply:

Hallo Dave – try GWT-Version 2.1.1.

Reply

Frank Reply:

Hallo Dave – try GWT-Version 2.1.1. that should solve your problem

Reply

Hallo Dave – try GWT-Version 2.1.1.

Reply

Thanks Frank for your answer, however I find that this solution seems to only work when the the object being clicked is an immediate child of the vertical panel being enclosed by the TouchScrollPanel. I’m thinking this has to do with the instruction in fireClick

var theTarget = $doc.elementFromPoint(x, y);
if (theTarget.nodeType == 3) theTarget = theTarget.parentNode;

but I am not an expert on javascript so I do not know this for certain. Any ideas anyone?

Reply

COuld you post here the full sample code, so I could test it myself too?

Reply

Leave a comment

(required)

(required)