Showing a loading indicator based on button click

I have some tasks that launch on a button click. The tasks take between 1-10 seconds on the server to be done. As soon as the task is launched, I reply with a waiting status and later send a success or failure when the task is done.

A couple of things need to happen when I click the button:

  • Disable the button
  • I’d like to change the text of the button to evaluating....
  • Change the loading indicator (both text and color)

This is some of the code I have so far:

    <div class="relative">
      <button
        type="button"
        class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-75"
        disabled={@status == "waiting"}
        phx-disable-with
        phx-click={
          JS.push("evaluate")
          |> JS.remove_class(evaluation_style_span(@status), to: "##{@id}-span")
          |> JS.add_class(evaluation_style_span("waiting"), to: "##{@id}-span")
          |> JS.remove_class(evaluation_style_span(@status), to: "##{@id}-svg")
          |> JS.add_class(evaluation_style_span("waiting"), to: "##{@id}-svg")
        }
        phx-target={@myself}
      >
        <Heroicons.play_circle solid class="-ml-1 mr-2 h-5 w-5" /> Evaluate
      </button>


      <div id="evaluation-status" class="absolute top-0 right-0">
        <span
          id={"#{@id}-span"}
          class={[
            "inline-flex items-center rounded-md px-2.5 py-0.5 text-sm font-medium",
            evaluation_style_span(@status)
          ]}
        >
          <%= case @status do %>
            <% "initial" -> %>
              not evaluated
            <% "waiting" -> %>
              in progress
            <% "success" -> %>
              last execution was succesful
            <% "failure" -> %>
              last execution failed
          <% end %>
          <svg
            id={"#{@id}-svg"}
            class={["ml-1.5 -mr-0.5 h-4 w-4", evaluation_style_svg(@status)]}
            fill="currentColor"
            viewBox="0 0 8 8"
          >
            <circle cx="4" cy="4" r="4" />
          </svg>
        </span>
      </div>
    </div>

...
  defp evaluation_style_span("initial"), do: "text-indigo-800"
  defp evaluation_style_span("waiting"), do: "text-orange-800"
  defp evaluation_style_span("success"), do: "text-green-800"
  defp evaluation_style_span("failure"), do: "text-red-800"
  defp evaluation_style_svg("initial"), do: "text-indigo-400"
  defp evaluation_style_svg("waiting"), do: "text-orange-400"
  defp evaluation_style_svg("success"), do: "text-green-400"
  defp evaluation_style_svg("failure"), do: "text-red-400"

Some questions:

  1. I cannot change the text of button to Evaluating... with phx-disable-with because then I loose the Heroicon. Can I solve this in an other way?
  2. The JS.add_class are persisted across renders (by design) but as soon as the server answers with the waiting status and later success the classes will 2 text colors.
  3. I have no idea how I can change the text inside the evaluation-status div when the button is clicked.

So far these are the possible options for a solution I can think of:

  1. Don’t bother and only show update the loading when the server first can respond.
  2. Find a solution with Phoenix.JS somehow (don’t know how)
  3. Do all of this directly in javascript

I don’t like solution 1 really, I don’t know how to do 2, and if possible I’d like to avoid 3.

Does anyone have suggestions on how to solve this in a nice way?

1 Like

Do you need to do all of that with the JS module? I would just render the different states of the UI on the server.

That’s what I mean with solution 1. The main downside for me is that there is some lag between clicking the button and showing the status indicator.
If possible I’d like to improve on that.

I’m trying to work something out with alpinejs + JS.set_attribute({"x-data", "{loading: false}"}, to: "#loading-indicator"}

The strange thing is that alpinejs doesn’t always detect the changes to x-data. And it seems to depend on where I put the alpine component in the DOM.
More specifically, it only works when the component is placed in the same LiveComponent.

I’m confused. I don’t know why this should influence that? Does anyone have any idea?

Anyway, even if this would work, this results in the same issue…

Js.set_attribute is persisted after a rerender.

I feel I need to have something that would allow me to do something when the click is done. So when the server responded.

You could change the ID, which should make LV consider this a “new” button.