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

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