Skip to content

Commit 9cacddd

Browse files
committed
Convert README.md to mkdocs-built documentation
1 parent 3b8edf1 commit 9cacddd

16 files changed

+959
-586
lines changed

README.md

+2-584
Large diffs are not rendered by default.

docs/component_ids.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Component IDs
2+
3+
- Every component must have a root element that includes its ID. The ID is `id={{ component_id }}`.
4+
- Component IDs represent the component hierarchy and are formatted as "|parent:id|child:id". For example, we can have a component |form:0|button:submit where "button" is the component type, "submit" is its name, and "form:0" is its parent.
5+
6+
In many contexts, you get access to the StateAddress object, which consists of the session ID and the component ID.
7+
In this pair, the component ID is a "ComponentId" instance (a subclass of str), which has a helpful method
8+
to create child items.
9+
10+
It can be used like this, without explicitly setting the child own_id:
11+
12+
state_address.component_id | "child_component"
13+
14+
Or like this, with explicitly setting the child own_id:
15+
16+
state_address.component_id | ("child_component", "child_own_id")

docs/components.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Components
2+
3+
## Component State
4+
5+
The state is defined in a separate class. The state must include parameters, passed to the component as keyword arguments, so that the component gets all necessary information to re-render itself on partial render.
6+
7+
For example, given the template the alert component:
8+
9+
```html
10+
<alert>{{ message }}</alert>
11+
```
12+
13+
that you want to use as
14+
15+
```html
16+
{% component "alert" message="Hello, world!" %}
17+
```
18+
19+
Assuming that the component will be re-rendered on partial render, the state must include the "message" parameter:
20+
21+
```python
22+
from pydantic import BaseModel
23+
from livecomponents.component import LiveComponent
24+
from livecomponents.manager.manager import InitStateContext
25+
26+
class AlertState(BaseModel):
27+
message: str = ""
28+
29+
30+
class Alert(LiveComponent):
31+
32+
template_name = "alert.html"
33+
34+
35+
def init_state(self, context: InitStateContext) -> AlertState:
36+
return AlertState(**context.component_kwargs)
37+
```
38+
39+
Component states don't need to be stored if components are not expected to be re-rendered independently, and only
40+
as part of the parent component. For example, components for buttons are rarely re-rendered independently, so
41+
you get away without the state model.
42+
43+
## Serializing Component State
44+
45+
When the page is rendered for the first time, a new session is created, and each component is initialized with its
46+
state by calling the `init_state()` method.
47+
48+
The state is then serialized and stored in the session store, and as long as the session is the same (in other words,
49+
while the page is not loaded), the state is reused.
50+
51+
The state is serialized using the `StateSerializer` class and saved in Redis. By default, the `PickleStateSerializer`
52+
is used. The serializer uses custom pickler and is optimized to store effectively the most common types of data, used
53+
in a Django app. More specifically:
54+
55+
- When serializing a Django model, only the model's name and primary key are stored. The serializer takes advantage of
56+
the persistent_id/persistent_load pickle mechanism.
57+
- When serializing a Pydantic model, only the model's name and the values of the fields are stored.
58+
- When serializing a Django form, only the form's class name, as well as initial data and data, are stored.
59+
60+
61+
## Stateless components
62+
63+
If the component doesn't store any state, you can inherit it from the StatelessLiveComponent class. You may find it
64+
helpful for rendering the hierarchy of components where the shared state is stored in the root components.
65+
66+
```python
67+
from livecomponents.component import StatelessLiveComponent
68+
69+
class StatelessAlert(StatelessLiveComponent):
70+
71+
template_name = "alert.html"
72+
73+
def get_extra_context_data(
74+
self, extra_context_request: "ExtraContextRequest[State]"
75+
) -> dict:
76+
state_manager = extra_context_request.state_manager
77+
root_addr = extra_context_request.state_addr.must_find_ancestor("root")
78+
root_state = state_manager.get_component_state(root_addr)
79+
return {"message": root_state.message}
80+
```
81+
82+
## Returning results from command handlers
83+
84+
Here's the signature of the Livecomponent function:
85+
86+
```python
87+
from livecomponents import LiveComponent, CallContext, command
88+
from livecomponents.manager.execution_results import IExecutionResult
89+
90+
class MyComponent(LiveComponent):
91+
92+
@command
93+
def my_command_handler(self , call_context: CallContext, **kwargs) -> list[IExecutionResult] | IExecutionResult | None :
94+
...
95+
```
96+
97+
Notice the type of the returned value for the handler. If set to something other than None, it can shape the
98+
partial HTTP response.
99+
100+
More specifically here's what you can do:
101+
102+
- Return ComponentDirty() to mark the component as dirty. This will result in the component being re-rendered and sent to the client. This is the default behavior. If you don't return anything, the component will be marked as dirty.
103+
- Return ComponentDirty(component_id) to mark a different component as dirty.
104+
- Return ComponentClean() to mark the current component as clean (not needing re-rendering).
105+
- Return ParentDirty() to mark the parent component as dirty.
106+
- Return RefreshPage(). If the command returns RefreshPage(), a "HX-Refresh: true" header will be sent to the client.
107+
- Return RedirectPage(url). If the command returns Redirect(), a "HX-Redirect: url" header will be sent to the client.
108+
- Return ReplaceUrl(url). If the command returns ReplaceUrl(), a "HX-Replace: url" header will be sent to the client. This will replace the current URL in the browser without reloading the page.
109+
110+
## Raising exceptions from command handlers
111+
112+
In some rare scenarios, you may need to cancel rendering the component and instruct the command handler to return an empty string to the client.
113+
114+
If this is the case, you can raise a `livecomponents.exceptions.CancelRendering()` exception.
115+
116+
The exception can be raised directly from a command handler or from one of the methods that it will call, such as `get_extra_context_data()`.
117+
118+
```python
119+
from livecomponents.exceptions import CancelRendering
120+
...
121+
122+
class MyComponent(LiveComponent):
123+
124+
@command
125+
def my_command_handler(self, call_context: CallContext, **kwargs):
126+
if not self.pre_condition_met(call_context):
127+
raise CancelRendering()
128+
...
129+
```
130+
131+
We encountered this situation at least once, where a race condition caused the pre-condition that was true when we started executing a command to no longer be true when we rendered a sub-component. In this case, we couldn't render the sub-component but also didn't want to return a partially rendered component. The best solution was to return an empty string, effectively making the command have no effect.
132+
133+
134+
## Calling component methods from others
135+
136+
There are several ways to call component methods from other components:
137+
138+
**Using the component ID.** For example, if you have a component with ID "|message.0" and a method "set_message", you can call it like this:
139+
140+
```python
141+
from livecomponents import LiveComponent, command, CallContext
142+
143+
class MyComponent(LiveComponent):
144+
145+
@command
146+
def do_something(self, call_context: CallContext):
147+
call_context.find_one("|message:0").set_message("Hello, world!")
148+
```
149+
150+
**Using the "parent" reference.**
151+
152+
```python
153+
from livecomponents import LiveComponent, command, CallContext
154+
155+
class MyComponent(LiveComponent):
156+
157+
@command
158+
def do_something(self, call_context: CallContext):
159+
call_context.parent.set_message("Hello, world!")
160+
```

docs/configuration.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Configuration
2+
3+
The application is configured with the "LIVECOMPONENTS" dictionary in the settings.py file. Here's the default settings:
4+
5+
```python
6+
LIVECOMPONENTS = {
7+
"state_serializer": {
8+
"cls": "livecomponents.manager.serializers.PickleStateSerializer",
9+
"config": {},
10+
},
11+
"state_store": {
12+
# You can also use "MemoryStateStore" for tests.
13+
"cls": "livecomponents.manager.stores.RedisStateStore",
14+
# See "RedisStateStore" constructor for config options.
15+
"config": {},
16+
},
17+
"state_manager": {
18+
"cls": "livecomponents.manager.manager.StateManager",
19+
"config": {},
20+
},
21+
}
22+
```

docs/context.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Storing Component Context
2+
3+
During the first render, components use the entire page context to render themselves.
4+
5+
During subsequent renders, components by default use the context populated from their state.
6+
7+
However, it is possible to save some variables from the context of the first render. To do this, pass the `save_context` variable with a comma-separated list of variables that need to be sent to the `livecomponent` templatetag.
8+
9+
This approach is commonly used when working with live component slots.
10+
11+
Let's first look at an example of a "non-prepared" component that will only work on the first render:
12+
13+
```html
14+
{% livecomponent_block "alert" %}
15+
{% fill "body" %}Sending a message to {{ user.email }}!{% endfill %}
16+
{% endlivecomponent_block %}
17+
```
18+
19+
This will not work on partial renders because the component will be rendered without the "user" variable.
20+
21+
To address this, add the "save_context" variable:
22+
23+
```diff
24+
-{% livecomponent_block "alert" %}
25+
+{% livecomponent_block "alert" save_context="user" %}
26+
{% fill "body" %}Sending a message to {{ user.email }}!{% endfill %}
27+
{% endlivecomponent_block %}
28+
```

docs/decorators.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Decorators
2+
3+
LiveComponents provide a method decorator to ensure the user is authenticated.
4+
5+
6+
```python
7+
from livecomponents import LiveComponent, InitStateContext, CallContext
8+
from livecomponents.decorators import livecomponents_login_required
9+
10+
11+
class Something(LiveComponent):
12+
13+
@classmethod
14+
@livecomponents_login_required
15+
def init_state(cls, context: InitStateContext):
16+
...
17+
18+
@classmethod
19+
@livecomponents_login_required
20+
def do_something(cls, call_context: CallContext[SomethingState], **kwargs):
21+
...
22+
```

docs/error_handling.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Error handling
2+
3+
The response to calling the command can be an HTTP error. If the command handler fails to find a session, it will return
4+
an HTTP 410 Gone error.
5+
6+
You can handle this error on the client side. For example, here a JavaScript code snippet to reload the page on the
7+
error 410.
8+
9+
```javascript
10+
document.addEventListener("htmx:responseError", function (event) {
11+
const statusCode = event.detail.xhr.status;
12+
if (statusCode === 410) {
13+
document.location.reload();
14+
}
15+
});
16+
```
17+
18+
If you use [hyperscript](https://hyperscript.org/), you can write the same code much shorter as a one-liner, attached
19+
directly to the component, or to the document:
20+
21+
```html
22+
<body ... _="on htmx:responseError[detail.xhr.status == 410] window.location.reload()">
23+
...
24+
</body>
25+
```

docs/example_project.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Example project
2+
3+
While the fully fledged documentation is not ready and the project is in flux, it's better to use the "example" project as the reference.
4+
5+
Run it locally and play with it to get a better understanding of how the library works.
6+
7+
```bash
8+
poetry install
9+
cd example
10+
cp env.example .env
11+
poetry run python manage.py migrate
12+
poetry run python manage.py runserver
13+
```

docs/index.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Django Live Components
2+
3+
Django Live Component is a library to create dynamic web applications that handle user interaction with the DOM on the server. It relies on Django, HTMX, and Alpine.js to provide a seamless experience for developers and users.
4+
5+
To get started, follow the [quickstart guide](quickstart.md)

0 commit comments

Comments
 (0)