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.

RootConnector.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.HashSet;
  6. import java.util.Iterator;
  7. import com.google.gwt.core.client.GWT;
  8. import com.google.gwt.core.client.Scheduler;
  9. import com.google.gwt.dom.client.NativeEvent;
  10. import com.google.gwt.dom.client.Style;
  11. import com.google.gwt.dom.client.Style.Position;
  12. import com.google.gwt.event.shared.HandlerRegistration;
  13. import com.google.gwt.user.client.Command;
  14. import com.google.gwt.user.client.DOM;
  15. import com.google.gwt.user.client.Event;
  16. import com.google.gwt.user.client.History;
  17. import com.google.gwt.user.client.Window;
  18. import com.google.gwt.user.client.ui.RootPanel;
  19. import com.google.gwt.user.client.ui.Widget;
  20. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  21. import com.vaadin.terminal.gwt.client.BrowserInfo;
  22. import com.vaadin.terminal.gwt.client.ComponentConnector;
  23. import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent;
  24. import com.vaadin.terminal.gwt.client.ConnectorMap;
  25. import com.vaadin.terminal.gwt.client.Focusable;
  26. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  27. import com.vaadin.terminal.gwt.client.Paintable;
  28. import com.vaadin.terminal.gwt.client.UIDL;
  29. import com.vaadin.terminal.gwt.client.Util;
  30. import com.vaadin.terminal.gwt.client.VConsole;
  31. import com.vaadin.terminal.gwt.client.communication.RpcProxy;
  32. import com.vaadin.terminal.gwt.client.communication.ServerRpc;
  33. import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
  34. import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
  35. import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle;
  36. import com.vaadin.ui.Root;
  37. @Component(value = Root.class, loadStyle = LoadStyle.EAGER)
  38. public class RootConnector extends AbstractComponentContainerConnector
  39. implements Paintable {
  40. public interface RootServerRPC extends ClickRPC, ServerRpc {
  41. }
  42. private RootServerRPC rpc;
  43. private HandlerRegistration childStateChangeHandlerRegistration;
  44. private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
  45. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  46. // TODO Should use a more specific handler that only reacts to
  47. // size changes
  48. onChildSizeChange();
  49. }
  50. };
  51. @Override
  52. protected void init() {
  53. super.init();
  54. rpc = RpcProxy.create(RootServerRPC.class, this);
  55. }
  56. public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
  57. ConnectorMap paintableMap = ConnectorMap.get(getConnection());
  58. getWidget().rendering = true;
  59. getWidget().id = getConnectorId();
  60. boolean firstPaint = getWidget().connection == null;
  61. getWidget().connection = client;
  62. getWidget().immediate = getState().isImmediate();
  63. getWidget().resizeLazy = uidl.hasAttribute(VView.RESIZE_LAZY);
  64. String newTheme = uidl.getStringAttribute("theme");
  65. if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) {
  66. // Complete page refresh is needed due css can affect layout
  67. // calculations etc
  68. getWidget().reloadHostPage();
  69. } else {
  70. getWidget().theme = newTheme;
  71. }
  72. // this also implicitly removes old styles
  73. String styles = "";
  74. styles += getWidget().getStylePrimaryName() + " ";
  75. if (getState().hasStyles()) {
  76. for (String style : getState().getStyles()) {
  77. styles += style + " ";
  78. }
  79. }
  80. if (!client.getConfiguration().isStandalone()) {
  81. styles += getWidget().getStylePrimaryName() + "-embedded";
  82. }
  83. getWidget().setStyleName(styles.trim());
  84. clickEventHandler.handleEventHandlerRegistration();
  85. if (!getWidget().isEmbedded() && getState().getCaption() != null) {
  86. // only change window title if we're in charge of the whole page
  87. com.google.gwt.user.client.Window.setTitle(getState().getCaption());
  88. }
  89. // Process children
  90. int childIndex = 0;
  91. // Open URL:s
  92. boolean isClosed = false; // was this window closed?
  93. while (childIndex < uidl.getChildCount()
  94. && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
  95. final UIDL open = uidl.getChildUIDL(childIndex);
  96. final String url = client.translateVaadinUri(open
  97. .getStringAttribute("src"));
  98. final String target = open.getStringAttribute("name");
  99. if (target == null) {
  100. // source will be opened to this browser window, but we may have
  101. // to finish rendering this window in case this is a download
  102. // (and window stays open).
  103. Scheduler.get().scheduleDeferred(new Command() {
  104. public void execute() {
  105. VView.goTo(url);
  106. }
  107. });
  108. } else if ("_self".equals(target)) {
  109. // This window is closing (for sure). Only other opens are
  110. // relevant in this change. See #3558, #2144
  111. isClosed = true;
  112. VView.goTo(url);
  113. } else {
  114. String options;
  115. if (open.hasAttribute("border")) {
  116. if (open.getStringAttribute("border").equals("minimal")) {
  117. options = "menubar=yes,location=no,status=no";
  118. } else {
  119. options = "menubar=no,location=no,status=no";
  120. }
  121. } else {
  122. options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
  123. }
  124. if (open.hasAttribute("width")) {
  125. int w = open.getIntAttribute("width");
  126. options += ",width=" + w;
  127. }
  128. if (open.hasAttribute("height")) {
  129. int h = open.getIntAttribute("height");
  130. options += ",height=" + h;
  131. }
  132. Window.open(url, target, options);
  133. }
  134. childIndex++;
  135. }
  136. if (isClosed) {
  137. // don't render the content, something else will be opened to this
  138. // browser view
  139. getWidget().rendering = false;
  140. return;
  141. }
  142. // Draw this application level window
  143. UIDL childUidl = uidl.getChildUIDL(childIndex);
  144. final ComponentConnector lo = client.getPaintable(childUidl);
  145. boolean layoutChanged = getWidget().layout != lo;
  146. if (getWidget().layout != null) {
  147. if (layoutChanged) {
  148. // remove old
  149. client.unregisterPaintable(getWidget().layout);
  150. if (childStateChangeHandlerRegistration != null) {
  151. childStateChangeHandlerRegistration.removeHandler();
  152. childStateChangeHandlerRegistration = null;
  153. }
  154. // add new
  155. getWidget().setWidget(lo.getWidget());
  156. getWidget().layout = lo;
  157. }
  158. } else {
  159. getWidget().setWidget(lo.getWidget());
  160. getWidget().layout = lo;
  161. if (layoutChanged) {
  162. childStateChangeHandlerRegistration = lo
  163. .addStateChangeHandler(childStateChangeHandler);
  164. // Must handle new child here as state change events are already
  165. // fired
  166. onChildSizeChange();
  167. }
  168. }
  169. // Save currently open subwindows to track which will need to be closed
  170. final HashSet<VWindow> removedSubWindows = new HashSet<VWindow>(
  171. getWidget().subWindows);
  172. // Handle other UIDL children
  173. while ((childUidl = uidl.getChildUIDL(++childIndex)) != null) {
  174. String tag = childUidl.getTag().intern();
  175. if (tag == "actions") {
  176. if (getWidget().actionHandler == null) {
  177. getWidget().actionHandler = new ShortcutActionHandler(
  178. getWidget().id, client);
  179. }
  180. getWidget().actionHandler.updateActionMap(childUidl);
  181. } else if (tag == "execJS") {
  182. String script = childUidl.getStringAttribute("script");
  183. VView.eval(script);
  184. } else if (tag == "notifications") {
  185. for (final Iterator<?> it = childUidl.getChildIterator(); it
  186. .hasNext();) {
  187. final UIDL notification = (UIDL) it.next();
  188. VNotification.showNotification(client, notification);
  189. }
  190. } else {
  191. // subwindows
  192. final WindowConnector w = (WindowConnector) client
  193. .getPaintable(childUidl);
  194. VWindow windowWidget = w.getWidget();
  195. if (getWidget().subWindows.contains(windowWidget)) {
  196. removedSubWindows.remove(windowWidget);
  197. } else {
  198. getWidget().subWindows.add(windowWidget);
  199. }
  200. w.updateFromUIDL(childUidl, client);
  201. }
  202. }
  203. // Close old windows which where not in UIDL anymore
  204. for (final Iterator<VWindow> rem = removedSubWindows.iterator(); rem
  205. .hasNext();) {
  206. final VWindow w = rem.next();
  207. client.unregisterPaintable(ConnectorMap.get(getConnection())
  208. .getConnector(w));
  209. getWidget().subWindows.remove(w);
  210. w.hide();
  211. }
  212. if (uidl.hasAttribute("focused")) {
  213. // set focused component when render phase is finished
  214. Scheduler.get().scheduleDeferred(new Command() {
  215. public void execute() {
  216. ComponentConnector paintable = (ComponentConnector) uidl
  217. .getPaintableAttribute("focused", getConnection());
  218. final Widget toBeFocused = paintable.getWidget();
  219. /*
  220. * Two types of Widgets can be focused, either implementing
  221. * GWT HasFocus of a thinner Vaadin specific Focusable
  222. * interface.
  223. */
  224. if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
  225. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
  226. toBeFocusedWidget.setFocus(true);
  227. } else if (toBeFocused instanceof Focusable) {
  228. ((Focusable) toBeFocused).focus();
  229. } else {
  230. VConsole.log("Could not focus component");
  231. }
  232. }
  233. });
  234. }
  235. // Add window listeners on first paint, to prevent premature
  236. // variablechanges
  237. if (firstPaint) {
  238. Window.addWindowClosingHandler(getWidget());
  239. Window.addResizeHandler(getWidget());
  240. }
  241. // finally set scroll position from UIDL
  242. if (uidl.hasVariable("scrollTop")) {
  243. getWidget().scrollable = true;
  244. getWidget().scrollTop = uidl.getIntVariable("scrollTop");
  245. DOM.setElementPropertyInt(getWidget().getElement(), "scrollTop",
  246. getWidget().scrollTop);
  247. getWidget().scrollLeft = uidl.getIntVariable("scrollLeft");
  248. DOM.setElementPropertyInt(getWidget().getElement(), "scrollLeft",
  249. getWidget().scrollLeft);
  250. } else {
  251. getWidget().scrollable = false;
  252. }
  253. // Safari workaround must be run after scrollTop is updated as it sets
  254. // scrollTop using a deferred command.
  255. if (BrowserInfo.get().isSafari()) {
  256. Util.runWebkitOverflowAutoFix(getWidget().getElement());
  257. }
  258. getWidget().scrollIntoView(uidl);
  259. if (uidl.hasAttribute(VView.FRAGMENT_VARIABLE)) {
  260. getWidget().currentFragment = uidl
  261. .getStringAttribute(VView.FRAGMENT_VARIABLE);
  262. if (!getWidget().currentFragment.equals(History.getToken())) {
  263. History.newItem(getWidget().currentFragment, true);
  264. }
  265. } else {
  266. // Initial request for which the server doesn't yet have a fragment
  267. // (and haven't shown any interest in getting one)
  268. getWidget().currentFragment = History.getToken();
  269. // Include current fragment in the next request
  270. client.updateVariable(getWidget().id, VView.FRAGMENT_VARIABLE,
  271. getWidget().currentFragment, false);
  272. }
  273. getWidget().rendering = false;
  274. }
  275. public void init(String rootPanelId,
  276. ApplicationConnection applicationConnection) {
  277. DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
  278. | Event.ONSCROLL);
  279. // iview is focused when created so element needs tabIndex
  280. // 1 due 0 is at the end of natural tabbing order
  281. DOM.setElementProperty(getWidget().getElement(), "tabIndex", "1");
  282. RootPanel root = RootPanel.get(rootPanelId);
  283. // Remove the v-app-loading or any splash screen added inside the div by
  284. // the user
  285. root.getElement().setInnerHTML("");
  286. root.addStyleName("v-theme-"
  287. + applicationConnection.getConfiguration().getThemeName());
  288. root.add(getWidget());
  289. if (applicationConnection.getConfiguration().isStandalone()) {
  290. // set focus to iview element by default to listen possible keyboard
  291. // shortcuts. For embedded applications this is unacceptable as we
  292. // don't want to steal focus from the main page nor we don't want
  293. // side-effects from focusing (scrollIntoView).
  294. getWidget().getElement().focus();
  295. }
  296. }
  297. private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
  298. @Override
  299. protected void fireClick(NativeEvent event,
  300. MouseEventDetails mouseDetails) {
  301. rpc.click(mouseDetails);
  302. }
  303. };
  304. public void updateCaption(ComponentConnector component) {
  305. // NOP The main view never draws caption for its layout
  306. }
  307. @Override
  308. public VView getWidget() {
  309. return (VView) super.getWidget();
  310. }
  311. @Override
  312. protected Widget createWidget() {
  313. return GWT.create(VView.class);
  314. }
  315. protected void onChildSizeChange() {
  316. ComponentConnector child = getWidget().layout;
  317. Style childStyle = child.getWidget().getElement().getStyle();
  318. /*
  319. * Must set absolute position if the child has relative height and
  320. * there's a chance of horizontal scrolling as some browsers will
  321. * otherwise not take the scrollbar into account when calculating the
  322. * height. Assuming v-view does not have an undefined width for now, see
  323. * #8460.
  324. */
  325. if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) {
  326. childStyle.setPosition(Position.ABSOLUTE);
  327. } else {
  328. childStyle.clearPosition();
  329. }
  330. }
  331. /**
  332. * Checks if the given sub window is a child of this Root Connector
  333. *
  334. * @deprecated Should be replaced by a more generic mechanism for getting
  335. * non-ComponentConnector children
  336. * @param wc
  337. * @return
  338. */
  339. @Deprecated
  340. public boolean hasSubWindow(WindowConnector wc) {
  341. return getWidget().subWindows.contains(wc.getWidget());
  342. }
  343. @Override
  344. public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
  345. super.onConnectorHierarchyChange(event);
  346. for (ComponentConnector c : getChildren()) {
  347. if (c instanceof WindowConnector) {
  348. WindowConnector wc = (WindowConnector) c;
  349. wc.setWindowOrderAndPosition();
  350. }
  351. }
  352. }
  353. }