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.

ApplicationConnection.java 63KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.Date;
  7. import java.util.HashMap;
  8. import java.util.HashSet;
  9. import java.util.Iterator;
  10. import java.util.Set;
  11. import com.google.gwt.core.client.GWT;
  12. import com.google.gwt.core.client.JavaScriptObject;
  13. import com.google.gwt.core.client.JsArray;
  14. import com.google.gwt.core.client.JsArrayString;
  15. import com.google.gwt.http.client.Request;
  16. import com.google.gwt.http.client.RequestBuilder;
  17. import com.google.gwt.http.client.RequestCallback;
  18. import com.google.gwt.http.client.RequestException;
  19. import com.google.gwt.http.client.Response;
  20. import com.google.gwt.user.client.Command;
  21. import com.google.gwt.user.client.DOM;
  22. import com.google.gwt.user.client.DeferredCommand;
  23. import com.google.gwt.user.client.Element;
  24. import com.google.gwt.user.client.Event;
  25. import com.google.gwt.user.client.History;
  26. import com.google.gwt.user.client.Timer;
  27. import com.google.gwt.user.client.Window;
  28. import com.google.gwt.user.client.impl.HTTPRequestImpl;
  29. import com.google.gwt.user.client.ui.FocusWidget;
  30. import com.google.gwt.user.client.ui.HasWidgets;
  31. import com.google.gwt.user.client.ui.Widget;
  32. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  33. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  34. import com.vaadin.terminal.gwt.client.ui.Field;
  35. import com.vaadin.terminal.gwt.client.ui.VContextMenu;
  36. import com.vaadin.terminal.gwt.client.ui.VNotification;
  37. import com.vaadin.terminal.gwt.client.ui.VView;
  38. import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent;
  39. /**
  40. * Entry point classes define <code>onModuleLoad()</code>.
  41. */
  42. public class ApplicationConnection {
  43. private static final String MODIFIED_CLASSNAME = "v-modified";
  44. private static final String REQUIRED_CLASSNAME_EXT = "-required";
  45. private static final String ERROR_CLASSNAME_EXT = "-error";
  46. public static final String VAR_RECORD_SEPARATOR = "\u001e";
  47. public static final String VAR_FIELD_SEPARATOR = "\u001f";
  48. public static final String VAR_BURST_SEPARATOR = "\u001d";
  49. public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
  50. public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
  51. /**
  52. * @deprecated use UIDL_SECURITY_TOKEN_ID instead
  53. */
  54. @Deprecated
  55. public static final String UIDL_SECURITY_HEADER = UIDL_SECURITY_TOKEN_ID;
  56. public static final String PARAM_UNLOADBURST = "onunloadburst";
  57. public static final String ATTRIBUTE_DESCRIPTION = "description";
  58. public static final String ATTRIBUTE_ERROR = "error";
  59. private String uidl_security_key = "init";
  60. private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
  61. private static Console console;
  62. private final ArrayList<String> pendingVariables = new ArrayList<String>();
  63. private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap
  64. .create();
  65. private final WidgetSet widgetSet;
  66. private VContextMenu contextMenu = null;
  67. private Timer loadTimer;
  68. private Timer loadTimer2;
  69. private Timer loadTimer3;
  70. private Element loadElement;
  71. private final VView view;
  72. private boolean applicationRunning = false;
  73. private int activeRequests = 0;
  74. /** Parameters for this application connection loaded from the web-page */
  75. private final ApplicationConfiguration configuration;
  76. /** List of pending variable change bursts that must be submitted in order */
  77. private final ArrayList<ArrayList<String>> pendingVariableBursts = new ArrayList<ArrayList<String>>();
  78. /** Timer for automatic refirect to SessionExpiredURL */
  79. private Timer redirectTimer;
  80. /** redirectTimer scheduling interval in seconds */
  81. private int sessionExpirationInterval;
  82. private ArrayList<Paintable> relativeSizeChanges = new ArrayList<Paintable>();;
  83. private ArrayList<Paintable> componentCaptionSizeChanges = new ArrayList<Paintable>();;
  84. private Date requestStartTime;
  85. private boolean validatingLayouts = false;
  86. private Set<Paintable> zeroWidthComponents = null;
  87. private Set<Paintable> zeroHeightComponents = null;
  88. public ApplicationConnection(WidgetSet widgetSet,
  89. ApplicationConfiguration cnf) {
  90. this.widgetSet = widgetSet;
  91. configuration = cnf;
  92. windowName = configuration.getInitialWindowName();
  93. if (isDebugMode()) {
  94. console = new VDebugConsole(this, cnf, !isQuietDebugMode());
  95. } else {
  96. console = new NullConsole();
  97. }
  98. ComponentLocator componentLocator = new ComponentLocator(this);
  99. String appRootPanelName = cnf.getRootPanelId();
  100. // remove the end (window name) of autogenerated rootpanel id
  101. appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", "");
  102. initializeTestbenchHooks(componentLocator, appRootPanelName);
  103. initializeClientHooks();
  104. view = new VView(cnf.getRootPanelId());
  105. showLoadingIndicator();
  106. }
  107. /**
  108. * Starts this application. Don't call this method directly - it's called by
  109. * {@link ApplicationConfiguration#startNextApplication()}, which should be
  110. * called once this application has started (first response received) or
  111. * failed to start. This ensures that the applications are started in order,
  112. * to avoid session-id problems.
  113. */
  114. void start() {
  115. makeUidlRequest("", true, false, false);
  116. }
  117. private native void initializeTestbenchHooks(
  118. ComponentLocator componentLocator, String TTAppId)
  119. /*-{
  120. var ap = this;
  121. var client = {};
  122. client.isActive = function() {
  123. return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isLoadingIndicatorVisible()();
  124. }
  125. var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
  126. if (vi) {
  127. client.getVersionInfo = function() {
  128. return vi;
  129. }
  130. }
  131. client.getElementByPath = function(id) {
  132. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
  133. }
  134. client.getPathForElement = function(element) {
  135. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
  136. }
  137. if(!$wnd.vaadin.clients) {
  138. $wnd.vaadin.clients = {};
  139. }
  140. $wnd.vaadin.clients[TTAppId] = client;
  141. }-*/;
  142. /**
  143. * Helper for tt initialization
  144. */
  145. @SuppressWarnings("unused")
  146. private JavaScriptObject getVersionInfo() {
  147. return configuration.getVersionInfoJSObject();
  148. }
  149. /**
  150. * Publishes a JavaScript API for mash-up applications.
  151. * <ul>
  152. * <li><code>vaadin.forceSync()</code> sends pending variable changes, in
  153. * effect synchronizing the server and client state. This is done for all
  154. * applications on host page.</li>
  155. * <li><code>vaadin.postRequestHooks</code> is a map of functions which gets
  156. * called after each XHR made by vaadin application. Note, that it is
  157. * attaching js functions responsibility to create the variable like this:
  158. *
  159. * <code><pre>
  160. * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
  161. * postRequestHooks.myHook = function(appId) {
  162. * if(appId == "MyAppOfInterest") {
  163. * // do the staff you need on xhr activity
  164. * }
  165. * }
  166. * </pre></code> First parameter passed to these functions is the identifier
  167. * of Vaadin application that made the request.
  168. * </ul>
  169. *
  170. * TODO make this multi-app aware
  171. */
  172. private native void initializeClientHooks()
  173. /*-{
  174. var app = this;
  175. var oldSync;
  176. if($wnd.vaadin.forceSync) {
  177. oldSync = $wnd.vaadin.forceSync;
  178. }
  179. $wnd.vaadin.forceSync = function() {
  180. if(oldSync) {
  181. oldSync();
  182. }
  183. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
  184. }
  185. var oldForceLayout;
  186. if($wnd.vaadin.forceLayout) {
  187. oldForceLayout = $wnd.vaadin.forceLayout;
  188. }
  189. $wnd.vaadin.forceLayout = function() {
  190. if(oldForceLayout) {
  191. oldForceLayout();
  192. }
  193. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()();
  194. }
  195. }-*/;
  196. /**
  197. * Runs possibly registered client side post request hooks. This is expected
  198. * to be run after each uidl request made by Vaadin application.
  199. *
  200. * @param appId
  201. */
  202. private static native void runPostRequestHooks(String appId)
  203. /*-{
  204. if($wnd.vaadin.postRequestHooks) {
  205. for(var hook in $wnd.vaadin.postRequestHooks) {
  206. if(typeof($wnd.vaadin.postRequestHooks[hook]) == "function") {
  207. try {
  208. $wnd.vaadin.postRequestHooks[hook](appId);
  209. } catch(e) {}
  210. }
  211. }
  212. }
  213. }-*/;
  214. public static Console getConsole() {
  215. return console;
  216. }
  217. /**
  218. * Checks if client side is in debug mode. Practically this is invoked by
  219. * adding ?debug parameter to URI.
  220. *
  221. * @return true if client side is currently been debugged
  222. */
  223. public native static boolean isDebugMode()
  224. /*-{
  225. if($wnd.vaadin.debug) {
  226. var parameters = $wnd.location.search;
  227. var re = /debug[^\/]*$/;
  228. return re.test(parameters);
  229. } else {
  230. return false;
  231. }
  232. }-*/;
  233. private native static boolean isQuietDebugMode()
  234. /*-{
  235. var uri = $wnd.location;
  236. var re = /debug=q[^\/]*$/;
  237. return re.test(uri);
  238. }-*/;
  239. public String getAppUri() {
  240. return configuration.getApplicationUri();
  241. };
  242. public boolean hasActiveRequest() {
  243. return (activeRequests > 0);
  244. }
  245. private void makeUidlRequest(final String requestData,
  246. final boolean repaintAll, final boolean forceSync,
  247. final boolean analyzeLayouts) {
  248. startRequest();
  249. // Security: double cookie submission pattern
  250. final String rd = uidl_security_key + VAR_BURST_SEPARATOR + requestData;
  251. console.log("Making UIDL Request with params: " + rd);
  252. String uri;
  253. if (configuration.usePortletURLs()) {
  254. uri = configuration.getPortletActionURLBase() + "&UIDL=true";
  255. } else {
  256. uri = getAppUri() + "UIDL" + configuration.getPathInfo();
  257. }
  258. if (repaintAll) {
  259. // collect some client side data that will be sent to server on
  260. // initial uidl request
  261. int clientHeight = Window.getClientHeight();
  262. int clientWidth = Window.getClientWidth();
  263. com.google.gwt.dom.client.Element pe = view.getElement()
  264. .getParentElement();
  265. int offsetHeight = pe.getOffsetHeight();
  266. int offsetWidth = pe.getOffsetWidth();
  267. int screenWidth = BrowserInfo.get().getScreenWidth();
  268. int screenHeight = BrowserInfo.get().getScreenHeight();
  269. String token = History.getToken();
  270. // TODO figure out how client and view size could be used better on
  271. // server. screen size can be accessed via Browser object, but other
  272. // values currently only via transaction listener.
  273. if (configuration.usePortletURLs()) {
  274. uri += "&";
  275. } else {
  276. uri += "?";
  277. }
  278. uri += "repaintAll=1&" + "sh=" + screenHeight + "&sw="
  279. + screenWidth + "&cw=" + clientWidth + "&ch="
  280. + clientHeight + "&vw=" + offsetWidth + "&vh="
  281. + offsetHeight + "&fr=" + token;
  282. if (analyzeLayouts) {
  283. uri += "&analyzeLayouts=1";
  284. }
  285. }
  286. if (windowName != null && windowName.length() > 0) {
  287. uri += (repaintAll || configuration.usePortletURLs() ? "&" : "?") + "windowName=" + windowName;
  288. }
  289. if (!forceSync) {
  290. boolean success = false;
  291. final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
  292. uri);
  293. // TODO enable timeout
  294. // rb.setTimeoutMillis(timeoutMillis);
  295. rb.setHeader("Content-Type", "text/plain;charset=utf-8");
  296. try {
  297. rb.sendRequest(rd, new RequestCallback() {
  298. public void onError(Request request, Throwable exception) {
  299. showCommunicationError(exception.getMessage());
  300. endRequest();
  301. if (!applicationRunning) {
  302. // start failed, let's try to start the next app
  303. ApplicationConfiguration.startNextApplication();
  304. }
  305. }
  306. public void onResponseReceived(Request request,
  307. Response response) {
  308. console.log("Server visit took "
  309. + String.valueOf((new Date()).getTime()
  310. - requestStartTime.getTime()) + "ms");
  311. switch (response.getStatusCode()) {
  312. case 0:
  313. showCommunicationError("Invalid status code 0 (server down?)");
  314. return;
  315. // TODO could add more cases
  316. case 503:
  317. // We'll assume msec instead of the usual seconds
  318. int delay = Integer.parseInt(response
  319. .getHeader("Retry-After"));
  320. console.log("503, retrying in " + delay + "msec");
  321. (new Timer() {
  322. @Override
  323. public void run() {
  324. activeRequests--;
  325. makeUidlRequest(requestData, repaintAll,
  326. forceSync, analyzeLayouts);
  327. }
  328. }).schedule(delay);
  329. return;
  330. }
  331. if (applicationRunning) {
  332. handleReceivedJSONMessage(response);
  333. } else {
  334. applicationRunning = true;
  335. handleWhenCSSLoaded(response);
  336. ApplicationConfiguration.startNextApplication();
  337. }
  338. }
  339. int cssWaits = 0;
  340. static final int MAX_CSS_WAITS = 20;
  341. private void handleWhenCSSLoaded(final Response response) {
  342. int heightOfLoadElement = DOM.getElementPropertyInt(
  343. loadElement, "offsetHeight");
  344. if (heightOfLoadElement == 0
  345. && cssWaits < MAX_CSS_WAITS) {
  346. (new Timer() {
  347. @Override
  348. public void run() {
  349. handleWhenCSSLoaded(response);
  350. }
  351. }).schedule(50);
  352. console
  353. .log("Assuming CSS loading is not complete, "
  354. + "postponing render phase. "
  355. + "(.v-loading-indicator height == 0)");
  356. cssWaits++;
  357. } else {
  358. handleReceivedJSONMessage(response);
  359. if (cssWaits >= MAX_CSS_WAITS) {
  360. console
  361. .error("CSS files may have not loaded properly.");
  362. }
  363. }
  364. }
  365. });
  366. } catch (final RequestException e) {
  367. ClientExceptionHandler.displayError(e);
  368. endRequest();
  369. }
  370. } else {
  371. // Synchronized call, discarded response
  372. syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class))
  373. .createXmlHTTPRequest(), uri + "&" + PARAM_UNLOADBURST
  374. + "=1", rd);
  375. }
  376. }
  377. /**
  378. * Shows the communication error notification. The 'details' only go to the
  379. * console for now.
  380. *
  381. * @param details
  382. * Optional details for debugging.
  383. */
  384. private void showCommunicationError(String details) {
  385. console.error("Communication error: " + details);
  386. String html = "";
  387. if (configuration.getCommunicationErrorCaption() != null) {
  388. html += "<h1>" + configuration.getCommunicationErrorCaption()
  389. + "</h1>";
  390. }
  391. if (configuration.getCommunicationErrorMessage() != null) {
  392. html += "<p>" + configuration.getCommunicationErrorMessage()
  393. + "</p>";
  394. }
  395. if (html.length() > 0) {
  396. VNotification n = new VNotification(1000 * 60 * 45);
  397. n.addEventListener(new NotificationRedirect(configuration
  398. .getCommunicationErrorUrl()));
  399. n
  400. .show(html, VNotification.CENTERED_TOP,
  401. VNotification.STYLE_SYSTEM);
  402. } else {
  403. redirect(configuration.getCommunicationErrorUrl());
  404. }
  405. }
  406. private native void syncSendForce(JavaScriptObject xmlHttpRequest,
  407. String uri, String requestData)
  408. /*-{
  409. try {
  410. xmlHttpRequest.open("POST", uri, false);
  411. xmlHttpRequest.setRequestHeader("Content-Type", "text/plain;charset=utf-8");
  412. xmlHttpRequest.send(requestData);
  413. } catch (e) {
  414. // No errors are managed as this is synchronous forceful send that can just fail
  415. }
  416. this.@com.vaadin.terminal.gwt.client.ApplicationConnection::endRequest()();
  417. }-*/;
  418. private void startRequest() {
  419. activeRequests++;
  420. requestStartTime = new Date();
  421. // show initial throbber
  422. if (loadTimer == null) {
  423. loadTimer = new Timer() {
  424. @Override
  425. public void run() {
  426. /*
  427. * IE7 does not properly cancel the event with
  428. * loadTimer.cancel() so we have to check that we really
  429. * should make it visible
  430. */
  431. if (loadTimer != null) {
  432. showLoadingIndicator();
  433. }
  434. }
  435. };
  436. // First one kicks in at 300ms
  437. }
  438. loadTimer.schedule(300);
  439. }
  440. private void endRequest() {
  441. if (applicationRunning) {
  442. checkForPendingVariableBursts();
  443. runPostRequestHooks(configuration.getRootPanelId());
  444. }
  445. activeRequests--;
  446. // deferring to avoid flickering
  447. DeferredCommand.addCommand(new Command() {
  448. public void execute() {
  449. if (activeRequests == 0) {
  450. hideLoadingIndicator();
  451. }
  452. }
  453. });
  454. }
  455. /**
  456. * This method is called after applying uidl change set to application.
  457. *
  458. * It will clean current and queued variable change sets. And send next
  459. * change set if it exists.
  460. */
  461. private void checkForPendingVariableBursts() {
  462. cleanVariableBurst(pendingVariables);
  463. if (pendingVariableBursts.size() > 0) {
  464. for (Iterator<ArrayList<String>> iterator = pendingVariableBursts
  465. .iterator(); iterator.hasNext();) {
  466. cleanVariableBurst(iterator.next());
  467. }
  468. ArrayList<String> nextBurst = pendingVariableBursts.get(0);
  469. pendingVariableBursts.remove(0);
  470. buildAndSendVariableBurst(nextBurst, false);
  471. }
  472. }
  473. /**
  474. * Cleans given queue of variable changes of such changes that came from
  475. * components that do not exist anymore.
  476. *
  477. * @param variableBurst
  478. */
  479. private void cleanVariableBurst(ArrayList<String> variableBurst) {
  480. for (int i = 1; i < variableBurst.size(); i += 2) {
  481. String id = variableBurst.get(i);
  482. id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR));
  483. if (!idToPaintableDetail.containsKey(id)) {
  484. // variable owner does not exist anymore
  485. variableBurst.remove(i - 1);
  486. variableBurst.remove(i - 1);
  487. i -= 2;
  488. ApplicationConnection.getConsole().log(
  489. "Removed variable from removed component: " + id);
  490. }
  491. }
  492. }
  493. private void showLoadingIndicator() {
  494. // show initial throbber
  495. if (loadElement == null) {
  496. loadElement = DOM.createDiv();
  497. DOM.setStyleAttribute(loadElement, "position", "absolute");
  498. DOM.appendChild(view.getElement(), loadElement);
  499. ApplicationConnection.getConsole().log("inserting load indicator");
  500. }
  501. DOM.setElementProperty(loadElement, "className", "v-loading-indicator");
  502. DOM.setStyleAttribute(loadElement, "display", "block");
  503. // Initialize other timers
  504. loadTimer2 = new Timer() {
  505. @Override
  506. public void run() {
  507. DOM.setElementProperty(loadElement, "className",
  508. "v-loading-indicator-delay");
  509. }
  510. };
  511. // Second one kicks in at 1500ms from request start
  512. loadTimer2.schedule(1200);
  513. loadTimer3 = new Timer() {
  514. @Override
  515. public void run() {
  516. DOM.setElementProperty(loadElement, "className",
  517. "v-loading-indicator-wait");
  518. }
  519. };
  520. // Third one kicks in at 5000ms from request start
  521. loadTimer3.schedule(4700);
  522. }
  523. private void hideLoadingIndicator() {
  524. if (loadTimer != null) {
  525. loadTimer.cancel();
  526. if (loadTimer2 != null) {
  527. loadTimer2.cancel();
  528. loadTimer3.cancel();
  529. }
  530. loadTimer = null;
  531. }
  532. if (loadElement != null) {
  533. DOM.setStyleAttribute(loadElement, "display", "none");
  534. }
  535. }
  536. public boolean isLoadingIndicatorVisible() {
  537. if (loadElement == null) {
  538. return false;
  539. }
  540. if (loadElement.getStyle().getProperty("display").equals("none")) {
  541. return false;
  542. }
  543. return true;
  544. }
  545. private static native ValueMap parseJSONResponse(String jsonText)
  546. /*-{
  547. try {
  548. return JSON.parse(jsonText);
  549. } catch(ignored) {
  550. return eval('(' + jsonText + ')');
  551. }
  552. }-*/;
  553. private void handleReceivedJSONMessage(Response response) {
  554. final Date start = new Date();
  555. String jsonText = response.getText();
  556. // for(;;);[realjson]
  557. jsonText = jsonText.substring(9, jsonText.length() - 1);
  558. ValueMap json;
  559. try {
  560. json = parseJSONResponse(jsonText);
  561. } catch (final Exception e) {
  562. endRequest();
  563. showCommunicationError(e.getMessage() + " - Original JSON-text:");
  564. console.log(jsonText);
  565. return;
  566. }
  567. ApplicationConnection.getConsole()
  568. .log(
  569. "JSON parsing took "
  570. + (new Date().getTime() - start.getTime()));
  571. // Handle redirect
  572. if (json.containsKey("redirect")) {
  573. String url = json.getValueMap("redirect").getString("url");
  574. console.log("redirecting to " + url);
  575. redirect(url);
  576. return;
  577. }
  578. // Get security key
  579. if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) {
  580. uidl_security_key = json.getString(UIDL_SECURITY_TOKEN_ID);
  581. }
  582. if (json.containsKey("resources")) {
  583. ValueMap resources = json.getValueMap("resources");
  584. JsArrayString keyArray = resources.getKeyArray();
  585. int l = keyArray.length();
  586. for (int i = 0; i < l; i++) {
  587. String key = keyArray.get(i);
  588. resourcesMap.put(key, resources.getAsString(key));
  589. }
  590. }
  591. if (json.containsKey("typeMappings")) {
  592. configuration.addComponentMappings(
  593. json.getValueMap("typeMappings"), widgetSet);
  594. }
  595. if (json.containsKey("locales")) {
  596. // Store locale data
  597. JsArray<ValueMap> valueMapArray = json
  598. .getJSValueMapArray("locales");
  599. LocaleService.addLocales(valueMapArray);
  600. }
  601. ValueMap meta = null;
  602. if (json.containsKey("meta")) {
  603. meta = json.getValueMap("meta");
  604. if (meta.containsKey("repaintAll")) {
  605. view.clear();
  606. idToPaintableDetail.clear();
  607. if (meta.containsKey("invalidLayouts")) {
  608. validatingLayouts = true;
  609. zeroWidthComponents = new HashSet<Paintable>();
  610. zeroHeightComponents = new HashSet<Paintable>();
  611. }
  612. }
  613. if (meta.containsKey("timedRedirect")) {
  614. final ValueMap timedRedirect = meta
  615. .getValueMap("timedRedirect");
  616. redirectTimer = new Timer() {
  617. @Override
  618. public void run() {
  619. redirect(timedRedirect.getString("url"));
  620. }
  621. };
  622. sessionExpirationInterval = timedRedirect.getInt("interval");
  623. }
  624. }
  625. if (redirectTimer != null) {
  626. redirectTimer.schedule(1000 * sessionExpirationInterval);
  627. }
  628. // Process changes
  629. JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
  630. ArrayList<Paintable> updatedWidgets = new ArrayList<Paintable>();
  631. relativeSizeChanges.clear();
  632. componentCaptionSizeChanges.clear();
  633. int length = changes.length();
  634. for (int i = 0; i < length; i++) {
  635. try {
  636. final UIDL change = changes.get(i).cast();
  637. try {
  638. console.dirUIDL(change);
  639. } catch (final Exception e) {
  640. ClientExceptionHandler.displayError(e);
  641. // TODO: dir doesn't work in any browser although it should
  642. // work (works in hosted mode)
  643. // it partially did at some part but now broken.
  644. }
  645. final UIDL uidl = change.getChildUIDL(0);
  646. // TODO optimize
  647. final Paintable paintable = getPaintable(uidl.getId());
  648. if (paintable != null) {
  649. paintable.updateFromUIDL(uidl, this);
  650. // paintable may have changed during render to another
  651. // implementation, use the new one for updated widgets map
  652. updatedWidgets.add(idToPaintableDetail.get(uidl.getId())
  653. .getComponent());
  654. } else {
  655. if (!uidl.getTag().equals("0")) {
  656. ClientExceptionHandler
  657. .displayError("Received update for "
  658. + uidl.getTag()
  659. + ", but there is no such paintable ("
  660. + uidl.getId() + ") rendered.");
  661. } else {
  662. view.updateFromUIDL(uidl, this);
  663. }
  664. }
  665. } catch (final Throwable e) {
  666. ClientExceptionHandler.displayError(e);
  667. }
  668. }
  669. // Check which widgets' size has been updated
  670. Set<Paintable> sizeUpdatedWidgets = new HashSet<Paintable>();
  671. updatedWidgets.addAll(relativeSizeChanges);
  672. sizeUpdatedWidgets.addAll(componentCaptionSizeChanges);
  673. for (Paintable paintable : updatedWidgets) {
  674. ComponentDetail detail = idToPaintableDetail.get(getPid(paintable));
  675. Widget widget = (Widget) paintable;
  676. Size oldSize = detail.getOffsetSize();
  677. Size newSize = new Size(widget.getOffsetWidth(), widget
  678. .getOffsetHeight());
  679. if (oldSize == null || !oldSize.equals(newSize)) {
  680. sizeUpdatedWidgets.add(paintable);
  681. detail.setOffsetSize(newSize);
  682. }
  683. }
  684. Util.componentSizeUpdated(sizeUpdatedWidgets);
  685. if (meta != null) {
  686. if (meta.containsKey("appError")) {
  687. ValueMap error = meta.getValueMap("appError");
  688. String html = "";
  689. if (error.containsKey("caption")
  690. && error.getString("caption") != null) {
  691. html += "<h1>" + error.getAsString("caption") + "</h1>";
  692. }
  693. if (error.containsKey("message")
  694. && error.getString("message") != null) {
  695. html += "<p>" + error.getAsString("message") + "</p>";
  696. }
  697. String url = null;
  698. if (error.containsKey("url")) {
  699. url = error.getString("url");
  700. }
  701. if (html.length() != 0) {
  702. /* 45 min */
  703. VNotification n = new VNotification(1000 * 60 * 45);
  704. n.addEventListener(new NotificationRedirect(url));
  705. n.show(html, VNotification.CENTERED_TOP,
  706. VNotification.STYLE_SYSTEM);
  707. } else {
  708. redirect(url);
  709. }
  710. applicationRunning = false;
  711. }
  712. if (validatingLayouts) {
  713. getConsole().printLayoutProblems(meta, this,
  714. zeroHeightComponents, zeroWidthComponents);
  715. zeroHeightComponents = null;
  716. zeroWidthComponents = null;
  717. validatingLayouts = false;
  718. }
  719. }
  720. final long prosessingTime = (new Date().getTime()) - start.getTime();
  721. console.log(" Processing time was " + String.valueOf(prosessingTime)
  722. + "ms for " + jsonText.length() + " characters of JSON");
  723. console.log("Referenced paintables: " + idToPaintableDetail.size());
  724. endRequest();
  725. }
  726. /**
  727. * This method assures that all pending variable changes are sent to server.
  728. * Method uses synchronized xmlhttprequest and does not return before the
  729. * changes are sent. No UIDL updates are processed and thus UI is left in
  730. * inconsistent state. This method should be called only when closing
  731. * windows - normally sendPendingVariableChanges() should be used.
  732. */
  733. public void sendPendingVariableChangesSync() {
  734. if (applicationRunning) {
  735. pendingVariableBursts.add(pendingVariables);
  736. ArrayList<String> nextBurst = pendingVariableBursts.get(0);
  737. pendingVariableBursts.remove(0);
  738. buildAndSendVariableBurst(nextBurst, true);
  739. }
  740. }
  741. // Redirect browser, null reloads current page
  742. private static native void redirect(String url)
  743. /*-{
  744. if (url) {
  745. $wnd.location = url;
  746. } else {
  747. $wnd.location.reload(false);
  748. }
  749. }-*/;
  750. public void registerPaintable(String id, Paintable paintable) {
  751. ComponentDetail componentDetail = new ComponentDetail();
  752. componentDetail.setComponent(paintable);
  753. idToPaintableDetail.put(id, componentDetail);
  754. setPid(((Widget) paintable).getElement(), id);
  755. }
  756. private native void setPid(Element el, String pid)
  757. /*-{
  758. el.tkPid = pid;
  759. }-*/;
  760. public String getPid(Paintable paintable) {
  761. return getPid(((Widget) paintable).getElement());
  762. }
  763. public native String getPid(Element el)
  764. /*-{
  765. return el.tkPid;
  766. }-*/;
  767. public Element getElementByPid(String pid) {
  768. return ((Widget) getPaintable(pid)).getElement();
  769. }
  770. public void unregisterPaintable(Paintable p) {
  771. if (p == null) {
  772. ApplicationConnection.getConsole().error(
  773. "WARN: Trying to unregister null paintable");
  774. return;
  775. }
  776. String id = getPid(p);
  777. idToPaintableDetail.remove(id);
  778. if (p instanceof HasWidgets) {
  779. unregisterChildPaintables((HasWidgets) p);
  780. }
  781. }
  782. public void unregisterChildPaintables(HasWidgets container) {
  783. final Iterator<Widget> it = container.iterator();
  784. while (it.hasNext()) {
  785. final Widget w = it.next();
  786. if (w instanceof Paintable) {
  787. unregisterPaintable((Paintable) w);
  788. } else if (w instanceof HasWidgets) {
  789. unregisterChildPaintables((HasWidgets) w);
  790. }
  791. }
  792. }
  793. /**
  794. * Returns Paintable element by its id
  795. *
  796. * @param id
  797. * Paintable ID
  798. */
  799. public Paintable getPaintable(String id) {
  800. ComponentDetail componentDetail = idToPaintableDetail.get(id);
  801. if (componentDetail == null) {
  802. return null;
  803. } else {
  804. return componentDetail.getComponent();
  805. }
  806. }
  807. private void addVariableToQueue(String paintableId, String variableName,
  808. String encodedValue, boolean immediate, char type) {
  809. final String id = paintableId + VAR_FIELD_SEPARATOR + variableName
  810. + VAR_FIELD_SEPARATOR + type;
  811. for (int i = 1; i < pendingVariables.size(); i += 2) {
  812. if ((pendingVariables.get(i)).equals(id)) {
  813. pendingVariables.remove(i - 1);
  814. pendingVariables.remove(i - 1);
  815. break;
  816. }
  817. }
  818. pendingVariables.add(encodedValue);
  819. pendingVariables.add(id);
  820. if (immediate) {
  821. sendPendingVariableChanges();
  822. }
  823. }
  824. /**
  825. * This method sends currently queued variable changes to server. It is
  826. * called when immediate variable update must happen.
  827. *
  828. * To ensure correct order for variable changes (due servers multithreading
  829. * or network), we always wait for active request to be handler before
  830. * sending a new one. If there is an active request, we will put varible
  831. * "burst" to queue that will be purged after current request is handled.
  832. *
  833. */
  834. @SuppressWarnings("unchecked")
  835. public void sendPendingVariableChanges() {
  836. if (applicationRunning) {
  837. if (hasActiveRequest()) {
  838. // skip empty queues if there are pending bursts to be sent
  839. if (pendingVariables.size() > 0
  840. || pendingVariableBursts.size() == 0) {
  841. ArrayList<String> burst = (ArrayList<String>) pendingVariables
  842. .clone();
  843. pendingVariableBursts.add(burst);
  844. pendingVariables.clear();
  845. }
  846. } else {
  847. buildAndSendVariableBurst(pendingVariables, false);
  848. }
  849. }
  850. }
  851. /**
  852. * Build the variable burst and send it to server.
  853. *
  854. * When sync is forced, we also force sending of all pending variable-bursts
  855. * at the same time. This is ok as we can assume that DOM will never be
  856. * updated after this.
  857. *
  858. * @param pendingVariables
  859. * Vector of variable changes to send
  860. * @param forceSync
  861. * Should we use synchronous request?
  862. */
  863. private void buildAndSendVariableBurst(ArrayList<String> pendingVariables,
  864. boolean forceSync) {
  865. final StringBuffer req = new StringBuffer();
  866. while (!pendingVariables.isEmpty()) {
  867. for (int i = 0; i < pendingVariables.size(); i++) {
  868. if (i > 0) {
  869. if (i % 2 == 0) {
  870. req.append(VAR_RECORD_SEPARATOR);
  871. } else {
  872. req.append(VAR_FIELD_SEPARATOR);
  873. }
  874. }
  875. req.append(pendingVariables.get(i));
  876. }
  877. pendingVariables.clear();
  878. // Append all the busts to this synchronous request
  879. if (forceSync && !pendingVariableBursts.isEmpty()) {
  880. pendingVariables = pendingVariableBursts.get(0);
  881. pendingVariableBursts.remove(0);
  882. req.append(VAR_BURST_SEPARATOR);
  883. }
  884. }
  885. makeUidlRequest(req.toString(), false, forceSync, false);
  886. }
  887. public void updateVariable(String paintableId, String variableName,
  888. Paintable newValue, boolean immediate) {
  889. String pid = (newValue != null) ? getPid(newValue) : null;
  890. addVariableToQueue(paintableId, variableName, pid, immediate, 'p');
  891. }
  892. public void updateVariable(String paintableId, String variableName,
  893. String newValue, boolean immediate) {
  894. addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
  895. }
  896. public void updateVariable(String paintableId, String variableName,
  897. int newValue, boolean immediate) {
  898. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  899. 'i');
  900. }
  901. public void updateVariable(String paintableId, String variableName,
  902. long newValue, boolean immediate) {
  903. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  904. 'l');
  905. }
  906. public void updateVariable(String paintableId, String variableName,
  907. float newValue, boolean immediate) {
  908. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  909. 'f');
  910. }
  911. public void updateVariable(String paintableId, String variableName,
  912. double newValue, boolean immediate) {
  913. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  914. 'd');
  915. }
  916. public void updateVariable(String paintableId, String variableName,
  917. boolean newValue, boolean immediate) {
  918. addVariableToQueue(paintableId, variableName, newValue ? "true"
  919. : "false", immediate, 'b');
  920. }
  921. public void updateVariable(String paintableId, String variableName,
  922. Object[] values, boolean immediate) {
  923. final StringBuffer buf = new StringBuffer();
  924. for (int i = 0; i < values.length; i++) {
  925. if (i > 0) {
  926. buf.append(VAR_ARRAYITEM_SEPARATOR);
  927. }
  928. buf.append(values[i].toString());
  929. }
  930. addVariableToQueue(paintableId, variableName, buf.toString(),
  931. immediate, 'a');
  932. }
  933. /**
  934. * Update generic component features.
  935. *
  936. * <h2>Selecting correct implementation</h2>
  937. *
  938. * <p>
  939. * The implementation of a component depends on many properties, including
  940. * styles, component features, etc. Sometimes the user changes those
  941. * properties after the component has been created. Calling this method in
  942. * the beginning of your updateFromUIDL -method automatically replaces your
  943. * component with more appropriate if the requested implementation changes.
  944. * </p>
  945. *
  946. * <h2>Caption, icon, error messages and description</h2>
  947. *
  948. * <p>
  949. * Component can delegate management of caption, icon, error messages and
  950. * description to parent layout. This is optional an should be decided by
  951. * component author
  952. * </p>
  953. *
  954. * <h2>Component visibility and disabling</h2>
  955. *
  956. * This method will manage component visibility automatically and if
  957. * component is an instanceof FocusWidget, also handle component disabling
  958. * when needed.
  959. *
  960. * @param component
  961. * Widget to be updated, expected to implement an instance of
  962. * Paintable
  963. * @param uidl
  964. * UIDL to be painted
  965. * @param manageCaption
  966. * True if you want to delegate caption, icon, description and
  967. * error message management to parent.
  968. *
  969. * @return Returns true iff no further painting is needed by caller
  970. */
  971. public boolean updateComponent(Widget component, UIDL uidl,
  972. boolean manageCaption) {
  973. String pid = getPid(component.getElement());
  974. if (pid == null) {
  975. getConsole().error(
  976. "Trying to update an unregistered component: "
  977. + Util.getSimpleName(component));
  978. return true;
  979. }
  980. ComponentDetail componentDetail = idToPaintableDetail.get(pid);
  981. if (componentDetail == null) {
  982. getConsole().error(
  983. "ComponentDetail not found for "
  984. + Util.getSimpleName(component) + " with PID "
  985. + pid + ". This should not happen.");
  986. return true;
  987. }
  988. // If the server request that a cached instance should be used, do
  989. // nothing
  990. if (uidl.getBooleanAttribute("cached")) {
  991. return true;
  992. }
  993. // Visibility
  994. boolean visible = !uidl.getBooleanAttribute("invisible");
  995. boolean wasVisible = component.isVisible();
  996. component.setVisible(visible);
  997. if (wasVisible != visible) {
  998. // Changed invisibile <-> visible
  999. if (wasVisible && manageCaption) {
  1000. // Must hide caption when component is hidden
  1001. final Container parent = Util.getLayout(component);
  1002. if (parent != null) {
  1003. parent.updateCaption((Paintable) component, uidl);
  1004. }
  1005. }
  1006. }
  1007. if (!visible) {
  1008. // component is invisible, delete old size to notify parent, if
  1009. // later make visible
  1010. componentDetail.setOffsetSize(null);
  1011. return true;
  1012. }
  1013. // Switch to correct implementation if needed
  1014. if (!widgetSet.isCorrectImplementation(component, uidl, configuration)) {
  1015. final Container parent = Util.getLayout(component);
  1016. if (parent != null) {
  1017. final Widget w = (Widget) widgetSet.createWidget(uidl,
  1018. configuration);
  1019. parent.replaceChildComponent(component, w);
  1020. unregisterPaintable((Paintable) component);
  1021. registerPaintable(uidl.getId(), (Paintable) w);
  1022. ((Paintable) w).updateFromUIDL(uidl, this);
  1023. return true;
  1024. }
  1025. }
  1026. boolean enabled = !uidl.getBooleanAttribute("disabled");
  1027. if (component instanceof FocusWidget) {
  1028. FocusWidget fw = (FocusWidget) component;
  1029. if (uidl.hasAttribute("tabindex")) {
  1030. fw.setTabIndex(uidl.getIntAttribute("tabindex"));
  1031. }
  1032. // Disabled state may affect tabindex
  1033. fw.setEnabled(enabled);
  1034. }
  1035. StringBuffer styleBuf = new StringBuffer();
  1036. final String primaryName = component.getStylePrimaryName();
  1037. styleBuf.append(primaryName);
  1038. // first disabling and read-only status
  1039. if (!enabled) {
  1040. styleBuf.append(" ");
  1041. styleBuf.append("v-disabled");
  1042. }
  1043. if (uidl.getBooleanAttribute("readonly")) {
  1044. styleBuf.append(" ");
  1045. styleBuf.append("v-readonly");
  1046. }
  1047. // add additional styles as css classes, prefixed with component default
  1048. // stylename
  1049. if (uidl.hasAttribute("style")) {
  1050. final String[] styles = uidl.getStringAttribute("style").split(" ");
  1051. for (int i = 0; i < styles.length; i++) {
  1052. styleBuf.append(" ");
  1053. styleBuf.append(primaryName);
  1054. styleBuf.append("-");
  1055. styleBuf.append(styles[i]);
  1056. styleBuf.append(" ");
  1057. styleBuf.append(styles[i]);
  1058. }
  1059. }
  1060. // add modified classname to Fields
  1061. if (uidl.hasAttribute("modified") && component instanceof Field) {
  1062. styleBuf.append(" ");
  1063. styleBuf.append(MODIFIED_CLASSNAME);
  1064. }
  1065. TooltipInfo tooltipInfo = componentDetail.getTooltipInfo(null);
  1066. // Update tooltip
  1067. if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION)) {
  1068. tooltipInfo
  1069. .setTitle(uidl.getStringAttribute(ATTRIBUTE_DESCRIPTION));
  1070. } else {
  1071. tooltipInfo.setTitle(null);
  1072. }
  1073. // add error classname to components w/ error
  1074. if (uidl.hasAttribute(ATTRIBUTE_ERROR)) {
  1075. tooltipInfo.setErrorUidl(uidl.getErrors());
  1076. styleBuf.append(" ");
  1077. styleBuf.append(primaryName);
  1078. styleBuf.append(ERROR_CLASSNAME_EXT);
  1079. } else {
  1080. tooltipInfo.setErrorUidl(null);
  1081. }
  1082. // add required style to required components
  1083. if (uidl.hasAttribute("required")) {
  1084. styleBuf.append(" ");
  1085. styleBuf.append(primaryName);
  1086. styleBuf.append(REQUIRED_CLASSNAME_EXT);
  1087. }
  1088. // Styles + disabled & readonly
  1089. component.setStyleName(styleBuf.toString());
  1090. // Set captions
  1091. if (manageCaption) {
  1092. final Container parent = Util.getLayout(component);
  1093. if (parent != null) {
  1094. parent.updateCaption((Paintable) component, uidl);
  1095. }
  1096. }
  1097. if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) {
  1098. DOM.setElementProperty(component.getElement(), "id", uidl.getId()
  1099. .substring(5));
  1100. }
  1101. /*
  1102. * updateComponentSize need to be after caption update so caption can be
  1103. * taken into account
  1104. */
  1105. updateComponentSize(componentDetail, uidl);
  1106. return false;
  1107. }
  1108. private void updateComponentSize(ComponentDetail cd, UIDL uidl) {
  1109. String w = uidl.hasAttribute("width") ? uidl
  1110. .getStringAttribute("width") : "";
  1111. String h = uidl.hasAttribute("height") ? uidl
  1112. .getStringAttribute("height") : "";
  1113. float relativeWidth = Util.parseRelativeSize(w);
  1114. float relativeHeight = Util.parseRelativeSize(h);
  1115. // First update maps so they are correct in the setHeight/setWidth calls
  1116. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1117. // One or both is relative
  1118. FloatSize relativeSize = new FloatSize(relativeWidth,
  1119. relativeHeight);
  1120. if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) {
  1121. // The component has changed from absolute size to relative size
  1122. relativeSizeChanges.add(cd.getComponent());
  1123. }
  1124. cd.setRelativeSize(relativeSize);
  1125. } else if (relativeHeight < 0.0 && relativeWidth < 0.0) {
  1126. if (cd.getRelativeSize() != null) {
  1127. // The component has changed from relative size to absolute size
  1128. relativeSizeChanges.add(cd.getComponent());
  1129. }
  1130. cd.setRelativeSize(null);
  1131. }
  1132. Widget component = (Widget) cd.getComponent();
  1133. // Set absolute sizes
  1134. if (relativeHeight < 0.0) {
  1135. component.setHeight(h);
  1136. }
  1137. if (relativeWidth < 0.0) {
  1138. component.setWidth(w);
  1139. }
  1140. // Set relative sizes
  1141. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1142. // One or both is relative
  1143. handleComponentRelativeSize(cd);
  1144. }
  1145. }
  1146. /**
  1147. * Traverses recursively child widgets until ContainerResizedListener child
  1148. * widget is found. They will delegate it further if needed.
  1149. *
  1150. * @param container
  1151. */
  1152. private boolean runningLayout = false;
  1153. public void runDescendentsLayout(HasWidgets container) {
  1154. if (runningLayout) {
  1155. // getConsole().log(
  1156. // "Already running descendents layout. Not running again for "
  1157. // + Util.getSimpleName(container));
  1158. return;
  1159. }
  1160. runningLayout = true;
  1161. internalRunDescendentsLayout(container);
  1162. runningLayout = false;
  1163. }
  1164. /**
  1165. * This will cause re-layouting of all components. Mainly used for
  1166. * development. Published to JavaScript.
  1167. */
  1168. public void forceLayout() {
  1169. Set<Paintable> set = new HashSet<Paintable>();
  1170. for (ComponentDetail cd : idToPaintableDetail.values()) {
  1171. set.add(cd.getComponent());
  1172. }
  1173. Util.componentSizeUpdated(set);
  1174. }
  1175. private void internalRunDescendentsLayout(HasWidgets container) {
  1176. // getConsole().log(
  1177. // "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
  1178. final Iterator<Widget> childWidgets = container.iterator();
  1179. while (childWidgets.hasNext()) {
  1180. final Widget child = childWidgets.next();
  1181. if (child instanceof Paintable) {
  1182. if (handleComponentRelativeSize(child)) {
  1183. /*
  1184. * Only need to propagate event if "child" has a relative
  1185. * size
  1186. */
  1187. if (child instanceof ContainerResizedListener) {
  1188. ((ContainerResizedListener) child).iLayout();
  1189. }
  1190. if (child instanceof HasWidgets) {
  1191. final HasWidgets childContainer = (HasWidgets) child;
  1192. internalRunDescendentsLayout(childContainer);
  1193. }
  1194. }
  1195. } else if (child instanceof HasWidgets) {
  1196. // propagate over non Paintable HasWidgets
  1197. internalRunDescendentsLayout((HasWidgets) child);
  1198. }
  1199. }
  1200. }
  1201. /**
  1202. * Converts relative sizes into pixel sizes.
  1203. *
  1204. * @param child
  1205. * @return true if the child has a relative size
  1206. */
  1207. private boolean handleComponentRelativeSize(ComponentDetail cd) {
  1208. if (cd == null) {
  1209. return false;
  1210. }
  1211. boolean debugSizes = false;
  1212. FloatSize relativeSize = cd.getRelativeSize();
  1213. if (relativeSize == null) {
  1214. return false;
  1215. }
  1216. Widget widget = (Widget) cd.getComponent();
  1217. boolean horizontalScrollBar = false;
  1218. boolean verticalScrollBar = false;
  1219. Container parent = Util.getLayout(widget);
  1220. RenderSpace renderSpace;
  1221. // Parent-less components (like sub-windows) are relative to browser
  1222. // window.
  1223. if (parent == null) {
  1224. renderSpace = new RenderSpace(Window.getClientWidth(), Window
  1225. .getClientHeight());
  1226. } else {
  1227. renderSpace = parent.getAllocatedSpace(widget);
  1228. }
  1229. if (relativeSize.getHeight() >= 0) {
  1230. if (renderSpace != null) {
  1231. if (renderSpace.getScrollbarSize() > 0) {
  1232. if (relativeSize.getWidth() > 100) {
  1233. horizontalScrollBar = true;
  1234. } else if (relativeSize.getWidth() < 0
  1235. && renderSpace.getWidth() > 0) {
  1236. int offsetWidth = widget.getOffsetWidth();
  1237. int width = renderSpace.getWidth();
  1238. if (offsetWidth > width) {
  1239. horizontalScrollBar = true;
  1240. }
  1241. }
  1242. }
  1243. int height = renderSpace.getHeight();
  1244. if (horizontalScrollBar) {
  1245. height -= renderSpace.getScrollbarSize();
  1246. }
  1247. if (validatingLayouts && height <= 0) {
  1248. zeroHeightComponents.add(cd.getComponent());
  1249. }
  1250. height = (int) (height * relativeSize.getHeight() / 100.0);
  1251. if (height < 0) {
  1252. height = 0;
  1253. }
  1254. if (debugSizes) {
  1255. getConsole()
  1256. .log(
  1257. "Widget "
  1258. + Util.getSimpleName(widget)
  1259. + "/"
  1260. + getPid(widget.getElement())
  1261. + " relative height "
  1262. + relativeSize.getHeight()
  1263. + "% of "
  1264. + renderSpace.getHeight()
  1265. + "px (reported by "
  1266. + Util.getSimpleName(parent)
  1267. + "/"
  1268. + (parent == null ? "?" : parent
  1269. .hashCode()) + ") : "
  1270. + height + "px");
  1271. }
  1272. widget.setHeight(height + "px");
  1273. } else {
  1274. widget.setHeight(relativeSize.getHeight() + "%");
  1275. ApplicationConnection.getConsole().error(
  1276. Util.getLayout(widget).getClass().getName()
  1277. + " did not produce allocatedSpace for "
  1278. + widget.getClass().getName());
  1279. }
  1280. }
  1281. if (relativeSize.getWidth() >= 0) {
  1282. if (renderSpace != null) {
  1283. int width = renderSpace.getWidth();
  1284. if (renderSpace.getScrollbarSize() > 0) {
  1285. if (relativeSize.getHeight() > 100) {
  1286. verticalScrollBar = true;
  1287. } else if (relativeSize.getHeight() < 0
  1288. && renderSpace.getHeight() > 0
  1289. && widget.getOffsetHeight() > renderSpace
  1290. .getHeight()) {
  1291. verticalScrollBar = true;
  1292. }
  1293. }
  1294. if (verticalScrollBar) {
  1295. width -= renderSpace.getScrollbarSize();
  1296. }
  1297. if (validatingLayouts && width <= 0) {
  1298. zeroWidthComponents.add(cd.getComponent());
  1299. }
  1300. width = (int) (width * relativeSize.getWidth() / 100.0);
  1301. if (width < 0) {
  1302. width = 0;
  1303. }
  1304. if (debugSizes) {
  1305. getConsole().log(
  1306. "Widget " + Util.getSimpleName(widget) + "/"
  1307. + getPid(widget.getElement())
  1308. + " relative width "
  1309. + relativeSize.getWidth() + "% of "
  1310. + renderSpace.getWidth()
  1311. + "px (reported by "
  1312. + Util.getSimpleName(parent) + "/"
  1313. + (parent == null ? "?" : getPid(parent))
  1314. + ") : " + width + "px");
  1315. }
  1316. widget.setWidth(width + "px");
  1317. } else {
  1318. widget.setWidth(relativeSize.getWidth() + "%");
  1319. ApplicationConnection.getConsole().error(
  1320. Util.getLayout(widget).getClass().getName()
  1321. + " did not produce allocatedSpace for "
  1322. + widget.getClass().getName());
  1323. }
  1324. }
  1325. return true;
  1326. }
  1327. /**
  1328. * Converts relative sizes into pixel sizes.
  1329. *
  1330. * @param child
  1331. * @return true if the child has a relative size
  1332. */
  1333. public boolean handleComponentRelativeSize(Widget child) {
  1334. return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child
  1335. .getElement())));
  1336. }
  1337. public FloatSize getRelativeSize(Widget widget) {
  1338. return idToPaintableDetail.get(getPid(widget.getElement()))
  1339. .getRelativeSize();
  1340. }
  1341. /**
  1342. * Get either existing or new Paintable for given UIDL.
  1343. *
  1344. * If corresponding Paintable has been previously painted, return it.
  1345. * Otherwise create and register a new Paintable from UIDL. Caller must
  1346. * update the returned Paintable from UIDL after it has been connected to
  1347. * parent.
  1348. *
  1349. * @param uidl
  1350. * UIDL to create Paintable from.
  1351. * @return Either existing or new Paintable corresponding to UIDL.
  1352. */
  1353. public Paintable getPaintable(UIDL uidl) {
  1354. final String id = uidl.getId();
  1355. Paintable w = getPaintable(id);
  1356. if (w != null) {
  1357. return w;
  1358. } else {
  1359. w = widgetSet.createWidget(uidl, configuration);
  1360. registerPaintable(id, w);
  1361. return w;
  1362. }
  1363. }
  1364. /**
  1365. * Returns a Paintable element by its root element
  1366. *
  1367. * @param element
  1368. * Root element of the paintable
  1369. */
  1370. public Paintable getPaintable(Element element) {
  1371. return getPaintable(getPid(element));
  1372. }
  1373. public String getResource(String name) {
  1374. return resourcesMap.get(name);
  1375. }
  1376. /**
  1377. * Singleton method to get instance of app's context menu.
  1378. *
  1379. * @return VContextMenu object
  1380. */
  1381. public VContextMenu getContextMenu() {
  1382. if (contextMenu == null) {
  1383. contextMenu = new VContextMenu();
  1384. DOM.setElementProperty(contextMenu.getElement(), "id",
  1385. "PID_VAADIN_CM");
  1386. }
  1387. return contextMenu;
  1388. }
  1389. /**
  1390. * Translates custom protocols in UIDL URI's to be recognizable by browser.
  1391. * All uri's from UIDL should be routed via this method before giving them
  1392. * to browser due URI's in UIDL may contain custom protocols like theme://.
  1393. *
  1394. * @param uidlUri
  1395. * Vaadin URI from uidl
  1396. * @return translated URI ready for browser
  1397. */
  1398. public String translateVaadinUri(String uidlUri) {
  1399. if (uidlUri == null) {
  1400. return null;
  1401. }
  1402. if (uidlUri.startsWith("theme://")) {
  1403. final String themeUri = configuration.getThemeUri();
  1404. if (themeUri == null) {
  1405. console
  1406. .error("Theme not set: ThemeResource will not be found. ("
  1407. + uidlUri + ")");
  1408. }
  1409. uidlUri = themeUri + uidlUri.substring(7);
  1410. }
  1411. return uidlUri;
  1412. }
  1413. public String getThemeUri() {
  1414. return configuration.getThemeUri();
  1415. }
  1416. /**
  1417. * Listens for Notification hide event, and redirects. Used for system
  1418. * messages, such as session expired.
  1419. *
  1420. */
  1421. private class NotificationRedirect implements VNotification.EventListener {
  1422. String url;
  1423. NotificationRedirect(String url) {
  1424. this.url = url;
  1425. }
  1426. public void notificationHidden(HideEvent event) {
  1427. redirect(url);
  1428. }
  1429. }
  1430. /* Extended title handling */
  1431. /**
  1432. * Data showed in tooltips are stored centrilized as it may be needed in
  1433. * varios place: caption, layouts, and in owner components themselves.
  1434. *
  1435. * Updating TooltipInfo is done in updateComponent method.
  1436. *
  1437. */
  1438. public TooltipInfo getTooltipTitleInfo(Paintable titleOwner, Object key) {
  1439. if (null == titleOwner) {
  1440. return null;
  1441. }
  1442. ComponentDetail cd = idToPaintableDetail.get(getPid(titleOwner));
  1443. if (null != cd) {
  1444. return cd.getTooltipInfo(key);
  1445. } else {
  1446. return null;
  1447. }
  1448. }
  1449. private final VTooltip tooltip = new VTooltip(this);
  1450. /**
  1451. * Component may want to delegate Tooltip handling to client. Layouts add
  1452. * Tooltip (description, errors) to caption, but some components may want
  1453. * them to appear one other elements too.
  1454. *
  1455. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1456. *
  1457. * @param event
  1458. * @param owner
  1459. */
  1460. public void handleTooltipEvent(Event event, Paintable owner) {
  1461. tooltip.handleTooltipEvent(event, owner, null);
  1462. }
  1463. /**
  1464. * Component may want to delegate Tooltip handling to client. Layouts add
  1465. * Tooltip (description, errors) to caption, but some components may want
  1466. * them to appear one other elements too.
  1467. *
  1468. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1469. *
  1470. * @param event
  1471. * @param owner
  1472. * @param key
  1473. * the key for tooltip if this is "additional" tooltip, null for
  1474. * components "main tooltip"
  1475. */
  1476. public void handleTooltipEvent(Event event, Paintable owner, Object key) {
  1477. tooltip.handleTooltipEvent(event, owner, key);
  1478. }
  1479. /**
  1480. * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
  1481. *
  1482. * @param el
  1483. * the IMG element to fix
  1484. */
  1485. public void addPngFix(Element el) {
  1486. BrowserInfo b = BrowserInfo.get();
  1487. if (b.isIE6()) {
  1488. Util.addPngFix(el, getThemeUri() + "/../runo/common/img/blank.gif");
  1489. }
  1490. }
  1491. /*
  1492. * Helper to run layout functions triggered by child components with a
  1493. * decent interval.
  1494. */
  1495. private final Timer layoutTimer = new Timer() {
  1496. private boolean isPending = false;
  1497. @Override
  1498. public void schedule(int delayMillis) {
  1499. if (!isPending) {
  1500. super.schedule(delayMillis);
  1501. isPending = true;
  1502. }
  1503. }
  1504. @Override
  1505. public void run() {
  1506. getConsole().log(
  1507. "Running re-layout of " + view.getClass().getName());
  1508. runDescendentsLayout(view);
  1509. isPending = false;
  1510. }
  1511. };
  1512. /**
  1513. * Components can call this function to run all layout functions. This is
  1514. * usually done, when component knows that its size has changed.
  1515. */
  1516. public void requestLayoutPhase() {
  1517. layoutTimer.schedule(500);
  1518. }
  1519. private String windowName = null;
  1520. /**
  1521. * Reset the name of the current browser-window. This should reflect the
  1522. * window-name used in the server, but might be different from the
  1523. * window-object target-name on client.
  1524. *
  1525. * @param stringAttribute
  1526. * New name for the window.
  1527. */
  1528. public void setWindowName(String newName) {
  1529. windowName = newName;
  1530. }
  1531. public void captionSizeUpdated(Paintable component) {
  1532. componentCaptionSizeChanges.add(component);
  1533. }
  1534. public void analyzeLayouts() {
  1535. makeUidlRequest("", true, false, true);
  1536. }
  1537. public VView getView() {
  1538. return view;
  1539. }
  1540. /**
  1541. * If component has several tooltips in addition to the one provided by
  1542. * {@link com.vaadin.ui.AbstractComponent}, component can register them with
  1543. * this method.
  1544. * <p>
  1545. * Component must also pipe events to
  1546. * {@link #handleTooltipEvent(Event, Paintable, Object)} method.
  1547. * <p>
  1548. * This method can also be used to deregister tooltips by using null as
  1549. * tooltip
  1550. *
  1551. * @param paintable
  1552. * Paintable "owning" this tooltip
  1553. * @param key
  1554. * key assosiated with given tooltip. Can be any object. For
  1555. * example a related dom element. Same key must be given for
  1556. * {@link #handleTooltipEvent(Event, Paintable, Object)} method.
  1557. *
  1558. * @param tooltip
  1559. * the TooltipInfo object containing details shown in tooltip,
  1560. * null if deregistering tooltip
  1561. */
  1562. public void registerTooltip(Paintable paintable, Object key,
  1563. TooltipInfo tooltip) {
  1564. ComponentDetail componentDetail = idToPaintableDetail
  1565. .get(getPid(paintable));
  1566. componentDetail.putAdditionalTooltip(key, tooltip);
  1567. }
  1568. public ApplicationConfiguration getConfiguration() {
  1569. return configuration;
  1570. }
  1571. }