Convert all API endpoint URLs to SX expression format
Every URL at sx-web.org now uses bracketed SX expressions — pages AND API endpoints. defhandler :path values, sx-get/sx-post/sx-delete attrs, code examples, and Python route decorators all converted. - Add SxAtomConverter to handlers.py for parameter matching inside expression URLs (e.g. /(api.(item.<sx:item_id>))) - Convert ~50 defhandler :path values in ref-api.sx and examples.sx - Convert ~90 sx-get/sx-post/sx-delete URLs in reference.sx, examples.sx - Convert ~30 code example URLs in examples-content.sx - Convert ~30 API URLs in pages.py (Python string code examples) - Convert ~70 page navigation URLs in pages.py - Convert 7 Python route decorators in routes.py - Convert ~10 reactive API URLs in marshes.sx - Add API redirect patterns to sx_router.py (301 for old paths) - Remove /api/ skip in app.py redirects (old API paths now redirect) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
:description "The simplest sx interaction: click a button, fetch content from the server, swap it in."
|
||||
:demo-description "Click the button to load server-rendered content."
|
||||
:demo (~click-to-load-demo)
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/click\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.click))))\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
|
||||
:handler-code (handler-source "ex-click")
|
||||
:comp-placeholder-id "click-comp"
|
||||
:wire-placeholder-id "click-wire"
|
||||
@@ -20,7 +20,7 @@
|
||||
:description "Forms with sx-post submit via AJAX and swap the response into a target."
|
||||
:demo-description "Enter a name and submit."
|
||||
:demo (~form-demo)
|
||||
:sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/form\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
|
||||
:sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.form))))\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
|
||||
:handler-code (handler-source "ex-form")
|
||||
:comp-placeholder-id "form-comp"
|
||||
:wire-placeholder-id "form-wire"))
|
||||
@@ -31,7 +31,7 @@
|
||||
:description "Use sx-trigger with \"every\" to poll the server at regular intervals."
|
||||
:demo-description "This div polls the server every 2 seconds."
|
||||
:demo (~polling-demo)
|
||||
:sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/poll\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
|
||||
:sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.poll))))\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
|
||||
:handler-code (handler-source "ex-poll")
|
||||
:comp-placeholder-id "poll-comp"
|
||||
:wire-placeholder-id "poll-wire"
|
||||
@@ -48,7 +48,7 @@
|
||||
(list "3" "Write documentation")
|
||||
(list "4" "Deploy to production")
|
||||
(list "5" "Add unit tests")))
|
||||
:sx-code "(button\n :sx-delete \"/api/delete/1\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
|
||||
:sx-code "(button\n :sx-delete \"/(geography.(hypermedia.(example.(api.(delete.1)))))\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
|
||||
:handler-code (handler-source "ex-delete")
|
||||
:comp-placeholder-id "delete-comp"
|
||||
:wire-placeholder-id "delete-wire"
|
||||
@@ -73,7 +73,7 @@
|
||||
:description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM."
|
||||
:demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."
|
||||
:demo (~oob-demo)
|
||||
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/geography/hypermedia/examples/api/oob\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
|
||||
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.oob))))\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
|
||||
:handler-code (handler-source "ex-oob")
|
||||
:wire-placeholder-id "oob-wire"
|
||||
:wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID."))
|
||||
@@ -84,7 +84,7 @@
|
||||
:description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold."
|
||||
:demo-description "Content loads automatically when the page renders."
|
||||
:demo (~lazy-loading-demo)
|
||||
:sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/lazy\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
|
||||
:sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.lazy))))\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
|
||||
:handler-code (handler-source "ex-lazy")
|
||||
:comp-placeholder-id "lazy-comp"
|
||||
:wire-placeholder-id "lazy-wire"))
|
||||
@@ -95,7 +95,7 @@
|
||||
:description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel."
|
||||
:demo-description "Scroll down in the container to load more items (5 pages total)."
|
||||
:demo (~infinite-scroll-demo)
|
||||
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/geography/hypermedia/examples/api/scroll?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
|
||||
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/(geography.(hypermedia.(example.(api.scroll))))?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
|
||||
:handler-code (handler-source "ex-scroll")
|
||||
:comp-placeholder-id "scroll-comp"
|
||||
:wire-placeholder-id "scroll-wire"))
|
||||
@@ -106,7 +106,7 @@
|
||||
:description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete."
|
||||
:demo-description "Click start to begin a simulated job."
|
||||
:demo (~progress-bar-demo)
|
||||
:sx-code ";; Start the job\n(button\n :sx-post \"/geography/hypermedia/examples/api/progress/start\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/api/progress/status?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
|
||||
:sx-code ";; Start the job\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.progress-start))))\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/(geography.(hypermedia.(example.(api.progress-status))))?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
|
||||
:handler-code (str (handler-source "ex-progress-start") "\n\n" (handler-source "ex-progress-status"))
|
||||
:comp-placeholder-id "progress-comp"
|
||||
:wire-placeholder-id "progress-wire"))
|
||||
@@ -117,7 +117,7 @@
|
||||
:description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages."
|
||||
:demo-description "Type to search through 20 programming languages."
|
||||
:demo (~active-search-demo)
|
||||
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/search\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
|
||||
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.search))))\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
|
||||
:handler-code (handler-source "ex-search")
|
||||
:comp-placeholder-id "search-comp"
|
||||
:wire-placeholder-id "search-wire"))
|
||||
@@ -128,7 +128,7 @@
|
||||
:description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline."
|
||||
:demo-description "Enter an email and click away (blur) to validate."
|
||||
:demo (~inline-validation-demo)
|
||||
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/geography/hypermedia/examples/api/validate\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
|
||||
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/(geography.(hypermedia.(example.(api.validate))))\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
|
||||
:handler-code (handler-source "ex-validate")
|
||||
:comp-placeholder-id "validate-comp"
|
||||
:wire-placeholder-id "validate-wire"))
|
||||
@@ -139,7 +139,7 @@
|
||||
:description "Two linked selects: pick a category and the second select updates with matching items via sx-get."
|
||||
:demo-description "Select a category to populate the item dropdown."
|
||||
:demo (~value-select-demo)
|
||||
:sx-code "(select :name \"category\"\n :sx-get \"/geography/hypermedia/examples/api/values\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
|
||||
:sx-code "(select :name \"category\"\n :sx-get \"/(geography.(hypermedia.(example.(api.values))))\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
|
||||
:handler-code (handler-source "ex-values")
|
||||
:comp-placeholder-id "values-comp"
|
||||
:wire-placeholder-id "values-wire"))
|
||||
@@ -150,7 +150,7 @@
|
||||
:description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission."
|
||||
:demo-description "Submit a message — the input resets after each send."
|
||||
:demo (~reset-on-submit-demo)
|
||||
:sx-code "(form :id \"reset-form\"\n :sx-post \"/geography/hypermedia/examples/api/reset-submit\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
|
||||
:sx-code "(form :id \"reset-form\"\n :sx-post \"/(geography.(hypermedia.(example.(api.reset-submit))))\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
|
||||
:handler-code (handler-source "ex-reset-submit")
|
||||
:comp-placeholder-id "reset-comp"
|
||||
:wire-placeholder-id "reset-wire"))
|
||||
@@ -165,7 +165,7 @@
|
||||
(list "2" "Widget B" "24.50" "89")
|
||||
(list "3" "Widget C" "12.00" "305")
|
||||
(list "4" "Widget D" "45.00" "67")))
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
|
||||
:handler-code (str (handler-source "ex-editrow-form") "\n\n" (handler-source "ex-editrow-save"))
|
||||
:comp-placeholder-id "editrow-comp"
|
||||
:wire-placeholder-id "editrow-wire"))
|
||||
@@ -181,7 +181,7 @@
|
||||
(list "3" "Carol Zhang" "carol@example.com" "active")
|
||||
(list "4" "Dan Okafor" "dan@example.com" "inactive")
|
||||
(list "5" "Eve Larsson" "eve@example.com" "active")))
|
||||
:sx-code "(button\n :sx-post \"/geography/hypermedia/examples/api/bulk?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
|
||||
:sx-code "(button\n :sx-post \"/(geography.(hypermedia.(example.(api.bulk))))?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
|
||||
:handler-code (handler-source "ex-bulk")
|
||||
:comp-placeholder-id "bulk-comp"
|
||||
:wire-placeholder-id "bulk-wire"))
|
||||
@@ -192,7 +192,7 @@
|
||||
:description "Demonstrates different swap modes: beforeend appends, afterbegin prepends, and none skips the main swap while still processing OOB updates."
|
||||
:demo-description "Try each button to see different swap behaviours."
|
||||
:demo (~swap-positions-demo)
|
||||
:sx-code ";; Append to end\n(button :sx-post \"/api/swap-log?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/api/swap-log?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/api/swap-log?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
|
||||
:sx-code ";; Append to end\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
|
||||
:handler-code (handler-source "ex-swap-log")
|
||||
:wire-placeholder-id "swap-wire"))
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
:description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters."
|
||||
:demo-description "Different buttons select different parts of the same server response."
|
||||
:demo (~select-filter-demo)
|
||||
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
|
||||
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
|
||||
:handler-code (handler-source "ex-dashboard")
|
||||
:wire-placeholder-id "filter-wire"))
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
:description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs."
|
||||
:demo-description "Click tabs to switch content. Watch the browser URL change."
|
||||
:demo (~tabs-demo)
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/tabs/tab1\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/geography/hypermedia/examples/tabs?tab=tab1\"\n \"Overview\")"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(tabs.tab1)))))\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/(geography.(hypermedia.(example.tabs)))?tab=tab1\"\n \"Overview\")"
|
||||
:handler-code (handler-source "ex-tabs")
|
||||
:wire-placeholder-id "tabs-wire"))
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
:description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour."
|
||||
:demo-description "Click to swap in content with a fade-in animation."
|
||||
:demo (~animations-demo)
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/animate\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.animate))))\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
|
||||
:handler-code (handler-source "ex-animate")
|
||||
:comp-placeholder-id "anim-comp"
|
||||
:wire-placeholder-id "anim-wire"))
|
||||
@@ -233,7 +233,7 @@
|
||||
:description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed."
|
||||
:demo-description "Click to open a modal dialog."
|
||||
:demo (~dialogs-demo)
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/dialog\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/geography/hypermedia/examples/api/dialog/close\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog-close))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
|
||||
:handler-code (str (handler-source "ex-dialog") "\n\n" (handler-source "ex-dialog-close"))
|
||||
:comp-placeholder-id "dialog-comp"
|
||||
:wire-placeholder-id "dialog-wire"))
|
||||
@@ -244,7 +244,7 @@
|
||||
:description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs."
|
||||
:demo-description "Press s, n, or h on your keyboard."
|
||||
:demo (~keyboard-shortcuts-demo)
|
||||
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/geography/hypermedia/examples/api/keyboard?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
|
||||
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/(geography.(hypermedia.(example.(api.keyboard))))?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
|
||||
:handler-code (handler-source "ex-keyboard")
|
||||
:comp-placeholder-id "kbd-comp"
|
||||
:wire-placeholder-id "kbd-wire"))
|
||||
@@ -255,7 +255,7 @@
|
||||
:description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields."
|
||||
:demo-description "Click Edit All to replace the full profile via PUT."
|
||||
:demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer")
|
||||
:sx-code ";; Replace entire resource\n(form :sx-put \"/geography/hypermedia/examples/api/putpatch\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
|
||||
:sx-code ";; Replace entire resource\n(form :sx-put \"/(geography.(hypermedia.(example.(api.putpatch))))\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
|
||||
:handler-code (str (handler-source "ex-pp-edit-all") "\n\n" (handler-source "ex-pp-put"))
|
||||
:comp-placeholder-id "pp-comp"
|
||||
:wire-placeholder-id "pp-wire"))
|
||||
@@ -266,7 +266,7 @@
|
||||
:description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received."
|
||||
:demo-description "Submit the form and see the JSON body the server received."
|
||||
:demo (~json-encoding-demo)
|
||||
:sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/json-echo\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
|
||||
:sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.json-echo))))\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
|
||||
:handler-code (handler-source "ex-json-echo")
|
||||
:comp-placeholder-id "json-comp"
|
||||
:wire-placeholder-id "json-wire"))
|
||||
@@ -277,7 +277,7 @@
|
||||
:description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received."
|
||||
:demo-description "Click each button to see what the server receives."
|
||||
:demo (~vals-headers-demo)
|
||||
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-vals\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-headers\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
|
||||
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-vals))))\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-headers))))\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
|
||||
:handler-code (str (handler-source "ex-echo-vals") "\n\n" (handler-source "ex-echo-headers"))
|
||||
:comp-placeholder-id "vals-comp"
|
||||
:wire-placeholder-id "vals-wire"))
|
||||
@@ -288,7 +288,7 @@
|
||||
:description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading."
|
||||
:demo-description "Click the button — it shows a spinner during the 2-second request."
|
||||
:demo (~loading-states-demo)
|
||||
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/geography/hypermedia/examples/api/slow\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
|
||||
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow))))\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
|
||||
:handler-code (handler-source "ex-slow")
|
||||
:comp-placeholder-id "loading-comp"
|
||||
:wire-placeholder-id "loading-wire"))
|
||||
@@ -299,7 +299,7 @@
|
||||
:description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays."
|
||||
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
|
||||
:demo (~sync-replace-demo)
|
||||
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/slow-search\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
|
||||
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow-search))))\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
|
||||
:handler-code (handler-source "ex-slow-search")
|
||||
:comp-placeholder-id "sync-comp"
|
||||
:wire-placeholder-id "sync-wire"))
|
||||
@@ -310,7 +310,7 @@
|
||||
:description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd."
|
||||
:demo-description "Click the button — watch it retry automatically after failures."
|
||||
:demo (~retry-demo)
|
||||
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/flaky\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
|
||||
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.flaky))))\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
|
||||
:handler-code (handler-source "ex-flaky")
|
||||
:comp-placeholder-id "retry-comp"
|
||||
:wire-placeholder-id "retry-wire"))
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-click
|
||||
:path "/geography/hypermedia/examples/api/click"
|
||||
:path "/(geography.(hypermedia.(example.(api.click))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -73,7 +73,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-form
|
||||
:path "/geography/hypermedia/examples/api/form"
|
||||
:path "/(geography.(hypermedia.(example.(api.form))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -92,7 +92,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-poll
|
||||
:path "/geography/hypermedia/examples/api/poll"
|
||||
:path "/(geography.(hypermedia.(example.(api.poll))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -113,7 +113,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-delete
|
||||
:path "/geography/hypermedia/examples/api/delete/<item_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))"
|
||||
:method :delete
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -130,7 +130,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-edit-form
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -143,7 +143,7 @@
|
||||
:text (str "(~inline-edit-form :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-save
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -157,7 +157,7 @@
|
||||
:text (str "(~inline-view :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-cancel
|
||||
:path "/geography/hypermedia/examples/api/edit/cancel"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit-cancel))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -175,7 +175,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-oob
|
||||
:path "/geography/hypermedia/examples/api/oob"
|
||||
:path "/(geography.(hypermedia.(example.(api.oob))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -195,7 +195,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-lazy
|
||||
:path "/geography/hypermedia/examples/api/lazy"
|
||||
:path "/(geography.(hypermedia.(example.(api.lazy))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -213,7 +213,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-scroll
|
||||
:path "/geography/hypermedia/examples/api/scroll"
|
||||
:path "/(geography.(hypermedia.(example.(api.scroll))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -227,7 +227,7 @@
|
||||
(range start (+ start 5)))
|
||||
(if (<= (+ pg 1) 6)
|
||||
(div :id "scroll-sentinel"
|
||||
:sx-get (str "/geography/hypermedia/examples/api/scroll?page=" (+ pg 1))
|
||||
:sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" (+ pg 1))
|
||||
:sx-trigger "intersect once"
|
||||
:sx-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -244,7 +244,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-progress-start
|
||||
:path "/geography/hypermedia/examples/api/progress/start"
|
||||
:path "/(geography.(hypermedia.(example.(api.progress-start))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -261,7 +261,7 @@
|
||||
:text (str "(~progress-status :percent 0 :job-id \"" job-id "\")"))))))
|
||||
|
||||
(defhandler ex-progress-status
|
||||
:path "/geography/hypermedia/examples/api/progress/status"
|
||||
:path "/(geography.(hypermedia.(example.(api.progress-status))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -282,7 +282,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-search
|
||||
:path "/geography/hypermedia/examples/api/search"
|
||||
:path "/(geography.(hypermedia.(example.(api.search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -304,7 +304,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-validate
|
||||
:path "/geography/hypermedia/examples/api/validate"
|
||||
:path "/(geography.(hypermedia.(example.(api.validate))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -331,7 +331,7 @@
|
||||
:text (nth result 1))))))
|
||||
|
||||
(defhandler ex-validate-submit
|
||||
:path "/geography/hypermedia/examples/api/validate/submit"
|
||||
:path "/(geography.(hypermedia.(example.(api.validate-submit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -347,7 +347,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-values
|
||||
:path "/geography/hypermedia/examples/api/values"
|
||||
:path "/(geography.(hypermedia.(example.(api.values))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -367,7 +367,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-reset-submit
|
||||
:path "/geography/hypermedia/examples/api/reset-submit"
|
||||
:path "/(geography.(hypermedia.(example.(api.reset-submit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -387,7 +387,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-editrow-form
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
@@ -402,7 +402,7 @@
|
||||
:text (str "(~edit-row-form :id \"" (get row "id") "\" ...)"))))))
|
||||
|
||||
(defhandler ex-editrow-save
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -420,7 +420,7 @@
|
||||
:text (str "(~edit-row-view :id \"" row-id "\" ...)")))))
|
||||
|
||||
(defhandler ex-editrow-cancel
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>/cancel"
|
||||
:path "/(geography.(hypermedia.(example.(api.(editrow-cancel.<sx:row_id>)))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
@@ -439,7 +439,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-bulk
|
||||
:path "/geography/hypermedia/examples/api/bulk"
|
||||
:path "/(geography.(hypermedia.(example.(api.bulk))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -476,7 +476,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-swap-log
|
||||
:path "/geography/hypermedia/examples/api/swap-log"
|
||||
:path "/(geography.(hypermedia.(example.(api.swap-log))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -500,7 +500,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dashboard
|
||||
:path "/geography/hypermedia/examples/api/dashboard"
|
||||
:path "/(geography.(hypermedia.(example.(api.dashboard))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -530,7 +530,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-tabs
|
||||
:path "/geography/hypermedia/examples/api/tabs/<tab>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(tabs.<sx:tab>)))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key tab)
|
||||
@@ -551,7 +551,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-animate
|
||||
:path "/geography/hypermedia/examples/api/animate"
|
||||
:path "/(geography.(hypermedia.(example.(api.animate))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -571,7 +571,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dialog
|
||||
:path "/geography/hypermedia/examples/api/dialog"
|
||||
:path "/(geography.(hypermedia.(example.(api.dialog))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -584,7 +584,7 @@
|
||||
:text "(~dialog-modal :title \"Confirm Action\" :message \"...\")")))
|
||||
|
||||
(defhandler ex-dialog-close
|
||||
:path "/geography/hypermedia/examples/api/dialog/close"
|
||||
:path "/(geography.(hypermedia.(example.(api.dialog-close))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -598,7 +598,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-keyboard
|
||||
:path "/geography/hypermedia/examples/api/keyboard"
|
||||
:path "/(geography.(hypermedia.(example.(api.keyboard))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -617,7 +617,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-pp-edit-all
|
||||
:path "/geography/hypermedia/examples/api/putpatch/edit-all"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -631,7 +631,7 @@
|
||||
:text (str "(~pp-form-full :name \"" (get p "name") "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-put
|
||||
:path "/geography/hypermedia/examples/api/putpatch"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch))))"
|
||||
:method :put
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -648,7 +648,7 @@
|
||||
:text (str "(~pp-view :name \"" name "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-cancel
|
||||
:path "/geography/hypermedia/examples/api/putpatch/cancel"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -667,7 +667,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-json-echo
|
||||
:path "/geography/hypermedia/examples/api/json-echo"
|
||||
:path "/(geography.(hypermedia.(example.(api.json-echo))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -688,7 +688,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-echo-vals
|
||||
:path "/geography/hypermedia/examples/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(example.(api.echo-vals))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -705,7 +705,7 @@
|
||||
:text (str "(~echo-result :label \"values\" :items (list ...))")))))))
|
||||
|
||||
(defhandler ex-echo-headers
|
||||
:path "/geography/hypermedia/examples/api/echo-headers"
|
||||
:path "/(geography.(hypermedia.(example.(api.echo-headers))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -725,7 +725,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow
|
||||
:path "/geography/hypermedia/examples/api/slow"
|
||||
:path "/(geography.(hypermedia.(example.(api.slow))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -744,7 +744,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow-search
|
||||
:path "/geography/hypermedia/examples/api/slow-search"
|
||||
:path "/(geography.(hypermedia.(example.(api.slow-search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -764,7 +764,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-flaky
|
||||
:path "/geography/hypermedia/examples/api/flaky"
|
||||
:path "/(geography.(hypermedia.(example.(api.flaky))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
;; --- sx-get demo: server time ---
|
||||
|
||||
(defhandler ref-time
|
||||
:path "/geography/hypermedia/reference/api/time"
|
||||
:path "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -19,7 +19,7 @@
|
||||
;; --- sx-post demo: greet ---
|
||||
|
||||
(defhandler ref-greet
|
||||
:path "/geography/hypermedia/reference/api/greet"
|
||||
:path "/(geography.(hypermedia.(reference.(api.greet))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -33,7 +33,7 @@
|
||||
;; --- sx-put demo: status update ---
|
||||
|
||||
(defhandler ref-status
|
||||
:path "/geography/hypermedia/reference/api/status"
|
||||
:path "/(geography.(hypermedia.(reference.(api.status))))"
|
||||
:method :put
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -47,7 +47,7 @@
|
||||
;; --- sx-patch demo: theme ---
|
||||
|
||||
(defhandler ref-theme
|
||||
:path "/geography/hypermedia/reference/api/theme"
|
||||
:path "/(geography.(hypermedia.(reference.(api.theme))))"
|
||||
:method :patch
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -61,7 +61,7 @@
|
||||
;; --- sx-delete demo ---
|
||||
|
||||
(defhandler ref-delete-item
|
||||
:path "/geography/hypermedia/reference/api/item/<item_id>"
|
||||
:path "/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
|
||||
:method :delete
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -72,7 +72,7 @@
|
||||
;; --- sx-trigger demo: search ---
|
||||
|
||||
(defhandler ref-trigger-search
|
||||
:path "/geography/hypermedia/reference/api/trigger-search"
|
||||
:path "/(geography.(hypermedia.(reference.(api.trigger-search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -89,7 +89,7 @@
|
||||
;; --- sx-swap demo ---
|
||||
|
||||
(defhandler ref-swap-item
|
||||
:path "/geography/hypermedia/reference/api/swap-item"
|
||||
:path "/(geography.(hypermedia.(reference.(api.swap-item))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -102,7 +102,7 @@
|
||||
;; --- sx-swap-oob demo ---
|
||||
|
||||
(defhandler ref-oob
|
||||
:path "/geography/hypermedia/reference/api/oob"
|
||||
:path "/(geography.(hypermedia.(reference.(api.oob))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -117,7 +117,7 @@
|
||||
;; --- sx-select demo ---
|
||||
|
||||
(defhandler ref-select-page
|
||||
:path "/geography/hypermedia/reference/api/select-page"
|
||||
:path "/(geography.(hypermedia.(reference.(api.select-page))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -134,7 +134,7 @@
|
||||
;; --- sx-sync demo: slow echo ---
|
||||
|
||||
(defhandler ref-slow-echo
|
||||
:path "/geography/hypermedia/reference/api/slow-echo"
|
||||
:path "/(geography.(hypermedia.(reference.(api.slow-echo))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -148,7 +148,7 @@
|
||||
;; --- sx-prompt demo ---
|
||||
|
||||
(defhandler ref-prompt-echo
|
||||
:path "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:path "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -161,7 +161,7 @@
|
||||
;; --- Error demo ---
|
||||
|
||||
(defhandler ref-error-500
|
||||
:path "/geography/hypermedia/reference/api/error-500"
|
||||
:path "/(geography.(hypermedia.(reference.(api.error-500))))"
|
||||
:method :get
|
||||
:returns "nil"
|
||||
(&key)
|
||||
@@ -175,7 +175,7 @@
|
||||
;; --- sx-encoding demo: file upload name ---
|
||||
|
||||
(defhandler ref-upload-name
|
||||
:path "/geography/hypermedia/reference/api/upload-name"
|
||||
:path "/(geography.(hypermedia.(reference.(api.upload-name))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -190,7 +190,7 @@
|
||||
;; --- sx-headers demo: echo custom headers ---
|
||||
|
||||
(defhandler ref-echo-headers
|
||||
:path "/geography/hypermedia/reference/api/echo-headers"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-headers))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -214,7 +214,7 @@
|
||||
;; --- sx-include demo: echo GET query params ---
|
||||
|
||||
(defhandler ref-echo-vals-get
|
||||
:path "/geography/hypermedia/reference/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -235,7 +235,7 @@
|
||||
;; --- sx-vals demo: echo POST form values ---
|
||||
|
||||
(defhandler ref-echo-vals-post
|
||||
:path "/geography/hypermedia/reference/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -257,7 +257,7 @@
|
||||
;; --- sx-retry demo: flaky endpoint (fails 2/3 times) ---
|
||||
|
||||
(defhandler ref-flaky
|
||||
:path "/geography/hypermedia/reference/api/flaky"
|
||||
:path "/(geography.(hypermedia.(reference.(api.flaky))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -275,7 +275,7 @@
|
||||
;; --- sx-trigger-event demo: response header triggers ---
|
||||
|
||||
(defhandler ref-trigger-event
|
||||
:path "/geography/hypermedia/reference/api/trigger-event"
|
||||
:path "/(geography.(hypermedia.(reference.(api.trigger-event))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -287,7 +287,7 @@
|
||||
;; --- sx-retarget demo: response header retargets ---
|
||||
|
||||
(defhandler ref-retarget
|
||||
:path "/geography/hypermedia/reference/api/retarget"
|
||||
:path "/(geography.(hypermedia.(reference.(api.retarget))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
|
||||
(~demo-marsh-product)
|
||||
|
||||
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/geography/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
|
||||
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/(geography.(reactive.(api.flash-sale)))\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
|
||||
|
||||
(~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp"))
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
(div :id "marsh-flash-target"
|
||||
:class "min-h-[2rem]")
|
||||
(button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-get "/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-flash-target"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch from server"))
|
||||
@@ -334,7 +334,7 @@
|
||||
|
||||
(~demo-marsh-settle)
|
||||
|
||||
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/geography/reactive/api/settle-data\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
|
||||
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/(geography.(reactive.(api.settle-data)))\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
|
||||
|
||||
(p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives."))
|
||||
|
||||
@@ -348,7 +348,7 @@
|
||||
|
||||
(~demo-marsh-signal-url)
|
||||
|
||||
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/geography/reactive/api/search/\"\n (deref mode) \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
|
||||
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/(geography.(reactive.(api.search-\"\n (deref mode) \")))\" \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
|
||||
|
||||
(p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute."))
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
|
||||
(~demo-marsh-view-transform)
|
||||
|
||||
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/geography/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
|
||||
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/(geography.(reactive.(api.catalog)))\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
|
||||
|
||||
(p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state."))
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
(div :class "border-t border-stone-200 pt-3"
|
||||
(div :class "flex items-center gap-3"
|
||||
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-get "/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-server-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Price from Server")
|
||||
@@ -471,7 +471,7 @@
|
||||
(span :class "text-sm text-stone-600" " times")))
|
||||
(div :class "flex items-center gap-3"
|
||||
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
:sx-get "/geography/reactive/api/settle-data"
|
||||
:sx-get "/(geography.(reactive.(api.settle-data)))"
|
||||
:sx-target "#settle-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-on-settle "(swap! (use-store \"settle-count\") inc)"
|
||||
@@ -516,15 +516,15 @@
|
||||
:class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400")
|
||||
(button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
:sx-get (computed (fn ()
|
||||
(str "/geography/reactive/api/search/" (deref mode)
|
||||
"?q=" (deref query))))
|
||||
(str "/(geography.(reactive.(api.search-" (deref mode)
|
||||
")))""?q=" (deref query))))
|
||||
:sx-target "#signal-results"
|
||||
:sx-swap "innerHTML"
|
||||
"Search"))
|
||||
;; Current URL display
|
||||
(p :class "text-xs text-stone-400 font-mono"
|
||||
"URL: " (computed (fn ()
|
||||
(str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query)))))
|
||||
(str "/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query)))))
|
||||
;; Results
|
||||
(div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2"
|
||||
(p :class "text-sm text-stone-400 italic" "Select a category and search.")))))
|
||||
@@ -561,7 +561,7 @@
|
||||
;; Fetch button — response writes structured data to store via data-init
|
||||
(div :class "flex items-center gap-3"
|
||||
(button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700"
|
||||
:sx-get "/geography/reactive/api/catalog"
|
||||
:sx-get "/(geography.(reactive.(api.catalog)))"
|
||||
:sx-target "#catalog-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Catalog")
|
||||
|
||||
Reference in New Issue
Block a user