New: A more Pythonic way to handle events

Introducing the @handle decorator

We are changing the way event handlers work in Anvil! In Form code, methods that are registered as event handlers will now be decorated with @handle("<component_name>", "<event_name>"). This new decorator makes it explicit in code which methods are event handlers for which components.

You can set event handlers in code by decorating them with @handle, and when you create an event handler from the Anvil Designer (using the Object Palette or the Properties Panel), the method that is set up for you will have this decorator.

Here’s an example click event handler for a Button called submit_button:

@handle("submit_button", "click")
def submit_button_click(self, **event_args):
    """This method is called when the button is clicked"""
    anvil.server.call("submit_form", self.item)
    self.submit_button.text = "Submitted!"

Setting event handlers in code

It’s a lot smoother to set and modify events handlers decorated with @handle. Just add the decorator to a function in your Form code and give it the name of the component and the event you want to raise: @handle("<component_name>", "<event_name>")

Your function can have any name, but should have **event_args as one of its parameters.

Modifying event handlers

The @handle decorator also makes it simple to modify existing event handlers. Want to bind the handler to a different component? Just change the component name in the decorator! The same goes for if you want to change the event that triggers the function.

It’s easier to see existing event handlers

The @handle decorator makes it a lot easier to know which components are connected to which event handlers because it’s all explicit in the code. You no longer have to scroll to the bottom of the Properties Panel to see the event handlers that a component raises.

Things to note

Existing event handlers in existing apps will not change. The new decorator will only be added to newly created events. You can easily update an existing event handler to use @handle by deleting the name of the event handler from the Properties Panel and re-adding it by clicking the blue arrow.

When an event is decorated with @handle, it will show up at the bottom of the Properties Panel as expected. However, you will not be able to modify the event from here. If you want to modify the event or delete it completely, you’ll need to do this in code.

Happy event handling!
Brooke

11 Likes

Undocumented:

    @handle("button_1", "click")
    @handle("button_2", "click")
    def any_button_click(self, **event_args):
        """This method is called when the button is clicked"""
        pass

works! You may have to add the 2nd @handle manually.

Nice catch - although we mentioned it in the announcement blog post, it was missing from the docs.

Stacking @handles is a neat technique everyone should know about, so I’ve added it there too :slight_smile:

2 Likes

Absolutely!

In my case, it’s usually not the event that varies from one line to the next, but the component. But it works in both cases.

This is so great, I just realized you can change the name of the function without having to change things in the editor!

3 Likes

Is anvil-app-server going to support this soon?

I’ve recently made some changes to events in a couple of forms, and I’m getting AttributeError: module 'anvil' has no attribute 'handle' in forms which have the new decorators, when deploying the app locally.

In the meantime I’m going to have to create these events dynamically with add_event_handler in the form’s __init__, I guess? Because I presumably can’t get the “old-style” handlers back in gui (and even if I do, they’ll keep getting automatically changed to the decorator).

1 Like

Feature Request - HTML Support

It would be really awesome if this would work for custom html elements too using the anvil-name property (and/or id?).

For example

html:

<button anvil-name="my-button">My Button</button>

Form:

@anvil.handle("my-button", "click")
def my_button_handler(self,  **event_args):
  print("my button was clicked")

Current Method

It would essentially just doing the DOM lookup and registration for us:

class ButtonTesting(ButtonTestingTemplate):
    def __init__(self, **properties):
        self.init_components(**properties)
        self.dom_nodes["my-button"].addEventListener("click", self.my_button_handler)

    def my_button_handler(self, **event_args):
        print("my button was clicked")

Matching Priority

  1. Try matching to native anvil component names
  2. Try matching to dom_nodes keys

Demo

I wanted to see if I could get something working the same way for html components. Here is a simple demo for that.

3 Likes

Is there a way to use the @handler for form_show events? Add a handler to the entire form?

When I use the IDE to add such a handler, it sets the decorator’s first parameter to “”.

3 Likes