.. 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 ``