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

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