Is there another way to load global variable inside LiveView `~H`

Hi, I have a global parameter in top of my module like this:

  @tailwind_settings [
    {"layout", "Layout"},
    {"flexbox_grid", "Flexbox & Grid"},
    {"spacing", "Spacing"},
    ....
  ]

And I need to use it in a component, but it does not give me access to use it and thinks it does not exist in attributes

** (KeyError) key :tailwind_settings not found in: %{__changed__: nil, __given__: %{__changed__: nil, block_id: "28bd592e-ebf3-4ddf-8576-63eb4ad1f657"}, block_id: "28bd592e-ebf3-4ddf-8576-63eb4ad1f657", custom_class: "layout-icons", on_click: %Phoenix.LiveView.JS{ops: []}, type: "layout"}

So I am forced to assign it to another local var and use the local var inside the ~H like this:

  attr :block_id, :string, required: true
  attr :type, :string, required: false, default: "layout"
  attr :custom_class, :string, required: false, default: "layout-icons"
  attr :on_click, JS, default: %JS{}

  @spec block_settings(map) :: Phoenix.LiveView.Rendered.t()
  defp block_settings(assigns) do
    tailwind_settings = @tailwind_settings
    ~H"""
    <Heroicons.wrench_screwdriver
      class={@custom_class}
      phx-click={show_modal("#{@type}-settings-#{@block_id}")}
    />
    <.modal id={"#{@type}-settings-#{@block_id}"}>
      <p class="text-center font-bold mb-4 text-lg">Please select the section you want to edit</p>
      <div class="grid grid-cols-3 gap-3 text-gray-500 mt-8 mb-10 md:grid-cols-4 lg:grid-cols-5">
        <.block :for={{id, title} <- tailwind_settings} id={id} title={title}>
          <Heroicons.inbox_stack class="w-6 h-6 mx-auto stroke-current" />
        </.block>
      ....
    </.modal>
    """
  end

Thank you in advance

Even though attributes and assigns (from template) have the same @ syntax, they are totally different things. When a template is rendered the assigns as passed as parameter, however you are trying to access a module attribute, witch will never work.

One solution you can use is private functions, however you need to be sure that the template is part the module where you define your private functions. For example in classic phoenix you can do this in your *_view.ex files, because the rendered templates are part of that module.

Another solution is to define functions in one module and import them in your template:

defmodule MySettings do

@tailwind_settings [
    {"layout", "Layout"},
    {"flexbox_grid", "Flexbox & Grid"},
    {"spacing", "Spacing"},
    ....
  ]

  def tailwind_settings, do: @tailwind_settings
end

~H"""
<%= import MySettings %>
<%= tailwind_settings() %>
"""

1 Like

I just want something like this in JavaScript to get it out of function and prevent to duplicate coding, both of solutions are creating duplicate and if I am force to create function, I prefer to put all the list inside function instead of calling @tailwind_settings again

But Thank you

I don’t see how making a function creates “duplicate coding”, either using it from attribute or defining it directly in the function has the same effect on the compiled output.

3 Likes

Don’t mistake “convenience” for “simple”… :slight_smile:

Module attributes are not accessible outside of the modules they were defined in, but as @D4no0 said, you can make a function to expose that module attribute.

I’ve come to appreciate the simplicity of Elixir. A part of that simplicity is how modules work.

Further, your example is just not how HEEX and render/1 works. From what I understand, sigil_H (aka ~H"""""") is a macro that expands @foo to assigns.foo or maybe assigns[:foo], I don’t really know.

So you have to put things inside the assigns var for it to be seen in the HEEX macro.

I kind of agree that this is “magic”… and is confusing… and why I dislike Rails. I kinda wish HEEX was a macro that took an arg to make things less confusing…

def render(assigns) do
  assigns = assign(assign, :thing, "Hi, mom!")
  heex(assigns, """
    <div><%= @thing> %></div>
  """)
end

To me, that makes it more explicit that you can’t use module variables in render/1.

3 Likes