diff --git a/src/main/java/com/osiris/desku/App.java b/src/main/java/com/osiris/desku/App.java
index 15974f0..efb1048 100644
--- a/src/main/java/com/osiris/desku/App.java
+++ b/src/main/java/com/osiris/desku/App.java
@@ -74,6 +74,14 @@ public class App {
public static File styles;
public static File javascript;
+ /**
+ * Make sure {@link LoggerParams} has debugging enabled for this to work.
+ * If this is enabled the debug output will include a much more detailed output
+ * related to the html that is added, the attributes being set etc.
+ * This also adds similar logging to the browsers console output.
+ */
+ public static boolean isInDepthDebugging = false;
+
static {
updateDirs();
}
@@ -116,7 +124,7 @@ public static void updateDirs(){
public static class LoggerParams{
public String name = "Logger";
- public boolean debug = true;
+ public boolean debug = false;
public File logsDir;
public File latestLogFile;
public File mirrorOutFile;
@@ -160,12 +168,12 @@ public static void init(UIManager uiManager, LoggerParams loggerParams) {
AL.start(loggerParams.name, loggerParams.debug, loggerParams.latestLogFile, loggerParams.ansi, loggerParams.forceAnsi);
AL.mirrorSystemStreams(loggerParams.mirrorOutFile, loggerParams.mirrorErrFile);
}
- AL.info("Starting application...");
- AL.info("workingDir = " + workingDir);
- AL.info("tempDir = " + tempDir);
- AL.info("userDir = " + userDir);
- AL.info("htmlDir = " + htmlDir);
- AL.info("Java = " + System.getProperty("java.vendor") + " " + System.getProperty("java.version"));
+ AL.debug(App.class, "Starting application...");
+ AL.debug(App.class, "workingDir = " + workingDir);
+ AL.debug(App.class, "tempDir = " + tempDir);
+ AL.debug(App.class, "userDir = " + userDir);
+ AL.debug(App.class, "htmlDir = " + htmlDir);
+ AL.debug(App.class, "Java = " + System.getProperty("java.vendor") + " " + System.getProperty("java.version"));
// Clear the directory at each app startup, since
// its aim is to provide a cache to load pages faster
@@ -187,7 +195,7 @@ public static void init(UIManager uiManager, LoggerParams loggerParams) {
appendToGlobalCSS(getCSS(Bootstrap.class));
appendToGlobalJS(getJS(Bootstrap.class));
- AL.info("Started application successfully!");
+ AL.debug(App.class, "Started application successfully!");
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/src/main/java/com/osiris/desku/ui/Component.java b/src/main/java/com/osiris/desku/ui/Component.java
index e2d7022..0ad6f2a 100644
--- a/src/main/java/com/osiris/desku/ui/Component.java
+++ b/src/main/java/com/osiris/desku/ui/Component.java
@@ -22,6 +22,8 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -61,6 +63,11 @@ public class Component, VALUE> {
*
*/
public final int id = idCounter.getAndIncrement();
+ /**
+ * List of children. Normally it's read-only.
+ * Thus do not modfify directly and use methods like {@link #add(Component[])} or {@link #remove(Component[])}
+ * instead, to ensure the changes are also visible in the browser.
+ */
public final CopyOnWriteArrayList children = new CopyOnWriteArrayList<>();
/**
* Executed when a child was added on the Java side.
@@ -149,17 +156,17 @@ public boolean isAttached(){
child.element.remove();
child.update();
- // Update UI
- if (isAttached && !ui.isLoading()){
- ui.executeJavaScriptSafely(ui.jsGetComp("comp", id) +
- ui.jsGetComp("childComp", child.id) +
- "comp.removeChild(childComp);\n",
- "internal", 0);
- }
-
- child.isAttached = false;
- onChildRemove.execute(child);
}
+ UI.PendingAppend.removeFromPendingAppends(ui, child);
+
+ // Update UI always
+ // Child might have already been removed in Java but not in JS
+ executeJS(ui.jsGetComp("childComp", child.id) +
+ "comp.removeChild(childComp);\n" +
+ (App.isInDepthDebugging ? "console.log('parent comp:', comp); console.log('➡️❌ removed childComp:', childComp); \n" : ""));
+
+ child.isAttached = false;
+ onChildRemove.execute(child);
};
public Consumer _removeSelf = self -> {
UI ui = UI.get(); // Necessary for updating the actual UI via JavaScript
@@ -167,13 +174,12 @@ public boolean isAttached(){
self.element.remove();
}
self.update();
+ UI.PendingAppend.removeFromPendingAppends(ui, self);
// Update UI
- if (isAttached && !ui.isLoading()){
- ui.executeJavaScriptSafely(ui.jsGetComp("comp", self.id) +
- "comp.parentNode.removeChild(comp);\n",
- "internal", 0);
- }
+ executeJS(ui.jsGetComp("comp", self.id) +
+ "comp.parentNode.removeChild(comp);\n"+
+ (App.isInDepthDebugging ? "console.log('parent comp:', comp.parentNode); console.log('➡️❌ removed self:', comp); \n" : ""));
self.isAttached = false;
//onChildRemove.execute(self);
@@ -191,6 +197,8 @@ public boolean isAttached(){
e.childComp.update();
element.insertChildren(iOtherComp, e.childComp.element);
} else if (e.isReplace) {
+ // childComp is the new component to be added
+ // and otherChildComp is the one that gets removed/replaced
int iOtherComp = children.indexOf(e.otherChildComp);
children.set(iOtherComp, e.childComp);
e.childComp.update();
@@ -263,7 +271,9 @@ public boolean isAttached(){
executeJS("comp.setAttribute(`" + key
+ "`, `" + value + "`);\n" +
"comp[`"+key+"`] = `"+value+"`\n"); // Change UI representation
- //System.out.println(key+" = "+ value);
+ if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" _attributeChange javascript -> "+key+" = "+ value);
+ } else {
+ if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" _attributeChange Java -> "+key+" = "+ value);
}
} else {// Remove attribute
@@ -334,11 +344,15 @@ public Component(@UnknownNullability VALUE value, @NotNull Class valueCla
* that was set in the constructor.
*/
public THIS getValue(Consumer<@NotNull VALUE> v) {
+
UI ui = UI.get();
- if(!isAttached || ui == null || ui.isLoading()) // Since never attached once, user didn't have a chance to change the value, thus return internal directly
+ if(!isAttached || ui == null || ui.isLoading()) { // Since never attached once, user didn't have a chance to change the value, thus return internal directly
v.accept(internalValue);
+ if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" getValue() returns internalValue = "+ internalValue);
+ }
else
gatr("value", valueAsString -> {
+ if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" getValue() returns from javascript value attribute = "+valueAsString);
VALUE value = Value.stringToVal(valueAsString, this);
v.accept(value);
});
@@ -359,6 +373,8 @@ public THIS setValue(@Nullable VALUE v) {
else newValJsonSafe = "\""+Value.escapeForJSON(newVal)+"\""; // json object or other primitive
String message = "{\"newValue\": "+newValJsonSafe+"}";
+ if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" setValue() message -> "+message);
+
JsonObject jsonEl = JsonFile.parser.fromJson(message, JsonObject.class);
ValueChangeEvent event = new ValueChangeEvent<>(message, jsonEl, _this, v, this.internalValue, true);
this.internalValue = v;
@@ -411,8 +427,16 @@ public boolean isValuesEqual(VALUE val1, VALUE val2){
/**
* Executes the provided JavaScript code now, or later
* if this component is not attached yet.
+ *
+ * Does nothing and directly returns if the UI is null or still loading, since
+ * we assume that your provided JS code is strongly related to this component and that
+ * it does a manipulation that was done in Java code before (like for example changing its HTML, a style or attribute)
+ * to prevent duplicate operations.
+ * If that is not the case use {@link UI#executeJavaScriptSafely(String, String, int)} instead.
+ *
* Your code will be encapsulated in a try/catch block and errors logged to
* the clients JavaScript console.
+ *
* A reference of this component will be added before your code, thus you can access
* this component via the "comp" variable in your provided JavaScript code.
*/
@@ -424,6 +448,7 @@ public THIS executeJS(String code){
* @see #executeJS(String)
*/
public THIS executeJS(UI ui, String code){
+ if(ui == null || ui.isLoading()) return _this;
if(isAttached){
ui.executeJavaScriptSafely(
"try{"+
@@ -475,14 +500,17 @@ public THIS now(Consumer code) {
}
/**
- * Executes the provided code asynchronously in a new thread.
+ * Executes the provided code asynchronously in a thread from {@link App#executor} and returns directly.
* This function needs to be run inside UI context
* since it executes {@link UI#get()}, otherwise {@link NullPointerException} is thrown.
*
* Note that your code-block will have access to the current UI,
* which means that you can add/remove/change UI components without issues.
* This also means that you will have to handle Thread-safety yourself
- * when doing things to the same component from multiple threads at the same time.
+ * when doing things to the same component from multiple threads at the same time.
+ *
+ * Also note that your code is not allowed to run forever/block, since
+ * sometimes added components get added (in the browser) only after your code was run, see {@link UI#pendingAppends} and {@link UI#access(Runnable)}.
*
* @param code the code to be executed asynchronously, contains this component as parameter.
*/
@@ -837,6 +865,26 @@ public THIS visible(boolean b) {
return _this;
}
+ /**
+ * Requires that the children have absolute width/height.
+ * If this component has width/height set, those are used, otherwise 100% as width/height is used.
+ */
+ public THIS scrollable(boolean b) {
+ String width = style.get("width").isEmpty() ? "100%" : style.get("width");
+ String height = style.get("height").isEmpty() ? "100%" : style.get("height");
+ scrollable(b, width, height, "", "");
+ return _this;
+ }
+
+ /**
+ * Requires that the children have absolute width/height.
+ */
+ public THIS scrollable(boolean b, String width, String height) {
+ scrollable(b, width, height, "", "");
+ return _this;
+ }
+
+
/**
* Makes this component scrollable.
* Note that you must also set the width and height for this to work,
diff --git a/src/main/java/com/osiris/desku/ui/HTTPServer.java b/src/main/java/com/osiris/desku/ui/HTTPServer.java
index 8e8bcfb..c41c0e1 100644
--- a/src/main/java/com/osiris/desku/ui/HTTPServer.java
+++ b/src/main/java/com/osiris/desku/ui/HTTPServer.java
@@ -57,7 +57,7 @@ public Response serve(IHTTPSession session) {
return sendHTMLString(msg + "