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