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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  1. /*
  2. * Copyright 2000-2014 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 java.util.logging.Logger;
  21. import com.google.gwt.core.client.Scheduler;
  22. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  23. import com.google.gwt.dom.client.Document;
  24. import com.google.gwt.dom.client.Element;
  25. import com.google.gwt.dom.client.HeadElement;
  26. import com.google.gwt.dom.client.LinkElement;
  27. import com.google.gwt.dom.client.NativeEvent;
  28. import com.google.gwt.dom.client.NodeList;
  29. import com.google.gwt.dom.client.Style;
  30. import com.google.gwt.dom.client.Style.Position;
  31. import com.google.gwt.dom.client.StyleElement;
  32. import com.google.gwt.dom.client.StyleInjector;
  33. import com.google.gwt.event.dom.client.KeyDownEvent;
  34. import com.google.gwt.event.dom.client.KeyDownHandler;
  35. import com.google.gwt.event.dom.client.ScrollEvent;
  36. import com.google.gwt.event.dom.client.ScrollHandler;
  37. import com.google.gwt.event.logical.shared.ResizeEvent;
  38. import com.google.gwt.event.logical.shared.ResizeHandler;
  39. import com.google.gwt.http.client.URL;
  40. import com.google.gwt.user.client.Command;
  41. import com.google.gwt.user.client.DOM;
  42. import com.google.gwt.user.client.Event;
  43. import com.google.gwt.user.client.History;
  44. import com.google.gwt.user.client.Timer;
  45. import com.google.gwt.user.client.Window;
  46. import com.google.gwt.user.client.Window.Location;
  47. import com.google.gwt.user.client.ui.RootPanel;
  48. import com.google.gwt.user.client.ui.Widget;
  49. import com.google.web.bindery.event.shared.HandlerRegistration;
  50. import com.vaadin.client.ApplicationConnection;
  51. import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
  52. import com.vaadin.client.BrowserInfo;
  53. import com.vaadin.client.ComponentConnector;
  54. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  55. import com.vaadin.client.Focusable;
  56. import com.vaadin.client.Paintable;
  57. import com.vaadin.client.ResourceLoader;
  58. import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
  59. import com.vaadin.client.ResourceLoader.ResourceLoadListener;
  60. import com.vaadin.client.ServerConnector;
  61. import com.vaadin.client.UIDL;
  62. import com.vaadin.client.Util;
  63. import com.vaadin.client.VConsole;
  64. import com.vaadin.client.ValueMap;
  65. import com.vaadin.client.annotations.OnStateChange;
  66. import com.vaadin.client.communication.StateChangeEvent;
  67. import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
  68. import com.vaadin.client.ui.AbstractConnector;
  69. import com.vaadin.client.ui.AbstractSingleComponentContainerConnector;
  70. import com.vaadin.client.ui.ClickEventHandler;
  71. import com.vaadin.client.ui.ShortcutActionHandler;
  72. import com.vaadin.client.ui.VNotification;
  73. import com.vaadin.client.ui.VOverlay;
  74. import com.vaadin.client.ui.VUI;
  75. import com.vaadin.client.ui.VWindow;
  76. import com.vaadin.client.ui.layout.MayScrollChildren;
  77. import com.vaadin.client.ui.window.WindowConnector;
  78. import com.vaadin.server.Page.Styles;
  79. import com.vaadin.shared.ApplicationConstants;
  80. import com.vaadin.shared.MouseEventDetails;
  81. import com.vaadin.shared.Version;
  82. import com.vaadin.shared.communication.MethodInvocation;
  83. import com.vaadin.shared.ui.ComponentStateUtil;
  84. import com.vaadin.shared.ui.Connect;
  85. import com.vaadin.shared.ui.Connect.LoadStyle;
  86. import com.vaadin.shared.ui.ui.DebugWindowClientRpc;
  87. import com.vaadin.shared.ui.ui.DebugWindowServerRpc;
  88. import com.vaadin.shared.ui.ui.PageClientRpc;
  89. import com.vaadin.shared.ui.ui.PageState;
  90. import com.vaadin.shared.ui.ui.ScrollClientRpc;
  91. import com.vaadin.shared.ui.ui.UIClientRpc;
  92. import com.vaadin.shared.ui.ui.UIConstants;
  93. import com.vaadin.shared.ui.ui.UIServerRpc;
  94. import com.vaadin.shared.ui.ui.UIState;
  95. import com.vaadin.shared.util.SharedUtil;
  96. import com.vaadin.ui.UI;
  97. @Connect(value = UI.class, loadStyle = LoadStyle.EAGER)
  98. public class UIConnector extends AbstractSingleComponentContainerConnector
  99. implements Paintable, MayScrollChildren {
  100. private HandlerRegistration childStateChangeHandlerRegistration;
  101. private String activeTheme = null;
  102. private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
  103. @Override
  104. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  105. // TODO Should use a more specific handler that only reacts to
  106. // size changes
  107. onChildSizeChange();
  108. }
  109. };
  110. @Override
  111. protected void init() {
  112. super.init();
  113. registerRpc(PageClientRpc.class, new PageClientRpc() {
  114. @Override
  115. public void reload() {
  116. Window.Location.reload();
  117. }
  118. });
  119. registerRpc(ScrollClientRpc.class, new ScrollClientRpc() {
  120. @Override
  121. public void setScrollTop(int scrollTop) {
  122. getWidget().getElement().setScrollTop(scrollTop);
  123. }
  124. @Override
  125. public void setScrollLeft(int scrollLeft) {
  126. getWidget().getElement().setScrollLeft(scrollLeft);
  127. }
  128. });
  129. registerRpc(UIClientRpc.class, new UIClientRpc() {
  130. @Override
  131. public void uiClosed(final boolean sessionExpired) {
  132. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  133. @Override
  134. public void execute() {
  135. // Only notify user if we're still running and not eg.
  136. // navigating away (#12298)
  137. if (getConnection().isApplicationRunning()) {
  138. if (sessionExpired) {
  139. getConnection().showSessionExpiredError(null);
  140. } else {
  141. getState().enabled = false;
  142. updateEnabledState(getState().enabled);
  143. }
  144. getConnection().setApplicationRunning(false);
  145. }
  146. }
  147. });
  148. }
  149. });
  150. registerRpc(DebugWindowClientRpc.class, new DebugWindowClientRpc() {
  151. @Override
  152. public void reportLayoutProblems(String json) {
  153. VConsole.printLayoutProblems(getValueMap(json), getConnection());
  154. }
  155. private native ValueMap getValueMap(String json)
  156. /*-{
  157. return JSON.parse(json);
  158. }-*/;
  159. });
  160. getWidget().addResizeHandler(new ResizeHandler() {
  161. @Override
  162. public void onResize(ResizeEvent event) {
  163. getRpcProxy(UIServerRpc.class).resize(event.getHeight(),
  164. event.getWidth(), Window.getClientWidth(),
  165. Window.getClientHeight());
  166. if (getState().immediate || getPageState().hasResizeListeners) {
  167. getConnection().getServerRpcQueue().flush();
  168. }
  169. }
  170. });
  171. getWidget().addScrollHandler(new ScrollHandler() {
  172. private int lastSentScrollTop = Integer.MAX_VALUE;
  173. private int lastSentScrollLeft = Integer.MAX_VALUE;
  174. @Override
  175. public void onScroll(ScrollEvent event) {
  176. Element element = getWidget().getElement();
  177. int newScrollTop = element.getScrollTop();
  178. int newScrollLeft = element.getScrollLeft();
  179. if (newScrollTop != lastSentScrollTop
  180. || newScrollLeft != lastSentScrollLeft) {
  181. lastSentScrollTop = newScrollTop;
  182. lastSentScrollLeft = newScrollLeft;
  183. getRpcProxy(UIServerRpc.class).scroll(newScrollTop,
  184. newScrollLeft);
  185. }
  186. }
  187. });
  188. }
  189. private native void open(String url, String name)
  190. /*-{
  191. $wnd.open(url, name);
  192. }-*/;
  193. @Override
  194. public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
  195. getWidget().id = getConnectorId();
  196. boolean firstPaint = getWidget().connection == null;
  197. getWidget().connection = client;
  198. getWidget().immediate = getState().immediate;
  199. getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY);
  200. // this also implicitly removes old styles
  201. String styles = "";
  202. styles += getWidget().getStylePrimaryName() + " ";
  203. if (ComponentStateUtil.hasStyles(getState())) {
  204. for (String style : getState().styles) {
  205. styles += style + " ";
  206. }
  207. }
  208. if (!client.getConfiguration().isStandalone()) {
  209. styles += getWidget().getStylePrimaryName() + "-embedded";
  210. }
  211. getWidget().setStyleName(styles.trim());
  212. getWidget().makeScrollable();
  213. clickEventHandler.handleEventHandlerRegistration();
  214. // Process children
  215. int childIndex = 0;
  216. // Open URL:s
  217. boolean isClosed = false; // was this window closed?
  218. while (childIndex < uidl.getChildCount()
  219. && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
  220. final UIDL open = uidl.getChildUIDL(childIndex);
  221. final String url = client.translateVaadinUri(open
  222. .getStringAttribute("src"));
  223. final String target = open.getStringAttribute("name");
  224. if (target == null) {
  225. // source will be opened to this browser window, but we may have
  226. // to finish rendering this window in case this is a download
  227. // (and window stays open).
  228. Scheduler.get().scheduleDeferred(new Command() {
  229. @Override
  230. public void execute() {
  231. VUI.goTo(url);
  232. }
  233. });
  234. } else if ("_self".equals(target)) {
  235. // This window is closing (for sure). Only other opens are
  236. // relevant in this change. See #3558, #2144
  237. isClosed = true;
  238. VUI.goTo(url);
  239. } else {
  240. String options;
  241. boolean alwaysAsPopup = true;
  242. if (open.hasAttribute("popup")) {
  243. alwaysAsPopup = open.getBooleanAttribute("popup");
  244. }
  245. if (alwaysAsPopup) {
  246. if (open.hasAttribute("border")) {
  247. if (open.getStringAttribute("border").equals("minimal")) {
  248. options = "menubar=yes,location=no,status=no";
  249. } else {
  250. options = "menubar=no,location=no,status=no";
  251. }
  252. } else {
  253. options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
  254. }
  255. if (open.hasAttribute("width")) {
  256. int w = open.getIntAttribute("width");
  257. options += ",width=" + w;
  258. }
  259. if (open.hasAttribute("height")) {
  260. int h = open.getIntAttribute("height");
  261. options += ",height=" + h;
  262. }
  263. Window.open(url, target, options);
  264. } else {
  265. open(url, target);
  266. }
  267. }
  268. childIndex++;
  269. }
  270. if (isClosed) {
  271. // We're navigating away, so stop the application.
  272. client.setApplicationRunning(false);
  273. return;
  274. }
  275. // Handle other UIDL children
  276. UIDL childUidl;
  277. while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) {
  278. String tag = childUidl.getTag().intern();
  279. if (tag == "actions") {
  280. if (getWidget().actionHandler == null) {
  281. getWidget().actionHandler = new ShortcutActionHandler(
  282. getWidget().id, client);
  283. }
  284. getWidget().actionHandler.updateActionMap(childUidl);
  285. } else if (tag == "notifications") {
  286. for (final Iterator<?> it = childUidl.getChildIterator(); it
  287. .hasNext();) {
  288. final UIDL notification = (UIDL) it.next();
  289. VNotification.showNotification(client, notification);
  290. }
  291. } else if (tag == "css-injections") {
  292. injectCSS(childUidl);
  293. }
  294. }
  295. if (uidl.hasAttribute("focused")) {
  296. // set focused component when render phase is finished
  297. Scheduler.get().scheduleDeferred(new Command() {
  298. @Override
  299. public void execute() {
  300. ComponentConnector connector = (ComponentConnector) uidl
  301. .getPaintableAttribute("focused", getConnection());
  302. if (connector == null) {
  303. // Do not try to focus invisible components which not
  304. // present in UIDL
  305. return;
  306. }
  307. final Widget toBeFocused = connector.getWidget();
  308. /*
  309. * Two types of Widgets can be focused, either implementing
  310. * GWT Focusable of a thinner Vaadin specific Focusable
  311. * interface.
  312. */
  313. if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
  314. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
  315. toBeFocusedWidget.setFocus(true);
  316. } else if (toBeFocused instanceof Focusable) {
  317. ((Focusable) toBeFocused).focus();
  318. } else {
  319. getLogger()
  320. .severe("Server is trying to set focus to the widget of connector "
  321. + Util.getConnectorString(connector)
  322. + " but it is not focusable. The widget should implement either "
  323. + com.google.gwt.user.client.ui.Focusable.class
  324. .getName()
  325. + " or "
  326. + Focusable.class.getName());
  327. }
  328. }
  329. });
  330. }
  331. // Add window listeners on first paint, to prevent premature
  332. // variablechanges
  333. if (firstPaint) {
  334. Window.addWindowClosingHandler(getWidget());
  335. Window.addResizeHandler(getWidget());
  336. }
  337. if (uidl.hasAttribute("scrollTo")) {
  338. final ComponentConnector connector = (ComponentConnector) uidl
  339. .getPaintableAttribute("scrollTo", getConnection());
  340. scrollIntoView(connector);
  341. }
  342. if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) {
  343. String location = uidl
  344. .getStringAttribute(UIConstants.LOCATION_VARIABLE);
  345. String newFragment;
  346. int fragmentIndex = location.indexOf('#');
  347. if (fragmentIndex >= 0) {
  348. // Decode fragment to avoid double encoding (#10769)
  349. newFragment = URL.decodePathSegment(location
  350. .substring(fragmentIndex + 1));
  351. if (newFragment.isEmpty()
  352. && Location.getHref().indexOf('#') == -1) {
  353. // Ensure there is a trailing # even though History and
  354. // Location.getHash() treat null and "" the same way.
  355. Location.assign(Location.getHref() + "#");
  356. }
  357. } else {
  358. // No fragment in server-side location, but can't completely
  359. // remove the browser fragment since that would reload the page
  360. newFragment = "";
  361. }
  362. getWidget().currentFragment = newFragment;
  363. if (!newFragment.equals(History.getToken())) {
  364. History.newItem(newFragment, true);
  365. }
  366. }
  367. if (firstPaint) {
  368. // Queue the initial window size to be sent with the following
  369. // request.
  370. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  371. @Override
  372. public void execute() {
  373. getWidget().sendClientResized();
  374. }
  375. });
  376. }
  377. }
  378. /**
  379. * Reads CSS strings and resources injected by {@link Styles#inject} from
  380. * the UIDL stream.
  381. *
  382. * @param uidl
  383. * The uidl which contains "css-resource" and "css-string" tags
  384. */
  385. private void injectCSS(UIDL uidl) {
  386. /*
  387. * Search the UIDL stream for CSS resources and strings to be injected.
  388. */
  389. for (Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
  390. UIDL cssInjectionsUidl = (UIDL) it.next();
  391. // Check if we have resources to inject
  392. if (cssInjectionsUidl.getTag().equals("css-resource")) {
  393. String url = getWidget().connection
  394. .translateVaadinUri(cssInjectionsUidl
  395. .getStringAttribute("url"));
  396. LinkElement link = LinkElement.as(DOM
  397. .createElement(LinkElement.TAG));
  398. link.setRel("stylesheet");
  399. link.setHref(url);
  400. link.setType("text/css");
  401. getHead().appendChild(link);
  402. // Check if we have CSS string to inject
  403. } else if (cssInjectionsUidl.getTag().equals("css-string")) {
  404. for (Iterator<?> it2 = cssInjectionsUidl.getChildIterator(); it2
  405. .hasNext();) {
  406. StyleInjector.injectAtEnd((String) it2.next());
  407. StyleInjector.flush();
  408. }
  409. }
  410. }
  411. }
  412. /**
  413. * Internal helper to get the <head> tag of the page
  414. *
  415. * @since 7.3
  416. * @return the head element
  417. */
  418. private HeadElement getHead() {
  419. return HeadElement.as(Document.get()
  420. .getElementsByTagName(HeadElement.TAG).getItem(0));
  421. }
  422. /**
  423. * Internal helper for removing any stylesheet with the given URL
  424. *
  425. * @since 7.3
  426. * @param url
  427. * the url to match with existing stylesheets
  428. */
  429. private void removeStylesheet(String url) {
  430. NodeList<Element> linkTags = getHead().getElementsByTagName(
  431. LinkElement.TAG);
  432. for (int i = 0; i < linkTags.getLength(); i++) {
  433. LinkElement link = LinkElement.as(linkTags.getItem(i));
  434. if (!"stylesheet".equals(link.getRel())) {
  435. continue;
  436. }
  437. if (!"text/css".equals(link.getType())) {
  438. continue;
  439. }
  440. if (url.equals(link.getHref())) {
  441. getHead().removeChild(link);
  442. }
  443. }
  444. }
  445. public void init(String rootPanelId,
  446. ApplicationConnection applicationConnection) {
  447. // Create a style tag for style injections so they don't end up in
  448. // the theme tag in IE8-IE10 (we don't want to wipe them out if we
  449. // change theme).
  450. // StyleInjectorImplIE always injects to the last style tag on the page.
  451. if (BrowserInfo.get().isIE()
  452. && BrowserInfo.get().getBrowserMajorVersion() < 11) {
  453. StyleElement style = Document.get().createStyleElement();
  454. style.setType("text/css");
  455. getHead().appendChild(style);
  456. }
  457. Widget shortcutContextWidget = getWidget();
  458. if (applicationConnection.getConfiguration().isStandalone()) {
  459. // Listen to body for standalone apps (#19392)
  460. shortcutContextWidget = RootPanel.get(); // document body
  461. }
  462. shortcutContextWidget.addDomHandler(new KeyDownHandler() {
  463. @Override
  464. public void onKeyDown(KeyDownEvent event) {
  465. if (getWidget().actionHandler != null) {
  466. Element target = Element.as(event.getNativeEvent()
  467. .getEventTarget());
  468. if (target == Document.get().getBody()
  469. || getWidget().getElement().isOrHasChild(target)) {
  470. // Only react to body and elements inside the UI
  471. getWidget().actionHandler
  472. .handleKeyboardEvent((Event) event
  473. .getNativeEvent().cast());
  474. }
  475. }
  476. }
  477. }, KeyDownEvent.getType());
  478. DOM.sinkEvents(getWidget().getElement(), Event.ONSCROLL);
  479. RootPanel root = RootPanel.get(rootPanelId);
  480. // Remove the v-app-loading or any splash screen added inside the div by
  481. // the user
  482. root.getElement().setInnerHTML("");
  483. // Activate the initial theme by only adding the class name. Not calling
  484. // activateTheme here as it will also cause a full layout and updates to
  485. // the overlay container which has not yet been created at this point
  486. activeTheme = applicationConnection.getConfiguration().getThemeName();
  487. root.addStyleName(activeTheme);
  488. root.add(getWidget());
  489. // Set default tab index before focus call. State change handler
  490. // will update this later if needed.
  491. getWidget().setTabIndex(1);
  492. if (applicationConnection.getConfiguration().isStandalone()) {
  493. // set focus to iview element by default to listen possible keyboard
  494. // shortcuts. For embedded applications this is unacceptable as we
  495. // don't want to steal focus from the main page nor we don't want
  496. // side-effects from focusing (scrollIntoView).
  497. getWidget().getElement().focus();
  498. }
  499. applicationConnection.addHandler(
  500. ApplicationConnection.ApplicationStoppedEvent.TYPE,
  501. new ApplicationConnection.ApplicationStoppedHandler() {
  502. @Override
  503. public void onApplicationStopped(
  504. ApplicationStoppedEvent event) {
  505. // Stop any polling
  506. if (pollTimer != null) {
  507. pollTimer.cancel();
  508. pollTimer = null;
  509. }
  510. }
  511. });
  512. }
  513. private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
  514. @Override
  515. protected void fireClick(NativeEvent event,
  516. MouseEventDetails mouseDetails) {
  517. getRpcProxy(UIServerRpc.class).click(mouseDetails);
  518. }
  519. };
  520. private Timer pollTimer = null;
  521. @Override
  522. public void updateCaption(ComponentConnector component) {
  523. // NOP The main view never draws caption for its layout
  524. }
  525. @Override
  526. public VUI getWidget() {
  527. return (VUI) super.getWidget();
  528. }
  529. @Override
  530. protected ComponentConnector getContent() {
  531. ComponentConnector connector = super.getContent();
  532. // VWindow (WindowConnector is its connector)is also a child component
  533. // but it's never a content widget
  534. if (connector instanceof WindowConnector) {
  535. return null;
  536. } else {
  537. return connector;
  538. }
  539. }
  540. protected void onChildSizeChange() {
  541. ComponentConnector child = getContent();
  542. if (child == null) {
  543. return;
  544. }
  545. Style childStyle = child.getWidget().getElement().getStyle();
  546. /*
  547. * Must set absolute position if the child has relative height and
  548. * there's a chance of horizontal scrolling as some browsers will
  549. * otherwise not take the scrollbar into account when calculating the
  550. * height. Assuming v-ui does not have an undefined width for now, see
  551. * #8460.
  552. */
  553. if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) {
  554. childStyle.setPosition(Position.ABSOLUTE);
  555. } else {
  556. childStyle.clearPosition();
  557. }
  558. }
  559. /**
  560. * Checks if the given sub window is a child of this UI Connector
  561. *
  562. * @deprecated Should be replaced by a more generic mechanism for getting
  563. * non-ComponentConnector children
  564. * @param wc
  565. * @return
  566. */
  567. @Deprecated
  568. public boolean hasSubWindow(WindowConnector wc) {
  569. return getChildComponents().contains(wc);
  570. }
  571. /**
  572. * Return an iterator for current subwindows. This method is meant for
  573. * testing purposes only.
  574. *
  575. * @return
  576. */
  577. public List<WindowConnector> getSubWindows() {
  578. ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>();
  579. for (ComponentConnector child : getChildComponents()) {
  580. if (child instanceof WindowConnector) {
  581. windows.add((WindowConnector) child);
  582. }
  583. }
  584. return windows;
  585. }
  586. @Override
  587. public UIState getState() {
  588. return (UIState) super.getState();
  589. }
  590. /**
  591. * Returns the state of the Page associated with the UI.
  592. * <p>
  593. * Note that state is considered an internal part of the connector. You
  594. * should not rely on the state object outside of the connector who owns it.
  595. * If you depend on the state of other connectors you should use their
  596. * public API instead of their state object directly. The page state might
  597. * not be an independent state object but can be embedded in UI state.
  598. * </p>
  599. *
  600. * @since 7.1
  601. * @return state object of the page
  602. */
  603. public PageState getPageState() {
  604. return getState().pageState;
  605. }
  606. @Override
  607. public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
  608. ComponentConnector oldChild = null;
  609. ComponentConnector newChild = getContent();
  610. for (ComponentConnector c : event.getOldChildren()) {
  611. if (!(c instanceof WindowConnector)) {
  612. oldChild = c;
  613. break;
  614. }
  615. }
  616. if (oldChild != newChild) {
  617. if (childStateChangeHandlerRegistration != null) {
  618. childStateChangeHandlerRegistration.removeHandler();
  619. childStateChangeHandlerRegistration = null;
  620. }
  621. if (newChild != null) {
  622. getWidget().setWidget(newChild.getWidget());
  623. childStateChangeHandlerRegistration = newChild
  624. .addStateChangeHandler(childStateChangeHandler);
  625. // Must handle new child here as state change events are already
  626. // fired
  627. onChildSizeChange();
  628. } else {
  629. getWidget().setWidget(null);
  630. }
  631. }
  632. for (ComponentConnector c : getChildComponents()) {
  633. if (c instanceof WindowConnector) {
  634. WindowConnector wc = (WindowConnector) c;
  635. wc.setWindowOrderAndPosition();
  636. VWindow window = wc.getWidget();
  637. if (!window.isAttached()) {
  638. // Attach so that all widgets inside the Window are attached
  639. // when their onStateChange is run
  640. // Made invisible here for legacy reasons and made visible
  641. // at the end of stateChange. This dance could probably be
  642. // removed
  643. window.setVisible(false);
  644. window.show();
  645. }
  646. }
  647. }
  648. // Close removed sub windows
  649. for (ComponentConnector c : event.getOldChildren()) {
  650. if (c.getParent() != this && c instanceof WindowConnector) {
  651. ((WindowConnector) c).getWidget().hide();
  652. }
  653. }
  654. }
  655. @Override
  656. public boolean hasTooltip() {
  657. /*
  658. * Always return true so there's always top level tooltip handler that
  659. * takes care of hiding tooltips whenever the mouse is moved somewhere
  660. * else.
  661. */
  662. return true;
  663. }
  664. /**
  665. * Tries to scroll the viewport so that the given connector is in view.
  666. *
  667. * @param componentConnector
  668. * The connector which should be visible
  669. *
  670. */
  671. public void scrollIntoView(final ComponentConnector componentConnector) {
  672. if (componentConnector == null) {
  673. return;
  674. }
  675. Scheduler.get().scheduleDeferred(new Command() {
  676. @Override
  677. public void execute() {
  678. componentConnector.getWidget().getElement().scrollIntoView();
  679. }
  680. });
  681. }
  682. @Override
  683. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  684. super.onStateChanged(stateChangeEvent);
  685. if (stateChangeEvent.hasPropertyChanged("tooltipConfiguration")) {
  686. getConnection().getVTooltip().setCloseTimeout(
  687. getState().tooltipConfiguration.closeTimeout);
  688. getConnection().getVTooltip().setOpenDelay(
  689. getState().tooltipConfiguration.openDelay);
  690. getConnection().getVTooltip().setQuickOpenDelay(
  691. getState().tooltipConfiguration.quickOpenDelay);
  692. getConnection().getVTooltip().setQuickOpenTimeout(
  693. getState().tooltipConfiguration.quickOpenTimeout);
  694. getConnection().getVTooltip().setMaxWidth(
  695. getState().tooltipConfiguration.maxWidth);
  696. }
  697. if (stateChangeEvent
  698. .hasPropertyChanged("loadingIndicatorConfiguration")) {
  699. getConnection().getLoadingIndicator().setFirstDelay(
  700. getState().loadingIndicatorConfiguration.firstDelay);
  701. getConnection().getLoadingIndicator().setSecondDelay(
  702. getState().loadingIndicatorConfiguration.secondDelay);
  703. getConnection().getLoadingIndicator().setThirdDelay(
  704. getState().loadingIndicatorConfiguration.thirdDelay);
  705. }
  706. if (stateChangeEvent.hasPropertyChanged("pollInterval")) {
  707. configurePolling();
  708. }
  709. if (stateChangeEvent.hasPropertyChanged("pageState.title")) {
  710. String title = getState().pageState.title;
  711. if (title != null) {
  712. com.google.gwt.user.client.Window.setTitle(title);
  713. }
  714. }
  715. if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) {
  716. getConnection().getMessageSender().setPushEnabled(
  717. getState().pushConfiguration.mode.isEnabled());
  718. }
  719. if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) {
  720. getConnection().getConnectionStateHandler().configurationUpdated();
  721. }
  722. if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) {
  723. VOverlay.setOverlayContainerLabel(getConnection(),
  724. getState().overlayContainerLabel);
  725. }
  726. }
  727. private void configurePolling() {
  728. if (pollTimer != null) {
  729. pollTimer.cancel();
  730. pollTimer = null;
  731. }
  732. if (getState().pollInterval >= 0) {
  733. pollTimer = new Timer() {
  734. @Override
  735. public void run() {
  736. if (getState().pollInterval < 0) {
  737. // Polling has been cancelled server side
  738. pollTimer.cancel();
  739. pollTimer = null;
  740. return;
  741. }
  742. getRpcProxy(UIServerRpc.class).poll();
  743. // Send changes even though poll is @Delayed
  744. getConnection().getServerRpcQueue().flush();
  745. }
  746. };
  747. pollTimer.scheduleRepeating(getState().pollInterval);
  748. } else {
  749. // Ensure no more polls are sent as polling has been disabled
  750. getConnection().getServerRpcQueue().removeMatching(
  751. new MethodInvocation(getConnectorId(), UIServerRpc.class
  752. .getName(), "poll"));
  753. }
  754. }
  755. /**
  756. * Invokes the layout analyzer on the server
  757. *
  758. * @since 7.1
  759. */
  760. public void analyzeLayouts() {
  761. getRpcProxy(DebugWindowServerRpc.class).analyzeLayouts();
  762. }
  763. /**
  764. * Sends a request to the server to print details to console that will help
  765. * the developer to locate the corresponding server-side connector in the
  766. * source code.
  767. *
  768. * @since 7.1
  769. * @param serverConnector
  770. * the connector to locate
  771. */
  772. public void showServerDebugInfo(ServerConnector serverConnector) {
  773. getRpcProxy(DebugWindowServerRpc.class).showServerDebugInfo(
  774. serverConnector);
  775. }
  776. /**
  777. * Sends a request to the server to print a design to the console for the
  778. * given component.
  779. *
  780. * @since 7.5
  781. * @param connector
  782. * the component connector to output a declarative design for
  783. */
  784. public void showServerDesign(ServerConnector connector) {
  785. getRpcProxy(DebugWindowServerRpc.class).showServerDesign(connector);
  786. }
  787. @OnStateChange("theme")
  788. void onThemeChange() {
  789. final String oldTheme = activeTheme;
  790. final String newTheme = getState().theme;
  791. final String oldThemeUrl = getThemeUrl(oldTheme);
  792. final String newThemeUrl = getThemeUrl(newTheme);
  793. if (SharedUtil.equals(oldTheme, newTheme)) {
  794. // This should only happen on the initial load when activeTheme has
  795. // been updated in init.
  796. if (newTheme == null) {
  797. return;
  798. }
  799. // For the embedded case we cannot be 100% sure that the theme has
  800. // been loaded and that the style names have been set.
  801. if (findStylesheetTag(oldThemeUrl) == null) {
  802. // If there is no style tag, load it the normal way (the class
  803. // name will be added when theme has been loaded)
  804. replaceTheme(null, newTheme, null, newThemeUrl);
  805. } else if (!getWidget().getParent().getElement()
  806. .hasClassName(newTheme)) {
  807. // If only the class name is missing, add that
  808. activateTheme(newTheme);
  809. }
  810. return;
  811. }
  812. getLogger().info("Changing theme from " + oldTheme + " to " + newTheme);
  813. replaceTheme(oldTheme, newTheme, oldThemeUrl, newThemeUrl);
  814. }
  815. /**
  816. * Loads the new theme and removes references to the old theme
  817. *
  818. * @since 7.4.3
  819. * @param oldTheme
  820. * The name of the old theme
  821. * @param newTheme
  822. * The name of the new theme
  823. * @param oldThemeUrl
  824. * The url of the old theme
  825. * @param newThemeUrl
  826. * The url of the new theme
  827. */
  828. protected void replaceTheme(final String oldTheme, final String newTheme,
  829. String oldThemeUrl, final String newThemeUrl) {
  830. LinkElement tagToReplace = null;
  831. if (oldTheme != null) {
  832. tagToReplace = findStylesheetTag(oldThemeUrl);
  833. if (tagToReplace == null) {
  834. getLogger()
  835. .warning(
  836. "Did not find the link tag for the old theme ("
  837. + oldThemeUrl
  838. + "), adding a new stylesheet for the new theme ("
  839. + newThemeUrl + ")");
  840. }
  841. }
  842. if (newTheme != null) {
  843. loadTheme(newTheme, newThemeUrl, tagToReplace);
  844. } else {
  845. if (tagToReplace != null) {
  846. tagToReplace.getParentElement().removeChild(tagToReplace);
  847. }
  848. activateTheme(null);
  849. }
  850. }
  851. private void updateVaadinFavicon(String newTheme) {
  852. NodeList<Element> iconElements = querySelectorAll("link[rel~=\"icon\"]");
  853. for (int i = 0; i < iconElements.getLength(); i++) {
  854. Element iconElement = iconElements.getItem(i);
  855. String href = iconElement.getAttribute("href");
  856. if (href != null && href.contains("VAADIN/themes")
  857. && href.endsWith("/favicon.ico")) {
  858. href = href.replaceFirst("VAADIN/themes/.+?/favicon.ico",
  859. "VAADIN/themes/" + newTheme + "/favicon.ico");
  860. iconElement.setAttribute("href", href);
  861. }
  862. }
  863. }
  864. private static native NodeList<Element> querySelectorAll(String selector)
  865. /*-{
  866. return $doc.querySelectorAll(selector);
  867. }-*/;
  868. /**
  869. * Finds a link tag for a style sheet with the given URL
  870. *
  871. * @since 7.3
  872. * @param url
  873. * the URL of the style sheet
  874. * @return the link tag or null if no matching link tag was found
  875. */
  876. private LinkElement findStylesheetTag(String url) {
  877. NodeList<Element> linkTags = getHead().getElementsByTagName(
  878. LinkElement.TAG);
  879. for (int i = 0; i < linkTags.getLength(); i++) {
  880. final LinkElement link = LinkElement.as(linkTags.getItem(i));
  881. if ("stylesheet".equals(link.getRel())
  882. && "text/css".equals(link.getType())
  883. && url.equals(link.getHref())) {
  884. return link;
  885. }
  886. }
  887. return null;
  888. }
  889. /**
  890. * Loads the given theme and replaces the given link element with the new
  891. * theme link element.
  892. *
  893. * @param newTheme
  894. * The name of the new theme
  895. * @param newThemeUrl
  896. * The url of the new theme
  897. * @param tagToReplace
  898. * The link element to replace. If null, then the new link
  899. * element is added at the end.
  900. */
  901. private void loadTheme(final String newTheme, final String newThemeUrl,
  902. final LinkElement tagToReplace) {
  903. LinkElement newThemeLinkElement = Document.get().createLinkElement();
  904. newThemeLinkElement.setRel("stylesheet");
  905. newThemeLinkElement.setType("text/css");
  906. newThemeLinkElement.setHref(newThemeUrl);
  907. ResourceLoader.addOnloadHandler(newThemeLinkElement,
  908. new ResourceLoadListener() {
  909. @Override
  910. public void onLoad(ResourceLoadEvent event) {
  911. getLogger().info(
  912. "Loading of " + newTheme + " from "
  913. + newThemeUrl + " completed");
  914. if (tagToReplace != null) {
  915. tagToReplace.getParentElement().removeChild(
  916. tagToReplace);
  917. }
  918. activateTheme(newTheme);
  919. }
  920. @Override
  921. public void onError(ResourceLoadEvent event) {
  922. getLogger().warning(
  923. "Could not load theme from "
  924. + getThemeUrl(newTheme));
  925. }
  926. }, null);
  927. if (tagToReplace != null) {
  928. getHead().insertBefore(newThemeLinkElement, tagToReplace);
  929. } else {
  930. getHead().appendChild(newThemeLinkElement);
  931. }
  932. }
  933. /**
  934. * Activates the new theme. Assumes the theme has been loaded and taken into
  935. * use in the browser.
  936. *
  937. * @since 7.4.3
  938. * @param newTheme
  939. * The name of the new theme
  940. */
  941. protected void activateTheme(String newTheme) {
  942. if (activeTheme != null) {
  943. getWidget().getParent().removeStyleName(activeTheme);
  944. VOverlay.getOverlayContainer(getConnection()).removeClassName(
  945. activeTheme);
  946. }
  947. String oldThemeBase = getConnection().translateVaadinUri("theme://");
  948. activeTheme = newTheme;
  949. if (newTheme != null) {
  950. getWidget().getParent().addStyleName(newTheme);
  951. VOverlay.getOverlayContainer(getConnection()).addClassName(
  952. activeTheme);
  953. updateVaadinFavicon(newTheme);
  954. }
  955. // Request a full resynchronization from the server to deal with legacy
  956. // components
  957. getConnection().getMessageSender().resynchronize();
  958. // Immediately update state and do layout while waiting for the resync
  959. forceStateChangeRecursively(UIConnector.this);
  960. getLayoutManager().forceLayout();
  961. }
  962. /**
  963. * Force a full recursive recheck of every connector's state variables.
  964. *
  965. * @see #forceStateChange()
  966. *
  967. * @since 7.3
  968. */
  969. protected static void forceStateChangeRecursively(
  970. AbstractConnector connector) {
  971. connector.forceStateChange();
  972. for (ServerConnector child : connector.getChildren()) {
  973. if (child instanceof AbstractConnector) {
  974. forceStateChangeRecursively((AbstractConnector) child);
  975. } else {
  976. getLogger().warning(
  977. "Could not force state change for unknown connector type: "
  978. + child.getClass().getName());
  979. }
  980. }
  981. }
  982. /**
  983. * Internal helper to get the theme URL for a given theme
  984. *
  985. * @since 7.3
  986. * @param theme
  987. * the name of the theme
  988. * @return The URL the theme can be loaded from
  989. */
  990. private String getThemeUrl(String theme) {
  991. String themeUrl = getConnection().translateVaadinUri(
  992. ApplicationConstants.VAADIN_PROTOCOL_PREFIX + "themes/" + theme
  993. + "/styles" + ".css");
  994. // Parameter appended to bypass caches after version upgrade.
  995. themeUrl += "?v=" + Version.getFullVersion();
  996. return themeUrl;
  997. }
  998. /**
  999. * Returns the name of the theme currently in used by the UI
  1000. *
  1001. * @since 7.3
  1002. * @return the theme name used by this UI
  1003. */
  1004. public String getActiveTheme() {
  1005. return activeTheme;
  1006. }
  1007. private static Logger getLogger() {
  1008. return Logger.getLogger(UIConnector.class.getName());
  1009. }
  1010. /**
  1011. * Send an acknowledgement RPC to the server. This allows the server to know
  1012. * which messages the client has received, even when the client is not
  1013. * sending any other traffic.
  1014. */
  1015. public void sendAck() {
  1016. getRpcProxy(UIServerRpc.class).acknowledge();
  1017. }
  1018. }