wagtail / telepath

A library for exchanging data between Python and JavaScript
https://wagtail.github.io/telepath/
BSD 3-Clause "New" or "Revised" License
143 stars 4 forks source link

Suggestion: allow easier extending of registered classes #12

Open enzedonline opened 1 year ago

enzedonline commented 1 year ago

I came across this writing a custom chooser widget. All I needed to do for the adapter was update the widget class in the chooser factory which should be an easy thing by extending the ChooserFactory class. This would keep it in sync with the Wagtail current code and require minimum maintennance and coding.

I couldn't find any way to access the underlying class which made me think it would be really beneficial to expose classes as part of the telepath.register() process.

For example:

window.telepath.register('wagtail.snippets.widgets.SnippetChooser', SnippetChooserFactory,); could expose the class via window.telepath.wagtail.snippets.widgets.SnippetChooser

then the custom ChooserFactory can just be declared as

class SnippetPreviewChooserFactory extends window.telepath.wagtail.snippets.widgets.SnippetChooser {
    widgetClass = window.SnippetPreviewChooser;
}
window.telepath.register('core.widgets.choosers.SnippetPreviewChooser', SnippetPreviewChooserFactory);

Doesn't require source code or hacking the minified code to expose the class.

I've ended up copying the minified code, working out which is the relevant class and exposing it in there. Ugly and overly complicated solution that requires continuous updating to be version compliant now. Perhaps I've just missed a cleaner, better way to do this.

enzedonline commented 1 year ago

Ok, after all that, I found the window.telepath.constructors dictionary - this info could do with being added to the JavaScript API docs.

I could use it as follows:

const waitForObject = (object) => {
    return Promise.resolve(object).then((resolvedObject) => {
        if (!resolvedObject) {
            return new Promise((resolve) => {
                window.addEventListener('load', () => {
                    resolve(resolvedObject);
                });
            });
        }
        return resolvedObject;
    });
};

const declareSnippetPreviewChooserFactory = async () => {
    const SnippetChooserFactory = window.telepath.constructors['wagtail.snippets.widgets.SnippetChooser'];
    await waitForObject(SnippetChooserFactory);

    class SnippetPreviewChooserFactory extends SnippetChooserFactory {
        widgetClass = window.SnippetPreviewChooser;
    }

    window.telepath.register('core.widgets.choosers.SnippetPreviewChooser', SnippetPreviewChooserFactory);
};

declareSnippetPreviewChooserFactory();

I wonder if this could be simplified using a mixin approach and an 'extends' optional parameter in the telepath.register method, so my code above would just be something along the lines of:

class SnippetPreviewChooserFactory {
    widgetClass = window.SnippetPreviewChooser;
}

window.telepath.register(
    'core.widgets.choosers.SnippetPreviewChooser', 
    SnippetPreviewChooserFactory,
    { extends: 'wagtail.snippets.widgets.SnippetChooser'}
);

This would take care of the async code waiing for the window.telepath.constructors object to finish loading.