How can I create widgets with dynamic id's?

I have a configuration that defines different widgets that need to be created dynamically with different ID’s:

{
    doc-viewer-widgets: [
        {
            "id": "activity-guide",
            "icon": "activity-guide-icon",
            "url": "https://example.com/activity-guide/beginner"
        }, 
        {
            "id": "language-reference-guides",
            "icon": "reference-guides-icon",
            "url": "https://example.com/reference-guides"
        }
    ]
}

I was able to create view contribution that can load this config and handle the opening/toggling of widgets. However, I’m not sure how to instantiate the widget itself.

Normally, you call the WidgetManager.getOrCreateWidget(id), but the widgetManager uses widgets registered through inversify. Since my widgets are dynamic, I’m not sure how to bind them in the container module. Can anyone help me understand the approach I should be taking here?

Hi!

I don’t have a clear answer for you (and I may be misunderstanding your use case). But could you maybe bind your widgets like this, for example:

bind(WidgetFactory).toDynamicValue(({ container }) => ({
    id: 'activity-guide',
    createWidget: () => container.get(ActivityGuideWidget)
})).inSingletonScope();

bind(WidgetFactory).toDynamicValue(({ container }) => ({
    id: 'language-referece-guides',
    createWidget: () => container.get(LanguageReferenceGuidesWidget)
})).inSingletonScope();

And then try to get them using WidgetManager.getOrCreateWidget(id)?

See also this snippet, maybe it will help you (it’s about binding the navigator view): https://github.com/eclipse-theia/theia/blob/9d2dcd0a0923d7f7d8def0b7377fc9667706c967/packages/navigator/src/browser/navigator-frontend-module.ts#L45-L73

Again, apologies if I misunderstood you.

Close, but the problem is that those id’s are read from a configuration file, so hard-coding them isn’t an option. I have a class called DocConfigService with a method called .getConfig(), that loads the config from disk.

I could loop over the config within the ContainerModule, but I can’t think of a way to instantiate that service inside the ContainerModule (since the service itself is injectable).

I was finally able to figure this out. I was making an assumption that the id in the widget factory callback was used to identify the instance of the widget. What actually happens is the shell combines that ID with the options passed to createWidget() – it serializes those options together with the id and uses that as the key for looking up the widget.

So, I was able to do something like this:

doc-viewer-widget.ts:

export interface DocViewerWidgetOptions {
    docId: string;
    url: string;
    label: string;
    icon: string;
}

doc-viewer-contribution.ts

// I copied openView() from AbstractViewContribution and modified it to take a `docId` parameter:
    async openView(docId: string, args: Partial<OpenViewArguments> = {}): Promise<void> {
        const shell = this.shell;
        const viewId = this.getWidgetId(docId);
        const docConfig = await this.getConfig();
        const config = (docConfig as any)[docId];
        const widgetOptions: DocViewerWidgetOptions = {
            docId: docId,
            url: config.url,
            label: config.label,
            icon: config.icon
        };
        const widget = await this.widgetManager.getOrCreateWidget(DocViewerWidget.ID, widgetOptions);
        // ... snip ...

And here’s the frontend module:

    bind(WidgetFactory).toDynamicValue(ctx => ({
        id: DocViewerWidget.ID,   // a constant value
        createWidget: (options: DocViewerWidgetOptions) => {
            const child = new Container({ defaultScope: 'Singleton' });
            child.parent = ctx.container;
            child.bind(DocViewerWidget).toSelf();
            child.bind(DocViewerWidgetOptions).toConstantValue(options);
            return child.get(DocViewerWidget);
        }
    }));
1 Like