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.

UIConnector.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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.ui.ui;
  17. import java.util.ArrayList;
  18. import java.util.Iterator;
  19. import java.util.List;
  20. import com.google.gwt.core.client.Scheduler;
  21. import com.google.gwt.dom.client.NativeEvent;
  22. import com.google.gwt.dom.client.Style;
  23. import com.google.gwt.dom.client.Style.Position;
  24. import com.google.gwt.event.logical.shared.ResizeEvent;
  25. import com.google.gwt.event.logical.shared.ResizeHandler;
  26. import com.google.gwt.user.client.Command;
  27. import com.google.gwt.user.client.DOM;
  28. import com.google.gwt.user.client.Event;
  29. import com.google.gwt.user.client.History;
  30. import com.google.gwt.user.client.Window;
  31. import com.google.gwt.user.client.ui.RootPanel;
  32. import com.google.gwt.user.client.ui.Widget;
  33. import com.google.web.bindery.event.shared.HandlerRegistration;
  34. import com.vaadin.client.ApplicationConnection;
  35. import com.vaadin.client.BrowserInfo;
  36. import com.vaadin.client.ComponentConnector;
  37. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  38. import com.vaadin.client.ConnectorMap;
  39. import com.vaadin.client.Focusable;
  40. import com.vaadin.client.Paintable;
  41. import com.vaadin.client.UIDL;
  42. import com.vaadin.client.VConsole;
  43. import com.vaadin.client.communication.RpcProxy;
  44. import com.vaadin.client.communication.StateChangeEvent;
  45. import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
  46. import com.vaadin.client.ui.AbstractComponentContainerConnector;
  47. import com.vaadin.client.ui.ClickEventHandler;
  48. import com.vaadin.client.ui.ShortcutActionHandler;
  49. import com.vaadin.client.ui.layout.MayScrollChildren;
  50. import com.vaadin.client.ui.notification.VNotification;
  51. import com.vaadin.client.ui.window.WindowConnector;
  52. import com.vaadin.shared.MouseEventDetails;
  53. import com.vaadin.shared.ui.ComponentStateUtil;
  54. import com.vaadin.shared.ui.Connect;
  55. import com.vaadin.shared.ui.Connect.LoadStyle;
  56. import com.vaadin.shared.ui.ui.PageClientRpc;
  57. import com.vaadin.shared.ui.ui.UIConstants;
  58. import com.vaadin.shared.ui.ui.UIServerRpc;
  59. import com.vaadin.shared.ui.ui.UIState;
  60. import com.vaadin.ui.UI;
  61. @Connect(value = UI.class, loadStyle = LoadStyle.EAGER)
  62. public class UIConnector extends AbstractComponentContainerConnector implements
  63. Paintable, MayScrollChildren {
  64. private UIServerRpc rpc = RpcProxy.create(UIServerRpc.class, this);
  65. private HandlerRegistration childStateChangeHandlerRegistration;
  66. private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
  67. @Override
  68. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  69. // TODO Should use a more specific handler that only reacts to
  70. // size changes
  71. onChildSizeChange();
  72. }
  73. };
  74. @Override
  75. protected void init() {
  76. super.init();
  77. registerRpc(PageClientRpc.class, new PageClientRpc() {
  78. @Override
  79. public void setTitle(String title) {
  80. com.google.gwt.user.client.Window.setTitle(title);
  81. }
  82. });
  83. getWidget().addResizeHandler(new ResizeHandler() {
  84. @Override
  85. public void onResize(ResizeEvent event) {
  86. rpc.resize(event.getHeight(), event.getWidth(),
  87. Window.getClientWidth(), Window.getClientHeight());
  88. if (getState().immediate) {
  89. getConnection().sendPendingVariableChanges();
  90. }
  91. }
  92. });
  93. }
  94. @Override
  95. public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
  96. ConnectorMap paintableMap = ConnectorMap.get(getConnection());
  97. getWidget().rendering = true;
  98. getWidget().id = getConnectorId();
  99. boolean firstPaint = getWidget().connection == null;
  100. getWidget().connection = client;
  101. getWidget().immediate = getState().immediate;
  102. getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY);
  103. String newTheme = uidl.getStringAttribute("theme");
  104. if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) {
  105. // Complete page refresh is needed due css can affect layout
  106. // calculations etc
  107. getWidget().reloadHostPage();
  108. } else {
  109. getWidget().theme = newTheme;
  110. }
  111. // this also implicitly removes old styles
  112. String styles = "";
  113. styles += getWidget().getStylePrimaryName() + " ";
  114. if (ComponentStateUtil.hasStyles(getState())) {
  115. for (String style : getState().styles) {
  116. styles += style + " ";
  117. }
  118. }
  119. if (!client.getConfiguration().isStandalone()) {
  120. styles += getWidget().getStylePrimaryName() + "-embedded";
  121. }
  122. getWidget().setStyleName(styles.trim());
  123. getWidget().makeScrollable();
  124. clickEventHandler.handleEventHandlerRegistration();
  125. // Process children
  126. int childIndex = 0;
  127. // Open URL:s
  128. boolean isClosed = false; // was this window closed?
  129. while (childIndex < uidl.getChildCount()
  130. && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
  131. final UIDL open = uidl.getChildUIDL(childIndex);
  132. final String url = client.translateVaadinUri(open
  133. .getStringAttribute("src"));
  134. final String target = open.getStringAttribute("name");
  135. if (target == null) {
  136. // source will be opened to this browser window, but we may have
  137. // to finish rendering this window in case this is a download
  138. // (and window stays open).
  139. Scheduler.get().scheduleDeferred(new Command() {
  140. @Override
  141. public void execute() {
  142. VUI.goTo(url);
  143. }
  144. });
  145. } else if ("_self".equals(target)) {
  146. // This window is closing (for sure). Only other opens are
  147. // relevant in this change. See #3558, #2144
  148. isClosed = true;
  149. VUI.goTo(url);
  150. } else {
  151. String options;
  152. if (open.hasAttribute("border")) {
  153. if (open.getStringAttribute("border").equals("minimal")) {
  154. options = "menubar=yes,location=no,status=no";
  155. } else {
  156. options = "menubar=no,location=no,status=no";
  157. }
  158. } else {
  159. options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
  160. }
  161. if (open.hasAttribute("width")) {
  162. int w = open.getIntAttribute("width");
  163. options += ",width=" + w;
  164. }
  165. if (open.hasAttribute("height")) {
  166. int h = open.getIntAttribute("height");
  167. options += ",height=" + h;
  168. }
  169. Window.open(url, target, options);
  170. }
  171. childIndex++;
  172. }
  173. if (isClosed) {
  174. // don't render the content, something else will be opened to this
  175. // browser view
  176. getWidget().rendering = false;
  177. return;
  178. }
  179. // Handle other UIDL children
  180. UIDL childUidl;
  181. while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) {
  182. String tag = childUidl.getTag().intern();
  183. if (tag == "actions") {
  184. if (getWidget().actionHandler == null) {
  185. getWidget().actionHandler = new ShortcutActionHandler(
  186. getWidget().id, client);
  187. }
  188. getWidget().actionHandler.updateActionMap(childUidl);
  189. } else if (tag == "notifications") {
  190. for (final Iterator<?> it = childUidl.getChildIterator(); it
  191. .hasNext();) {
  192. final UIDL notification = (UIDL) it.next();
  193. VNotification.showNotification(client, notification);
  194. }
  195. }
  196. }
  197. if (uidl.hasAttribute("focused")) {
  198. // set focused component when render phase is finished
  199. Scheduler.get().scheduleDeferred(new Command() {
  200. @Override
  201. public void execute() {
  202. ComponentConnector paintable = (ComponentConnector) uidl
  203. .getPaintableAttribute("focused", getConnection());
  204. final Widget toBeFocused = paintable.getWidget();
  205. /*
  206. * Two types of Widgets can be focused, either implementing
  207. * GWT HasFocus of a thinner Vaadin specific Focusable
  208. * interface.
  209. */
  210. if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
  211. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
  212. toBeFocusedWidget.setFocus(true);
  213. } else if (toBeFocused instanceof Focusable) {
  214. ((Focusable) toBeFocused).focus();
  215. } else {
  216. VConsole.log("Could not focus component");
  217. }
  218. }
  219. });
  220. }
  221. // Add window listeners on first paint, to prevent premature
  222. // variablechanges
  223. if (firstPaint) {
  224. Window.addWindowClosingHandler(getWidget());
  225. Window.addResizeHandler(getWidget());
  226. }
  227. // finally set scroll position from UIDL
  228. if (uidl.hasVariable("scrollTop")) {
  229. getWidget().scrollable = true;
  230. getWidget().scrollTop = uidl.getIntVariable("scrollTop");
  231. DOM.setElementPropertyInt(getWidget().getElement(), "scrollTop",
  232. getWidget().scrollTop);
  233. getWidget().scrollLeft = uidl.getIntVariable("scrollLeft");
  234. DOM.setElementPropertyInt(getWidget().getElement(), "scrollLeft",
  235. getWidget().scrollLeft);
  236. } else {
  237. getWidget().scrollable = false;
  238. }
  239. if (uidl.hasAttribute("scrollTo")) {
  240. final ComponentConnector connector = (ComponentConnector) uidl
  241. .getPaintableAttribute("scrollTo", getConnection());
  242. scrollIntoView(connector);
  243. }
  244. if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) {
  245. String location = uidl
  246. .getStringAttribute(UIConstants.LOCATION_VARIABLE);
  247. int fragmentIndex = location.indexOf('#');
  248. if (fragmentIndex >= 0) {
  249. getWidget().currentFragment = location
  250. .substring(fragmentIndex + 1);
  251. }
  252. if (!getWidget().currentFragment.equals(History.getToken())) {
  253. History.newItem(getWidget().currentFragment, true);
  254. }
  255. }
  256. if (firstPaint) {
  257. // Queue the initial window size to be sent with the following
  258. // request.
  259. getWidget().sendClientResized();
  260. }
  261. getWidget().rendering = false;
  262. }
  263. public void init(String rootPanelId,
  264. ApplicationConnection applicationConnection) {
  265. DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
  266. | Event.ONSCROLL);
  267. // iview is focused when created so element needs tabIndex
  268. // 1 due 0 is at the end of natural tabbing order
  269. DOM.setElementProperty(getWidget().getElement(), "tabIndex", "1");
  270. RootPanel root = RootPanel.get(rootPanelId);
  271. // Remove the v-app-loading or any splash screen added inside the div by
  272. // the user
  273. root.getElement().setInnerHTML("");
  274. String themeName = applicationConnection.getConfiguration()
  275. .getThemeName();
  276. // Remove chars that are not suitable for style names
  277. themeName = themeName.replaceAll("[^a-zA-Z0-9]", "");
  278. root.addStyleName("v-theme-" + themeName);
  279. root.add(getWidget());
  280. if (applicationConnection.getConfiguration().isStandalone()) {
  281. // set focus to iview element by default to listen possible keyboard
  282. // shortcuts. For embedded applications this is unacceptable as we
  283. // don't want to steal focus from the main page nor we don't want
  284. // side-effects from focusing (scrollIntoView).
  285. getWidget().getElement().focus();
  286. }
  287. }
  288. private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
  289. @Override
  290. protected void fireClick(NativeEvent event,
  291. MouseEventDetails mouseDetails) {
  292. rpc.click(mouseDetails);
  293. }
  294. };
  295. @Override
  296. public void updateCaption(ComponentConnector component) {
  297. // NOP The main view never draws caption for its layout
  298. }
  299. @Override
  300. public VUI getWidget() {
  301. return (VUI) super.getWidget();
  302. }
  303. protected ComponentConnector getContent() {
  304. return (ComponentConnector) getState().content;
  305. }
  306. protected void onChildSizeChange() {
  307. ComponentConnector child = getContent();
  308. Style childStyle = child.getWidget().getElement().getStyle();
  309. /*
  310. * Must set absolute position if the child has relative height and
  311. * there's a chance of horizontal scrolling as some browsers will
  312. * otherwise not take the scrollbar into account when calculating the
  313. * height. Assuming v-view does not have an undefined width for now, see
  314. * #8460.
  315. */
  316. if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) {
  317. childStyle.setPosition(Position.ABSOLUTE);
  318. } else {
  319. childStyle.clearPosition();
  320. }
  321. }
  322. /**
  323. * Checks if the given sub window is a child of this UI Connector
  324. *
  325. * @deprecated Should be replaced by a more generic mechanism for getting
  326. * non-ComponentConnector children
  327. * @param wc
  328. * @return
  329. */
  330. @Deprecated
  331. public boolean hasSubWindow(WindowConnector wc) {
  332. return getChildComponents().contains(wc);
  333. }
  334. /**
  335. * Return an iterator for current subwindows. This method is meant for
  336. * testing purposes only.
  337. *
  338. * @return
  339. */
  340. public List<WindowConnector> getSubWindows() {
  341. ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>();
  342. for (ComponentConnector child : getChildComponents()) {
  343. if (child instanceof WindowConnector) {
  344. windows.add((WindowConnector) child);
  345. }
  346. }
  347. return windows;
  348. }
  349. @Override
  350. public UIState getState() {
  351. return (UIState) super.getState();
  352. }
  353. @Override
  354. public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
  355. super.onConnectorHierarchyChange(event);
  356. ComponentConnector oldChild = null;
  357. ComponentConnector newChild = getContent();
  358. for (ComponentConnector c : event.getOldChildren()) {
  359. if (!(c instanceof WindowConnector)) {
  360. oldChild = c;
  361. break;
  362. }
  363. }
  364. if (oldChild != newChild) {
  365. if (childStateChangeHandlerRegistration != null) {
  366. childStateChangeHandlerRegistration.removeHandler();
  367. childStateChangeHandlerRegistration = null;
  368. }
  369. getWidget().setWidget(newChild.getWidget());
  370. childStateChangeHandlerRegistration = newChild
  371. .addStateChangeHandler(childStateChangeHandler);
  372. // Must handle new child here as state change events are already
  373. // fired
  374. onChildSizeChange();
  375. }
  376. for (ComponentConnector c : getChildComponents()) {
  377. if (c instanceof WindowConnector) {
  378. WindowConnector wc = (WindowConnector) c;
  379. wc.setWindowOrderAndPosition();
  380. }
  381. }
  382. // Close removed sub windows
  383. for (ComponentConnector c : event.getOldChildren()) {
  384. if (c.getParent() != this && c instanceof WindowConnector) {
  385. ((WindowConnector) c).getWidget().hide();
  386. }
  387. }
  388. }
  389. /**
  390. * Tries to scroll the viewport so that the given connector is in view.
  391. *
  392. * @param componentConnector
  393. * The connector which should be visible
  394. *
  395. */
  396. public void scrollIntoView(final ComponentConnector componentConnector) {
  397. if (componentConnector == null) {
  398. return;
  399. }
  400. Scheduler.get().scheduleDeferred(new Command() {
  401. @Override
  402. public void execute() {
  403. componentConnector.getWidget().getElement().scrollIntoView();
  404. }
  405. });
  406. }
  407. }