From c93bdb960d29da895b24bdf112553ef7c36f675f Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Wed, 5 Nov 2025 21:45:52 +0100 Subject: [PATCH 1/8] Pagination component --- .../sqlpage/migrations/69_pagination.sql | 230 ++++++++++++++++++ .../99_shared_id_class_attributes.sql | 6 +- .../sqlpage/templates/pagination.handlebars | 76 ++++++ 3 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 examples/official-site/sqlpage/migrations/69_pagination.sql create mode 100644 examples/official-site/sqlpage/templates/pagination.handlebars diff --git a/examples/official-site/sqlpage/migrations/69_pagination.sql b/examples/official-site/sqlpage/migrations/69_pagination.sql new file mode 100644 index 00000000..e17ae3b3 --- /dev/null +++ b/examples/official-site/sqlpage/migrations/69_pagination.sql @@ -0,0 +1,230 @@ +INSERT INTO component(name, icon, description, introduced_in_version) VALUES + ('pagination', 'sailboat-2', ' +Pagination is a component that enables users to navigate through a large dataset. +The data is divided into pages, each containing a fixed number of rows. + +This component is typically used in conjunction with a table component. + +The pagination component offers numerous customization features: +- It supports buttons to navigate to the first or last page, as well as the previous or next page. +- Navigation buttons can display text or icons. +- If the number of pages is too large, an offset can be used to enhance the component''s readability. + +This component only handles the display of pagination. It must be dynamically generated by your code based on the volume of data to be presented to the user of your application. +', '0.40.0'); + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'pagination', * FROM (VALUES + -- Top-level parameters + ('first_link','A target URL to which the user should be directed to get to the first page. If none, the link is not displayed.','URL',TRUE,TRUE), + ('previous_link','A target URL to which the user should be directed to get to the previous page. If none, the link is not displayed.','URL',TRUE,TRUE), + ('next_link','A target URL to which the user should be directed to get to the next page. If none, the link is not displayed.','URL',TRUE,TRUE), + ('last_link','A target URL to which the user should be directed to get to the last page. If none, the link is not displayed.','URL',TRUE,TRUE), + ('first_title','The text displayed on the button to go to the first page.','TEXT',TRUE,TRUE), + ('previous_title','The text displayed on the button to go to the previous page.','TEXT',TRUE,TRUE), + ('next_title','The text displayed on the button to go to the next page.','TEXT',TRUE,TRUE), + ('last_title','The text displayed on the button to go to the last page.','TEXT',TRUE,TRUE), + ('first_disabled','disables the button to go to the first page.','BOOLEAN',TRUE,TRUE), + ('previous_disabled','disables the button to go to the previous page.','BOOLEAN',TRUE,TRUE), + ('next_disabled','Disables the button to go to the next page.','BOOLEAN',TRUE,TRUE), + ('last_disabled','disables the button to go to the last page.','BOOLEAN',TRUE,TRUE), + ('outline','Whether to use outline version of the pagination.','BOOLEAN',TRUE,TRUE), + ('circle','Whether to use circle version of the pagination.','BOOLEAN',TRUE,TRUE), + -- Item-level parameters (for each page) + ('contents','Page number.','INTEGER',FALSE,FALSE), + ('link','A target URL to which the user should be redirected to view the requested page of data.','URL',FALSE,TRUE), + ('offset','Whether to use offset to show only a few pages at a time. Usefull if the count of pages is too large. Defaults to false','BOOLEAN',FALSE,TRUE), + ('active','Whether the link is active or not. Defaults to false.','BOOLEAN',FALSE,TRUE) +) x; + + +-- Insert example(s) for the component +INSERT INTO example(component, description, properties) +VALUES ( + 'pagination', + 'This is an extremely simple example of a pagination component that displays only the page numbers, with the first page being the current page.', + JSON( + '[ + { + "component": "pagination" + }, + { + "contents": 1, + "link": "?page=1", + "active": true + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + } + ]' + ) + ), + ( + 'pagination', + 'The ouline style adds a rectangular border to each navigation link.', + JSON( + '[ + { + "component": "pagination", + "outline": true + }, + { + "contents": 1, + "link": "?page=1", + "active": true + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + } + ]' + ) + ), + ( + 'pagination', + 'The circle style adds a circular border to each navigation link.', + JSON( + '[ + { + "component": "pagination", + "circle": true + }, + { + "contents": 1, + "link": "?page=1", + "active": true + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + } + ]' + ) + ), + ( + 'pagination', + 'The following example implements navigation links that can be enabled or disabled as needed. Since a navigation link does not appear if no link is assigned to it, you must always assign a link to display it as disabled.', + JSON( + '[ + { + "component": "pagination", + "first_link": "?page", + "first_disabled": true, + "previous_link": "?page", + "previous_disabled": true, + "next_link": "#?page=2", + "last_link": "#?page=3" + + }, + { + "contents": 1, + "link": "?page=1", + "active": true + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + } + ]' + ) + ), + ( + 'pagination', + 'Instead of using icons, you can apply text to the navigation links.', + JSON( + '[ + { + "component": "pagination", + "first_title": "First", + "last_title": "Last", + "previous_title": "Previous", + "next_title": "Next", + "first_link": "?page", + "first_disabled": true, + "previous_link": "?page", + "previous_disabled": true, + "next_link": "#?page=2", + "last_link": "#?page=3" + + }, + { + "contents": 1, + "link": "?page=1", + "active": true + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + } + ]' + ) + ), + ( + 'pagination', + 'If you have a large number of pages to display, you can use an offset to represent a group of pages.', + JSON( + '[ + { + "component": "pagination", + "first_link": "#?page=1", + "previous_link": "#?page=3", + "next_link": "#?page=4", + "last_link": "#?page=99" + + }, + { + "contents": 1, + "link": "?page=1" + }, + { + "contents": 2, + "link": "?page=2" + }, + { + "contents": 3, + "link": "?page=3" + }, + { + "contents": 4, + "link": "?page=4", + "active": true + }, + { + "contents": 5, + "link": "?page=5" + }, + { + "contents": 6, + "link": "?page=6" + }, + { + "offset": true + }, + { + "contents": 99, + "link": "?page=99" + }, + ]' + ) + ); + \ No newline at end of file diff --git a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql index df054541..a5d438c8 100644 --- a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql +++ b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql @@ -19,7 +19,8 @@ FROM (VALUES ('tracking', TRUE), ('text', TRUE), ('carousel', TRUE), - ('login', TRUE) + ('login', TRUE), + ('pagination', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -51,6 +52,7 @@ FROM (VALUES ('title', TRUE), ('tracking', TRUE), ('carousel', TRUE), - ('login', TRUE) + ('login', TRUE), + ('pagination', TRUE) ); diff --git a/examples/official-site/sqlpage/templates/pagination.handlebars b/examples/official-site/sqlpage/templates/pagination.handlebars new file mode 100644 index 00000000..c4929136 --- /dev/null +++ b/examples/official-site/sqlpage/templates/pagination.handlebars @@ -0,0 +1,76 @@ +
+
+ +
+
From 4c7cef913ae89eba7aa19a2c6167acae8ab590e4 Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Tue, 11 Nov 2025 16:17:31 +0100 Subject: [PATCH 2/8] New pagination component --- .gitignore | 1 + examples/official-site/blog/pagination.png | Bin 0 -> 30914 bytes .../{69_pagination.sql => 70_pagination.sql} | 0 .../sqlpage/migrations/71_blog_pagination.sql | 160 ++++++++++++++++++ .../templates/pagination.handlebars | 0 5 files changed, 161 insertions(+) create mode 100644 examples/official-site/blog/pagination.png rename examples/official-site/sqlpage/migrations/{69_pagination.sql => 70_pagination.sql} (100%) create mode 100644 examples/official-site/sqlpage/migrations/71_blog_pagination.sql rename {examples/official-site/sqlpage => sqlpage}/templates/pagination.handlebars (100%) diff --git a/.gitignore b/.gitignore index a1a6898f..9cf4a906 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_STORE /target sqlpage.db .idea/ diff --git a/examples/official-site/blog/pagination.png b/examples/official-site/blog/pagination.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4137eadf1237a16bdf91fa77260da31661c9d6 GIT binary patch literal 30914 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 aw_fzPq*BtLozMP>tg^hiT 1 THEN + json_object( + ''page'', (CAST($page AS INTEGER) - 1), + ''idx_page'', (CASE + WHEN CAST($idx_page AS INTEGER) > 1 THEN (CAST($idx_page AS INTEGER) - 1) + ELSE $idx_page + END) + ) + ELSE json_object() END +); +``` + +The logic is quite similar for the URL to view the next page. First, it is necessary to verify that the user is not already on the last page. Then, the `page` variable can be incremented and the `idx_page` variable updated. + +``` +SET next_parameters = ( + CASE + WHEN CAST($page AS INTEGER) < CAST($pages_count AS INTEGER) THEN + json_object( + ''page'', (CAST($page AS INTEGER) + 1), + ''idx_page'', (CASE + WHEN CAST($idx_page AS INTEGER) < (CAST($pages_count AS INTEGER) - CAST($MAX_PAGES AS INTEGER) + 1) THEN (CAST($idx_page AS INTEGER) + 1) + ELSE $idx_page + END) + ) + ELSE json_object() END +); +``` + +We can now add the pagination component, which is placed below the table displaying the data. All the logic for managing the buttons is entirely handled in SQL: +- the buttons to access the first or last page, +- the buttons to view the previous or next page, +- the enabling or disabling of these buttons based on the context. + +``` +SELECT + ''pagination'' AS component, + (CAST($page AS INTEGER) = 1) AS first_disabled, + (CAST($page AS INTEGER) = 1) AS previous_disabled, + (CAST($page AS INTEGER) = CAST($pages_count AS INTEGER)) AS next_disabled, + (CAST($page AS INTEGER) = CAST($pages_count AS INTEGER)) AS last_disabled, + sqlpage.link(sqlpage.path(), json_object(''page'', 1, ''idx_page'', 1)) as first_link, + sqlpage.link(sqlpage.path(), $previous_parameters) AS previous_link, + sqlpage.link(sqlpage.path(), $next_parameters) AS next_link, + sqlpage.link( + sqlpage.path(), + json_object(''page'', $pages_count, ''idx_page'', ( + CASE + WHEN (CAST($pages_count AS INTEGER) <= CAST($MAX_PAGES AS INTEGER)) THEN 1 + ELSE (CAST($pages_count AS INTEGER) - CAST($MAX_PAGES AS INTEGER) + 1) + END) + ) + ) AS last_link, + TRUE AS outline; +``` + +The final step is to generate the page numbers based on the number of pages and the index of the first page displayed to the left of the component. To do this, we use a recursive CTE query. + +``` +WITH RECURSIVE page_numbers AS ( + SELECT $idx_page AS number + UNION ALL + SELECT number + 1 + FROM page_numbers + LIMIT CAST($MAX_PAGES AS INTEGER) +) +SELECT + number AS contents, + sqlpage.link(sqlpage.path(), json_object(''page'', number, ''idx_page'', $idx_page)) as link, + (number = CAST($page AS INTEGER)) AS active +FROM page_numbers; +``` + +If the added page matches the content of the `page` variable, the `active` option is set to `TRUE` so that the user knows it is the current page. +'); \ No newline at end of file diff --git a/examples/official-site/sqlpage/templates/pagination.handlebars b/sqlpage/templates/pagination.handlebars similarity index 100% rename from examples/official-site/sqlpage/templates/pagination.handlebars rename to sqlpage/templates/pagination.handlebars From 70a8c0ee4c07f553909987e11bed76d687347503 Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Wed, 12 Nov 2025 22:03:40 +0100 Subject: [PATCH 3/8] Some changes --- .../sqlpage/migrations/71_blog_pagination.sql | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/71_blog_pagination.sql b/examples/official-site/sqlpage/migrations/71_blog_pagination.sql index ac6de158..41b14cc6 100644 --- a/examples/official-site/sqlpage/migrations/71_blog_pagination.sql +++ b/examples/official-site/sqlpage/migrations/71_blog_pagination.sql @@ -3,7 +3,7 @@ INSERT INTO blog_posts (title, description, icon, created_at, content) VALUES ( 'How to use the pagination component', - 'Concrete advice on how to make your SQLPage webapp fast', + 'A tutorial for using the pagination component', 'sailboat-2', '2025-11-10', ' @@ -62,10 +62,11 @@ We can now read and display the data based on the active page. To do this, we si SELECT ''table'' as component SELECT - AlbumId AS id, - Title AS title + user_id AS id, + last_name AS "Last name", + first_name AS "First name" FROM - album + users LIMIT CAST($MAX_RECORD_PER_PAGE AS INTEGER) OFFSET (CAST($page AS INTEGER) - 1) * CAST($MAX_RECORD_PER_PAGE AS INTEGER); ``` From bfa3217d65b7cb0709fd48d97a4642e29cae7509 Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Sat, 15 Nov 2025 11:54:47 +0100 Subject: [PATCH 4/8] Adding a warning about the use of LIMIT and OFFSET in the blog post --- .../official-site/sqlpage/migrations/71_blog_pagination.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/official-site/sqlpage/migrations/71_blog_pagination.sql b/examples/official-site/sqlpage/migrations/71_blog_pagination.sql index 41b14cc6..859b9e8c 100644 --- a/examples/official-site/sqlpage/migrations/71_blog_pagination.sql +++ b/examples/official-site/sqlpage/migrations/71_blog_pagination.sql @@ -15,6 +15,8 @@ This component offers many options, and I recommend consulting its documentation Of course, this component only handles its display and does not implement any logic for data processing or state changes. In this tutorial, we will implement a complete example of using the pagination component with a SQLite database, but the code should work without modification (or with very little modification) with any relational database management system (RDBMS). +> This article serves as a tutorial on the pagination component, rather than an advanced guide on paginated data retrieval from a database. The document employs a straightforward approach using the LIMIT and OFFSET instructions. This approach is interesting only for datasets that are big enough not to be realistically loadable on a single webpage, yet small enough for being queryable with OFFSET...LIMIT. + ## Initialization We first need to define two constants that indicate the maximum number of rows per page and the maximum number of pages that the component should display. @@ -36,7 +38,7 @@ It is possible that the number of rows in the table is greater than the estimate ``` SET pages_count = ( CASE - WHEN (CAST($pages_count AS INTEGER) * CAST($MAX_RECORD_PER_PAGE AS INTEGER)) = CAST($records_count AS INTEGER) THEN $pages_count + WHEN MOD(CAST($records_count AS INTEGER),CAST($MAX_RECORD_PER_PAGE AS INTEGER)) = 0 THEN $pages_count ELSE (CAST($pages_count AS INTEGER) + 1) END ); From 5a04ced0d0fccfbde422b116207506b02fbc6c86 Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Sat, 22 Nov 2025 07:47:30 +0100 Subject: [PATCH 5/8] Use icon_img for icons --- sqlpage/templates/pagination.handlebars | 33 +++---------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/sqlpage/templates/pagination.handlebars b/sqlpage/templates/pagination.handlebars index c4929136..970bf4e7 100644 --- a/sqlpage/templates/pagination.handlebars +++ b/sqlpage/templates/pagination.handlebars @@ -6,14 +6,7 @@ {{#if first_title}} {{first_title}} {{else}} - - - - - - + {{icon_img 'chevrons-left'}} {{/if}} {{/if}} @@ -22,13 +15,7 @@ {{#if previous_title}} {{previous_title}} {{else}} - - - - - + {{icon_img 'chevron-left'}} {{/if}} {{/if}} @@ -46,12 +33,7 @@ {{#if next_title}} {{next_title}} {{else}} - - - - + {{icon_img 'chevron-right'}} {{/if}} {{/if}} @@ -60,14 +42,7 @@ {{#if last_title}} {{last_title}} {{else}} - - - - - - + {{icon_img 'chevrons-right'}} {{/if}} {{/if}} From 6d1c7c91c9b760034973af6ad82473131eba237e Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Sat, 22 Nov 2025 18:54:00 +0100 Subject: [PATCH 6/8] Correction of the icons size --- sqlpage/templates/pagination.handlebars | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlpage/templates/pagination.handlebars b/sqlpage/templates/pagination.handlebars index 970bf4e7..c3cf29a4 100644 --- a/sqlpage/templates/pagination.handlebars +++ b/sqlpage/templates/pagination.handlebars @@ -6,7 +6,7 @@ {{#if first_title}} {{first_title}} {{else}} - {{icon_img 'chevrons-left'}} + {{icon_img 'chevrons-left' 19}} {{/if}} {{/if}} @@ -15,7 +15,7 @@ {{#if previous_title}} {{previous_title}} {{else}} - {{icon_img 'chevron-left'}} + {{icon_img 'chevron-left' 19}} {{/if}} {{/if}} @@ -33,7 +33,7 @@ {{#if next_title}} {{next_title}} {{else}} - {{icon_img 'chevron-right'}} + {{icon_img 'chevron-right' 19}} {{/if}} {{/if}} @@ -42,7 +42,7 @@ {{#if last_title}} {{last_title}} {{else}} - {{icon_img 'chevrons-right'}} + {{icon_img 'chevrons-right' 19}} {{/if}} {{/if}} From ba7956f4132fea7cbd109edf7a5e014fc1126c58 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 23 Nov 2025 00:26:20 +0100 Subject: [PATCH 7/8] enhance pagination docs examples --- .../sqlpage/migrations/70_pagination.sql | 89 ++++++++++++------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/70_pagination.sql b/examples/official-site/sqlpage/migrations/70_pagination.sql index e17ae3b3..4d1dded9 100644 --- a/examples/official-site/sqlpage/migrations/70_pagination.sql +++ b/examples/official-site/sqlpage/migrations/70_pagination.sql @@ -1,16 +1,37 @@ INSERT INTO component(name, icon, description, introduced_in_version) VALUES ('pagination', 'sailboat-2', ' -Pagination is a component that enables users to navigate through a large dataset. -The data is divided into pages, each containing a fixed number of rows. +Navigation links to go to the first, previous, next, or last page of a dataset. +Useful when data is divided into pages, each containing a fixed number of rows. -This component is typically used in conjunction with a table component. +This component only handles the display of pagination. +**Your sql queries are responsible for filtering data** based on the page number passed as a URL parameter. -The pagination component offers numerous customization features: -- It supports buttons to navigate to the first or last page, as well as the previous or next page. -- Navigation buttons can display text or icons. -- If the number of pages is too large, an offset can be used to enhance the component''s readability. +This component is typically used in conjunction with a [table](?component=table), +[list](?component=list), or [card](?component=card) component. -This component only handles the display of pagination. It must be dynamically generated by your code based on the volume of data to be presented to the user of your application. +The pagination component displays navigation buttons (first, previous, next, last) customizable with text or icons. + +For large numbers of pages, an offset can limit the visible page links. + +A minimal example of a SQL query that uses the pagination would be: +```sql +select ''table'' as component; +select * from my_table limit 100 offset $offset; + +select ''pagination'' as component; +with recursive pages as ( + select 0 as offset + union all + select offset + 100 from pages + where offset + 100 < (select count(*) from my_table) +) +select + (offset/100+1) as contents, + sqlpage.link(sqlpage.path(), json_object(''offset'', offset)) as link, + (offset/100+1 = $offset) as active from pages; +``` + +For more advanced usage, the [pagination guide](blog.sql?post=How+to+use+the+pagination+component) provides a complete tutorial. ', '0.40.0'); INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'pagination', * FROM (VALUES @@ -49,16 +70,16 @@ VALUES ( }, { "contents": 1, - "link": "?page=1", + "link": "?component=pagination&page=1", "active": true }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" } ]' ) @@ -74,16 +95,16 @@ VALUES ( }, { "contents": 1, - "link": "?page=1", + "link": "?component=pagination&page=1", "active": true }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" } ]' ) @@ -99,16 +120,16 @@ VALUES ( }, { "contents": 1, - "link": "?page=1", + "link": "?component=pagination&page=1", "active": true }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" } ]' ) @@ -120,9 +141,9 @@ VALUES ( '[ { "component": "pagination", - "first_link": "?page", + "first_link": "?component=pagination", "first_disabled": true, - "previous_link": "?page", + "previous_link": "?component=pagination", "previous_disabled": true, "next_link": "#?page=2", "last_link": "#?page=3" @@ -130,16 +151,16 @@ VALUES ( }, { "contents": 1, - "link": "?page=1", + "link": "?component=pagination&page=1", "active": true }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" } ]' ) @@ -155,9 +176,9 @@ VALUES ( "last_title": "Last", "previous_title": "Previous", "next_title": "Next", - "first_link": "?page", + "first_link": "?component=pagination", "first_disabled": true, - "previous_link": "?page", + "previous_link": "?component=pagination", "previous_disabled": true, "next_link": "#?page=2", "last_link": "#?page=3" @@ -165,16 +186,16 @@ VALUES ( }, { "contents": 1, - "link": "?page=1", + "link": "?component=pagination&page=1", "active": true }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" } ]' ) @@ -194,35 +215,35 @@ VALUES ( }, { "contents": 1, - "link": "?page=1" + "link": "?component=pagination&page=1" }, { "contents": 2, - "link": "?page=2" + "link": "?component=pagination&page=2" }, { "contents": 3, - "link": "?page=3" + "link": "?component=pagination&page=3" }, { "contents": 4, - "link": "?page=4", + "link": "?component=pagination&page=4", "active": true }, { "contents": 5, - "link": "?page=5" + "link": "?component=pagination&page=5" }, { "contents": 6, - "link": "?page=6" + "link": "?component=pagination&page=6" }, { "offset": true }, { "contents": 99, - "link": "?page=99" + "link": "?component=pagination&page=99" }, ]' ) From c4cfcf1056329b0a6e509e2d87b60cff77ce18db Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sun, 23 Nov 2025 00:40:07 +0100 Subject: [PATCH 8/8] update pagination docs --- examples/official-site/sqlpage/migrations/70_pagination.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/official-site/sqlpage/migrations/70_pagination.sql b/examples/official-site/sqlpage/migrations/70_pagination.sql index 4d1dded9..5bb50d01 100644 --- a/examples/official-site/sqlpage/migrations/70_pagination.sql +++ b/examples/official-site/sqlpage/migrations/70_pagination.sql @@ -28,7 +28,8 @@ with recursive pages as ( select (offset/100+1) as contents, sqlpage.link(sqlpage.path(), json_object(''offset'', offset)) as link, - (offset/100+1 = $offset) as active from pages; + offset = coalesce(cast($offset as integer), 0) as active +from pages; ``` For more advanced usage, the [pagination guide](blog.sql?post=How+to+use+the+pagination+component) provides a complete tutorial.