Last active
September 18, 2023 15:55
-
-
Save mercuryseries/00081e3b8de000eb249ecef5472c2dab to your computer and use it in GitHub Desktop.
infinite scroll
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[data-controller="infinite-scroll"] [data-infinite-scroll-target="loader"] { | |
display: none; | |
} | |
.infinite-scroll--busy [data-infinite-scroll-target="loader"] { | |
display: block; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div {{ stimulus_controller('infinite-scroll', { | |
endpointUrl: path(app.current_route, app.request.get('_route_params', [])), | |
pageCount: page_count, | |
rootMargin: '100px' | |
}, controllerClasses = {loading: 'infinite-scroll--busy'}) }}> | |
<div {{ stimulus_target('infinite-scroll', 'entries') }}> | |
{{ include('shared/_posts.html.twig') }} | |
</div> | |
<div {{ stimulus_target('infinite-scroll', 'trigger') }}></div> | |
<div {{ stimulus_target('infinite-scroll', 'loader') }}> | |
Loading... | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Controller; | |
use Knp\Component\Pager\PaginatorInterface; | |
use function Symfony\Component\String\u; | |
// ... | |
class HomeController extends AbstractController | |
{ | |
public function index(Request $request, PostRepository $postRepository, PaginatorInterface $paginator): Response | |
{ | |
$page = $request->query->getInt('page', 1); | |
/** @var \Knp\Bundle\PaginatorBundle\Pagination $pager */ | |
$pager = $paginator->paginate( | |
$postRepository->createTimelineQueryBuilder($this->getUser()), | |
$page, | |
Post::NUM_ITEMS_PER_PAGE | |
); | |
if ($this->wantsJson($request)) { | |
return $this->json([ | |
'entries' => $this->renderView('shared/posts.html.twig', [ | |
'pager' => $pager, | |
'page_count' => $pageCount, | |
]), | |
]); | |
} else { | |
return $this->render('home.html.twig', [ | |
'pager' => $pager, | |
'page_count' => $pageCount, | |
]); | |
} | |
} | |
// peut être déplacé au niveau d'un trait si tu l'utilises à plein d'endroits | |
private function wantsJson(Request $request) | |
{ | |
$acceptable = $request->getAcceptableContentTypes(); | |
return isset($acceptable[0]) && u($acceptable[0])->ignoreCase()->containsAny(['/json', '+json']); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
export default class extends Controller<HTMLDivElement> { | |
page: number = 1; | |
intersectionObserver!: IntersectionObserver; | |
declare readonly entriesTarget: HTMLInputElement; | |
declare readonly triggerTarget: HTMLInputElement; | |
declare readonly loaderTarget: HTMLInputElement; | |
declare readonly endpointUrlValue: string; | |
declare readonly pageCountValue: number; | |
declare readonly rootMarginValue: string; | |
declare readonly thresholdValue: number; | |
declare readonly loadingClass: string; | |
static targets = ["entries", "trigger", "loader"]; | |
static values = { | |
endpointUrl: String, | |
pageCount: Number, | |
rootMargin: { type: String, default: "0px" }, | |
threshold: { type: Number, default: 0 }, | |
}; | |
static classes = ["loading"]; | |
initialize(): void { | |
this.intersectionObserver = new IntersectionObserver( | |
(entries) => | |
entries.forEach((entry) => { | |
if (entry.isIntersecting) { | |
this.fetchEntries(); | |
} | |
}), | |
{ | |
rootMargin: this.rootMarginValue, | |
threshold: this.thresholdValue, | |
} | |
); | |
} | |
connect(): void { | |
this.intersectionObserver.observe(this.triggerTarget); | |
} | |
disconnect(): void { | |
this.intersectionObserver.unobserve(this.triggerTarget); | |
} | |
async fetchEntries(): Promise<void> { | |
this.page += 1; | |
if (this.page > this.pageCountValue) { | |
return; | |
} | |
this.element.classList.add(this.loadingClass); | |
let [response] = await Promise.allSettled([ | |
fetch(`${this.endpointUrlValue}?page=${this.page}`, { | |
headers: { | |
Accept: "application/json", | |
}, | |
}), | |
// artificial delay for better ux | |
new Promise((resolve) => setTimeout(resolve, 600)), | |
]); | |
if (response.status === "fulfilled") { | |
const data = await response.value.json(); | |
this.entriesTarget.insertAdjacentHTML("beforeend", data.entries); | |
} | |
this.element.classList.remove(this.loadingClass); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment