How to pass an argument to the constructor of a RepeatingPanel's item_template?

What I’m trying to do:

I’m tring to optimize the speed of populating a RepeatingPanel with a large number of items. Specifically, I am trying to optimize the code in the __init__ of the item template.

What I’ve tried and what’s not working:

I have the following form called “item” that is used as the item_template in a repeating panel:

from ._anvil_designer import itemTemplate
from anvil import *
from anvil_extras.storage import local_storage

class item(itemTemplate):
  def __init__(self, **properties):

    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    pref_language = local_storage['preferred_language']
    item_name = self.item['titles'][pref_language]

    # rest of code
    ...

Accessing the local_storage (pref_language = local_storage['preferred_language']) takes a substantial amount of time when assigning a large number of items to the repeater (as it is accessed once for each item)

I’ve tried to modify the code by moving pref_language = local_storage['preferred_language'] before the class declaration, in the following manner:

from ._anvil_designer import itemTemplate
from anvil import *
from anvil_extras.storage import local_storage
pref_language = local_storage['preferred_language']

class item(itemTemplate):
  def __init__(self, **properties):

    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    item_name = self.item['titles'][pref_language]

    # rest of code
    ...

The problem is that the code before the class declaration is executed only on app startup. Hence, any changes to local_storage['preferred_language'] would not be reflected to pref_language within the same session.

What I want to do is add pref_language as a parameter to the __init__ so that the local_storage does not have to be accessed for each item:

from ._anvil_designer import itemTemplate
from anvil import *
from anvil_extras.storage import local_storage

class item(itemTemplate):
  def __init__(self, pref_language:str, **properties):

    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    item_name = self.item['titles'][pref_language]

    # rest of code
    ...

The problem I am facing is - with a RepeatingPanel I don’t call the item_template’s form constructor directly, it is automatically called N times whenever the .items property changes.

At what point (and how) can I pass an argument to the pref_language parameter of the constructor? Or could I maybe do something with data bindings?

Any ideas or pointers would be much appreciated!

2 suggestions:

  1. Set it as a property on the repeating panel and access it off of the parent form
  2. Set it as a global variable and access it that way
1 Like

I would create one _preferred_language variable and two get_preferred_language and set_preferred_language functions, all in a client module.

get_preferred_language gets the value from local storage if _preferred_language is None and return it.

set_preferred_language puts the value in local storage and updates _preferred_language.

These two functions are the only place in the whole app where the local storage (for this value) is accessed.

PS
I’m surprised to hear that reading a value from local storage is slow, but life is full of surprises. I have many forms that ready dozens of values from local storage, and I haven’t noticed any performance hit… but I don’t use those forms in a repeating panel.

PS2
It’s a common convention to use CamelCase for class names. When I see item I think of a value, when I see Item I think of a class. But this is just a convention and doesn’t change the behavior of the code.

2 Likes

For newcomers to Python, this Python Enhancement Proposal lists many of those popular conventions, all in one place.

2 Likes

Ah, that seems like a neat and tidy way to do it. I’ll try it out. Thanks for the extensive suggestion :slight_smile:

Well it only becomes an issue for repeaters with hundreds of items, in which case it might take 2-3 seconds. I guess it isn’t that big of an issue but I’m using it in a widget that should feel responsive and it currently feels laggy, so I’m trying to squeeze out more performance.

Thanks for the heads up. I have to learn to start actually using these conventions and best practices… :sweat_smile:

Yes, I was thinking the same actually but I don’t think there’s a way to avoid the overhead of calling get_open_form() for each repeater item? (although I don’t know how much of an overhead this would be until I try it)

I usually avoid repeating panels with so many items.

When there are so many items I either use:

  • a DataGrid, which loads them in chunks (assuming we are dealing with a search iterator, I think 100 rows by default, or q.page_size) and renders them in even smaller smaller chunks (the number of items in each page)
  • a LinearPanel, to which I can feed items in small chunks, for example using the AutoScroll custom component
2 Likes

And I just learned about q.page_size.

+1!

If you set the property on the repeating panel, then in the repeating row template you can use self.parent to refer to the repeating panel. So in the main form you’d do something like:

    self.repeating_panel_1.item_name = self.item['titles'][pref_language]

And then in the repeating panel’s row template you’d use:

    item_name = self.parent.item_name

No need for get_open_form().

4 Likes

This is exactly what I wanted to do from the beginning, but for whatever reason I (wrongly) remember that this wasn’t possible in anvil.

Not sure how or when I convinced myself that traversing the component hierarchy wasn’t possible but I’m glad for the wakeup call :sweat_smile:

Ay ay ay, I need to catch up on some sleep.

1 Like