From 7da90f63a3b8384c2c483190f95e69b7f92a6196 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Wed, 29 Jul 2026 11:19:20 +0200 Subject: [PATCH] Fix form options source query parameters --- CHANGELOG.md | 1 + .../sqlpage/migrations/01_documentation.sql | 4 +++- sqlpage/tomselect.js | 6 +++--- tests/end-to-end/official-site.spec.ts | 12 ++++++++---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a833e5a3..b7b9126b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## unreleased - `sqlpage.send_mail` now supports rich email bodies. Use `body_html` for a caller-provided HTML alternative, or `body_md` to render Markdown as HTML. Messages retain a plain-text alternative; `body` may be omitted when `body_md` is used, and `body_md` and `body_html` cannot be combined. + - Form `options_source` URLs now preserve existing query parameters when adding the dynamic `search` parameter. ## v0.45 diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index 012b670a..5f1ee7ca 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -442,6 +442,8 @@ We''ll write a second SQL file, `options_source.sql`, that will receive the user When both `options` and `options_source` are set, the local `options` are loaded first. Search results from `options_source` are loaded into the same option list; a result with the same `value` updates the existing option. This is useful to set a default preselected value with `options_source`. +`options_source` may already contain query parameters. SQLPage preserves them and adds or replaces the `search` parameter when loading options. + ##### `options_source.sql` ```sql @@ -458,7 +460,7 @@ where label like $search || ''%''; {"name": "component", "type": "select", "value": "form", "options": [{"label": "Form", "value": "form"}], - "options_source": "examples/from_component_options_source.sql", + "options_source": "examples/from_component_options_source.sql?category=component", "description": "Start typing the name of a component like ''map'' or ''form''..." }]')), ('form', 'This example illustrates the use of the `radio` type. diff --git a/sqlpage/tomselect.js b/sqlpage/tomselect.js index 90dd15df..1e0f40e1 100644 --- a/sqlpage/tomselect.js +++ b/sqlpage/tomselect.js @@ -53,9 +53,9 @@ function sqlpage_load_options_source(options_source) { if (!options_source) return; return async (query, callback) => { const err = (label) => callback([{ label, value: "" }]); - const resp = await fetch( - `${options_source}?search=${encodeURIComponent(query)}`, - ); + const options_url = new URL(options_source, document.baseURI); + options_url.searchParams.set("search", query); + const resp = await fetch(options_url); if (!resp.ok) { return err( `Error loading options from "${options_source}": ${resp.status} ${resp.statusText}`, diff --git a/tests/end-to-end/official-site.spec.ts b/tests/end-to-end/official-site.spec.ts index f4f5618d..c7f8c728 100644 --- a/tests/end-to-end/official-site.spec.ts +++ b/tests/end-to-end/official-site.spec.ts @@ -213,14 +213,14 @@ test("form select combines initial options with remote search results", async ({ const select = page .locator( - 'select[data-options_source="examples/from_component_options_source.sql"]', + 'select[data-options_source="examples/from_component_options_source.sql?category=component"]', ) .first(); await expect(select).toBeAttached(); await page.waitForFunction( () => !!document.querySelector( - 'select[data-options_source="examples/from_component_options_source.sql"]', + 'select[data-options_source="examples/from_component_options_source.sql?category=component"]', )?.tomselect, ); @@ -246,7 +246,9 @@ test("form select combines initial options with remote search results", async ({ await page.waitForResponse((response) => response .url() - .includes("examples/from_component_options_source.sql?search=form"), + .includes( + "examples/from_component_options_source.sql?category=component&search=form", + ), ); await expect .poll(async () => @@ -265,7 +267,9 @@ test("form select combines initial options with remote search results", async ({ await page.waitForResponse((response) => response .url() - .includes("examples/from_component_options_source.sql?search=map"), + .includes( + "examples/from_component_options_source.sql?category=component&search=map", + ), ); await expect .poll(async () =>