.. _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). .. image:: ../_static/how-tos/htmx-demo.gif .. warning:: This is a proof of concept showing what can be achieved using both technologies. We are working on `plmustache `_ which will further improve the HTML aspect of this how-to. 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; grant usage, select on sequence api.todos_id_seq to web_anon; Next, add the ``text/html`` as a :ref:`custom_media`. 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:: postgres create domain "text/html" as text; Creating an HTML Response ------------------------- Let's create a function that returns a basic HTML file, using `Pico CSS `_ for styling and `Ionicons `_ to show some icons later. .. code-block:: postgres create or replace function api.index() returns "text/html" as $$ select $html$ PostgREST + HTMX To-Do List
PostgREST + HTMX To-Do List
$html$; $$ language sql; The web browser will open the web page at ``http://localhost:3000/rpc/index``. .. image:: ../_static/how-tos/htmx-simple.jpg .. _html_htmx_list_create: Listing and Creating To-Dos --------------------------- Now, let's show a list of the to-dos already inserted in the database. For that, we'll also need a function to help us sanitize the HTML content that may be present in the task. .. code-block:: postgres create or replace function api.sanitize_html(text) returns text as $$ select replace(replace(replace(replace(replace($1, '&', '&'), '"', '"'),'>', '>'),'<', '<'), '''', ''') $$ language sql; create or replace function api.html_todo(api.todos) returns text as $$ select format($html$
<%2$s> %3$s
$html$, $1.id, case when $1.done then 's' else 'span' end, api.sanitize_html($1.task) ); $$ language sql stable; create or replace function api.html_all_todos() returns text as $$ select coalesce( string_agg(api.html_todo(t), '
' order by t.id), '

There is nothing else to do.

' ) from api.todos t; $$ language sql; These two functions are used to build the to-do list template. We won't use them as PostgREST endpoints. - The ``api.html_todo`` function uses the table ``api.todos`` as a parameter and formats each item into a list element ``
  • ``. The PostgreSQL `format `_ is useful to that end. It replaces the values according to the position in the template, e.g. ``%1$s`` will be replaced with the value of ``$1.id`` (the first parameter). - The ``api.html_all_todos`` function returns the ``
      `` wrapper for all the list elements. It 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/html" as $$ insert into api.todos(task) values (_task); select api.html_all_todos(); $$ language sql; create or replace function api.index() returns "text/html" as $$ select $html$ PostgREST + HTMX To-Do List
      PostgREST + HTMX To-Do List
      $html$ || api.html_all_todos() || $html$
      $html$; $$ language sql; - 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 while 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. Don't forget to refresh the :ref:`schema cache `. .. image:: ../_static/how-tos/htmx-insert.gif Editing and Deleting To-Dos --------------------------- Now, let's modify ``api.html_todo`` and make it more functional. .. code-block:: postgres create or replace function api.html_todo(api.todos) returns text as $$ select format($html$
      <%2$s style="cursor: pointer"> %3$s
      $html$, $1.id, case when $1.done then 's' else 'span' end, api.sanitize_html($1.task), (not $1.done)::text ); $$ language sql stable; Let's deconstruct the new htmx features added: - 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-trigger="click"``: htmx does the request after clicking on the element. - For the first ``