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.

WidgetUtil.java 65KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import java.io.Serializable;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.logging.Logger;
  21. import com.google.gwt.core.client.JavaScriptObject;
  22. import com.google.gwt.core.client.Scheduler;
  23. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  24. import com.google.gwt.dom.client.AnchorElement;
  25. import com.google.gwt.dom.client.DivElement;
  26. import com.google.gwt.dom.client.Document;
  27. import com.google.gwt.dom.client.Element;
  28. import com.google.gwt.dom.client.NativeEvent;
  29. import com.google.gwt.dom.client.Node;
  30. import com.google.gwt.dom.client.NodeList;
  31. import com.google.gwt.dom.client.Style;
  32. import com.google.gwt.dom.client.Style.Display;
  33. import com.google.gwt.dom.client.Style.Unit;
  34. import com.google.gwt.dom.client.Touch;
  35. import com.google.gwt.event.dom.client.KeyEvent;
  36. import com.google.gwt.regexp.shared.MatchResult;
  37. import com.google.gwt.regexp.shared.RegExp;
  38. import com.google.gwt.user.client.Command;
  39. import com.google.gwt.user.client.DOM;
  40. import com.google.gwt.user.client.Event;
  41. import com.google.gwt.user.client.EventListener;
  42. import com.google.gwt.user.client.Window;
  43. import com.google.gwt.user.client.ui.RootPanel;
  44. import com.google.gwt.user.client.ui.Widget;
  45. import com.vaadin.shared.ui.ErrorLevel;
  46. import com.vaadin.shared.util.SharedUtil;
  47. /**
  48. * Utility methods which are related to client side code only
  49. */
  50. public class WidgetUtil {
  51. /**
  52. * Helper method for debugging purposes.
  53. *
  54. * Stops execution on firefox browsers on a breakpoint.
  55. *
  56. */
  57. public static native void browserDebugger()
  58. /*-{
  59. if($wnd.console)
  60. debugger;
  61. }-*/;
  62. /**
  63. * Redirects the browser to the given url or refreshes the page if url is
  64. * null
  65. *
  66. * @since 7.6
  67. * @param url
  68. * The url to redirect to or null to refresh
  69. */
  70. public static native void redirect(String url)
  71. /*-{
  72. if (url) {
  73. $wnd.location = url;
  74. } else {
  75. $wnd.location.reload(false);
  76. }
  77. }-*/;
  78. /**
  79. * Helper method for a bug fix #14041. For mozilla getKeyCode return 0 for
  80. * space bar (because space is considered as char). If return 0 use
  81. * getCharCode.
  82. *
  83. * @param event
  84. * @return return key code
  85. * @since 7.2.4
  86. */
  87. public static int getKeyCode(KeyEvent<?> event) {
  88. int keyCode = event.getNativeEvent().getKeyCode();
  89. if (keyCode == 0) {
  90. keyCode = event.getNativeEvent().getCharCode();
  91. }
  92. return keyCode;
  93. }
  94. /**
  95. *
  96. * Returns the topmost element of from given coordinates.
  97. *
  98. * TODO fix crossplat issues clientX vs pageX. See quircksmode. Not critical
  99. * for vaadin as we scroll div istead of page.
  100. *
  101. * @param x
  102. * @param y
  103. * @return the element at given coordinates
  104. */
  105. public static native Element getElementFromPoint(int clientX, int clientY)
  106. /*-{
  107. var el = $wnd.document.elementFromPoint(clientX, clientY);
  108. // Call elementFromPoint two times to make sure IE8 also returns something sensible if the application is running in an iframe
  109. el = $wnd.document.elementFromPoint(clientX, clientY);
  110. if(el != null && el.nodeType == 3) {
  111. el = el.parentNode;
  112. }
  113. return el;
  114. }-*/;
  115. public static float parseRelativeSize(String size) {
  116. if (size == null || !size.endsWith("%")) {
  117. return -1;
  118. }
  119. try {
  120. return Float.parseFloat(size.substring(0, size.length() - 1));
  121. } catch (Exception e) {
  122. getLogger().warning("Unable to parse relative size");
  123. return -1;
  124. }
  125. }
  126. private static final Element escapeHtmlHelper = DOM.createDiv();
  127. /**
  128. * Converts html entities to text.
  129. *
  130. * @param html
  131. * @return escaped string presentation of given html
  132. */
  133. public static String escapeHTML(String html) {
  134. DOM.setInnerText(escapeHtmlHelper, html);
  135. String escapedText = DOM.getInnerHTML(escapeHtmlHelper);
  136. if (BrowserInfo.get().isIE8()) {
  137. // #7478 IE8 "incorrectly" returns "<br>" for newlines set using
  138. // setInnerText. The same for " " which is converted to "&nbsp;"
  139. escapedText = escapedText.replaceAll("<(BR|br)>", "\n");
  140. escapedText = escapedText.replaceAll("&nbsp;", " ");
  141. }
  142. return escapedText;
  143. }
  144. /**
  145. * Escapes the string so it is safe to write inside an HTML attribute.
  146. *
  147. * @param attribute
  148. * The string to escape
  149. * @return An escaped version of <literal>attribute</literal>.
  150. */
  151. public static String escapeAttribute(String attribute) {
  152. if (attribute == null) {
  153. return "";
  154. }
  155. attribute = attribute.replace("\"", "&quot;");
  156. attribute = attribute.replace("'", "&#39;");
  157. attribute = attribute.replace(">", "&gt;");
  158. attribute = attribute.replace("<", "&lt;");
  159. attribute = attribute.replace("&", "&amp;");
  160. return attribute;
  161. }
  162. /**
  163. * Clones given element as in JavaScript.
  164. *
  165. * Deprecate this if there appears similar method into GWT someday.
  166. *
  167. * @param element
  168. * @param deep
  169. * clone child tree also
  170. * @return
  171. */
  172. public static native Element cloneNode(Element element, boolean deep)
  173. /*-{
  174. return element.cloneNode(deep);
  175. }-*/;
  176. public static int measureHorizontalPaddingAndBorder(Element element,
  177. int paddingGuess) {
  178. String originalWidth = DOM.getStyleAttribute(element, "width");
  179. int originalOffsetWidth = element.getOffsetWidth();
  180. int widthGuess = (originalOffsetWidth - paddingGuess);
  181. if (widthGuess < 1) {
  182. widthGuess = 1;
  183. }
  184. element.getStyle().setWidth(widthGuess, Unit.PX);
  185. int padding = element.getOffsetWidth() - widthGuess;
  186. element.getStyle().setProperty("width", originalWidth);
  187. return padding;
  188. }
  189. public static int measureVerticalPaddingAndBorder(Element element,
  190. int paddingGuess) {
  191. String originalHeight = DOM.getStyleAttribute(element, "height");
  192. int originalOffsetHeight = element.getOffsetHeight();
  193. int widthGuess = (originalOffsetHeight - paddingGuess);
  194. if (widthGuess < 1) {
  195. widthGuess = 1;
  196. }
  197. element.getStyle().setHeight(widthGuess, Unit.PX);
  198. int padding = element.getOffsetHeight() - widthGuess;
  199. element.getStyle().setProperty("height", originalHeight);
  200. return padding;
  201. }
  202. public static int measureHorizontalBorder(Element element) {
  203. int borders;
  204. if (BrowserInfo.get().isIE()) {
  205. String width = element.getStyle().getProperty("width");
  206. String height = element.getStyle().getProperty("height");
  207. int offsetWidth = element.getOffsetWidth();
  208. int offsetHeight = element.getOffsetHeight();
  209. if (offsetHeight < 1) {
  210. offsetHeight = 1;
  211. }
  212. if (offsetWidth < 1) {
  213. offsetWidth = 10;
  214. }
  215. element.getStyle().setPropertyPx("height", offsetHeight);
  216. element.getStyle().setPropertyPx("width", offsetWidth);
  217. borders = element.getOffsetWidth() - element.getClientWidth();
  218. element.getStyle().setProperty("width", width);
  219. element.getStyle().setProperty("height", height);
  220. } else {
  221. borders = element.getOffsetWidth()
  222. - element.getPropertyInt("clientWidth");
  223. }
  224. assert borders >= 0;
  225. return borders;
  226. }
  227. public static int measureVerticalBorder(Element element) {
  228. int borders;
  229. if (BrowserInfo.get().isIE()) {
  230. String width = element.getStyle().getProperty("width");
  231. String height = element.getStyle().getProperty("height");
  232. int offsetWidth = element.getOffsetWidth();
  233. int offsetHeight = element.getOffsetHeight();
  234. if (offsetHeight < 1) {
  235. offsetHeight = 1;
  236. }
  237. if (offsetWidth < 1) {
  238. offsetWidth = 10;
  239. }
  240. element.getStyle().setPropertyPx("width", offsetWidth);
  241. element.getStyle().setPropertyPx("height", offsetHeight);
  242. borders = element.getOffsetHeight()
  243. - element.getPropertyInt("clientHeight");
  244. element.getStyle().setProperty("height", height);
  245. element.getStyle().setProperty("width", width);
  246. } else {
  247. borders = element.getOffsetHeight()
  248. - element.getPropertyInt("clientHeight");
  249. }
  250. assert borders >= 0;
  251. return borders;
  252. }
  253. public static int measureMarginLeft(Element element) {
  254. return element.getAbsoluteLeft()
  255. - element.getParentElement().getAbsoluteLeft();
  256. }
  257. public static int setHeightExcludingPaddingAndBorder(Widget widget,
  258. String height, int paddingBorderGuess) {
  259. if (height.equals("")) {
  260. setHeight(widget, "");
  261. return paddingBorderGuess;
  262. } else if (height.endsWith("px")) {
  263. int pixelHeight = Integer
  264. .parseInt(height.substring(0, height.length() - 2));
  265. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  266. pixelHeight, paddingBorderGuess, false);
  267. } else {
  268. // Set the height in unknown units
  269. setHeight(widget, height);
  270. // Use the offsetWidth
  271. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  272. widget.getOffsetHeight(), paddingBorderGuess, true);
  273. }
  274. }
  275. private static void setWidth(Widget widget, String width) {
  276. widget.getElement().getStyle().setProperty("width", width);
  277. }
  278. private static void setHeight(Widget widget, String height) {
  279. widget.getElement().getStyle().setProperty("height", height);
  280. }
  281. public static int setWidthExcludingPaddingAndBorder(Widget widget,
  282. String width, int paddingBorderGuess) {
  283. if (width.equals("")) {
  284. setWidth(widget, "");
  285. return paddingBorderGuess;
  286. } else if (width.endsWith("px")) {
  287. int pixelWidth = Integer
  288. .parseInt(width.substring(0, width.length() - 2));
  289. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  290. pixelWidth, paddingBorderGuess, false);
  291. } else {
  292. setWidth(widget, width);
  293. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  294. widget.getOffsetWidth(), paddingBorderGuess, true);
  295. }
  296. }
  297. public static int setWidthExcludingPaddingAndBorder(Element element,
  298. int requestedWidth, int horizontalPaddingBorderGuess,
  299. boolean requestedWidthIncludesPaddingBorder) {
  300. int widthGuess = requestedWidth - horizontalPaddingBorderGuess;
  301. if (widthGuess < 0) {
  302. widthGuess = 0;
  303. }
  304. element.getStyle().setWidth(widthGuess, Unit.PX);
  305. int captionOffsetWidth = DOM.getElementPropertyInt(element,
  306. "offsetWidth");
  307. int actualPadding = captionOffsetWidth - widthGuess;
  308. if (requestedWidthIncludesPaddingBorder) {
  309. actualPadding += actualPadding;
  310. }
  311. if (actualPadding != horizontalPaddingBorderGuess) {
  312. int w = requestedWidth - actualPadding;
  313. if (w < 0) {
  314. // Cannot set negative width even if we would want to
  315. w = 0;
  316. }
  317. element.getStyle().setWidth(w, Unit.PX);
  318. }
  319. return actualPadding;
  320. }
  321. public static int setHeightExcludingPaddingAndBorder(Element element,
  322. int requestedHeight, int verticalPaddingBorderGuess,
  323. boolean requestedHeightIncludesPaddingBorder) {
  324. int heightGuess = requestedHeight - verticalPaddingBorderGuess;
  325. if (heightGuess < 0) {
  326. heightGuess = 0;
  327. }
  328. element.getStyle().setHeight(heightGuess, Unit.PX);
  329. int captionOffsetHeight = DOM.getElementPropertyInt(element,
  330. "offsetHeight");
  331. int actualPadding = captionOffsetHeight - heightGuess;
  332. if (requestedHeightIncludesPaddingBorder) {
  333. actualPadding += actualPadding;
  334. }
  335. if (actualPadding != verticalPaddingBorderGuess) {
  336. int h = requestedHeight - actualPadding;
  337. if (h < 0) {
  338. // Cannot set negative height even if we would want to
  339. h = 0;
  340. }
  341. element.getStyle().setHeight(h, Unit.PX);
  342. }
  343. return actualPadding;
  344. }
  345. public static void setFloat(Element element, String value) {
  346. if (BrowserInfo.get().isIE()) {
  347. element.getStyle().setProperty("styleFloat", value);
  348. } else {
  349. element.getStyle().setProperty("cssFloat", value);
  350. }
  351. }
  352. private static int detectedScrollbarSize = -1;
  353. private static int detectedSubPixelRoundingFactor = -1;
  354. public static int getNativeScrollbarSize() {
  355. if (detectedScrollbarSize < 0) {
  356. Element scroller = DOM.createDiv();
  357. scroller.getStyle().setProperty("width", "50px");
  358. scroller.getStyle().setProperty("height", "50px");
  359. scroller.getStyle().setProperty("overflow", "scroll");
  360. scroller.getStyle().setProperty("position", "absolute");
  361. scroller.getStyle().setProperty("marginLeft", "-5000px");
  362. RootPanel.getBodyElement().appendChild(scroller);
  363. detectedScrollbarSize = scroller.getOffsetWidth()
  364. - scroller.getPropertyInt("clientWidth");
  365. RootPanel.getBodyElement().removeChild(scroller);
  366. }
  367. return detectedScrollbarSize;
  368. }
  369. /**
  370. * Defers the execution of {@link #runWebkitOverflowAutoFix(Element)}
  371. *
  372. * @since 7.2.6
  373. * @param elem
  374. * with overflow auto
  375. */
  376. public static void runWebkitOverflowAutoFixDeferred(final Element elem) {
  377. Scheduler.get().scheduleDeferred(new Command() {
  378. @Override
  379. public void execute() {
  380. WidgetUtil.runWebkitOverflowAutoFix(elem);
  381. }
  382. });
  383. }
  384. /**
  385. * Run workaround for webkits overflow auto issue.
  386. *
  387. * See: our bug #2138 and https://bugs.webkit.org/show_bug.cgi?id=21462
  388. *
  389. * @param elem
  390. * with overflow auto
  391. */
  392. public static void runWebkitOverflowAutoFix(final Element elem) {
  393. // Add max version if fix lands sometime to Webkit
  394. // Starting from Opera 11.00, also a problem in Opera
  395. if (BrowserInfo.get().requiresOverflowAutoFix()) {
  396. final String originalOverflow = elem.getStyle()
  397. .getProperty("overflow");
  398. final String originalOverflowX = elem.getStyle()
  399. .getProperty("overflowX");
  400. final String originalOverflowY = elem.getStyle()
  401. .getProperty("overflowY");
  402. if ("hidden".equals(originalOverflow)
  403. || "hidden".equals(originalOverflowX)
  404. || "hidden".equals(originalOverflowY)) {
  405. return;
  406. }
  407. // check the scrolltop value before hiding the element
  408. final int scrolltop = elem.getScrollTop();
  409. final int scrollleft = elem.getScrollLeft();
  410. elem.getStyle().setProperty("overflow", "hidden");
  411. Scheduler.get().scheduleDeferred(new Command() {
  412. @Override
  413. public void execute() {
  414. // Dough, Safari scroll auto means actually just a moped
  415. elem.getStyle().setProperty("overflow", originalOverflow);
  416. if (!originalOverflowX.isEmpty()) {
  417. elem.getStyle().setProperty("overflowX",
  418. originalOverflowX);
  419. }
  420. if (!originalOverflowY.isEmpty()) {
  421. elem.getStyle().setProperty("overflowY",
  422. originalOverflowY);
  423. }
  424. if (scrolltop > 0 || elem.getScrollTop() > 0) {
  425. int scrollvalue = scrolltop;
  426. if (scrollvalue == 0) {
  427. // mysterious are the ways of webkits scrollbar
  428. // handling. In some cases webkit reports bad (0)
  429. // scrolltop before hiding the element temporary,
  430. // sometimes after.
  431. scrollvalue = elem.getScrollTop();
  432. }
  433. // fix another bug where scrollbar remains in wrong
  434. // position
  435. elem.setScrollTop(scrollvalue - 1);
  436. elem.setScrollTop(scrollvalue);
  437. }
  438. // fix for #6940 : Table horizontal scroll sometimes not
  439. // updated when collapsing/expanding columns
  440. // Also appeared in Safari 5.1 with webkit 534 (#7667)
  441. if ((BrowserInfo.get().isChrome() || (BrowserInfo.get()
  442. .isSafari()
  443. && BrowserInfo.get().getWebkitVersion() >= 534))
  444. && (scrollleft > 0 || elem.getScrollLeft() > 0)) {
  445. int scrollvalue = scrollleft;
  446. if (scrollvalue == 0) {
  447. // mysterious are the ways of webkits scrollbar
  448. // handling. In some cases webkit may report a bad
  449. // (0) scrollleft before hiding the element
  450. // temporary, sometimes after.
  451. scrollvalue = elem.getScrollLeft();
  452. }
  453. // fix another bug where scrollbar remains in wrong
  454. // position
  455. elem.setScrollLeft(scrollvalue - 1);
  456. elem.setScrollLeft(scrollvalue);
  457. }
  458. }
  459. });
  460. }
  461. }
  462. public static void alert(String string) {
  463. if (true) {
  464. Window.alert(string);
  465. }
  466. }
  467. /**
  468. * Gets the border-box width for the given element, i.e. element width +
  469. * border + padding. Always rounds up to nearest integer.
  470. *
  471. * @param element
  472. * The element to check
  473. * @return The border-box width for the element
  474. */
  475. public static int getRequiredWidth(
  476. com.google.gwt.dom.client.Element element) {
  477. int reqWidth = getRequiredWidthBoundingClientRect(element);
  478. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  479. int csSize = getRequiredWidthComputedStyle(element);
  480. if (csSize == reqWidth + 1) {
  481. // If computed style reports one pixel larger than requiredWidth
  482. // we would be rounding in the wrong direction in IE9. Round up
  483. // instead.
  484. // We do not always use csSize as it e.g. for 100% wide Labels
  485. // in GridLayouts produces senseless values (see e.g.
  486. // ThemeTestUI with Runo).
  487. return csSize;
  488. }
  489. }
  490. return reqWidth;
  491. }
  492. /**
  493. * Gets the border-box width for the given element, i.e. element width +
  494. * border + padding.
  495. *
  496. * @since 7.5.1
  497. * @param element
  498. * The element to check
  499. * @return The border-box width for the element
  500. */
  501. public static double getRequiredWidthDouble(
  502. com.google.gwt.dom.client.Element element) {
  503. double reqWidth = getRequiredWidthBoundingClientRectDouble(element);
  504. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  505. double csWidth = getRequiredWidthComputedStyleDouble(element);
  506. if (csWidth > reqWidth && csWidth <= (reqWidth + 1)) {
  507. // IE9 rounds reqHeight to integers BUT sometimes reports wrong
  508. // csHeight it seems, so we only use csHeight if it is within a
  509. // rounding error
  510. return csWidth;
  511. }
  512. }
  513. return reqWidth;
  514. }
  515. /**
  516. * Gets the border-box height for the given element, i.e. element height +
  517. * border + padding. Always rounds up to nearest integer.
  518. *
  519. * @param element
  520. * The element to check
  521. * @return The border-box height for the element
  522. */
  523. public static int getRequiredHeight(
  524. com.google.gwt.dom.client.Element element) {
  525. int reqHeight = getRequiredHeightBoundingClientRect(element);
  526. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  527. int csSize = getRequiredHeightComputedStyle(element);
  528. if (csSize == reqHeight + 1) {
  529. // If computed style reports one pixel larger than
  530. // requiredHeight we would be rounding in the wrong direction in
  531. // IE9. Round up instead.
  532. // We do not always use csSize as it e.g. for 100% wide Labels
  533. // in GridLayouts produces senseless values (see e.g.
  534. // ThemeTestUI with Runo).
  535. return csSize;
  536. }
  537. }
  538. return reqHeight;
  539. }
  540. /**
  541. * Gets the border-box height for the given element, i.e. element height +
  542. * border + padding.
  543. *
  544. * @since 7.5.1
  545. * @param element
  546. * The element to check
  547. * @return The border-box height for the element
  548. */
  549. public static double getRequiredHeightDouble(
  550. com.google.gwt.dom.client.Element element) {
  551. double reqHeight = getRequiredHeightBoundingClientRectDouble(element);
  552. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  553. double csHeight = getRequiredHeightComputedStyleDouble(element);
  554. if (csHeight > reqHeight && csHeight <= (reqHeight + 1)) {
  555. // IE9 rounds reqHeight to integers BUT sometimes reports wrong
  556. // csHeight it seems, so we only use csHeight if it is within a
  557. // rounding error
  558. // Although sometimes it also happens that IE9 returns an
  559. // incorrectly rounded down requiredHeight and a computed height
  560. // which is exactly one larger, hence the "<="...
  561. return csHeight;
  562. }
  563. }
  564. return reqHeight;
  565. }
  566. /**
  567. * Calculates the width of the element's bounding rectangle.
  568. * <p>
  569. * In case the browser doesn't support bounding rectangles, the returned
  570. * value is the offset width.
  571. *
  572. * @param element
  573. * the element of which to calculate the width
  574. * @return the width of the element
  575. */
  576. public static int getRequiredWidthBoundingClientRect(
  577. com.google.gwt.dom.client.Element element) {
  578. return (int) Math
  579. .ceil(getRequiredWidthBoundingClientRectDouble(element));
  580. }
  581. /**
  582. * Calculates the width of the element's bounding rectangle to subpixel
  583. * precision.
  584. * <p>
  585. * In case the browser doesn't support bounding rectangles, the returned
  586. * value is the offset width.
  587. *
  588. * @param element
  589. * the element of which to calculate the width
  590. * @return the subpixel-accurate width of the element
  591. * @since 7.4
  592. */
  593. public static native double getRequiredWidthBoundingClientRectDouble(
  594. com.google.gwt.dom.client.Element element)
  595. /*-{
  596. if (element.getBoundingClientRect) {
  597. var rect = element.getBoundingClientRect();
  598. return rect.right - rect.left;
  599. } else {
  600. return element.offsetWidth;
  601. }
  602. }-*/;
  603. public static int getRequiredHeightComputedStyle(
  604. com.google.gwt.dom.client.Element element) {
  605. return (int) Math.ceil(getRequiredHeightComputedStyleDouble(element));
  606. }
  607. public static native double getRequiredHeightComputedStyleDouble(
  608. com.google.gwt.dom.client.Element element)
  609. /*-{
  610. var cs = element.ownerDocument.defaultView.getComputedStyle(element);
  611. var heightPx = cs.height;
  612. if(heightPx == 'auto'){
  613. // Fallback for inline elements
  614. return @com.vaadin.client.WidgetUtil::getRequiredHeightBoundingClientRectDouble(Lcom/google/gwt/dom/client/Element;)(element);
  615. }
  616. var height = parseFloat(heightPx); // Will automatically skip "px" suffix
  617. var border = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth); // Will automatically skip "px" suffix
  618. var padding = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); // Will automatically skip "px" suffix
  619. return height+border+padding;
  620. }-*/;
  621. public static int getRequiredWidthComputedStyle(
  622. com.google.gwt.dom.client.Element element) {
  623. return (int) Math.ceil(getRequiredWidthComputedStyleDouble(element));
  624. }
  625. public static native int getRequiredWidthComputedStyleDouble(
  626. com.google.gwt.dom.client.Element element)
  627. /*-{
  628. var cs = element.ownerDocument.defaultView.getComputedStyle(element);
  629. var widthPx = cs.width;
  630. if(widthPx == 'auto'){
  631. // Fallback for inline elements
  632. return @com.vaadin.client.WidgetUtil::getRequiredWidthBoundingClientRectDouble(Lcom/google/gwt/dom/client/Element;)(element);
  633. }
  634. var width = parseFloat(widthPx); // Will automatically skip "px" suffix
  635. var border = parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth); // Will automatically skip "px" suffix
  636. var padding = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); // Will automatically skip "px" suffix
  637. return width+border+padding;
  638. }-*/;
  639. /**
  640. * Calculates the height of the element's bounding rectangle.
  641. * <p>
  642. * In case the browser doesn't support bounding rectangles, the returned
  643. * value is the offset height.
  644. *
  645. * @param element
  646. * the element of which to calculate the height
  647. * @return the height of the element
  648. */
  649. public static int getRequiredHeightBoundingClientRect(
  650. com.google.gwt.dom.client.Element element) {
  651. return (int) Math
  652. .ceil(getRequiredHeightBoundingClientRectDouble(element));
  653. }
  654. /**
  655. * Calculates the height of the element's bounding rectangle to subpixel
  656. * precision.
  657. * <p>
  658. * In case the browser doesn't support bounding rectangles, the returned
  659. * value is the offset height.
  660. *
  661. * @param element
  662. * the element of which to calculate the height
  663. * @return the subpixel-accurate height of the element
  664. * @since 7.4
  665. */
  666. public static native double getRequiredHeightBoundingClientRectDouble(
  667. com.google.gwt.dom.client.Element element)
  668. /*-{
  669. var height;
  670. if (element.getBoundingClientRect != null) {
  671. var rect = element.getBoundingClientRect();
  672. height = rect.bottom - rect.top;
  673. } else {
  674. height = element.offsetHeight;
  675. }
  676. return height;
  677. }-*/;
  678. public static int getRequiredWidth(Widget widget) {
  679. return getRequiredWidth(widget.getElement());
  680. }
  681. public static int getRequiredHeight(Widget widget) {
  682. return getRequiredHeight(widget.getElement());
  683. }
  684. /**
  685. * Detects what is currently the overflow style attribute in given element.
  686. *
  687. * @param pe
  688. * the element to detect
  689. * @return true if auto or scroll
  690. */
  691. public static boolean mayHaveScrollBars(
  692. com.google.gwt.dom.client.Element pe) {
  693. String overflow = getComputedStyle(pe, "overflow");
  694. if (overflow != null) {
  695. if (overflow.equals("auto") || overflow.equals("scroll")) {
  696. return true;
  697. } else {
  698. return false;
  699. }
  700. } else {
  701. return false;
  702. }
  703. }
  704. /**
  705. * A simple helper method to detect "computed style" (aka style sheets +
  706. * element styles). Values returned differ a lot depending on browsers.
  707. * Always be very careful when using this.
  708. *
  709. * @param el
  710. * the element from which the style property is detected
  711. * @param p
  712. * the property to detect
  713. * @return String value of style property
  714. */
  715. private static native String getComputedStyle(
  716. com.google.gwt.dom.client.Element el, String p)
  717. /*-{
  718. try {
  719. if (el.currentStyle) {
  720. // IE
  721. return el.currentStyle[p];
  722. } else if (window.getComputedStyle) {
  723. // Sa, FF, Opera
  724. var view = el.ownerDocument.defaultView;
  725. return view.getComputedStyle(el,null).getPropertyValue(p);
  726. } else {
  727. // fall back for non IE, Sa, FF, Opera
  728. return "";
  729. }
  730. } catch (e) {
  731. return "";
  732. }
  733. }-*/;
  734. /**
  735. * Will (attempt) to focus the given DOM Element.
  736. *
  737. * @param el
  738. * the element to focus
  739. */
  740. public static native void focus(Element el)
  741. /*-{
  742. try {
  743. el.focus();
  744. } catch (e) {
  745. }
  746. }-*/;
  747. /**
  748. * Helper method to find first instance of any Widget found by traversing
  749. * DOM upwards from given element.
  750. * <p>
  751. * <strong>Note:</strong> If {@code element} is inside some widget {@code W}
  752. * , <em>and</em> {@code W} in turn is wrapped in a {@link Composite}
  753. * {@code C}, this method will not find {@code W} but returns {@code C}.
  754. * This may also be the case with other Composite-like classes that hijack
  755. * the event handling of their child widget(s).
  756. *
  757. * @param element
  758. * the element where to start seeking of Widget
  759. * @since 7.7.11
  760. */
  761. @SuppressWarnings("unchecked")
  762. public static <T> T findWidget(Element element) {
  763. return findWidget(element, null);
  764. }
  765. /**
  766. * Helper method to find first instance of given Widget type found by
  767. * traversing DOM upwards from given element.
  768. * <p>
  769. * <strong>Note:</strong> If {@code element} is inside some widget {@code W}
  770. * , <em>and</em> {@code W} in turn is wrapped in a {@link Composite}
  771. * {@code C}, this method will not find {@code W}. It returns either
  772. * {@code C} or null, depending on whether the class parameter matches. This
  773. * may also be the case with other Composite-like classes that hijack the
  774. * event handling of their child widget(s).
  775. * <p>
  776. * Only accepts the exact class {@code class1} if not null.
  777. *
  778. * @param element
  779. * the element where to start seeking of Widget
  780. * @param class1
  781. * the Widget type to seek for, null for any
  782. */
  783. @SuppressWarnings("unchecked")
  784. public static <T> T findWidget(Element element,
  785. Class<? extends Widget> class1) {
  786. return findWidget(element, class1, true);
  787. }
  788. /**
  789. * Helper method to find first instance of given Widget type found by
  790. * traversing DOM upwards from given element.
  791. * <p>
  792. * <strong>Note:</strong> If {@code element} is inside some widget {@code W}
  793. * , <em>and</em> {@code W} in turn is wrapped in a {@link Composite} {@code
  794. * C}, this method will not find {@code W}. It returns either {@code C} or
  795. * null, depending on whether the class parameter matches. This may also be
  796. * the case with other Composite-like classes that hijack the event handling
  797. * of their child widget(s).
  798. *
  799. * @param element
  800. * the element where to start seeking of Widget
  801. * @param class1
  802. * the Widget type to seek for
  803. * @param exactMatch
  804. * true to only accept class1, false to also accept its
  805. * superclasses
  806. * @since 7.7.11
  807. */
  808. @SuppressWarnings("unchecked")
  809. public static <T> T findWidget(Element element,
  810. Class<? extends Widget> class1, boolean exactMatch) {
  811. if (element != null) {
  812. /* First seek for the first EventListener (~Widget) from dom */
  813. EventListener eventListener = null;
  814. while (eventListener == null && element != null) {
  815. eventListener = Event.getEventListener(element);
  816. if (eventListener == null) {
  817. element = element.getParentElement();
  818. }
  819. }
  820. if (eventListener instanceof Widget) {
  821. /*
  822. * Then find the first widget of type class1 from widget
  823. * hierarchy
  824. */
  825. Widget w = (Widget) eventListener;
  826. if (class1 == null && w != null) {
  827. return (T) w;
  828. }
  829. while (w != null) {
  830. Class<?> widgetClass = w.getClass();
  831. while (widgetClass != null) {
  832. if (widgetClass == class1) {
  833. return (T) w;
  834. }
  835. // terminate after first check if looking for exact
  836. // match
  837. widgetClass = exactMatch ? null
  838. : widgetClass.getSuperclass();
  839. }
  840. w = w.getParent();
  841. }
  842. }
  843. }
  844. return null;
  845. }
  846. /**
  847. * Force webkit to redraw an element
  848. *
  849. * @param element
  850. * The element that should be redrawn
  851. */
  852. public static void forceWebkitRedraw(Element element) {
  853. Style style = element.getStyle();
  854. String s = style.getProperty("webkitTransform");
  855. if (s == null || s.length() == 0) {
  856. style.setProperty("webkitTransform", "scale(1)");
  857. } else {
  858. style.setProperty("webkitTransform", "");
  859. }
  860. }
  861. /**
  862. * Performs a hack to trigger a re-layout in the IE8. This is usually
  863. * necessary in cases where IE8 "forgets" to update child elements when they
  864. * resize.
  865. *
  866. * @param e
  867. * The element to perform the hack on
  868. */
  869. public static final void forceIE8Redraw(Element e) {
  870. if (BrowserInfo.get().isIE8()) {
  871. forceIERedraw(e);
  872. }
  873. }
  874. /**
  875. * Performs a hack to trigger a re-layout in the IE browser. This is usually
  876. * necessary in cases where IE "forgets" to update child elements when they
  877. * resize.
  878. *
  879. * @since 7.3
  880. * @param e
  881. * The element to perform the hack on
  882. */
  883. public static void forceIERedraw(Element e) {
  884. if (BrowserInfo.get().isIE()) {
  885. setStyleTemporarily(e, "zoom", "1");
  886. }
  887. }
  888. /**
  889. * Detaches and re-attaches the element from its parent. The element is
  890. * reattached at the same position in the DOM as it was before.
  891. *
  892. * Does nothing if the element is not attached to the DOM.
  893. *
  894. * @param element
  895. * The element to detach and re-attach
  896. */
  897. public static void detachAttach(Element element) {
  898. if (element == null) {
  899. return;
  900. }
  901. Node nextSibling = element.getNextSibling();
  902. Node parent = element.getParentNode();
  903. if (parent == null) {
  904. return;
  905. }
  906. parent.removeChild(element);
  907. if (nextSibling == null) {
  908. parent.appendChild(element);
  909. } else {
  910. parent.insertBefore(element, nextSibling);
  911. }
  912. }
  913. public static void sinkOnloadForImages(Element element) {
  914. NodeList<com.google.gwt.dom.client.Element> imgElements = element
  915. .getElementsByTagName("img");
  916. for (int i = 0; i < imgElements.getLength(); i++) {
  917. DOM.sinkEvents(imgElements.getItem(i), Event.ONLOAD);
  918. }
  919. }
  920. /**
  921. * Returns the index of the childElement within its parent.
  922. *
  923. * @param subElement
  924. * @return
  925. */
  926. public static int getChildElementIndex(Element childElement) {
  927. int idx = 0;
  928. Node n = childElement;
  929. while ((n = n.getPreviousSibling()) != null) {
  930. idx++;
  931. }
  932. return idx;
  933. }
  934. /**
  935. * Temporarily sets the {@code styleProperty} to {@code tempValue} and then
  936. * resets it to its current value. Used mainly to work around rendering
  937. * issues in IE (and possibly in other browsers)
  938. *
  939. * @param element
  940. * The target element
  941. * @param styleProperty
  942. * The name of the property to set
  943. * @param tempValue
  944. * The temporary value
  945. */
  946. public static void setStyleTemporarily(Element element,
  947. final String styleProperty, String tempValue) {
  948. final Style style = element.getStyle();
  949. final String currentValue = style.getProperty(styleProperty);
  950. style.setProperty(styleProperty, tempValue);
  951. // Read a style-based property to force the browser to recalculate the
  952. // element's dimensions with the temporary style.
  953. element.getOffsetWidth();
  954. style.setProperty(styleProperty, currentValue);
  955. }
  956. /**
  957. * A helper method to return the client position from an event. Returns
  958. * position from either first changed touch (if touch event) or from the
  959. * event itself.
  960. *
  961. * @param event
  962. * @return
  963. */
  964. public static int getTouchOrMouseClientX(Event event) {
  965. if (isTouchEvent(event)) {
  966. return event.getChangedTouches().get(0).getClientX();
  967. } else {
  968. return event.getClientX();
  969. }
  970. }
  971. /**
  972. * Find the element corresponding to the coordinates in the passed mouse
  973. * event. Please note that this is not always the same as the target of the
  974. * event e.g. if event capture is used.
  975. *
  976. * @param event
  977. * the mouse event to get coordinates from
  978. * @return the element at the coordinates of the event
  979. */
  980. public static Element getElementUnderMouse(NativeEvent event) {
  981. int pageX = getTouchOrMouseClientX(event);
  982. int pageY = getTouchOrMouseClientY(event);
  983. return getElementFromPoint(pageX, pageY);
  984. }
  985. /**
  986. * A helper method to return the client position from an event. Returns
  987. * position from either first changed touch (if touch event) or from the
  988. * event itself.
  989. *
  990. * @param event
  991. * @return
  992. */
  993. public static int getTouchOrMouseClientY(Event event) {
  994. if (isTouchEvent(event)) {
  995. return event.getChangedTouches().get(0).getClientY();
  996. } else {
  997. return event.getClientY();
  998. }
  999. }
  1000. /**
  1001. *
  1002. * @see #getTouchOrMouseClientY(Event)
  1003. * @param currentGwtEvent
  1004. * @return
  1005. */
  1006. public static int getTouchOrMouseClientY(NativeEvent currentGwtEvent) {
  1007. return getTouchOrMouseClientY(Event.as(currentGwtEvent));
  1008. }
  1009. /**
  1010. * @see #getTouchOrMouseClientX(Event)
  1011. *
  1012. * @param event
  1013. * @return
  1014. */
  1015. public static int getTouchOrMouseClientX(NativeEvent event) {
  1016. return getTouchOrMouseClientX(Event.as(event));
  1017. }
  1018. public static boolean isTouchEvent(Event event) {
  1019. return event.getType().contains("touch");
  1020. }
  1021. public static boolean isTouchEvent(NativeEvent event) {
  1022. return isTouchEvent(Event.as(event));
  1023. }
  1024. public static void simulateClickFromTouchEvent(Event touchevent,
  1025. Widget widget) {
  1026. Touch touch = touchevent.getChangedTouches().get(0);
  1027. final NativeEvent createMouseUpEvent = Document.get()
  1028. .createMouseUpEvent(0, touch.getScreenX(), touch.getScreenY(),
  1029. touch.getClientX(), touch.getClientY(), false, false,
  1030. false, false, NativeEvent.BUTTON_LEFT);
  1031. final NativeEvent createMouseDownEvent = Document.get()
  1032. .createMouseDownEvent(0, touch.getScreenX(), touch.getScreenY(),
  1033. touch.getClientX(), touch.getClientY(), false, false,
  1034. false, false, NativeEvent.BUTTON_LEFT);
  1035. final NativeEvent createMouseClickEvent = Document.get()
  1036. .createClickEvent(0, touch.getScreenX(), touch.getScreenY(),
  1037. touch.getClientX(), touch.getClientY(), false, false,
  1038. false, false);
  1039. /*
  1040. * Get target with element from point as we want the actual element, not
  1041. * the one that sunk the event.
  1042. */
  1043. final Element target = getElementFromPoint(touch.getClientX(),
  1044. touch.getClientY());
  1045. /*
  1046. * Fixes infocusable form fields in Safari of iOS 5.x and some Android
  1047. * browsers.
  1048. */
  1049. Widget targetWidget = findWidget(target);
  1050. if (targetWidget instanceof com.google.gwt.user.client.ui.Focusable) {
  1051. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) targetWidget;
  1052. toBeFocusedWidget.setFocus(true);
  1053. } else if (targetWidget instanceof Focusable) {
  1054. ((Focusable) targetWidget).focus();
  1055. }
  1056. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  1057. @Override
  1058. public void execute() {
  1059. try {
  1060. target.dispatchEvent(createMouseDownEvent);
  1061. target.dispatchEvent(createMouseUpEvent);
  1062. target.dispatchEvent(createMouseClickEvent);
  1063. } catch (Exception e) {
  1064. }
  1065. }
  1066. });
  1067. }
  1068. /**
  1069. * Gets the currently focused element.
  1070. *
  1071. * @return The active element or null if no active element could be found.
  1072. */
  1073. public native static Element getFocusedElement()
  1074. /*-{
  1075. if ($wnd.document.activeElement) {
  1076. return $wnd.document.activeElement;
  1077. }
  1078. return null;
  1079. }-*/;
  1080. /**
  1081. * Gets currently focused element and checks if it's editable
  1082. *
  1083. * @since 7.4
  1084. *
  1085. * @return true if focused element is editable
  1086. */
  1087. public static boolean isFocusedElementEditable() {
  1088. Element focusedElement = WidgetUtil.getFocusedElement();
  1089. if (focusedElement != null) {
  1090. String tagName = focusedElement.getTagName();
  1091. String contenteditable = focusedElement
  1092. .getAttribute("contenteditable");
  1093. return "textarea".equalsIgnoreCase(tagName)
  1094. || "input".equalsIgnoreCase(tagName)
  1095. || "true".equalsIgnoreCase(contenteditable);
  1096. }
  1097. return false;
  1098. }
  1099. /**
  1100. * Kind of stronger version of isAttached(). In addition to std isAttached,
  1101. * this method checks that this widget nor any of its parents is hidden. Can
  1102. * be e.g used to check whether component should react to some events or
  1103. * not.
  1104. *
  1105. * @param widget
  1106. * @return true if attached and displayed
  1107. */
  1108. public static boolean isAttachedAndDisplayed(Widget widget) {
  1109. if (widget.isAttached()) {
  1110. /*
  1111. * Failfast using offset size, then by iterating the widget tree
  1112. */
  1113. boolean notZeroSized = widget.getOffsetHeight() > 0
  1114. || widget.getOffsetWidth() > 0;
  1115. return notZeroSized || checkVisibilityRecursively(widget);
  1116. } else {
  1117. return false;
  1118. }
  1119. }
  1120. private static boolean checkVisibilityRecursively(Widget widget) {
  1121. if (widget.isVisible()) {
  1122. Widget parent = widget.getParent();
  1123. if (parent == null) {
  1124. return true; // root panel
  1125. } else {
  1126. return checkVisibilityRecursively(parent);
  1127. }
  1128. } else {
  1129. return false;
  1130. }
  1131. }
  1132. /**
  1133. * Scrolls an element into view vertically only. Modified version of
  1134. * Element.scrollIntoView.
  1135. *
  1136. * @param elem
  1137. * The element to scroll into view
  1138. */
  1139. public static native void scrollIntoViewVertically(Element elem)
  1140. /*-{
  1141. var top = elem.offsetTop;
  1142. var height = elem.offsetHeight;
  1143. if (elem.parentNode != elem.offsetParent) {
  1144. top -= elem.parentNode.offsetTop;
  1145. }
  1146. var cur = elem.parentNode;
  1147. while (cur && (cur.nodeType == 1)) {
  1148. if (top < cur.scrollTop) {
  1149. cur.scrollTop = top;
  1150. }
  1151. if (top + height > cur.scrollTop + cur.clientHeight) {
  1152. cur.scrollTop = (top + height) - cur.clientHeight;
  1153. }
  1154. var offsetTop = cur.offsetTop;
  1155. if (cur.parentNode != cur.offsetParent) {
  1156. offsetTop -= cur.parentNode.offsetTop;
  1157. }
  1158. top += offsetTop - cur.scrollTop;
  1159. cur = cur.parentNode;
  1160. }
  1161. }-*/;
  1162. /**
  1163. * Checks if the given event is either a touch event or caused by the left
  1164. * mouse button
  1165. *
  1166. * @param event
  1167. * @return true if the event is a touch event or caused by the left mouse
  1168. * button, false otherwise
  1169. */
  1170. public static boolean isTouchEventOrLeftMouseButton(Event event) {
  1171. boolean touchEvent = WidgetUtil.isTouchEvent(event);
  1172. return touchEvent || event.getButton() == Event.BUTTON_LEFT;
  1173. }
  1174. /**
  1175. * Resolve a relative URL to an absolute URL based on the current document's
  1176. * location.
  1177. *
  1178. * @param url
  1179. * a string with the relative URL to resolve
  1180. * @return the corresponding absolute URL as a string
  1181. */
  1182. public static String getAbsoluteUrl(String url) {
  1183. if (BrowserInfo.get().isIE8()) {
  1184. // The hard way - must use innerHTML and attach to DOM in IE8
  1185. DivElement divElement = Document.get().createDivElement();
  1186. divElement.getStyle().setDisplay(Display.NONE);
  1187. RootPanel.getBodyElement().appendChild(divElement);
  1188. divElement.setInnerHTML(
  1189. "<a href='" + escapeAttribute(url) + "' ></a>");
  1190. AnchorElement a = divElement.getChild(0).cast();
  1191. String href = a.getHref();
  1192. RootPanel.getBodyElement().removeChild(divElement);
  1193. return href;
  1194. } else {
  1195. AnchorElement a = Document.get().createAnchorElement();
  1196. a.setHref(url);
  1197. return a.getHref();
  1198. }
  1199. }
  1200. /**
  1201. * Sets the selection range of an input element.
  1202. *
  1203. * We need this JSNI function to set selection range so that we can use the
  1204. * optional direction attribute to set the anchor to the end and the focus
  1205. * to the start. This makes Firefox work the same way as other browsers
  1206. * (#13477)
  1207. *
  1208. * @param elem
  1209. * the html input element.
  1210. * @param pos
  1211. * the index of the first selected character.
  1212. * @param length
  1213. * the selection length.
  1214. * @param direction
  1215. * a string indicating the direction in which the selection was
  1216. * performed. This may be "forward" or "backward", or "none" if
  1217. * the direction is unknown or irrelevant.
  1218. *
  1219. * @since 7.3
  1220. */
  1221. public native static void setSelectionRange(Element elem, int pos,
  1222. int length, String direction)
  1223. /*-{
  1224. try {
  1225. elem.setSelectionRange(pos, pos + length, direction);
  1226. } catch (e) {
  1227. // Firefox throws exception if TextBox is not visible, even if attached
  1228. }
  1229. }-*/;
  1230. /**
  1231. * JavaScript hack to prevent text selection in various browsers.
  1232. *
  1233. * @since 7.6
  1234. * @param e
  1235. * element for enabling or disabling text selection
  1236. * @param enable
  1237. * <code>true</code> if selection is enabled; </code>false</code>
  1238. * if not
  1239. */
  1240. public native static void setTextSelectionEnabled(Element e, boolean enable)
  1241. /*-{
  1242. if (!enable) {
  1243. e.ondrag = function () { return false; };
  1244. e.onselectstart = function () { return false; };
  1245. e.style.webkitUserSelect = "none";
  1246. } else {
  1247. e.ondrag = null;
  1248. e.onselectstart = null;
  1249. e.style.webkitUserSelect = "text";
  1250. }
  1251. }-*/;
  1252. /**
  1253. * JavaScript hack to clear text selection in various browsers.
  1254. *
  1255. * @since 7.6
  1256. */
  1257. public native static void clearTextSelection()
  1258. /*-{
  1259. if ($wnd.getSelection) {
  1260. $wnd.getSelection().removeAllRanges();
  1261. }
  1262. }-*/;
  1263. /**
  1264. * The allowed value inaccuracy when comparing two double-typed pixel
  1265. * values.
  1266. * <p>
  1267. * Since we're comparing pixels on a screen, epsilon must be less than 1.
  1268. * 0.49 was deemed a perfectly fine and beautifully round number.
  1269. */
  1270. public static final double PIXEL_EPSILON = 0.49d;
  1271. /**
  1272. * Compares two double values with the error margin of
  1273. * {@link #PIXEL_EPSILON} (i.e. {@value #PIXEL_EPSILON})
  1274. *
  1275. * @param num1
  1276. * the first value for which to compare equality
  1277. * @param num2
  1278. * the second value for which to compare equality
  1279. * @since 7.4
  1280. *
  1281. * @return true if the values are considered equals; false otherwise
  1282. */
  1283. public static boolean pixelValuesEqual(final double num1,
  1284. final double num2) {
  1285. return Math.abs(num1 - num2) <= PIXEL_EPSILON;
  1286. }
  1287. public static native TextRectangle getBoundingClientRect(Element e)
  1288. /*-{
  1289. return e.getBoundingClientRect();
  1290. }-*/;
  1291. public static final class TextRectangle extends JavaScriptObject {
  1292. protected TextRectangle() {
  1293. }
  1294. public native double getBottom()
  1295. /*-{
  1296. return this.bottom;
  1297. }-*/;
  1298. public native double getHeight()
  1299. /*-{
  1300. return this.height;
  1301. }-*/;
  1302. public native double getLeft()
  1303. /*-{
  1304. return this.left;
  1305. }-*/;
  1306. public native double getRight()
  1307. /*-{
  1308. return this.right;
  1309. }-*/;
  1310. public native double getTop()
  1311. /*-{
  1312. return this.top;
  1313. }-*/;
  1314. public native double getWidth()
  1315. /*-{
  1316. return this.width;
  1317. }-*/;
  1318. }
  1319. /**
  1320. * Wrap a css size value and its unit and translate back and forth to the
  1321. * string representation.<br/>
  1322. * Eg. 50%, 123px, ...
  1323. *
  1324. * @since 7.2.6
  1325. * @author Vaadin Ltd
  1326. */
  1327. @SuppressWarnings("serial")
  1328. public static class CssSize implements Serializable {
  1329. /*
  1330. * Map the size units with their type.
  1331. */
  1332. private static Map<String, Unit> type2Unit = new HashMap<String, Style.Unit>();
  1333. static {
  1334. for (Unit unit : Unit.values()) {
  1335. type2Unit.put(unit.getType(), unit);
  1336. }
  1337. }
  1338. /**
  1339. * Gets the unit value by its type.
  1340. *
  1341. * @param type
  1342. * the type of the unit as found in the style.
  1343. * @return the unit value.
  1344. */
  1345. public static Unit unitByType(String type) {
  1346. return type2Unit.get(type);
  1347. }
  1348. /*
  1349. * Regex to parse the size.
  1350. */
  1351. private static final RegExp sizePattern = RegExp
  1352. .compile(SharedUtil.SIZE_PATTERN);
  1353. /**
  1354. * Parse the size from string format to {@link CssSize}.
  1355. *
  1356. * @param s
  1357. * the size as string.
  1358. * @return a {@link CssSize} object.
  1359. */
  1360. public static CssSize fromString(String s) {
  1361. if (s == null) {
  1362. return null;
  1363. }
  1364. s = s.trim();
  1365. if ("".equals(s)) {
  1366. return null;
  1367. }
  1368. float size = 0;
  1369. Unit unit = null;
  1370. MatchResult matcher = sizePattern.exec(s);
  1371. if (matcher.getGroupCount() > 1) {
  1372. size = Float.parseFloat(matcher.getGroup(1));
  1373. if (size < 0) {
  1374. size = -1;
  1375. unit = Unit.PX;
  1376. } else {
  1377. String symbol = matcher.getGroup(2);
  1378. unit = unitByType(symbol);
  1379. }
  1380. } else {
  1381. throw new IllegalArgumentException(
  1382. "Invalid size argument: \"" + s + "\" (should match "
  1383. + sizePattern.getSource() + ")");
  1384. }
  1385. return new CssSize(size, unit);
  1386. }
  1387. /**
  1388. * Creates a {@link CssSize} using a value and its measurement unit.
  1389. *
  1390. * @param value
  1391. * the value.
  1392. * @param unit
  1393. * the unit.
  1394. * @return the {@link CssSize} object.
  1395. */
  1396. public static CssSize fromValueUnit(float value, Unit unit) {
  1397. return new CssSize(value, unit);
  1398. }
  1399. /*
  1400. * The value.
  1401. */
  1402. private final float value;
  1403. /*
  1404. * The measure unit.
  1405. */
  1406. private final Unit unit;
  1407. private CssSize(float value, Unit unit) {
  1408. this.value = value;
  1409. this.unit = unit;
  1410. }
  1411. /**
  1412. * Gets the value for this css size.
  1413. *
  1414. * @return the value.
  1415. */
  1416. public float getValue() {
  1417. return value;
  1418. }
  1419. /**
  1420. * Gets the measurement unit for this css size.
  1421. *
  1422. * @return the unit.
  1423. */
  1424. public Unit getUnit() {
  1425. return unit;
  1426. }
  1427. @Override
  1428. public String toString() {
  1429. return value + unit.getType();
  1430. }
  1431. @Override
  1432. public boolean equals(Object obj) {
  1433. if (obj instanceof CssSize) {
  1434. CssSize size = (CssSize) obj;
  1435. return size.value == value && size.unit == unit;
  1436. }
  1437. return false;
  1438. }
  1439. /**
  1440. * Check whether the two sizes are equals.
  1441. *
  1442. * @param cssSize1
  1443. * the first size to compare.
  1444. * @param cssSize2
  1445. * the other size to compare with the first one.
  1446. * @return true if the two sizes are equals, otherwise false.
  1447. */
  1448. public static boolean equals(String cssSize1, String cssSize2) {
  1449. return CssSize.fromString(cssSize1)
  1450. .equals(CssSize.fromString(cssSize2));
  1451. }
  1452. }
  1453. private static Logger getLogger() {
  1454. return Logger.getLogger(WidgetUtil.class.getName());
  1455. }
  1456. /**
  1457. * Returns the thickness of the given element's top border.
  1458. * <p>
  1459. * The value is determined using computed style when available and
  1460. * calculated otherwise.
  1461. *
  1462. * @since 7.5.0
  1463. * @param element
  1464. * the element to measure
  1465. * @return the top border thickness
  1466. */
  1467. public static double getBorderTopThickness(Element element) {
  1468. return getBorderThickness(element, new String[] { "borderTopWidth" });
  1469. }
  1470. /**
  1471. * Returns the thickness of the given element's bottom border.
  1472. * <p>
  1473. * The value is determined using computed style when available and
  1474. * calculated otherwise.
  1475. *
  1476. * @since 7.5.0
  1477. * @param element
  1478. * the element to measure
  1479. * @return the bottom border thickness
  1480. */
  1481. public static double getBorderBottomThickness(Element element) {
  1482. return getBorderThickness(element,
  1483. new String[] { "borderBottomWidth" });
  1484. }
  1485. /**
  1486. * Returns the combined thickness of the given element's top and bottom
  1487. * borders.
  1488. * <p>
  1489. * The value is determined using computed style when available and
  1490. * calculated otherwise.
  1491. *
  1492. * @since 7.5.0
  1493. * @param element
  1494. * the element to measure
  1495. * @return the top and bottom border thickness
  1496. */
  1497. public static double getBorderTopAndBottomThickness(Element element) {
  1498. return getBorderThickness(element,
  1499. new String[] { "borderTopWidth", "borderBottomWidth" });
  1500. }
  1501. /**
  1502. * Returns the thickness of the given element's left border.
  1503. * <p>
  1504. * The value is determined using computed style when available and
  1505. * calculated otherwise.
  1506. *
  1507. * @since 7.5.0
  1508. * @param element
  1509. * the element to measure
  1510. * @return the left border thickness
  1511. */
  1512. public static double getBorderLeftThickness(Element element) {
  1513. return getBorderThickness(element, new String[] { "borderLeftWidth" });
  1514. }
  1515. /**
  1516. * Returns the thickness of the given element's right border.
  1517. * <p>
  1518. * The value is determined using computed style when available and
  1519. * calculated otherwise.
  1520. *
  1521. * @since 7.5.0
  1522. * @param element
  1523. * the element to measure
  1524. * @return the right border thickness
  1525. */
  1526. public static double getBorderRightThickness(Element element) {
  1527. return getBorderThickness(element, new String[] { "borderRightWidth" });
  1528. }
  1529. /**
  1530. * Returns the thickness of the given element's left and right borders.
  1531. * <p>
  1532. * The value is determined using computed style when available and
  1533. * calculated otherwise.
  1534. *
  1535. * @since 7.5.0
  1536. * @param element
  1537. * the element to measure
  1538. * @return the top border thickness
  1539. */
  1540. public static double getBorderLeftAndRightThickness(Element element) {
  1541. return getBorderThickness(element,
  1542. new String[] { "borderLeftWidth", "borderRightWidth" });
  1543. }
  1544. private static native double getBorderThickness(
  1545. com.google.gwt.dom.client.Element element, String[] borderNames)
  1546. /*-{
  1547. if (typeof $wnd.getComputedStyle === 'function') {
  1548. var computedStyle = $wnd.getComputedStyle(element);
  1549. var width = 0;
  1550. for (i=0; i< borderNames.length; i++) {
  1551. var borderWidth = computedStyle[borderNames[i]];
  1552. width += parseFloat(borderWidth);
  1553. }
  1554. return width;
  1555. } else {
  1556. var parentElement = element.offsetParent;
  1557. var cloneElement = element.cloneNode(false);
  1558. cloneElement.style.boxSizing ="content-box";
  1559. parentElement.appendChild(cloneElement);
  1560. cloneElement.style.height = "10px"; // IE8 wants the height to be set to something...
  1561. var heightWithBorder = cloneElement.offsetHeight;
  1562. for (i=0; i< borderNames.length; i++) {
  1563. cloneElement.style[borderNames[i]] = "0";
  1564. }
  1565. var heightWithoutBorder = cloneElement.offsetHeight;
  1566. parentElement.removeChild(cloneElement);
  1567. return heightWithBorder - heightWithoutBorder;
  1568. }
  1569. }-*/;
  1570. /**
  1571. * Rounds the given size up to a value which the browser will accept.
  1572. *
  1573. * Safari/WebKit uses 1/64th of a pixel to enable using integer math
  1574. * (http://trac.webkit.org/wiki/LayoutUnit).
  1575. *
  1576. * Firefox uses 1/60th of a pixel because it is divisible by three
  1577. * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
  1578. *
  1579. * @since 7.5.1
  1580. * @param size
  1581. * the value to round
  1582. * @return the rounded value
  1583. */
  1584. public static double roundSizeUp(double size) {
  1585. return roundSize(size, true);
  1586. }
  1587. /**
  1588. * Rounds the given size down to a value which the browser will accept.
  1589. *
  1590. * Safari/WebKit uses 1/64th of a pixel to enable using integer math
  1591. * (http://trac.webkit.org/wiki/LayoutUnit).
  1592. *
  1593. * Firefox uses 1/60th of a pixel because it is divisible by three
  1594. * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
  1595. *
  1596. * IE9+ uses 1/100th of a pixel
  1597. *
  1598. * @since 7.5.1
  1599. * @param size
  1600. * the value to round
  1601. * @return the rounded value
  1602. */
  1603. public static double roundSizeDown(double size) {
  1604. return roundSize(size, false);
  1605. }
  1606. private static double roundSize(double size, boolean roundUp) {
  1607. if (BrowserInfo.get().isIE8()) {
  1608. if (roundUp) {
  1609. return Math.ceil(size);
  1610. } else {
  1611. return (int) size;
  1612. }
  1613. }
  1614. double factor = getSubPixelRoundingFactor();
  1615. if (factor < 0 || size < 0) {
  1616. return size;
  1617. }
  1618. if (roundUp) {
  1619. return roundSizeUp(size, factor);
  1620. } else {
  1621. return roundSizeDown(size, factor);
  1622. }
  1623. }
  1624. /**
  1625. * Returns the factor used by browsers to round subpixel values
  1626. *
  1627. * @since 7.5.1
  1628. * @return the factor N used by the browser when storing subpixels as X+Y/N
  1629. */
  1630. private static double getSubPixelRoundingFactor() {
  1631. // Detects how the browser does subpixel rounding
  1632. // Currently Firefox uses 1/60th pixels
  1633. // and Safari uses 1/64th pixels
  1634. // IE 1/100th pixels
  1635. if (detectedSubPixelRoundingFactor != -1) {
  1636. return detectedSubPixelRoundingFactor;
  1637. }
  1638. double probeSize = 0.999999;
  1639. DivElement div = Document.get().createDivElement();
  1640. Document.get().getBody().appendChild(div);
  1641. div.getStyle().setHeight(probeSize, Unit.PX);
  1642. ComputedStyle computedStyle = new ComputedStyle(div);
  1643. double computedHeight = computedStyle.getHeight();
  1644. if (computedHeight < probeSize) {
  1645. // Rounded down by browser, all browsers but Firefox do this
  1646. // today
  1647. detectedSubPixelRoundingFactor = (int) Math
  1648. .round(1.0 / (1.0 - computedHeight));
  1649. } else {
  1650. // Rounded up / to nearest by browser
  1651. probeSize = 1;
  1652. while (computedStyle.getHeight() != 0.0) {
  1653. computedHeight = computedStyle.getHeight();
  1654. probeSize /= 2.0;
  1655. div.getStyle().setHeight(probeSize, Unit.PX);
  1656. }
  1657. detectedSubPixelRoundingFactor = (int) Math
  1658. .round(1.0 / computedHeight);
  1659. }
  1660. div.removeFromParent();
  1661. return detectedSubPixelRoundingFactor;
  1662. }
  1663. private static double roundSizeUp(double size, double divisor) {
  1664. // In: 12.51, 60.0
  1665. // 12
  1666. double integerPart = (int) size;
  1667. // (12.51 - 12) * 60 = 30.6
  1668. double nrFractions = (size - integerPart) * divisor;
  1669. // 12 + ceil(30.6) / 60 = 12 + 31/60 = 12.51666
  1670. return integerPart + (Math.ceil(nrFractions)) / divisor;
  1671. }
  1672. private static double roundSizeDown(double size, double divisor) {
  1673. // In: 12.51, 60.0
  1674. // 12
  1675. double integerPart = (int) size;
  1676. // (12.51 - 12) * 60 = 30.6
  1677. double nrFractions = (size - integerPart) * divisor;
  1678. // 12 + int(30.6) / 60 = 12 + 30/60 = 12.5
  1679. return integerPart + ((int) nrFractions) / divisor;
  1680. }
  1681. /**
  1682. * Returns whether the given element is displayed.
  1683. * <p>
  1684. * This method returns false if either the given element or any of its
  1685. * ancestors has the style {@code display: none} applied.
  1686. *
  1687. * @param element
  1688. * the element to test for visibility
  1689. * @return {@code true} if the element is displayed, {@code false} otherwise
  1690. * @since 7.7.13
  1691. */
  1692. public static native boolean isDisplayed(Element element)
  1693. /*-{
  1694. // This measurement is borrowed from JQuery and measures the visible
  1695. // size of the element. The measurement should return false when either
  1696. // the element or any of its ancestors has "display: none" style.
  1697. return !!(element.offsetWidth || element.offsetHeight
  1698. || element.getClientRects().length);
  1699. }-*/;
  1700. /**
  1701. * Utility methods for displaying error message on components.
  1702. *
  1703. * @since 7.7.11
  1704. */
  1705. public static class ErrorUtil {
  1706. /**
  1707. * Sets the error level style name for the given element and removes all
  1708. * previously applied error level style names. The style name has the
  1709. * {@code prefix-errorLevel} format.
  1710. *
  1711. * @param element
  1712. * element to apply the style name to
  1713. * @param prefix
  1714. * part of the style name before the error level string
  1715. * @param errorLevel
  1716. * error level for which the style will be applied
  1717. */
  1718. public static void setErrorLevelStyle(Element element, String prefix,
  1719. ErrorLevel errorLevel) {
  1720. for (ErrorLevel errorLevelValue : ErrorLevel.values()) {
  1721. String className = prefix + "-"
  1722. + errorLevelValue.toString().toLowerCase();
  1723. if (errorLevel == errorLevelValue) {
  1724. element.addClassName(className);
  1725. } else {
  1726. element.removeClassName(className);
  1727. }
  1728. }
  1729. }
  1730. }
  1731. }