From 12d8c0bcd73fbf8c49127fedf8e23a2b64197171 Mon Sep 17 00:00:00 2001 From: Heng Sin Low Date: Mon, 23 Apr 2012 15:27:22 +0800 Subject: [PATCH] IDEMPIERE-231 Zk6: Improve the tablet experience. Clean up and added tablet useragent detection. --- .../org/adempiere/webui/AdempiereWebUI.java | 21 ++++- .../src/org/adempiere/webui/ClientInfo.java | 2 + .../src/org/adempiere/webui/apps/AEnv.java | 6 ++ .../adempiere/webui/component/GridPanel.java | 10 ++- .../webui/dashboard/DPFavourites.java | 33 ++++---- .../webui/dashboard/DPRecentItems.java | 25 +++--- .../webui/desktop/DefaultDesktop.java | 14 ++-- .../webui/event/TouchEventHelper.java | 81 +++++++++++++++++-- .../adempiere/webui/event/TouchEvents.java | 1 + .../org/adempiere/webui/panel/ADTabpanel.java | 13 ++- .../org/adempiere/webui/panel/MenuPanel.java | 5 +- .../theme/default/css/theme.css.dsp | 2 +- 12 files changed, 166 insertions(+), 47 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java index 63d904abbb..e3c1948cc6 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java @@ -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, 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; + } + } } } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/ClientInfo.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/ClientInfo.java index 9805ba20a2..ee68868fe0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/ClientInfo.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/ClientInfo.java @@ -38,4 +38,6 @@ public class ClientInfo implements Serializable { public int screenHeight; public int screenWidth; public TimeZone timeZone; + public String userAgent; + public boolean tablet; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java index ebab065448..574c0a3780 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/AEnv.java @@ -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 diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/GridPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/GridPanel.java index 0cfc362572..9a5113e991 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/GridPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/GridPanel.java @@ -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 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 { south.setVisible(false); } + + if (AEnv.isTablet()) { + TouchEventHelper.addTabletScrollingFix(listbox); + } } private void updateModel() { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPFavourites.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPFavourites.java index 886942c2c6..ba354737c4 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPFavourites.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPFavourites.java @@ -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 // 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 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 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 { diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java index 9c3c7ac566..f31f2eb29e 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/dashboard/DPRecentItems.java @@ -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 { + 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 { 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 { } 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 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); } } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/event/TouchEvents.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/event/TouchEvents.java index 32dc058c88..feb916cffd 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/event/TouchEvents.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/event/TouchEvents.java @@ -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"; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java index 932e752f23..6d9f260375 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/ADTabpanel.java @@ -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); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/MenuPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/MenuPanel.java index 820e13cc52..08ea83aedd 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/MenuPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/MenuPanel.java @@ -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 link.addEventListener(Events.ON_CLICK, this); link.setSclass("menu-href"); - TouchEventHelper.addOnTapEventListener(link, this); + if (AEnv.isTablet()) { + TouchEventHelper.addOnTapEventListener(link, this); + } } } } diff --git a/org.adempiere.ui.zk/theme/default/css/theme.css.dsp b/org.adempiere.ui.zk/theme/default/css/theme.css.dsp index e6d530ed6c..43da38f913 100644 --- a/org.adempiere.ui.zk/theme/default/css/theme.css.dsp +++ b/org.adempiere.ui.zk/theme/default/css/theme.css.dsp @@ -527,7 +527,7 @@ img.z-group-img-close { } <%-- Tablet --%> -.mobile-scrolling { +.tablet-scrolling { -webkit-overflow-scrolling: touch; }