You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Util.java 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.Arrays;
  7. import java.util.Collection;
  8. import java.util.Iterator;
  9. import java.util.List;
  10. import com.google.gwt.core.client.Scheduler;
  11. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  12. import com.google.gwt.dom.client.Document;
  13. import com.google.gwt.dom.client.NativeEvent;
  14. import com.google.gwt.dom.client.Node;
  15. import com.google.gwt.dom.client.NodeList;
  16. import com.google.gwt.dom.client.Style;
  17. import com.google.gwt.dom.client.Touch;
  18. import com.google.gwt.user.client.Command;
  19. import com.google.gwt.user.client.DOM;
  20. import com.google.gwt.user.client.Element;
  21. import com.google.gwt.user.client.Event;
  22. import com.google.gwt.user.client.EventListener;
  23. import com.google.gwt.user.client.Window;
  24. import com.google.gwt.user.client.ui.HasWidgets;
  25. import com.google.gwt.user.client.ui.RootPanel;
  26. import com.google.gwt.user.client.ui.Widget;
  27. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  28. import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
  29. public class Util {
  30. /**
  31. * Helper method for debugging purposes.
  32. *
  33. * Stops execution on firefox browsers on a breakpoint.
  34. *
  35. */
  36. public static native void browserDebugger()
  37. /*-{
  38. if($wnd.console)
  39. debugger;
  40. }-*/;
  41. /**
  42. *
  43. * Returns the topmost element of from given coordinates.
  44. *
  45. * TODO fix crossplat issues clientX vs pageX. See quircksmode. Not critical
  46. * for vaadin as we scroll div istead of page.
  47. *
  48. * @param x
  49. * @param y
  50. * @return the element at given coordinates
  51. */
  52. public static native Element getElementFromPoint(int clientX, int clientY)
  53. /*-{
  54. var el = $wnd.document.elementFromPoint(clientX, clientY);
  55. if(el != null && el.nodeType == 3) {
  56. el = el.parentNode;
  57. }
  58. return el;
  59. }-*/;
  60. /**
  61. * This helper method can be called if components size have been changed
  62. * outside rendering phase. It notifies components parent about the size
  63. * change so it can react.
  64. *
  65. * When using this method, developer should consider if size changes could
  66. * be notified lazily. If lazy flag is true, method will save widget and
  67. * wait for a moment until it notifies parents in chunks. This may vastly
  68. * optimize layout in various situation. Example: if component have a lot of
  69. * images their onload events may fire "layout phase" many times in a short
  70. * period.
  71. *
  72. * @param widget
  73. * @param lazy
  74. * run componentSizeUpdated lazyly
  75. */
  76. public static void notifyParentOfSizeChange(Widget widget, boolean lazy) {
  77. ApplicationConnection applicationConnection = findApplicationConnectionFor(widget);
  78. if (applicationConnection != null) {
  79. applicationConnection.doLayout(lazy);
  80. }
  81. }
  82. private static boolean findAppConnectionWarningDisplayed = false;
  83. private static ApplicationConnection findApplicationConnectionFor(
  84. Widget widget) {
  85. if (!findAppConnectionWarningDisplayed) {
  86. findAppConnectionWarningDisplayed = true;
  87. VConsole.log("Warning: Using Util.findApplicationConnectionFor which should be eliminated once there is a better way to find the ApplicationConnection for a Paintable");
  88. }
  89. List<ApplicationConnection> runningApplications = ApplicationConfiguration
  90. .getRunningApplications();
  91. for (ApplicationConnection applicationConnection : runningApplications) {
  92. ConnectorMap connectorMap = applicationConnection.getConnectorMap();
  93. ComponentConnector connector = connectorMap.getConnector(widget);
  94. if (connector == null) {
  95. continue;
  96. }
  97. if (connector.getConnection() == applicationConnection) {
  98. return applicationConnection;
  99. }
  100. }
  101. return null;
  102. }
  103. public static float parseRelativeSize(String size) {
  104. if (size == null || !size.endsWith("%")) {
  105. return -1;
  106. }
  107. try {
  108. return Float.parseFloat(size.substring(0, size.length() - 1));
  109. } catch (Exception e) {
  110. VConsole.log("Unable to parse relative size");
  111. return -1;
  112. }
  113. }
  114. private static final Element escapeHtmlHelper = DOM.createDiv();
  115. /**
  116. * Converts html entities to text.
  117. *
  118. * @param html
  119. * @return escaped string presentation of given html
  120. */
  121. public static String escapeHTML(String html) {
  122. DOM.setInnerText(escapeHtmlHelper, html);
  123. String escapedText = DOM.getInnerHTML(escapeHtmlHelper);
  124. if (BrowserInfo.get().isIE8()) {
  125. // #7478 IE8 "incorrectly" returns "<br>" for newlines set using
  126. // setInnerText. The same for " " which is converted to "&nbsp;"
  127. escapedText = escapedText.replaceAll("<(BR|br)>", "\n");
  128. escapedText = escapedText.replaceAll("&nbsp;", " ");
  129. }
  130. return escapedText;
  131. }
  132. /**
  133. * Escapes the string so it is safe to write inside an HTML attribute.
  134. *
  135. * @param attribute
  136. * The string to escape
  137. * @return An escaped version of <literal>attribute</literal>.
  138. */
  139. public static String escapeAttribute(String attribute) {
  140. attribute = attribute.replace("\"", "&quot;");
  141. attribute = attribute.replace("'", "&#39;");
  142. attribute = attribute.replace(">", "&gt;");
  143. attribute = attribute.replace("<", "&lt;");
  144. attribute = attribute.replace("&", "&amp;");
  145. return attribute;
  146. }
  147. /**
  148. * Clones given element as in JavaScript.
  149. *
  150. * Deprecate this if there appears similar method into GWT someday.
  151. *
  152. * @param element
  153. * @param deep
  154. * clone child tree also
  155. * @return
  156. */
  157. public static native Element cloneNode(Element element, boolean deep)
  158. /*-{
  159. return element.cloneNode(deep);
  160. }-*/;
  161. public static int measureHorizontalPaddingAndBorder(Element element,
  162. int paddingGuess) {
  163. String originalWidth = DOM.getStyleAttribute(element, "width");
  164. int originalOffsetWidth = element.getOffsetWidth();
  165. int widthGuess = (originalOffsetWidth - paddingGuess);
  166. if (widthGuess < 1) {
  167. widthGuess = 1;
  168. }
  169. DOM.setStyleAttribute(element, "width", widthGuess + "px");
  170. int padding = element.getOffsetWidth() - widthGuess;
  171. DOM.setStyleAttribute(element, "width", originalWidth);
  172. return padding;
  173. }
  174. public static int measureVerticalPaddingAndBorder(Element element,
  175. int paddingGuess) {
  176. String originalHeight = DOM.getStyleAttribute(element, "height");
  177. int originalOffsetHeight = element.getOffsetHeight();
  178. int widthGuess = (originalOffsetHeight - paddingGuess);
  179. if (widthGuess < 1) {
  180. widthGuess = 1;
  181. }
  182. DOM.setStyleAttribute(element, "height", widthGuess + "px");
  183. int padding = element.getOffsetHeight() - widthGuess;
  184. DOM.setStyleAttribute(element, "height", originalHeight);
  185. return padding;
  186. }
  187. public static int measureHorizontalBorder(Element element) {
  188. int borders;
  189. if (BrowserInfo.get().isIE()) {
  190. String width = element.getStyle().getProperty("width");
  191. String height = element.getStyle().getProperty("height");
  192. int offsetWidth = element.getOffsetWidth();
  193. int offsetHeight = element.getOffsetHeight();
  194. if (offsetHeight < 1) {
  195. offsetHeight = 1;
  196. }
  197. if (offsetWidth < 1) {
  198. offsetWidth = 10;
  199. }
  200. element.getStyle().setPropertyPx("height", offsetHeight);
  201. element.getStyle().setPropertyPx("width", offsetWidth);
  202. borders = element.getOffsetWidth() - element.getClientWidth();
  203. element.getStyle().setProperty("width", width);
  204. element.getStyle().setProperty("height", height);
  205. } else {
  206. borders = element.getOffsetWidth()
  207. - element.getPropertyInt("clientWidth");
  208. }
  209. assert borders >= 0;
  210. return borders;
  211. }
  212. public static int measureVerticalBorder(Element element) {
  213. int borders;
  214. if (BrowserInfo.get().isIE()) {
  215. String width = element.getStyle().getProperty("width");
  216. String height = element.getStyle().getProperty("height");
  217. int offsetWidth = element.getOffsetWidth();
  218. int offsetHeight = element.getOffsetHeight();
  219. if (offsetHeight < 1) {
  220. offsetHeight = 1;
  221. }
  222. if (offsetWidth < 1) {
  223. offsetWidth = 10;
  224. }
  225. element.getStyle().setPropertyPx("width", offsetWidth);
  226. element.getStyle().setPropertyPx("height", offsetHeight);
  227. borders = element.getOffsetHeight()
  228. - element.getPropertyInt("clientHeight");
  229. element.getStyle().setProperty("height", height);
  230. element.getStyle().setProperty("width", width);
  231. } else {
  232. borders = element.getOffsetHeight()
  233. - element.getPropertyInt("clientHeight");
  234. }
  235. assert borders >= 0;
  236. return borders;
  237. }
  238. public static int measureMarginLeft(Element element) {
  239. return element.getAbsoluteLeft()
  240. - element.getParentElement().getAbsoluteLeft();
  241. }
  242. public static int setHeightExcludingPaddingAndBorder(Widget widget,
  243. String height, int paddingBorderGuess) {
  244. if (height.equals("")) {
  245. setHeight(widget, "");
  246. return paddingBorderGuess;
  247. } else if (height.endsWith("px")) {
  248. int pixelHeight = Integer.parseInt(height.substring(0,
  249. height.length() - 2));
  250. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  251. pixelHeight, paddingBorderGuess, false);
  252. } else {
  253. // Set the height in unknown units
  254. setHeight(widget, height);
  255. // Use the offsetWidth
  256. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  257. widget.getOffsetHeight(), paddingBorderGuess, true);
  258. }
  259. }
  260. private static void setWidth(Widget widget, String width) {
  261. DOM.setStyleAttribute(widget.getElement(), "width", width);
  262. }
  263. private static void setHeight(Widget widget, String height) {
  264. DOM.setStyleAttribute(widget.getElement(), "height", height);
  265. }
  266. public static int setWidthExcludingPaddingAndBorder(Widget widget,
  267. String width, int paddingBorderGuess) {
  268. if (width.equals("")) {
  269. setWidth(widget, "");
  270. return paddingBorderGuess;
  271. } else if (width.endsWith("px")) {
  272. int pixelWidth = Integer.parseInt(width.substring(0,
  273. width.length() - 2));
  274. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  275. pixelWidth, paddingBorderGuess, false);
  276. } else {
  277. setWidth(widget, width);
  278. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  279. widget.getOffsetWidth(), paddingBorderGuess, true);
  280. }
  281. }
  282. public static int setWidthExcludingPaddingAndBorder(Element element,
  283. int requestedWidth, int horizontalPaddingBorderGuess,
  284. boolean requestedWidthIncludesPaddingBorder) {
  285. int widthGuess = requestedWidth - horizontalPaddingBorderGuess;
  286. if (widthGuess < 0) {
  287. widthGuess = 0;
  288. }
  289. DOM.setStyleAttribute(element, "width", widthGuess + "px");
  290. int captionOffsetWidth = DOM.getElementPropertyInt(element,
  291. "offsetWidth");
  292. int actualPadding = captionOffsetWidth - widthGuess;
  293. if (requestedWidthIncludesPaddingBorder) {
  294. actualPadding += actualPadding;
  295. }
  296. if (actualPadding != horizontalPaddingBorderGuess) {
  297. int w = requestedWidth - actualPadding;
  298. if (w < 0) {
  299. // Cannot set negative width even if we would want to
  300. w = 0;
  301. }
  302. DOM.setStyleAttribute(element, "width", w + "px");
  303. }
  304. return actualPadding;
  305. }
  306. public static int setHeightExcludingPaddingAndBorder(Element element,
  307. int requestedHeight, int verticalPaddingBorderGuess,
  308. boolean requestedHeightIncludesPaddingBorder) {
  309. int heightGuess = requestedHeight - verticalPaddingBorderGuess;
  310. if (heightGuess < 0) {
  311. heightGuess = 0;
  312. }
  313. DOM.setStyleAttribute(element, "height", heightGuess + "px");
  314. int captionOffsetHeight = DOM.getElementPropertyInt(element,
  315. "offsetHeight");
  316. int actualPadding = captionOffsetHeight - heightGuess;
  317. if (requestedHeightIncludesPaddingBorder) {
  318. actualPadding += actualPadding;
  319. }
  320. if (actualPadding != verticalPaddingBorderGuess) {
  321. int h = requestedHeight - actualPadding;
  322. if (h < 0) {
  323. // Cannot set negative height even if we would want to
  324. h = 0;
  325. }
  326. DOM.setStyleAttribute(element, "height", h + "px");
  327. }
  328. return actualPadding;
  329. }
  330. public static String getSimpleName(Object widget) {
  331. if (widget == null) {
  332. return "(null)";
  333. }
  334. String name = widget.getClass().getName();
  335. return name.substring(name.lastIndexOf('.') + 1);
  336. }
  337. public static void setFloat(Element element, String value) {
  338. if (BrowserInfo.get().isIE()) {
  339. DOM.setStyleAttribute(element, "styleFloat", value);
  340. } else {
  341. DOM.setStyleAttribute(element, "cssFloat", value);
  342. }
  343. }
  344. private static int detectedScrollbarSize = -1;
  345. public static int getNativeScrollbarSize() {
  346. if (detectedScrollbarSize < 0) {
  347. Element scroller = DOM.createDiv();
  348. scroller.getStyle().setProperty("width", "50px");
  349. scroller.getStyle().setProperty("height", "50px");
  350. scroller.getStyle().setProperty("overflow", "scroll");
  351. scroller.getStyle().setProperty("position", "absolute");
  352. scroller.getStyle().setProperty("marginLeft", "-5000px");
  353. RootPanel.getBodyElement().appendChild(scroller);
  354. detectedScrollbarSize = scroller.getOffsetWidth()
  355. - scroller.getPropertyInt("clientWidth");
  356. RootPanel.getBodyElement().removeChild(scroller);
  357. }
  358. return detectedScrollbarSize;
  359. }
  360. /**
  361. * Run workaround for webkits overflow auto issue.
  362. *
  363. * See: our bug #2138 and https://bugs.webkit.org/show_bug.cgi?id=21462
  364. *
  365. * @param elem
  366. * with overflow auto
  367. */
  368. public static void runWebkitOverflowAutoFix(final Element elem) {
  369. // Add max version if fix lands sometime to Webkit
  370. // Starting from Opera 11.00, also a problem in Opera
  371. if ((BrowserInfo.get().getWebkitVersion() > 0 || BrowserInfo.get()
  372. .getOperaVersion() >= 11) && getNativeScrollbarSize() > 0) {
  373. final String originalOverflow = elem.getStyle().getProperty(
  374. "overflow");
  375. if ("hidden".equals(originalOverflow)) {
  376. return;
  377. }
  378. // check the scrolltop value before hiding the element
  379. final int scrolltop = elem.getScrollTop();
  380. final int scrollleft = elem.getScrollLeft();
  381. elem.getStyle().setProperty("overflow", "hidden");
  382. Scheduler.get().scheduleDeferred(new Command() {
  383. public void execute() {
  384. // Dough, Safari scroll auto means actually just a moped
  385. elem.getStyle().setProperty("overflow", originalOverflow);
  386. if (scrolltop > 0 || elem.getScrollTop() > 0) {
  387. int scrollvalue = scrolltop;
  388. if (scrollvalue == 0) {
  389. // mysterious are the ways of webkits scrollbar
  390. // handling. In some cases webkit reports bad (0)
  391. // scrolltop before hiding the element temporary,
  392. // sometimes after.
  393. scrollvalue = elem.getScrollTop();
  394. }
  395. // fix another bug where scrollbar remains in wrong
  396. // position
  397. elem.setScrollTop(scrollvalue - 1);
  398. elem.setScrollTop(scrollvalue);
  399. }
  400. // fix for #6940 : Table horizontal scroll sometimes not
  401. // updated when collapsing/expanding columns
  402. // Also appeared in Safari 5.1 with webkit 534 (#7667)
  403. if ((BrowserInfo.get().isChrome() || (BrowserInfo.get()
  404. .isSafari() && BrowserInfo.get().getWebkitVersion() >= 534))
  405. && (scrollleft > 0 || elem.getScrollLeft() > 0)) {
  406. int scrollvalue = scrollleft;
  407. if (scrollvalue == 0) {
  408. // mysterious are the ways of webkits scrollbar
  409. // handling. In some cases webkit may report a bad
  410. // (0) scrollleft before hiding the element
  411. // temporary, sometimes after.
  412. scrollvalue = elem.getScrollLeft();
  413. }
  414. // fix another bug where scrollbar remains in wrong
  415. // position
  416. elem.setScrollLeft(scrollvalue - 1);
  417. elem.setScrollLeft(scrollvalue);
  418. }
  419. }
  420. });
  421. }
  422. }
  423. /**
  424. * Parses shared state and fetches the relative size of the component. If a
  425. * dimension is not specified as relative it will return -1. If the shared
  426. * state does not contain width or height specifications this will return
  427. * null.
  428. *
  429. * @param state
  430. * @return
  431. */
  432. public static FloatSize parseRelativeSize(ComponentState state) {
  433. if (state.isUndefinedHeight() && state.isUndefinedWidth()) {
  434. return null;
  435. }
  436. float relativeWidth = Util.parseRelativeSize(state.getWidth());
  437. float relativeHeight = Util.parseRelativeSize(state.getHeight());
  438. FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight);
  439. return relativeSize;
  440. }
  441. @Deprecated
  442. public static boolean isCached(UIDL uidl) {
  443. return uidl.getBooleanAttribute("cached");
  444. }
  445. public static void alert(String string) {
  446. if (true) {
  447. Window.alert(string);
  448. }
  449. }
  450. public static boolean equals(Object a, Object b) {
  451. if (a == null) {
  452. return b == null;
  453. }
  454. return a.equals(b);
  455. }
  456. public static void updateRelativeChildrenAndSendSizeUpdateEvent(
  457. ApplicationConnection client, HasWidgets container, Widget widget) {
  458. notifyParentOfSizeChange(widget, false);
  459. }
  460. public static native int getRequiredWidth(
  461. com.google.gwt.dom.client.Element element)
  462. /*-{
  463. if (element.getBoundingClientRect) {
  464. var rect = element.getBoundingClientRect();
  465. return Math.ceil(rect.right - rect.left);
  466. } else {
  467. return element.offsetWidth;
  468. }
  469. }-*/;
  470. public static native int getRequiredHeight(
  471. com.google.gwt.dom.client.Element element)
  472. /*-{
  473. var height;
  474. if (element.getBoundingClientRect != null) {
  475. var rect = element.getBoundingClientRect();
  476. height = Math.ceil(rect.bottom - rect.top);
  477. } else {
  478. height = element.offsetHeight;
  479. }
  480. return height;
  481. }-*/;
  482. public static int getRequiredWidth(Widget widget) {
  483. return getRequiredWidth(widget.getElement());
  484. }
  485. public static int getRequiredHeight(Widget widget) {
  486. return getRequiredHeight(widget.getElement());
  487. }
  488. /**
  489. * Detects what is currently the overflow style attribute in given element.
  490. *
  491. * @param pe
  492. * the element to detect
  493. * @return true if auto or scroll
  494. */
  495. public static boolean mayHaveScrollBars(com.google.gwt.dom.client.Element pe) {
  496. String overflow = getComputedStyle(pe, "overflow");
  497. if (overflow != null) {
  498. if (overflow.equals("auto") || overflow.equals("scroll")) {
  499. return true;
  500. } else {
  501. return false;
  502. }
  503. } else {
  504. return false;
  505. }
  506. }
  507. /**
  508. * A simple helper method to detect "computed style" (aka style sheets +
  509. * element styles). Values returned differ a lot depending on browsers.
  510. * Always be very careful when using this.
  511. *
  512. * @param el
  513. * the element from which the style property is detected
  514. * @param p
  515. * the property to detect
  516. * @return String value of style property
  517. */
  518. private static native String getComputedStyle(
  519. com.google.gwt.dom.client.Element el, String p)
  520. /*-{
  521. try {
  522. if (el.currentStyle) {
  523. // IE
  524. return el.currentStyle[p];
  525. } else if (window.getComputedStyle) {
  526. // Sa, FF, Opera
  527. var view = el.ownerDocument.defaultView;
  528. return view.getComputedStyle(el,null).getPropertyValue(p);
  529. } else {
  530. // fall back for non IE, Sa, FF, Opera
  531. return "";
  532. }
  533. } catch (e) {
  534. return "";
  535. }
  536. }-*/;
  537. /**
  538. * Locates the nested child component of <literal>parent</literal> which
  539. * contains the element <literal>element</literal>. The child component is
  540. * also returned if "element" is part of its caption. If
  541. * <literal>element</literal> is not part of any child component, null is
  542. * returned.
  543. *
  544. * This method returns the deepest nested VPaintableWidget.
  545. *
  546. * @param client
  547. * A reference to ApplicationConnection
  548. * @param parent
  549. * The widget that contains <literal>element</literal>.
  550. * @param element
  551. * An element that is a sub element of the parent
  552. * @return The VPaintableWidget which the element is a part of. Null if the
  553. * element does not belong to a child.
  554. */
  555. public static ComponentConnector getConnectorForElement(
  556. ApplicationConnection client, Widget parent, Element element) {
  557. Element rootElement = parent.getElement();
  558. while (element != null && element != rootElement) {
  559. ComponentConnector paintable = ConnectorMap.get(client)
  560. .getConnector(element);
  561. if (paintable == null) {
  562. String ownerPid = VCaption.getCaptionOwnerPid(element);
  563. if (ownerPid != null) {
  564. paintable = (ComponentConnector) ConnectorMap.get(client)
  565. .getConnector(ownerPid);
  566. }
  567. }
  568. if (paintable != null) {
  569. // check that inside the rootElement
  570. while (element != null && element != rootElement) {
  571. element = (Element) element.getParentElement();
  572. }
  573. if (element != rootElement) {
  574. return null;
  575. } else {
  576. return paintable;
  577. }
  578. }
  579. element = (Element) element.getParentElement();
  580. }
  581. return null;
  582. }
  583. /**
  584. * Will (attempt) to focus the given DOM Element.
  585. *
  586. * @param el
  587. * the element to focus
  588. */
  589. public static native void focus(Element el)
  590. /*-{
  591. try {
  592. el.focus();
  593. } catch (e) {
  594. }
  595. }-*/;
  596. /**
  597. * Helper method to find the nearest parent paintable instance by traversing
  598. * the DOM upwards from given element.
  599. *
  600. * @param element
  601. * the element to start from
  602. */
  603. public static ComponentConnector findPaintable(
  604. ApplicationConnection client, Element element) {
  605. Widget widget = Util.findWidget(element, null);
  606. ConnectorMap vPaintableMap = ConnectorMap.get(client);
  607. while (widget != null && !vPaintableMap.isConnector(widget)) {
  608. widget = widget.getParent();
  609. }
  610. return vPaintableMap.getConnector(widget);
  611. }
  612. /**
  613. * Helper method to find first instance of given Widget type found by
  614. * traversing DOM upwards from given element.
  615. *
  616. * @param element
  617. * the element where to start seeking of Widget
  618. * @param class1
  619. * the Widget type to seek for
  620. */
  621. public static <T> T findWidget(Element element,
  622. Class<? extends Widget> class1) {
  623. if (element != null) {
  624. /* First seek for the first EventListener (~Widget) from dom */
  625. EventListener eventListener = null;
  626. while (eventListener == null && element != null) {
  627. eventListener = Event.getEventListener(element);
  628. if (eventListener == null) {
  629. element = (Element) element.getParentElement();
  630. }
  631. }
  632. if (eventListener != null) {
  633. /*
  634. * Then find the first widget of type class1 from widget
  635. * hierarchy
  636. */
  637. Widget w = (Widget) eventListener;
  638. while (w != null) {
  639. if (class1 == null || w.getClass() == class1) {
  640. return (T) w;
  641. }
  642. w = w.getParent();
  643. }
  644. }
  645. }
  646. return null;
  647. }
  648. /**
  649. * Force webkit to redraw an element
  650. *
  651. * @param element
  652. * The element that should be redrawn
  653. */
  654. public static void forceWebkitRedraw(Element element) {
  655. Style style = element.getStyle();
  656. String s = style.getProperty("webkitTransform");
  657. if (s == null || s.length() == 0) {
  658. style.setProperty("webkitTransform", "scale(1)");
  659. } else {
  660. style.setProperty("webkitTransform", "");
  661. }
  662. }
  663. /**
  664. * Detaches and re-attaches the element from its parent. The element is
  665. * reattached at the same position in the DOM as it was before.
  666. *
  667. * Does nothing if the element is not attached to the DOM.
  668. *
  669. * @param element
  670. * The element to detach and re-attach
  671. */
  672. public static void detachAttach(Element element) {
  673. if (element == null) {
  674. return;
  675. }
  676. Node nextSibling = element.getNextSibling();
  677. Node parent = element.getParentNode();
  678. if (parent == null) {
  679. return;
  680. }
  681. parent.removeChild(element);
  682. if (nextSibling == null) {
  683. parent.appendChild(element);
  684. } else {
  685. parent.insertBefore(element, nextSibling);
  686. }
  687. }
  688. public static void sinkOnloadForImages(Element element) {
  689. NodeList<com.google.gwt.dom.client.Element> imgElements = element
  690. .getElementsByTagName("img");
  691. for (int i = 0; i < imgElements.getLength(); i++) {
  692. DOM.sinkEvents((Element) imgElements.getItem(i), Event.ONLOAD);
  693. }
  694. }
  695. /**
  696. * Returns the index of the childElement within its parent.
  697. *
  698. * @param subElement
  699. * @return
  700. */
  701. public static int getChildElementIndex(Element childElement) {
  702. int idx = 0;
  703. Node n = childElement;
  704. while ((n = n.getPreviousSibling()) != null) {
  705. idx++;
  706. }
  707. return idx;
  708. }
  709. private static void printPaintablesInvocations(
  710. ArrayList<MethodInvocation> invocations, String id,
  711. ApplicationConnection c) {
  712. ComponentConnector paintable = (ComponentConnector) ConnectorMap.get(c)
  713. .getConnector(id);
  714. if (paintable != null) {
  715. VConsole.log("\t" + id + " (" + paintable.getClass() + ") :");
  716. for (MethodInvocation invocation : invocations) {
  717. Object[] parameters = invocation.getParameters();
  718. String formattedParams = null;
  719. if (ApplicationConnection.UPDATE_VARIABLE_METHOD
  720. .equals(invocation.getMethodName())
  721. && parameters.length == 2) {
  722. // name, value
  723. Object value = parameters[1];
  724. // TODO paintables inside lists/maps get rendered as
  725. // components in the debug console
  726. String formattedValue = value instanceof ServerConnector ? ((ServerConnector) value)
  727. .getConnectorId() : String.valueOf(value);
  728. formattedParams = parameters[0] + " : " + formattedValue;
  729. }
  730. if (null == formattedParams) {
  731. formattedParams = (null != parameters) ? Arrays
  732. .toString(parameters) : null;
  733. }
  734. VConsole.log("\t\t" + invocation.getInterfaceName() + "."
  735. + invocation.getMethodName() + "(" + formattedParams
  736. + ")");
  737. }
  738. } else {
  739. VConsole.log("\t" + id + ": Warning: no corresponding paintable!");
  740. }
  741. }
  742. static void logVariableBurst(ApplicationConnection c,
  743. ArrayList<MethodInvocation> loggedBurst) {
  744. try {
  745. VConsole.log("Variable burst to be sent to server:");
  746. String curId = null;
  747. ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
  748. for (int i = 0; i < loggedBurst.size(); i++) {
  749. String id = loggedBurst.get(i).getConnectorId();
  750. if (curId == null) {
  751. curId = id;
  752. } else if (!curId.equals(id)) {
  753. printPaintablesInvocations(invocations, curId, c);
  754. invocations.clear();
  755. curId = id;
  756. }
  757. invocations.add(loggedBurst.get(i));
  758. }
  759. if (!invocations.isEmpty()) {
  760. printPaintablesInvocations(invocations, curId, c);
  761. }
  762. } catch (Exception e) {
  763. VConsole.error(e);
  764. }
  765. }
  766. /**
  767. * Temporarily sets the {@code styleProperty} to {@code tempValue} and then
  768. * resets it to its current value. Used mainly to work around rendering
  769. * issues in IE (and possibly in other browsers)
  770. *
  771. * @param element
  772. * The target element
  773. * @param styleProperty
  774. * The name of the property to set
  775. * @param tempValue
  776. * The temporary value
  777. */
  778. public static void setStyleTemporarily(Element element,
  779. final String styleProperty, String tempValue) {
  780. final Style style = element.getStyle();
  781. final String currentValue = style.getProperty(styleProperty);
  782. style.setProperty(styleProperty, tempValue);
  783. element.getOffsetWidth();
  784. style.setProperty(styleProperty, currentValue);
  785. }
  786. /**
  787. * A helper method to return the client position from an event. Returns
  788. * position from either first changed touch (if touch event) or from the
  789. * event itself.
  790. *
  791. * @param event
  792. * @return
  793. */
  794. public static int getTouchOrMouseClientX(Event event) {
  795. if (isTouchEvent(event)) {
  796. return event.getChangedTouches().get(0).getClientX();
  797. } else {
  798. return event.getClientX();
  799. }
  800. }
  801. /**
  802. * A helper method to return the client position from an event. Returns
  803. * position from either first changed touch (if touch event) or from the
  804. * event itself.
  805. *
  806. * @param event
  807. * @return
  808. */
  809. public static int getTouchOrMouseClientY(Event event) {
  810. if (isTouchEvent(event)) {
  811. return event.getChangedTouches().get(0).getClientY();
  812. } else {
  813. return event.getClientY();
  814. }
  815. }
  816. /**
  817. *
  818. * @see #getTouchOrMouseClientY(Event)
  819. * @param currentGwtEvent
  820. * @return
  821. */
  822. public static int getTouchOrMouseClientY(NativeEvent currentGwtEvent) {
  823. return getTouchOrMouseClientY(Event.as(currentGwtEvent));
  824. }
  825. /**
  826. * @see #getTouchOrMouseClientX(Event)
  827. *
  828. * @param event
  829. * @return
  830. */
  831. public static int getTouchOrMouseClientX(NativeEvent event) {
  832. return getTouchOrMouseClientX(Event.as(event));
  833. }
  834. public static boolean isTouchEvent(Event event) {
  835. return event.getType().contains("touch");
  836. }
  837. public static boolean isTouchEvent(NativeEvent event) {
  838. return isTouchEvent(Event.as(event));
  839. }
  840. public static void simulateClickFromTouchEvent(Event touchevent,
  841. Widget widget) {
  842. Touch touch = touchevent.getChangedTouches().get(0);
  843. final NativeEvent createMouseUpEvent = Document.get()
  844. .createMouseUpEvent(0, touch.getScreenX(), touch.getScreenY(),
  845. touch.getClientX(), touch.getClientY(), false, false,
  846. false, false, NativeEvent.BUTTON_LEFT);
  847. final NativeEvent createMouseDownEvent = Document.get()
  848. .createMouseDownEvent(0, touch.getScreenX(),
  849. touch.getScreenY(), touch.getClientX(),
  850. touch.getClientY(), false, false, false, false,
  851. NativeEvent.BUTTON_LEFT);
  852. final NativeEvent createMouseClickEvent = Document.get()
  853. .createClickEvent(0, touch.getScreenX(), touch.getScreenY(),
  854. touch.getClientX(), touch.getClientY(), false, false,
  855. false, false);
  856. /*
  857. * Get target with element from point as we want the actual element, not
  858. * the one that sunk the event.
  859. */
  860. final Element target = getElementFromPoint(touch.getClientX(),
  861. touch.getClientY());
  862. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  863. public void execute() {
  864. try {
  865. target.dispatchEvent(createMouseDownEvent);
  866. target.dispatchEvent(createMouseUpEvent);
  867. target.dispatchEvent(createMouseClickEvent);
  868. } catch (Exception e) {
  869. }
  870. }
  871. });
  872. }
  873. /**
  874. * Gets the currently focused element for Internet Explorer.
  875. *
  876. * @return The currently focused element
  877. */
  878. public native static Element getIEFocusedElement()
  879. /*-{
  880. if ($wnd.document.activeElement) {
  881. return $wnd.document.activeElement;
  882. }
  883. return null;
  884. }-*/
  885. ;
  886. /**
  887. * Kind of stronger version of isAttached(). In addition to std isAttached,
  888. * this method checks that this widget nor any of its parents is hidden. Can
  889. * be e.g used to check whether component should react to some events or
  890. * not.
  891. *
  892. * @param widget
  893. * @return true if attached and displayed
  894. */
  895. public static boolean isAttachedAndDisplayed(Widget widget) {
  896. if (widget.isAttached()) {
  897. /*
  898. * Failfast using offset size, then by iterating the widget tree
  899. */
  900. boolean notZeroSized = widget.getOffsetHeight() > 0
  901. || widget.getOffsetWidth() > 0;
  902. return notZeroSized || checkVisibilityRecursively(widget);
  903. } else {
  904. return false;
  905. }
  906. }
  907. private static boolean checkVisibilityRecursively(Widget widget) {
  908. if (widget.isVisible()) {
  909. Widget parent = widget.getParent();
  910. if (parent == null) {
  911. return true; // root panel
  912. } else {
  913. return checkVisibilityRecursively(parent);
  914. }
  915. } else {
  916. return false;
  917. }
  918. }
  919. /**
  920. * Scrolls an element into view vertically only. Modified version of
  921. * Element.scrollIntoView.
  922. *
  923. * @param elem
  924. * The element to scroll into view
  925. */
  926. public static native void scrollIntoViewVertically(Element elem)
  927. /*-{
  928. var top = elem.offsetTop;
  929. var height = elem.offsetHeight;
  930. if (elem.parentNode != elem.offsetParent) {
  931. top -= elem.parentNode.offsetTop;
  932. }
  933. var cur = elem.parentNode;
  934. while (cur && (cur.nodeType == 1)) {
  935. if (top < cur.scrollTop) {
  936. cur.scrollTop = top;
  937. }
  938. if (top + height > cur.scrollTop + cur.clientHeight) {
  939. cur.scrollTop = (top + height) - cur.clientHeight;
  940. }
  941. var offsetTop = cur.offsetTop;
  942. if (cur.parentNode != cur.offsetParent) {
  943. offsetTop -= cur.parentNode.offsetTop;
  944. }
  945. top += offsetTop - cur.scrollTop;
  946. cur = cur.parentNode;
  947. }
  948. }-*/;
  949. /**
  950. * Checks if the given event is either a touch event or caused by the left
  951. * mouse button
  952. *
  953. * @param event
  954. * @return true if the event is a touch event or caused by the left mouse
  955. * button, false otherwise
  956. */
  957. public static boolean isTouchEventOrLeftMouseButton(Event event) {
  958. boolean touchEvent = Util.isTouchEvent(event);
  959. return touchEvent || event.getButton() == Event.BUTTON_LEFT;
  960. }
  961. /**
  962. * Performs a shallow comparison of the collections.
  963. *
  964. * @param collection1
  965. * The first collection
  966. * @param collection2
  967. * The second collection
  968. * @return true if the collections contain the same elements in the same
  969. * order, false otherwise
  970. */
  971. public static boolean collectionsEquals(Collection collection1,
  972. Collection collection2) {
  973. if (collection1 == null) {
  974. return collection2 == null;
  975. }
  976. if (collection2 == null) {
  977. return false;
  978. }
  979. Iterator<Object> collection1Iterator = collection1.iterator();
  980. Iterator<Object> collection2Iterator = collection2.iterator();
  981. while (collection1Iterator.hasNext()) {
  982. if (!collection2Iterator.hasNext()) {
  983. return false;
  984. }
  985. Object collection1Object = collection1Iterator.next();
  986. Object collection2Object = collection2Iterator.next();
  987. if (collection1Object != collection2Object) {
  988. return false;
  989. }
  990. }
  991. if (collection2Iterator.hasNext()) {
  992. return false;
  993. }
  994. return true;
  995. }
  996. public static String getConnectorString(ServerConnector p) {
  997. if (p == null) {
  998. return "null";
  999. }
  1000. return p.getClass().getName() + " (" + p.getConnectorId() + ")";
  1001. }
  1002. }