mirror of
https://github.com/Django-LiveView/docs.git
synced 2025-12-14 14:36:28 +01:00
Improve handlers documentation with FastAPI-style examples
Some checks failed
Gitea Actions Deploy / deploy (push) Has been cancelled
Some checks failed
Gitea Actions Deploy / deploy (push) Has been cancelled
- Add progressive examples from simple to complex - Include both Python and HTML code for each example - Add template examples for complex use cases - Fix data-data-* attributes to use snake_case (not camelCase) - Add detailed explanations for each feature - Improve readability and learning curve 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
382
one.org
382
one.org
@@ -177,38 +177,150 @@ We strongly recommend that you follow the [[#/quick-start/][Quick start]] to see
|
|||||||
|
|
||||||
Handlers are Python functions that respond to WebSocket messages from the client. They contain your business logic and can render HTML, update the database, broadcast to multiple users, and more.
|
Handlers are Python functions that respond to WebSocket messages from the client. They contain your business logic and can render HTML, update the database, broadcast to multiple users, and more.
|
||||||
|
|
||||||
** Creating Handlers
|
** Your First Handler
|
||||||
|
|
||||||
Use the ~@liveview_handler~ decorator to register functions that can be called from the client:
|
Let's start with the simplest possible handler. This handler will be called when a button is clicked and will update a section of the page.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
from liveview import liveview_handler, send
|
from liveview import liveview_handler, send
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
@liveview_handler("say_hello")
|
@liveview_handler("say_hello")
|
||||||
def say_hello(consumer, content):
|
def say_hello(consumer, content):
|
||||||
"""
|
send(consumer, {
|
||||||
Args:
|
"target": "#greeting",
|
||||||
consumer: WebSocket consumer instance
|
"html": "<p>Hello, World!</p>"
|
||||||
content: dict with:
|
|
||||||
- function: str - the function name
|
|
||||||
- data: dict - custom data from data-data-* attributes
|
|
||||||
- form: dict - form input values
|
|
||||||
- lang: str - current language
|
|
||||||
- room: str - room identifier
|
|
||||||
"""
|
|
||||||
name = content.get("form", {}).get("name", "World")
|
|
||||||
|
|
||||||
html = render_to_string("hello_message.html", {
|
|
||||||
"message": f"Hello, {name}!"
|
|
||||||
})
|
})
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div>
|
||||||
|
<div id="greeting"></div>
|
||||||
|
<button
|
||||||
|
data-liveview-function="say_hello"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Say Hello
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
When you click the button:
|
||||||
|
1. The frontend sends a WebSocket message with ~function: "say_hello"~
|
||||||
|
2. Django LiveView calls the ~say_hello~ handler
|
||||||
|
3. The handler sends back HTML to replace the content of ~#greeting~
|
||||||
|
4. The page updates instantly without a full reload
|
||||||
|
|
||||||
|
** Working with Form Data
|
||||||
|
|
||||||
|
Most handlers need to receive data from the user. All form inputs within the same container are automatically sent to your handler.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
@liveview_handler("greet_user")
|
||||||
|
def greet_user(consumer, content):
|
||||||
|
# Get the name from form data
|
||||||
|
name = content["form"].get("name", "Anonymous")
|
||||||
|
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": "#greeting",
|
"target": "#greeting",
|
||||||
|
"html": f"<p>Hello, {name}! Welcome to Django LiveView.</p>"
|
||||||
|
})
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div>
|
||||||
|
<div id="greeting"></div>
|
||||||
|
<input type="text" name="name" placeholder="Enter your name">
|
||||||
|
<button
|
||||||
|
data-liveview-function="greet_user"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Greet Me
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The ~content["form"]~ dictionary contains all input values with their ~name~ attribute as the key.
|
||||||
|
|
||||||
|
** Using Templates for Complex HTML
|
||||||
|
|
||||||
|
For anything beyond simple strings, use Django templates to render your HTML.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
@liveview_handler("show_profile")
|
||||||
|
def show_profile(consumer, content):
|
||||||
|
user_id = content["form"].get("user_id")
|
||||||
|
|
||||||
|
# Get user from database
|
||||||
|
from .models import User
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
|
||||||
|
# Render template with context
|
||||||
|
html = render_to_string("profile_card.html", {
|
||||||
|
"user": user,
|
||||||
|
"is_online": True
|
||||||
|
})
|
||||||
|
|
||||||
|
send(consumer, {
|
||||||
|
"target": "#profile-container",
|
||||||
"html": html
|
"html": html
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div>
|
||||||
|
<div id="profile-container"></div>
|
||||||
|
<input type="hidden" name="user_id" value="123">
|
||||||
|
<button
|
||||||
|
data-liveview-function="show_profile"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Load Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Template (~profile_card.html~):
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div class="profile-card">
|
||||||
|
<img src="{{ user.avatar }}" alt="{{ user.name }}">
|
||||||
|
<h3>{{ user.name }}</h3>
|
||||||
|
<p>{{ user.bio }}</p>
|
||||||
|
{% if is_online %}
|
||||||
|
<span class="badge">Online</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** Understanding the Content Parameter
|
||||||
|
|
||||||
|
Every handler receives two parameters: ~consumer~ and ~content~. The ~content~ dictionary contains all the information from the client.
|
||||||
|
|
||||||
|
#+BEGIN_SRC python
|
||||||
|
@liveview_handler("example")
|
||||||
|
def example(consumer, content):
|
||||||
|
# content structure:
|
||||||
|
# {
|
||||||
|
# "function": "example", # Handler name
|
||||||
|
# "form": {...}, # All form inputs
|
||||||
|
# "data": {...}, # Custom data-data-* attributes
|
||||||
|
# "lang": "en", # Current language
|
||||||
|
# "room": "user_123" # WebSocket room identifier
|
||||||
|
# }
|
||||||
|
pass
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** Auto-discovery
|
** Auto-discovery
|
||||||
|
|
||||||
Django LiveView automatically discovers handlers in ~liveview_components/~ directories within your installed apps:
|
Django LiveView automatically discovers handlers in ~liveview_components/~ directories within your installed apps:
|
||||||
@@ -229,80 +341,242 @@ Handlers are loaded on startup with this output:
|
|||||||
✓ Imported: my_app.liveview_components.comments
|
✓ Imported: my_app.liveview_components.comments
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Using the send() function
|
** Using Custom Data Attributes
|
||||||
|
|
||||||
The ~send()~ function sends data back to the client with many options:
|
Sometimes you need to send additional data that isn't part of a form. Use ~data-data-*~ attributes for this.
|
||||||
|
|
||||||
*** Replace HTML (Basic Update)
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@liveview_handler("update_content")
|
@liveview_handler("delete_comment")
|
||||||
def update_content(consumer, content):
|
def delete_comment(consumer, content):
|
||||||
|
# Access custom data from data-data-* attributes
|
||||||
|
comment_id = content["data"]["comment_id"]
|
||||||
|
post_id = content["data"]["post_id"]
|
||||||
|
|
||||||
|
# Delete from database
|
||||||
|
from .models import Comment
|
||||||
|
Comment.objects.filter(id=comment_id).delete()
|
||||||
|
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": "#my-element",
|
"target": f"#comment-{comment_id}",
|
||||||
"html": "<p>New content</p>"
|
"remove": True # Remove the element from DOM
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Append HTML
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div id="comment-123" class="comment">
|
||||||
|
<p>This is a comment</p>
|
||||||
|
<button
|
||||||
|
data-liveview-function="delete_comment"
|
||||||
|
data-data-comment-id="123"
|
||||||
|
data-data-post-id="456"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The attribute ~data-data-comment-id~ becomes ~comment_id~ (with underscores, not camelCase) within ~content["data"]~.
|
||||||
|
|
||||||
|
** Appending Content to Lists
|
||||||
|
|
||||||
|
When building dynamic lists (like infinite scroll or chat messages), you want to add items without replacing the entire list.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@liveview_handler("add_item")
|
@liveview_handler("load_more_posts")
|
||||||
def add_item(consumer, content):
|
def load_more_posts(consumer, content):
|
||||||
|
page = int(content["form"].get("page", 1))
|
||||||
|
|
||||||
|
# Get next page of posts
|
||||||
|
from .models import Post
|
||||||
|
posts = Post.objects.all()[(page-1)*10:page*10]
|
||||||
|
|
||||||
|
# Render new posts
|
||||||
|
html = render_to_string("posts_list.html", {
|
||||||
|
"posts": posts
|
||||||
|
})
|
||||||
|
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": "#items-list",
|
"target": "#posts-container",
|
||||||
"html": "<li>New item</li>",
|
"html": html,
|
||||||
"append": True # Adds to the end
|
"append": True # Add to the end instead of replacing
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Remove Element
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div id="posts-container">
|
||||||
|
<!-- Existing posts here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="page" value="2">
|
||||||
|
<button
|
||||||
|
data-liveview-function="load_more_posts"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Load More
|
||||||
|
</button>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Template (~posts_list.html~):
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
{% for post in posts %}
|
||||||
|
<article class="post">
|
||||||
|
<h3>{{ post.title }}</h3>
|
||||||
|
<p>{{ post.content }}</p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** Removing Elements from the DOM
|
||||||
|
|
||||||
|
Instead of hiding elements with CSS, you can completely remove them from the page.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@liveview_handler("delete_item")
|
@liveview_handler("archive_notification")
|
||||||
def delete_item(consumer, content):
|
def archive_notification(consumer, content):
|
||||||
item_id = content["data"]["id"]
|
notification_id = content["data"]["notification_id"]
|
||||||
# Delete from database...
|
|
||||||
|
|
||||||
|
# Archive in database
|
||||||
|
from .models import Notification
|
||||||
|
Notification.objects.filter(id=notification_id).update(archived=True)
|
||||||
|
|
||||||
|
# Remove from page
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": f"#item-{item_id}",
|
"target": f"#notification-{notification_id}",
|
||||||
"remove": True
|
"remove": True
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Update URL and Title
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div id="notification-42" class="notification">
|
||||||
|
<p>You have a new message</p>
|
||||||
|
<button
|
||||||
|
data-liveview-function="archive_notification"
|
||||||
|
data-data-notification-id="42"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** Updating the URL Without Page Reload
|
||||||
|
|
||||||
|
Create SPA-like navigation by updating both content and the browser URL.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@liveview_handler("navigate")
|
@liveview_handler("navigate_to_profile")
|
||||||
def navigate(consumer, content):
|
def navigate_to_profile(consumer, content):
|
||||||
|
user_id = content["data"]["user_id"]
|
||||||
|
|
||||||
|
# Get user data
|
||||||
|
from .models import User
|
||||||
|
user = User.objects.get(id=user_id)
|
||||||
|
|
||||||
|
# Render profile page
|
||||||
|
html = render_to_string("profile_page.html", {
|
||||||
|
"user": user
|
||||||
|
})
|
||||||
|
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": "#content",
|
"target": "#main-content",
|
||||||
"html": render_to_string("new_page.html"),
|
"html": html,
|
||||||
"url": "/new-page/",
|
"url": f"/profile/{user.username}/", # Update browser URL
|
||||||
"title": "New Page Title"
|
"title": f"{user.name} - Profile" # Update page title
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Scroll Management
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div id="main-content">
|
||||||
|
<h1>Home Page</h1>
|
||||||
|
<button
|
||||||
|
data-liveview-function="navigate_to_profile"
|
||||||
|
data-data-user-id="123"
|
||||||
|
data-action="click->page#run">
|
||||||
|
View Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The browser's back/forward buttons will work correctly, and users can bookmark or share the URL.
|
||||||
|
|
||||||
|
** Scrolling to Elements
|
||||||
|
|
||||||
|
After updating content, you often want to scroll to a specific element or to the top of the page.
|
||||||
|
|
||||||
|
Python code:
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
#+BEGIN_SRC python
|
||||||
@liveview_handler("show_section")
|
@liveview_handler("show_product_details")
|
||||||
def show_section(consumer, content):
|
def show_product_details(consumer, content):
|
||||||
send(consumer, {
|
product_id = content["data"]["product_id"]
|
||||||
"target": "#content",
|
|
||||||
"html": render_to_string("section.html"),
|
from .models import Product
|
||||||
"scroll": "#section-2" # Smooth scroll to element
|
product = Product.objects.get(id=product_id)
|
||||||
|
|
||||||
|
html = render_to_string("product_details.html", {
|
||||||
|
"product": product
|
||||||
})
|
})
|
||||||
|
|
||||||
@liveview_handler("back_to_top")
|
|
||||||
def back_to_top(consumer, content):
|
|
||||||
send(consumer, {
|
send(consumer, {
|
||||||
"target": "#content",
|
"target": "#product-details",
|
||||||
"html": render_to_string("content.html"),
|
"html": html,
|
||||||
|
"scroll": "#product-details" # Smooth scroll to this element
|
||||||
|
})
|
||||||
|
|
||||||
|
@liveview_handler("search_products")
|
||||||
|
def search_products(consumer, content):
|
||||||
|
query = content["form"].get("q")
|
||||||
|
|
||||||
|
from .models import Product
|
||||||
|
products = Product.objects.filter(name__icontains=query)
|
||||||
|
|
||||||
|
html = render_to_string("products_list.html", {
|
||||||
|
"products": products
|
||||||
|
})
|
||||||
|
|
||||||
|
send(consumer, {
|
||||||
|
"target": "#products-list",
|
||||||
|
"html": html,
|
||||||
"scrollTop": True # Scroll to top of page
|
"scrollTop": True # Scroll to top of page
|
||||||
})
|
})
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
HTML code:
|
||||||
|
|
||||||
|
#+BEGIN_SRC html
|
||||||
|
<div id="product-details"></div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
data-liveview-function="show_product_details"
|
||||||
|
data-data-product-id="789"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Show Details
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<input type="text" name="q" placeholder="Search products">
|
||||||
|
<button
|
||||||
|
data-liveview-function="search_products"
|
||||||
|
data-action="click->page#run">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
<div id="products-list"></div>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
* Frontend Integration
|
* Frontend Integration
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ONE: one-custom-default-doc
|
:ONE: one-custom-default-doc
|
||||||
|
|||||||
Reference in New Issue
Block a user