From d643aab261136a12962994699f64eec410be4841 Mon Sep 17 00:00:00 2001 From: Laurence Isla Date: Wed, 12 Jul 2023 09:46:41 -0500 Subject: [PATCH] Add html-htmx how-to --- .../providing-html-content-using-htmx.rst | 353 ++++++++++++++++++ postgrest.dict | 3 + 2 files changed, 356 insertions(+) create mode 100644 docs/how-tos/providing-html-content-using-htmx.rst diff --git a/docs/how-tos/providing-html-content-using-htmx.rst b/docs/how-tos/providing-html-content-using-htmx.rst new file mode 100644 index 000000000..0909fe5cf --- /dev/null +++ b/docs/how-tos/providing-html-content-using-htmx.rst @@ -0,0 +1,353 @@ +.. note:: + + This page is a work in progress. + +.. _providing_html_htmx: + +Providing HTML Content Using htmx +================================= + +:author: `Laurence Isla `_ + +This how-to shows a way to return HTML content and use the `htmx library `_ to handle the AJAX requests. +Htmx expects an HTML response and uses it to replace an element inside the DOM (see the `htmx introduction `_ in the docs). + +Preparatory Configuration +------------------------- + +We will make a to-do app based on the :ref:`tut0`, so make sure to complete it before continuing. + +To simplify things, we won't be using authentication, so grant all permissions on the ``todos`` table to the ``web_anon`` user. + +.. code-block:: postgres + + grant all on api.todos to web_anon; + +Next, add the ``text/html`` media type to the :ref:`raw-media-types` configuration. +With this, PostgREST can identify the request made by your web browser (with the ``Accept: text/html`` header) and return a raw HTML document file. + +.. code-block:: ini + + # tutorial.conf + raw-media-types = "text/html" + +Creating an HTML Response +------------------------- + +Let's create a function that returns a basic HTML file, using `Tailwind CSS `_ for styling. + +.. code-block:: postgres + + create or replace function api.index() returns text + language plpgsql + as $$ + begin + perform set_config('response.headers','[{"Content-Type": "text/html; charset=utf-8"}]', true); + return $html$ + + + + + + PostgREST To-Do list + + + + +
+
+
PostgREST To-Do List
+
+
+ + + $html$; + end $$; + +The web browser will open the web page at ``http://localhost:3000/rpc/index``. + +.. _html_htmx_list_create: + +Listing and Creating To-Dos +--------------------------- + +Now, let's show a list of the to-dos already inserted in the database. + +.. code-block:: postgres + + create or replace function api.html_todo(api.todos) returns text + language sql stable + as $$ + select format($html$ +
  • + + %3$s + +
  • + $html$, + $1.id, + case when $1.done then 'line-through text-gray-400' else '' end, + $1.task + ); + $$; + + create or replace function api.html_all_todos() returns text + language sql + as $$ + select coalesce( + '
      ' + || string_agg(api.html_todo(t), '' order by t.id) || + '
    ', + '

    There is nothing else to do.

    ' + ) + from api.todos t; + $$; + +These two functions are used to build the to-do list template. We won't call them outside of PostgreSQL. + +- The ``html_todo`` function uses the table ``api.todos`` as a parameter and is used to format each element into a list element ``
  • ``. + The PostgreSQL `format `_ function is useful to that end. + It replaces the values according to the position in the template, e.g. the ``%1$s`` in the template will be replaced with the value of ``$1.id`` (the first parameter). + +- The ``html_all_todos`` function is the ``
      `` wrapper for all the list elements and uses `string_arg `_ to concatenate all the to-dos in a single text value. + It also returns an alternative message, instead of a list, when the ``api.todos`` table is empty. + +Next, let's add an endpoint to register a to-do in the database and modify the ``/rpc/index`` page accordingly. + +.. code-block:: postgres + + create or replace function api.add_todo(_task text) returns text + language plpgsql + as $$ + begin + perform set_config('response.headers','[{"Content-Type": "text/html; charset=utf-8"}]', true); + insert into api.todos(task) values (_task); + return api.html_all_todos(); + end; + $$; + + create or replace function api.index() returns text + language plpgsql + as $$ + begin + perform set_config('response.headers','[{"Content-Type": "text/html; charset=utf-8"}]', true); + return $html$ + + + + + + PostgREST To-Do list + + + + + + +
      +
      +
      PostgREST To-Do List
      +
      + +
      +
      + $html$ + || api.html_all_todos() || + $html$ +
      +
      +
      + + + $html$; + end $$; + +- The ``/rpc/add_todo`` endpoint allows us to add a new to-do using the ``_task`` parameter and returns an ``html`` with all the to-dos in the database. + +- The ``/rpc/index`` now adds the ``hx-headers='{"Accept": "text/html"}'`` tag to the ````. + This will make sure that all htmx elements inside the body send this header, otherwise PostgREST won't recognize it as HTML. + + There is also a ``
      `` element that uses the htmx library. Let's break it down: + + + ``hx-post="/rpc/add_todo"``: sends an AJAX POST request to the ``/rpc/add_todo`` endpoint, with the value of the ``_task`` from the ```` element. + + + ``hx-target="#todo-list-area"``: the HTML content returned from the request will go inside ``
      `` (which is the list of to-dos). + + + ``hx-trigger="submit"``: htmx will do this request when submitting the form (by pressing enter inside the ````). + + + ``hx-on="htmx:afterRequest: this.reset()">``: this is a Javascript command that clears the form `after the request is done `_. + +With this, the ``http://localhost:3000/rpc/index`` page lists all the todos and adds new ones by submitting tasks in the input element. + +Editing and Deleting To-Dos +--------------------------- + +Now, let's modify the ``html_todo`` function and make it more functional. + +.. code-block:: postgres + + create or replace function api.html_todo_template(api.todos) returns text + language sql stable + as $$ + select format($html$ +
    • +
      +
      + + + %3$s + + +
      +
      + + +
      +
      +
    • + $html$, + $1.id, + case when $1.done then 'line-through text-gray-400' else '' end, + $1.task, + (not $1.done)::text + ); + $$; + +Let's deconstruct the new features: + +- The ``
      `` element is configured as follows: + + + ``hx-post="/rpc/change_todo_state"``: does an AJAX POST request to that endpoint. It will toggle the ``done`` state of the to-do. + + + ``hx-vals='{"_id": %1$s, "_done": %4$s}'``: adds the parameters to the request. + This is an alternative to using hidden inputs inside the ````. + + + ``hx-target="#todo-list-area"``: the returned HTML will replace the element with this id. + + + ``hx-trigger="click"``: htmx does the request after clicking on the element. + +- The ``