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

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