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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319
  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.Map;
  11. import java.util.Set;
  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.ui.FocusWidget;
  29. import com.google.gwt.user.client.ui.HasWidgets;
  30. import com.google.gwt.user.client.ui.Widget;
  31. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  32. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  33. import com.vaadin.terminal.gwt.client.ui.Field;
  34. import com.vaadin.terminal.gwt.client.ui.VContextMenu;
  35. import com.vaadin.terminal.gwt.client.ui.VNotification;
  36. import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent;
  37. import com.vaadin.terminal.gwt.client.ui.VView;
  38. import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
  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. // This indicates the whole page is generated by us (not embedded)
  58. public static final String GENERATED_BODY_CLASSNAME = "v-generated-body";
  59. private static final String MODIFIED_CLASSNAME = "v-modified";
  60. private static final String REQUIRED_CLASSNAME_EXT = "-required";
  61. private static final String ERROR_CLASSNAME_EXT = "-error";
  62. public static final String VAR_RECORD_SEPARATOR = "\u001e";
  63. public static final String VAR_FIELD_SEPARATOR = "\u001f";
  64. public static final String VAR_BURST_SEPARATOR = "\u001d";
  65. public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
  66. public static final String UIDL_SECURITY_TOKEN_ID = "Vaadin-Security-Key";
  67. /**
  68. * @deprecated use UIDL_SECURITY_TOKEN_ID instead
  69. */
  70. @Deprecated
  71. public static final String UIDL_SECURITY_HEADER = UIDL_SECURITY_TOKEN_ID;
  72. public static final String PARAM_UNLOADBURST = "onunloadburst";
  73. public static final String ATTRIBUTE_DESCRIPTION = "description";
  74. public static final String ATTRIBUTE_ERROR = "error";
  75. // will hold the UIDL security key (for XSS protection) once received
  76. private String uidl_security_key = "init";
  77. private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
  78. private static Console console;
  79. private final ArrayList<String> pendingVariables = new ArrayList<String>();
  80. private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap
  81. .create();
  82. private final WidgetSet widgetSet;
  83. private VContextMenu contextMenu = null;
  84. private Timer loadTimer;
  85. private Timer loadTimer2;
  86. private Timer loadTimer3;
  87. private Element loadElement;
  88. private final VView view;
  89. private boolean applicationRunning = false;
  90. private int activeRequests = 0;
  91. /** Parameters for this application connection loaded from the web-page */
  92. private final ApplicationConfiguration configuration;
  93. /** List of pending variable change bursts that must be submitted in order */
  94. private final ArrayList<ArrayList<String>> pendingVariableBursts = new ArrayList<ArrayList<String>>();
  95. /** Timer for automatic refirect to SessionExpiredURL */
  96. private Timer redirectTimer;
  97. /** redirectTimer scheduling interval in seconds */
  98. private int sessionExpirationInterval;
  99. private ArrayList<Paintable> relativeSizeChanges = new ArrayList<Paintable>();;
  100. private ArrayList<Paintable> componentCaptionSizeChanges = new ArrayList<Paintable>();;
  101. private Date requestStartTime;
  102. private boolean validatingLayouts = false;
  103. private Set<Paintable> zeroWidthComponents = null;
  104. private Set<Paintable> zeroHeightComponents = null;
  105. /**
  106. * Keeps track of if there are (potentially) {@link DeferredCommand}s that
  107. * are being executed. 0 == no DeferredCommands currently in progress, > 0
  108. * otherwise.
  109. */
  110. private int deferredCommandTrackers = 0;
  111. public ApplicationConnection(WidgetSet widgetSet,
  112. ApplicationConfiguration cnf) {
  113. this.widgetSet = widgetSet;
  114. configuration = cnf;
  115. windowName = configuration.getInitialWindowName();
  116. if (isDebugMode()) {
  117. console = new VDebugConsole(this, cnf, !isQuietDebugMode());
  118. } else {
  119. console = new NullConsole();
  120. }
  121. ComponentLocator componentLocator = new ComponentLocator(this);
  122. String appRootPanelName = cnf.getRootPanelId();
  123. // remove the end (window name) of autogenerated rootpanel id
  124. appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", "");
  125. initializeTestbenchHooks(componentLocator, appRootPanelName);
  126. initializeClientHooks();
  127. view = new VView();
  128. view.init(cnf.getRootPanelId());
  129. showLoadingIndicator();
  130. }
  131. /**
  132. * Starts this application. Don't call this method directly - it's called by
  133. * {@link ApplicationConfiguration#startNextApplication()}, which should be
  134. * called once this application has started (first response received) or
  135. * failed to start. This ensures that the applications are started in order,
  136. * to avoid session-id problems.
  137. */
  138. void start() {
  139. makeUidlRequest("", true, false, false);
  140. }
  141. private native void initializeTestbenchHooks(
  142. ComponentLocator componentLocator, String TTAppId)
  143. /*-{
  144. var ap = this;
  145. var client = {};
  146. client.isActive = function() {
  147. return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isExecutingDeferredCommands()();
  148. }
  149. var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
  150. if (vi) {
  151. client.getVersionInfo = function() {
  152. return vi;
  153. }
  154. }
  155. client.getElementByPath = function(id) {
  156. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
  157. }
  158. client.getPathForElement = function(element) {
  159. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
  160. }
  161. if(!$wnd.vaadin.clients) {
  162. $wnd.vaadin.clients = {};
  163. }
  164. $wnd.vaadin.clients[TTAppId] = client;
  165. }-*/;
  166. /**
  167. * Helper for tt initialization
  168. */
  169. @SuppressWarnings("unused")
  170. private JavaScriptObject getVersionInfo() {
  171. return configuration.getVersionInfoJSObject();
  172. }
  173. /**
  174. * Publishes a JavaScript API for mash-up applications.
  175. * <ul>
  176. * <li><code>vaadin.forceSync()</code> sends pending variable changes, in
  177. * effect synchronizing the server and client state. This is done for all
  178. * applications on host page.</li>
  179. * <li><code>vaadin.postRequestHooks</code> is a map of functions which gets
  180. * called after each XHR made by vaadin application. Note, that it is
  181. * attaching js functions responsibility to create the variable like this:
  182. *
  183. * <code><pre>
  184. * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
  185. * postRequestHooks.myHook = function(appId) {
  186. * if(appId == "MyAppOfInterest") {
  187. * // do the staff you need on xhr activity
  188. * }
  189. * }
  190. * </pre></code> First parameter passed to these functions is the identifier
  191. * of Vaadin application that made the request.
  192. * </ul>
  193. *
  194. * TODO make this multi-app aware
  195. */
  196. private native void initializeClientHooks()
  197. /*-{
  198. var app = this;
  199. var oldSync;
  200. if($wnd.vaadin.forceSync) {
  201. oldSync = $wnd.vaadin.forceSync;
  202. }
  203. $wnd.vaadin.forceSync = function() {
  204. if(oldSync) {
  205. oldSync();
  206. }
  207. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
  208. }
  209. var oldForceLayout;
  210. if($wnd.vaadin.forceLayout) {
  211. oldForceLayout = $wnd.vaadin.forceLayout;
  212. }
  213. $wnd.vaadin.forceLayout = function() {
  214. if(oldForceLayout) {
  215. oldForceLayout();
  216. }
  217. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()();
  218. }
  219. }-*/;
  220. /**
  221. * Runs possibly registered client side post request hooks. This is expected
  222. * to be run after each uidl request made by Vaadin application.
  223. *
  224. * @param appId
  225. */
  226. private static native void runPostRequestHooks(String appId)
  227. /*-{
  228. if($wnd.vaadin.postRequestHooks) {
  229. for(var hook in $wnd.vaadin.postRequestHooks) {
  230. if(typeof($wnd.vaadin.postRequestHooks[hook]) == "function") {
  231. try {
  232. $wnd.vaadin.postRequestHooks[hook](appId);
  233. } catch(e) {}
  234. }
  235. }
  236. }
  237. }-*/;
  238. /**
  239. * Get the active Console for writing debug messages. May return an actual
  240. * logging console, or the NullConsole if debugging is not turned on.
  241. *
  242. * @return the active Console
  243. */
  244. public static Console getConsole() {
  245. return console;
  246. }
  247. /**
  248. * Checks if client side is in debug mode. Practically this is invoked by
  249. * adding ?debug parameter to URI.
  250. *
  251. * @return true if client side is currently been debugged
  252. */
  253. public native static boolean isDebugMode()
  254. /*-{
  255. if($wnd.vaadin.debug) {
  256. var parameters = $wnd.location.search;
  257. var re = /debug[^\/]*$/;
  258. return re.test(parameters);
  259. } else {
  260. return false;
  261. }
  262. }-*/;
  263. private native static boolean isQuietDebugMode()
  264. /*-{
  265. var uri = $wnd.location;
  266. var re = /debug=q[^\/]*$/;
  267. return re.test(uri);
  268. }-*/;
  269. /**
  270. * Gets the application base URI. Using this other than as the download
  271. * action URI can cause problems in Portlet 2.0 deployments.
  272. *
  273. * @return application base URI
  274. */
  275. public String getAppUri() {
  276. return configuration.getApplicationUri();
  277. };
  278. /**
  279. * Indicates whether or not there are currently active UIDL requests. Used
  280. * internally to squence requests properly, seldom needed in Widgets.
  281. *
  282. * @return true if there are active requests
  283. */
  284. public boolean hasActiveRequest() {
  285. return (activeRequests > 0);
  286. }
  287. private void makeUidlRequest(final String requestData,
  288. final boolean repaintAll, final boolean forceSync,
  289. final boolean analyzeLayouts) {
  290. startRequest();
  291. // Security: double cookie submission pattern
  292. final String rd = uidl_security_key + VAR_BURST_SEPARATOR + requestData;
  293. console.log("Making UIDL Request with params: " + rd);
  294. String uri;
  295. if (configuration.usePortletURLs()) {
  296. uri = configuration.getPortletUidlURLBase();
  297. } else {
  298. uri = getAppUri() + "UIDL" + configuration.getPathInfo();
  299. }
  300. if (repaintAll) {
  301. // collect some client side data that will be sent to server on
  302. // initial uidl request
  303. int clientHeight = Window.getClientHeight();
  304. int clientWidth = Window.getClientWidth();
  305. com.google.gwt.dom.client.Element pe = view.getElement()
  306. .getParentElement();
  307. int offsetHeight = pe.getOffsetHeight();
  308. int offsetWidth = pe.getOffsetWidth();
  309. int screenWidth = BrowserInfo.get().getScreenWidth();
  310. int screenHeight = BrowserInfo.get().getScreenHeight();
  311. String token = History.getToken();
  312. // TODO figure out how client and view size could be used better on
  313. // server. screen size can be accessed via Browser object, but other
  314. // values currently only via transaction listener.
  315. if (configuration.usePortletURLs()) {
  316. uri += "&";
  317. } else {
  318. uri += "?";
  319. }
  320. uri += "repaintAll=1&" + "sh=" + screenHeight + "&sw="
  321. + screenWidth + "&cw=" + clientWidth + "&ch="
  322. + clientHeight + "&vw=" + offsetWidth + "&vh="
  323. + offsetHeight + "&fr=" + token;
  324. if (analyzeLayouts) {
  325. uri += "&analyzeLayouts=1";
  326. }
  327. }
  328. if (windowName != null && windowName.length() > 0) {
  329. uri += (repaintAll || configuration.usePortletURLs() ? "&" : "?")
  330. + "windowName=" + windowName;
  331. }
  332. if (!forceSync) {
  333. final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
  334. uri);
  335. // TODO enable timeout
  336. // rb.setTimeoutMillis(timeoutMillis);
  337. rb.setHeader("Content-Type", "text/plain;charset=utf-8");
  338. try {
  339. rb.sendRequest(rd, new RequestCallback() {
  340. public void onError(Request request, Throwable exception) {
  341. showCommunicationError(exception.getMessage());
  342. endRequest();
  343. if (!applicationRunning) {
  344. // start failed, let's try to start the next app
  345. ApplicationConfiguration.startNextApplication();
  346. }
  347. }
  348. public void onResponseReceived(Request request,
  349. Response response) {
  350. console.log("Server visit took "
  351. + String.valueOf((new Date()).getTime()
  352. - requestStartTime.getTime()) + "ms");
  353. int statusCode = response.getStatusCode();
  354. switch (statusCode) {
  355. case 0:
  356. showCommunicationError("Invalid status code 0 (server down?)");
  357. endRequest();
  358. return;
  359. case 401:
  360. /*
  361. * Authorization has failed. Could be that the
  362. * session has timed out and the container is
  363. * redirecting to a login page.
  364. */
  365. showAuthenticationError("");
  366. endRequest();
  367. return;
  368. case 503:
  369. // We'll assume msec instead of the usual seconds
  370. int delay = Integer.parseInt(response
  371. .getHeader("Retry-After"));
  372. console.log("503, retrying in " + delay + "msec");
  373. (new Timer() {
  374. @Override
  375. public void run() {
  376. activeRequests--;
  377. makeUidlRequest(requestData, repaintAll,
  378. forceSync, analyzeLayouts);
  379. }
  380. }).schedule(delay);
  381. return;
  382. }
  383. if ((statusCode / 100) == 4) {
  384. // Handle all 4xx errors the same way as (they are
  385. // all permanent errors)
  386. showCommunicationError("UIDL could not be read from server. Check servlets mappings. Error code: "
  387. + statusCode);
  388. endRequest();
  389. return;
  390. }
  391. if (applicationRunning) {
  392. handleReceivedJSONMessage(response);
  393. } else {
  394. applicationRunning = true;
  395. handleWhenCSSLoaded(response);
  396. ApplicationConfiguration.startNextApplication();
  397. }
  398. }
  399. int cssWaits = 0;
  400. static final int MAX_CSS_WAITS = 20;
  401. private void handleWhenCSSLoaded(final Response response) {
  402. int heightOfLoadElement = DOM.getElementPropertyInt(
  403. loadElement, "offsetHeight");
  404. if (heightOfLoadElement == 0
  405. && cssWaits < MAX_CSS_WAITS) {
  406. (new Timer() {
  407. @Override
  408. public void run() {
  409. handleWhenCSSLoaded(response);
  410. }
  411. }).schedule(50);
  412. console.log("Assuming CSS loading is not complete, "
  413. + "postponing render phase. "
  414. + "(.v-loading-indicator height == 0)");
  415. cssWaits++;
  416. } else {
  417. handleReceivedJSONMessage(response);
  418. if (cssWaits >= MAX_CSS_WAITS) {
  419. console.error("CSS files may have not loaded properly.");
  420. }
  421. }
  422. }
  423. });
  424. } catch (final RequestException e) {
  425. ClientExceptionHandler.displayError(e);
  426. endRequest();
  427. }
  428. } else {
  429. // Synchronized call, discarded response (leaving the page)
  430. SynchronousXHR syncXHR = (SynchronousXHR) SynchronousXHR.create();
  431. syncXHR.synchronousPost(uri + "&" + PARAM_UNLOADBURST + "=1", rd);
  432. /*
  433. * Although we are in theory leaving the page, the page may still
  434. * stay open. End request properly here too. See #3289
  435. */
  436. endRequest();
  437. }
  438. }
  439. /**
  440. * Shows the communication error notification.
  441. *
  442. * @param details
  443. * Optional details for debugging.
  444. */
  445. private void showCommunicationError(String details) {
  446. console.error("Communication error: " + details);
  447. showError(details, configuration.getCommunicationErrorCaption(),
  448. configuration.getCommunicationErrorMessage(),
  449. configuration.getCommunicationErrorUrl());
  450. }
  451. /**
  452. * Shows the authentication error notification.
  453. *
  454. * @param details
  455. * Optional details for debugging.
  456. */
  457. private void showAuthenticationError(String details) {
  458. console.error("Authentication error: " + details);
  459. showError(details, configuration.getAuthorizationErrorCaption(),
  460. configuration.getAuthorizationErrorMessage(),
  461. configuration.getAuthorizationErrorUrl());
  462. }
  463. /**
  464. * Shows the error notification.
  465. *
  466. * @param details
  467. * Optional details for debugging.
  468. */
  469. private void showError(String details, String caption, String message,
  470. String url) {
  471. StringBuilder html = new StringBuilder();
  472. if (caption != null) {
  473. html.append("<h1>");
  474. html.append(caption);
  475. html.append("</h1>");
  476. }
  477. if (message != null) {
  478. html.append("<p>");
  479. html.append(message);
  480. html.append("</p>");
  481. }
  482. if (html.length() > 0) {
  483. // Add error description
  484. html.append("<br/><p><I style=\"font-size:0.7em\">");
  485. html.append(details);
  486. html.append("</I></p>");
  487. VNotification n = new VNotification(1000 * 60 * 45);
  488. n.addEventListener(new NotificationRedirect(url));
  489. n.show(html.toString(), VNotification.CENTERED_TOP,
  490. VNotification.STYLE_SYSTEM);
  491. } else {
  492. redirect(url);
  493. }
  494. }
  495. private void startRequest() {
  496. activeRequests++;
  497. requestStartTime = new Date();
  498. // show initial throbber
  499. if (loadTimer == null) {
  500. loadTimer = new Timer() {
  501. @Override
  502. public void run() {
  503. /*
  504. * IE7 does not properly cancel the event with
  505. * loadTimer.cancel() so we have to check that we really
  506. * should make it visible
  507. */
  508. if (loadTimer != null) {
  509. showLoadingIndicator();
  510. }
  511. }
  512. };
  513. // First one kicks in at 300ms
  514. }
  515. loadTimer.schedule(300);
  516. }
  517. private void endRequest() {
  518. if (applicationRunning) {
  519. checkForPendingVariableBursts();
  520. runPostRequestHooks(configuration.getRootPanelId());
  521. }
  522. activeRequests--;
  523. // deferring to avoid flickering
  524. DeferredCommand.addCommand(new Command() {
  525. public void execute() {
  526. if (activeRequests == 0) {
  527. hideLoadingIndicator();
  528. }
  529. }
  530. });
  531. addDeferredCommandTracker();
  532. }
  533. /**
  534. * Adds a {@link DeferredCommand} tracker. Increments the tracker count when
  535. * called and decrements in a DeferredCommand that is executed after all
  536. * other DeferredCommands have executed.
  537. *
  538. */
  539. private void addDeferredCommandTracker() {
  540. deferredCommandTrackers++;
  541. DeferredCommand.addCommand(new Command() {
  542. public void execute() {
  543. deferredCommandTrackers--;
  544. }
  545. });
  546. }
  547. /**
  548. * This method is called after applying uidl change set to application.
  549. *
  550. * It will clean current and queued variable change sets. And send next
  551. * change set if it exists.
  552. */
  553. private void checkForPendingVariableBursts() {
  554. cleanVariableBurst(pendingVariables);
  555. if (pendingVariableBursts.size() > 0) {
  556. for (Iterator<ArrayList<String>> iterator = pendingVariableBursts
  557. .iterator(); iterator.hasNext();) {
  558. cleanVariableBurst(iterator.next());
  559. }
  560. ArrayList<String> nextBurst = pendingVariableBursts.get(0);
  561. pendingVariableBursts.remove(0);
  562. buildAndSendVariableBurst(nextBurst, false);
  563. }
  564. }
  565. /**
  566. * Cleans given queue of variable changes of such changes that came from
  567. * components that do not exist anymore.
  568. *
  569. * @param variableBurst
  570. */
  571. private void cleanVariableBurst(ArrayList<String> variableBurst) {
  572. for (int i = 1; i < variableBurst.size(); i += 2) {
  573. String id = variableBurst.get(i);
  574. id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR));
  575. if (!idToPaintableDetail.containsKey(id) && !id.startsWith("DD")) {
  576. // variable owner does not exist anymore
  577. variableBurst.remove(i - 1);
  578. variableBurst.remove(i - 1);
  579. i -= 2;
  580. ApplicationConnection.getConsole().log(
  581. "Removed variable from removed component: " + id);
  582. }
  583. }
  584. }
  585. private void showLoadingIndicator() {
  586. // show initial throbber
  587. if (loadElement == null) {
  588. loadElement = DOM.createDiv();
  589. DOM.setStyleAttribute(loadElement, "position", "absolute");
  590. DOM.appendChild(view.getElement(), loadElement);
  591. ApplicationConnection.getConsole().log("inserting load indicator");
  592. }
  593. DOM.setElementProperty(loadElement, "className", "v-loading-indicator");
  594. DOM.setStyleAttribute(loadElement, "display", "block");
  595. // Initialize other timers
  596. loadTimer2 = new Timer() {
  597. @Override
  598. public void run() {
  599. DOM.setElementProperty(loadElement, "className",
  600. "v-loading-indicator-delay");
  601. }
  602. };
  603. // Second one kicks in at 1500ms from request start
  604. loadTimer2.schedule(1200);
  605. loadTimer3 = new Timer() {
  606. @Override
  607. public void run() {
  608. DOM.setElementProperty(loadElement, "className",
  609. "v-loading-indicator-wait");
  610. }
  611. };
  612. // Third one kicks in at 5000ms from request start
  613. loadTimer3.schedule(4700);
  614. }
  615. private void hideLoadingIndicator() {
  616. if (loadTimer != null) {
  617. loadTimer.cancel();
  618. if (loadTimer2 != null) {
  619. loadTimer2.cancel();
  620. loadTimer3.cancel();
  621. }
  622. loadTimer = null;
  623. }
  624. if (loadElement != null) {
  625. DOM.setStyleAttribute(loadElement, "display", "none");
  626. }
  627. }
  628. /**
  629. * Checks if {@link DeferredCommand}s are (potentially) still being executed
  630. * as a result of an update from the server. Returns true if a
  631. * DeferredCommand might still be executing, false otherwise. This will fail
  632. * if a DeferredCommand adds another DeferredCommand.
  633. * <p>
  634. * Called by the native "client.isActive" function.
  635. * </p>
  636. *
  637. * @return
  638. */
  639. @SuppressWarnings("unused")
  640. private boolean isExecutingDeferredCommands() {
  641. return (deferredCommandTrackers > 0);
  642. }
  643. /**
  644. * Determines whether or not the loading indicator is showing.
  645. *
  646. * @return true if the loading indicator is visible
  647. */
  648. public boolean isLoadingIndicatorVisible() {
  649. if (loadElement == null) {
  650. return false;
  651. }
  652. if (loadElement.getStyle().getProperty("display").equals("none")) {
  653. return false;
  654. }
  655. return true;
  656. }
  657. private static native ValueMap parseJSONResponse(String jsonText)
  658. /*-{
  659. try {
  660. return JSON.parse(jsonText);
  661. } catch(ignored) {
  662. return eval('(' + jsonText + ')');
  663. }
  664. }-*/;
  665. private void handleReceivedJSONMessage(Response response) {
  666. final Date start = new Date();
  667. // for(;;);[realjson]
  668. final String jsonText = response.getText().substring(9,
  669. response.getText().length() - 1);
  670. final ValueMap json;
  671. try {
  672. json = parseJSONResponse(jsonText);
  673. } catch (final Exception e) {
  674. endRequest();
  675. showCommunicationError(e.getMessage() + " - Original JSON-text:"
  676. + jsonText);
  677. return;
  678. }
  679. ApplicationConnection.getConsole().log(
  680. "JSON parsing took " + (new Date().getTime() - start.getTime())
  681. + "ms");
  682. // Handle redirect
  683. if (json.containsKey("redirect")) {
  684. String url = json.getValueMap("redirect").getString("url");
  685. console.log("redirecting to " + url);
  686. redirect(url);
  687. return;
  688. }
  689. // Get security key
  690. if (json.containsKey(UIDL_SECURITY_TOKEN_ID)) {
  691. uidl_security_key = json.getString(UIDL_SECURITY_TOKEN_ID);
  692. }
  693. if (json.containsKey("resources")) {
  694. ValueMap resources = json.getValueMap("resources");
  695. JsArrayString keyArray = resources.getKeyArray();
  696. int l = keyArray.length();
  697. for (int i = 0; i < l; i++) {
  698. String key = keyArray.get(i);
  699. resourcesMap.put(key, resources.getAsString(key));
  700. }
  701. }
  702. if (json.containsKey("typeMappings")) {
  703. configuration.addComponentMappings(
  704. json.getValueMap("typeMappings"), widgetSet);
  705. }
  706. Command c = new Command() {
  707. public void execute() {
  708. if (json.containsKey("locales")) {
  709. // Store locale data
  710. JsArray<ValueMap> valueMapArray = json
  711. .getJSValueMapArray("locales");
  712. LocaleService.addLocales(valueMapArray);
  713. }
  714. ValueMap meta = null;
  715. if (json.containsKey("meta")) {
  716. meta = json.getValueMap("meta");
  717. if (meta.containsKey("repaintAll")) {
  718. view.clear();
  719. idToPaintableDetail.clear();
  720. if (meta.containsKey("invalidLayouts")) {
  721. validatingLayouts = true;
  722. zeroWidthComponents = new HashSet<Paintable>();
  723. zeroHeightComponents = new HashSet<Paintable>();
  724. }
  725. }
  726. if (meta.containsKey("timedRedirect")) {
  727. final ValueMap timedRedirect = meta
  728. .getValueMap("timedRedirect");
  729. redirectTimer = new Timer() {
  730. @Override
  731. public void run() {
  732. redirect(timedRedirect.getString("url"));
  733. }
  734. };
  735. sessionExpirationInterval = timedRedirect
  736. .getInt("interval");
  737. }
  738. }
  739. if (redirectTimer != null) {
  740. redirectTimer.schedule(1000 * sessionExpirationInterval);
  741. }
  742. // Process changes
  743. JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
  744. ArrayList<Paintable> updatedWidgets = new ArrayList<Paintable>();
  745. relativeSizeChanges.clear();
  746. componentCaptionSizeChanges.clear();
  747. int length = changes.length();
  748. for (int i = 0; i < length; i++) {
  749. try {
  750. final UIDL change = changes.get(i).cast();
  751. try {
  752. console.dirUIDL(change);
  753. } catch (final Exception e) {
  754. ClientExceptionHandler.displayError(e);
  755. // TODO: dir doesn't work in any browser although it
  756. // should
  757. // work (works in hosted mode)
  758. // it partially did at some part but now broken.
  759. }
  760. final UIDL uidl = change.getChildUIDL(0);
  761. // TODO optimize
  762. final Paintable paintable = getPaintable(uidl.getId());
  763. if (paintable != null) {
  764. paintable.updateFromUIDL(uidl,
  765. ApplicationConnection.this);
  766. // paintable may have changed during render to
  767. // another
  768. // implementation, use the new one for updated
  769. // widgets map
  770. updatedWidgets.add(idToPaintableDetail.get(
  771. uidl.getId()).getComponent());
  772. } else {
  773. if (!uidl.getTag().equals(
  774. configuration.getEncodedWindowTag())) {
  775. ClientExceptionHandler
  776. .displayError("Received update for "
  777. + uidl.getTag()
  778. + ", but there is no such paintable ("
  779. + uidl.getId() + ") rendered.");
  780. } else {
  781. String pid = uidl.getId();
  782. if (!idToPaintableDetail.containsKey(pid)) {
  783. registerPaintable(pid, view);
  784. }
  785. // VView does not call updateComponent so we
  786. // register any event listeners here
  787. ComponentDetail cd = idToPaintableDetail
  788. .get(pid);
  789. cd.registerEventListenersFromUIDL(uidl);
  790. // Finally allow VView to update itself
  791. view.updateFromUIDL(uidl,
  792. ApplicationConnection.this);
  793. }
  794. }
  795. } catch (final Throwable e) {
  796. ClientExceptionHandler.displayError(e);
  797. }
  798. }
  799. if (json.containsKey("dd")) {
  800. // response contains data for drag and drop service
  801. VDragAndDropManager.get().handleServerResponse(
  802. json.getValueMap("dd"));
  803. }
  804. // Check which widgets' size has been updated
  805. Set<Paintable> sizeUpdatedWidgets = new HashSet<Paintable>();
  806. updatedWidgets.addAll(relativeSizeChanges);
  807. sizeUpdatedWidgets.addAll(componentCaptionSizeChanges);
  808. for (Paintable paintable : updatedWidgets) {
  809. ComponentDetail detail = idToPaintableDetail
  810. .get(getPid(paintable));
  811. Widget widget = (Widget) paintable;
  812. Size oldSize = detail.getOffsetSize();
  813. Size newSize = new Size(widget.getOffsetWidth(),
  814. widget.getOffsetHeight());
  815. if (oldSize == null || !oldSize.equals(newSize)) {
  816. sizeUpdatedWidgets.add(paintable);
  817. detail.setOffsetSize(newSize);
  818. }
  819. }
  820. Util.componentSizeUpdated(sizeUpdatedWidgets);
  821. if (meta != null) {
  822. if (meta.containsKey("appError")) {
  823. ValueMap error = meta.getValueMap("appError");
  824. String html = "";
  825. if (error.containsKey("caption")
  826. && error.getString("caption") != null) {
  827. html += "<h1>" + error.getAsString("caption")
  828. + "</h1>";
  829. }
  830. if (error.containsKey("message")
  831. && error.getString("message") != null) {
  832. html += "<p>" + error.getAsString("message")
  833. + "</p>";
  834. }
  835. String url = null;
  836. if (error.containsKey("url")) {
  837. url = error.getString("url");
  838. }
  839. if (html.length() != 0) {
  840. /* 45 min */
  841. VNotification n = new VNotification(1000 * 60 * 45);
  842. n.addEventListener(new NotificationRedirect(url));
  843. n.show(html, VNotification.CENTERED_TOP,
  844. VNotification.STYLE_SYSTEM);
  845. } else {
  846. redirect(url);
  847. }
  848. applicationRunning = false;
  849. }
  850. if (validatingLayouts) {
  851. getConsole().printLayoutProblems(meta,
  852. ApplicationConnection.this,
  853. zeroHeightComponents, zeroWidthComponents);
  854. zeroHeightComponents = null;
  855. zeroWidthComponents = null;
  856. validatingLayouts = false;
  857. }
  858. }
  859. // TODO build profiling for widget impl loading time
  860. final long prosessingTime = (new Date().getTime())
  861. - start.getTime();
  862. console.log(" Processing time was "
  863. + String.valueOf(prosessingTime) + "ms for "
  864. + jsonText.length() + " characters of JSON");
  865. console.log("Referenced paintables: "
  866. + idToPaintableDetail.size());
  867. endRequest();
  868. }
  869. };
  870. ApplicationConfiguration.runWhenWidgetsLoaded(c);
  871. }
  872. /**
  873. * This method assures that all pending variable changes are sent to server.
  874. * Method uses synchronized xmlhttprequest and does not return before the
  875. * changes are sent. No UIDL updates are processed and thus UI is left in
  876. * inconsistent state. This method should be called only when closing
  877. * windows - normally sendPendingVariableChanges() should be used.
  878. */
  879. public void sendPendingVariableChangesSync() {
  880. if (applicationRunning) {
  881. pendingVariableBursts.add(pendingVariables);
  882. ArrayList<String> nextBurst = pendingVariableBursts.get(0);
  883. pendingVariableBursts.remove(0);
  884. buildAndSendVariableBurst(nextBurst, true);
  885. }
  886. }
  887. // Redirect browser, null reloads current page
  888. private static native void redirect(String url)
  889. /*-{
  890. if (url) {
  891. $wnd.location = url;
  892. } else {
  893. $wnd.location.reload(false);
  894. }
  895. }-*/;
  896. public void registerPaintable(String pid, Paintable paintable) {
  897. ComponentDetail componentDetail = new ComponentDetail(this, pid,
  898. paintable);
  899. idToPaintableDetail.put(pid, componentDetail);
  900. setPid(((Widget) paintable).getElement(), pid);
  901. }
  902. private native void setPid(Element el, String pid)
  903. /*-{
  904. el.tkPid = pid;
  905. }-*/;
  906. /**
  907. * Gets the paintableId for a specific paintable (a.k.a Vaadin Widget).
  908. * <p>
  909. * The paintableId is used in the UIDL to identify a specific widget
  910. * instance, effectively linking the widget with it's server side Component.
  911. * </p>
  912. *
  913. * @param paintable
  914. * the paintable who's id is needed
  915. * @return the id for the given paintable
  916. */
  917. public String getPid(Paintable paintable) {
  918. return getPid(((Widget) paintable).getElement());
  919. }
  920. /**
  921. * Gets the paintableId using a DOM element - the element should be the main
  922. * element for a paintable otherwise no id will be found. Use
  923. * {@link #getPid(Paintable)} instead whenever possible.
  924. *
  925. * @see #getPid(Paintable)
  926. * @param el
  927. * element of the paintable whose pid is desired
  928. * @return the pid of the element's paintable, if it's a paintable
  929. */
  930. public native String getPid(Element el)
  931. /*-{
  932. return el.tkPid;
  933. }-*/;
  934. /**
  935. * Gets the main element for the paintable with the given id. The revers of
  936. * {@link #getPid(Element)}.
  937. *
  938. * @param pid
  939. * the pid of the widget whose element is desired
  940. * @return the element for the paintable corresponding to the pid
  941. */
  942. public Element getElementByPid(String pid) {
  943. return ((Widget) getPaintable(pid)).getElement();
  944. }
  945. /**
  946. * Unregisters the given paintable; always use after removing a paintable.
  947. * Does not actually remove the paintable from the DOM.
  948. *
  949. * @param p
  950. * the paintable to remove
  951. */
  952. public void unregisterPaintable(Paintable p) {
  953. if (p == null) {
  954. ApplicationConnection.getConsole().error(
  955. "WARN: Trying to unregister null paintable");
  956. return;
  957. }
  958. String id = getPid(p);
  959. idToPaintableDetail.remove(id);
  960. if (p instanceof HasWidgets) {
  961. unregisterChildPaintables((HasWidgets) p);
  962. }
  963. }
  964. /**
  965. * Unregisters a paintable and all it's child paintables recursively. Use
  966. * when after removing a paintable that contains other paintables. Does not
  967. * unregister the given container itself. Does not actually remove the
  968. * paintable from the DOM.
  969. *
  970. * @see #unregisterPaintable(Paintable)
  971. * @param container
  972. */
  973. public void unregisterChildPaintables(HasWidgets container) {
  974. final Iterator<Widget> it = container.iterator();
  975. while (it.hasNext()) {
  976. final Widget w = it.next();
  977. if (w instanceof Paintable) {
  978. unregisterPaintable((Paintable) w);
  979. } else if (w instanceof HasWidgets) {
  980. unregisterChildPaintables((HasWidgets) w);
  981. }
  982. }
  983. }
  984. /**
  985. * Returns Paintable element by its id
  986. *
  987. * @param id
  988. * Paintable ID
  989. */
  990. public Paintable getPaintable(String id) {
  991. ComponentDetail componentDetail = idToPaintableDetail.get(id);
  992. if (componentDetail == null) {
  993. return null;
  994. } else {
  995. return componentDetail.getComponent();
  996. }
  997. }
  998. private void addVariableToQueue(String paintableId, String variableName,
  999. String encodedValue, boolean immediate, char type) {
  1000. final String id = paintableId + VAR_FIELD_SEPARATOR + variableName
  1001. + VAR_FIELD_SEPARATOR + type;
  1002. for (int i = 1; i < pendingVariables.size(); i += 2) {
  1003. if ((pendingVariables.get(i)).equals(id)) {
  1004. pendingVariables.remove(i - 1);
  1005. pendingVariables.remove(i - 1);
  1006. break;
  1007. }
  1008. }
  1009. pendingVariables.add(encodedValue);
  1010. pendingVariables.add(id);
  1011. if (immediate) {
  1012. sendPendingVariableChanges();
  1013. }
  1014. }
  1015. /**
  1016. * This method sends currently queued variable changes to server. It is
  1017. * called when immediate variable update must happen.
  1018. *
  1019. * To ensure correct order for variable changes (due servers multithreading
  1020. * or network), we always wait for active request to be handler before
  1021. * sending a new one. If there is an active request, we will put varible
  1022. * "burst" to queue that will be purged after current request is handled.
  1023. *
  1024. */
  1025. @SuppressWarnings("unchecked")
  1026. public void sendPendingVariableChanges() {
  1027. if (applicationRunning) {
  1028. if (hasActiveRequest()) {
  1029. // skip empty queues if there are pending bursts to be sent
  1030. if (pendingVariables.size() > 0
  1031. || pendingVariableBursts.size() == 0) {
  1032. ArrayList<String> burst = (ArrayList<String>) pendingVariables
  1033. .clone();
  1034. pendingVariableBursts.add(burst);
  1035. pendingVariables.clear();
  1036. }
  1037. } else {
  1038. buildAndSendVariableBurst(pendingVariables, false);
  1039. }
  1040. }
  1041. }
  1042. /**
  1043. * Build the variable burst and send it to server.
  1044. *
  1045. * When sync is forced, we also force sending of all pending variable-bursts
  1046. * at the same time. This is ok as we can assume that DOM will never be
  1047. * updated after this.
  1048. *
  1049. * @param pendingVariables
  1050. * Vector of variable changes to send
  1051. * @param forceSync
  1052. * Should we use synchronous request?
  1053. */
  1054. private void buildAndSendVariableBurst(ArrayList<String> pendingVariables,
  1055. boolean forceSync) {
  1056. final StringBuffer req = new StringBuffer();
  1057. while (!pendingVariables.isEmpty()) {
  1058. for (int i = 0; i < pendingVariables.size(); i++) {
  1059. if (i > 0) {
  1060. if (i % 2 == 0) {
  1061. req.append(VAR_RECORD_SEPARATOR);
  1062. } else {
  1063. req.append(VAR_FIELD_SEPARATOR);
  1064. }
  1065. }
  1066. req.append(pendingVariables.get(i));
  1067. }
  1068. pendingVariables.clear();
  1069. // Append all the busts to this synchronous request
  1070. if (forceSync && !pendingVariableBursts.isEmpty()) {
  1071. pendingVariables = pendingVariableBursts.get(0);
  1072. pendingVariableBursts.remove(0);
  1073. req.append(VAR_BURST_SEPARATOR);
  1074. }
  1075. }
  1076. makeUidlRequest(req.toString(), false, forceSync, false);
  1077. }
  1078. /**
  1079. * Sends a new value for the given paintables given variable to the server.
  1080. * <p>
  1081. * The update is actually queued to be sent at a suitable time. If immediate
  1082. * is true, the update is sent as soon as possible. If immediate is false,
  1083. * the update will be sent along with the next immediate update.
  1084. * </p>
  1085. *
  1086. * @param paintableId
  1087. * the id of the paintable that owns the variable
  1088. * @param variableName
  1089. * the name of the variable
  1090. * @param newValue
  1091. * the new value to be sent
  1092. * @param immediate
  1093. * true if the update is to be sent as soon as possible
  1094. */
  1095. public void updateVariable(String paintableId, String variableName,
  1096. Paintable newValue, boolean immediate) {
  1097. String pid = (newValue != null) ? getPid(newValue) : null;
  1098. addVariableToQueue(paintableId, variableName, pid, immediate, 'p');
  1099. }
  1100. /**
  1101. * Sends a new value for the given paintables given variable to the server.
  1102. * <p>
  1103. * The update is actually queued to be sent at a suitable time. If immediate
  1104. * is true, the update is sent as soon as possible. If immediate is false,
  1105. * the update will be sent along with the next immediate update.
  1106. * </p>
  1107. *
  1108. * @param paintableId
  1109. * the id of the paintable that owns the variable
  1110. * @param variableName
  1111. * the name of the variable
  1112. * @param newValue
  1113. * the new value to be sent
  1114. * @param immediate
  1115. * true if the update is to be sent as soon as possible
  1116. */
  1117. public void updateVariable(String paintableId, String variableName,
  1118. String newValue, boolean immediate) {
  1119. addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
  1120. }
  1121. /**
  1122. * Sends a new value for the given paintables given variable to the server.
  1123. * <p>
  1124. * The update is actually queued to be sent at a suitable time. If immediate
  1125. * is true, the update is sent as soon as possible. If immediate is false,
  1126. * the update will be sent along with the next immediate update.
  1127. * </p>
  1128. *
  1129. * @param paintableId
  1130. * the id of the paintable that owns the variable
  1131. * @param variableName
  1132. * the name of the variable
  1133. * @param newValue
  1134. * the new value to be sent
  1135. * @param immediate
  1136. * true if the update is to be sent as soon as possible
  1137. */
  1138. public void updateVariable(String paintableId, String variableName,
  1139. int newValue, boolean immediate) {
  1140. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  1141. 'i');
  1142. }
  1143. /**
  1144. * Sends a new value for the given paintables given variable to the server.
  1145. * <p>
  1146. * The update is actually queued to be sent at a suitable time. If immediate
  1147. * is true, the update is sent as soon as possible. If immediate is false,
  1148. * the update will be sent along with the next immediate update.
  1149. * </p>
  1150. *
  1151. * @param paintableId
  1152. * the id of the paintable that owns the variable
  1153. * @param variableName
  1154. * the name of the variable
  1155. * @param newValue
  1156. * the new value to be sent
  1157. * @param immediate
  1158. * true if the update is to be sent as soon as possible
  1159. */
  1160. public void updateVariable(String paintableId, String variableName,
  1161. long newValue, boolean immediate) {
  1162. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  1163. 'l');
  1164. }
  1165. /**
  1166. * Sends a new value for the given paintables given variable to the server.
  1167. * <p>
  1168. * The update is actually queued to be sent at a suitable time. If immediate
  1169. * is true, the update is sent as soon as possible. If immediate is false,
  1170. * the update will be sent along with the next immediate update.
  1171. * </p>
  1172. *
  1173. * @param paintableId
  1174. * the id of the paintable that owns the variable
  1175. * @param variableName
  1176. * the name of the variable
  1177. * @param newValue
  1178. * the new value to be sent
  1179. * @param immediate
  1180. * true if the update is to be sent as soon as possible
  1181. */
  1182. public void updateVariable(String paintableId, String variableName,
  1183. float newValue, boolean immediate) {
  1184. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  1185. 'f');
  1186. }
  1187. /**
  1188. * Sends a new value for the given paintables given variable to the server.
  1189. * <p>
  1190. * The update is actually queued to be sent at a suitable time. If immediate
  1191. * is true, the update is sent as soon as possible. If immediate is false,
  1192. * the update will be sent along with the next immediate update.
  1193. * </p>
  1194. *
  1195. * @param paintableId
  1196. * the id of the paintable that owns the variable
  1197. * @param variableName
  1198. * the name of the variable
  1199. * @param newValue
  1200. * the new value to be sent
  1201. * @param immediate
  1202. * true if the update is to be sent as soon as possible
  1203. */
  1204. public void updateVariable(String paintableId, String variableName,
  1205. double newValue, boolean immediate) {
  1206. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  1207. 'd');
  1208. }
  1209. /**
  1210. * Sends a new value for the given paintables given variable to the server.
  1211. * <p>
  1212. * The update is actually queued to be sent at a suitable time. If immediate
  1213. * is true, the update is sent as soon as possible. If immediate is false,
  1214. * the update will be sent along with the next immediate update.
  1215. * </p>
  1216. *
  1217. * @param paintableId
  1218. * the id of the paintable that owns the variable
  1219. * @param variableName
  1220. * the name of the variable
  1221. * @param newValue
  1222. * the new value to be sent
  1223. * @param immediate
  1224. * true if the update is to be sent as soon as possible
  1225. */
  1226. public void updateVariable(String paintableId, String variableName,
  1227. boolean newValue, boolean immediate) {
  1228. addVariableToQueue(paintableId, variableName, newValue ? "true"
  1229. : "false", immediate, 'b');
  1230. }
  1231. /**
  1232. * Sends a new value for the given paintables given variable to the server.
  1233. * <p>
  1234. * The update is actually queued to be sent at a suitable time. If immediate
  1235. * is true, the update is sent as soon as possible. If immediate is false,
  1236. * the update will be sent along with the next immediate update.
  1237. * </p>
  1238. *
  1239. * @param paintableId
  1240. * the id of the paintable that owns the variable
  1241. * @param variableName
  1242. * the name of the variable
  1243. * @param newValue
  1244. * the new value to be sent
  1245. * @param immediate
  1246. * true if the update is to be sent as soon as possible
  1247. */
  1248. public void updateVariable(String paintableId, String variableName,
  1249. Map<String, Object> map, boolean immediate) {
  1250. final StringBuffer buf = new StringBuffer();
  1251. Iterator<String> iterator = map.keySet().iterator();
  1252. while (iterator.hasNext()) {
  1253. String key = iterator.next();
  1254. Object value = map.get(key);
  1255. char transportType = getTransportType(value);
  1256. buf.append(transportType);
  1257. buf.append(key);
  1258. buf.append(VAR_ARRAYITEM_SEPARATOR);
  1259. if (transportType == 'p') {
  1260. buf.append(getPid((Paintable) value));
  1261. } else {
  1262. buf.append(value);
  1263. }
  1264. if (iterator.hasNext()) {
  1265. buf.append(VAR_ARRAYITEM_SEPARATOR);
  1266. }
  1267. }
  1268. addVariableToQueue(paintableId, variableName, buf.toString(),
  1269. immediate, 'm');
  1270. }
  1271. private char getTransportType(Object value) {
  1272. if (value instanceof String) {
  1273. return 's';
  1274. } else if (value instanceof Paintable) {
  1275. return 'p';
  1276. } else if (value instanceof Boolean) {
  1277. return 'b';
  1278. } else if (value instanceof Integer) {
  1279. return 'i';
  1280. } else if (value instanceof Float) {
  1281. return 'f';
  1282. } else if (value instanceof Double) {
  1283. return 'd';
  1284. } else if (value instanceof Long) {
  1285. return 'l';
  1286. } else if (value instanceof Enum) {
  1287. return 's'; // transported as string representation
  1288. }
  1289. return 'u';
  1290. }
  1291. /**
  1292. * Sends a new value for the given paintables given variable to the server.
  1293. *
  1294. * The update is actually queued to be sent at a suitable time. If immediate
  1295. * is true, the update is sent as soon as possible. If immediate is false,
  1296. * the update will be sent along with the next immediate update.
  1297. *
  1298. * A null array is sent as an empty array.
  1299. *
  1300. * @param paintableId
  1301. * the id of the paintable that owns the variable
  1302. * @param variableName
  1303. * the name of the variable
  1304. * @param newValue
  1305. * the new value to be sent
  1306. * @param immediate
  1307. * true if the update is to be sent as soon as possible
  1308. */
  1309. public void updateVariable(String paintableId, String variableName,
  1310. String[] values, boolean immediate) {
  1311. final StringBuffer buf = new StringBuffer();
  1312. if (values != null) {
  1313. for (int i = 0; i < values.length; i++) {
  1314. buf.append(values[i]);
  1315. // there will be an extra separator at the end to differentiate
  1316. // between an empty array and one containing an empty string
  1317. // only
  1318. buf.append(VAR_ARRAYITEM_SEPARATOR);
  1319. }
  1320. }
  1321. addVariableToQueue(paintableId, variableName, buf.toString(),
  1322. immediate, 'c');
  1323. }
  1324. /**
  1325. * Sends a new value for the given paintables given variable to the server.
  1326. *
  1327. * The update is actually queued to be sent at a suitable time. If immediate
  1328. * is true, the update is sent as soon as possible. If immediate is false,
  1329. * the update will be sent along with the next immediate update. </p>
  1330. *
  1331. * A null array is sent as an empty array.
  1332. *
  1333. *
  1334. * @param paintableId
  1335. * the id of the paintable that owns the variable
  1336. * @param variableName
  1337. * the name of the variable
  1338. * @param newValue
  1339. * the new value to be sent
  1340. * @param immediate
  1341. * true if the update is to be sent as soon as possible
  1342. */
  1343. public void updateVariable(String paintableId, String variableName,
  1344. Object[] values, boolean immediate) {
  1345. final StringBuffer buf = new StringBuffer();
  1346. if (values != null) {
  1347. for (int i = 0; i < values.length; i++) {
  1348. if (i > 0) {
  1349. buf.append(VAR_ARRAYITEM_SEPARATOR);
  1350. }
  1351. Object value = values[i];
  1352. char transportType = getTransportType(value);
  1353. // first char tells the type in array
  1354. buf.append(transportType);
  1355. if (transportType == 'p') {
  1356. buf.append(getPid((Paintable) value));
  1357. } else {
  1358. buf.append(value);
  1359. }
  1360. }
  1361. }
  1362. addVariableToQueue(paintableId, variableName, buf.toString(),
  1363. immediate, 'a');
  1364. }
  1365. /**
  1366. * Update generic component features.
  1367. *
  1368. * <h2>Selecting correct implementation</h2>
  1369. *
  1370. * <p>
  1371. * The implementation of a component depends on many properties, including
  1372. * styles, component features, etc. Sometimes the user changes those
  1373. * properties after the component has been created. Calling this method in
  1374. * the beginning of your updateFromUIDL -method automatically replaces your
  1375. * component with more appropriate if the requested implementation changes.
  1376. * </p>
  1377. *
  1378. * <h2>Caption, icon, error messages and description</h2>
  1379. *
  1380. * <p>
  1381. * Component can delegate management of caption, icon, error messages and
  1382. * description to parent layout. This is optional an should be decided by
  1383. * component author
  1384. * </p>
  1385. *
  1386. * <h2>Component visibility and disabling</h2>
  1387. *
  1388. * This method will manage component visibility automatically and if
  1389. * component is an instanceof FocusWidget, also handle component disabling
  1390. * when needed.
  1391. *
  1392. * @param component
  1393. * Widget to be updated, expected to implement an instance of
  1394. * Paintable
  1395. * @param uidl
  1396. * UIDL to be painted
  1397. * @param manageCaption
  1398. * True if you want to delegate caption, icon, description and
  1399. * error message management to parent.
  1400. *
  1401. * @return Returns true iff no further painting is needed by caller
  1402. */
  1403. public boolean updateComponent(Widget component, UIDL uidl,
  1404. boolean manageCaption) {
  1405. String pid = getPid(component.getElement());
  1406. if (pid == null) {
  1407. getConsole().error(
  1408. "Trying to update an unregistered component: "
  1409. + Util.getSimpleName(component));
  1410. return true;
  1411. }
  1412. ComponentDetail componentDetail = idToPaintableDetail.get(pid);
  1413. if (componentDetail == null) {
  1414. getConsole().error(
  1415. "ComponentDetail not found for "
  1416. + Util.getSimpleName(component) + " with PID "
  1417. + pid + ". This should not happen.");
  1418. return true;
  1419. }
  1420. // If the server request that a cached instance should be used, do
  1421. // nothing
  1422. if (uidl.getBooleanAttribute("cached")) {
  1423. return true;
  1424. }
  1425. // register the listened events by the server-side to the event-handler
  1426. // of the component
  1427. componentDetail.registerEventListenersFromUIDL(uidl);
  1428. // Visibility
  1429. boolean visible = !uidl.getBooleanAttribute("invisible");
  1430. boolean wasVisible = component.isVisible();
  1431. component.setVisible(visible);
  1432. if (wasVisible != visible) {
  1433. // Changed invisibile <-> visible
  1434. if (wasVisible && manageCaption) {
  1435. // Must hide caption when component is hidden
  1436. final Container parent = Util.getLayout(component);
  1437. if (parent != null) {
  1438. parent.updateCaption((Paintable) component, uidl);
  1439. }
  1440. }
  1441. }
  1442. if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) {
  1443. DOM.setElementProperty(component.getElement(), "id", uidl.getId()
  1444. .substring(5));
  1445. }
  1446. if (!visible) {
  1447. // component is invisible, delete old size to notify parent, if
  1448. // later make visible
  1449. componentDetail.setOffsetSize(null);
  1450. return true;
  1451. }
  1452. // Switch to correct implementation if needed
  1453. if (!widgetSet.isCorrectImplementation(component, uidl, configuration)) {
  1454. final Widget w = (Widget) widgetSet.createWidget(uidl,
  1455. configuration);
  1456. // deferred binding check TODO change isCorrectImplementation to use
  1457. // stored detected class, making this innecessary
  1458. if (w.getClass() != component.getClass()) {
  1459. final Container parent = Util.getLayout(component);
  1460. if (parent != null) {
  1461. parent.replaceChildComponent(component, w);
  1462. unregisterPaintable((Paintable) component);
  1463. registerPaintable(uidl.getId(), (Paintable) w);
  1464. ((Paintable) w).updateFromUIDL(uidl, this);
  1465. return true;
  1466. }
  1467. }
  1468. }
  1469. boolean enabled = !uidl.getBooleanAttribute("disabled");
  1470. if (component instanceof FocusWidget) {
  1471. FocusWidget fw = (FocusWidget) component;
  1472. if (uidl.hasAttribute("tabindex")) {
  1473. fw.setTabIndex(uidl.getIntAttribute("tabindex"));
  1474. }
  1475. // Disabled state may affect tabindex
  1476. fw.setEnabled(enabled);
  1477. }
  1478. StringBuffer styleBuf = new StringBuffer();
  1479. final String primaryName = component.getStylePrimaryName();
  1480. styleBuf.append(primaryName);
  1481. // first disabling and read-only status
  1482. if (!enabled) {
  1483. styleBuf.append(" ");
  1484. styleBuf.append("v-disabled");
  1485. }
  1486. if (uidl.getBooleanAttribute("readonly")) {
  1487. styleBuf.append(" ");
  1488. styleBuf.append("v-readonly");
  1489. }
  1490. // add additional styles as css classes, prefixed with component default
  1491. // stylename
  1492. if (uidl.hasAttribute("style")) {
  1493. final String[] styles = uidl.getStringAttribute("style").split(" ");
  1494. for (int i = 0; i < styles.length; i++) {
  1495. styleBuf.append(" ");
  1496. styleBuf.append(primaryName);
  1497. styleBuf.append("-");
  1498. styleBuf.append(styles[i]);
  1499. styleBuf.append(" ");
  1500. styleBuf.append(styles[i]);
  1501. }
  1502. }
  1503. // add modified classname to Fields
  1504. if (uidl.hasAttribute("modified") && component instanceof Field) {
  1505. styleBuf.append(" ");
  1506. styleBuf.append(MODIFIED_CLASSNAME);
  1507. }
  1508. TooltipInfo tooltipInfo = componentDetail.getTooltipInfo(null);
  1509. // Update tooltip
  1510. if (uidl.hasAttribute(ATTRIBUTE_DESCRIPTION)) {
  1511. tooltipInfo
  1512. .setTitle(uidl.getStringAttribute(ATTRIBUTE_DESCRIPTION));
  1513. } else {
  1514. tooltipInfo.setTitle(null);
  1515. }
  1516. // add error classname to components w/ error
  1517. if (uidl.hasAttribute(ATTRIBUTE_ERROR)) {
  1518. tooltipInfo.setErrorUidl(uidl.getErrors());
  1519. styleBuf.append(" ");
  1520. styleBuf.append(primaryName);
  1521. styleBuf.append(ERROR_CLASSNAME_EXT);
  1522. } else {
  1523. tooltipInfo.setErrorUidl(null);
  1524. }
  1525. // add required style to required components
  1526. if (uidl.hasAttribute("required")) {
  1527. styleBuf.append(" ");
  1528. styleBuf.append(primaryName);
  1529. styleBuf.append(REQUIRED_CLASSNAME_EXT);
  1530. }
  1531. // Styles + disabled & readonly
  1532. component.setStyleName(styleBuf.toString());
  1533. // Set captions
  1534. if (manageCaption) {
  1535. final Container parent = Util.getLayout(component);
  1536. if (parent != null) {
  1537. parent.updateCaption((Paintable) component, uidl);
  1538. }
  1539. }
  1540. /*
  1541. * updateComponentSize need to be after caption update so caption can be
  1542. * taken into account
  1543. */
  1544. updateComponentSize(componentDetail, uidl);
  1545. return false;
  1546. }
  1547. private void updateComponentSize(ComponentDetail cd, UIDL uidl) {
  1548. String w = uidl.hasAttribute("width") ? uidl
  1549. .getStringAttribute("width") : "";
  1550. String h = uidl.hasAttribute("height") ? uidl
  1551. .getStringAttribute("height") : "";
  1552. float relativeWidth = Util.parseRelativeSize(w);
  1553. float relativeHeight = Util.parseRelativeSize(h);
  1554. // First update maps so they are correct in the setHeight/setWidth calls
  1555. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1556. // One or both is relative
  1557. FloatSize relativeSize = new FloatSize(relativeWidth,
  1558. relativeHeight);
  1559. if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) {
  1560. // The component has changed from absolute size to relative size
  1561. relativeSizeChanges.add(cd.getComponent());
  1562. }
  1563. cd.setRelativeSize(relativeSize);
  1564. } else if (relativeHeight < 0.0 && relativeWidth < 0.0) {
  1565. if (cd.getRelativeSize() != null) {
  1566. // The component has changed from relative size to absolute size
  1567. relativeSizeChanges.add(cd.getComponent());
  1568. }
  1569. cd.setRelativeSize(null);
  1570. }
  1571. Widget component = (Widget) cd.getComponent();
  1572. // Set absolute sizes
  1573. if (relativeHeight < 0.0) {
  1574. component.setHeight(h);
  1575. }
  1576. if (relativeWidth < 0.0) {
  1577. component.setWidth(w);
  1578. }
  1579. // Set relative sizes
  1580. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1581. // One or both is relative
  1582. handleComponentRelativeSize(cd);
  1583. }
  1584. }
  1585. /**
  1586. * Traverses recursively child widgets until ContainerResizedListener child
  1587. * widget is found. They will delegate it further if needed.
  1588. *
  1589. * @param container
  1590. */
  1591. private boolean runningLayout = false;
  1592. /**
  1593. * Causes a re-calculation/re-layout of all paintables in a container.
  1594. *
  1595. * @param container
  1596. */
  1597. public void runDescendentsLayout(HasWidgets container) {
  1598. if (runningLayout) {
  1599. return;
  1600. }
  1601. runningLayout = true;
  1602. internalRunDescendentsLayout(container);
  1603. runningLayout = false;
  1604. }
  1605. /**
  1606. * This will cause re-layouting of all components. Mainly used for
  1607. * development. Published to JavaScript.
  1608. */
  1609. public void forceLayout() {
  1610. Set<Paintable> set = new HashSet<Paintable>();
  1611. for (ComponentDetail cd : idToPaintableDetail.values()) {
  1612. set.add(cd.getComponent());
  1613. }
  1614. Util.componentSizeUpdated(set);
  1615. }
  1616. private void internalRunDescendentsLayout(HasWidgets container) {
  1617. // getConsole().log(
  1618. // "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
  1619. final Iterator<Widget> childWidgets = container.iterator();
  1620. while (childWidgets.hasNext()) {
  1621. final Widget child = childWidgets.next();
  1622. if (child instanceof Paintable) {
  1623. if (handleComponentRelativeSize(child)) {
  1624. /*
  1625. * Only need to propagate event if "child" has a relative
  1626. * size
  1627. */
  1628. if (child instanceof ContainerResizedListener) {
  1629. ((ContainerResizedListener) child).iLayout();
  1630. }
  1631. if (child instanceof HasWidgets) {
  1632. final HasWidgets childContainer = (HasWidgets) child;
  1633. internalRunDescendentsLayout(childContainer);
  1634. }
  1635. }
  1636. } else if (child instanceof HasWidgets) {
  1637. // propagate over non Paintable HasWidgets
  1638. internalRunDescendentsLayout((HasWidgets) child);
  1639. }
  1640. }
  1641. }
  1642. /**
  1643. * Converts relative sizes into pixel sizes.
  1644. *
  1645. * @param child
  1646. * @return true if the child has a relative size
  1647. */
  1648. private boolean handleComponentRelativeSize(ComponentDetail cd) {
  1649. if (cd == null) {
  1650. return false;
  1651. }
  1652. boolean debugSizes = false;
  1653. FloatSize relativeSize = cd.getRelativeSize();
  1654. if (relativeSize == null) {
  1655. return false;
  1656. }
  1657. Widget widget = (Widget) cd.getComponent();
  1658. boolean horizontalScrollBar = false;
  1659. boolean verticalScrollBar = false;
  1660. Container parent = Util.getLayout(widget);
  1661. RenderSpace renderSpace;
  1662. // Parent-less components (like sub-windows) are relative to browser
  1663. // window.
  1664. if (parent == null) {
  1665. renderSpace = new RenderSpace(Window.getClientWidth(),
  1666. Window.getClientHeight());
  1667. } else {
  1668. renderSpace = parent.getAllocatedSpace(widget);
  1669. }
  1670. if (relativeSize.getHeight() >= 0) {
  1671. if (renderSpace != null) {
  1672. if (renderSpace.getScrollbarSize() > 0) {
  1673. if (relativeSize.getWidth() > 100) {
  1674. horizontalScrollBar = true;
  1675. } else if (relativeSize.getWidth() < 0
  1676. && renderSpace.getWidth() > 0) {
  1677. int offsetWidth = widget.getOffsetWidth();
  1678. int width = renderSpace.getWidth();
  1679. if (offsetWidth > width) {
  1680. horizontalScrollBar = true;
  1681. }
  1682. }
  1683. }
  1684. int height = renderSpace.getHeight();
  1685. if (horizontalScrollBar) {
  1686. height -= renderSpace.getScrollbarSize();
  1687. }
  1688. if (validatingLayouts && height <= 0) {
  1689. zeroHeightComponents.add(cd.getComponent());
  1690. }
  1691. height = (int) (height * relativeSize.getHeight() / 100.0);
  1692. if (height < 0) {
  1693. height = 0;
  1694. }
  1695. if (debugSizes) {
  1696. getConsole()
  1697. .log("Widget "
  1698. + Util.getSimpleName(widget)
  1699. + "/"
  1700. + getPid(widget.getElement())
  1701. + " relative height "
  1702. + relativeSize.getHeight()
  1703. + "% of "
  1704. + renderSpace.getHeight()
  1705. + "px (reported by "
  1706. + Util.getSimpleName(parent)
  1707. + "/"
  1708. + (parent == null ? "?" : parent.hashCode())
  1709. + ") : " + height + "px");
  1710. }
  1711. widget.setHeight(height + "px");
  1712. } else {
  1713. widget.setHeight(relativeSize.getHeight() + "%");
  1714. ApplicationConnection.getConsole().error(
  1715. Util.getLayout(widget).getClass().getName()
  1716. + " did not produce allocatedSpace for "
  1717. + widget.getClass().getName());
  1718. }
  1719. }
  1720. if (relativeSize.getWidth() >= 0) {
  1721. if (renderSpace != null) {
  1722. int width = renderSpace.getWidth();
  1723. if (renderSpace.getScrollbarSize() > 0) {
  1724. if (relativeSize.getHeight() > 100) {
  1725. verticalScrollBar = true;
  1726. } else if (relativeSize.getHeight() < 0
  1727. && renderSpace.getHeight() > 0
  1728. && widget.getOffsetHeight() > renderSpace
  1729. .getHeight()) {
  1730. verticalScrollBar = true;
  1731. }
  1732. }
  1733. if (verticalScrollBar) {
  1734. width -= renderSpace.getScrollbarSize();
  1735. }
  1736. if (validatingLayouts && width <= 0) {
  1737. zeroWidthComponents.add(cd.getComponent());
  1738. }
  1739. width = (int) (width * relativeSize.getWidth() / 100.0);
  1740. if (width < 0) {
  1741. width = 0;
  1742. }
  1743. if (debugSizes) {
  1744. getConsole().log(
  1745. "Widget " + Util.getSimpleName(widget) + "/"
  1746. + getPid(widget.getElement())
  1747. + " relative width "
  1748. + relativeSize.getWidth() + "% of "
  1749. + renderSpace.getWidth()
  1750. + "px (reported by "
  1751. + Util.getSimpleName(parent) + "/"
  1752. + (parent == null ? "?" : getPid(parent))
  1753. + ") : " + width + "px");
  1754. }
  1755. widget.setWidth(width + "px");
  1756. } else {
  1757. widget.setWidth(relativeSize.getWidth() + "%");
  1758. ApplicationConnection.getConsole().error(
  1759. Util.getLayout(widget).getClass().getName()
  1760. + " did not produce allocatedSpace for "
  1761. + widget.getClass().getName());
  1762. }
  1763. }
  1764. return true;
  1765. }
  1766. /**
  1767. * Converts relative sizes into pixel sizes.
  1768. *
  1769. * @param child
  1770. * @return true if the child has a relative size
  1771. */
  1772. public boolean handleComponentRelativeSize(Widget child) {
  1773. return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child
  1774. .getElement())));
  1775. }
  1776. /**
  1777. * Gets the specified Paintables relative size (percent).
  1778. *
  1779. * @param widget
  1780. * the paintable whose size is needed
  1781. * @return the the size if the paintable is relatively sized, -1 otherwise
  1782. */
  1783. public FloatSize getRelativeSize(Widget widget) {
  1784. return idToPaintableDetail.get(getPid(widget.getElement()))
  1785. .getRelativeSize();
  1786. }
  1787. /**
  1788. * Get either existing or new Paintable for given UIDL.
  1789. *
  1790. * If corresponding Paintable has been previously painted, return it.
  1791. * Otherwise create and register a new Paintable from UIDL. Caller must
  1792. * update the returned Paintable from UIDL after it has been connected to
  1793. * parent.
  1794. *
  1795. * @param uidl
  1796. * UIDL to create Paintable from.
  1797. * @return Either existing or new Paintable corresponding to UIDL.
  1798. */
  1799. public Paintable getPaintable(UIDL uidl) {
  1800. final String id = uidl.getId();
  1801. Paintable w = getPaintable(id);
  1802. if (w != null) {
  1803. return w;
  1804. } else {
  1805. w = widgetSet.createWidget(uidl, configuration);
  1806. registerPaintable(id, w);
  1807. return w;
  1808. }
  1809. }
  1810. /**
  1811. * Returns a Paintable element by its root element
  1812. *
  1813. * @param element
  1814. * Root element of the paintable
  1815. */
  1816. public Paintable getPaintable(Element element) {
  1817. return getPaintable(getPid(element));
  1818. }
  1819. /**
  1820. * Gets a recource that has been pre-loaded via UIDL, such as custom
  1821. * layouts.
  1822. *
  1823. * @param name
  1824. * identifier of the resource to get
  1825. * @return the resource
  1826. */
  1827. public String getResource(String name) {
  1828. return resourcesMap.get(name);
  1829. }
  1830. /**
  1831. * Singleton method to get instance of app's context menu.
  1832. *
  1833. * @return VContextMenu object
  1834. */
  1835. public VContextMenu getContextMenu() {
  1836. if (contextMenu == null) {
  1837. contextMenu = new VContextMenu();
  1838. DOM.setElementProperty(contextMenu.getElement(), "id",
  1839. "PID_VAADIN_CM");
  1840. }
  1841. return contextMenu;
  1842. }
  1843. /**
  1844. * Translates custom protocols in UIDL URI's to be recognizable by browser.
  1845. * All uri's from UIDL should be routed via this method before giving them
  1846. * to browser due URI's in UIDL may contain custom protocols like theme://.
  1847. *
  1848. * @param uidlUri
  1849. * Vaadin URI from uidl
  1850. * @return translated URI ready for browser
  1851. */
  1852. public String translateVaadinUri(String uidlUri) {
  1853. if (uidlUri == null) {
  1854. return null;
  1855. }
  1856. if (uidlUri.startsWith("theme://")) {
  1857. final String themeUri = configuration.getThemeUri();
  1858. if (themeUri == null) {
  1859. console.error("Theme not set: ThemeResource will not be found. ("
  1860. + uidlUri + ")");
  1861. }
  1862. uidlUri = themeUri + uidlUri.substring(7);
  1863. }
  1864. return uidlUri;
  1865. }
  1866. /**
  1867. * Gets the URI for the current theme. Can be used to reference theme
  1868. * resources.
  1869. *
  1870. * @return URI to the current theme
  1871. */
  1872. public String getThemeUri() {
  1873. return configuration.getThemeUri();
  1874. }
  1875. /**
  1876. * Listens for Notification hide event, and redirects. Used for system
  1877. * messages, such as session expired.
  1878. *
  1879. */
  1880. private class NotificationRedirect implements VNotification.EventListener {
  1881. String url;
  1882. NotificationRedirect(String url) {
  1883. this.url = url;
  1884. }
  1885. public void notificationHidden(HideEvent event) {
  1886. redirect(url);
  1887. }
  1888. }
  1889. /* Extended title handling */
  1890. /**
  1891. * Data showed in tooltips are stored centrilized as it may be needed in
  1892. * varios place: caption, layouts, and in owner components themselves.
  1893. *
  1894. * Updating TooltipInfo is done in updateComponent method.
  1895. *
  1896. */
  1897. public TooltipInfo getTooltipTitleInfo(Paintable titleOwner, Object key) {
  1898. if (null == titleOwner) {
  1899. return null;
  1900. }
  1901. ComponentDetail cd = idToPaintableDetail.get(getPid(titleOwner));
  1902. if (null != cd) {
  1903. return cd.getTooltipInfo(key);
  1904. } else {
  1905. return null;
  1906. }
  1907. }
  1908. private final VTooltip tooltip = new VTooltip(this);
  1909. /**
  1910. * Component may want to delegate Tooltip handling to client. Layouts add
  1911. * Tooltip (description, errors) to caption, but some components may want
  1912. * them to appear one other elements too.
  1913. *
  1914. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1915. *
  1916. * @param event
  1917. * @param owner
  1918. */
  1919. public void handleTooltipEvent(Event event, Paintable owner) {
  1920. tooltip.handleTooltipEvent(event, owner, null);
  1921. }
  1922. /**
  1923. * Component may want to delegate Tooltip handling to client. Layouts add
  1924. * Tooltip (description, errors) to caption, but some components may want
  1925. * them to appear one other elements too.
  1926. *
  1927. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1928. *
  1929. * @param event
  1930. * @param owner
  1931. * @param key
  1932. * the key for tooltip if this is "additional" tooltip, null for
  1933. * components "main tooltip"
  1934. */
  1935. public void handleTooltipEvent(Event event, Paintable owner, Object key) {
  1936. tooltip.handleTooltipEvent(event, owner, key);
  1937. }
  1938. /**
  1939. * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
  1940. *
  1941. * @param el
  1942. * the IMG element to fix
  1943. */
  1944. public void addPngFix(Element el) {
  1945. BrowserInfo b = BrowserInfo.get();
  1946. if (b.isIE6()) {
  1947. Util.addPngFix(el, getThemeUri() + "/../runo/common/img/blank.gif");
  1948. }
  1949. }
  1950. /*
  1951. * Helper to run layout functions triggered by child components with a
  1952. * decent interval.
  1953. */
  1954. private final Timer layoutTimer = new Timer() {
  1955. private boolean isPending = false;
  1956. @Override
  1957. public void schedule(int delayMillis) {
  1958. if (!isPending) {
  1959. super.schedule(delayMillis);
  1960. isPending = true;
  1961. }
  1962. }
  1963. @Override
  1964. public void run() {
  1965. getConsole().log(
  1966. "Running re-layout of " + view.getClass().getName());
  1967. runDescendentsLayout(view);
  1968. isPending = false;
  1969. }
  1970. };
  1971. /**
  1972. * Components can call this function to run all layout functions. This is
  1973. * usually done, when component knows that its size has changed.
  1974. */
  1975. public void requestLayoutPhase() {
  1976. layoutTimer.schedule(500);
  1977. }
  1978. private String windowName = null;
  1979. /**
  1980. * Reset the name of the current browser-window. This should reflect the
  1981. * window-name used in the server, but might be different from the
  1982. * window-object target-name on client.
  1983. *
  1984. * @param stringAttribute
  1985. * New name for the window.
  1986. */
  1987. public void setWindowName(String newName) {
  1988. windowName = newName;
  1989. }
  1990. /**
  1991. * Use to notify that the given component's caption has changed; layouts may
  1992. * have to be recalculated.
  1993. *
  1994. * @param component
  1995. * the Paintable whose caption has changed
  1996. */
  1997. public void captionSizeUpdated(Paintable component) {
  1998. componentCaptionSizeChanges.add(component);
  1999. }
  2000. /**
  2001. * Requests an analyze of layouts, to find inconsistensies. Exclusively used
  2002. * for debugging during develpoment.
  2003. */
  2004. public void analyzeLayouts() {
  2005. makeUidlRequest("", true, false, true);
  2006. }
  2007. /**
  2008. * Gets the main view, a.k.a top-level window.
  2009. *
  2010. * @return the main view
  2011. */
  2012. public VView getView() {
  2013. return view;
  2014. }
  2015. /**
  2016. * If component has several tooltips in addition to the one provided by
  2017. * {@link com.vaadin.ui.AbstractComponent}, component can register them with
  2018. * this method.
  2019. * <p>
  2020. * Component must also pipe events to
  2021. * {@link #handleTooltipEvent(Event, Paintable, Object)} method.
  2022. * <p>
  2023. * This method can also be used to deregister tooltips by using null as
  2024. * tooltip
  2025. *
  2026. * @param paintable
  2027. * Paintable "owning" this tooltip
  2028. * @param key
  2029. * key assosiated with given tooltip. Can be any object. For
  2030. * example a related dom element. Same key must be given for
  2031. * {@link #handleTooltipEvent(Event, Paintable, Object)} method.
  2032. *
  2033. * @param tooltip
  2034. * the TooltipInfo object containing details shown in tooltip,
  2035. * null if deregistering tooltip
  2036. */
  2037. public void registerTooltip(Paintable paintable, Object key,
  2038. TooltipInfo tooltip) {
  2039. ComponentDetail componentDetail = idToPaintableDetail
  2040. .get(getPid(paintable));
  2041. componentDetail.putAdditionalTooltip(key, tooltip);
  2042. }
  2043. /**
  2044. * Gets the {@link ApplicationConfiguration} for the current application.
  2045. *
  2046. * @see ApplicationConfiguration
  2047. * @return the configuration for this application
  2048. */
  2049. public ApplicationConfiguration getConfiguration() {
  2050. return configuration;
  2051. }
  2052. /**
  2053. * Checks if there is a registered server side listener for the event. The
  2054. * list of events which has server side listeners is updated automatically
  2055. * before the component is updated so the value is correct if called from
  2056. * updatedFromUIDL.
  2057. *
  2058. * @param eventIdentifier
  2059. * The identifier for the event
  2060. * @return true if at least one listener has been registered on server side
  2061. * for the event identified by eventIdentifier.
  2062. */
  2063. public boolean hasEventListeners(Paintable paintable, String eventIdentifier) {
  2064. return idToPaintableDetail.get(getPid(paintable)).hasEventListeners(
  2065. eventIdentifier);
  2066. }
  2067. }