]> git.argeo.org Git - gpl/argeo-suite.git/blob - swt/js/SwtBrowserJsPart.java
Prepare next development cycle
[gpl/argeo-suite.git] / swt / js / SwtBrowserJsPart.java
1 package org.argeo.app.swt.js;
2
3 import java.net.URI;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.Locale;
7 import java.util.concurrent.CompletableFuture;
8 import java.util.concurrent.CompletionStage;
9 import java.util.function.Function;
10
11 import org.argeo.api.cms.CmsLog;
12 import org.argeo.api.cms.ux.CmsView;
13 import org.argeo.app.ux.js.JsClient;
14 import org.argeo.cms.swt.CmsSwtUtils;
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.browser.Browser;
17 import org.eclipse.swt.browser.BrowserFunction;
18 import org.eclipse.swt.browser.ProgressEvent;
19 import org.eclipse.swt.browser.ProgressListener;
20 import org.eclipse.swt.layout.GridData;
21 import org.eclipse.swt.layout.GridLayout;
22 import org.eclipse.swt.widgets.Composite;
23 import org.eclipse.swt.widgets.Control;
24 import org.eclipse.swt.widgets.Display;
25
26 /**
27 * A part using a {@link Browser} and remote JavaScript components on the client
28 * side.
29 */
30 public class SwtBrowserJsPart implements JsClient {
31 private final static CmsLog log = CmsLog.getLog(SwtBrowserJsPart.class);
32
33 private final static String GLOBAL_THIS_ = "globalThis.";
34
35 private final Browser browser;
36 private final CompletableFuture<Boolean> readyStage = new CompletableFuture<>();
37
38 /**
39 * Tasks that were requested before the context was ready. Typically
40 * configuration methods on the part while the user interfaces is being build.
41 */
42 private List<PreReadyToDo> preReadyToDos = new ArrayList<>();
43
44 public SwtBrowserJsPart(Composite parent, int style, String url) {
45 CmsView cmsView = CmsSwtUtils.getCmsView(parent);
46 this.browser = new Browser(parent, 0);
47 if (parent.getLayout() instanceof GridLayout)
48 browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
49 // TODO other layouts
50
51 URI u = cmsView.toBackendUri(url);
52 browser.setUrl(u.toString());
53 browser.addProgressListener(new ProgressListener() {
54 static final long serialVersionUID = 1L;
55
56 @Override
57 public void completed(ProgressEvent event) {
58 try {
59 init();
60 loadExtensions();
61 // execute todos in order
62 for (PreReadyToDo toDo : preReadyToDos) {
63 toDo.run();
64 }
65 preReadyToDos.clear();
66 readyStage.complete(true);
67 } catch (Exception e) {
68 log.error("Cannot initialise " + url + " in browser", e);
69 readyStage.complete(false);
70 }
71 }
72
73 @Override
74 public void changed(ProgressEvent event) {
75 }
76 });
77 }
78
79 /*
80 * LIFECYCLE
81 */
82
83 /**
84 * Called when the page has been loaded, typically in order to initialise
85 * JavaScript objects. One MUST use {@link #doExecute(String, Object...)} in
86 * order to do so, since the context is not yet considered ready and calls to
87 * {@link #evaluate(String, Object...)} will block.
88 */
89 protected void init() {
90 }
91
92 /**
93 * To be overridden with calls to {@link #loadExtension(String)}.
94 */
95 protected void loadExtensions() {
96
97 }
98
99 protected void loadExtension(String url) {
100 URI u = CmsSwtUtils.getCmsView(getControl()).toBackendUri(url);
101 browser.evaluate(String.format(Locale.ROOT, "import('%s')", u.toString()));
102 }
103
104 public CompletionStage<Boolean> getReadyStage() {
105 return readyStage.minimalCompletionStage();
106 }
107
108 /*
109 * JAVASCRIPT ACCESS
110 */
111
112 @Override
113 public Object evaluate(String js, Object... args) {
114 assert browser.getDisplay().equals(Display.findDisplay(Thread.currentThread())) : "Not the proper UI thread.";
115 if (!readyStage.isDone())
116 throw new IllegalStateException("Methods returning a result can only be called after UI initialisation.");
117 // wait for the context to be ready
118 // boolean ready = readyStage.join();
119 // if (!ready)
120 // throw new IllegalStateException("Component is not initialised.");
121 Object result = browser.evaluate(String.format(Locale.ROOT, js, args));
122 return result;
123 }
124
125 @Override
126 public void execute(String js, Object... args) {
127 String jsToExecute = String.format(Locale.ROOT, js, args);
128 if (readyStage.isDone()) {
129 boolean success = browser.execute(jsToExecute);
130 if (!success)
131 throw new RuntimeException("JavaScript execution failed.");
132 } else {
133 PreReadyToDo toDo = new PreReadyToDo(jsToExecute);
134 preReadyToDos.add(toDo);
135 }
136 }
137
138 @Override
139 public String createJsFunction(String name, Function<Object[], Object> toDo) {
140 // browser functions must be directly on window (RAP specific)
141 new BrowserFunction(browser, name) {
142
143 @Override
144 public Object function(Object[] arguments) {
145 Object result = toDo.apply(arguments);
146 return result;
147 }
148
149 };
150 return "window." + name;
151 }
152
153 /**
154 * Directly executes, even if {@link #getReadyStage()} is not completed. Except
155 * in initialisation, {@link #evaluate(String, Object...)} should be used
156 * instead.
157 */
158 protected void doExecute(String js, Object... args) {
159 browser.execute(String.format(Locale.ROOT, js, args));
160 }
161
162 @Override
163 public String getJsVarName(String name) {
164 return GLOBAL_THIS_ + name;
165 }
166
167 class PreReadyToDo implements Runnable {
168 private String js;
169
170 public PreReadyToDo(String js) {
171 this.js = js;
172 }
173
174 @Override
175 public void run() {
176 boolean success = browser.execute(js);
177 if (!success && log.isTraceEnabled())
178 log.error("Pre-ready JavaScript failed: " + js);
179 }
180 }
181
182 /*
183 * ACCESSORS
184 */
185
186 public Control getControl() {
187 return browser;
188 }
189
190 }