Я не люблю писать Javascript. К сожалению, это необходимо, если вы хотите создавать интерактивные реактивные веб-сайты, или нет? Что, если есть способ реализовать многие преимущества Javascript на вашем веб-сайте без написания кода Javascript?

htmx — это небольшая библиотека, предоставляющая ряд функций Javascript — AJAX, WebSockets, проверка формы, анимация и другие — для записи в виде гипертекста в HTML. Никаких тегов сценария, никакого импорта библиотек (кроме сценария htmx), никакого управления пакетами. Только все наиболее распространенные интерактивные функции Javascript, которые используются в большинстве веб-приложений.

Вот как реализовать анимированный индикатор выполнения с помощью функции опроса загрузки htmx в приложении Rails. Пример довольно упрощенный — при нажатии кнопки Пуск индикатор выполнения будет постепенно заполняться каждую секунду в общей сложности на десять секунд. По прошествии каждой секунды полоса заполняется на десять процентов. По завершении появится кнопка, позволяющая начать весь процесс заново.

Добавьте htmx в Rails

Встроить библиотеку htmx очень просто — просто включите скрипт перед закрывающим тегом body в app/views/layouts/application.html.erb:

...
<body>
  <div class="container">
    <%= yield %>
  </div>
  <!-- htmx -->
  <script src="https://unpkg.com/[email protected]"></script>
  <!-- end htmlx -->
</body>
...

Добавьте контроллер

Я создал один контроллер с именем ProgressBarController для выполнения трех необходимых действий — #index, #start и #job:

  • Действие index отображает страницу со всеми элементами управления пользовательского интерфейса;
  • Действие запуска инициализируется и представляет индикатор выполнения и скрывает кнопку запуска;
  • Наконец, действие задания будет обновлять индикатор выполнения в течение десяти итераций, а затем завершится.
class ProgressBarController < ApplicationController
  # DEVELOPMENT PURPOSES ONLY
  skip_before_action :verify_authenticity_token
  def index; end
  def start
    @percent = 0
    render partial: 'progress'
  end
  def job
    @percent = params[:percent].to_i
    @percent += 10
    render partial: 'finished' and return if @percent >= 100
    render partial: 'progress'
  end
end

В действии start переменная @percent инициализируется значением 0, которое затем становится доступным для партиала _progress.

Действие job загружает процентное значение из параметров запроса URL в переменную `@percent`, а затем увеличивает его на 10. Если @percent достигает 100, индикатор выполнения заполняется, и задание завершается, поэтому отрисовывайте частичный _finished. Если индикатор выполнения не заполнен/завершен, повторите рендеринг партиала _progress.

Настроить маршрутизацию

Маршрутизация проста; нужно только три. /progress_bar перенаправит к основному действию HTML индекса. /progress_bar/start и /progress_bar/job перенаправят к соответствующим действиям в контроллере.

Обратите внимание, что /progress_bar/start использует глагол POST, потому что он запускается кнопкой.

Rails.application.routes.draw do
  # htmx Progress Bar
  get '/progress_bar', to: 'progress_bar#index'
  post '/progress_bar/start', to: 'progress_bar#start'
  get '/progress_bar/job', to: 'progress_bar#job'
end

Создайте представления

В этом приложении есть три вида; один основной вид и два частичных. Главный вид, app/views/progress_bar/index.html.erb, содержит все контроллеры и встроен в компоновку.

<h1>htmx Progress Bar</h1>
<div class="mt-5" hx-target="this" hx-swap="outerHTML">
  <p>Click the Start Job button to start the progress bar. It will run for 10 cycles, incrementing by 10 % each cycle.</p>
  
  <button type="button" class="btn btn-primary" hx-post="/progress_bar/start">
    Start Job
  </button>
</div>

Обратите внимание на два атрибута htmx во внешнем <div>, окружающем заголовок и кнопку. Директивы hx-target="this" hx-swap="outerHTML" означают, что любое манипулирование DOM, вызванное действием htmx, полностью перезапишет этот тег <div> (а не только HTML внутри тега — это будет innerHTML).

Кнопка также содержит директиву htmx; hx-post. Когда кнопка нажата, htmx вызовет URL-адрес /progress_bar/start, используя AJAX.

Волшебство происходит, когда эти теги объединяются. директивы htmx наследуются. В большинстве случаев вложенные атрибуты htmx получат директивы, объявленные их родителями. В приведенном выше примере кнопке не нужно объявлять атрибуты target или swap. Он унаследует hx-target и hx-swap от своего родительского тега <div>.

Комбинированные объявления htmx в этом примере означают, что при нажатии кнопки htmx вызовет /progress_bar/start и поменяет/заменит содержимое в <div> выводом вызова.

Партиал _progress отображает индикатор выполнения и содержит больше атрибутов htmx:

<div class="mt-5" hx-target="this"
    hx-get="/progress_bar/job?percent=<%= @percent %>"
    hx-trigger="load delay:600ms"
    hx-swap="outerHTML">
  <p>Running...</p>
  <div class="progress">
    <div id="pb" class="progress-bar" style="width:<%= @percent %>%">
  </div>
</div>

Атрибуты hx-target и hx-swap описаны выше. hx-get указывает htmx выполнить запрос GET на конечной точке /progress_bar/job, предоставляя процент в качестве параметра запроса.

Когда будет выполнен этот запрос GET? Это определяется hx-trigger. В этом случае он будет срабатывать каждые 600 миллисекунд.

Когда индикатор выполнения заполнен, действие ProgressBar#job отобразит частичный _finished, а не _progress. Он отобразит полный индикатор выполнения вместе с кнопкой, чтобы снова перезапустить индикатор выполнения.

<div class="mt-5" hx-target="this" hx-swap="outerHTML">
  <p>Complete! Press <strong>Restart Job</strong> to start the progress bar again.</p>
  <div class="progress">
    <div id="pb" class="progress-bar" style="width:100%">
  </div>
</div>
<button id="restart-btn" class="btn btn-primary mt-3" hx-post="/progress_bar/start" classes="add show:600ms">
  Restart Job
</button>

Как и на главной странице, при нажатии кнопки на этой странице htmx запускает POST очень на /progress_bar/start и заменяет верхний уровень <div> ответом.

Результат

Наш анимированный индикатор выполнения готов. Запустите сервер Rails, перейдите на страницу индекса и нажмите кнопку запуска, чтобы начать процесс.

Самое приятное то, что я не писал никакого Javascript для реализации индикатора выполнения — все было сделано путем простого добавления нескольких атрибутов к существующим тегам HTML.

Индикатор выполнения, показанный в этой статье, довольно упрощен и сам по себе не предоставляет никакой полезной функции. В следующей статье я покажу, как интегрировать индикатор выполнения с фоновым рабочим процессом, реализованным в Sidekiq, где панель предоставляет оперативные обновления на основе прогресса рабочего процесса.

Весь код доступен в моем проекте RailsScratch на Github. Пожалуйста, обратитесь туда за исходным кодом для этой и других демонстраций Rails и htmx. Обратите внимание, что по мере роста и развития репозитория некоторые примеры в этой статье могут отличаться от версий, перечисленных в этой статье.

Если вы заметили какие-либо ошибки или у вас есть предложения, как сделать содержание более понятным и точным, свяжитесь со мной. Я приветствую все отзывы.