Custom Component with standard properties (e.g., `visible`) -- how?

All the examples show how to create custom component properties with new names. But what if I just want to expose the custom component’s existing visible property? (Every custom component is a Form, which has its own properties, including visible.)

If I don’t define it as a property, then I can’t use it in data binding; i.e., I can’t use data binding expressions in the IDE to show/hide the custom component at run-time. So I must define it as a custom property.

But defining a custom property named visible just gets me into an infinite recursion in the setter function.

Is there a “right” way to do this? Or do I just have to settle for an alternate name (e.g., is_visible, my current workaround)?

1 Like

You must define a different variable name for the getter/setter. Standard practice seems to be to add an underscore to the property name.

In your example, define self._visible, then in your setter :

@visible.setter
def visible(self, value):
  self._visible = value

Yes, but how then do I set the component’s (form’s) visibility flag, which (within the component code) is self.visible?

Ah ignore me. I’m making a component invisible, not the form.

Checking again …

Don’t worry too much, @david.wylie I have a workaround. But it does make sense to provide component-users with familiar property names, when feasible.

Thanks, Dave, It looks like it will indeed solve the infinite recursion half of the problem.

The other half, of course, is being able to write to the “real” self.visible, to actually show/hide the form. Now that we’ve hijacked the member-lookup machinery, with @property, we’ll need to use different syntax, even inside def visible(self,value).

Perhaps there is a way to bypass this, e.g., some undocumented show() or hide() function? But even these are likely to use some assignment like self.visible = True to record the new status, again triggering the revised member-lookup.

For now, I’m going to stick with is_visible as the property name. It may not be standard, but at least it’s simple. No head-scratching during maintenance. :smile:

2 Likes

I figured out a way to use ‘visible’ as the property name. Just nest a container with the actual content of your component inside an extra blank Form, which you will use purely to set custom component properties. (You can set the space_above and space_below attributes of the outer Form to ‘none’ so that it doesn’t affect the layout at all.) Leave the visible attribute of the outer Form itself as True. Instead, have the custom component visible property toggle the corresponding attribute of the inner container nested inside the Form.

Here’s an example in which the “inner container” is a DataGrid (but it could just as easily be a column panel or a Custom HTML panel): Display a list of lists in a grid

2 Likes

Whenever I need pass through properties on my custom component to the container component base class i add the property in the custom component dialogue and then do the following in code.

   visible = HtmlPanel.visible 

where HtmlPanel might be ColumnPanel depending on which container you used for the custom component.

You’ll see this pattern a fair bit in the source code for anvil extras. There probably should be a better way to be explicit about a property that should be passed to the base container class :thinking:

1 Like

I have come and gone from this forum post about a dozen times.

I have never been able to create a custom component that has a ‘visible’ property, the way that all the Anvil standard components do. I usually end up resorting to an ‘is_visible’ property. This is functional but I’d prefer to avoid this workaround. Also, this method doesn’t render components as greyed out when visibility is toggled off in the IDE.

Here’s what I mean:
https://anvil.works/build#clone:V4JVCYLTSXSEWQ4J=6CGR2PU2VAGCEW4SXWEKQLCN

Is anyone able to take this minimal example and implement the visible property like a standard Anvil component?

I looked into anvil.extras dropdown component and found this:

    @property
    def visible(self):
        return _HtmlPanel.visible.__get__(self, type(self))

    @visible.setter
    def visible(self, val):
        self._el.data("selectpicker")["$bsContainer"].toggleClass(
            "visible-false", not val
        )
        _HtmlPanel.visible.__set__(self, val)

Oof. Is this what it takes? What’s even going on here?

1 Like

Custom components don’t change their look in the designer
That may change in the future, but for now that’s how things are.


Here’s your component edited so that the one line above

    visible = HtmlTemplate.visible

just works

https://anvil.works/build#clone:6RSGRONZEV2XXRYV=NVBNLABL6QZGNEOBB5TEOAH5


You may want to read this from the python documentation to go deeper
It describes descriptors which are pretty fundamental to understand what’s really going on here.
https://docs.python.org/3/howto/descriptor.html


CustomComponent class inheritance looks like this:

object
-> Component
   -> Container
      -> HtmlTemplate (probably or ColumnPanel)
         -> MyCustomComponentTemplate
            -> MyCustomComponent

If we give MyCustomComponent an is_visible property in the custom component dialogue
then we will inherit an is_visible descriptor from the MyCustomComponentTemplate class.

print(MyCustomComponentTemplate.is_visible)
# <anvil.CustomComponentProperty object>

This is a bit like python’s property class.
It’s your basic descriptor with a __get__ and __set__ and doesn’t do much apart from store the value.
(python’s property class is also a descriptor)

If you didn’t create your own @property for is_visible,
then setting is_visible on a custom component will fallback to the MyCustomComponentTemplate.is_visible descriptor.

This can be very useful if you don’t have any logic that needs to run in the getter/setter.
i.e. you get a basic descriptor for free.
If you need extra logic then you can create your own @property


Your current code looks like this

  @property
  def is_visible(self):
    return self.visible
  
  @is_visible.setter
  def is_visible(self, value):
    self.visible = value

You have overridden the is_visible CustomComponentProperty descriptor (inherited from the Template class) with your own descriptor.


When you access self.visible above where are you getting it from?

print(HtmlTemplate.visible)
# <anvil.ComponentProperty object>

HtmlTemlate has its own property descriptor that handles the visible property.
This descriptor knows how to actually make a component visible or not.


Now let’s say we want a visible property instead of an is_visible property.
If we add visible in the custom component dialogue what happens?

The Template class will now have a visible descriptor (instead of the is_visible descriptor).
Which by default doesn’t really do anything except store the value we set.
i.e. self.visible = True will do nothing.

The hack for this is to get our HtmlTemplate.visible descriptor back by adding it directly to our class.

class MyCustomComponent(MyCustomComponentTemplate):
    visible = HtmlTemplate.visible # give me the real visible descriptor

The anvil extras code needs to do some business logic so instead it creates its own @property descriptor, and then directly calls the __get__ and __set__ methods for the HtmlTemplate.visible descriptor.
(doing self.visible = True would just cause an infinite loop)


Hope that helps :sweat_smile:

Should it be easier to do this without needing to understand about descriptors?
Almost certainly :blush:

(the is_visible approach is a good one)

1 Like

Thank you for the detailed response @stucork. I love it when a single line does it :smiley:.

I need to read this a few more times but this generally makes sense. I think the key insight is that simply implementing the visible property in the MyCustomComponent class overrides the inheritance of the visible attribute (descriptor?). But when we add a ‘visible’ property in the Custom Component Configuration we are overriding the descriptor at the level of MyCustomComponentTemplate (as you describe) so we need to dodge this by going up two levels and grabbing from HtmlTemplate.

It feels like ‘standard’ properties should flow through to a CustomComponent, or at least be ‘togglable’ in the Custom Component Configuration. Suffice to say I’ll be adding the following in the Configuration and class definition of every custom component I build from now on…

visible = HtmlTemplate.visible
tag = HtmlTemplate.tag
role = HtmlTemplate.role
# etc
1 Like

This post could be useful too: Add standard properties to the properties tab of custom components

2 Likes

Feels like I’ve been Rick rolled

2 Likes

Oops… sorry, that’s what happens when planning the weekend bike ride while answering to a post while using a clipboard manager…

I edited the URL.

2 Likes