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 64KB

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