Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

UIConnector.java 45KB

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