Skip to content

Actions

Liveblade actions are methods on your component that can be triggered by frontend interactions like clicking a button or submitting a form.

They provide the developer experience of being able to call a Python method directly from the browser, allowing you to focus on the logic of your application without getting bogged down writing boilerplate code connecting your application's frontend and backend.

Let's explore a basic example of calling a save action on a CreatePost component:

python
from pyblade import liveblade
from app.models import Post

class CreatePost(liveblade.component):
    title = ""
    content  = ""

    def save(self):
        Post.objects.create(title=self.title, self.content)

    def render(self):
        return self.view('liveblade.create-post');
html
<form b-submit="save">
    <input type="text" b-model="title">

    <textarea b-model="content"></textarea>

    <button type="submit">Save</button>
</form>

In the above example, when a user submits the form by clicking "Save", b-submit intercepts the submit event and calls the save() method on the server.

In essence, actions are a way to easily map user interactions to server-side functionality without the hassle of submitting and handling AJAX requests manually.

Confirming an action

When allowing users to perform dangerous actions — such as deleting a post from the database — you may want to show them a confirmation alert to verify that they wish to perform that action.

Liveblade makes this easy by providing a simple directive called b-confirm:

html
<button
    type="button"
    b-click="delete"
    b-confirm="Are you sure you want to delete this post?"
>
    Delete post
</button>

When b-confirm is added to an element containing a Liveblade action, when a user tries to trigger that action, they will be presented with a confirmation dialog containing the provided message. They can either press "Yes" to confirm the action, or press "Cancel" or hit the escape key to cancel the action.

Passing parameters

Liveblade allows you to pass parameters from your PyBlade template to the actions in your component, giving you the opportunity to provide an action additional data or state from the frontend when the action is called.

For example, let's imagine you have a TodoList component that allows users to delete a task. You can pass the task's ID as a parameter to the delete() action in your Liveblade component. Then, the action can fetch the relevant task and delete it from the database:

python
rom pyblade import liveblade
from app.models import Task

class TodoList(liveblade.Component):
    
    def mount(self):
        self.tasks = Task.objects.all()

    def delete(self, id: int):
        task = Task.objects.get(id=id)
        task.delete()

    def render(self):
        return self.view("liveblade.todo-list")
html
<div>
    @for (task in tasks)
        <div key="{{ task.id }}">
            <h1>{{ task.title }}</h1>
            <span>{{ task.content }}</span>

            <button b-click="delete({{ task.id }})">Delete</button>
        </div>
    @endfor
</div>

For a task with an ID of 2, the "Delete" button in the PyBlade template above will render in the browser as:

html
<button b-click="delete(2)">Delete</button>

When this button is clicked, the delete() method will be called and id will be passed in with a value of "2".

Don't trust action parameters !

Action parameters should be treated just like HTTP request input, meaning action parameter values should not be trusted. You should always authorize ownership of an entity before updating it in the database.

For more information, consult our documentation regarding security concerns.

Skipping re-renders

Everytime an action in your component is trigerred, the render() method is called to re-render the component.

But, sometimes there might be an action in your component with no side effects that would change the rendered PyBlade template when the action is invoked. If so, you can skip the render portion of Liveblade's lifecycle by adding the @renderless decorator above the action method.

Let's say you want to track when a user clicks on a button, but this interaction doesn’t change any visible part of the UI.

python
from pyblade.liveblade import Component
from app.models import ActivityLog

class ActivityTracker(Component):
    user_id = None

    def mount(self, user_id):
        self.user_id = user_id

    @renderless
    def track_click(self):
        # Log the click to your database or analytics service
        ActivityLog.objects.create(user_id=self.user_id, action="clicked_button")
html
<button wire:click="track_click" class="btn">
    Click Me
</button>

What happens here is that when the track_click method is called, the @renderless decorator tells Liveblade not to call the render() method afterward.

This saves time and prevents an unnecessary re-render of the component. It’s perfect for fire-and-forget logic — like logging, silent actions, or external calls.

If you prefer to not utilize method attributes or need to conditionally skip rendering, you may invoke the skip_render() method in your component action:

python
class PostDetail(liveblade.Component):
    ...

    def increment_view_count():
        self.post.increment_views()
        self.skip_render() 

    def render():
        return view('liveblade.show-post');

JavaScript actions

Liveblade allows you to define JavaScript actions that run entirely on the client-side without making a server request. This is useful in two scenarios:

  1. When you want to perform simple UI updates that don't require server communication
  2. When you want to optimistically update the UI with JavaScript before making a server request

To define a JavaScript action, you can use the js() function inside a <script> tag in your component.

Here's an example of bookmarking a post that uses a JavaScript action to optimistically update the UI before making a server request. The JavaScript action immediately shows the filled bookmark icon, then makes a request to persist the bookmark in the database:

python
from pyblade import liveblade 

class PostDetail(liveblade.Component):
    
    def mount(self):
        self.bookmarked = self.post.is_bookmarked_by(request.user)

    def bookmark_post(self):
        self.post.bookmark(request.user)
        self.bookmarked = self.post.is_bookmarked_by(request.user)

    def render(self):
        return view('liveblade.post-detail')
html
<div>
    <button b-click="js.bookmark" class="flex items-center gap-1">
        {# Outlined bookmark icon... #}
        <svg b-show="!bookmarked" b-cloak xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z" />
        </svg>

        {# Solid bookmark icon... #}
        <svg b-show="bookmarked" b-cloak xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
            <path fill-rule="evenodd" d="M6.32 2.577a49.255 49.255 0 0 1 11.36 0c1.497.174 2.57 1.46 2.57 2.93V21a.75.75 0 0 1-1.085.67L12 18.089l-7.165 3.583A.75.75 0 0 1 3.75 21V5.507c0-1.47 1.073-2.756 2.57-2.93Z" clip-rule="evenodd" />
        </svg>
    </button>
</div>

@script
<script>
    js('bookmark', () => {
        liveblade.bookmarked = !liveblade.bookmarked

        liveblade.bookmark_post()
    })
</script>
@endscript

When a user clicks the heart button, the following sequence occurs:

  1. The "bookmark" JavaScript action is triggered
  2. The heart icon immediately updates by toggling wire.bookmarked on the client-side
  3. The bookmark_post() method is called to save the change to the database

This provides instant visual feedback while ensuring the bookmark state is properly persisted.

Calling from Python

JavaScript actions can also be called using the js() method from Python:

python
from pyblade import liveblade 

class CreatePost(liveblade.Component):
    
    def save(self):
        ...
        self.js("onPostSaved") 
html
<div>
    <!-- ... -->

    <button b-click="save">Save</button>
</div>

@script
<script>
    js('onPostSaved', () => {
        alert('Your post has been saved successfully!')
    })
</script>
@endscript

In this example, when the save() action is finished, the onPostSaved JavaScript action will be run, triggering the alert dialog.

Magic actions

Liveblade provides a set of "magic" actions that allow you to perform common tasks in your components without defining custom methods. These magic actions can be used within event listeners defined in your PyBlade templates.

Refreshing a component

Sometimes you may want to trigger a simple "refresh" of your component. For example, if you have a component checking the status of something in the database, you may want to show a button to your users allowing them to refresh the displayed results.

You can do this using Liveblade's simple refresh action anywhere you would normally reference your own component method:

html
<button type="button" b-click="refresh">...</button>

When the refresh action is triggered, Liveblade will make a server-roundtrip and re-render your component without calling any methods.

It's important to note that any pending data updates in your component (for example b-model bindings) will be applied on the server when the component is refreshed.

Calling parent actions

The parent magic variable allows you to access parent component properties and call parent component actions from a child component:

html
<button b-click="parent.remove_post({{ post.id }})">Remove</button>

In the above example, if a parent component has a remove_post() action, a child can call it directly from its PyBlade template using parent.remove_post().

Updating properties

The set magic action allows you to update a property in your Liveblade component directly from the PyBlade template. To use set, provide the property you want to update and the new value as arguments:

html
<button b-click="set('query', '')">Reset Search</button>

In this example, when the button is clicked, a network request is dispatched that sets the query property in the component to an empty string ''.

Toggling Boolean values

The toggle action is used to toggle the value of a boolean property in your Liveblade component:

html
<button b-click="toggle('sort_asc')">
    Sort {{ "Descending" if sort_asc else "Ascending" }}
</button>

In this example, when the button is clicked, the sort_asc property in the component will toggle between True and False.

Dispatching events

The emit action allows you to dispatch a Liveblade event directly in the browser. Below is an example of a button that, when clicked, will emit the post-deleted event:

html
<button type="submit" b-click="emit('post-deleted')">Delete Post</button>

Pro tip

You can also use the dispatch action to dispatch events. It works the same way as emit.

Accessing event objects

The event action may be used within event listeners like b-click. This action gives you access to the actual JavaScript event that was triggered, allowing you to reference the triggering element and other relevant information:

html
<input type="text" b-keydown.enter="search(event.target.value)">

When the enter key is pressed while a user is typing in the input above, the contents of the input will be passed as a parameter to the search() action.

Event listeners

Liveblade supports a variety of event listeners, allowing you to respond to various types of user interactions:

ListenerDescription
b-clickTriggered when an element is clicked
b-submitTriggered when a form is submitted
b-changeTriggered when an input value changes
b-keydownTriggered when a key is pressed down
b-keyupTriggered when a key is released
b-mouseenterTriggered when the mouse enters an element
b-*Whatever text follows b- will be used as the event name of the listener

Because the event name after b- can be anything, Liveblade supports any browser event you might need to listen for. For example, to listen for transitionend, you can use b-transitionend.

Listening for specific keys

You can use one of Liveblade's convenient aliases to narrow down key press event listeners to a specific key or combination of keys.

For example, to perform a search when a user hits Enter after typing into a search box, you can use b-keydown.enter:

html
<input b-model="query" b-keydown.enter="searchPosts">

You can chain more key aliases after the first to listen for combinations of keys. For example, if you would like to listen for the Enter key only while the Shift key is pressed, you may write the following:

html
<input b-keydown.shift.enter="...">

Below is a list of all the available key modifiers:

ModifierKey
.shiftShift
.enterEnter
.spaceSpace
.ctrlCtrl
.cmdCmd
.metaCmd on Mac, Windows key on Windows
.altAlt
.upUp arrow
.downDown arrow
.leftLeft arrow
.rightRight arrow
.escEscape
.tabTab
.caps-lockCaps Lock
.equalEqual, =
.periodPeriod, .
.slashForward Slash, /

Event handler modifiers

Liveblade also includes helpful modifiers to make common event-handling tasks trivial.

For example, if you need to call event.preventDefault() from inside an event listener, you can suffix the event name with .prevent:

html
<input b-keydown.prevent="...">

Here is a full list of all the available event listener modifiers and their functions:

ModifierKey
.preventEquivalent of calling .preventDefault()
.stopEquivalent of calling .stopPropagation()
.windowListens for event on the window object
.outsideOnly listens for clicks "outside" the element
.documentListens for events on the document object
.onceEnsures the listener is only called once
.debounceDebounce the handler by 250ms as a default
.debounce.100msDebounce the handler for a specific amount of time
.throttleThrottle the handler to being called every 250ms at minimum
.throttle.100msThrottle the handler at a custom duration
.selfOnly call listener if event originated on this element, not children
.camelConverts event name to camel case (b-custom-event -> "customEvent")
.dotConverts event name to dot notation (b-custom-event -> "custom.event")
.passiveb-touchstart.passive won't block scroll performance
.captureListen for event in the "capturing" phase

Disabling inputs while a form is being submitted

Consider the CreatePost example we previously discussed:

html
<form b-submit="save">
    <input b-model="title">

    <textarea b-model="content"></textarea>

    <button type="submit">Save</button>
</form>

When a user clicks "Save", a network request is sent to the server to call the save() action on the Liveblade component.

But, let's imagine that a user is filling out this form on a slow internet connection. The user clicks "Save" and nothing happens initially because the network request takes longer than usual. They might wonder if the submission failed and attempt to click the "Save" button again while the first request is still being handled.

In this case, there would be two requests for the same action being processed at the same time.

To prevent this scenario, Liveblade automatically disables the submit button and all form inputs inside the <form> element while a b-submit action is being processed. This ensures that a form isn't accidentally submitted twice.

To further lessen the confusion for users on slower connections, it is often helpful to show some loading indicator such as a subtle background color change or SVG animation.

Liveblade provides a b-loading directive that makes it trivial to show and hide loading indicators anywhere on a page. Here's a short example of using b-loading to show a loading message below the "Save" button:

html
<form b-submit="save">
    <textarea b-model="content"></textarea>

    <button type="submit">Save</button>

    <span b-loading>Saving...</span>
</form>

Security concerns

Remember that any method in your Liveblade component can be called from the client-side, even without an associated b-click handler that invokes it. In these scenarios, users can still trigger the action from the browser's DevTools. So to make your application secure, read out our Security concerns before using actions.