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.

VView.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.HashSet;
  7. import java.util.Iterator;
  8. import java.util.LinkedHashSet;
  9. import java.util.Set;
  10. import com.google.gwt.dom.client.DivElement;
  11. import com.google.gwt.dom.client.Document;
  12. import com.google.gwt.event.dom.client.DomEvent.Type;
  13. import com.google.gwt.event.logical.shared.ResizeEvent;
  14. import com.google.gwt.event.logical.shared.ResizeHandler;
  15. import com.google.gwt.event.shared.EventHandler;
  16. import com.google.gwt.event.shared.HandlerRegistration;
  17. import com.google.gwt.user.client.Command;
  18. import com.google.gwt.user.client.DOM;
  19. import com.google.gwt.user.client.DeferredCommand;
  20. import com.google.gwt.user.client.Element;
  21. import com.google.gwt.user.client.Event;
  22. import com.google.gwt.user.client.Timer;
  23. import com.google.gwt.user.client.Window;
  24. import com.google.gwt.user.client.ui.RootPanel;
  25. import com.google.gwt.user.client.ui.SimplePanel;
  26. import com.google.gwt.user.client.ui.Widget;
  27. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  28. import com.vaadin.terminal.gwt.client.BrowserInfo;
  29. import com.vaadin.terminal.gwt.client.Container;
  30. import com.vaadin.terminal.gwt.client.Focusable;
  31. import com.vaadin.terminal.gwt.client.Paintable;
  32. import com.vaadin.terminal.gwt.client.RenderSpace;
  33. import com.vaadin.terminal.gwt.client.UIDL;
  34. import com.vaadin.terminal.gwt.client.Util;
  35. import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  36. /**
  37. *
  38. */
  39. public class VView extends SimplePanel implements Container, ResizeHandler,
  40. Window.ClosingHandler, ShortcutActionHandlerOwner {
  41. private static final String CLASSNAME = "v-view";
  42. private String theme;
  43. private Paintable layout;
  44. private final LinkedHashSet<VWindow> subWindows = new LinkedHashSet<VWindow>();
  45. private String id;
  46. private ShortcutActionHandler actionHandler;
  47. /** stored width for IE resize optimization */
  48. private int width;
  49. /** stored height for IE resize optimization */
  50. private int height;
  51. private ApplicationConnection connection;
  52. /**
  53. * We are postponing resize process with IE. IE bugs with scrollbars in some
  54. * situations, that causes false onWindowResized calls. With Timer we will
  55. * give IE some time to decide if it really wants to keep current size
  56. * (scrollbars).
  57. */
  58. private Timer resizeTimer;
  59. private int scrollTop;
  60. private int scrollLeft;
  61. private boolean rendering;
  62. private boolean scrollable;
  63. private boolean immediate;
  64. /**
  65. * Reference to the parent frame/iframe. Null if there is no parent (i)frame
  66. * or if the application and parent frame are in different domains.
  67. */
  68. private Element parentFrame;
  69. private ClickEventHandler clickEventHandler = new ClickEventHandler(this,
  70. VPanel.CLICK_EVENT_IDENTIFIER) {
  71. @Override
  72. protected <H extends EventHandler> HandlerRegistration registerHandler(
  73. H handler, Type<H> type) {
  74. return addDomHandler(handler, type);
  75. }
  76. };
  77. public VView() {
  78. super();
  79. setStyleName(CLASSNAME);
  80. }
  81. public String getTheme() {
  82. return theme;
  83. }
  84. /**
  85. * Used to reload host page on theme changes.
  86. */
  87. private static native void reloadHostPage()
  88. /*-{
  89. $wnd.location.reload();
  90. }-*/;
  91. /**
  92. * Evaluate the given script in the browser document.
  93. *
  94. * @param script
  95. * Script to be executed.
  96. */
  97. private static native void eval(String script)
  98. /*-{
  99. try {
  100. if (script == null) return;
  101. $wnd.eval(script);
  102. } catch (e) {
  103. }
  104. }-*/;
  105. /**
  106. * Returns true if the body is NOT generated, i.e if someone else has made
  107. * the page that we're running in. Otherwise we're in charge of the whole
  108. * page.
  109. *
  110. * @return true if we're running embedded
  111. */
  112. public boolean isEmbedded() {
  113. return !getElement().getOwnerDocument().getBody().getClassName()
  114. .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME);
  115. }
  116. public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
  117. rendering = true;
  118. id = uidl.getId();
  119. boolean firstPaint = connection == null;
  120. connection = client;
  121. immediate = uidl.hasAttribute("immediate");
  122. String newTheme = uidl.getStringAttribute("theme");
  123. if (theme != null && !newTheme.equals(theme)) {
  124. // Complete page refresh is needed due css can affect layout
  125. // calculations etc
  126. reloadHostPage();
  127. } else {
  128. theme = newTheme;
  129. }
  130. if (uidl.hasAttribute("style")) {
  131. setStyleName(getStylePrimaryName() + " "
  132. + uidl.getStringAttribute("style"));
  133. }
  134. if (uidl.hasAttribute("name")) {
  135. client.setWindowName(uidl.getStringAttribute("name"));
  136. }
  137. clickEventHandler.handleEventHandlerRegistration(client);
  138. if (!isEmbedded()) {
  139. // only change window title if we're in charge of the whole page
  140. com.google.gwt.user.client.Window.setTitle(uidl
  141. .getStringAttribute("caption"));
  142. }
  143. // Process children
  144. int childIndex = 0;
  145. // Open URL:s
  146. boolean isClosed = false; // was this window closed?
  147. while (childIndex < uidl.getChildCount()
  148. && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
  149. final UIDL open = uidl.getChildUIDL(childIndex);
  150. final String url = open.getStringAttribute("src");
  151. final String target = open.getStringAttribute("name");
  152. if (target == null) {
  153. // source will be opened to this browser window, but we may have
  154. // to finish rendering this window in case this is a download
  155. // (and window stays open).
  156. DeferredCommand.addCommand(new Command() {
  157. public void execute() {
  158. goTo(url);
  159. }
  160. });
  161. } else if ("_self".equals(target)) {
  162. // This window is closing (for sure). Only other opens are
  163. // relevant in this change. See #3558, #2144
  164. isClosed = true;
  165. goTo(url);
  166. } else {
  167. String options;
  168. if (open.hasAttribute("border")) {
  169. if (open.getStringAttribute("border").equals("minimal")) {
  170. options = "menubar=yes,location=no,status=no";
  171. } else {
  172. options = "menubar=no,location=no,status=no";
  173. }
  174. } else {
  175. options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
  176. }
  177. if (open.hasAttribute("width")) {
  178. int w = open.getIntAttribute("width");
  179. options += ",width=" + w;
  180. }
  181. if (open.hasAttribute("height")) {
  182. int h = open.getIntAttribute("height");
  183. options += ",height=" + h;
  184. }
  185. Window.open(url, target, options);
  186. }
  187. childIndex++;
  188. }
  189. if (isClosed) {
  190. // don't render the content, something else will be opened to this
  191. // browser view
  192. rendering = false;
  193. return;
  194. }
  195. // Draw this application level window
  196. UIDL childUidl = uidl.getChildUIDL(childIndex);
  197. final Paintable lo = client.getPaintable(childUidl);
  198. if (layout != null) {
  199. if (layout != lo) {
  200. // remove old
  201. client.unregisterPaintable(layout);
  202. // add new
  203. setWidget((Widget) lo);
  204. layout = lo;
  205. }
  206. } else {
  207. setWidget((Widget) lo);
  208. layout = lo;
  209. }
  210. layout.updateFromUIDL(childUidl, client);
  211. if (!childUidl.getBooleanAttribute("cached")) {
  212. updateParentFrameSize();
  213. }
  214. // Save currently open subwindows to track which will need to be closed
  215. final HashSet<VWindow> removedSubWindows = new HashSet<VWindow>(
  216. subWindows);
  217. // Handle other UIDL children
  218. while ((childUidl = uidl.getChildUIDL(++childIndex)) != null) {
  219. String tag = childUidl.getTag().intern();
  220. if (tag == "actions") {
  221. if (actionHandler == null) {
  222. actionHandler = new ShortcutActionHandler(id, client);
  223. }
  224. actionHandler.updateActionMap(childUidl);
  225. } else if (tag == "execJS") {
  226. String script = childUidl.getStringAttribute("script");
  227. eval(script);
  228. } else if (tag == "notifications") {
  229. for (final Iterator it = childUidl.getChildIterator(); it
  230. .hasNext();) {
  231. final UIDL notification = (UIDL) it.next();
  232. String html = "";
  233. if (notification.hasAttribute("icon")) {
  234. final String parsedUri = client
  235. .translateVaadinUri(notification
  236. .getStringAttribute("icon"));
  237. html += "<img src=\"" + parsedUri + "\" />";
  238. }
  239. if (notification.hasAttribute("caption")) {
  240. html += "<h1>"
  241. + notification.getStringAttribute("caption")
  242. + "</h1>";
  243. }
  244. if (notification.hasAttribute("message")) {
  245. html += "<p>"
  246. + notification.getStringAttribute("message")
  247. + "</p>";
  248. }
  249. final String style = notification.hasAttribute("style") ? notification
  250. .getStringAttribute("style") : null;
  251. final int position = notification
  252. .getIntAttribute("position");
  253. final int delay = notification.getIntAttribute("delay");
  254. new VNotification(delay).show(html, position, style);
  255. }
  256. } else {
  257. // subwindows
  258. final Paintable w = client.getPaintable(childUidl);
  259. if (subWindows.contains(w)) {
  260. removedSubWindows.remove(w);
  261. } else {
  262. subWindows.add((VWindow) w);
  263. }
  264. w.updateFromUIDL(childUidl, client);
  265. }
  266. }
  267. // Close old windows which where not in UIDL anymore
  268. for (final Iterator<VWindow> rem = removedSubWindows.iterator(); rem
  269. .hasNext();) {
  270. final VWindow w = rem.next();
  271. client.unregisterPaintable(w);
  272. subWindows.remove(w);
  273. w.hide();
  274. }
  275. if (uidl.hasAttribute("focused")) {
  276. // set focused component when render phase is finished
  277. DeferredCommand.addCommand(new Command() {
  278. public void execute() {
  279. final Paintable toBeFocused = uidl.getPaintableAttribute(
  280. "focused", connection);
  281. /*
  282. * Two types of Widgets can be focused, either implementing
  283. * GWT HasFocus of a thinner Vaadin specific Focusable
  284. * interface.
  285. */
  286. if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
  287. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
  288. toBeFocusedWidget.setFocus(true);
  289. } else if (toBeFocused instanceof Focusable) {
  290. ((Focusable) toBeFocused).focus();
  291. } else {
  292. ApplicationConnection.getConsole().log(
  293. "Could not focus component");
  294. }
  295. }
  296. });
  297. }
  298. // Add window listeners on first paint, to prevent premature
  299. // variablechanges
  300. if (firstPaint) {
  301. Window.addWindowClosingHandler(this);
  302. Window.addResizeHandler(this);
  303. }
  304. onResize(Window.getClientWidth(), Window.getClientHeight());
  305. // finally set scroll position from UIDL
  306. if (uidl.hasVariable("scrollTop")) {
  307. scrollable = true;
  308. scrollTop = uidl.getIntVariable("scrollTop");
  309. DOM.setElementPropertyInt(getElement(), "scrollTop", scrollTop);
  310. scrollLeft = uidl.getIntVariable("scrollLeft");
  311. DOM.setElementPropertyInt(getElement(), "scrollLeft", scrollLeft);
  312. } else {
  313. scrollable = false;
  314. }
  315. // Safari workaround must be run after scrollTop is updated as it sets
  316. // scrollTop using a deferred command.
  317. if (BrowserInfo.get().isSafari()) {
  318. Util.runWebkitOverflowAutoFix(getElement());
  319. }
  320. scrollIntoView(uidl);
  321. rendering = false;
  322. }
  323. /**
  324. * Tries to scroll paintable referenced from given UIDL snippet to be
  325. * visible.
  326. *
  327. * @param uidl
  328. */
  329. void scrollIntoView(final UIDL uidl) {
  330. if (uidl.hasAttribute("scrollTo")) {
  331. DeferredCommand.addCommand(new Command() {
  332. public void execute() {
  333. final Paintable paintable = uidl.getPaintableAttribute(
  334. "scrollTo", connection);
  335. ((Widget) paintable).getElement().scrollIntoView();
  336. }
  337. });
  338. }
  339. }
  340. @Override
  341. public void onBrowserEvent(Event event) {
  342. super.onBrowserEvent(event);
  343. int type = DOM.eventGetType(event);
  344. if (type == Event.ONKEYDOWN && actionHandler != null) {
  345. actionHandler.handleKeyboardEvent(event);
  346. return;
  347. } else if (scrollable && type == Event.ONSCROLL) {
  348. updateScrollPosition();
  349. }
  350. }
  351. /**
  352. * Updates scroll position from DOM and saves variables to server.
  353. */
  354. private void updateScrollPosition() {
  355. int oldTop = scrollTop;
  356. int oldLeft = scrollLeft;
  357. scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
  358. scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
  359. if (connection != null && !rendering) {
  360. if (oldTop != scrollTop) {
  361. connection.updateVariable(id, "scrollTop", scrollTop, false);
  362. }
  363. if (oldLeft != scrollLeft) {
  364. connection.updateVariable(id, "scrollLeft", scrollLeft, false);
  365. }
  366. }
  367. }
  368. public void onResize(ResizeEvent event) {
  369. onResize(event.getWidth(), event.getHeight());
  370. }
  371. public void onResize(int wwidth, int wheight) {
  372. if (BrowserInfo.get().isIE()) {
  373. /*
  374. * IE will give us some false resized events due bugs with
  375. * scrollbars. Postponing layout phase to see if size was really
  376. * changed.
  377. */
  378. if (resizeTimer == null) {
  379. resizeTimer = new Timer() {
  380. @Override
  381. public void run() {
  382. boolean changed = false;
  383. if (width != getOffsetWidth()) {
  384. width = getOffsetWidth();
  385. changed = true;
  386. ApplicationConnection.getConsole().log(
  387. "window w" + width);
  388. }
  389. if (height != getOffsetHeight()) {
  390. height = getOffsetHeight();
  391. changed = true;
  392. ApplicationConnection.getConsole().log(
  393. "window h" + height);
  394. }
  395. if (changed) {
  396. ApplicationConnection
  397. .getConsole()
  398. .log("Running layout functions due window resize");
  399. connection.runDescendentsLayout(VView.this);
  400. sendClientResized();
  401. }
  402. }
  403. };
  404. } else {
  405. resizeTimer.cancel();
  406. }
  407. resizeTimer.schedule(200);
  408. } else {
  409. if (wwidth == width && wheight == height) {
  410. // No point in doing resize operations if window size has not
  411. // changed
  412. return;
  413. }
  414. width = Window.getClientWidth();
  415. height = Window.getClientHeight();
  416. ApplicationConnection.getConsole().log(
  417. "Running layout functions due window resize");
  418. connection.runDescendentsLayout(this);
  419. Util.runWebkitOverflowAutoFix(getElement());
  420. sendClientResized();
  421. }
  422. }
  423. /**
  424. * Send new dimensions to the server.
  425. */
  426. private void sendClientResized() {
  427. connection.updateVariable(id, "height", height, false);
  428. connection.updateVariable(id, "width", width, immediate);
  429. }
  430. public native static void goTo(String url)
  431. /*-{
  432. $wnd.location = url;
  433. }-*/;
  434. public void onWindowClosing(Window.ClosingEvent event) {
  435. // Change focus on this window in order to ensure that all state is
  436. // collected from textfields
  437. VTextField.flushChangesFromFocusedTextField();
  438. // Send the closing state to server
  439. connection.updateVariable(id, "close", true, false);
  440. connection.sendPendingVariableChangesSync();
  441. }
  442. private final RenderSpace myRenderSpace = new RenderSpace() {
  443. private int excessHeight = -1;
  444. private int excessWidth = -1;
  445. @Override
  446. public int getHeight() {
  447. return getElement().getOffsetHeight() - getExcessHeight();
  448. }
  449. private int getExcessHeight() {
  450. if (excessHeight < 0) {
  451. detectExcessSize();
  452. }
  453. return excessHeight;
  454. }
  455. private void detectExcessSize() {
  456. // TODO define that iview cannot be themed and decorations should
  457. // get to parent element, then get rid of this expensive and error
  458. // prone function
  459. final String overflow = getElement().getStyle().getProperty(
  460. "overflow");
  461. getElement().getStyle().setProperty("overflow", "hidden");
  462. if (BrowserInfo.get().isIE()
  463. && getElement().getPropertyInt("clientWidth") == 0) {
  464. // can't detect possibly themed border/padding width in some
  465. // situations (with some layout configurations), use empty div
  466. // to measure width properly
  467. DivElement div = Document.get().createDivElement();
  468. div.setInnerHTML("&nbsp;");
  469. div.getStyle().setProperty("overflow", "hidden");
  470. div.getStyle().setProperty("height", "1px");
  471. getElement().appendChild(div);
  472. excessWidth = getElement().getOffsetWidth()
  473. - div.getOffsetWidth();
  474. getElement().removeChild(div);
  475. } else {
  476. excessWidth = getElement().getOffsetWidth()
  477. - getElement().getPropertyInt("clientWidth");
  478. }
  479. excessHeight = getElement().getOffsetHeight()
  480. - getElement().getPropertyInt("clientHeight");
  481. getElement().getStyle().setProperty("overflow", overflow);
  482. }
  483. @Override
  484. public int getWidth() {
  485. int w = getElement().getOffsetWidth() - getExcessWidth();
  486. if (w < 10 && BrowserInfo.get().isIE7()) {
  487. // Overcome an IE7 bug #3295
  488. Util.shakeBodyElement();
  489. w = getElement().getOffsetWidth() - getExcessWidth();
  490. }
  491. return w;
  492. }
  493. private int getExcessWidth() {
  494. if (excessWidth < 0) {
  495. detectExcessSize();
  496. }
  497. return excessWidth;
  498. }
  499. @Override
  500. public int getScrollbarSize() {
  501. return Util.getNativeScrollbarSize();
  502. }
  503. };
  504. public RenderSpace getAllocatedSpace(Widget child) {
  505. return myRenderSpace;
  506. }
  507. public boolean hasChildComponent(Widget component) {
  508. return (component != null && component == layout);
  509. }
  510. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  511. // TODO This is untested as no layouts require this
  512. if (oldComponent != layout) {
  513. return;
  514. }
  515. setWidget(newComponent);
  516. layout = (Paintable) newComponent;
  517. }
  518. public boolean requestLayout(Set<Paintable> child) {
  519. /*
  520. * Can never propagate further and we do not want need to re-layout the
  521. * layout which has caused this request.
  522. */
  523. updateParentFrameSize();
  524. // layout size change may affect its available space (scrollbars)
  525. connection.handleComponentRelativeSize((Widget) layout);
  526. return true;
  527. }
  528. private void updateParentFrameSize() {
  529. if (parentFrame == null) {
  530. return;
  531. }
  532. int childHeight = Util.getRequiredHeight(getWidget().getElement());
  533. int childWidth = Util.getRequiredWidth(getWidget().getElement());
  534. parentFrame.getStyle().setPropertyPx("width", childWidth);
  535. parentFrame.getStyle().setPropertyPx("height", childHeight);
  536. }
  537. private static native Element getParentFrame()
  538. /*-{
  539. try {
  540. var frameElement = $wnd.frameElement;
  541. if (frameElement == null) {
  542. return null;
  543. }
  544. if (frameElement.getAttribute("autoResize") == "true") {
  545. return frameElement;
  546. }
  547. } catch (e) {
  548. }
  549. return null;
  550. }-*/;
  551. public void updateCaption(Paintable component, UIDL uidl) {
  552. // NOP Subwindows never draw caption for their first child (layout)
  553. }
  554. /**
  555. * Return an iterator for current subwindows. This method is meant for
  556. * testing purposes only.
  557. *
  558. * @return
  559. */
  560. public ArrayList<VWindow> getSubWindowList() {
  561. ArrayList<VWindow> windows = new ArrayList<VWindow>(subWindows.size());
  562. for (VWindow widget : subWindows) {
  563. windows.add(widget);
  564. }
  565. return windows;
  566. }
  567. public void init(String rootPanelId) {
  568. DOM.sinkEvents(getElement(), Event.ONKEYDOWN | Event.ONSCROLL);
  569. // iview is focused when created so element needs tabIndex
  570. // 1 due 0 is at the end of natural tabbing order
  571. DOM.setElementProperty(getElement(), "tabIndex", "1");
  572. RootPanel root = RootPanel.get(rootPanelId);
  573. root.add(this);
  574. root.removeStyleName("v-app-loading");
  575. BrowserInfo browser = BrowserInfo.get();
  576. // set focus to iview element by default to listen possible keyboard
  577. // shortcuts
  578. if (browser.isOpera() || browser.isSafari()
  579. && browser.getWebkitVersion() < 526) {
  580. // old webkits don't support focusing div elements
  581. Element fElem = DOM.createInputCheck();
  582. DOM.setStyleAttribute(fElem, "margin", "0");
  583. DOM.setStyleAttribute(fElem, "padding", "0");
  584. DOM.setStyleAttribute(fElem, "border", "0");
  585. DOM.setStyleAttribute(fElem, "outline", "0");
  586. DOM.setStyleAttribute(fElem, "width", "1px");
  587. DOM.setStyleAttribute(fElem, "height", "1px");
  588. DOM.setStyleAttribute(fElem, "position", "absolute");
  589. DOM.setStyleAttribute(fElem, "opacity", "0.1");
  590. DOM.appendChild(getElement(), fElem);
  591. Util.focus(fElem);
  592. } else {
  593. Util.focus(getElement());
  594. }
  595. parentFrame = getParentFrame();
  596. }
  597. public ShortcutActionHandler getShortcutActionHandler() {
  598. return actionHandler;
  599. }
  600. }