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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. * Copyright 2000-2013 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.core.client.Scheduler.ScheduledCommand;
  22. import com.google.gwt.dom.client.Document;
  23. import com.google.gwt.dom.client.HeadElement;
  24. import com.google.gwt.dom.client.LinkElement;
  25. import com.google.gwt.dom.client.NativeEvent;
  26. import com.google.gwt.dom.client.Style;
  27. import com.google.gwt.dom.client.Style.Position;
  28. import com.google.gwt.dom.client.StyleInjector;
  29. import com.google.gwt.event.dom.client.ScrollEvent;
  30. import com.google.gwt.event.dom.client.ScrollHandler;
  31. import com.google.gwt.event.logical.shared.ResizeEvent;
  32. import com.google.gwt.event.logical.shared.ResizeHandler;
  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.History;
  38. import com.google.gwt.user.client.Window;
  39. import com.google.gwt.user.client.ui.RootPanel;
  40. import com.google.gwt.user.client.ui.Widget;
  41. import com.google.web.bindery.event.shared.HandlerRegistration;
  42. import com.vaadin.client.ApplicationConnection;
  43. import com.vaadin.client.BrowserInfo;
  44. import com.vaadin.client.ComponentConnector;
  45. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  46. import com.vaadin.client.ConnectorMap;
  47. import com.vaadin.client.Focusable;
  48. import com.vaadin.client.Paintable;
  49. import com.vaadin.client.UIDL;
  50. import com.vaadin.client.VConsole;
  51. import com.vaadin.client.communication.StateChangeEvent;
  52. import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
  53. import com.vaadin.client.ui.AbstractSingleComponentContainerConnector;
  54. import com.vaadin.client.ui.ClickEventHandler;
  55. import com.vaadin.client.ui.ShortcutActionHandler;
  56. import com.vaadin.client.ui.VNotification;
  57. import com.vaadin.client.ui.VUI;
  58. import com.vaadin.client.ui.layout.MayScrollChildren;
  59. import com.vaadin.client.ui.window.WindowConnector;
  60. import com.vaadin.server.Page.Styles;
  61. import com.vaadin.shared.MouseEventDetails;
  62. import com.vaadin.shared.ui.ComponentStateUtil;
  63. import com.vaadin.shared.ui.Connect;
  64. import com.vaadin.shared.ui.Connect.LoadStyle;
  65. import com.vaadin.shared.ui.ui.PageClientRpc;
  66. import com.vaadin.shared.ui.ui.ScrollClientRpc;
  67. import com.vaadin.shared.ui.ui.UIConstants;
  68. import com.vaadin.shared.ui.ui.UIServerRpc;
  69. import com.vaadin.shared.ui.ui.UIState;
  70. import com.vaadin.ui.UI;
  71. @Connect(value = UI.class, loadStyle = LoadStyle.EAGER)
  72. public class UIConnector extends AbstractSingleComponentContainerConnector
  73. implements Paintable, MayScrollChildren {
  74. private HandlerRegistration childStateChangeHandlerRegistration;
  75. private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
  76. @Override
  77. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  78. // TODO Should use a more specific handler that only reacts to
  79. // size changes
  80. onChildSizeChange();
  81. }
  82. };
  83. @Override
  84. protected void init() {
  85. super.init();
  86. registerRpc(PageClientRpc.class, new PageClientRpc() {
  87. @Override
  88. public void setTitle(String title) {
  89. com.google.gwt.user.client.Window.setTitle(title);
  90. }
  91. @Override
  92. public void reload() {
  93. Window.Location.reload();
  94. }
  95. });
  96. registerRpc(ScrollClientRpc.class, new ScrollClientRpc() {
  97. @Override
  98. public void setScrollTop(int scrollTop) {
  99. getWidget().getElement().setScrollTop(scrollTop);
  100. }
  101. @Override
  102. public void setScrollLeft(int scrollLeft) {
  103. getWidget().getElement().setScrollLeft(scrollLeft);
  104. }
  105. });
  106. getWidget().addResizeHandler(new ResizeHandler() {
  107. @Override
  108. public void onResize(ResizeEvent event) {
  109. getRpcProxy(UIServerRpc.class).resize(event.getHeight(),
  110. event.getWidth(), Window.getClientWidth(),
  111. Window.getClientHeight());
  112. if (getState().immediate) {
  113. getConnection().sendPendingVariableChanges();
  114. }
  115. }
  116. });
  117. getWidget().addScrollHandler(new ScrollHandler() {
  118. private int lastSentScrollTop = Integer.MAX_VALUE;
  119. private int lastSentScrollLeft = Integer.MAX_VALUE;
  120. @Override
  121. public void onScroll(ScrollEvent event) {
  122. Element element = getWidget().getElement();
  123. int newScrollTop = element.getScrollTop();
  124. int newScrollLeft = element.getScrollLeft();
  125. if (newScrollTop != lastSentScrollTop
  126. || newScrollLeft != lastSentScrollLeft) {
  127. lastSentScrollTop = newScrollTop;
  128. lastSentScrollLeft = newScrollLeft;
  129. getRpcProxy(UIServerRpc.class).scroll(newScrollTop,
  130. newScrollLeft);
  131. }
  132. }
  133. });
  134. }
  135. private native void open(String url, String name)
  136. /*-{
  137. $wnd.open(url, name);
  138. }-*/;
  139. @Override
  140. public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
  141. ConnectorMap paintableMap = ConnectorMap.get(getConnection());
  142. getWidget().rendering = true;
  143. getWidget().id = getConnectorId();
  144. boolean firstPaint = getWidget().connection == null;
  145. getWidget().connection = client;
  146. getWidget().immediate = getState().immediate;
  147. getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY);
  148. String newTheme = uidl.getStringAttribute("theme");
  149. if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) {
  150. // Complete page refresh is needed due css can affect layout
  151. // calculations etc
  152. getWidget().reloadHostPage();
  153. } else {
  154. getWidget().theme = newTheme;
  155. }
  156. // this also implicitly removes old styles
  157. String styles = "";
  158. styles += getWidget().getStylePrimaryName() + " ";
  159. if (ComponentStateUtil.hasStyles(getState())) {
  160. for (String style : getState().styles) {
  161. styles += style + " ";
  162. }
  163. }
  164. if (!client.getConfiguration().isStandalone()) {
  165. styles += getWidget().getStylePrimaryName() + "-embedded";
  166. }
  167. getWidget().setStyleName(styles.trim());
  168. getWidget().makeScrollable();
  169. clickEventHandler.handleEventHandlerRegistration();
  170. // Process children
  171. int childIndex = 0;
  172. // Open URL:s
  173. boolean isClosed = false; // was this window closed?
  174. while (childIndex < uidl.getChildCount()
  175. && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
  176. final UIDL open = uidl.getChildUIDL(childIndex);
  177. final String url = client.translateVaadinUri(open
  178. .getStringAttribute("src"));
  179. final String target = open.getStringAttribute("name");
  180. if (target == null) {
  181. // source will be opened to this browser window, but we may have
  182. // to finish rendering this window in case this is a download
  183. // (and window stays open).
  184. Scheduler.get().scheduleDeferred(new Command() {
  185. @Override
  186. public void execute() {
  187. VUI.goTo(url);
  188. }
  189. });
  190. } else if ("_self".equals(target)) {
  191. // This window is closing (for sure). Only other opens are
  192. // relevant in this change. See #3558, #2144
  193. isClosed = true;
  194. VUI.goTo(url);
  195. } else {
  196. String options;
  197. boolean alwaysAsPopup = true;
  198. if (open.hasAttribute("popup")) {
  199. alwaysAsPopup = open.getBooleanAttribute("popup");
  200. }
  201. if (alwaysAsPopup) {
  202. if (open.hasAttribute("border")) {
  203. if (open.getStringAttribute("border").equals("minimal")) {
  204. options = "menubar=yes,location=no,status=no";
  205. } else {
  206. options = "menubar=no,location=no,status=no";
  207. }
  208. } else {
  209. options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
  210. }
  211. if (open.hasAttribute("width")) {
  212. int w = open.getIntAttribute("width");
  213. options += ",width=" + w;
  214. }
  215. if (open.hasAttribute("height")) {
  216. int h = open.getIntAttribute("height");
  217. options += ",height=" + h;
  218. }
  219. Window.open(url, target, options);
  220. } else {
  221. open(url, target);
  222. }
  223. }
  224. childIndex++;
  225. }
  226. if (isClosed) {
  227. // don't render the content, something else will be opened to this
  228. // browser view
  229. getWidget().rendering = false;
  230. return;
  231. }
  232. // Handle other UIDL children
  233. UIDL childUidl;
  234. while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) {
  235. String tag = childUidl.getTag().intern();
  236. if (tag == "actions") {
  237. if (getWidget().actionHandler == null) {
  238. getWidget().actionHandler = new ShortcutActionHandler(
  239. getWidget().id, client);
  240. }
  241. getWidget().actionHandler.updateActionMap(childUidl);
  242. } else if (tag == "notifications") {
  243. for (final Iterator<?> it = childUidl.getChildIterator(); it
  244. .hasNext();) {
  245. final UIDL notification = (UIDL) it.next();
  246. VNotification.showNotification(client, notification);
  247. }
  248. } else if (tag == "css-injections") {
  249. injectCSS(childUidl);
  250. }
  251. }
  252. if (uidl.hasAttribute("focused")) {
  253. // set focused component when render phase is finished
  254. Scheduler.get().scheduleDeferred(new Command() {
  255. @Override
  256. public void execute() {
  257. ComponentConnector paintable = (ComponentConnector) uidl
  258. .getPaintableAttribute("focused", getConnection());
  259. final Widget toBeFocused = paintable.getWidget();
  260. /*
  261. * Two types of Widgets can be focused, either implementing
  262. * GWT HasFocus of a thinner Vaadin specific Focusable
  263. * interface.
  264. */
  265. if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
  266. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
  267. toBeFocusedWidget.setFocus(true);
  268. } else if (toBeFocused instanceof Focusable) {
  269. ((Focusable) toBeFocused).focus();
  270. } else {
  271. VConsole.log("Could not focus component");
  272. }
  273. }
  274. });
  275. }
  276. // Add window listeners on first paint, to prevent premature
  277. // variablechanges
  278. if (firstPaint) {
  279. Window.addWindowClosingHandler(getWidget());
  280. Window.addResizeHandler(getWidget());
  281. }
  282. if (uidl.hasAttribute("scrollTo")) {
  283. final ComponentConnector connector = (ComponentConnector) uidl
  284. .getPaintableAttribute("scrollTo", getConnection());
  285. scrollIntoView(connector);
  286. }
  287. if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) {
  288. String location = uidl
  289. .getStringAttribute(UIConstants.LOCATION_VARIABLE);
  290. int fragmentIndex = location.indexOf('#');
  291. if (fragmentIndex >= 0) {
  292. getWidget().currentFragment = location
  293. .substring(fragmentIndex + 1);
  294. }
  295. if (!getWidget().currentFragment.equals(History.getToken())) {
  296. History.newItem(getWidget().currentFragment, true);
  297. }
  298. }
  299. if (firstPaint) {
  300. // Queue the initial window size to be sent with the following
  301. // request.
  302. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  303. @Override
  304. public void execute() {
  305. getWidget().sendClientResized();
  306. }
  307. });
  308. }
  309. getWidget().rendering = false;
  310. }
  311. /**
  312. * Reads CSS strings and resources injected by {@link Styles#inject} from
  313. * the UIDL stream.
  314. *
  315. * @param uidl
  316. * The uidl which contains "css-resource" and "css-string" tags
  317. */
  318. private void injectCSS(UIDL uidl) {
  319. final HeadElement head = HeadElement.as(Document.get()
  320. .getElementsByTagName(HeadElement.TAG).getItem(0));
  321. /*
  322. * Search the UIDL stream for CSS resources and strings to be injected.
  323. */
  324. for (Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
  325. UIDL cssInjectionsUidl = (UIDL) it.next();
  326. // Check if we have resources to inject
  327. if (cssInjectionsUidl.getTag().equals("css-resource")) {
  328. String url = getWidget().connection
  329. .translateVaadinUri(cssInjectionsUidl
  330. .getStringAttribute("url"));
  331. LinkElement link = LinkElement.as(DOM
  332. .createElement(LinkElement.TAG));
  333. link.setRel("stylesheet");
  334. link.setHref(url);
  335. link.setType("text/css");
  336. head.appendChild(link);
  337. // Check if we have CSS string to inject
  338. } else if (cssInjectionsUidl.getTag().equals("css-string")) {
  339. for (Iterator<?> it2 = cssInjectionsUidl.getChildIterator(); it2
  340. .hasNext();) {
  341. StyleInjector.injectAtEnd((String) it2.next());
  342. StyleInjector.flush();
  343. }
  344. }
  345. }
  346. }
  347. public void init(String rootPanelId,
  348. ApplicationConnection applicationConnection) {
  349. DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
  350. | Event.ONSCROLL);
  351. RootPanel root = RootPanel.get(rootPanelId);
  352. // Remove the v-app-loading or any splash screen added inside the div by
  353. // the user
  354. root.getElement().setInnerHTML("");
  355. String themeName = applicationConnection.getConfiguration()
  356. .getThemeName();
  357. root.addStyleName(themeName);
  358. root.add(getWidget());
  359. // Set default tab index before focus call. State change handler
  360. // will update this later if needed.
  361. getWidget().setTabIndex(1);
  362. if (applicationConnection.getConfiguration().isStandalone()) {
  363. // set focus to iview element by default to listen possible keyboard
  364. // shortcuts. For embedded applications this is unacceptable as we
  365. // don't want to steal focus from the main page nor we don't want
  366. // side-effects from focusing (scrollIntoView).
  367. getWidget().getElement().focus();
  368. }
  369. }
  370. private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
  371. @Override
  372. protected void fireClick(NativeEvent event,
  373. MouseEventDetails mouseDetails) {
  374. getRpcProxy(UIServerRpc.class).click(mouseDetails);
  375. }
  376. };
  377. @Override
  378. public void updateCaption(ComponentConnector component) {
  379. // NOP The main view never draws caption for its layout
  380. }
  381. @Override
  382. public VUI getWidget() {
  383. return (VUI) super.getWidget();
  384. }
  385. protected void onChildSizeChange() {
  386. ComponentConnector child = getContent();
  387. if (child == null) {
  388. return;
  389. }
  390. Style childStyle = child.getWidget().getElement().getStyle();
  391. /*
  392. * Must set absolute position if the child has relative height and
  393. * there's a chance of horizontal scrolling as some browsers will
  394. * otherwise not take the scrollbar into account when calculating the
  395. * height. Assuming v-ui does not have an undefined width for now, see
  396. * #8460.
  397. */
  398. if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) {
  399. childStyle.setPosition(Position.ABSOLUTE);
  400. } else {
  401. childStyle.clearPosition();
  402. }
  403. }
  404. /**
  405. * Checks if the given sub window is a child of this UI Connector
  406. *
  407. * @deprecated Should be replaced by a more generic mechanism for getting
  408. * non-ComponentConnector children
  409. * @param wc
  410. * @return
  411. */
  412. @Deprecated
  413. public boolean hasSubWindow(WindowConnector wc) {
  414. return getChildComponents().contains(wc);
  415. }
  416. /**
  417. * Return an iterator for current subwindows. This method is meant for
  418. * testing purposes only.
  419. *
  420. * @return
  421. */
  422. public List<WindowConnector> getSubWindows() {
  423. ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>();
  424. for (ComponentConnector child : getChildComponents()) {
  425. if (child instanceof WindowConnector) {
  426. windows.add((WindowConnector) child);
  427. }
  428. }
  429. return windows;
  430. }
  431. @Override
  432. public UIState getState() {
  433. return (UIState) super.getState();
  434. }
  435. @Override
  436. public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
  437. ComponentConnector oldChild = null;
  438. ComponentConnector newChild = getContent();
  439. for (ComponentConnector c : event.getOldChildren()) {
  440. if (!(c instanceof WindowConnector)) {
  441. oldChild = c;
  442. break;
  443. }
  444. }
  445. if (oldChild != newChild) {
  446. if (childStateChangeHandlerRegistration != null) {
  447. childStateChangeHandlerRegistration.removeHandler();
  448. childStateChangeHandlerRegistration = null;
  449. }
  450. if (newChild != null) {
  451. getWidget().setWidget(newChild.getWidget());
  452. childStateChangeHandlerRegistration = newChild
  453. .addStateChangeHandler(childStateChangeHandler);
  454. // Must handle new child here as state change events are already
  455. // fired
  456. onChildSizeChange();
  457. } else {
  458. getWidget().setWidget(null);
  459. }
  460. }
  461. for (ComponentConnector c : getChildComponents()) {
  462. if (c instanceof WindowConnector) {
  463. WindowConnector wc = (WindowConnector) c;
  464. wc.setWindowOrderAndPosition();
  465. }
  466. }
  467. // Close removed sub windows
  468. for (ComponentConnector c : event.getOldChildren()) {
  469. if (c.getParent() != this && c instanceof WindowConnector) {
  470. ((WindowConnector) c).getWidget().hide();
  471. }
  472. }
  473. }
  474. @Override
  475. public boolean hasTooltip() {
  476. /*
  477. * Always return true so there's always top level tooltip handler that
  478. * takes care of hiding tooltips whenever the mouse is moved somewhere
  479. * else.
  480. */
  481. return true;
  482. }
  483. /**
  484. * Tries to scroll the viewport so that the given connector is in view.
  485. *
  486. * @param componentConnector
  487. * The connector which should be visible
  488. *
  489. */
  490. public void scrollIntoView(final ComponentConnector componentConnector) {
  491. if (componentConnector == null) {
  492. return;
  493. }
  494. Scheduler.get().scheduleDeferred(new Command() {
  495. @Override
  496. public void execute() {
  497. componentConnector.getWidget().getElement().scrollIntoView();
  498. }
  499. });
  500. }
  501. @Override
  502. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  503. super.onStateChanged(stateChangeEvent);
  504. if (stateChangeEvent.hasPropertyChanged("tooltipConfiguration")) {
  505. getConnection().getVTooltip().setCloseTimeout(
  506. getState().tooltipConfiguration.closeTimeout);
  507. getConnection().getVTooltip().setOpenDelay(
  508. getState().tooltipConfiguration.openDelay);
  509. getConnection().getVTooltip().setQuickOpenDelay(
  510. getState().tooltipConfiguration.quickOpenDelay);
  511. getConnection().getVTooltip().setQuickOpenTimeout(
  512. getState().tooltipConfiguration.quickOpenTimeout);
  513. getConnection().getVTooltip().setMaxWidth(
  514. getState().tooltipConfiguration.maxWidth);
  515. }
  516. if (stateChangeEvent
  517. .hasPropertyChanged("loadingIndicatorConfiguration")) {
  518. getConnection().getLoadingIndicator().setInitialStateDelay(
  519. getState().loadingIndicatorConfiguration.initialDelay);
  520. getConnection().getLoadingIndicator().setWaitStateDelay(
  521. getState().loadingIndicatorConfiguration.waitStateDelay);
  522. getConnection().getLoadingIndicator().setDelayStateDelay(
  523. getState().loadingIndicatorConfiguration.delayStateDelay);
  524. }
  525. }
  526. }