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

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