Add load more how to [deploy-site]
This commit is contained in:
parent
d60f326230
commit
bebd836f45
|
@ -23,6 +23,7 @@ func setupHowTos(ctx context.Context, router chi.Router) error {
|
||||||
Label: "How Tos",
|
Label: "How Tos",
|
||||||
Links: []*SidebarLink{
|
Links: []*SidebarLink{
|
||||||
{ID: "how_to_bind_keydown_events_to_specific_keys"},
|
{ID: "how_to_bind_keydown_events_to_specific_keys"},
|
||||||
|
{ID: "how_to_load_more_list_items"},
|
||||||
{ID: "how_to_poll_the_backend_at_regular_intervals"},
|
{ID: "how_to_poll_the_backend_at_regular_intervals"},
|
||||||
{ID: "how_to_redirect_the_page_from_the_backend"},
|
{ID: "how_to_redirect_the_page_from_the_backend"},
|
||||||
},
|
},
|
||||||
|
@ -76,8 +77,9 @@ func setupHowTos(ctx context.Context, router chi.Router) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := errors.Join(
|
if err := errors.Join(
|
||||||
|
setupHowTosLoadMore(howTosRouter),
|
||||||
setupHowTosPolling(howTosRouter),
|
setupHowTosPolling(howTosRouter),
|
||||||
setupHowTosRedirects(howTosRouter),
|
setupHowTosRedirect(howTosRouter),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
panic(fmt.Sprintf("error setting up examples routes: %s", err))
|
panic(fmt.Sprintf("error setting up examples routes: %s", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package site
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
datastar "github.com/starfederation/datastar/sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OffsetSignals struct {
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupHowTosLoadMore(howTosRedirect chi.Router) error {
|
||||||
|
|
||||||
|
howTosRedirect.Route("/load_more/data", func(dataRouter chi.Router) {
|
||||||
|
|
||||||
|
dataRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
signals := &OffsetSignals{}
|
||||||
|
if err := datastar.ReadSignals(r, signals); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
max := 5
|
||||||
|
limit := 1
|
||||||
|
offset := signals.Offset
|
||||||
|
|
||||||
|
sse := datastar.NewSSE(w, r)
|
||||||
|
|
||||||
|
if offset < max {
|
||||||
|
newOffset := offset + limit
|
||||||
|
sse.MergeFragments(fmt.Sprintf(`<div class="text-primary font-bold">Item %d</div>`, newOffset),
|
||||||
|
datastar.WithSelectorID("list"),
|
||||||
|
datastar.WithMergeMode(datastar.FragmentMergeModeAppend),
|
||||||
|
)
|
||||||
|
if newOffset < max {
|
||||||
|
sse.MergeSignals([]byte(fmt.Sprintf(`{offset: %d}`, newOffset)))
|
||||||
|
} else {
|
||||||
|
sse.RemoveFragments(`#load-more`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
datastar "github.com/starfederation/datastar/sdk/go"
|
datastar "github.com/starfederation/datastar/sdk/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupHowTosRedirects(howTosRedirect chi.Router) error {
|
func setupHowTosRedirect(howTosRedirect chi.Router) error {
|
||||||
|
|
||||||
howTosRedirect.Route("/redirect/data", func(dataRouter chi.Router) {
|
howTosRedirect.Route("/redirect/data", func(dataRouter chi.Router) {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
datastar "github.com/starfederation/datastar/sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OffsetSignals struct {
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
signals := &OffsetSignals{}
|
||||||
|
if err := datastar.ReadSignals(r, signals); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
max := 5
|
||||||
|
limit := 1
|
||||||
|
offset := signals.Offset
|
||||||
|
|
||||||
|
sse := datastar.NewSSE(w, r)
|
||||||
|
|
||||||
|
if offset < max {
|
||||||
|
newOffset := offset + limit
|
||||||
|
sse.MergeFragments(fmt.Sprintf(`<div>Item %d</div>`, newOffset),
|
||||||
|
datastar.WithSelectorID("list"),
|
||||||
|
datastar.WithMergeMode(datastar.FragmentMergeModeAppend),
|
||||||
|
)
|
||||||
|
if newOffset < max {
|
||||||
|
sse.MergeSignals([]byte(fmt.Sprintf(`{offset: %d}`, newOffset)))
|
||||||
|
} else {
|
||||||
|
sse.RemoveFragments(`#load-more`)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use starfederation\datastar\enums\FragmentMergeMode;
|
||||||
|
use starfederation\datastar\ServerSentEventGenerator;
|
||||||
|
|
||||||
|
$signals = ServerSentEventGenerator::readSignals();
|
||||||
|
|
||||||
|
$max = 5;
|
||||||
|
$limit = 1;
|
||||||
|
$offset = $signals['offset'] ?? 1;
|
||||||
|
|
||||||
|
$sse = new ServerSentEventGenerator();
|
||||||
|
|
||||||
|
if ($offset < $max) {
|
||||||
|
$newOffset = $offset + $limit;
|
||||||
|
$sse->mergeFragments(`<div>Item $newOffset</div>`, [
|
||||||
|
'selector' => '#list',
|
||||||
|
'mergeMode' => FragmentMergeMode::Append,
|
||||||
|
]);
|
||||||
|
if (newOffset < $max) {
|
||||||
|
$sse->mergeSignals(['offset' => $newOffset]);
|
||||||
|
} else {
|
||||||
|
$sse->removeFragments('#load-more');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
# How to load more list items
|
||||||
|
|
||||||
|
## Intro
|
||||||
|
|
||||||
|
Loading more list items into the DOM from the backend is a common alternative to pagination. What's makes it different is that we need to append the new items to the existing list, rather than replace them.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Our goal is to incrementally append list items into a specific part of the DOM, each time a button is clicked. Once five items are visible, the button should be removed.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
<div id="list" data-signals-offset="1">
|
||||||
|
<div class="text-primary font-bold">Item 1</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="load-more" data-on-click="@get('/how_tos/load_more/data')" class="btn btn-primary font-bold">
|
||||||
|
Click to load another item
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
We'll give the list item container and the button unique IDs, so that we can target them individually.
|
||||||
|
|
||||||
|
We'll use a `data-signals-*` attribute to set the initial `offset` to `1`, and a `data-on-click` button that will send a `GET` request to the backend.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="list">
|
||||||
|
<div>Item 1</div>
|
||||||
|
</div>
|
||||||
|
<button id="load-more"
|
||||||
|
data-signals-offset="1"
|
||||||
|
data-on-click="@get('/how_tos/load_more/data')">
|
||||||
|
Click to load another item
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
The backend will receive the `offset` signal and, if not above the max number of allowed items, will return the next item to be appended to the list.
|
||||||
|
|
||||||
|
We'll set up our backend to send a [`datastar-merge-fragments`](/reference/sse_events#datastar-merge-fragments) event with the `selector` option set to `#list` and the `mergeMode` option set to `append`. This tells Datastar to _append_ the fragments _into_ the `#list` container (rather than the default behaviour of replacing it).
|
||||||
|
|
||||||
|
```
|
||||||
|
event: datastar-merge-fragments
|
||||||
|
data: selector #list
|
||||||
|
data: mergeMode append
|
||||||
|
data: fragments <div>Item 2</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, we'll send a [`datastar-merge-signals`](/reference/sse_events#datastar-merge-signals) event to update the `offset`.
|
||||||
|
|
||||||
|
```
|
||||||
|
event: datastar-merge-signals
|
||||||
|
data: signals {offset: 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
In the case when the incoming offset is `2`, we'll remove the button from the DOM entirely.
|
||||||
|
|
||||||
|
```
|
||||||
|
event: datastar-remove-fragments
|
||||||
|
data: selector #load-more
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's how it might look using the SDKs.
|
||||||
|
|
||||||
|
!!!CODE_SNIPPET:how_tos/load_more!!!
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
While using the default merge mode of `morph` is generally recommended, appending to a list is a good example of when to use an alternative merge mode.
|
|
@ -56,7 +56,7 @@ data: fragments </div>
|
||||||
|
|
||||||
Be careful not to add `.leading` to the modifier in the response, as it will cause the frontend to immediately send another request.
|
Be careful not to add `.leading` to the modifier in the response, as it will cause the frontend to immediately send another request.
|
||||||
|
|
||||||
Here's how it would look using the SDKs.
|
Here's how it might look using the SDKs.
|
||||||
|
|
||||||
!!!CODE_SNIPPET:how_tos/polling_1!!!
|
!!!CODE_SNIPPET:how_tos/polling_1!!!
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ event: datastar-execute-script
|
||||||
data: script window.location.href = "/guide"
|
data: script window.location.href = "/guide"
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how it would look using the SDKs.
|
Here's how it might look using the SDKs.
|
||||||
|
|
||||||
!!!CODE_SNIPPET:how_tos/redirect_1!!!
|
!!!CODE_SNIPPET:how_tos/redirect_1!!!
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue