IDEMPIERE-231 Zk6: Improve the tablet experience. Clean up and added tablet useragent detection.

This commit is contained in:
Heng Sin Low 2012-04-23 15:27:22 +08:00
parent eabc07667c
commit 12d8c0bcd7
12 changed files with 166 additions and 47 deletions

View File

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpSession;
import org.adempiere.webui.apps.AEnv;
@ -29,7 +30,6 @@ import org.adempiere.webui.component.TokenCommand;
import org.adempiere.webui.component.ZoomCommand;
import org.adempiere.webui.desktop.DefaultDesktop;
import org.adempiere.webui.desktop.IDesktop;
import org.adempiere.webui.event.TokenEvent;
import org.adempiere.webui.session.SessionContextListener;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
@ -43,6 +43,7 @@ import org.compiere.model.MUser;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.Language;
import org.zkoss.web.servlet.Servlets;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Page;
@ -69,8 +70,10 @@ import org.zkoss.zul.Window;
*
* @author hengsin
*/
public class AdempiereWebUI extends Window implements EventListener, IWebClient
public class AdempiereWebUI extends Window implements EventListener<Event>, IWebClient
{
public static final String APPLICATION_DESKTOP_KEY = "application.desktop";
/**
*
*/
@ -196,7 +199,7 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient
String autoNew = userPreference.getProperty(UserPreference.P_AUTO_NEW);
Env.setAutoNew(ctx, "true".equalsIgnoreCase(autoNew) || "y".equalsIgnoreCase(autoNew));
IDesktop d = (IDesktop) currSess.getAttribute("application.desktop");
IDesktop d = (IDesktop) currSess.getAttribute(APPLICATION_DESKTOP_KEY);
if (d != null && d instanceof IDesktop)
{
ExecutionCarryOver eco = (ExecutionCarryOver) currSess.getAttribute(EXECUTION_CARRYOVER_SESSION_KEY);
@ -267,7 +270,7 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient
createDesktop();
appDesktop.setClientInfo(clientInfo);
appDesktop.createPart(this.getPage());
currSess.setAttribute("application.desktop", appDesktop);
currSess.setAttribute(APPLICATION_DESKTOP_KEY, appDesktop);
ExecutionCarryOver eco = new ExecutionCarryOver(this.getPage().getDesktop());
currSess.setAttribute(EXECUTION_CARRYOVER_SESSION_KEY, eco);
currSess.setAttribute(ZK_DESKTOP_SESSION_KEY, this.getPage().getDesktop());
@ -346,6 +349,16 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient
clientInfo.timeZone = c.getTimeZone();
if (appDesktop != null)
appDesktop.setClientInfo(clientInfo);
String ua = Servlets.getUserAgent((ServletRequest) Executions.getCurrent().getNativeRequest());
clientInfo.userAgent = ua;
if (Servlets.getBrowser(ua).equals("webkit")) {
ua = ua.toLowerCase();
if (ua.indexOf("ipad") >= 0) {
clientInfo.tablet = true;
} else if (ua.indexOf("android") >= 0 && ua.indexOf("chrome") >= 0 && ua.indexOf("mobile") < 0) {
clientInfo.tablet = true;
}
}
}
}

View File

@ -38,4 +38,6 @@ public class ClientInfo implements Serializable {
public int screenHeight;
public int screenWidth;
public TimeZone timeZone;
public String userAgent;
public boolean tablet;
}

View File

@ -38,6 +38,7 @@ import javax.servlet.ServletRequest;
import org.adempiere.webui.AdempiereWebUI;
import org.adempiere.webui.component.Window;
import org.adempiere.webui.desktop.IDesktop;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.compiere.acct.Doc;
@ -778,4 +779,9 @@ public final class AEnv
return inUIThread ? Executions.getCurrent().getDesktop()
: (Desktop) Env.getCtx().get(AdempiereWebUI.ZK_DESKTOP_SESSION_KEY);
}
public static boolean isTablet() {
IDesktop appDesktop = SessionManager.getAppDesktop();
return appDesktop != null ? appDesktop.getClientInfo().tablet : false;
}
} // AEnv

View File

@ -20,7 +20,9 @@ import java.util.Map;
import javax.swing.table.AbstractTableModel;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.editor.WEditor;
import org.adempiere.webui.event.TouchEventHelper;
import org.adempiere.webui.panel.AbstractADWindowPanel;
import org.adempiere.webui.util.SortComparator;
import org.compiere.model.GridField;
@ -335,7 +337,9 @@ public class GridPanel extends Borderlayout implements EventListener<Event>
Center center = new Center();
center.appendChild(listbox);
LayoutUtils.addSclass("mobile-scrolling", center);
if (AEnv.isTablet()) {
LayoutUtils.addSclass("tablet-scrolling", center);
}
this.appendChild(center);
if (pageSize > 0)
@ -352,6 +356,10 @@ public class GridPanel extends Borderlayout implements EventListener<Event>
{
south.setVisible(false);
}
if (AEnv.isTablet()) {
TouchEventHelper.addTabletScrollingFix(listbox);
}
}
private void updateModel() {

View File

@ -25,8 +25,9 @@ import org.compiere.model.MTreeNode;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.DropEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
@ -87,9 +88,10 @@ public class DPFavourites extends DashboardPanel implements EventListener<Event>
// Elaine 2008/07/24
Image img = new Image("/images/Delete24.png");
favToolbar.appendChild(img);
img.setAlign("right");
img.setDroppable(DELETE_FAV_DROPPABLE);
img.addEventListener(Events.ON_DROP, this);
img.setStyle("text-align: right");
img.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Delete")));
img.setDroppable(DELETE_FAV_DROPPABLE);
img.addEventListener(Events.ON_DROP, this);
//
favContent.setDroppable(FAVOURITE_DROPPABLE);
@ -131,13 +133,17 @@ public class DPFavourites extends DashboardPanel implements EventListener<Event>
btnFavItem.addEventListener(Events.ON_DROP, this);
btnFavItem.setSclass("menu-href");
if (getPage() != null)
if (AEnv.isTablet())
{
TouchEventHelper.addOnTapEventListener(btnFavItem, this);
}
else
{
Executions.schedule(AEnv.getDesktop(), this, new Event(ON_ADD_TAP_EVENT_LISTENER, btnFavItem, null));
if (getPage() != null)
{
TouchEventHelper.addOnTapEventListener(btnFavItem, this);
}
else
{
btnFavItem.addEventListener(ON_ADD_TAP_EVENT_LISTENER, this);
Events.echoEvent(new Event(ON_ADD_TAP_EVENT_LISTENER, btnFavItem, null));
}
}
}
}
@ -290,14 +296,9 @@ public class DPFavourites extends DashboardPanel implements EventListener<Event>
btnFavItem.addEventListener(Events.ON_CLICK, this);
btnFavItem.addEventListener(Events.ON_DROP, this);
btnFavItem.setSclass("menu-href");
if (getPage() != null)
{
if (AEnv.isTablet()) {
TouchEventHelper.addOnTapEventListener(btnFavItem, this);
}
else
{
Executions.schedule(AEnv.getDesktop(), this, new Event(ON_ADD_TAP_EVENT_LISTENER, btnFavItem, null));
}
bxFav.removeChild(lblMsg);
bxFav.invalidate();
} else {

View File

@ -25,8 +25,9 @@ import org.compiere.model.MRecentItem;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.DropEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
@ -77,14 +78,16 @@ public class DPRecentItems extends DashboardPanel implements EventListener<Event
Image imgr = new Image("/images/Refresh24.png");
recentItemsToolbar.appendChild(imgr);
imgr.setAlign("left");
imgr.setStyle("text-align: right; cursor: pointer;");
imgr.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Refresh")));
imgr.addEventListener(Events.ON_CLICK, this);
//
Image img = new Image("/images/Delete24.png");
recentItemsToolbar.appendChild(img);
img.setAlign("right");
img.setStyle("text-align: right;");
img.setDroppable(DELETE_RECENTITEMS_DROPPABLE);
img.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Delete")));
img.addEventListener(Events.ON_DROP, this);
//
@ -199,13 +202,17 @@ public class DPRecentItems extends DashboardPanel implements EventListener<Event
btnrecentItem.addEventListener(Events.ON_CLICK, this);
btnrecentItem.addEventListener(Events.ON_DROP, this);
btnrecentItem.setSclass("menu-href");
if (getPage() != null)
if (AEnv.isTablet())
{
TouchEventHelper.addOnTapEventListener(btnrecentItem, this);
}
else
{
Executions.schedule(AEnv.getDesktop(), this, new Event(ON_ADD_TAP_EVENT_LISTENER, btnrecentItem, null));
if (getPage() != null)
{
TouchEventHelper.addOnTapEventListener(btnrecentItem, this);
}
else
{
btnrecentItem.addEventListener(ON_ADD_TAP_EVENT_LISTENER, this);
Events.echoEvent(new Event(ON_ADD_TAP_EVENT_LISTENER, btnrecentItem, null));
}
}
riShown++;

View File

@ -22,6 +22,7 @@ import java.util.Properties;
import org.adempiere.util.ServerContext;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.apps.BusyDialog;
import org.adempiere.webui.component.Tabpanel;
import org.adempiere.webui.component.ToolBarButton;
@ -181,15 +182,18 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
private void renderHomeTab()
{
homeTab.getChildren().clear();
homeTab.addEventListener("onAddMobileScrolling", this);
homeTab.getChildren().clear();
//register as 0
registerWindow(homeTab);
dashboardController.render(homeTab, this);
Events.echoEvent("onAddMobileScrolling", homeTab, null);
if (AEnv.isTablet())
{
homeTab.addEventListener("onAddTabletScrolling", this);
Events.echoEvent("onAddTabletScrolling", homeTab, null);
}
}
public void onEvent(Event event)
@ -210,9 +214,9 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
}
}
}
else if (eventName.equals("onAddMobileScrolling"))
else if (eventName.equals("onAddTabletScrolling"))
{
LayoutUtils.addSclass("mobile-scrolling", homeTab);
LayoutUtils.addSclass("tablet-scrolling", homeTab);
}
}

View File

@ -13,12 +13,14 @@
*****************************************************************************/
package org.adempiere.webui.event;
import org.adempiere.webui.apps.AEnv;
import org.zkoss.zk.au.out.AuScript;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Grid;
/**
* @author hengsin
@ -26,6 +28,8 @@ import org.zkoss.zk.ui.util.Clients;
*/
public class TouchEventHelper implements TouchEvents, EventListener<Event> {
private static final String TOUCH_LISTENER_INIT = "touch.listener.init";
private static final String TABLET_SCROLLING_FIX_INIT = "tablet.scrolling.fix.init";
private static final String TOUCH_TAP_TIME = "touch.tap.time";
private Component touchStart = null;
private long touchStartTime = 0;
@ -44,26 +48,48 @@ public class TouchEventHelper implements TouchEvents, EventListener<Event> {
this.component = component;
}
private void addClientTouchListener(Component component) {
/**
* add client side listener to enable the touchstart, touchmove and touchend event
* @param component
*/
public static void addClientTouchListener(Component component) {
Boolean b = (Boolean) component.getAttribute(TOUCH_LISTENER_INIT);
if (b != null && b.booleanValue())
return;
StringBuilder touchScript = new StringBuilder();
touchScript.append("var widget = zk.Widget.$('")
.append(component.getUuid()).append("');");
touchScript.append("jq(widget.$n()).bind('touchstart',");
touchScript.append("function(event){");
touchScript.append("function(e){");
touchScript.append("var widget = zk.Widget.$('");
touchScript.append(component.getUuid()).append("');");
touchScript.append("var zEvent = new zk.Event(widget, 'onTouchstart', {}, {toServer: true});");
touchScript.append("var zEvent = new zk.Event(widget, 'onTouchstart', {altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey," +
"rotation: e.rotation, scale: e.scale, shiftKey: e.shiftKey}, {toServer: true});");
touchScript.append("zAu.send(zEvent);");
touchScript.append("});");
touchScript.append("jq(widget.$n()).bind('touchend',");
touchScript.append("function(event){");
touchScript.append("function(e){");
touchScript.append("var widget = zk.Widget.$('");
touchScript.append(component.getUuid()).append("');");
touchScript.append("var zEvent = new zk.Event(widget, 'onTouchend', {}, {toServer: true});");
touchScript.append("var zEvent = new zk.Event(widget, 'onTouchend', {altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey," +
"rotation: e.rotation, scale: e.scale, shiftKey: e.shiftKey}, {toServer: true});");
touchScript.append("zAu.send(zEvent);");
touchScript.append("});");
Clients.response(new AuScript(component, touchScript.toString()));
touchScript.append("jq(widget.$n()).bind('touchmove',");
touchScript.append("function(event){");
touchScript.append("var widget = zk.Widget.$('");
touchScript.append(component.getUuid()).append("');");
touchScript.append("var zEvent = new zk.Event(widget, 'onTouchmove', {altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey," +
"rotation: e.rotation, scale: e.scale, shiftKey: e.shiftKey}, {toServer: true});");
touchScript.append("zAu.send(zEvent);");
touchScript.append("});");
Clients.response(new AuScript(component, touchScript.toString()));
component.setAttribute(TOUCH_LISTENER_INIT, Boolean.TRUE);
}
private void registerTouchStart(Event event) {
@ -87,19 +113,60 @@ public class TouchEventHelper implements TouchEvents, EventListener<Event> {
} else if (event.getName().equals(ON_TOUCH_END)) {
registerTouchEnd(event);
if (isTap()) {
reset();
Events.sendEvent(component, new Event(ON_TAP, component, null));
component.setAttribute(TOUCH_TAP_TIME, System.currentTimeMillis());
}
}
}
private void reset() {
touchStart = null;
touchEnd = null;
touchStartTime = 0;
touchEndTime = 0;
}
/**
* add OnTap event hook
* @param component
* @param listener
*/
public static void addOnTapEventListener(Component component, EventListener<Event> listener) {
new TouchEventHelper(component);
component.addEventListener(ON_TAP, listener);
}
/**
*
* @param component
* @return true if onClick should be ignore ( replace by onTap )
*/
public static boolean isIgnoreClick(Component component) {
Number n = (Number) component.getAttribute(TOUCH_TAP_TIME);
return n != null && ((System.currentTimeMillis() - n.longValue()) < (60000));
Boolean b = (Boolean) component.getAttribute(TOUCH_LISTENER_INIT);
if (b != null && b.booleanValue() && AEnv.isTablet())
return true;
else
return false;
}
/**
* iPad: Use client side listener to workaround the issue that grid header doesn't scroll together with body.
* Outstanding Issues: Frozen not working.
* @param grid
*/
public static void addTabletScrollingFix(Grid grid) {
Boolean b = (Boolean) grid.getAttribute(TABLET_SCROLLING_FIX_INIT);
if (b != null && b.booleanValue())
return;
String script = "jq('#" + grid.getUuid() + "-body').bind('scroll'," +
"function(event) { setTimeout(function(){" +
"jq('#" + grid.getUuid() + "-head').scrollLeft(" +
"jq('#" + grid.getUuid() + "-body').scrollLeft()); },0);});";
Clients.response(new AuScript(grid, script));
grid.setAttribute(TABLET_SCROLLING_FIX_INIT, Boolean.TRUE);
}
}

View File

@ -22,5 +22,6 @@ public interface TouchEvents {
public final static String ON_TOUCH_START = "onTouchstart";
public final static String ON_TOUCH_END = "onTouchend";
public final static String ON_TOUCH_MOVE = "onTouchmove";
public final static String ON_TAP = "onTap";
}

View File

@ -30,6 +30,7 @@ import java.util.Properties;
import java.util.logging.Level;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.component.Column;
import org.adempiere.webui.component.Columns;
import org.adempiere.webui.component.EditorBox;
@ -223,8 +224,11 @@ DataStatusListener, IADTabpanel, VetoableChangeListener
formComponent = layout;
treePanel.getTree().addEventListener(Events.ON_SELECT, this);
LayoutUtils.addSclass("mobile-scrolling", west);
LayoutUtils.addSclass("mobile-scrolling", center);
if (AEnv.isTablet())
{
LayoutUtils.addSclass("tablet-scrolling", west);
LayoutUtils.addSclass("tablet-scrolling", center);
}
}
else
{
@ -234,7 +238,10 @@ DataStatusListener, IADTabpanel, VetoableChangeListener
this.appendChild(div);
formComponent = div;
LayoutUtils.addSclass("mobile-scrolling", div);
if (AEnv.isTablet())
{
LayoutUtils.addSclass("tablet-scrolling", div);
}
}
this.appendChild(listPanel);
listPanel.setVisible(false);

View File

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.component.ToolBarButton;
import org.adempiere.webui.event.MenuListener;
import org.adempiere.webui.event.TouchEventHelper;
@ -211,7 +212,9 @@ public class MenuPanel extends Panel implements EventListener<Event>
link.addEventListener(Events.ON_CLICK, this);
link.setSclass("menu-href");
TouchEventHelper.addOnTapEventListener(link, this);
if (AEnv.isTablet()) {
TouchEventHelper.addOnTapEventListener(link, this);
}
}
}
}

View File

@ -527,7 +527,7 @@ img.z-group-img-close {
}
<%-- Tablet --%>
.mobile-scrolling {
.tablet-scrolling {
-webkit-overflow-scrolling: touch;
}