Skip to content

Attaching Events Handlers

theRealProHacker edited this page Aug 28, 2023 · 1 revision

Attaching event handler to elements is a pretty useful and relevant task. Therefore, it is made very simple in Positron.

Simple Event Handler Attachment with CSS Selectors

Firstly, you need to know which element(s) you want to select. You do this with css selectors. In most cases you will select by id, class or tag.

from positron import *

# Select the element with `id=my-button`
SingleJ("#my-button")

# Select all buttons
J("button")

All the intricacies of CSS selectors will not be covered here.

To attach event handlers to the selected elements we use the beautiful syntax called decorators. In this example we print the id of every button every time it is clicked.

@J("button").on("click")
def _(event: Event):
    print(event.target.id)

Define callback repititions

You can also define how many times your event handler should be called. In most cases you want to either call the handler the first time or every time an event happens. For prior there is a special method once. In this example, we print the first time a button is hovered over.

@J("button").once("hover")
def _(event: Event):
    print(f"Button with id {event.target.id} was hovered over the first time at {event.timestamp}")

For finer control use the repeat argument.

lives = 10
@J(".dangerous-object").on("hover", repeat=lives)
def _(event: Event):
    nonlocal lives
    lives -= 1
    if lives:
        print(f"{lives} lives left. Be careful")
    else:
        print(f"You died")

Interacting with the event

The different event types and their attributes are listed in (Events.md). Most of the atributes should never be written to. If you do, the behaviour is undefined - it might blow up the application or nothing might happen.

But there are three boolean flags you can set to a certain value (and only that).

  • cancelled: If you set this to True and the event is cancellable then no default actions will be executed
  • propagation: If you set this to False and the event normally bubbles then no handlers attached to parent elements will be called
  • immediate_propagation: If you set this to False then no further handlers will be called.

cancellable and bubbles are web dev terms that have very specific meanings that will not be covered here.

For web devs: These attributes replace preventDefault(), stopPropagation() and stopImmediatePropagation()

Async Event Handlers

You can easily use asynchronous handlers. But be careful - these handlers will not run immediately. Instead, they will be scheduled to run later. This means that setting any of the above mentioned flags might have no effect.

However, in general asynchronous event handlers are pretty cool, if you want to do heavy I/O.

Tip: use asyncio.to_thread() to make your synchronous functions run asynchronously.

In this example, we try to interrupt the default action when an anchor is clicked (navigating to the href) and download the content at the href instead. But this will most likely not work as expected because the event manager doesn't wait for the asynchronous function to finish and executes the default action before the event handler has even run once.

@J("a").on("click")
async def _(event: Event):
    event.cancelled = True # This doesn't work
    data = await download(event.target.href)
    ... # do something with the data

Instead do this

async def the_async_function(url):
    data = await download(url)
    ... # do something with the data

@J("a").on("click")
def _(event: Event):
    event.cancelled = True # This works
    return the_async_function(event.target.href)

This works because we return an awaitable which is almost like defining an asynchronous function in the first place. But the function is run immediately and so the event actually gets cancelled.