]> git.argeo.org Git - gpl/argeo-suite.git/blob - js/SwtBrowserJsPart.java
Prepare next development cycle
[gpl/argeo-suite.git] / 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 if (browser.isDisposed())
118 return null;
119 Object result = browser.evaluate(String.format(Locale.ROOT, js, args));
120 return result;
121 }
122
123 @Override
124 public void execute(String js, Object... args) {
125 String jsToExecute = String.format(Locale.ROOT, js, args);
126 if (readyStage.isDone()) {
127 if (browser.isDisposed())
128 return;
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 if (browser.isDisposed())
160 return;
161 browser.execute(String.format(Locale.ROOT, js, args));
162 }
163
164 @Override
165 public String getJsVarName(String name) {
166 return GLOBAL_THIS_ + name;
167 }
168
169 class PreReadyToDo implements Runnable {
170 private String js;
171
172 public PreReadyToDo(String js) {
173 this.js = js;
174 }
175
176 @Override
177 public void run() {
178 if (browser.isDisposed())
179 return;
180 boolean success = browser.execute(js);
181 if (!success && log.isTraceEnabled())
182 log.error("Pre-ready JavaScript failed: " + js);
183 }
184 }
185
186 /*
187 * ACCESSORS
188 */
189
190 public Control getControl() {
191 return browser;
192 }
193
194 }