GumptionWare

GumptionWare

Hey @sezaru, I am stuck on getting this error when I try to compile.

error: undefined function put_notification/2 (expected MinderyWeb.UserSessionController to define such a function or for it to be imported, but none are available)

I can see from your repo that this is defined in flashy.ex, but my import in app.js is apparently not finding it–or maybe my mix deps.get is not picking it up. (I have verified both multiple times).

I’m probably making a dumb mistake/subtle typo somewhere. Anything obvious jump out at you?

0
Post #11
sezaru

sezaru

Did you went through the installation steps GitHub - sezaru/flashy: Flashy is a small library that extends LiveView's flash support to function and live components. · GitHub? In this specific case it seems you are missing the import Flashy in your lib/<your_app>_web.ex file

0
Post #12
GumptionWare

GumptionWare

UPDATE:

We resolved the undefined function put_notification issue. (We were missing the import Flashy in both places in MyAppWeb.ex.)

I think we are getting close. We are now figuring out how to change the icons displaying for each type.

1
Post #13
GumptionWare

GumptionWare

UPDATE:

We have Flashy functionality working, but we are figuring out why our configuration results in the Petal Alert components getting rendered for us much differently than they do in the flashy_demo project.

All that aside, many thanks for creating this. This is exactly the kind of thing I was saying we all need for our Phoenix projects.

1
Post #14
sezaru

sezaru

Not sure what is being rendered differently, but one thing that can affect it is what css you have applied in tags above it in the app.html.heex and root.html.heex.

If that is the case, you can always customize the Flashy.Container css to adapt to your needs.

0
Post #15
GumptionWare

GumptionWare

Thank you @sezaru. As a Phoenix newbie, I’m struggling with adapting my Liveview tests for using Flashy. For example:

...
  refute get_session(conn, :user_token)
  assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Password reset successfully"
...

I’m getting ** (FunctionClauseError) no function clause matching in Kernel.=~/2 against that Phoenix.Flash.get(conn.assigns.flash, :info) =~ ... line, which makes sense, because our conn.assigns no longer has a flash.

What I cannot discern from the Flashy docs is how to retrieve the value from the put_notification that replaced the former call to put_flash.

0
Post #16
sezaru

sezaru

So, the way flash mechanism work is that it is a key/value structure.

In other words, that Phoenix.Flash.get(conn.assigns.flash, :info) is searching for a flash with the key :info on it, internally, the function just does a simple Map.get .

This would work with the default flash that comes with phoenix since you create a flash giving it a key like :info, :error, etc and a string as a value.

That’s why you can’t have more than one flash message per key.

Flashy leverages the key/value to store strings as the key and a struct that implements the Flashy.Notification.Protocol as the value.

You can look how that works in this file flashy/lib/flashy.ex at master · sezaru/flashy · GitHub

If you want to test that, i guess you could mock the :erlang.unique_integer/1 function for the test, that way you would have control to how the key is generated during the test.

Or you can also just traverse the conn.assigns.flash map and find the notification there.

0
Post #17
GumptionWare

GumptionWare

Thanks, I have abstracted those dynamic values you assign to key (flashy-N) for my testing. I will share some examples once I work through the variations in my current suite of tests.

0
Post #18
davidmac

davidmac

Here is a solution using hooks. It hides info flash messages after 10 seconds and leaves error flash messages open until the user closes them.

Create a file called hooks.js for your hooks.

// assets/js/hooks.js
let Hooks = {}

Hooks.HideFlash =  {
  mounted() {
    setTimeout(() => {
      this.pushEvent("lv:clear-flash", { key: this.el.dataset.key })
      this.el.style.display = "none"
    }, 10000)
  }
}

export default Hooks

Don’t forget to add your hooks to your liveview socket.

// assets/js/app.js
// ...
let liveSocket = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: {_csrf_token: csrfToken},
  hooks: Hooks,
})
// ...

Add the hook to the flash component.

  # core_components.ex
  def flash(assigns) do
    assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)

    ~H"""
    <div
      :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
      id={@id}
      phx-hook={@kind == :info && "HideFlash"}
      phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
      role="alert"
      class={[
        "fixed top-2 right-2 mx-2 min-w-80 max-w-xl z-50 rounded-lg p-3 ring-1",
        @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
        @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
      ]}
      {@rest}
    >
      <p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
        <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
        <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
        <%= @title %>
      </p>
      <pre class="mt-2 text-sm leading-5 whitespace-pre-wrap"><%= msg %></pre>
      <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
        <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
      </button>
    </div>
    """
  end
10
Post #19
z7ealth

z7ealth

I used @sfusato solution this way

# myapp_web.ex
def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {NubacoinsWeb.Layouts, :app}

      unquote(html_helpers())
      unquote(flash_helpers()) # <- Added this line
    end
end

def flash_helpers do
    quote do
      def handle_info(:clear_flash, socket) do
        {:noreply, clear_flash(socket)}
      end
    end
end

# app.html.heex
<.flash_group phx-mount={Process.send_after(self(), :clear_flash, 3000)} flash={@flash} />

It worked so far

3
Post #20