Wierd behaviour of init and binding of layout/custom component

First of all, this is related to Add slots inside designer-placed components in HTMLComponent

I’ve been experimenting with using a form as both layout and custom component.

Since it’s a weird situation, I’m start by giving names:

  • There’s the basic form, that is now a layout. I’ll call that BaseLayout (BL);
  • I made a form that uses BL as it’s layout. This form is also a CustomComponent. I’ll call it LayoutComponent (LC);
  • There’s another form, that uses another layout and has LC as a component that was added using the designer. I’ll call it NormalForm (NF).

The BaseLayout has a property called mode, which is of type Enum and has two values: INIT and CONFIG. The default is CONFIG.
The LayoutComponent also has a property called internal_mode, which behaves exactly the same as BL, with the only difference is that it’s default value is INIT.

In LayoutComponent, the layout.mode is binded to self.internal_mode in a way that if another usage of LayoutComponent as a component changes the default value, it should also propagate to BaseLayout.

NormalForm has layout_component_1.internal_mode statically set as CONFIG in the designer.

Expected behaviour
When I open LayoutComponent as a form, it should use the default configurations: INIT for itself and propagate INIT for the layout. Also, I expected that LayoutComponent’s __init__ method started before, then BaseLayout’s __init__ would be called in LayoutComponent.init_components(**properties). All of this was true for the first part.

I also expected that when I opened NormalForm, it should pass the CONFIG parameter to LayoutComponent, which would then pass to it’s layout and would all work well. This was not what happened.

What happened
When opening NormalForm, BaseLayout should be setup with CONFIG as it’s mode, but this wasn’t the case. I put a few prints, before and after each of the self.init_components(**properties) of the components to see the behaviour.

This was the output for opening LayoutComponent as a form:

LayoutComponent before init_components. internal_mode: INIT properties: {'routing_context': <routing.router._context.RoutingContext object>, 'internal_mode': 'INIT'}
BaseLayout before init_components. mode: CONFIG properties: {'__ignore_property_exceptions': True, 'mode': 'INIT'}
BaseLayout AFTER init_components
LayoutComponent AFTER init_components

The the component started, than it’s layout started and the properties were correctly sent as expected.

However, the output for opening NormalForm was:

LayoutComponent before init_components. internal_mode: INIT properties: {'__ignore_property_exceptions': True, 'internal_mode': 'CONFIG'}
BaseLayout before init_components. mode: CONFIG properties: {'__ignore_property_exceptions': True, 'mode': 'INIT'}
BaseLayout AFTER init_components
LayoutComponent AFTER init_components
NormalForm before init_components
NormalForm AFTER init_components

NormalForm was initialized AFTER the component inside of it. Also, LayoutComponent correctly received internal_mode: CONFIG in it’s property. However, BaseLayout DIDN’T receive mode: CONFIG, even though it’s binded in the layout.

Another thing that I observed it’s that sometimes this order was changed if I used self.layout before init_components.

I don’t really understand why. I think I’m navigating uncharted territory and it’s making me really confused.

If anyone can help me with that…

Could we put together a clone link for this? Minimal working examples really help when things get complex like this.

Sure. Here it goes:

There are 4 forms in this app:

  • BaseLayout: That has 3 cards, a slot, a property called mode of type Enum with two options: ONE and ALL. Default is ALL. This form is a Layout and a CustomComponent. On init, if mode == 'ONE', all cards except the first will be set to visible = False;
  • CustomComponent: It uses BaseLayout as it’s layout. Another card was added to the slot. Has a property called internal_mode of the same type and options as BaseLayout, but defaults to ONE. The value for mode on the layout was manually changed in the designer to ONE and then was binded to self.internal_mode. On init, if internal_mode == 'ONE' the added card will be hidden as well as the other cards;
  • OtherLayout: Just a simple layout to be used by another form. Not important;
  • NormalForm: Uses OtherLayout as it’s layout. Has CustomComponent added to the layout. internal_mode was changed to ALL in the designer.

Behaviour:
Different cards should be visible depending on which form is set as startup:

  • BaseLayout: Expected all 3 cards should be visible since it’s default mode is ‘ALL’, and this happened correctly.

  • CustomComponent: Expected only the first card to be visible, since layout.mode is binded to self.internal_mode and the last one has ONE as it’s default. This happens as expected (but not for the right reasons).

  • NormalForm: Expected to show all 4 cards (3 from layout + 1 from component), since the custom_component_1.internal_mode is statically set to All. However, the first card is visible and the added card is also visible, which means that internal_mode received the value set in NormalForm, but the bind didn’t propagate to layout.
    image

Basically, in my tests, the bindings made from a form to it’s layout’s properties doens’t seem to work or at least not during initialization.

1 Like

Ah - yes I have come across a similar issue with layouts so moving to bug reports

Understanding the order might help here:
We instantiate the custom component
then when we need it, we instantiate the base layout
we then assign the base layout instance as the layout for the custom component
We call refresh_data_bindings on the custom component

BUT - it doesn’t look like we’re calling refresh_data_bindings on the base layout after we’ve assigned the custom component its layout

I think this is the issue at least.

To fix I would do the following (for now - until we fix it internally)



class BaseLayout(BaseLayoutTemplate):
    ...

    def form_refreshing_data_bindings(self, **event_args):
        for card in [self.card_2, self.card_3]:
            card.visible = self.mode == "ALL"

    def form_show(self, **event_args):
        self.refresh_data_bindings()

This should fire refresh_data_bindings
And now we set the visibility correctly when our bindings change

You can either do it this way
Or have a property getter/setter for the mode property
instead of the form_refreshing_data_bindings event handler

1 Like

Yeah, I thought it was something like that, so what I did while waiting for an answer was moving everything that needed mode from init to getters and setters and, as you said, it worked as expected. Since it’s still a custom component it’s a better design to be able to update itself whenever the property changes instead of only in the initialization anyway.

Still, I didn’t close this issue because it’s counter intuitive that the bind doesn’t work as expected and as it works with every other custom component. Thanks for the work!

Linking this discussion as it seems relevant

1 Like