Fix extra closing paren in ex-tabs handler (examples.sx)
Two issues: `)` inside string content wasn't a syntactic paren, and one extra syntactic `)` at end of handler. Removed both. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
792
sx/sx/handlers/examples.sx
Normal file
792
sx/sx/handlers/examples.sx
Normal file
@@ -0,0 +1,792 @@
|
||||
;; ==========================================================================
|
||||
;; Example API endpoints — live demos for hypermedia examples pages
|
||||
;;
|
||||
;; Each defhandler with :path registers as a public route automatically.
|
||||
;; OOB swaps show wire format and component source alongside each demo.
|
||||
;; ==========================================================================
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Data constants (captured in handler closures)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define search-languages
|
||||
(list "Python" "JavaScript" "TypeScript" "Rust" "Go" "Java" "C" "C++"
|
||||
"Ruby" "Elixir" "Haskell" "Clojure" "Scala" "Kotlin" "Swift"
|
||||
"Zig" "OCaml" "Lua" "Perl" "PHP"))
|
||||
|
||||
(define value-select-data
|
||||
{"Languages" (list "Python" "JavaScript" "Rust" "Go")
|
||||
"Frameworks" (list "Quart" "FastAPI" "React" "Svelte")
|
||||
"Databases" (list "PostgreSQL" "Redis" "SQLite" "MongoDB")})
|
||||
|
||||
(define taken-emails
|
||||
(list "admin@example.com" "test@example.com" "user@example.com"))
|
||||
|
||||
(define tab-content
|
||||
{"tab1" (div (p :class "text-stone-700" "Welcome to the Overview tab.")
|
||||
(p :class "text-stone-500 text-sm mt-2"
|
||||
"This is the default tab content loaded via sx-get."))
|
||||
"tab2" (div (p :class "text-stone-700" "Here are the details.")
|
||||
(ul :class "mt-2 space-y-1 text-sm text-stone-600"
|
||||
(li "Version: 1.0.0")
|
||||
(li "Build: 2024-01-15")
|
||||
(li "Engine: sx")))
|
||||
"tab3" (div (p :class "text-stone-700" "Recent history:")
|
||||
(ol :class "mt-2 space-y-1 text-sm text-stone-600 list-decimal list-inside"
|
||||
(li "Initial release")
|
||||
(li "Added component caching")
|
||||
(li "Wire format v2")))})
|
||||
|
||||
(define kbd-actions
|
||||
{"s" "Search panel activated"
|
||||
"n" "New item created"
|
||||
"h" "Help panel opened"})
|
||||
|
||||
(define anim-colors
|
||||
(list "bg-violet-100" "bg-emerald-100" "bg-blue-100" "bg-amber-100" "bg-rose-100"))
|
||||
|
||||
(define edit-row-defaults
|
||||
{"1" {"id" "1" "name" "Widget A" "price" "19.99" "stock" "142"}
|
||||
"2" {"id" "2" "name" "Widget B" "price" "24.50" "stock" "89"}
|
||||
"3" {"id" "3" "name" "Widget C" "price" "12.00" "stock" "305"}
|
||||
"4" {"id" "4" "name" "Widget D" "price" "45.00" "stock" "67"}})
|
||||
|
||||
(define bulk-user-defaults
|
||||
{"1" {"id" "1" "name" "Alice Chen" "email" "alice@example.com" "status" "active"}
|
||||
"2" {"id" "2" "name" "Bob Rivera" "email" "bob@example.com" "status" "inactive"}
|
||||
"3" {"id" "3" "name" "Carol Zhang" "email" "carol@example.com" "status" "active"}
|
||||
"4" {"id" "4" "name" "Dan Okafor" "email" "dan@example.com" "status" "inactive"}
|
||||
"5" {"id" "5" "name" "Eve Larsson" "email" "eve@example.com" "status" "active"}})
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Click to Load
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-click
|
||||
:path "/geography/hypermedia/examples/api/click"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((now (now "%Y-%m-%d %H:%M:%S")))
|
||||
(<>
|
||||
(~click-result :time now)
|
||||
(~doc-oob-code :target-id "click-comp"
|
||||
:text (component-source "click-result"))
|
||||
(~doc-oob-code :target-id "click-wire"
|
||||
:text (str "(~click-result :time \"" now "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Form Submission
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-form
|
||||
:path "/geography/hypermedia/examples/api/form"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((name (request-form "name" "")))
|
||||
(<>
|
||||
(~form-result :name name)
|
||||
(~doc-oob-code :target-id "form-comp"
|
||||
:text (component-source "form-result"))
|
||||
(~doc-oob-code :target-id "form-wire"
|
||||
:text (str "(~form-result :name \"" name "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Polling
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-poll
|
||||
:path "/geography/hypermedia/examples/api/poll"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((n (+ (state-get "ex-poll-n" 0) 1)))
|
||||
(state-set! "ex-poll-n" n)
|
||||
(let ((now (now "%H:%M:%S"))
|
||||
(count (if (< n 10) n 10)))
|
||||
(<>
|
||||
(~poll-result :time now :count count)
|
||||
(~doc-oob-code :target-id "poll-comp"
|
||||
:text (component-source "poll-result"))
|
||||
(~doc-oob-code :target-id "poll-wire"
|
||||
:text (str "(~poll-result :time \"" now "\" :count " count ")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Delete Row
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-delete
|
||||
:path "/geography/hypermedia/examples/api/delete/<item_id>"
|
||||
:method :delete
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key item-id)
|
||||
(<>
|
||||
(~doc-oob-code :target-id "delete-comp"
|
||||
:text (component-source "delete-row"))
|
||||
(~doc-oob-code :target-id "delete-wire"
|
||||
:text "(empty — row removed by outerHTML swap)")))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Inline Edit
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-edit-form
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((value (request-arg "value" "")))
|
||||
(<>
|
||||
(~inline-edit-form :value value)
|
||||
(~doc-oob-code :target-id "edit-comp"
|
||||
:text (component-source "inline-edit-form"))
|
||||
(~doc-oob-code :target-id "edit-wire"
|
||||
:text (str "(~inline-edit-form :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-save
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((value (request-form "value" "")))
|
||||
(<>
|
||||
(~inline-view :value value)
|
||||
(~doc-oob-code :target-id "edit-comp"
|
||||
:text (component-source "inline-view"))
|
||||
(~doc-oob-code :target-id "edit-wire"
|
||||
:text (str "(~inline-view :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-cancel
|
||||
:path "/geography/hypermedia/examples/api/edit/cancel"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((value (request-arg "value" "")))
|
||||
(<>
|
||||
(~inline-view :value value)
|
||||
(~doc-oob-code :target-id "edit-comp"
|
||||
:text (component-source "inline-view"))
|
||||
(~doc-oob-code :target-id "edit-wire"
|
||||
:text (str "(~inline-view :value \"" value "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Out-of-Band Swaps
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-oob
|
||||
:path "/geography/hypermedia/examples/api/oob"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((now (now "%H:%M:%S")))
|
||||
(<>
|
||||
(p :class "text-emerald-600 font-medium" "Box A updated!")
|
||||
(p :class "text-sm text-stone-500" (str "at " now))
|
||||
(div :id "oob-box-b" :sx-swap-oob "innerHTML"
|
||||
(p :class "text-violet-600 font-medium" "Box B updated via OOB!")
|
||||
(p :class "text-sm text-stone-500" (str "at " now)))
|
||||
(~doc-oob-code :target-id "oob-wire"
|
||||
:text (str "(<> (p ... \"Box A updated!\") (div :id \"oob-box-b\" :sx-swap-oob \"innerHTML\" (p ... \"Box B updated!\")))")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Lazy Loading
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-lazy
|
||||
:path "/geography/hypermedia/examples/api/lazy"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((now (now "%H:%M:%S")))
|
||||
(<>
|
||||
(~lazy-result :time now)
|
||||
(~doc-oob-code :target-id "lazy-comp"
|
||||
:text (component-source "lazy-result"))
|
||||
(~doc-oob-code :target-id "lazy-wire"
|
||||
:text (str "(~lazy-result :time \"" now "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Infinite Scroll
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-scroll
|
||||
:path "/geography/hypermedia/examples/api/scroll"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((page (request-arg "page" "2")))
|
||||
(let ((pg (parse-int page))
|
||||
(start (+ (* (- (parse-int page) 1) 5) 1)))
|
||||
(<>
|
||||
(map (fn (i)
|
||||
(div :class "px-4 py-3 border-b border-stone-100 text-sm text-stone-700"
|
||||
(str "Item " i " — loaded from page " page)))
|
||||
(range start (+ start 5)))
|
||||
(if (<= (+ pg 1) 6)
|
||||
(div :id "scroll-sentinel"
|
||||
:sx-get (str "/geography/hypermedia/examples/api/scroll?page=" (+ pg 1))
|
||||
:sx-trigger "intersect once"
|
||||
:sx-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
:class "p-3 text-center text-stone-400 text-sm"
|
||||
"Loading more...")
|
||||
(div :class "p-3 text-center text-stone-500 text-sm font-medium"
|
||||
"All items loaded."))
|
||||
(~doc-oob-code :target-id "scroll-wire"
|
||||
:text (str "(items for page " page " + sentinel)"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Progress Bar
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-progress-start
|
||||
:path "/geography/hypermedia/examples/api/progress/start"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((n (+ (state-get "ex-job-counter" 0) 1)))
|
||||
(state-set! "ex-job-counter" n)
|
||||
(let ((job-id (str "job-" n)))
|
||||
(state-set! (str "ex-job-" job-id) 0)
|
||||
(<>
|
||||
(~progress-status :percent 0 :job-id job-id)
|
||||
(~doc-oob-code :target-id "progress-comp"
|
||||
:text (component-source "progress-status"))
|
||||
(~doc-oob-code :target-id "progress-wire"
|
||||
:text (str "(~progress-status :percent 0 :job-id \"" job-id "\")"))))))
|
||||
|
||||
(defhandler ex-progress-status
|
||||
:path "/geography/hypermedia/examples/api/progress/status"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((job-id (request-arg "job" "")))
|
||||
(let ((current (state-get (str "ex-job-" job-id) 0)))
|
||||
(let ((next (if (>= (+ current (random-int 15 30)) 100) 100 (+ current (random-int 15 30)))))
|
||||
(state-set! (str "ex-job-" job-id) next)
|
||||
(<>
|
||||
(~progress-status :percent next :job-id job-id)
|
||||
(~doc-oob-code :target-id "progress-comp"
|
||||
:text (component-source "progress-status"))
|
||||
(~doc-oob-code :target-id "progress-wire"
|
||||
:text (str "(~progress-status :percent " next " :job-id \"" job-id "\")")))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Active Search
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-search
|
||||
:path "/geography/hypermedia/examples/api/search"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((q (request-arg "q" "")))
|
||||
(let ((results (if (= q "")
|
||||
search-languages
|
||||
(filter (fn (lang) (contains? (lower-case lang) (lower-case q)))
|
||||
search-languages))))
|
||||
(<>
|
||||
(~search-results :items results :query q)
|
||||
(~doc-oob-code :target-id "search-comp"
|
||||
:text (component-source "search-results"))
|
||||
(~doc-oob-code :target-id "search-wire"
|
||||
:text (str "(~search-results :items (list ...) :query \"" q "\")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Inline Validation
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-validate
|
||||
:path "/geography/hypermedia/examples/api/validate"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((email (request-arg "email" "")))
|
||||
(let ((result
|
||||
(cond
|
||||
(= email "")
|
||||
(list "validation-error" "(~validation-error :message \"Email is required\")"
|
||||
(~validation-error :message "Email is required"))
|
||||
(not (contains? email "@"))
|
||||
(list "validation-error" "(~validation-error :message \"Invalid email format\")"
|
||||
(~validation-error :message "Invalid email format"))
|
||||
(some (fn (e) (= (lower-case e) (lower-case email))) taken-emails)
|
||||
(list "validation-error" (str "(~validation-error :message \"" email " is already taken\")")
|
||||
(~validation-error :message (str email " is already taken")))
|
||||
:else
|
||||
(list "validation-ok" (str "(~validation-ok :email \"" email "\")")
|
||||
(~validation-ok :email email)))))
|
||||
(<>
|
||||
(nth result 2)
|
||||
(~doc-oob-code :target-id "validate-comp"
|
||||
:text (component-source (first result)))
|
||||
(~doc-oob-code :target-id "validate-wire"
|
||||
:text (nth result 1))))))
|
||||
|
||||
(defhandler ex-validate-submit
|
||||
:path "/geography/hypermedia/examples/api/validate/submit"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((email (request-form "email" "")))
|
||||
(if (or (= email "") (not (contains? email "@")))
|
||||
(p :class "text-sm text-rose-600 mt-2" "Please enter a valid email.")
|
||||
(p :class "text-sm text-emerald-600 mt-2" (str "Form submitted with: " email)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Value Select
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-values
|
||||
:path "/geography/hypermedia/examples/api/values"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((cat (request-arg "category" "")))
|
||||
(let ((items (get value-select-data cat (list))))
|
||||
(let ((options (if (empty? items)
|
||||
(list (option :value "" "No items"))
|
||||
(map (fn (i) (option :value i i)) items))))
|
||||
(<>
|
||||
options
|
||||
(~doc-oob-code :target-id "values-wire"
|
||||
:text (str "(options for \"" cat "\")")))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Reset on Submit
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-reset-submit
|
||||
:path "/geography/hypermedia/examples/api/reset-submit"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((msg (request-form "message" "(empty)"))
|
||||
(now (now "%H:%M:%S")))
|
||||
(<>
|
||||
(~reset-message :message msg :time now)
|
||||
(~doc-oob-code :target-id "reset-comp"
|
||||
:text (component-source "reset-message"))
|
||||
(~doc-oob-code :target-id "reset-wire"
|
||||
:text (str "(~reset-message :message \"" msg "\" :time \"" now "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Edit Row
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-editrow-form
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
(let ((default (get edit-row-defaults row-id {"id" row-id "name" "" "price" "0" "stock" "0"})))
|
||||
(let ((row (state-get (str "ex-row-" row-id) default)))
|
||||
(<>
|
||||
(~edit-row-form :id (get row "id") :name (get row "name")
|
||||
:price (get row "price") :stock (get row "stock"))
|
||||
(~doc-oob-code :target-id "editrow-comp"
|
||||
:text (component-source "edit-row-form"))
|
||||
(~doc-oob-code :target-id "editrow-wire"
|
||||
:text (str "(~edit-row-form :id \"" (get row "id") "\" ...)"))))))
|
||||
|
||||
(defhandler ex-editrow-save
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
(let ((name (request-form "name" ""))
|
||||
(price (request-form "price" "0"))
|
||||
(stock (request-form "stock" "0")))
|
||||
(state-set! (str "ex-row-" row-id)
|
||||
{"id" row-id "name" name "price" price "stock" stock})
|
||||
(<>
|
||||
(~edit-row-view :id row-id :name name :price price :stock stock)
|
||||
(~doc-oob-code :target-id "editrow-comp"
|
||||
:text (component-source "edit-row-view"))
|
||||
(~doc-oob-code :target-id "editrow-wire"
|
||||
:text (str "(~edit-row-view :id \"" row-id "\" ...)")))))
|
||||
|
||||
(defhandler ex-editrow-cancel
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>/cancel"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
(let ((default (get edit-row-defaults row-id {"id" row-id "name" "" "price" "0" "stock" "0"})))
|
||||
(let ((row (state-get (str "ex-row-" row-id) default)))
|
||||
(<>
|
||||
(~edit-row-view :id (get row "id") :name (get row "name")
|
||||
:price (get row "price") :stock (get row "stock"))
|
||||
(~doc-oob-code :target-id "editrow-comp"
|
||||
:text (component-source "edit-row-view"))
|
||||
(~doc-oob-code :target-id "editrow-wire"
|
||||
:text (str "(~edit-row-view :id \"" (get row "id") "\" ...)"))))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Bulk Update
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-bulk
|
||||
:path "/geography/hypermedia/examples/api/bulk"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((action (request-arg "action" "activate"))
|
||||
(ids (request-form-list "ids")))
|
||||
(let ((new-status (if (= action "activate") "active" "inactive")))
|
||||
;; Update matching users in state
|
||||
(for-each (fn (uid)
|
||||
(let ((default (get bulk-user-defaults uid nil)))
|
||||
(let ((user (state-get (str "ex-bulk-" uid) default)))
|
||||
(when user
|
||||
(state-set! (str "ex-bulk-" uid)
|
||||
(assoc user "status" new-status))))))
|
||||
ids)
|
||||
;; Return all rows
|
||||
(let ((rows (map (fn (uid)
|
||||
(let ((default (get bulk-user-defaults uid
|
||||
{"id" uid "name" "" "email" "" "status" "active"})))
|
||||
(let ((u (state-get (str "ex-bulk-" uid) default)))
|
||||
(~bulk-row :id (get u "id") :name (get u "name")
|
||||
:email (get u "email") :status (get u "status")))))
|
||||
(list "1" "2" "3" "4" "5"))))
|
||||
(<>
|
||||
rows
|
||||
(~doc-oob-code :target-id "bulk-comp"
|
||||
:text (component-source "bulk-row"))
|
||||
(~doc-oob-code :target-id "bulk-wire"
|
||||
:text (str "(updated " (len ids) " users to " new-status ")")))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Swap Positions
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-swap-log
|
||||
:path "/geography/hypermedia/examples/api/swap-log"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((mode (request-arg "mode" "beforeend"))
|
||||
(n (+ (state-get "ex-swap-n" 0) 1))
|
||||
(now (now "%H:%M:%S")))
|
||||
(state-set! "ex-swap-n" n)
|
||||
(<>
|
||||
(div :class "px-3 py-2 text-sm text-stone-700"
|
||||
(str "[" now "] " mode " (#" n ")"))
|
||||
(span :id "swap-counter" :sx-swap-oob "innerHTML"
|
||||
:class "self-center text-sm text-stone-500"
|
||||
(str "Count: " n))
|
||||
(~doc-oob-code :target-id "swap-wire"
|
||||
:text (str "(entry + oob counter: " n ")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Select Filter (Dashboard)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dashboard
|
||||
:path "/geography/hypermedia/examples/api/dashboard"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((now (now "%H:%M:%S")))
|
||||
(<>
|
||||
(div :id "dash-header" :class "p-3 bg-violet-50 rounded mb-3"
|
||||
(h4 :class "font-semibold text-violet-800" "Dashboard Header")
|
||||
(p :class "text-sm text-violet-600" (str "Generated at " now)))
|
||||
(div :id "dash-stats" :class "grid grid-cols-3 gap-3 mb-3"
|
||||
(div :class "p-3 bg-emerald-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-emerald-700" "142")
|
||||
(p :class "text-xs text-emerald-600" "Users"))
|
||||
(div :class "p-3 bg-blue-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-blue-700" "89")
|
||||
(p :class "text-xs text-blue-600" "Orders"))
|
||||
(div :class "p-3 bg-amber-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-amber-700" "$4.2k")
|
||||
(p :class "text-xs text-amber-600" "Revenue")))
|
||||
(div :id "dash-footer" :class "p-3 bg-stone-50 rounded"
|
||||
(p :class "text-sm text-stone-500" (str "Last updated: " now)))
|
||||
(~doc-oob-code :target-id "filter-wire"
|
||||
:text (str "(<> (div :id \"dash-header\" ...) (div :id \"dash-stats\" ...) (div :id \"dash-footer\" ...))")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Tabs
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-tabs
|
||||
:path "/geography/hypermedia/examples/api/tabs/<tab>"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key tab)
|
||||
(let ((content (get tab-content tab (get tab-content "tab1"))))
|
||||
(<>
|
||||
content
|
||||
(div :id "tab-buttons" :sx-swap-oob "innerHTML"
|
||||
:class "flex border-b border-stone-200"
|
||||
(~tab-btn :tab "tab1" :label "Overview" :active (if (= tab "tab1") "true" "false"))
|
||||
(~tab-btn :tab "tab2" :label "Details" :active (if (= tab "tab2") "true" "false"))
|
||||
(~tab-btn :tab "tab3" :label "History" :active (if (= tab "tab3") "true" "false")))
|
||||
(~doc-oob-code :target-id "tabs-wire"
|
||||
:text (str "(content for " tab " + oob tab buttons")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Animations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-animate
|
||||
:path "/geography/hypermedia/examples/api/animate"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((idx (random-int 0 4))
|
||||
(now (now "%H:%M:%S")))
|
||||
(let ((color (nth anim-colors idx)))
|
||||
(<>
|
||||
(~anim-result :color color :time now)
|
||||
(~doc-oob-code :target-id "anim-comp"
|
||||
:text (component-source "anim-result"))
|
||||
(~doc-oob-code :target-id "anim-wire"
|
||||
:text (str "(~anim-result :color \"" color "\" :time \"" now "\")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Dialogs
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dialog
|
||||
:path "/geography/hypermedia/examples/api/dialog"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(<>
|
||||
(~dialog-modal :title "Confirm Action"
|
||||
:message "Are you sure you want to proceed? This is a demo dialog rendered entirely with sx components.")
|
||||
(~doc-oob-code :target-id "dialog-comp"
|
||||
:text (component-source "dialog-modal"))
|
||||
(~doc-oob-code :target-id "dialog-wire"
|
||||
:text "(~dialog-modal :title \"Confirm Action\" :message \"...\")")))
|
||||
|
||||
(defhandler ex-dialog-close
|
||||
:path "/geography/hypermedia/examples/api/dialog/close"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(<>
|
||||
(~doc-oob-code :target-id "dialog-wire"
|
||||
:text "(empty — dialog closed)")))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Keyboard Shortcuts
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-keyboard
|
||||
:path "/geography/hypermedia/examples/api/keyboard"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((key (request-arg "key" "")))
|
||||
(let ((action (get kbd-actions key (str "Unknown key: " key))))
|
||||
(<>
|
||||
(~kbd-result :key key :action action)
|
||||
(~doc-oob-code :target-id "kbd-comp"
|
||||
:text (component-source "kbd-result"))
|
||||
(~doc-oob-code :target-id "kbd-wire"
|
||||
:text (str "(~kbd-result :key \"" key "\" :action \"" action "\")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; PUT / PATCH
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-pp-edit-all
|
||||
:path "/geography/hypermedia/examples/api/putpatch/edit-all"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((p (state-get "ex-profile"
|
||||
{"name" "Ada Lovelace" "email" "ada@example.com" "role" "Engineer"})))
|
||||
(<>
|
||||
(~pp-form-full :name (get p "name") :email (get p "email") :role (get p "role"))
|
||||
(~doc-oob-code :target-id "pp-comp"
|
||||
:text (component-source "pp-form-full"))
|
||||
(~doc-oob-code :target-id "pp-wire"
|
||||
:text (str "(~pp-form-full :name \"" (get p "name") "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-put
|
||||
:path "/geography/hypermedia/examples/api/putpatch"
|
||||
:method :put
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((name (request-form "name" ""))
|
||||
(email (request-form "email" ""))
|
||||
(role (request-form "role" "")))
|
||||
(state-set! "ex-profile" {"name" name "email" email "role" role})
|
||||
(<>
|
||||
(~pp-view :name name :email email :role role)
|
||||
(~doc-oob-code :target-id "pp-comp"
|
||||
:text (component-source "pp-view"))
|
||||
(~doc-oob-code :target-id "pp-wire"
|
||||
:text (str "(~pp-view :name \"" name "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-cancel
|
||||
:path "/geography/hypermedia/examples/api/putpatch/cancel"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((p (state-get "ex-profile"
|
||||
{"name" "Ada Lovelace" "email" "ada@example.com" "role" "Engineer"})))
|
||||
(<>
|
||||
(~pp-view :name (get p "name") :email (get p "email") :role (get p "role"))
|
||||
(~doc-oob-code :target-id "pp-comp"
|
||||
:text (component-source "pp-view"))
|
||||
(~doc-oob-code :target-id "pp-wire"
|
||||
:text (str "(~pp-view :name \"" (get p "name") "\" ...)")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; JSON Encoding
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-json-echo
|
||||
:path "/geography/hypermedia/examples/api/json-echo"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((data (request-json))
|
||||
(ct (request-content-type)))
|
||||
(let ((body (json-encode data)))
|
||||
(<>
|
||||
(~json-result :body body :content-type ct)
|
||||
(~doc-oob-code :target-id "json-comp"
|
||||
:text (component-source "json-result"))
|
||||
(~doc-oob-code :target-id "json-wire"
|
||||
:text (str "(~json-result :body \"" body "\" :content-type \"" ct "\")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Vals & Headers
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-echo-vals
|
||||
:path "/geography/hypermedia/examples/api/echo-vals"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((vals (into (list) (request-args-all))))
|
||||
(let ((filtered (filter (fn (pair) (and (not (= (first pair) "_"))
|
||||
(not (= (first pair) "sx-request"))))
|
||||
vals)))
|
||||
(let ((items (map (fn (pair) (str (first pair) ": " (nth pair 1))) filtered)))
|
||||
(<>
|
||||
(~echo-result :label "values" :items items)
|
||||
(~doc-oob-code :target-id "vals-comp"
|
||||
:text (component-source "echo-result"))
|
||||
(~doc-oob-code :target-id "vals-wire"
|
||||
:text (str "(~echo-result :label \"values\" :items (list ...))")))))))
|
||||
|
||||
(defhandler ex-echo-headers
|
||||
:path "/geography/hypermedia/examples/api/echo-headers"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((all-headers (into (list) (request-headers-all))))
|
||||
(let ((custom (filter (fn (pair) (starts-with? (first pair) "x-")) all-headers)))
|
||||
(let ((items (map (fn (pair) (str (first pair) ": " (nth pair 1))) custom)))
|
||||
(<>
|
||||
(~echo-result :label "headers" :items items)
|
||||
(~doc-oob-code :target-id "vals-comp"
|
||||
:text (component-source "echo-result"))
|
||||
(~doc-oob-code :target-id "vals-wire"
|
||||
:text (str "(~echo-result :label \"headers\" :items (list ...))")))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Loading States
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow
|
||||
:path "/geography/hypermedia/examples/api/slow"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(sleep 2000)
|
||||
(let ((now (now "%H:%M:%S")))
|
||||
(<>
|
||||
(~loading-result :time now)
|
||||
(~doc-oob-code :target-id "loading-comp"
|
||||
:text (component-source "loading-result"))
|
||||
(~doc-oob-code :target-id "loading-wire"
|
||||
:text (str "(~loading-result :time \"" now "\")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Request Abort (sync replace)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow-search
|
||||
:path "/geography/hypermedia/examples/api/slow-search"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((delay-ms (random-int 500 2000)))
|
||||
(sleep delay-ms)
|
||||
(let ((q (request-arg "q" "")))
|
||||
(<>
|
||||
(~sync-result :query q :delay (str delay-ms))
|
||||
(~doc-oob-code :target-id "sync-comp"
|
||||
:text (component-source "sync-result"))
|
||||
(~doc-oob-code :target-id "sync-wire"
|
||||
:text (str "(~sync-result :query \"" q "\" :delay \"" delay-ms "\")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Retry
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-flaky
|
||||
:path "/geography/hypermedia/examples/api/flaky"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
(let ((n (+ (state-get "ex-flaky-n" 0) 1)))
|
||||
(state-set! "ex-flaky-n" n)
|
||||
(if (not (= (mod n 3) 0))
|
||||
(do
|
||||
(set-response-status 503)
|
||||
"")
|
||||
(<>
|
||||
(~retry-result :attempt (str n) :message "Success! The endpoint finally responded.")
|
||||
(~doc-oob-code :target-id "retry-comp"
|
||||
:text (component-source "retry-result"))
|
||||
(~doc-oob-code :target-id "retry-wire"
|
||||
:text (str "(~retry-result :attempt \"" n "\" ...)"))))))
|
||||
Reference in New Issue
Block a user