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.

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