Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for javascript classes inside @JSBody tags #909

Open
shannah opened this issue Apr 18, 2024 · 2 comments
Open

Support for javascript classes inside @JSBody tags #909

shannah opened this issue Apr 18, 2024 · 2 comments

Comments

@shannah
Copy link
Sponsor Contributor

shannah commented Apr 18, 2024

The following example prints warning messages to the console on compile, but the app works. I think it is related to an attempt to parse the Javascript class.

package ca.weblite.teavm.webcomponent;

import ca.weblite.teavm.delegate.WebComponentDelegate;
import ca.weblite.teavm.factory.PojoFactory;
import ca.weblite.teavm.factory.WebComponentFactory;
import ca.weblite.teavm.factory.WebComponentDelegateFactory;
import ca.weblite.teavm.jso.HTMLElement;
import org.teavm.jso.JSBody;

import java.util.HashMap;
import java.util.Map;

public class WebComponentRegistry {
    private final Map<String, WebComponentFactory> components = new HashMap<>();

    private PojoFactory pojoFactory;

    private static WebComponentRegistry instance;

    public static WebComponentRegistry getInstance() {
        if (instance == null) {
            instance = new WebComponentRegistry();
        }
        return instance;
    }

    public void setPojoFactory(PojoFactory pojoFactory) {
        this.pojoFactory = pojoFactory;
    }

    public void register(String name, WebComponentFactory factory) {
        components.put(name, factory);
        registerNative(name, new WebComponentDelegateFactory() {
            @Override
            public WebComponentDelegate create(String elementType) {
                WebComponent component = factory.create();
                return new WebComponentDelegate() {
                    @Override
                    public void constructorCallback(HTMLElement self) {
                        component.constructorCallback(self);
                    }

                    @Override
                    public void connectedCallback(HTMLElement self) {
                        component.connectedCallback(self);
                    }

                    @Override
                    public void disconnectedCallback(HTMLElement self) {
                        component.disconnectedCallback(self);
                    }

                    @Override
                    public void adoptedCallback(HTMLElement self) {
                        component.adoptedCallback(self);
                    }

                    @Override
                    public void attributeChangedCallback(HTMLElement self, String name, String oldValue, String newValue) {
                        component.attributeChangedCallback(self, name, oldValue, newValue);
                    }
                };
            }
        }, factory.create().getObservedProperties());
    }

    public void register(String name, Class<? extends WebComponent> componentClass) {
        register(name, () -> {
            try {
                if (pojoFactory != null) {
                    return pojoFactory.create(componentClass);
                }
                return componentClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }

    public WebComponent create(String name) {
        return components.get(name).create();
    }

    @JSBody(params = {"elementName", "factory", "observedAttributes"}, script = """
            class MyCustomElement extends HTMLElement {
                #delegate;
                static get observedAttributes() {
                    return observedAttributes;
                }
                constructor() {
                    super();
                    this.#delegate = factory.create(elementName);
                    this.#delegate.constructorCallback(this);
                }
               
                connectedCallback() {
                    this.#delegate.connectedCallback(this);
                }
               
                disconnectedCallback() {
                    this.#delegate.disconnectedCallback(this);
                }
               
                adoptedCallback() {
                    this.#delegate.adoptedCallback(this);
                }
               
                attributeChangedCallback(name, oldValue, newValue) {
                    this.#delegate.attributeChangedCallback(this, name, oldValue, newValue);
                }
               
            }
            customElements.define(elementName, MyCustomElement);
                                                            
            """)
    private native static void registerNative(
            String elementName,
            WebComponentDelegateFactory factory,
            String[] observedAttributes
    );
}

The warnings I receive are:

WARNING: Error in @JSBody script line 0, char 33: missing ; before statement
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 0, char 41: missing ; before statement
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 0, char 53: missing ; before statement
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 0, char 54: missing ; before statement
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 1, char 5: illegal character: #
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 1, char 5: missing } in compound statement
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
WARNING: Error in @JSBody script line 1, char 5: missing } after function body
    at ca.weblite.teavm.webcomponent.WebComponentRegistry.registerNative
@konsoletyper
Copy link
Owner

This is because TeaVM uses Rhino to parse JS. Rhino does not fully conform ES2015, not even close. Writing own JS parser is a lot of work, patching Rhino is impossible due to licensing issues. Anyway, for me it seems strange to have large pieces of code inside @JSBody. Would not just subclassing HTMLElement in Java side enough? AFAIR, there are only few issues preventing that: properly define class's prototype via Reflection.construct and ability to take class as a value (or rather passing Class to native JS methods). What do you think?

@shannah
Copy link
Sponsor Contributor Author

shannah commented Apr 20, 2024

Would not just subclassing HTMLElement in Java side enough?

I'm creating custom elements by following the structure described in this MDN doc.

It is actually working really nicely. Just these warning are unsettling, so I thought I'd post them.

I first attempted to translate these structure into the old function/prototype style classes, but wasn't able to get it to work.

What kinds of things are likely to break when warnings like this occur?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants