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.

VUI.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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;
  17. import java.util.ArrayList;
  18. import com.google.gwt.core.client.Scheduler;
  19. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  20. import com.google.gwt.dom.client.Element;
  21. import com.google.gwt.event.dom.client.HasScrollHandlers;
  22. import com.google.gwt.event.dom.client.ScrollEvent;
  23. import com.google.gwt.event.dom.client.ScrollHandler;
  24. import com.google.gwt.event.logical.shared.HasResizeHandlers;
  25. import com.google.gwt.event.logical.shared.ResizeEvent;
  26. import com.google.gwt.event.logical.shared.ResizeHandler;
  27. import com.google.gwt.event.logical.shared.ValueChangeEvent;
  28. import com.google.gwt.event.logical.shared.ValueChangeHandler;
  29. import com.google.gwt.event.shared.HandlerRegistration;
  30. import com.google.gwt.http.client.URL;
  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.SimplePanel;
  37. import com.vaadin.client.ApplicationConnection;
  38. import com.vaadin.client.BrowserInfo;
  39. import com.vaadin.client.ComponentConnector;
  40. import com.vaadin.client.ConnectorMap;
  41. import com.vaadin.client.Focusable;
  42. import com.vaadin.client.LayoutManager;
  43. import com.vaadin.client.Profiler;
  44. import com.vaadin.client.Util;
  45. import com.vaadin.client.VConsole;
  46. import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  47. import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
  48. import com.vaadin.shared.ApplicationConstants;
  49. import com.vaadin.shared.ui.ui.UIConstants;
  50. /**
  51. *
  52. */
  53. public class VUI extends SimplePanel implements ResizeHandler,
  54. Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable,
  55. com.google.gwt.user.client.ui.Focusable, HasResizeHandlers,
  56. HasScrollHandlers {
  57. private static int MONITOR_PARENT_TIMER_INTERVAL = 1000;
  58. /** For internal use only. May be removed or replaced in the future. */
  59. public String theme;
  60. /** For internal use only. May be removed or replaced in the future. */
  61. public String id;
  62. /** For internal use only. May be removed or replaced in the future. */
  63. public ShortcutActionHandler actionHandler;
  64. /*
  65. * Last known window size used to detect whether VView should be layouted
  66. * again. Detection must check window size, because the VView size might be
  67. * fixed and thus not automatically adapt to changed window sizes.
  68. */
  69. private int windowWidth;
  70. private int windowHeight;
  71. /*
  72. * Last know view size used to detect whether new dimensions should be sent
  73. * to the server.
  74. */
  75. private int viewWidth;
  76. private int viewHeight;
  77. /** For internal use only. May be removed or replaced in the future. */
  78. public ApplicationConnection connection;
  79. /**
  80. * Keep track of possible parent size changes when an embedded application.
  81. *
  82. * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to
  83. * keep track of when there is a real change.
  84. */
  85. private Timer resizeTimer;
  86. /** stored width of parent for embedded application auto-resize */
  87. private int parentWidth;
  88. /** stored height of parent for embedded application auto-resize */
  89. private int parentHeight;
  90. /** For internal use only. May be removed or replaced in the future. */
  91. public boolean immediate;
  92. /** For internal use only. May be removed or replaced in the future. */
  93. public boolean resizeLazy = false;
  94. private HandlerRegistration historyHandlerRegistration;
  95. private TouchScrollHandler touchScrollHandler;
  96. /**
  97. * The current URI fragment, used to avoid sending updates if nothing has
  98. * changed.
  99. * <p>
  100. * For internal use only. May be removed or replaced in the future.
  101. */
  102. public String currentFragment;
  103. /**
  104. * Listener for URI fragment changes. Notifies the server of the new value
  105. * whenever the value changes.
  106. */
  107. private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
  108. @Override
  109. public void onValueChange(ValueChangeEvent<String> event) {
  110. String newFragment = event.getValue();
  111. // Send the location to the server if the fragment has changed
  112. // and flush active connectors in UI.
  113. if (!newFragment.equals(currentFragment) && connection != null) {
  114. /*
  115. * Ensure the fragment is properly encoded in all browsers
  116. * (#10769)
  117. *
  118. * createUrlBuilder does not properly pass an empty fragment to
  119. * UrlBuilder on Webkit browsers so do it manually (#11686)
  120. */
  121. String location = Window.Location
  122. .createUrlBuilder()
  123. .setHash(
  124. URL.decodeQueryString(Window.Location.getHash()))
  125. .buildString();
  126. currentFragment = newFragment;
  127. connection.flushActiveConnector();
  128. connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
  129. location, true);
  130. }
  131. }
  132. };
  133. private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
  134. new ScheduledCommand() {
  135. @Override
  136. public void execute() {
  137. performSizeCheck();
  138. }
  139. });
  140. private Element storedFocus;
  141. public VUI() {
  142. super();
  143. // Allow focusing the view by using the focus() method, the view
  144. // should not be in the document focus flow
  145. getElement().setTabIndex(-1);
  146. makeScrollable();
  147. }
  148. /**
  149. * Start to periodically monitor for parent element resizes if embedded
  150. * application (e.g. portlet).
  151. */
  152. @Override
  153. protected void onLoad() {
  154. super.onLoad();
  155. if (isMonitoringParentSize()) {
  156. resizeTimer = new Timer() {
  157. @Override
  158. public void run() {
  159. // trigger check to see if parent size has changed,
  160. // recalculate layouts
  161. performSizeCheck();
  162. resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
  163. }
  164. };
  165. resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
  166. }
  167. }
  168. @Override
  169. protected void onAttach() {
  170. super.onAttach();
  171. historyHandlerRegistration = History
  172. .addValueChangeHandler(historyChangeHandler);
  173. currentFragment = History.getToken();
  174. }
  175. @Override
  176. protected void onDetach() {
  177. super.onDetach();
  178. historyHandlerRegistration.removeHandler();
  179. historyHandlerRegistration = null;
  180. }
  181. /**
  182. * Stop monitoring for parent element resizes.
  183. */
  184. @Override
  185. protected void onUnload() {
  186. if (resizeTimer != null) {
  187. resizeTimer.cancel();
  188. resizeTimer = null;
  189. }
  190. super.onUnload();
  191. }
  192. /**
  193. * Called when the window or parent div might have been resized.
  194. *
  195. * This immediately checks the sizes of the window and the parent div (if
  196. * monitoring it) and triggers layout recalculation if they have changed.
  197. */
  198. protected void performSizeCheck() {
  199. windowSizeMaybeChanged(Window.getClientWidth(),
  200. Window.getClientHeight());
  201. }
  202. /**
  203. * Called when the window or parent div might have been resized.
  204. *
  205. * This immediately checks the sizes of the window and the parent div (if
  206. * monitoring it) and triggers layout recalculation if they have changed.
  207. *
  208. * @param newWindowWidth
  209. * The new width of the window
  210. * @param newWindowHeight
  211. * The new height of the window
  212. *
  213. * @deprecated use {@link #performSizeCheck()}
  214. */
  215. @Deprecated
  216. protected void windowSizeMaybeChanged(int newWindowWidth,
  217. int newWindowHeight) {
  218. if (connection == null) {
  219. // Connection is null if the timer fires before the first UIDL
  220. // update
  221. return;
  222. }
  223. boolean changed = false;
  224. ComponentConnector connector = ConnectorMap.get(connection)
  225. .getConnector(this);
  226. if (windowWidth != newWindowWidth) {
  227. windowWidth = newWindowWidth;
  228. changed = true;
  229. connector.getLayoutManager().reportOuterWidth(connector,
  230. newWindowWidth);
  231. VConsole.log("New window width: " + windowWidth);
  232. }
  233. if (windowHeight != newWindowHeight) {
  234. windowHeight = newWindowHeight;
  235. changed = true;
  236. connector.getLayoutManager().reportOuterHeight(connector,
  237. newWindowHeight);
  238. VConsole.log("New window height: " + windowHeight);
  239. }
  240. Element parentElement = getElement().getParentElement();
  241. if (isMonitoringParentSize() && parentElement != null) {
  242. // check also for parent size changes
  243. int newParentWidth = parentElement.getClientWidth();
  244. int newParentHeight = parentElement.getClientHeight();
  245. if (parentWidth != newParentWidth) {
  246. parentWidth = newParentWidth;
  247. changed = true;
  248. VConsole.log("New parent width: " + parentWidth);
  249. }
  250. if (parentHeight != newParentHeight) {
  251. parentHeight = newParentHeight;
  252. changed = true;
  253. VConsole.log("New parent height: " + parentHeight);
  254. }
  255. }
  256. if (changed) {
  257. /*
  258. * If the window size has changed, layout the VView again and send
  259. * new size to the server if the size changed. (Just checking VView
  260. * size would cause us to ignore cases when a relatively sized VView
  261. * should shrink as the content's size is fixed and would thus not
  262. * automatically shrink.)
  263. */
  264. VConsole.log("Running layout functions due to window or parent resize");
  265. // update size to avoid (most) redundant re-layout passes
  266. // there can still be an extra layout recalculation if webkit
  267. // overflow fix updates the size in a deferred block
  268. if (isMonitoringParentSize() && parentElement != null) {
  269. parentWidth = parentElement.getClientWidth();
  270. parentHeight = parentElement.getClientHeight();
  271. }
  272. sendClientResized();
  273. LayoutManager layoutManager = connector.getLayoutManager();
  274. if (layoutManager.isLayoutRunning()) {
  275. layoutManager.layoutLater();
  276. } else {
  277. layoutManager.layoutNow();
  278. }
  279. }
  280. }
  281. public String getTheme() {
  282. return theme;
  283. }
  284. /**
  285. * Used to reload host page on theme changes.
  286. * <p>
  287. * For internal use only. May be removed or replaced in the future.
  288. */
  289. public static native void reloadHostPage()
  290. /*-{
  291. $wnd.location.reload();
  292. }-*/;
  293. /**
  294. * Returns true if the body is NOT generated, i.e if someone else has made
  295. * the page that we're running in. Otherwise we're in charge of the whole
  296. * page.
  297. *
  298. * @return true if we're running embedded
  299. */
  300. public boolean isEmbedded() {
  301. return !getElement().getOwnerDocument().getBody().getClassName()
  302. .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME);
  303. }
  304. /**
  305. * Returns true if the size of the parent should be checked periodically and
  306. * the application should react to its changes.
  307. *
  308. * @return true if size of parent should be tracked
  309. */
  310. protected boolean isMonitoringParentSize() {
  311. // could also perform a more specific check (Liferay portlet)
  312. return isEmbedded();
  313. }
  314. @Override
  315. public void onBrowserEvent(Event event) {
  316. super.onBrowserEvent(event);
  317. int type = DOM.eventGetType(event);
  318. if (type == Event.ONKEYDOWN && actionHandler != null) {
  319. actionHandler.handleKeyboardEvent(event);
  320. return;
  321. }
  322. }
  323. /*
  324. * (non-Javadoc)
  325. *
  326. * @see
  327. * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
  328. * .gwt.event.logical.shared.ResizeEvent)
  329. */
  330. @Override
  331. public void onResize(ResizeEvent event) {
  332. triggerSizeChangeCheck();
  333. }
  334. /**
  335. * Called when a resize event is received.
  336. *
  337. * This may trigger a lazy refresh or perform the size check immediately
  338. * depending on the browser used and whether the server side requests
  339. * resizes to be lazy.
  340. */
  341. private void triggerSizeChangeCheck() {
  342. /*
  343. * IE (pre IE9 at least) will give us some false resize events due to
  344. * problems with scrollbars. Firefox 3 might also produce some extra
  345. * events. We postpone both the re-layouting and the server side event
  346. * for a while to deal with these issues.
  347. *
  348. * We may also postpone these events to avoid slowness when resizing the
  349. * browser window. Constantly recalculating the layout causes the resize
  350. * operation to be really slow with complex layouts.
  351. */
  352. boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
  353. if (lazy) {
  354. delayedResizeExecutor.trigger();
  355. } else {
  356. performSizeCheck();
  357. }
  358. }
  359. /**
  360. * Send new dimensions to the server.
  361. * <p>
  362. * For internal use only. May be removed or replaced in the future.
  363. */
  364. public void sendClientResized() {
  365. Profiler.enter("VUI.sendClientResized");
  366. Element parentElement = getElement().getParentElement();
  367. int viewHeight = parentElement.getClientHeight();
  368. int viewWidth = parentElement.getClientWidth();
  369. ResizeEvent.fire(this, viewWidth, viewHeight);
  370. Profiler.leave("VUI.sendClientResized");
  371. }
  372. public native static void goTo(String url)
  373. /*-{
  374. $wnd.location = url;
  375. }-*/;
  376. @Override
  377. public void onWindowClosing(Window.ClosingEvent event) {
  378. // Change focus on this window in order to ensure that all state is
  379. // collected from textfields
  380. // TODO this is a naive hack, that only works with text fields and may
  381. // cause some odd issues. Should be replaced with a decent solution, see
  382. // also related BeforeShortcutActionListener interface. Same interface
  383. // might be usable here.
  384. VTextField.flushChangesFromFocusedTextField();
  385. }
  386. private native static void loadAppIdListFromDOM(ArrayList<String> list)
  387. /*-{
  388. var j;
  389. for(j in $wnd.vaadin.vaadinConfigurations) {
  390. // $entry not needed as function is not exported
  391. list.@java.util.Collection::add(Ljava/lang/Object;)(j);
  392. }
  393. }-*/;
  394. @Override
  395. public ShortcutActionHandler getShortcutActionHandler() {
  396. return actionHandler;
  397. }
  398. @Override
  399. public void focus() {
  400. setFocus(true);
  401. }
  402. /**
  403. * Ensures the widget is scrollable eg. after style name changes.
  404. * <p>
  405. * For internal use only. May be removed or replaced in the future.
  406. */
  407. public void makeScrollable() {
  408. if (touchScrollHandler == null) {
  409. touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
  410. }
  411. touchScrollHandler.addElement(getElement());
  412. }
  413. @Override
  414. public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) {
  415. return addHandler(resizeHandler, ResizeEvent.getType());
  416. }
  417. @Override
  418. public HandlerRegistration addScrollHandler(ScrollHandler scrollHandler) {
  419. return addHandler(scrollHandler, ScrollEvent.getType());
  420. }
  421. @Override
  422. public int getTabIndex() {
  423. return FocusUtil.getTabIndex(this);
  424. }
  425. @Override
  426. public void setAccessKey(char key) {
  427. FocusUtil.setAccessKey(this, key);
  428. }
  429. @Override
  430. public void setFocus(boolean focused) {
  431. FocusUtil.setFocus(this, focused);
  432. }
  433. @Override
  434. public void setTabIndex(int index) {
  435. FocusUtil.setTabIndex(this, index);
  436. }
  437. /**
  438. * Allows to store the currently focused Element.
  439. *
  440. * Current use case is to store the focus when a Window is opened. Does
  441. * currently handle only a single value. Needs to be extended for #12158
  442. *
  443. * @param focusedElement
  444. */
  445. public void storeFocus() {
  446. storedFocus = Util.getFocusedElement();
  447. }
  448. /**
  449. * Restores the previously stored focus Element.
  450. *
  451. * Current use case is to restore the focus when a Window is closed. Does
  452. * currently handle only a single value. Needs to be extended for #12158
  453. *
  454. * @return the lastFocusElementBeforeDialogOpened
  455. */
  456. public void focusStoredElement() {
  457. if (storedFocus != null) {
  458. storedFocus.focus();
  459. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  460. @Override
  461. public void execute() {
  462. storedFocus.focus();
  463. }
  464. });
  465. }
  466. }
  467. }