From f0cb958ea281719677ccd59d6aecf7a63034e31e Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 22 Nov 2024 21:32:49 +0100 Subject: [PATCH] Controllers+Views+Routes: Add ScriptAfterLoad functionality --- Public/js/site.js | 12 +++++-- Sources/App/Controllers/TestController.swift | 30 ++++++++++++++++ Sources/App/Controllers/TodoController.swift | 2 ++ .../ToastComponent.swift} | 16 ++++----- .../Components/TodosTableComponent.swift | 2 ++ Sources/App/Views/Shared/MainLayout.swift | 2 +- .../App/Views/Shared/ScriptAfterLoad.swift | 35 +++++++++++++++++++ Sources/App/routes.swift | 3 +- 8 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 Sources/App/Controllers/TestController.swift rename Sources/App/Views/{Shared/ToastView.swift => Components/ToastComponent.swift} (73%) create mode 100644 Sources/App/Views/Shared/ScriptAfterLoad.swift diff --git a/Public/js/site.js b/Public/js/site.js index 74edf0e..9f71be8 100644 --- a/Public/js/site.js +++ b/Public/js/site.js @@ -1,11 +1,17 @@ +window.web = {}; + +// ----------------------------------------- + // Activate Bootstrap tooltips -const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') -const tooltipList = [...tooltipTriggerList].map(element => new bootstrap.Tooltip(element)) +web.tooltips = function() { + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') + const tooltipList = [...tooltipTriggerList].map(element => new bootstrap.Tooltip(element)) +} // ----------------------------------------- // HTMX events -function runOnceAfterSettle(func) { +web.afterLoad = function(func) { // https://htmx.org/docs/#request-operations document.addEventListener("htmx:afterSettle", function handler(event) { func(); diff --git a/Sources/App/Controllers/TestController.swift b/Sources/App/Controllers/TestController.swift new file mode 100644 index 0000000..5aaa4ed --- /dev/null +++ b/Sources/App/Controllers/TestController.swift @@ -0,0 +1,30 @@ +import Elementary +import ElementaryHTMX +import ElementaryHTMXSSE +import ElementaryHTMXWS +import Fluent +import Vapor +import VaporElementary + +struct TestController: RouteCollection { + + func boot(routes: RoutesBuilder) throws { + routes.group("test") { test in + test.get("toast", use: toast) + } + } + + @Sendable + func toast(req: Request) async throws -> HTMLResponse { + + let state = try getState(request: req) + state.toast = ToastState(message: "Wow!", + title: "This is my title", + level: ToastState.Level.success) + + throw Abort(.badRequest, headers: ["HX-Trigger": "toast"]) + + // return HTMLResponse { } + } + +} diff --git a/Sources/App/Controllers/TodoController.swift b/Sources/App/Controllers/TodoController.swift index 9f1370a..dd468e6 100644 --- a/Sources/App/Controllers/TodoController.swift +++ b/Sources/App/Controllers/TodoController.swift @@ -27,6 +27,8 @@ struct TodoController: RouteCollection { MainLayout(title: "Todos") { TodosTableComponent(name: "todos", todos: todos) TodosFormComponent(name: "todos-form", target: "todos") + + button(.class("btn btn-primary"), .type(.button), .hx.get("/test/toast")) { "Toast" } } } } diff --git a/Sources/App/Views/Shared/ToastView.swift b/Sources/App/Views/Components/ToastComponent.swift similarity index 73% rename from Sources/App/Views/Shared/ToastView.swift rename to Sources/App/Views/Components/ToastComponent.swift index ef0f0c1..a352212 100644 --- a/Sources/App/Views/Shared/ToastView.swift +++ b/Sources/App/Views/Components/ToastComponent.swift @@ -9,7 +9,7 @@ import ElementaryHTMX // // The header "HX-Trigger" will make the part refresh and show the toast message -struct ToastView: HTML { +struct ToastComponent: HTML { var state: ToastState = ToastState() @@ -33,16 +33,14 @@ struct ToastView: HTML { div(.class("toast-body")) { state.message } } if !state.message.isEmpty { - script { + ScriptAfterLoad(initial: false) { """ - runOnceAfterSettle(function () { - const element = document.getElementById("toast"); - const toast = new bootstrap.Toast(element, { autohide: true, delay: 5000 }); - toast.show(); + const element = document.getElementById("toast"); + const toast = new bootstrap.Toast(element, { autohide: true, delay: 5000 }); + toast.show(); - element.addEventListener("hidden.bs.toast", function () { - element.remove(); - }); + element.addEventListener("hidden.bs.toast", function () { + element.remove(); }); """ } diff --git a/Sources/App/Views/Components/TodosTableComponent.swift b/Sources/App/Views/Components/TodosTableComponent.swift index 4950b0d..1d0ef6b 100644 --- a/Sources/App/Views/Components/TodosTableComponent.swift +++ b/Sources/App/Views/Components/TodosTableComponent.swift @@ -47,6 +47,8 @@ struct TodosTableComponent: HTML { } } } + + ScriptAfterLoad(initial: !refresh) { "web.tooltips();" }; } } diff --git a/Sources/App/Views/Shared/MainLayout.swift b/Sources/App/Views/Shared/MainLayout.swift index ce316ee..d2f0681 100644 --- a/Sources/App/Views/Shared/MainLayout.swift +++ b/Sources/App/Views/Shared/MainLayout.swift @@ -59,7 +59,7 @@ struct MainLayout: HTMLDocument { } // Placeholder for all toast messages - ToastView() + ToastComponent() } // --------------------------------- diff --git a/Sources/App/Views/Shared/ScriptAfterLoad.swift b/Sources/App/Views/Shared/ScriptAfterLoad.swift new file mode 100644 index 0000000..b1693c8 --- /dev/null +++ b/Sources/App/Views/Shared/ScriptAfterLoad.swift @@ -0,0 +1,35 @@ +import Elementary + +struct ScriptAfterLoad: HTML { + + var initial: Bool = false + var js: String = "" + + init(initial: Bool = false, js: () -> String) { + self.initial = initial + self.js = js() + } + + // ------------------------------------- + + var content: some HTML { + if initial { + script { + """ + document.addEventListener("DOMContentLoaded", function() { + \(js) + }); + """ + } + } else { + script { + """ + web.afterLoad(function () { + \(js) + }); + """ + } + } + } + +} diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index fe7cbf0..657cdb5 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -21,6 +21,7 @@ func routes(_ app: Application) throws { app.get("toast", use: toast) try app.register(collection: TodoController()) + try app.register(collection: TestController()) try app.group("api") { api in try api.register(collection: TodoAPIController()) @@ -32,7 +33,7 @@ func toast(req: Request) throws -> HTMLResponse { let state = try getState(request: req) return HTMLResponse { - ToastView(state: state.toast) + ToastComponent(state: state.toast) } }