PyBlade Live directives
pb-click
PyBlade Live provides a simple pb-click directive for calling component methods (aka actions) when a user clicks a specific element on the page.
For example, given the InvoiceDetail component below:
from pyblade import live
from app.models import Invoice
class ShowInvoice(live.Component):
invoice: Invoice
def download(self):
return response().download(
f"{self.invoice.file_path}_invoice.pdf"
)In the component's template, you can trigger the download() method from the class above when a user clicks a "Download Invoice" button by adding pb-click="download":
<button type="button" pb-click="download">
Download Invoice
</button>Using pb-click on links
When using pb-click on <a> tags, you must append .prevent to prevent the default handling of a link in the browser. Otherwise, the browser will visit the provided link and update the page's URL.
<a href="#" pb-click.prevent="...">Going deeper
The pb-click directive is just one of many different available event listeners in PyBlade Live. For full documentation on its (and other event listeners) capabilities, visit the PyBlade Live documentation page.
pb-submit
PyBlade Live makes it easy to handle form submissions via the pb-submit directive. By adding pb-submit to a <form> element, PyBlade Live will intercept the form submission, prevent the default browser handling, and call any PyBlade Live component method.
Here's a basic example of using pb-submit to handle a "PostCreate" form submission:
from pyblade import live
from app.models import Post
class PostCreate(live.Component)
title: str = ""
content: str = ""
def save(self):
Post.objects.create(title=self.title, content=self.content)
self.redirect("/posts")
def render(self):
return self.view("live.post-create")<form pb-submit="save">
<input type="text" pb-model="title">
<textarea pb-model="content"></textarea>
<button type="submit">Save</button>
</form>In the above example, when a user submits the form by clicking "Save", pb-submit intercepts the submit event and calls the save() action on the server.
PyBlade Live automatically calls `preventDefault()`
pb-submit is different than other PyBlade Live event handlers in that it internally calls event.preventDefault() without the need for the .prevent modifier. This is because there are very few instances you would be listening for the submit event and NOT want to prevent it's default browser handling (performing a full form submission to an endpoint).
PyBlade Live automatically disables forms while submitting
By default, when PyBlade Live is sending a form submission to the server, it will disable form submit buttons and mark all form inputs as readonly. This way a user cannot submit the same form again until the initial submission is complete.
Going deeper
pb-submit is just one of many event listeners that PyBlade Live provides. The PyBlade Live forms section provides much more complete documentation on using pb-submit in your application:
pb-model
PyBlade Live makes it easy to bind a component property's value with form inputs using pb-model.
Here is a simple example of using pb-model to bind the title and content properties with form inputs in a PostCreate component:
from pyblade import live
from app.models import Post
class PostCreate(live.Component)
title: str = ""
content: str = ""
def save(self):
Post.objects.create(title=self.title, content=self.content)
self.redirect("/posts")
def render(self):
return self.view("live.post-create")<form pb-submit="save">
<input type="text" pb-model="title">
<textarea pb-model="content"></textarea>
<button type="submit">Save</button>
</form>
```>Because both inputs use pb-model, their values will be synchronized with the server's properties when the "Save" button is pressed.
"Why isn't my component live updating as I type?"
If you tried this in your browser and are confused why the title isn't automatically updating, it's because PyBlade Live only updates a component when an "action" is submitted — like pressing a submit button — not when a user types into a field. This cuts down on network requests and improves performance.
Customizing update timing
By default, PyBlade Live will only send a network request when an action is performed (like pb-click or pb-submit), NOT when a pb-model input is updated.
This drastically improves the performance of PyBlade Live by reducing network requests and provides a smoother experience for your users.
However, there are occasions where you may want to update the server more frequently for things like real-time validation.
PyBlade Live updating
To send property updates to the server as a user types into an input-field, you can append the .live modifier to pb-model:
<input type="text" pb-model.live="title">Customizing the debounce
By default, when using pb-model.live, PyBlade Live adds a 150 millisecond debounce to server updates. This means if a user is continually typing, PyBlade Live will wait until the user stops typing for 150 milliseconds before sending a request.
You can customize this timing by appending .debounce.Xms to the input, where Xis the number of milliseconds. Here is an example of changing the debounce to 250 milliseconds:
<input type="text" pb-model.live.debounce.250ms="title">Updating on "blur" event
By appending the .blur modifier, PyBlade Live will only send network requests with property updates when a user clicks away from an input, or presses the tab key to move to the next input.
Adding .blur is helpful for scenarios where you want to update the server more frequently, but not as a user types. For example, real-time validation is a common instance where .blur is helpful.
<input type="text" pb-model.blur="title">Updating on "change" event
There are times when the behavior of .blur isn't exactly what you want and instead .change is.
For example, if you want to run validation every time a select input is changed, by adding .change, PyBlade Live will send a network request and validate the property as soon as a user selects a new option. As opposed to .blur which will only update the server after the user tabs away from the select input.
<select pb-model.change="title">
<!-- ... -->
</select>Any changes made to the select input will be automatically synchronized with the title property in your PyBlade Live component.
All available modifiers
| Modifier | Description |
|---|---|
.live | Send updates as a user types |
.blur | Only send updates on the blur event |
.change | Only send updates on the the change event |
.lazy | An alias for .change |
.debounce.Xms | Debounce the sending of updates by X milliseconds delay |
.throttle.Xms | Throttle network request updates by Xmilliseconds interval |
.number | Cast the text value of an input to int on the server |
.boolean | Cast the text value of an input to bool on the server |
.fill | Use the initial value provided by a "value" HTML attribute on page-load |
Input fields
PyBlade Live supports most native input elements out of the box. Meaning you should just be able to attach pb-model to any input element in the browser and easily bind properties to them.
Here's a comprehensive list of the different available input types and how you use them in a PyBlade Live context.
Text inputs
First and foremost, text inputs are the bedrock of most forms. Here's how to bind a property named "title" to one:
<input type="text" pb-model="title">Textarea inputs
Textarea elements are similarly straightforward. Simply add pb-model to a textarea and the value will be bound:
<textarea type="text" pb-model="content"></textarea>If the "content" value is initialized with a string, PyBlade Live will fill the textarea with that value - there's no need to do something like the following:
<!-- Warning: This snippet demonstrates what NOT to do... -->
<textarea type="text" pb-model="content">{{ content }}</textarea> [!code --]Checkboxes
Checkboxes can be used for single values, such as when toggling a boolean property. Or, checkboxes may be used to toggle a single value in a group of related values. We'll discuss both scenarios:
Single checkbox
At the end of a signup form, you might have a checkbox allowing the user to opt-in to email updates. You might call this property receive_updates. You can easily bind this value to the checkbox using pb-model:
<input type="checkbox" pb-model="receiveUpdates">Now when the receive_updates value is False, the checkbox will be unchecked. Of course, when the value is True, the checkbox will be checked.
Multiple checkboxes
Now, let's say in addition to allowing the user to decide to receive updates, you have a list property in your class called update_types, allowing the user to choose from a variety of update types:
update_types: list[str] = []By binding multiple checkboxes to the update_types property, the user can select multiple update types and they will be added to the update_types list property:
<input type="checkbox" value="email" pb-model="update_types">
<input type="checkbox" value="sms" pb-model="update_types">
<input type="checkbox" value="notification" pb-model="update_types">For example, if the user checks the first two boxes but not the third, the value of update_types will be: ["email", "sms"]
Radio buttons
To toggle between two or more different values for a single property, you may use radio buttons:
<input type="radio" value="yes" pb-model="receive_updates">
<input type="radio" value="no" pb-model="receive_updates">Select dropdowns
PyBlade Live makes it simple to work with <select> dropdowns. When adding pb-model to a dropdown, the currently selected value will be bound to the provided property name and vice versa.
In addition, there's no need to manually add selected to the option that will be selected - PyBlade Live handles that for you automatically.
Below is an example of a select dropdown filled with a static list of states:
<select pb-model="state">
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
...
</select>When a specific state is selected, for example, "Alaska", the state property on the component will be set to AK. If you would prefer the value to be set to "Alaska" instead of "AK", you can leave the value="" attribute off the <option> element entirely.
Often, you may build your dropdown options dynamically using PyBlade:
<select pb-model="state">
@for (state in state_choices)
<option value="{{ state.id }}">{{ state.label }}</option>
@endfor
</select>If you don't have a specific option selected by default, you may want to show a muted placeholder option by default, such as "Select a state":
<select pb-model="state">
<option disabled value="">Select a state...</option>
@for(state in state_choices)
<option value="{{ state.id }}">{{ state.label }}</option>
@endfor
</select>As you can see, there is no "placeholder" attribute for a select menu like there is for text inputs. Instead, you have to add a disabled option element as the first option in the list.
Dependent select dropdowns
Sometimes you may want one select menu to be dependent on another. For example, a list of cities that changes based on which state is selected.
For the most part, this works as you'd expect, however there is one important gotcha: You must add a pb-key to the changing select so that PyBlade Live properly refreshes its value when the options change.
Here's an example of two selects, one for states, one for cities. When the state select changes, the options in the city select will change properly:
<!-- States select menu... -->
<select pb-model.live="selected_state">
@for(state in country.states)
<option value="{{ state.id }}">{{ state.label }}</option>
@endfor
</select>
<!-- Cities dependent select menu... -->
<select pb-model.live="selected_city" pb-key="{{ selected_state }}">
@for (city in selected_state.cities)
<option value="{{ city.id }}">{{ city.label }}</option>
@endfor
</select>Again, the only thing non-standard here is the pb-key that has been added to the second select. This ensures that when the state changes, the "selected_city" value will be reset properly.
Multi-select dropdowns
If you are using a "multiple" select menu, PyBlade Live works as expected. In this example, states will be added to the states list property when they are selected and removed if they are deselected:
<select pb-model="states" multiple>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
...
</select>Going deeper
For a more complete documentation on using pb-model in the context of HTML forms, visit the PyBlade Live forms documentation page.
pb-loading
Loading indicators are an important part of crafting good user interfaces. They give users visual feedback when a request is being made to the server, so they know they are waiting for a process to complete.
Basic usage
PyBlade Live provides a simple yet extremely powerful syntax for controlling loading indicators: pb-loading. Adding pb-loading to any element will hide it by default (using display: none in CSS) and show it when a request is sent to the server.
Below is a basic example of a PostCreate component's form with pb-loading being used to toggle a loading message:
<form pb-submit="save">
<!-- ... -->
<button type="submit">Save</button>
<div pb-loading>
Saving post...
</div>
</form>When a user presses "Save", the "Saving post..." message will appear below the button while the "save" action is being executed. The message will disappear when the response is received from the server and processed by PyBlade Live.
Removing elements
Alternatively, you can append .remove for the inverse effect, showing an element by default and hiding it during requests to the server:
<form pb-submit="save">
<!-- ... -->
<button type="submit">
<span pb-loading.remove>Save</span>
<span pb-loading>Saving post...</span>
</button>
</form>Toggling classes
In addition to toggling the visibility of entire elements, it's often useful to change the styling of an existing element by toggling CSS classes on and off during requests to the server. This technique can be used for things like changing background colors, lowering opacity, triggering spinning animations, and more.
Below is a simple example of using the Tailwind class opacity-50 to make the "Save" button fainter while the form is being submitted:
<button pb-loading.class="opacity-50">Save</button>Like toggling an element, you can perform the inverse class operation by appending .remove to the pb-loading directive. In the example below, the button's bg-blue-500 class will be removed when the "Save" button is pressed:
<button class="bg-blue-500" pb-loading.class.remove="bg-blue-500">
Save
</button>Toggling attributes
By default, when a form is submitted, PyBlade Live will automatically disable the submit button and add the readonly attribute to each input element while the form is being processed.
However, in addition to this default behavior, PyBlade Live offers the .attr modifier to allow you to toggle other attributes on an element or toggle attributes on elements that are outside of forms:
<button
type="button"
pb-click="remove"
pb-loading.attr="disabled"
>
Remove
</button>Because the button above isn't a submit button, it won't be disabled by PyBlade Live's default form handling behavior when pressed. Instead, we manually added pb-loading.attr="disabled" to achieve this behavior.
Targeting specific actions
By default, pb-loading will be triggered whenever a component makes a request to the server.
However, in components with multiple elements that can trigger server requests, you should scope your loading indicators down to individual actions.
For example, consider the following "Save post" form. In addition to a "Save" button that submits the form, there might also be a "Remove" button that executes a "remove" action on the component.
By adding pb-target to the following pb-loading element, you can instruct PyBlade Live to only show the loading message when the "Remove" button is clicked:
<form pb-submit="save">
<!-- ... -->
<button type="submit">Save</button>
<button type="button" pb-click="remove">Remove</button>
<div pb-loading pb-target="remove">
Removing post...
</div>
</form>When the above "Remove" button is pressed, the "Removing post..." message will be displayed to the user. However, the message will not be displayed when the "Save" button is pressed.
Targeting multiple actions
You may find yourself in a situation where you would like pb-loading to react to some, but not all, actions on a page. In these cases you can pass multiple actions into pb-target separated by a comma. For example:
<form pb-submit="save">
<input type="text" pb-model.blur="title">
<!-- ... -->
<button type="submit">Save</button>
<button type="button" pb-click="remove">Remove</button>
<div pb-loading pb-target="save, remove">
Updating post...
</div>
</form>The loading indicator ("Updating post...") will now only be shown when the "Remove" or "Save" button are pressed, and not when the title field is being sent to the server.
Targeting action parameters
In situations where the same action is triggered with different parameters from multiple places on a page, you can further scope pb-target to a specific action by passing in additional parameters. For example, consider the following scenario where a "Remove" button exists for each post on the page:
<div>
@for(post in posts)
<div pb-key="{{ post.id }}">
<h2>{{ post.title }}</h2>
<button pb-click="remove({{ post.id }})">Remove</button>
<div pb-loading pb-target="remove({{ post.id }})"> <!-- [! highlight:2] -->
Removing post...
</div>
</div>
@endfor
</div>Without passing {{ post.id }} to pb-target="remove", the "Removing post..." message would show when any of the buttons on the page are clicked.
However, because we are passing in unique parameters to each instance of pb-target, PyBlade Live will only show the loading message when the matching parameters are passed to the "remove" action.
Multiple action parameters aren't supported
At this time, PyBlade Live only supports targeting a loading indicator by a single method/parameter pair. For example pb-target="remove(1)" is supported, however pb-target="remove(1), add(1)" is not.
Targeting property updates
PyBlade Live also allows you to target specific component property updates by passing the property's name to the pb-target directive.
Consider the following example where a form input named username uses pb-model.live for real-time validation as a user types:
<form pb-submit="save">
<input type="text" pb-model.live="username">
@error('username') <span>{{ message }}</span> @enderror
<div pb-loading pb-target="username">
Checking availability of username...
</div>
<!-- ... -->
</form>The "Checking availability..." message will show when the server is updated with the new username as the user types into the input field.
Excluding specific loading targets
Sometimes you may wish to display a loading indicator for every PyBlade Live request except a specific property or action. In these cases you can use the pb-target.except modifier like so:
<div pb-loading pb-target.except="download">...</div>The above loading indicator will now be shown for every PyBlade Live update request on the component except the "download" action.
Customizing CSS display property
When pb-loading is added to an element, PyBlade Live updates the CSS display property of the element to show and hide the element. By default, PyBlade Live uses none to hide and inline-block to show.
If you are toggling an element that uses a display value other than inline-block, like flex in the following example, you can append .flex to pb-loading:
<div class="flex" pb-loading.flex>...</div>Below is the complete list of available display values:
<div pb-loading.inline-flex>...</div>
<div pb-loading.inline>...</div>
<div pb-loading.block>...</div>
<div pb-loading.table>...</div>
<div pb-loading.flex>...</div>
<div pb-loading.grid>...</div>Delaying a loading indicator
On fast connections, updates often happen so quickly that loading indicators only flash briefly on the screen before being removed. In these cases, the indicator is more of a distraction than a helpful affordance.
For this reason, PyBlade Live provides a .delay modifier to delay the showing of an indicator. For example, if you add pb-loading.delay to an element like so:
<div pb-loading.delay>...</div>The above element will only appear if the request takes over 200 milliseconds. The user will never see the indicator if the request completes before then.
To customize the amount of time to delay the loading indicator, you can use one of PyBlade Live's helpful interval aliases:
<div pb-loading.delay.shortest>...</div> <!-- 50ms -->
<div pb-loading.delay.shorter>...</div> <!-- 100ms -->
<div pb-loading.delay.short>...</div> <!-- 150ms -->
<div pb-loading.delay>...</div> <!-- 200ms -->
<div pb-loading.delay.long>...</div> <!-- 300ms -->
<div pb-loading.delay.longer>...</div> <!-- 500ms -->
<div pb-loading.delay.longest>...</div> <!-- 1000ms -->pb-navigate
PyBlade Live's pb-navigate feature makes page navigation much faster, providing an SPA-like experience for your users.
This page is a simple reference for the pb-navigate directive. Be sure to read the page on PyBlade Live's Navigation feature for more complete documentation.
Below is a simple example of adding pb-navigate to links in a nav bar:
<nav>
<a href="/" pb-navigate>Dashboard</a>
<a href="/posts" pb-navigate>Posts</a>
<a href="/users" pb-navigate>Users</a>
</nav>When any of these links are clicked, PyBlade Live will intercept the click and, instead of allowing the browser to perform a full page visit, PyBlade Live will fetch the page in the background and swap it with the current page (resulting in much faster and smoother page navigation).
Prefetching pages on hover
By adding the .hover modifier, PyBlade Live will pre-fetch a page when a user hovers over a link. This way, the page will have already been downloaded from the server when the user clicks on the link.
<a href="/" pb-navigate.hover>Dashboard</a>Going deeper
For more complete documentation on this feature, visit PyBlade Live's navigation documentation page.
pb-current
The pb-current directive allows you to easily detect and style currently active links on a page.
Here's a simple example of adding pb-current to links in a navbar so that the currently active link has a stronger font weight:
<nav>
<a href="/dashboard" ... pb-current="font-bold text-zinc-800">Dashboard</a>
<a href="/posts" ... pb-current="font-bold text-zinc-800">Posts</a>
<a href="/users" ... pb-current="font-bold text-zinc-800">Users</a>
</nav>Now when a user visits /posts, the "Posts" link will have a stronger font treatment than the other links.
You should note that pb-current works out of the box with pb-navigate links and page changes.
Troubleshooting
If pb-current is not detecting the current link correctly, ensure the following:
- You have at least one PyBlade Live component on the page, or have hardcoded
@liveblade_scriptsin your layout - You have a
hrefattribute on the link.
pb-cloak
pb-cloak is a directive that hides elements on page load until PyBlade Live is fully initialized. This is useful for preventing the "flash of unstyled content" that can occur when the page loads before PyBlade Live has a chance to initialize.
Basic usage
To use pb-cloak, add the directive to any element you want to hide during page load:
<div pb-cloak>
This content will be hidden until PyBlade Live is fully loaded
</div>Dynamic content
pb-cloak is particularly useful in scenarios where you want to prevent users from seeing uninitialized dynamic content such as element shown or hidden using pb-show.
<div>
<div pb-show="starred" pb-cloak>
<!-- Yellow star icon... -->
</div>
<div pb-show="!starred" pb-cloak>
<!-- Gray star icon... -->
</div>
</div>In the above example, without pb-cloak, both icons would be shown before PyBlade Live initializes. However, with pb-cloak, both elements will be hidden until initialization.
pb-dirty
In a traditional HTML page containing a form, the form is only ever submitted when the user presses the "Submit" button.
However, PyBlade Live is capable of much more than traditional form submissions. You can validate form inputs in real-time or even save the form as a user types.
In these "real-time" update scenarios, it can be helpful to signal to your users when a form or subset of a form has been changed, but hasn't been saved to the database.
When a form contains un-saved input, that form is considered "dirty". It only becomes "clean" when a network request has been triggered to synchronize the server state with the client-side state.
Basic usage
PyBlade Live allows you to easily toggle visual elements on the page using the pb-dirty directive.
By adding pb-dirty to an element, you are instructing PyBlade Live to only show the element when the client-side state diverges from the server-side state.
To demonstrate, here is an example of an UpdatePost form containing a visual "Unsaved changes..." indication that signals to the user that the form contains input that has not been saved:
<form pb-submit="update">
<input type="text" pb-model="title">
<!-- ... -->
<button type="submit">Update</button>
<div pb-dirty>Unsaved changes...</div>
</form>Because pb-dirty has been added to the "Unsaved changes..." message, the message will be hidden by default. PyBlade Live will automatically display the message when the user starts modifying the form inputs.
When the user submits the form, the message will disappear again, since the server / client data is back in sync.
Removing elements
By adding the .remove modifier to pb-dirty, you can instead show an element by default and only hide it when the component has "dirty" state:
<div pb-dirty.remove>The data is in-sync...</div>Targeting property updates
Imagine you are using pb-model.blur to update a property on the server immediately after a user leaves an input field. In this scenario, you can provide a "dirty" indication for only that property by adding pb-target to the element that contains the pb-dirty directive.
Here is an example of only showing a dirty indication when the title property has been changed:
<form pb-submit="update">
<input pb-model.blur="title">
<div pb-dirty pb-target="title">Unsaved title...</div>
<button type="submit">Update</button>
</form>Toggling classes
Often, instead of toggling entire elements, you may want to toggle individual CSS classes on an input when its state is "dirty". For this purpose, use the .classmodifier on the dirty directive.
Below is an example where a user types into an input field and the border becomes yellow, indicating an "unsaved" state. Then, when the user tabs away from the field, the border is removed, indicating that the state has been saved on the server:
<input pb-model.blur="title" pb-dirty.class="border-yellow-500">pb-confirm
Before performing dangerous actions in PyBlade Live, you may want to provide your users with some sort of visual confirmation.
PyBlade Live makes this easy to do by adding pb-confirm in addition to any action (pb-click, pb-submit, etc.).
Here's an example of adding a confirmation dialog to a "Delete post" button:
<button
type="button"
pb-click="delete"
pb-confirm="Are you sure you want to delete this post?"
>
Delete post
</button>When a user clicks "Delete post", PyBlade Live will trigger a confirmation dialog containing a "Cancel" button and a confirmation button containg the same text as the dialog trigger button (for instance "Delete post"). If the user hits escape or presses the "Cancel", the action won't be performed. If they press the confirmation button, the action will be completed.
Customizing the Confirmation dialogue
Of course, you can change che confirmation dialogue design to meet your own. Run the pyblade live:stubs --confirm. This command will add a component named "confirm-dialogue", design you can can re-design it to feet your needs and PyBlade Live will use it for confirmation dialogues.
Using the @confirm decorator
For security renforcement, you may use the @confirm decorator inside your PyBlade Live's component class.
Imagine you have a PostDelete component where a user can delete a blog post. To prevent accidental or malicious deletions, you can enforce a confirmation step using the @confirm decorator.
from pyblade import Component, confirm
class PostDelete(Component):
post_id = None
def mount(self, post_id):
self.post_id = post_id
@confirm("Are you sure you want to delete this post?")
def delete(self):
post = Post.objects.get(self.post_id)
post.delete()
self.notify("The post has been deleted.")<button pb-click="delete" class="btn btn-danger">
Delete Post
</button>This is ideal for destructive actions like deleting users, posts, or clearing data.
pb-transition
Basic usage
Showing or hiding content in PyBlade Live is as simple as using one of PyBlade's conditional directives like @if. To enhance this experience for your users, PyBlade Live provides a pb-transition directive that allows you to transition conditional elements smoothly in and out of the page.
For example, below is a PostDetail component with the ability to toggle viewing comments on and off:
from pyblade import live
class PostDetail(live.Component):
show_comments = False
...<div>
<!-- ... -->
<button pb-click="set('show_comments', True)">Show comments</button>
@if (show_comments)
<div pb-transition>
@for (comment in post.comments)
<!-- ... -->
@endfor
</div>
@endif
</div>Because pb-transition has been added to the <div> containing the post's comments, when the "Show comments" button is pressed, show_comments will be set to True and the comments will "fade" onto the page instead of abruptly appearing.
Limitations
Currently, pb-transition is only supported on a single element inside a PyBlade conditional like @if. It will not work as expected when used in a list of sibling elements. For example, the following will NOT work properly:
<!-- Warning: The following is code that will not work properly -->
<ul>
@for (commnet in comments)
<li pb-transition pb-key="{{ comment.id }}">{{ comment.content }}</li>
@endfor
</ul>If one of the above comment <li> elements were to get removed, you would expect PyBlade Live to transition it out. However, because of hurdles with PyBlade Live's underlying "morph" mechanism, this will not be the case. There is currently no way to transition dynamic lists in PyBlade Live using pb-transition.
Default transition style
By default, PyBlade Live applies both an opacity and a scale CSS transition to elements with pb-transition. Here's a visual preview:
<div pb-data="{ show: false }" pb-cloak class="border border-gray-700 rounded-xl p-6 w-full flex justify-between">
<a href="#" pb-on:click.prevent="show = ! show" class="py-2.5 outline-none">
Preview transition <span pb-text="show ? 'out' : 'in →'">in</span>
</a>
<div class="hey">
<div
pb-show="show"
pb-transition
class="inline-flex px-16 py-2.5 rounded-[10px] bg-pink-400 text-white uppercase font-medium transition focus-visible:outline-none focus-visible:!ring-1 focus-visible:!ring-white"
style="
background: linear-gradient(109.48deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 100%), #EE5D99;
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.5), inset 0px 1px 0px rgba(255, 255, 255, 0.1);
"
>
</div>
</div>
</div>The above transition uses the following values for transitioning by default:
| Transition in | Transition out |
|---|---|
duration: 150ms | duration: 75ms |
opacity: [0 - 100] | opacity: [100 - 0] |
transform: scale([0.95 - 1]) | transform: scale([1 - 0.95]) |
Customizing transitions
To customize the CSS PyBlade Live internally uses when transitioning, you can use any combination of the available modifiers:
| Modifier | Description |
|---|---|
.in | Only transition the element "in" |
.out | Only transition the element "out" |
.duration.Xms | Customize the transition duration inX milliseconds |
.duration.Xs | Customize the transition duration in X seconds |
.delay.Xms | Customize the transition delay in X milliseconds |
.delay.Xs | Customize the transition delay in X seconds |
.opacity | Only apply the opacity transition |
.scale | Only apply the scale transition |
.origin.[top|bottom|left|right] | Customize the scale "origin" used |
Below is a list of various transition combinations that may help to better visualize these customizations:
Fade-only transition
By default, PyBlade Live both fades and scales the element when transitioning. You can disable scaling and only fade by adding the .opacity modifier. This is useful for things like transitioning a full-page overlay, where adding a scale doesn't make sense.
<div pb-transition.opacity><div pb-data="{ show: false }" pb-cloak class="border border-gray-700 rounded-xl p-6 w-full flex justify-between">
<a href="#" pb-on:click.prevent="show = ! show" class="py-2.5 outline-none">
Preview transition <span pb-text="show ? 'out' : 'in →'">in</span>
</a>
<div class="hey">
<div
pb-show="show"
pb-transition.opacity
class="inline-flex px-16 py-2.5 rounded-[10px] bg-pink-400 text-white uppercase font-medium transition focus-visible:outline-none focus-visible:!ring-1 focus-visible:!ring-white"
style="
background: linear-gradient(109.48deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 100%), #EE5D99;
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.5), inset 0px 1px 0px rgba(255, 255, 255, 0.1);
"
>
...
</div>
</div>
</div>Fade-out transition
A common transition technique is to show an element immediately when transitioning in, and fade its opacity when transitioning out. You'll notice this effect on most native MacOS dropdowns and menus. Therefore it's commonly applied on the web to dropdowns, popovers, and menus.
<div pb-transition.out.opacity.duration.200ms><div pb-data="{ show: false }" pb-cloak class="border border-gray-700 rounded-xl p-6 w-full flex justify-between">
<a href="#" pb-on:click.prevent="show = ! show" class="py-2.5 outline-none">
Preview transition <span pb-text="show ? 'out' : 'in →'">in</span>
</a>
<div class="hey">
<div
pb-show="show"
pb-transition.out.opacity.duration.200ms
class="inline-flex px-16 py-2.5 rounded-[10px] bg-pink-400 text-white uppercase font-medium transition focus-visible:outline-none focus-visible:!ring-1 focus-visible:!ring-white"
style="
background: linear-gradient(109.48deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 100%), #EE5D99;
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.5), inset 0px 1px 0px rgba(255, 255, 255, 0.1);
"
>
...
</div>
</div>
</div>Origin-top transition
When using PyBlade Live to transition an element such as a dropdown menu, it makes sense to scale in from the top of the menu as the origin, rather than center (PyBlade Live's default). This way the menu feels visually anchored to the element that triggered it.
<div pb-transition.scale.origin.top><div pb-data="{ show: false }" pb-cloak class="border border-gray-700 rounded-xl p-6 w-full flex justify-between">
<a href="#" pb-on:click.prevent="show = ! show" class="py-2.5 outline-none">
Preview transition <span pb-text="show ? 'out' : 'in →'">in</span>
</a>
<div class="hey">
<div
pb-show="show"
pb-transition.origin.top
class="inline-flex px-16 py-2.5 rounded-[10px] bg-pink-400 text-white uppercase font-medium transition focus-visible:outline-none focus-visible:!ring-1 focus-visible:!ring-white"
style="
background: linear-gradient(109.48deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 100%), #EE5D99;
box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.5), inset 0px 1px 0px rgba(255, 255, 255, 0.1);
"
>
...
</div>
</div>
</div>pb-poll
Polling is a technique used in web applications to "poll" the server (send requests on a regular interval) for updates. It's a simple way to keep a page up-to-date without the need for a more sophisticated technology like WebSockets.
Basic usage
Using polling inside PyBlade Live is as simple as adding pb-poll to an element.
Below is an example of a SubscriberCount component that shows a user's subscriber count:
from pyblade import live, auth
class SubscriberCount(live.Component):
def render(self):
user = auth()
return self.view('live.subscriber-count', [
'count' => user.subscribers.count(),
]);<div pb-poll>
Subscribers: {{ count }}
</div>Normally, this component would show the subscriber count for the user and never update until the page was refreshed. However, because of pb-poll on the component's template, this component will now refresh itself every 2.5 seconds, keeping the subscriber count up-to-date.
You can also specify an action to fire on the polling interval by passing a value to pb-poll:
<div pb-poll="refresh_subscribers">
Subscribers: {{ count }}
</div>Now, the refresh_subscribers() method on the component will be called every 2.5 seconds.
Timing control
The primary drawback of polling is that it can be resource intensive. If you have a thousand visitors on a page that uses polling, one thousand network requests will be triggered every 2.5 seconds.
The best way to reduce requests in this scenario is simply to make the polling interval longer.
Warning
Please checkout the PyBlade Live's Security concerns before using this feature as it may lead to some security risks when misused.
You can manually control how often the component will poll by appending the desired duration to pb-poll like so:
<div pb-poll.15s> <!-- In seconds... -->
<div pb-poll.15000ms> <!-- In milliseconds... -->Background throttling
To further cut down on server requests, PyBlade Live automatically throttles polling when a page is in the background. For example, if a user keeps a page open in a different browser tab, PyBlade Live will reduce the number of polling requests by 95% until the user revisits the tab.
If you want to opt-out of this behavior and keep polling continuously, even when a tab is in the background, you can add the .keep-alive modifier to pb-poll:
<div pb-poll.keep-alive>Viewport throttling
Another measure you can take to only poll when necessary, is to add the .visible modifier to pb-poll. The .visible modifier instructs PyBlade Live to only poll the component when it is visible on the page:
<div pb-poll.visible>If a component using pb-visible is at the bottom of a long page, it won't start polling until the user scrolls it into the viewport. When the user scrolls away, it will stop polling again.
pb-offline
In certain circumstances it can be helpful for your users to know if they are currently connected to the internet.
If for example, you have built a blogging platform on PyBlade Live, you may want to notify your users in some way if they are offline so that they don't draft an entire blog post without the ability for PyBlade Live to save it to the database.
PyBlade Live make this trivial by providing the pb-offline directive. By attaching pb-offline to an element in your PyBlade Live component, it will be hidden by default and only be displayed when PyBlade Live detects the network connection has been interrupted and is unavailable. It will then disappear again when the network has regained connection.
For example:
<p class="alert alert-warning" pb-offline>
Whoops, your device has lost connection. The web page you are viewing is offline.
</p>pb-ignore
PyBlade Live's ability to make updates to the page is what makes it "live", however, there are times when you might want to prevent PyBlade Live from updating a portion of the page.
In these cases, you can use the pb-ignore directive to instruct PyBlade Live to ignore the contents of a particular element, even if they change between requests.
This is most useful in the context of working with third-party javascript libraries for custom form inputs and such.
Below is an example of wrapping an element used by a third-party library in pb-ignore so that PyBlade Live doesn't tamper with the HTML generated by the library:
<form>
<!-- ... -->
<div pb-ignore>
<!-- This element would be reference by a -->
<!-- third-party library for initialization... -->
<input id="id-for-date-picker-library">
</div>
<!-- ... -->
</form>You can also instruct PyBlade Live to only ignore changes to attributes of the root element rather than observing changes to its contents using pb-ignore.attrs.
<div pb-ignore.attrs>
<!-- ... -->
</div>pb-replace
PyBlade Live's DOM diffing is useful for updating existing elements on your page, but occasionally you may need to force some elements to render from scratch to reset internal state.
In these cases, you can use the pb-replace directive to instruct PyBlade Live to skip DOM diffing on the children of an element, and instead completely replace the content with the new elements from the server.
This is most useful in the context of working with third-party javascript libraries and custom web components, or when element re-use could cause problems when keeping state.
Below is an example of wrapping a web component with a shadow DOM pb-replace so that PyBlade Live completely replaces the element allowing the custom element to handle its own life-cycle:
<form>
<!-- ... -->
<div pb-replace>
<!-- This custom element would have its own internal state -->
<json-viewer>@json(some_property)</json-viewer>
</div>
<!-- ... -->
</form>You can also instruct PyBlade Live to replace the target element as well as all children with pb-replace.self.
<div pb-data="{open: False}" pb-replace.self>
<!-- Ensure that the "open" state is reset to F alse on each render -->
</div>pb-show
PyBlade Live's pb-show directive makes it easy to show and hide elements based on the result of a boolean expression.
The pb-show directive is different than using @if in PyBlade in that it toggles an element's visibility using CSS (display: none) rather than entirely removing the element from the DOM. This means the element remains in the page but is hidden, allowing for smoother transitions without requiring a server round-trip.
Basic usage
Here's a practical example of using pb-show to toggle a "Create Post" modal:
from pyblade import live
from app.models import Post
class PostCreate(live.Component):
show_modal: bool = False
content: str = ''
def save(self):
Post.objects.create(content=self.content)
self.reset('content');
self.show_modal = False;<div>
<button pb-click="py.show_modal = True">New Post</button>
<div pb-show="show_modal">
<form pb-submit="save">
<textarea pb-model="content"></textarea>
<button type="submit">Save Post</button>
</form>
</div>
</div>When the "New Post" button is clicked, the modal appears without a server roundtrip. After successfully saving the post, the modal is hidden and the form is reset.
Using transitions
You can combine pb-show transitions to create smooth show/hide animations. Since pb-show only toggles the CSS display property, PyBlade Live's pb-transition directive work perfectly with it:
<div>
<button pb-on:click="py.show_modal = True">New Post</button>
<div pb-show="show_modal" pb-transition.duration.500ms>
<form pb-submit="save">
<textarea pb-model="content"></textarea>
<button type="submit">Save Post</button>
</form>
</div>
</div>The transition classes above will create a fade and scale effect when the modal shows and hides.
You can review the full pb-transition documentation.
pb-stream
PyBlade Live allows you to stream content to a web page before a request is complete via the pb-stream API. This is an extremely useful feature for things like AI chat-bots which stream responses as they are generated.
To demonstrate the most basic functionality of pb-stream, below is a simple CountDown component that when a button is pressed displays a count-down to the user from 3 to 0:
import time
from pyblade import live
class CountDown(live.Component):
start: int = 3
def begin(self):
while self.start >= 0:
# Stream the current count to the browser...
self.stream(
to="count",
content=self.start,
replace=True,
)
# Pause for 1 second between numbers...
time.sleep(1)
# Decrement the counter...
self.start -= 1
def render(self):
return self.view("live.count-down")<div>
<button pb-click="begin">Start count-down</button>
<h1>Count: <span pb-stream="count">{{ start }}</span></h1>
</div>Here's what's happening from the user's perspective when they press "Start count-down":
- "Count: 3" is shown on the page
- They press the "Start count-down" button
- One second elapses and "Count: 2" is shown
- This process continues until "Count: 0" is shown
INFO
All of the above happens while a single network request is out to the server.
Here's what's happening from the system's perspective when the button is pressed:
- A request is sent to PyBlade Live to call the
begin()method - The
begin()method is called and thewhileloop begins self.stream()is called and immediately starts a "streamed response" to the browser- The browser receives a streamed response with instructions to find the element in the component with
pb-stream="count", and replace its contents with the received payload (3in the case of the first streamed number) - The
time.sleep(1)method causes the server to hang for one second - The
whileloop is repeated and the process of streaming a new number every second continues until thewhilecondition is falsy - When
begin()has finished running and all the counts have been streamed to the browser, PyBlade Live finishes it's request lifecycle, rendering the component and sending the final response to the browser
Streaming chat-bot responses
A common use-case for pb-stream is streaming chat-bot responses as they are received from an API that supports streamed responses (like OpenAI's ChatGPT).
Below is an example of using pb-stream to accomplish a ChatGPT-like interface:
import openai # You need to install this (pip install openai)
from pyblade import live
openai.api_key = "your-api-key" # Replace with your actual OpenAI API Key
class ChatBot(live.Component):
prompt: str = ""
question: str = ""
answer: str = ""
def submit_prompt(self):
self.question = self.prompt
self.prompt = ""
self.js("py.ask()")
def ask(self):
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": self.question}],
stream=True
)
for chunk in response:
if "choices" in chunk and chunk["choices"]:
self.anwser = chunk["choices"][0]["delta"].get("content", "")
self.stream(to="answer", content: self.answer);
def render(self):
self.view("live.chat-bot")<div>
<section>
<div>ChatBot</div>
@if (question)
<article>
<hgroup>
<h3>User</h3>
<p>{{ question }}</p>
</hgroup>
<hgroup>
<h3>ChatBot</h3>
<p pb-stream="answer">{{ answer }}</p>
</hgroup>
</article>
@endif
</section>
<form pb-submit="submit_prompt">
<input pb-model="prompt" type="text" placeholder="Send a message" autofocus>
</form>
</div>Here's what's going on in the above example:
- A user types into a text field labeled "Send a message" to ask the chat-bot a question.
- They press the Enter key.
- A network request is sent to the server, sets the message to the
questionproperty, and clears thepromptproperty. - The response is sent back to the browser and the input is cleared. Because
self.js('...')was called, a new request is triggered to the server calling theask()method. - The
ask()method calls on the OpenAI API and receives streamed response via thestream=Trueargument in theopenai.ChatCompletion.create()method. - Each
chunkgets streamed to the browser into thepb-stream="answer"element on the page, showing the answer progressively reveal itself to the user. - When the entire response is received, the PyBlade Live request finishes and the user receives the full response.
Replace vs. append
When streaming content to an element using self.stream(), you can tell PyBlade Live to either replace the contents of the target element with the streamed contents or append them to the existing contents.
Replacing or appending can both be desirable depending on the scenario. For example, when streaming a response from a chatbot, typically appending is desired (and is therefore the default). However, when showing something like a count-down, replacing is more fitting.
You can configure either by passing the replace parameter to self.stream with a boolean value:
# Append contents...
self.stream(to='target', content="...")
# Replace contents...
self.stream(to='target', content="...", replace=True)Append/replace can also be specified at the target element level by appending or removing the .replace modifier:
<!-- Append contents... -->
<div pb-stream="target">
<!-- Replace contents... -->
<div pb-stream.replace="target">pb-text
pb-text is a directive that dynamically updates an element's text content based on a component property or a boolean expression. Unlike using PyBlade's {{ }} syntax, pb-text updates the content without requiring a network roundtrip to re-render the component.
Basic usage
Here's an example of using pb-text to optimistically show updates to a PyBlade Live property without waiting for a network roundtrip.
from pyblade import live
from app.models import Post
class PostDetail(live.Component):
post: Post
likes: int
def mount(self):
self.likes = self.post.like_count
def like(self):
self.post.like()
self.likes = self.post.fresh().like_count;<div>
<button b:click="py.likes++" pb-click="like">❤️ Like</button>
Likes: <span pb-text="likes"></span>
</div>When the button is clicked, py.likes++ immediately updates the displayed count through pb-text, while pb-click="like" persists the change to the database in the background.
This pattern makes pb-text perfect for building optimistic UIs in PyBlade Live.