X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=swt%2Forg.argeo.app.swt%2Fsrc%2Forg%2Fargeo%2Fapp%2Fswt%2Fjs%2FSwtBrowserJsPart.java;h=6782f5dd22a03c6d371d914340cd226805b371f8;hb=aeb4037e6c1ecbdc58a0f5c7377248f0c13b2b9d;hp=fac099ffe36bfa65bb746509bdd5874d62071815;hpb=f9b3af44af6897b286de0674bc9a919c689ff64e;p=gpl%2Fargeo-suite.git diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java index fac099f..6782f5d 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java @@ -1,13 +1,17 @@ package org.argeo.app.swt.js; -import java.util.Arrays; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; -import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.app.ux.js.JsClient; +import org.argeo.cms.swt.CmsSwtUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.BrowserFunction; @@ -17,26 +21,35 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; /** * A part using a {@link Browser} and remote JavaScript components on the client * side. */ -public class SwtBrowserJsPart { +public class SwtBrowserJsPart implements JsClient { private final static CmsLog log = CmsLog.getLog(SwtBrowserJsPart.class); private final static String GLOBAL_THIS_ = "globalThis."; private final Browser browser; - private final CompletableFuture pageLoaded = new CompletableFuture<>(); + private final CompletableFuture readyStage = new CompletableFuture<>(); + + /** + * Tasks that were requested before the context was ready. Typically + * configuration methods on the part while the user interfaces is being build. + */ + private List preReadyToDos = new ArrayList<>(); public SwtBrowserJsPart(Composite parent, int style, String url) { + CmsView cmsView = CmsSwtUtils.getCmsView(parent); this.browser = new Browser(parent, 0); if (parent.getLayout() instanceof GridLayout) browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // TODO other layouts - browser.setUrl(url); + URI u = cmsView.toBackendUri(url); + browser.setUrl(u.toString()); browser.addProgressListener(new ProgressListener() { static final long serialVersionUID = 1L; @@ -45,10 +58,15 @@ public class SwtBrowserJsPart { try { init(); loadExtensions(); - pageLoaded.complete(true); + // execute todos in order + for (PreReadyToDo toDo : preReadyToDos) { + toDo.run(); + } + preReadyToDos.clear(); + readyStage.complete(true); } catch (Exception e) { log.error("Cannot initialise " + url + " in browser", e); - pageLoaded.complete(false); + readyStage.complete(false); } } @@ -62,55 +80,63 @@ public class SwtBrowserJsPart { * LIFECYCLE */ - /** Called when the page has been loaded. */ + /** + * Called when the page has been loaded, typically in order to initialise + * JavaScript objects. One MUST use {@link #doExecute(String, Object...)} in + * order to do so, since the context is not yet considered ready and calls to + * {@link #evaluate(String, Object...)} will block. + */ protected void init() { } - /** To be overridden with calls to {@link #loadExtension(String)}. */ + /** + * To be overridden with calls to {@link #loadExtension(String)}. + */ protected void loadExtensions() { } protected void loadExtension(String url) { -// String js = """ -// var script = document.createElement("script"); -// script.src = '%s'; -// document.head.appendChild(script); -// """; -// browser.evaluate(String.format(Locale.ROOT, js, url)); - browser.evaluate(String.format(Locale.ROOT, "import('%s')", url)); + URI u = CmsSwtUtils.getCmsView(getControl()).toBackendUri(url); + browser.evaluate(String.format(Locale.ROOT, "import('%s')", u.toString())); } - protected CompletionStage getReadyStage() { - return pageLoaded.minimalCompletionStage(); + public CompletionStage getReadyStage() { + return readyStage.minimalCompletionStage(); } /* * JAVASCRIPT ACCESS */ - /** - * Execute this JavaScript on the client side after making sure that the page - * has been loaded and the map object has been created. - * - * @param js the JavaScript code, possibly formatted according to - * {@link String#format}, with {@link Locale#ROOT} as locale (for - * stability of decimal separator, as expected by JavaScript. - * @param args the optional arguments of - * {@link String#format(String, Object...)} - */ - protected CompletionStage evaluate(String js, Object... args) { - CompletableFuture res = pageLoaded.thenApply((ready) -> { - if (!ready) - throw new IllegalStateException("Component is not initialised."); - Object result = browser.evaluate(String.format(Locale.ROOT, js, args)); - return result; - }); - return res.minimalCompletionStage(); + @Override + public Object evaluate(String js, Object... args) { + assert browser.getDisplay().equals(Display.findDisplay(Thread.currentThread())) : "Not the proper UI thread."; + if (!readyStage.isDone()) + throw new IllegalStateException("Methods returning a result can only be called after UI initialisation."); + if (browser.isDisposed()) + return null; + Object result = browser.evaluate(String.format(Locale.ROOT, js, args)); + return result; + } + + @Override + public void execute(String js, Object... args) { + String jsToExecute = String.format(Locale.ROOT, js, args); + if (readyStage.isDone()) { + if (browser.isDisposed()) + return; + boolean success = browser.execute(jsToExecute); + if (!success) + throw new RuntimeException("JavaScript execution failed."); + } else { + PreReadyToDo toDo = new PreReadyToDo(jsToExecute); + preReadyToDos.add(toDo); + } } - /** @return the globally usable function name. */ - protected String createJsFunction(String name, Function toDo) { + @Override + public String createJsFunction(String name, Function toDo) { // browser functions must be directly on window (RAP specific) new BrowserFunction(browser, name) { @@ -124,52 +150,37 @@ public class SwtBrowserJsPart { return "window." + name; } - /** Directly executes */ + /** + * Directly executes, even if {@link #getReadyStage()} is not completed. Except + * in initialisation, {@link #evaluate(String, Object...)} should be used + * instead. + */ protected void doExecute(String js, Object... args) { + if (browser.isDisposed()) + return; browser.execute(String.format(Locale.ROOT, js, args)); } - protected CompletionStage callMethod(String jsObject, String methodCall, Object... args) { - return evaluate(jsObject + '.' + methodCall, args); - } - - protected String getJsVarName(String name) { + @Override + public String getJsVarName(String name) { return GLOBAL_THIS_ + name; } - protected static String toJsArray(int... arr) { - return Arrays.toString(arr); - } - - protected static String toJsArray(long... arr) { - return Arrays.toString(arr); - } - - protected static String toJsArray(double... arr) { - return Arrays.toString(arr); - } - - protected static String toJsArray(String... arr) { - return toJsArray((Object[]) arr); - } + class PreReadyToDo implements Runnable { + private String js; - protected static String toJsArray(Object... arr) { - StringJoiner sj = new StringJoiner(",", "[", "]"); - for (Object o : arr) { - sj.add(toJsValue(o)); + public PreReadyToDo(String js) { + this.js = js; } - return sj.toString(); - } - protected static String toJsValue(Object o) { - if (o instanceof CharSequence) - return '\"' + o.toString() + '\"'; - else if (o instanceof Number) - return o.toString(); - else if (o instanceof Boolean) - return o.toString(); - else - return '\"' + o.toString() + '\"'; + @Override + public void run() { + if (browser.isDisposed()) + return; + boolean success = browser.execute(js); + if (!success && log.isTraceEnabled()) + log.error("Pre-ready JavaScript failed: " + js); + } } /*