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 48KB

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