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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client;
  5. import java.util.Date;
  6. import java.util.HashMap;
  7. import java.util.Iterator;
  8. import java.util.Vector;
  9. import com.google.gwt.core.client.JavaScriptObject;
  10. import com.google.gwt.http.client.Request;
  11. import com.google.gwt.http.client.RequestBuilder;
  12. import com.google.gwt.http.client.RequestCallback;
  13. import com.google.gwt.http.client.RequestException;
  14. import com.google.gwt.http.client.Response;
  15. import com.google.gwt.json.client.JSONArray;
  16. import com.google.gwt.json.client.JSONObject;
  17. import com.google.gwt.json.client.JSONParser;
  18. import com.google.gwt.json.client.JSONString;
  19. import com.google.gwt.json.client.JSONValue;
  20. import com.google.gwt.user.client.Command;
  21. import com.google.gwt.user.client.DOM;
  22. import com.google.gwt.user.client.DeferredCommand;
  23. import com.google.gwt.user.client.Element;
  24. import com.google.gwt.user.client.Event;
  25. import com.google.gwt.user.client.Timer;
  26. import com.google.gwt.user.client.Window;
  27. import com.google.gwt.user.client.WindowCloseListener;
  28. import com.google.gwt.user.client.ui.FocusWidget;
  29. import com.google.gwt.user.client.ui.HasFocus;
  30. import com.google.gwt.user.client.ui.HasWidgets;
  31. import com.google.gwt.user.client.ui.Widget;
  32. import com.itmill.toolkit.terminal.gwt.client.ui.ContextMenu;
  33. import com.itmill.toolkit.terminal.gwt.client.ui.Field;
  34. import com.itmill.toolkit.terminal.gwt.client.ui.IView;
  35. import com.itmill.toolkit.terminal.gwt.client.ui.Notification;
  36. import com.itmill.toolkit.terminal.gwt.client.ui.Notification.HideEvent;
  37. /**
  38. * Entry point classes define <code>onModuleLoad()</code>.
  39. */
  40. public class ApplicationConnection {
  41. private static final String MODIFIED_CLASSNAME = "i-modified";
  42. private static final String REQUIRED_CLASSNAME_EXT = "-required";
  43. private static final String ERROR_CLASSNAME_EXT = "-error";
  44. public static final String VAR_RECORD_SEPARATOR = "\u001e";
  45. public static final String VAR_FIELD_SEPARATOR = "\u001f";
  46. private final HashMap resourcesMap = new HashMap();
  47. private static Console console;
  48. private static boolean testingMode;
  49. private final Vector pendingVariables = new Vector();
  50. private final HashMap idToPaintable = new HashMap();
  51. private final HashMap paintableToId = new HashMap();
  52. /** Contains ExtendedTitleInfo by paintable id */
  53. private final HashMap paintableToTitle = new HashMap();
  54. private final WidgetSet widgetSet;
  55. private ContextMenu contextMenu = null;
  56. private Timer loadTimer;
  57. private Timer loadTimer2;
  58. private Timer loadTimer3;
  59. private Element loadElement;
  60. private final IView view;
  61. private boolean applicationRunning = false;
  62. /**
  63. * True if each Paintable objects id is injected to DOM. Used for Testing
  64. * Tools.
  65. */
  66. private boolean usePaintableIdsInDOM = false;
  67. /**
  68. * Contains reference for client wrapper given to Testing Tools.
  69. *
  70. * Used in JSNI functions
  71. *
  72. * @SuppressWarnings
  73. */
  74. private final JavaScriptObject ttClientWrapper = null;
  75. private int activeRequests = 0;
  76. private final ApplicationConfiguration configuration;
  77. private final Vector pendingVariableBursts = new Vector();
  78. public ApplicationConnection(WidgetSet widgetSet,
  79. ApplicationConfiguration cnf) {
  80. this.widgetSet = widgetSet;
  81. configuration = cnf;
  82. if (isDebugMode()) {
  83. console = new DebugConsole(this, cnf);
  84. } else {
  85. console = new NullConsole();
  86. }
  87. if (checkTestingMode()) {
  88. usePaintableIdsInDOM = true;
  89. initializeTestingTools();
  90. Window.addWindowCloseListener(new WindowCloseListener() {
  91. public void onWindowClosed() {
  92. uninitializeTestingTools();
  93. }
  94. public String onWindowClosing() {
  95. return null;
  96. }
  97. });
  98. }
  99. initializeClientHooks();
  100. // TODO remove hard coded id name
  101. view = new IView(cnf.getRootPanelId());
  102. makeUidlRequest("", true);
  103. applicationRunning = true;
  104. }
  105. /**
  106. * Method to check if application is in testing mode. Can be used after
  107. * application init.
  108. *
  109. * @return true if in testing mode
  110. */
  111. public static boolean isTestingMode() {
  112. return testingMode;
  113. }
  114. /**
  115. * Check is application is run in testing mode.
  116. *
  117. * @return true if in testing mode
  118. */
  119. private native static boolean checkTestingMode()
  120. /*-{
  121. @com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::testingMode = $wnd.top.itmill && $wnd.top.itmill.registerToTT ? true : false;
  122. return @com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::testingMode;
  123. }-*/;
  124. private native void initializeTestingTools()
  125. /*-{
  126. var ap = this;
  127. var client = {};
  128. client.isActive = function() {
  129. return ap.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::hasActiveRequest()();
  130. }
  131. var vi = ap.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
  132. if (vi) {
  133. client.getVersionInfo = function() {
  134. return vi;
  135. }
  136. }
  137. $wnd.top.itmill.registerToTT(client);
  138. this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::ttClientWrapper = client;
  139. }-*/;
  140. /**
  141. * Helper for tt initialization
  142. */
  143. private JavaScriptObject getVersionInfo() {
  144. return configuration.getVersionInfoJSObject();
  145. }
  146. private native void uninitializeTestingTools()
  147. /*-{
  148. $wnd.top.itmill.unregisterFromTT(this.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::ttClientWrapper);
  149. }-*/;
  150. /**
  151. * Publishes a JavaScript API for mash-up applications.
  152. * <ul>
  153. * <li><code>itmill.forceSync()</code> sends pending variable changes, in
  154. * effect synchronizing the server and client state. This is done for all
  155. * applications on host page.</li>
  156. * </ul>
  157. *
  158. * TODO make this multi-app aware
  159. */
  160. private native void initializeClientHooks()
  161. /*-{
  162. var app = this;
  163. var oldSync;
  164. if($wnd.itmill.forceSync) {
  165. oldSync = $wnd.itmill.forceSync;
  166. }
  167. $wnd.itmill.forceSync = function() {
  168. if(oldSync) {
  169. oldSync();
  170. }
  171. app.@com.itmill.toolkit.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
  172. }
  173. }-*/;
  174. public static Console getConsole() {
  175. return console;
  176. }
  177. private native static boolean isDebugMode()
  178. /*-{
  179. var uri = $wnd.location;
  180. var re = /debug[^\/]*$/;
  181. return re.test(uri);
  182. }-*/;
  183. public String getAppUri() {
  184. return configuration.getApplicationUri();
  185. };
  186. public boolean hasActiveRequest() {
  187. return (activeRequests > 0);
  188. }
  189. private void makeUidlRequest(String requestData, boolean repaintAll) {
  190. startRequest();
  191. console.log("Making UIDL Request with params: " + requestData);
  192. String uri = getAppUri() + "UIDL" + configuration.getPathInfo();
  193. if (repaintAll) {
  194. uri += "?repaintAll=1";
  195. }
  196. final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
  197. // rb.setHeader("Content-Type",
  198. // "application/x-www-form-urlencoded; charset=utf-8");
  199. rb.setHeader("Content-Type", "text/plain;charset=utf-8");
  200. try {
  201. rb.sendRequest(requestData, new RequestCallback() {
  202. public void onError(Request request, Throwable exception) {
  203. // TODO Better reporting to user
  204. console.error("Got error");
  205. endRequest();
  206. }
  207. public void onResponseReceived(Request request,
  208. Response response) {
  209. handleReceivedJSONMessage(response);
  210. }
  211. });
  212. } catch (final RequestException e) {
  213. // TODO Better reporting to user
  214. console.error(e.getMessage());
  215. endRequest();
  216. }
  217. }
  218. private void startRequest() {
  219. activeRequests++;
  220. showLoadingIndicator();
  221. }
  222. private void endRequest() {
  223. checkForPendingVariableBursts();
  224. activeRequests--;
  225. // deferring to avoid flickering
  226. DeferredCommand.addCommand(new Command() {
  227. public void execute() {
  228. if (activeRequests == 0) {
  229. hideLoadingIndicator();
  230. }
  231. }
  232. });
  233. }
  234. /**
  235. * This method is called after applying uidl change set to application.
  236. *
  237. * It will clean current and queued variable change sets. And send next
  238. * change set if it exists.
  239. */
  240. private void checkForPendingVariableBursts() {
  241. cleanVariableBurst(pendingVariables);
  242. if (pendingVariableBursts.size() > 0) {
  243. for (Iterator iterator = pendingVariableBursts.iterator(); iterator
  244. .hasNext();) {
  245. cleanVariableBurst((Vector) iterator.next());
  246. }
  247. Vector nextBurst = (Vector) pendingVariableBursts.firstElement();
  248. pendingVariableBursts.remove(0);
  249. buildAndSendVariableBurst(nextBurst);
  250. }
  251. }
  252. /**
  253. * Cleans given queue of variable changes of such changes that came from
  254. * components that do not exist anymore.
  255. *
  256. * @param variableBurst
  257. */
  258. private void cleanVariableBurst(Vector variableBurst) {
  259. for (int i = 1; i < variableBurst.size(); i += 2) {
  260. String id = (String) variableBurst.get(i);
  261. id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR));
  262. if (!idToPaintable.containsKey(id)) {
  263. // variable owner does not exist anymore
  264. variableBurst.remove(i - 1);
  265. variableBurst.remove(i - 1);
  266. i -= 2;
  267. ApplicationConnection.getConsole().log(
  268. "Removed variable from removed component: " + id);
  269. }
  270. }
  271. }
  272. private void showLoadingIndicator() {
  273. // show initial throbber
  274. if (loadTimer == null) {
  275. loadTimer = new Timer() {
  276. public void run() {
  277. // show initial throbber
  278. if (loadElement == null) {
  279. loadElement = DOM.createDiv();
  280. DOM.setStyleAttribute(loadElement, "position",
  281. "absolute");
  282. DOM.appendChild(view.getElement(), loadElement);
  283. ApplicationConnection.getConsole().log(
  284. "inserting load indicator");
  285. // Position
  286. DOM.setStyleAttribute(loadElement, "top", (view
  287. .getAbsoluteTop() + 6)
  288. + "px");
  289. }
  290. DOM.setElementProperty(loadElement, "className",
  291. "i-loading-indicator");
  292. DOM.setStyleAttribute(loadElement, "display", "block");
  293. final int updatedX = Window.getScrollLeft()
  294. + view.getAbsoluteLeft()
  295. + view.getOffsetWidth()
  296. - DOM.getElementPropertyInt(loadElement,
  297. "offsetWidth") - 5;
  298. DOM.setStyleAttribute(loadElement, "left", updatedX + "px");
  299. final int updatedY = Window.getScrollTop() + 6
  300. + view.getAbsoluteTop();
  301. DOM.setStyleAttribute(loadElement, "top", updatedY + "px");
  302. // Initialize other timers
  303. loadTimer2 = new Timer() {
  304. public void run() {
  305. DOM.setElementProperty(loadElement, "className",
  306. "i-loading-indicator-delay");
  307. }
  308. };
  309. // Second one kicks in at 1500ms
  310. loadTimer2.schedule(1200);
  311. loadTimer3 = new Timer() {
  312. public void run() {
  313. DOM.setElementProperty(loadElement, "className",
  314. "i-loading-indicator-wait");
  315. }
  316. };
  317. // Third one kicks in at 5000ms
  318. loadTimer3.schedule(4700);
  319. }
  320. };
  321. // First one kicks in at 300ms
  322. loadTimer.schedule(300);
  323. }
  324. }
  325. private void hideLoadingIndicator() {
  326. if (loadTimer != null) {
  327. loadTimer.cancel();
  328. if (loadTimer2 != null) {
  329. loadTimer2.cancel();
  330. loadTimer3.cancel();
  331. }
  332. loadTimer = null;
  333. }
  334. if (loadElement != null) {
  335. DOM.removeChild(view.getElement(), loadElement);
  336. loadElement = null;
  337. }
  338. }
  339. private void handleReceivedJSONMessage(Response response) {
  340. final Date start = new Date();
  341. String jsonText = response.getText();
  342. // for(;;);[realjson]
  343. jsonText = jsonText.substring(9, jsonText.length() - 1);
  344. JSONValue json;
  345. try {
  346. json = JSONParser.parse(jsonText);
  347. } catch (final com.google.gwt.json.client.JSONException e) {
  348. endRequest();
  349. console.log(e.getMessage() + " - Original JSON-text:");
  350. console.log(jsonText);
  351. return;
  352. }
  353. // Handle redirect
  354. final JSONObject redirect = (JSONObject) ((JSONObject) json)
  355. .get("redirect");
  356. if (redirect != null) {
  357. final JSONString url = (JSONString) redirect.get("url");
  358. if (url != null) {
  359. console.log("redirecting to " + url.stringValue());
  360. redirect(url.stringValue());
  361. return;
  362. }
  363. }
  364. // Store resources
  365. final JSONObject resources = (JSONObject) ((JSONObject) json)
  366. .get("resources");
  367. for (final Iterator i = resources.keySet().iterator(); i.hasNext();) {
  368. final String key = (String) i.next();
  369. resourcesMap.put(key, ((JSONString) resources.get(key))
  370. .stringValue());
  371. }
  372. // Store locale data
  373. if (((JSONObject) json).containsKey("locales")) {
  374. final JSONArray l = (JSONArray) ((JSONObject) json).get("locales");
  375. for (int i = 0; i < l.size(); i++) {
  376. LocaleService.addLocale((JSONObject) l.get(i));
  377. }
  378. }
  379. JSONObject meta = null;
  380. if (((JSONObject) json).containsKey("meta")) {
  381. meta = ((JSONObject) json).get("meta").isObject();
  382. if (meta.containsKey("repaintAll")) {
  383. view.clear();
  384. idToPaintable.clear();
  385. paintableToId.clear();
  386. }
  387. }
  388. // Process changes
  389. final JSONArray changes = (JSONArray) ((JSONObject) json)
  390. .get("changes");
  391. for (int i = 0; i < changes.size(); i++) {
  392. try {
  393. final UIDL change = new UIDL((JSONArray) changes.get(i));
  394. try {
  395. console.dirUIDL(change);
  396. } catch (final Exception e) {
  397. console.log(e.getMessage());
  398. // TODO: dir doesn't work in any browser although it should
  399. // work (works in hosted mode)
  400. // it partially did at some part but now broken.
  401. }
  402. final UIDL uidl = change.getChildUIDL(0);
  403. final Paintable paintable = getPaintable(uidl.getId());
  404. if (paintable != null) {
  405. paintable.updateFromUIDL(uidl, this);
  406. } else {
  407. if (!uidl.getTag().equals("window")) {
  408. System.out.println("Received update for "
  409. + uidl.getTag()
  410. + ", but there is no such paintable ("
  411. + uidl.getId() + ") rendered.");
  412. } else {
  413. view.updateFromUIDL(uidl, this);
  414. }
  415. }
  416. } catch (final Throwable e) {
  417. e.printStackTrace();
  418. }
  419. }
  420. if (meta != null) {
  421. if (meta.containsKey("focus")) {
  422. final String focusPid = meta.get("focus").isString()
  423. .stringValue();
  424. final Paintable toBeFocused = getPaintable(focusPid);
  425. if (toBeFocused instanceof HasFocus) {
  426. final HasFocus toBeFocusedWidget = (HasFocus) toBeFocused;
  427. toBeFocusedWidget.setFocus(true);
  428. } else if (toBeFocused instanceof Focusable) {
  429. ((Focusable) toBeFocused).focus();
  430. } else {
  431. getConsole().log("Could not focus component");
  432. }
  433. }
  434. if (meta.containsKey("appError")) {
  435. JSONObject error = meta.get("appError").isObject();
  436. JSONValue val = error.get("caption");
  437. String html = "";
  438. if (val.isString() != null) {
  439. html += "<h1>" + val.isString().stringValue() + "</h1>";
  440. }
  441. val = error.get("message");
  442. if (val.isString() != null) {
  443. html += "<p>" + val.isString().stringValue() + "</p>";
  444. }
  445. val = error.get("url");
  446. String url = null;
  447. if (val.isString() != null) {
  448. url = val.isString().stringValue();
  449. }
  450. if (html.length() != 0) {
  451. Notification n = new Notification(1000 * 60 * 45); // 45min
  452. n.addEventListener(new NotificationRedirect(url));
  453. n.show(html, Notification.CENTERED_TOP, "system");
  454. } else {
  455. redirect(url);
  456. }
  457. applicationRunning = false;
  458. }
  459. }
  460. final long prosessingTime = (new Date().getTime()) - start.getTime();
  461. console.log(" Processing time was " + String.valueOf(prosessingTime)
  462. + "ms for " + jsonText.length() + " characters of JSON");
  463. console.log("Referenced paintables: " + idToPaintable.size());
  464. endRequest();
  465. }
  466. // Redirect browser, null reloads current page
  467. private static native void redirect(String url)
  468. /*-{
  469. if (url) {
  470. $wnd.location = url;
  471. } else {
  472. $wnd.location = $wnd.location;
  473. }
  474. }-*/;
  475. public void registerPaintable(String id, Paintable paintable) {
  476. idToPaintable.put(id, paintable);
  477. paintableToId.put(paintable, id);
  478. }
  479. public void unregisterPaintable(Paintable p) {
  480. Object id = paintableToId.get(p);
  481. idToPaintable.remove(id);
  482. paintableToTitle.remove(id);
  483. paintableToId.remove(p);
  484. if (p instanceof HasWidgets) {
  485. unregisterChildPaintables((HasWidgets) p);
  486. }
  487. }
  488. public void unregisterChildPaintables(HasWidgets container) {
  489. final Iterator it = container.iterator();
  490. while (it.hasNext()) {
  491. final Widget w = (Widget) it.next();
  492. if (w instanceof Paintable) {
  493. unregisterPaintable((Paintable) w);
  494. } else if (w instanceof HasWidgets) {
  495. unregisterChildPaintables((HasWidgets) w);
  496. }
  497. }
  498. }
  499. /**
  500. * Returns Paintable element by its id
  501. *
  502. * @param id
  503. * Paintable ID
  504. */
  505. public Paintable getPaintable(String id) {
  506. return (Paintable) idToPaintable.get(id);
  507. }
  508. private void addVariableToQueue(String paintableId, String variableName,
  509. String encodedValue, boolean immediate, char type) {
  510. final String id = paintableId + VAR_FIELD_SEPARATOR + variableName
  511. + VAR_FIELD_SEPARATOR + type;
  512. for (int i = 1; i < pendingVariables.size(); i += 2) {
  513. if ((pendingVariables.get(i)).equals(id)) {
  514. pendingVariables.remove(i - 1);
  515. pendingVariables.remove(i - 1);
  516. break;
  517. }
  518. }
  519. pendingVariables.add(encodedValue);
  520. pendingVariables.add(id);
  521. if (immediate) {
  522. sendPendingVariableChanges();
  523. }
  524. }
  525. /**
  526. * This method sends currently queued variable changes to server. It is
  527. * called when immediate variable update must happen.
  528. *
  529. * To ensure correct order for variable changes (due servers multithreading
  530. * or network), we always wait for active request to be handler before
  531. * sending a new one. If there is an active request, we will put varible
  532. * "burst" to queue that will be purged after current request is handled.
  533. *
  534. */
  535. public void sendPendingVariableChanges() {
  536. if (applicationRunning) {
  537. if (hasActiveRequest()) {
  538. // skip empty queues if there are pending bursts to be sent
  539. if (pendingVariables.size() > 0
  540. || pendingVariableBursts.size() == 0) {
  541. Vector burst = (Vector) pendingVariables.clone();
  542. pendingVariableBursts.add(burst);
  543. }
  544. } else {
  545. buildAndSendVariableBurst(pendingVariables);
  546. }
  547. }
  548. }
  549. private void buildAndSendVariableBurst(Vector pendingVariables) {
  550. final StringBuffer req = new StringBuffer();
  551. for (int i = 0; i < pendingVariables.size(); i++) {
  552. if (i > 0) {
  553. if (i % 2 == 0) {
  554. req.append(VAR_RECORD_SEPARATOR);
  555. } else {
  556. req.append(VAR_FIELD_SEPARATOR);
  557. }
  558. }
  559. req.append(pendingVariables.get(i));
  560. }
  561. pendingVariables.clear();
  562. makeUidlRequest(req.toString(), false);
  563. }
  564. public void updateVariable(String paintableId, String variableName,
  565. String newValue, boolean immediate) {
  566. addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
  567. }
  568. public void updateVariable(String paintableId, String variableName,
  569. int newValue, boolean immediate) {
  570. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  571. 'i');
  572. }
  573. public void updateVariable(String paintableId, String variableName,
  574. long newValue, boolean immediate) {
  575. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  576. 'l');
  577. }
  578. public void updateVariable(String paintableId, String variableName,
  579. float newValue, boolean immediate) {
  580. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  581. 'f');
  582. }
  583. public void updateVariable(String paintableId, String variableName,
  584. double newValue, boolean immediate) {
  585. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  586. 'd');
  587. }
  588. public void updateVariable(String paintableId, String variableName,
  589. boolean newValue, boolean immediate) {
  590. addVariableToQueue(paintableId, variableName, newValue ? "true"
  591. : "false", immediate, 'b');
  592. }
  593. public void updateVariable(String paintableId, String variableName,
  594. Object[] values, boolean immediate) {
  595. final StringBuffer buf = new StringBuffer();
  596. for (int i = 0; i < values.length; i++) {
  597. if (i > 0) {
  598. buf.append(",");
  599. }
  600. buf.append(values[i].toString());
  601. }
  602. addVariableToQueue(paintableId, variableName, buf.toString(),
  603. immediate, 'a');
  604. }
  605. /**
  606. * Update generic component features.
  607. *
  608. * <h2>Selecting correct implementation</h2>
  609. *
  610. * <p>
  611. * The implementation of a component depends on many properties, including
  612. * styles, component features, etc. Sometimes the user changes those
  613. * properties after the component has been created. Calling this method in
  614. * the beginning of your updateFromUIDL -method automatically replaces your
  615. * component with more appropriate if the requested implementation changes.
  616. * </p>
  617. *
  618. * <h2>Caption, icon, error messages and description</h2>
  619. *
  620. * <p>
  621. * Component can delegate management of caption, icon, error messages and
  622. * description to parent layout. This is optional an should be decided by
  623. * component author
  624. * </p>
  625. *
  626. * <h2>Component visibility and disabling</h2>
  627. *
  628. * This method will manage component visibility automatically and if
  629. * component is an instanceof FocusWidget, also handle component disabling
  630. * when needed.
  631. *
  632. * @param component
  633. * Widget to be updated, expected to implement an instance of
  634. * Paintable
  635. * @param uidl
  636. * UIDL to be painted
  637. * @param manageCaption
  638. * True if you want to delegate caption, icon, description
  639. * and error message management to parent.
  640. *
  641. * @return Returns true iff no further painting is needed by caller
  642. */
  643. public boolean updateComponent(Widget component, UIDL uidl,
  644. boolean manageCaption) {
  645. // If the server request that a cached instance should be used, do
  646. // nothing
  647. if (uidl.getBooleanAttribute("cached")) {
  648. return true;
  649. }
  650. // Visibility
  651. boolean visible = !uidl.getBooleanAttribute("invisible");
  652. component.setVisible(visible);
  653. if (!visible) {
  654. return true;
  655. }
  656. // Switch to correct implementation if needed
  657. if (!widgetSet.isCorrectImplementation(component, uidl)) {
  658. final Container parent = Util.getParentLayout(component);
  659. if (parent != null) {
  660. final Widget w = widgetSet.createWidget(uidl);
  661. parent.replaceChildComponent(component, w);
  662. registerPaintable(uidl.getId(), (Paintable) w);
  663. ((Paintable) w).updateFromUIDL(uidl, this);
  664. return true;
  665. }
  666. }
  667. updateComponentSize(component, uidl);
  668. boolean enabled = !uidl.getBooleanAttribute("disabled");
  669. if (component instanceof FocusWidget) {
  670. ((FocusWidget) component).setEnabled(enabled);
  671. }
  672. StringBuffer styleBuf = new StringBuffer();
  673. final String primaryName = component.getStylePrimaryName();
  674. styleBuf.append(primaryName);
  675. // first disabling and read-only status
  676. if (!enabled) {
  677. styleBuf.append(" ");
  678. styleBuf.append("i-disabled");
  679. }
  680. if (uidl.getBooleanAttribute("readonly")) {
  681. styleBuf.append(" ");
  682. styleBuf.append("i-readonly");
  683. }
  684. // add additional styles as css classes, prefixed with component default
  685. // stylename
  686. if (uidl.hasAttribute("style")) {
  687. final String[] styles = uidl.getStringAttribute("style").split(" ");
  688. for (int i = 0; i < styles.length; i++) {
  689. styleBuf.append(" ");
  690. styleBuf.append(primaryName);
  691. styleBuf.append("-");
  692. styleBuf.append(styles[i]);
  693. }
  694. }
  695. // add modified classname to Fields
  696. if (uidl.hasAttribute("modified") && component instanceof Field) {
  697. styleBuf.append(" ");
  698. styleBuf.append(MODIFIED_CLASSNAME);
  699. }
  700. TooltipInfo tooltipInfo = getTitleInfo((Paintable) component);
  701. if (uidl.hasAttribute("description")) {
  702. tooltipInfo.setTitle(uidl.getStringAttribute("description"));
  703. } else {
  704. tooltipInfo.setTitle(null);
  705. }
  706. // add error classname to components w/ error
  707. if (uidl.hasAttribute("error")) {
  708. styleBuf.append(" ");
  709. styleBuf.append(primaryName);
  710. styleBuf.append(ERROR_CLASSNAME_EXT);
  711. tooltipInfo.setErrorUidl(uidl.getErrors());
  712. } else {
  713. tooltipInfo.setErrorUidl(null);
  714. }
  715. // add required style to required components
  716. if (uidl.hasAttribute("required")) {
  717. styleBuf.append(" ");
  718. styleBuf.append(primaryName);
  719. styleBuf.append(REQUIRED_CLASSNAME_EXT);
  720. }
  721. // Styles + disabled & readonly
  722. component.setStyleName(styleBuf.toString());
  723. // Set captions
  724. if (manageCaption) {
  725. final Container parent = Util.getParentLayout(component);
  726. if (parent != null) {
  727. parent.updateCaption((Paintable) component, uidl);
  728. }
  729. }
  730. if (usePaintableIdsInDOM) {
  731. DOM.setElementProperty(component.getElement(), "id", uidl.getId());
  732. }
  733. return false;
  734. }
  735. private void updateComponentSize(Widget component, UIDL uidl) {
  736. String w = uidl.hasAttribute("width") ? uidl
  737. .getStringAttribute("width") : "";
  738. component.setWidth(w);
  739. String h = uidl.hasAttribute("height") ? uidl
  740. .getStringAttribute("height") : "";
  741. component.setHeight(h);
  742. }
  743. /**
  744. * Get either existing or new Paintable for given UIDL.
  745. *
  746. * If corresponding Paintable has been previously painted, return it.
  747. * Otherwise create and register a new Paintable from UIDL. Caller must
  748. * update the returned Paintable from UIDL after it has been connected to
  749. * parent.
  750. *
  751. * @param uidl
  752. * UIDL to create Paintable from.
  753. * @return Either existing or new Paintable corresponding to UIDL.
  754. */
  755. public Paintable getPaintable(UIDL uidl) {
  756. final String id = uidl.getId();
  757. Paintable w = getPaintable(id);
  758. if (w != null) {
  759. return w;
  760. }
  761. w = (Paintable) widgetSet.createWidget(uidl);
  762. registerPaintable(id, w);
  763. return w;
  764. }
  765. public String getResource(String name) {
  766. return (String) resourcesMap.get(name);
  767. }
  768. /**
  769. * Singleton method to get instance of app's context menu.
  770. *
  771. * @return IContextMenu object
  772. */
  773. public ContextMenu getContextMenu() {
  774. if (contextMenu == null) {
  775. contextMenu = new ContextMenu();
  776. if (usePaintableIdsInDOM) {
  777. DOM.setElementProperty(contextMenu.getElement(), "id",
  778. "PID_TOOLKIT_CM");
  779. }
  780. }
  781. return contextMenu;
  782. }
  783. /**
  784. * Translates custom protocols in UIRL URI's to be recognizable by browser.
  785. * All uri's from UIDL should be routed via this method before giving them
  786. * to browser due URI's in UIDL may contain custom protocols like theme://.
  787. *
  788. * @param toolkitUri
  789. * toolkit URI from uidl
  790. * @return translated URI ready for browser
  791. */
  792. public String translateToolkitUri(String toolkitUri) {
  793. if (toolkitUri == null) {
  794. return null;
  795. }
  796. if (toolkitUri.startsWith("theme://")) {
  797. final String themeUri = configuration.getThemeUri();
  798. if (themeUri == null) {
  799. console
  800. .error("Theme not set: ThemeResource will not be found. ("
  801. + toolkitUri + ")");
  802. }
  803. toolkitUri = themeUri + toolkitUri.substring(7);
  804. }
  805. return toolkitUri;
  806. }
  807. public String getThemeUri() {
  808. return configuration.getThemeUri();
  809. }
  810. /**
  811. * Listens for Notification hide event, and redirects. Used for system
  812. * messages, such as session expired.
  813. *
  814. */
  815. private class NotificationRedirect implements Notification.EventListener {
  816. String url;
  817. NotificationRedirect(String url) {
  818. this.url = url;
  819. }
  820. public void notificationHidden(HideEvent event) {
  821. redirect(url);
  822. }
  823. }
  824. /* Extended title handling */
  825. /**
  826. * Data showed in tooltips are stored centrilized as it may be needed in
  827. * varios place: caption, layouts, and in owner components themselves.
  828. *
  829. * Updating TooltipInfo is done in updateComponent method.
  830. *
  831. */
  832. public TooltipInfo getTitleInfo(Paintable titleOwner) {
  833. TooltipInfo info = (TooltipInfo) paintableToTitle.get(titleOwner);
  834. if (info == null) {
  835. info = new TooltipInfo();
  836. paintableToTitle.put(titleOwner, info);
  837. }
  838. return info;
  839. }
  840. private final Tooltip tooltip = new Tooltip(this);
  841. /**
  842. * Component may want to delegate Tooltip handling to client. Layouts add
  843. * Tooltip (description, errors) to caption, but some components may want
  844. * them to appear one other elements too.
  845. *
  846. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  847. *
  848. * @param event
  849. * @param owner
  850. */
  851. public void handleTooltipEvent(Event event, Paintable owner) {
  852. tooltip.handleTooltipEvent(event, owner);
  853. }
  854. /**
  855. * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
  856. *
  857. * @param el
  858. * the IMG element to fix
  859. */
  860. public void addPngFix(Element el) {
  861. BrowserInfo b = BrowserInfo.get();
  862. if (b.isIE6()) {
  863. Util.addPngFix(el, getThemeUri()
  864. + "/../default/common/img/blank.gif");
  865. }
  866. }
  867. }