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.
|
||||
|
||||
** 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
|
||||
from liveview import liveview_handler, send
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
@liveview_handler("say_hello")
|
||||
def say_hello(consumer, content):
|
||||
"""
|
||||
Args:
|
||||
consumer: WebSocket consumer instance
|
||||
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}!"
|
||||
send(consumer, {
|
||||
"target": "#greeting",
|
||||
"html": "<p>Hello, World!</p>"
|
||||
})
|
||||
#+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, {
|
||||
"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
|
||||
})
|
||||
#+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
|
||||
|
||||
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
|
||||
#+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
|
||||
@liveview_handler("update_content")
|
||||
def update_content(consumer, content):
|
||||
@liveview_handler("delete_comment")
|
||||
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, {
|
||||
"target": "#my-element",
|
||||
"html": "<p>New content</p>"
|
||||
"target": f"#comment-{comment_id}",
|
||||
"remove": True # Remove the element from DOM
|
||||
})
|
||||
#+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
|
||||
@liveview_handler("add_item")
|
||||
def add_item(consumer, content):
|
||||
@liveview_handler("load_more_posts")
|
||||
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, {
|
||||
"target": "#items-list",
|
||||
"html": "<li>New item</li>",
|
||||
"append": True # Adds to the end
|
||||
"target": "#posts-container",
|
||||
"html": html,
|
||||
"append": True # Add to the end instead of replacing
|
||||
})
|
||||
#+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
|
||||
@liveview_handler("delete_item")
|
||||
def delete_item(consumer, content):
|
||||
item_id = content["data"]["id"]
|
||||
# Delete from database...
|
||||
@liveview_handler("archive_notification")
|
||||
def archive_notification(consumer, content):
|
||||
notification_id = content["data"]["notification_id"]
|
||||
|
||||
# Archive in database
|
||||
from .models import Notification
|
||||
Notification.objects.filter(id=notification_id).update(archived=True)
|
||||
|
||||
# Remove from page
|
||||
send(consumer, {
|
||||
"target": f"#item-{item_id}",
|
||||
"target": f"#notification-{notification_id}",
|
||||
"remove": True
|
||||
})
|
||||
#+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
|
||||
@liveview_handler("navigate")
|
||||
def navigate(consumer, content):
|
||||
@liveview_handler("navigate_to_profile")
|
||||
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, {
|
||||
"target": "#content",
|
||||
"html": render_to_string("new_page.html"),
|
||||
"url": "/new-page/",
|
||||
"title": "New Page Title"
|
||||
"target": "#main-content",
|
||||
"html": html,
|
||||
"url": f"/profile/{user.username}/", # Update browser URL
|
||||
"title": f"{user.name} - Profile" # Update page title
|
||||
})
|
||||
#+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
|
||||
@liveview_handler("show_section")
|
||||
def show_section(consumer, content):
|
||||
send(consumer, {
|
||||
"target": "#content",
|
||||
"html": render_to_string("section.html"),
|
||||
"scroll": "#section-2" # Smooth scroll to element
|
||||
@liveview_handler("show_product_details")
|
||||
def show_product_details(consumer, content):
|
||||
product_id = content["data"]["product_id"]
|
||||
|
||||
from .models import Product
|
||||
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, {
|
||||
"target": "#content",
|
||||
"html": render_to_string("content.html"),
|
||||
"target": "#product-details",
|
||||
"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
|
||||
})
|
||||
#+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
|
||||
:PROPERTIES:
|
||||
:ONE: one-custom-default-doc
|
||||
|
||||
Reference in New Issue
Block a user