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

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