document-level delegated click handler for non-infinite Load More button#56
document-level delegated click handler for non-infinite Load More button#56jbouganim wants to merge 1 commit into
Conversation
WalkthroughThis change updates version identifiers across package.json (1.0.16 → 1.0.17), query-loop-load-more.php header, readme.txt stable tag, and assets/js/build/frontend.asset.php (hash). In JavaScript, non-infinite "Load More" click handling is moved from per-button listeners to a document-level delegated click handler. The IntersectionObserver and handler variable names are swapped, with corresponding updates to observe/unobserve calls. The compiled build files reflect these adjustments. Overall fetch, DOM update, URL handling, and error flow remain the same. Possibly related PRs
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (9)
assets/js/src/frontend.js (3)
164-172: Delegated click handler: good fix; guard against duplicate bindings.If this script is executed more than once (e.g., via HMR or certain navigations), the document-level listener can be attached multiple times. Wrap the registration with a one-time guard without short-circuiting the rest of domReady.
Apply:
- // Delegated click handler for non-infinite buttons - document.addEventListener( 'click', function ( e ) { - const btn = e.target.closest( '.wp-load-more__button:not(.wp-load-more__infinite-scroll)' ); - if ( ! btn ) { - return; - } - e.preventDefault(); - fetchPosts( btn ); - } ); + // Delegated click handler for non-infinite buttons (bind once) + if ( ! window.__qllmDelegatedClickBound ) { + document.addEventListener( 'click', function ( e ) { + const btn = e.target.closest( '.wp-load-more__button:not(.wp-load-more__infinite-scroll)' ); + if ( ! btn ) { + return; + } + e.preventDefault(); + fetchPosts( btn ); + } ); + window.__qllmDelegatedClickBound = true; + }
55-60: Use Accept header for GET instead of Content-Type.Content-Type describes a request body (none here). Prefer Accept to signal expected response.
fetch( url, { method: 'GET', headers: { - 'Content-Type': 'text/html', + 'Accept': 'text/html', }, } )
174-181: Infinite scroll after DOM replacement: observe newly added buttons.Delegation fixed non-infinite clicks across client-side DOM swaps; infinite buttons added later won’t be observed because observe() runs only at domReady. Add a lightweight MutationObserver to auto-observe future
.wp-load-more__infinite-scrollnodes.document .querySelectorAll( '.wp-load-more__infinite-scroll' ) .forEach( function ( button ) { intersectionObserver.observe( button ); } ); + + // Observe future additions for infinite scroll buttons + const qllmMo = new MutationObserver( (mutations) => { + for ( const m of mutations ) { + m.addedNodes.forEach( (node) => { + if ( node.nodeType !== 1 ) return; + if ( node.matches?.('.wp-load-more__infinite-scroll') ) { + intersectionObserver.observe( node ); + } + node.querySelectorAll?.('.wp-load-more__infinite-scroll').forEach( (el) => { + intersectionObserver.observe( el ); + } ); + } ); + } + } ); + qllmMo.observe( document.body, { childList: true, subtree: true } );To verify: after triggering a client-side region replacement that re-renders a Query Loop with infinite scroll enabled, ensure new buttons get observed (posts load when button comes into view) without a full page reload.
assets/js/build/frontend.js (6)
1-1: Harden delegated click: guard non-Element targets and respect meta/middle-click.Click events can originate from non-Element targets;
.closestwould throw. Also, allow Cmd/Ctrl/middle-click to open in a new tab. Apply inassets/js/src/frontend.js; rebuild afterward.- document.addEventListener("click",(e)=> { - const btn = e.target.closest('.wp-load-more__button:not(.wp-load-more__infinite-scroll)'); - if (btn) { e.preventDefault(); fetchPosts(btn); } - }); + document.addEventListener('click', (e) => { + const target = e.target instanceof Element ? e.target : null; + const btn = target?.closest('.wp-load-more__button:not(.wp-load-more__infinite-scroll)'); + if (!btn) return; + // Let users open in a new tab/window. + if (e.metaKey || e.ctrlKey || e.button === 1) return; + e.preventDefault(); + fetchPosts(btn); + });
1-1: Rebind infinite-scroll buttons added after DOM replacements.Interactivity/router updates can replace the sentinel button; the observer won’t reattach. Add a lightweight MutationObserver to observe new
.wp-load-more__infinite-scrollnodes. Apply inassets/js/src/frontend.js; rebuild.Please verify infinite scroll still triggers after a client-side region replacement.
domReady(() => { // existing: document.addEventListener('click', ...) and initial observes document.querySelectorAll('.wp-load-more__infinite-scroll').forEach((el) => observer.observe(el)); + + // Observe future additions/removals of infinite-scroll buttons. + const ensureObserved = (root) => { + root.querySelectorAll?.('.wp-load-more__infinite-scroll')?.forEach((el) => observer.observe(el)); + }; + ensureObserved(document); + const mo = new MutationObserver((mutations) => { + for (const m of mutations) { + m.addedNodes.forEach((node) => { + if (node.nodeType === 1) { + if (node.matches?.('.wp-load-more__infinite-scroll')) observer.observe(node); + else ensureObserved(node); + } + }); + m.removedNodes.forEach((node) => { + if (node.nodeType === 1) { + if (node.matches?.('.wp-load-more__infinite-scroll')) observer.unobserve(node); + else node.querySelectorAll?.('.wp-load-more__infinite-scroll')?.forEach((el) => observer.unobserve(el)); + } + }); + } + }); + mo.observe(document.body, { childList: true, subtree: true }); });
1-1: Preserve existing query params when updating the button href.Current code overwrites the full query string. Use URL API to only mutate the paging param. Apply in
assets/js/src/frontend.js; rebuild.- btn.href = '?' + btn.dataset.queryUrl + '=' + btn.dataset.queryNextPage; + { + const url = new URL(btn.href, window.location.href); + url.searchParams.set(btn.dataset.queryUrl, btn.dataset.queryNextPage); + btn.href = url.toString(); + }
1-1: Don’t set Content-Type on a GET fetch.The header is unnecessary for GET and can cause odd server behavior. Apply in
assets/js/src/frontend.js; rebuild.- fetch(url, { method: 'GET', headers: { 'Content-Type': 'text/html' } }) + fetch(url, { method: 'GET' })
1-1: Avoid re-arming the observer after the button is removed.Guard with a DOM presence check to skip unnecessary measure/re-observe. Apply in
assets/js/src/frontend.js; rebuild.- if (btn.classList.contains('wp-load-more__infinite-scroll')) { - const rect = btn.getBoundingClientRect(); - if (rect.bottom > 0 && rect.top < window.innerHeight) { - observer.unobserve(btn); - observer.observe(btn); - } - } + if (btn.classList.contains('wp-load-more__infinite-scroll')) { + if (!document.body.contains(btn)) return; + const rect = btn.getBoundingClientRect(); + if (rect.bottom > 0 && rect.top < window.innerHeight) { + observer.unobserve(btn); + observer.observe(btn); + } + }
1-1: Remove stray container “loading” class toggle or also add it.You remove
loadingfrom the.wp-block-buttoncontainer but never add it. Either delete the removal or add a matchingaddfor consistency. Apply inassets/js/src/frontend.js; rebuild.- const container = btn.closest('.wp-block-button'); - if (container) container.classList.remove('loading'); + // If container-level loading state is desired, also add it where the button state is set. + // Otherwise, remove this block for consistency.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
assets/js/build/frontend.asset.php(1 hunks)assets/js/build/frontend.js(1 hunks)assets/js/src/frontend.js(1 hunks)package.json(1 hunks)query-loop-load-more.php(1 hunks)readme.txt(2 hunks)
🔇 Additional comments (5)
query-loop-load-more.php (1)
7-7: Version bump matches release intent.Header updated to 1.0.17; aligns with readme and package.json.
package.json (1)
3-3: Package version updated correctly.Version set to 1.0.17; consistent with plugin header and readme.
assets/js/build/frontend.asset.php (1)
1-1: Asset version updated.Hash bump reflects rebuilt frontend bundle; deps unchanged.
readme.txt (1)
6-6: Docs aligned with the change.Stable tag and changelog accurately describe the delegated click handler and its motivation.
Also applies to: 74-76
assets/js/build/frontend.js (1)
1-1: Document-level delegated click handler: solid fix.This removes per-button listeners, prevents duplicate handlers, and survives client-side DOM replacements. Infinite-scroll path remains intact.
|
@jbouganim Thank you for the PR. The latest version 1.0.17 attaches the listener to the document. #57 |
|
@robrobsn great, good if I close this then? |
@jbouganim Yes. Thanks! |
Summary
Switch Load More click handling to a document-level delegated listener to prevent lost bindings after DOM replacements (Interactivity API/router updates) and avoid duplicate listeners. Infinite scroll remains unchanged.
What’s fixed
Changes
assets/js/src/frontend.js: replace per-button listeners with a single document-level delegated click handler for.wp-load-more__button:not(.wp-load-more__infinite-scroll).readme.txt: add 1.0.17 changelog entry documenting the change.How to test
Version
Summary by CodeRabbit
Bug Fixes
Documentation
Chores