Developer guide
How the loader works, how to run your scripts on newly loaded products, and how to adapt it from a theme or plugin.
How loading works
There is no AJAX or REST endpoint for products. The script requests the archive's real next page (GET the next link, same origin, with X-Requested-With: XMLHttpRequest), parses it with DOMParser and copies the product elements into the current list. Scripts inside the fetched HTML never run, so anything your theme attaches to products must be re-run through the events below.
| Free | Pro | |
|---|---|---|
| Namespace | Siteryn\SiterynInfiniteScroll | SiterynPro\SiterynInfiniteScroll |
| Boot | plugins_loaded, then siteryn_is_loaded | On siteryn_is_loaded, then siteryn_is_pro_loaded |
| Storefront decision | On wp: context → enabled → pagination state → siteryn_is_should_load | Adds contexts and rules through Free's filters |
| API version | SITERYN_IS_API_VERSION (1.0.0), for compatibility checks | — |
| Script handle | siteryn-is (footer, deferred, no jQuery) | siteryn-is-pro |
JavaScript events
Events are dispatched on the product list and bubble, so listen on document.
| Event | detail | When |
|---|---|---|
siteryn-is:init | instance | The loader started |
siteryn-is:before-load | instance, url, page, trigger | Before appending a page; cancelable. trigger: scroll, button, retry, api, restore (Pro) |
siteryn-is:loaded | instance, url, page, items, nextUrl, trigger, document | After products were appended |
siteryn-is:before-replace · siteryn-is:replaced | instance, url · + page, items, nextUrl, document | Pro numbered and Previous / Next page switches (before is cancelable) |
siteryn-is:end | instance | No more pages |
siteryn-is:error | instance, url, error | A load failed |
siteryn-is:reset · siteryn-is:destroy | instance, url · instance | After reset() (url = new next URL) · destroy() |
WordPress's jQuery post-load event also fires on document.body after every load. Pro's earlier pages (loaded above the list when a shopper who arrived on a later page scrolls up) fire only post-load, so listen to both.
( function ( $ ) {
function init( root ) {
// e.g. root.querySelectorAll( '.my-slider:not(.is-ready)' ).forEach( mySlider );
}
document.addEventListener( 'siteryn-is:loaded', function ( e ) { e.detail.items.forEach( init ); } );
document.addEventListener( 'siteryn-is:replaced', function ( e ) { e.detail.items.forEach( init ); } );
if ( $ ) {
$( document.body ).on( 'post-load', function () { init( document ); } ); // also covers Pro's earlier pages
}
} )( window.jQuery );
Catching the start reliably
The script is deferred, so siteryn-is:init usually fires before your own deferred scripts run. Load your script only where the loader runs, and handle existing instances too:
add_action( 'siteryn_is_enqueue_assets', function () {
wp_enqueue_script( 'my-is-glue', get_stylesheet_directory_uri() . '/js/is-glue.js',
array( 'siteryn-is' ), '1.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
} );
function attach( loader ) {
if ( loader && ! loader.myGlue ) { loader.myGlue = true; /* … */ }
}
( window.SiterynInfiniteScroll ? window.SiterynInfiniteScroll.instances : [] ).forEach( attach );
document.addEventListener( 'siteryn-is:init', function ( e ) { attach( e.detail.instance ); } );
JavaScript API
| Member | Use |
|---|---|
SiterynInfiniteScroll.get( i ) · .instances | The loader (first by default) · all loaders |
SiterynInfiniteScroll.create( list, overrides ) | Start a loader on another list; returns null when the list is already enhanced, sits inside .siteryn-is-disable or has no next page. Overrides are merged one level deep, so a selectors override replaces the whole object |
SiterynInfiniteScroll.formatNumber( n ) | A number in the page language's digits and grouping |
SiterynInfiniteScroll.config | The page config (window.siterynIsConfig) |
loader.load() | Load the next page now; resolves true when products were appended |
loader.setMode( 'infinite' | 'load_more' ) | Switch mode |
loader.setButtonLabel( text ) | Change the Load More label shown when it isn't loading |
loader.fetchDocument( url, signal ) | The single network call every load goes through. You may wrap it; it must resolve { html, url } or reject |
loader.reset( nextUrl ) | Re-read the list after your own code replaced its products |
loader.destroy() | Stop and restore the theme pagination; then create() on a new list |
document.addEventListener( 'siteryn-is:loaded', function ( e ) {
if ( 'scroll' === e.detail.trigger && e.detail.page >= 4 ) {
e.detail.instance.setMode( 'load_more' );
}
} );
Page config
window.siterynIsConfig (filter it with siteryn_is_frontend_config):
| Key | Content |
|---|---|
mode · buttonLoading | infinite or load_more · button or text (While Loading) |
mobile | { mode, breakpoint } (On Small Screens, Small Screen Width) |
threshold · timeout | Trigger Distance in px · request timeout in ms (20000) |
animate · focusNew · showEnd | Fade In New Products · Move Focus to New Products · Show End of Results Message |
selectors | { container, item, pagination, next, count } |
state | { currentPage, totalPages, totalProducts, nextUrl } from the main query |
i18n | { loading, end, error, retry, button, loaded } |
version · locale · context · templateId | Plugin version · BCP 47 locale · shop or product_cat · the controls <template> id |
Markup and classes
- Loaded products get
data-siteryn-is-page="N"(and.siteryn-is-item-enterwhile fading in). - The list gets
.siteryn-is-containerandaria-busywhile loading; the controls get.is-loading,.is-doneand.is-error,data-siteryn-is-mode, and--align-*,--loader-*and--style-*modifier classes. - Colors and sizes are CSS custom properties on
.siteryn-is-controls, such as--siteryn-is-button-bg,--siteryn-is-button-radius,--siteryn-is-link-colorand--siteryn-is-loader-size. - The hidden theme pagination gets
.siteryn-is-pagination-hidden. - Lists inside
.siteryn-is-disableare skipped. Only the first eligible list on a page is enhanced.
Custom selectors in code
add_filter( 'siteryn_is_settings_get_all', function ( $s ) {
if ( is_admin() ) {
return $s; // otherwise the settings form shows (and saves) these values
}
$s['container_selector'] = '.my-grid';
$s['item_selector'] = '.product-card'; // direct children of the list
$s['pagination_selector'] = '.my-pagination';
$s['next_selector'] = '.my-pagination a.next';
$s['result_count_selector'] = '.my-result-count';
return $s;
} );
Templates
| Template | Theme override | Contains |
|---|---|---|
controls.php (Free) | your-theme/siteryn-infinite-scroll/controls.php | Live region, loader, Load More, end and error messages, sentinel |
pro-controls.php (Pro) | your-theme/siteryn-infinite-scroll/pro-controls.php | Product counter |
pro-extras.php (Pro) | your-theme/siteryn-infinite-scroll/pro-extras.php | Previous / Next pager, Show All button, top progress bar |
Keep every data-siteryn-is-* attribute. controls.php receives $settings, $context and $pagination; keep its @version header, because an outdated copy triggers an admin notice.
Data
| Name | Type | Content |
|---|---|---|
siteryn_is_settings · siteryn_is_version | Option | Free settings · version |
siteryn_is_pro_settings · siteryn_is_pro_version | Option | Pro settings (including term_rules) · version |
siteryn_license_infinite-scroll_key · _status · _update | Option | Pro license key and update check (not autoloaded) · cached status (autoloaded, read on storefront requests) |
siteryn_license_infinite-scroll_retry · _lock | Transient | License server back-off after a failed check (1 hour) · request lock (30 seconds) |
siteryn_is_outdated_templates | Transient | Outdated template check, 1 day |
siteryn_is_pro_license_check | Cron | Daily license check |
No post or term meta is written. Uninstalling removes each plugin's own options (on every site of a network).
Endpoints
No REST routes, shortcodes, blocks or public AJAX actions. Admin endpoints require manage_woocommerce and a nonce:
| Action | Used for |
|---|---|
wp_ajax_siteryn_is_save_settings | Saving a settings tab |
admin_post_siteryn_is_reset_settings · admin_post_siteryn_is_pro_reset_settings | Reset to Defaults |
wp_ajax_siteryn_deactivation_feedback | The optional deactivation survey on the Plugins screen (needs activate_plugins) |
wp_ajax_siteryn_is_pro_license_action | Activate, deactivate or check the license (do) |
wp-config constants
| Constant | Effect |
|---|---|
SCRIPT_DEBUG | Load unminified CSS and JS |
SITERYN_IS_PRO_LICENSE_API_BASE | License server URL (default https://siteryn.com/wp-json/siteryn/v1) |
SITERYN_IS_PRO_LICENSE_ALLOW_HTTP | Allow a plain-HTTP license server (local development only) |
Every hook, with more examples, is on Hooks reference.