Simplify htmx how-to functions using Pico CSS and Ionicons

This commit is contained in:
Laurence Isla
2024-01-26 20:28:58 -05:00
committed by GitHub
parent 76b1c00935
commit 439db880dd
5 changed files with 112 additions and 107 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

+112 -107
View File
@@ -38,7 +38,8 @@ and return a raw HTML document file.
Creating an HTML Response Creating an HTML Response
------------------------- -------------------------
Let's create a function that returns a basic HTML file, using `Tailwind CSS <https://v2.tailwindcss.com/>`_ for styling. Let's create a function that returns a basic HTML file, using `Pico CSS <https://v2.picocss.com/>`_ for styling and
`Ionicons <https://ionic.io/ionicons>`_ to show some icons later.
.. code-block:: postgres .. code-block:: postgres
@@ -50,15 +51,20 @@ Let's create a function that returns a basic HTML file, using `Tailwind CSS <htt
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title> <title>PostgREST + HTMX To-Do List</title>
<!-- Tailwind for CSS styling --> <!-- Pico CSS for CSS styling -->
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet" />
</head> </head>
<body class="bg-gray-900"> <body>
<div class="flex justify-center"> <main class="container">
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl"> <article>
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">PostgREST + HTMX To-Do List</h5> <h5 style="text-align: center;">
</div> PostgREST + HTMX To-Do List
</div> </h5>
</article>
</main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body> </body>
</html> </html>
$html$; $html$;
@@ -84,24 +90,22 @@ For that, we'll also need a function to help us sanitize the HTML content that m
create or replace function api.html_todo(api.todos) returns text as $$ create or replace function api.html_todo(api.todos) returns text as $$
select format($html$ select format($html$
<li class="py-3"> <div>
<span class="ml-2 %2$s"> <%2$s>
%3$s %3$s
</span> </%2$s>
</li> </div>
$html$, $html$,
$1.id, $1.id,
case when $1.done then 'line-through text-gray-400' else '' end, case when $1.done then 's' else 'span' end,
api.sanitize_html($1.task) api.sanitize_html($1.task)
); );
$$ language sql stable; $$ language sql stable;
create or replace function api.html_all_todos() returns text as $$ create or replace function api.html_all_todos() returns text as $$
select coalesce( select coalesce(
'<ul id="todo-list" role="list" class="divide-y divide-gray-700 text-gray-100">' string_agg(api.html_todo(t), '<hr/>' order by t.id),
|| string_agg(api.html_todo(t), '' order by t.id) || '<p><em>There is nothing else to do.</em></p>'
'</ul>',
'<p class="text-gray-100">There is nothing else to do.</p>'
) )
from api.todos t; from api.todos t;
$$ language sql; $$ language sql;
@@ -126,39 +130,44 @@ Next, let's add an endpoint to register a to-do in the database and modify the `
$$ language sql; $$ language sql;
create or replace function api.index() returns "text/html" as $$ create or replace function api.index() returns "text/html" as $$
select $html$ select $html$
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title> <title>PostgREST + HTMX To-Do List</title>
<!-- Tailwind for CSS styling --> <!-- Pico CSS for CSS styling -->
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet"/>
<!-- htmx for AJAX requests --> <!-- htmx for AJAX requests -->
<script src="https://unpkg.com/htmx.org"></script> <script src="https://unpkg.com/htmx.org"></script>
</head> </head>
<body class="bg-gray-900" <body>
hx-headers='{"Accept": "text/html"}'> <main class="container"
<div class="flex justify-center"> style="max-width: 600px"
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl"> hx-headers='{"Accept": "text/html"}'>
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">PostgREST + HTMX To-Do List</h5> <article>
<form hx-post="/rpc/add_todo" <h5 style="text-align: center;">
hx-target="#todo-list-area" PostgREST + HTMX To-Do List
hx-trigger="submit" </h5>
hx-on="htmx:afterRequest: this.reset()"> <form hx-post="/rpc/add_todo"
<input class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 mb-3 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" hx-target="#todo-list-area"
type="text" name="_task" placeholder="Add a todo..."> hx-trigger="submit"
</form> hx-on="htmx:afterRequest: this.reset()">
<div id="todo-list-area"> <input type="text" name="_task" placeholder="Add a todo...">
$html$ </form>
|| api.html_all_todos() || <div id="todo-list-area">
$html$ $html$
<div> || api.html_all_todos() ||
</div> $html$
</div> <div>
</body> </article>
</html> </main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body>
</html>
$html$; $html$;
$$ language sql; $$ language sql;
@@ -190,49 +199,46 @@ Now, let's modify ``api.html_todo`` and make it more functional.
.. code-block:: postgres .. code-block:: postgres
create or replace function api.html_todo(api.todos) returns text as $$ create or replace function api.html_todo(api.todos) returns text as $$
select format($html$ select format($html$
<li class="py-3"> <div class="grid">
<div class="flex justify-between items-center"> <div id="todo-edit-area-%1$s">
<div id="todo-edit-area-%1$s" class="pr-5"> <form id="edit-task-state-%1$s"
<form id="edit-task-state-%1$s" hx-post="/rpc/change_todo_state"
hx-post="/rpc/change_todo_state" hx-vals='{"_id": %1$s, "_done": %4$s}'
hx-vals='{"_id": %1$s, "_done": %4$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<span class="ml-2 %2$s cursor-pointer">
%3$s
</span>
</form>
</div>
<div>
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
hx-get="/rpc/html_editable_task"
hx-vals='{"_id": "%1$s"}'
hx-target="#todo-edit-area-%1$s"
hx-trigger="click">
<svg class="w-4 h-4 text-blue-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
<path d="M12.687 14.408a3.01 3.01 0 0 1-1.533.821l-3.566.713a3 3 0 0 1-3.53-3.53l.713-3.566a3.01 3.01 0 0 1 .821-1.533L10.905 2H2.167A2.169 2.169 0 0 0 0 4.167v11.666A2.169 2.169 0 0 0 2.167 18h11.666A2.169 2.169 0 0 0 16 15.833V11.1l-3.313 3.308Zm5.53-9.065.546-.546a2.518 2.518 0 0 0 0-3.56 2.576 2.576 0 0 0-3.559 0l-.547.547 3.56 3.56Z"/>
<path d="M13.243 3.2 7.359 9.081a.5.5 0 0 0-.136.256L6.51 12.9a.5.5 0 0 0 .59.59l3.566-.713a.5.5 0 0 0 .255-.136L16.8 6.757 13.243 3.2Z"/>
</svg>
</button>
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
hx-post="/rpc/delete_todo"
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area" hx-target="#todo-list-area"
hx-trigger="click"> hx-trigger="click">
<svg class="w-4 h-4 text-red-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20"> <%2$s style="cursor: pointer">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"/> %3$s
</svg> </%2$s>
</button> </form>
</div>
<div style="text-align: right">
<button class="outline"
hx-get="/rpc/html_editable_task"
hx-vals='{"_id": "%1$s"}'
hx-target="#todo-edit-area-%1$s"
hx-trigger="click">
<span>
<ion-icon name="create"></ion-icon>
</span>
</button>
<button class="outline contrast"
hx-post="/rpc/delete_todo"
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<span>
<ion-icon name="trash" style="color: #f87171"></ion-icon>
</span>
</button>
</div>
</div> </div>
</div> $html$,
</li> $1.id,
$html$, case when $1.done then 's' else 'span' end,
$1.id, api.sanitize_html($1.task),
case when $1.done then 'line-through text-gray-400' else '' end, (not $1.done)::text
api.sanitize_html($1.task), );
(not $1.done)::text
);
$$ language sql stable; $$ language sql stable;
Let's deconstruct the new htmx features added: Let's deconstruct the new htmx features added:
@@ -267,22 +273,21 @@ That's why we create the ``api.html_editable_task`` function as an endpoint:
.. code-block:: postgres .. code-block:: postgres
create or replace function api.html_editable_task(_id int) returns "text/html" as $$ create or replace function api.html_editable_task(_id int) returns "text/html" as $$
select format ($html$ select format ($html$
<form id="edit-task-%1$s" <form id="edit-task-%1$s"
hx-post="/rpc/change_todo_task" hx-post="/rpc/change_todo_task"
hx-headers='{"Accept": "text/html"}' hx-headers='{"Accept": "text/html"}'
hx-vals='{"_id": %1$s}' hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area" hx-target="#todo-list-area"
hx-trigger="submit,focusout"> hx-trigger="submit,focusout">
<input class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500" <input id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
id="task-%1$s" type="text" name="_task" value="%2$s" autofocus> </form>
</form> $html$,
$html$, id,
id, api.sanitize_html(task)
api.sanitize_html(task) )
) from api.todos
from api.todos where id = _id;
where id = _id;
$$ language sql; $$ language sql;
In this example, this will return an input field that allows us to edit the corresponding to-do task. In this example, this will return an input field that allows us to edit the corresponding to-do task.