Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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