Setting event handlers dynamically

I have a couple of questions about setting event handlers to form components in code.

The first question is when to add an event handler - before or after adding component to the form. An example:

l = Link(text="Some link")
self.add_component(l)
l.set_event_handler("click", self.handle_my_event)

or:

l = Link(text="Some link")
l.set_event_handler("click", self.handle_my_event)
self.add_component(l)

The second question is about changing the event handler for another - are there any limitations?

I’m asking this because I try to build dynamic menus and submenus. For each menu and submenu I create lists of Link() components, and when I have to change menu options (i. e. user chooses a submenu), I remove_from_parent() each of the Link() components and with add_component() I’m adding new components to the sidebar.

Firstly, main menu is shown and everything works great. When I go to submenu, still works. When I go back to main menu, click event doesn’t fire anymore. The menu rebuild goes like this:

def displayMenu(self, menuName):                     #function, takes name of menu to show
  if len(self.displayedMenu) > 0:                    #if some menu is displayed, remove links
    for choice in self.menus[self.displayedMenu]:    #each choice (link) gets removed
      choice.remove_from_parent()
  for lnk in self.menus[menuName]:                   #for all links in a menu menuName
    lnk.set_event_handler("click", self.evhndl)      #... set event handler
    self.add_component(lnk, slot='sidebar')          #... and add link to the sidebar

self.menus is a dictionary where keys are menu names and values are lists of Links.

Maybe I should be changing the link texts instead of the Links, but if you have objects, then you use them :slight_smile:

I can answer the first part - it doesn’t matter which order you do that in, as long as you do it within the scope of the variable you assign to the link (in your case, l). I’ve just tested that.

For the second part - why are you changing the event handlers for the links? Wouldn’t a link always go to the same handler, even if that handler dynamically altered what it did based on other data (such as the link object passed to it)? Just trying to understand …

Thanks for nr. 1!

Nr. 2 - I made the code thinking that the component should already be on the form, so if I remove it, the event handler has to be ā€˜reestablished’. I’ll try to change this, since we know the answer for question nr. 1. The trouble might be the ā€œscope of the variableā€ that you mentioned, because I basically have:

def buildingMenus(self):
  self.myMenu = []
  l = Link(text="Some link")
  l.set_event_handler("click", self.handle_my_event)
  self.myMenu.append(l)

def showMenus():
  for m in self.myMenu
    self.add_component(m)

So, I don’t know whether the handler gets lost in between or not …

Ok, here’s what I just tried :

from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    # You must call self.init_components() before doing anything else in this function
    self.init_components(**properties)

    self.myMenu=[]
    
  def buildingMenus(self):
    l = Link(text="Some link")
    l.set_event_handler("click", self.event_1)
    self.myMenu.append(l)

    l = Link(text="Some link 2")
    l.set_event_handler("click", self.event_1)
    self.myMenu.append(l)
  
  def showMenus(self):
    print("menu=",self.myMenu)
    for m in self.myMenu:
      self.add_component(m)    
      
  def event_1(self,**rest):
    print("In 1",rest)
    rest['sender'].set_event_handler("click",self.event_2)
    
  def event_2(self,**rest):
    print("In 2")

  def form_show (self, **event_args):
    self.buildingMenus()
    self.showMenus()

It assigns event_1 handler to each new created link.
When clicked, the event handler assigns a new event handler to the object passed.

It sort of says that changing the event handlers works.

Have I got the right end of the stick, so to speak?

Yes, this works. I have a bit more complicated case - if a user hits an option to go to submenu, and then he goes back, the click event doesn’t fire anymore, if the links are removed and then added again.

Here’s a simplified version - event handler calls showMenus again and rebuilds menus:

from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    # You must call self.init_components() before doing anything else in this function
    self.init_components(**properties)

    self.myMenu=[]
    
  def buildingMenus(self):
    l = Link(text="Some link")
    l.set_event_handler("click", self.eventHandler)
    self.myMenu.append(l)

    l = Link(text="Some link 2")
    l.set_event_handler("click", self.eventHandler)
    self.myMenu.append(l)
  
  def showMenus(self):
    if self.menusShown:
      for m in self.myMenu:
        print("removed:",m)
        m.remove_from_parent()
    for m in self.myMenu:
      print("added:",m)
      self.add_component(m, slot='sidebar')
    self.menusShown = True
  
  def eventHandler(self, **event_args):
    self.showMenus()
  
  def form_show (self, **event_args):
    self.buildingMenus()
    self.menusShown = False
    self.showMenus()

Does that code fail for you?

When I run that code, it does what I expect. First time I run the app it just adds the links, then with each click it removes the links and adds them again. Clicked them both many times.

I might not be understanding something.

(edit) - that ā€œslotā€ parameter to the add_component, I’m not sure I know what that does?

slot parameter puts a component in predefined area, like default - cards, sidebar and nav-right on MaterialDesign theme (forum post)

In fact, I tried to run the above code without the slot parameter, and if works from some reason! Maybe it is related to particular browser, or the fact that I’m still on a trial plan, so there might be some additional iframes around my screen.

But I found an even better and ā€˜prettier’ solution - by putting a LinearPanel around Link elements, so each menu is a LinearPanel, which can then be hidden or shown, like this:

from anvil import *

class Form1(Form1Template):

  def __init__(self, **properties):
    self.init_components(**properties)
    self.myMenu1Items=[]
    self.myMenu2Items=[]
    
  def buildingMenus(self):
    l = Link(text="First menu 1")
    l.set_event_handler("click", self.eventHandler)
    self.myMenu1Items.append(l)

    l = Link(text="First menu 2")
    l.set_event_handler("click", self.eventHandler)
    self.myMenu1Items.append(l)

    l = Link(text="Second menu 1")
    l.set_event_handler("click", self.eventHandler)
    self.myMenu2Items.append(l)
    
  def showMenus(self):
    self.myMenu1 = LinearPanel(visible=True)
    for m1 in self.myMenu1Items:
      self.myMenu1.add_component(m1)
    self.myMenu2 = LinearPanel(visible=False)
    for m2 in self.myMenu2Items:
      self.myMenu2.add_component(m2)
    self.add_component(self.myMenu1, slot='sidebar')
    self.add_component(self.myMenu2, slot='sidebar')
  
  def eventHandler(self, **event_args):
    self.add_component(TextBox(text="menu was clicked"))
    self.myMenu1.visible = not self.myMenu1.visible
    self.myMenu2.visible = not self.myMenu2.visible
  
  def form_show (self, **event_args):
    self.buildingMenus()
    self.showMenus()

Thanks for your guidance!

1 Like

I’d forgotten all about that post.

Glad you got it working. I think your new way is much better.