Layout and DOM classes aren't being applied to child widget of custom widget

I am attempting to implement a custom widget that a) has one or more child widgets and b) calls some custom functionality from the render() function when the widget displays.

While I have gotten the custom widget with the child widget(s) to display, it appears that the DOM classes aren’t being set on the child widgets, nor is their layout being applied.

I suspect that I am missing call to initialize their layout or something. Does anyone know what I am missing? Any help would be appreciated.

Here is a toy example of my code, boiled down to the simplest case:

class ParentWidget(DOMWidget):
    _model_name = Unicode('ParentWidgetModel').tag(sync=True)
    _model_module = Unicode(module_name).tag(sync=True)
    _model_module_version = Unicode(module_version).tag(sync=True)

    _view_name = Unicode('ParentWidgetView').tag(sync=True)
    _view_module = Unicode(module_name).tag(sync=True)
    _view_module_version = Unicode(module_version).tag(sync=True)

    # Declare the child widget
    child_widget = Instance(GridBox, ()).tag(sync=True, **widget_serialization)

    def __init__(self, **kwargs):
        DOMWidget.__init__(self, **kwargs)

        # Init the child widget
        label = Label(layout=Layout(width='auto', grid_area='label'))
        input = Text(layout=Layout(width='auto', grid_area='input'))
        self.child_widget = GridBox([label, input], _dom_classes=['childwidget'], layout=Layout(
            width='100%',
            grid_template_rows='auto auto',
            grid_template_columns='25% 75%',
            grid_template_areas='"label input"'), **kwargs)
export class ParentWidgetView extends DOMWidgetView {
    render() {
        super.render();

        // Do custom rendering stuff here

        // Add the child widget
        const parent_element = this.el;
        const child_model = this.model.get('child_widget');
        this.create_child_view(child_model).then((view) => {
            parent_element.appendChild(view.el);
            return view;
        }).catch(reject('Could not add child to the parent', true));
    }
}

export class ParentWidgetModel extends DOMWidgetModel {
    static model_name = 'ParentWidgetModel';
    static model_module = MODULE_NAME;
    static model_module_version = MODULE_VERSION;
    static view_name = 'ParentWidgetView';
    static view_module = MODULE_NAME;
    static view_module_version = MODULE_VERSION;

    static serializers: ISerializers = {
        ...DOMWidgetModel.serializers,
        child_widget: {
            deserialize: (value: any, manager: ManagerBase<any>|undefined) =>
                unpack_models(value, manager as ManagerBase<any>)
        }
    };

    defaults() {
        return {
            ...super.defaults(),
            _model_name: ParentWidgetModel.model_name,
            _model_module: ParentWidgetModel.model_module,
            _model_module_version: ParentWidgetModel.model_module_version,
            _view_name: ParentWidgetModel.view_name,
            _view_module: ParentWidgetModel.view_module,
            _view_module_version: ParentWidgetModel.view_module_version,
            child_widget: undefined
        };
    }
}

If you run this, you’ll notice that the widget and child widget both display, but that the childwidget class isn’t applied to the child widget, nor is the grid template included.

It appears that this issue is because displayed isn’t being triggered for the child widgets. I can manually trigger this by in the render function including:

view.trigger('displayed');

Unfortunately, this doesn’t also recursively trigger the displayed event for all child widgets, so I wrote a function to do that.

I’ve included my solution below. Hopefully it will be of use to someone else. However, it still feels like a hack to me, so if anyone has any insight into how to do this more elegantly, it would be appreciated.

export class ParentWidgetView extends DOMWidgetView {
    render() {
        super.render();

        // Do custom rendering stuff here

        // Add the child widget
        const parent_element = this.el;
        const child_model = this.model.get('child_widget');
        this.create_child_view(child_model).then((view) => {
            parent_element.appendChild(view.el);
            this._initialize_display(child_model, view);
            return view;
        }).catch(reject('Could not add child to the parent', true));
    }

    _initialize_display(model:DOMWidgetModel, view:DOMWidgetView|any) {
        // Trigger the display for this widget
        view.trigger('displayed');

        // Recursively trigger th display for all child widgets
        if ('children_views' in view) {
            view.children_views.update(model.get('children')).then((children:DOMWidgetView[]) => {
                children.forEach((child) => {
                    this._initialize_display(child.model, child);
                });
            });
        }
    }
}
1 Like