Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

I have a button that may be disabled or not, depending on a set of conditions. I want to disable/enable the button without having to reload the page.

To achieve this I am using the following code:

my_app_live.ex

#provided as a sample
def disable_buttton? do
  if :rand.uniform(100) > 50 do
    "true"
  else
    "false"
  end
end

my_app.html.heex

<.button disabled={disable_button?)}>Execute Command</.button>

core_components.ex

  def button(assigns) do
    extra = assigns_to_attributes(assigns, [:disabled])

    assigns = assign(assigns, :disabled?, case Map.get(extra[:rest], :disabled) do
      "true" -> true
      _ -> false
    end)

    ~H"""
    <p><%= "INSIDE BUTTON: #{inspect(@disabled?)}" %></p>
    <button
      type={@type}
      class={if @disabled? do
      [
        "rounded-md bg-slate-400 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        @class
      ]
    else
      [
        "rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        "active:text-white/80",
        @class
      ]
    end}
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

Problem

For some reason, <p><%= "INSIDE BUTTON: #{inspect(@disabled?)}" %></p> always shows true and thus the button is always disabled.

In reality disable_buttton? may return true/false depending on which buttons are selected (if all forms have a value, then the button should be enabled), however I am unaware of any pattern to do this in Phoenix.

I am also not convinced I am using assigns properly.

Questions

  1. How can I enable/disable a button in Phoenix, depending on state?
  2. Am I using assigns_to_attributes and assign correctly in this sample?

I read Phoenix.Component — Phoenix LiveView v1.2.5 but I still don’t quite understand what I am missing here.

Showing Posts 6 to 1

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

So, for those who are curious, this is the code I ended up with:

core_components.ex

This is my modified button function:

  @doc """
  Renders a button.

  ## Examples

      <.button>Send!</.button>
      <.button phx-click="go" class="ml-2" disabled=false>Send!</.button>
  """
  attr :type, :string, default: nil
  attr :class, :string, default: nil
  attr :disabled, :boolean, default: false
  attr :rest, :global, include: ~w(form name value)

  slot :inner_block, required: true

  def button(assigns) do
    ~H"""
    <button
      type={@type}
      class={if @disabled do
      [
        "rounded-md bg-slate-400 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        @class
      ]
    else
      [
        "phx-submit-loading:opacity-75 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        "hover:bg-indigo-500 active:text-white/80",
        @class
      ]
    end}
      {@rest}
      disabled={@disabled}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

And here is how I use it:

my_app_live.html.heex

<.button class="min-w-full" disabled={disable_button?(@param1, @state)}>Execute Command</.button>

This depends on the state, because the state changes with time, which means my button’s appearance will also change with time!

thanks everyone for the help!

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

So I understand you advocate I should change to:

  attr :type, :string, default: nil
  attr :class, :string, default: nil
  attr :disabled, boolean, default: false
  attr :rest, :global, include: ~w(form name value)

  slot :inner_block, required: true

correct?

So, in Phoenix LV, if the value is false the attribute disabled is removed from the node, while if it is any other thing, the node gets the attribute as disabled="something else", which means that because disabled is still present, irrespective of value, it is considered as a disabled button.

I didn’t know about this. So this is yet another thing I learned :smiley:

LostKobrakai

LostKobrakai

@rest is mean as a “catch all” for all the attributes you’re not explicitly interested in / you don’t use besides passing them onwards.

Any other attributes should have an explicit attr definition.

You’re not passing false, you’re passing the string "false", which is a truthy value in elixir.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I am confused. After reading Global Attributes (Phoenix.Component — Phoenix LiveView v1.2.5) I thought I could simply do @disabled because the button function is defined with the following attributes:

  attr :type, :string, default: nil
  attr :class, :string, default: nil
  attr :rest, :global, include: ~w(disabled form name value)

  slot :inner_block, required: true

However, when using @disabled the code crashes. So I am guessing I am doing something wrong. What have I missed?

If I pass the value manually, i.e., <.button disabled={"false"}>Execute Command</.button>, then the button has the correct colour and appearance, but when I click it nothing happens, which means it is still disabled, even though the value is false.

This is rather confusing to me.

Very well, I have added a timer that changes random_number every second and I have assigned it with assigns |> assign(assigns, :random_numer, val).

Let us assume for this sample, that @random_number will be between 0 - 100 and it changes periodically.
As a consequence, the code is now:

my_app.html.heex

<.button disabled={"#{@random_number > 50}"}>Execute Command</.button>

Which, in conjunction with the core_components.ex:

  attr :type, :string, default: nil
  attr :class, :string, default: nil
  attr :rest, :global, include: ~w(disabled form name value)

  slot :inner_block, required: true

  def button(assigns) do
    extra = assigns_to_attributes(assigns, [:disabled])

    assigns = assign(assigns, :disabled?, case Map.get(extra[:rest], :disabled) do
      "true" -> true
      _ -> false
    end)

    ~H"""

    <button
      type={@type}
      class={if @disabled? do
      [
        "rounded-md bg-slate-400 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        @class
      ]
    else
      [
        "rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm",
        "active:text-white/80",
        @class
      ]
    end}
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </button>
    """
  end

Now changes the colour of the button correctly, from bg-slate-400 to bg-indigo-600. However, I have more questions now:

  1. Is there a way to move the condition @random_number > 50 to a function inside my_app_live.ex instead of simply having it in the heex file and still have this behaviour work?
  2. As mentioned in the reply to @LostKobrakai , the button still works as if it was disabled. Am I missing something from the correct usage of the global attribute @rest ?
  3. What would be the proper way to access disabled ?

Thanks everyone for the replies and help!

josevalim

josevalim

Creator of Elixir

There is no visible change happening in this code. So Phoenix will render it once and never change it unless you reload the page. If instead you have a button or a timer trigger the rand value and store it in an assign, it will work.

You could say “well, there is a change, it is random”. But if Phoenix assumed that every code had a random component, then we would not be able to change tracking anything ever.

TL;DR: Use assigns for changing state.

LostKobrakai

LostKobrakai

Not really. If you’re pulling out values out of assigns.rest you’re doing something wrong. The attribute shouldn’t be put into the catch all @rest in the first place, but be an explicit attr.

attr :disabled, :boolean, [opts]
attr :rest, :global, [opts]

def button(assigns) do
  # assigns[:disabled] or assigns.disabled (if you configure a default or the attr is required)
  …
end

Are you sure this is not releated to your :rand usage / function call? Do things work if you manually pass true or false?

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews