sb logoToday I Learned

2 posts about #phoenix

Using Phoenix hooks to control parent DOM elements

I’m building a scrollable modal that overlays a screen that’s also scrollable. I find it to be a bit of an awkward UX if both the foreground and background are scrollable in this case, so I want to disable the background scrolling when the modal opens.

The problem is that the document body can’t see the state of my LiveView. Fortunately, LiveView (combined with Tailwind CSS in our case) can handle this in another way. Using hooks, we can tell our app to add a CSS class when our modal opens, and then remove the class on modal close.

<!-- root.html.eex -->
<body id="app">
  ...
  <%= @inner_content %>
</body>

And now we add our hook:

// assets/js/hooks/index.js
const hooks = {}

hooks.ToggleAppScroll = {
  mounted: () => {
    document.getElementById("app").classList.toggle('overflow-hidden');
  },
  destroyed: () => {
    document.getElementById("app").classList.toggle('overflow-hidden');
  }
}

export default hooks

And then in our modal component:

  def render(assigns) do
    ~L"""
      <div phx-hook="ToggleAppScroll" class="bg-gray-300 bg-opacity-50 fixed top-0">
          <!-- Modal content goes here -->
      </div>
     """
  end

And now any new component that wants to disable scrolling of the app simply has to add phx-hook="ToggleAppScroll" to its attributes. The phx-hook lifecycle will handle the rest.

Using Dynamic queries in Ecto

When you have a Phoenix Controller and you need to do a query based on the params, you might end up with something likes this:

defmodule App.PostController do
  def index(conn, params) do
     posts = App.Context.list_posts(params)
     render(conn, "index.html", posts: posts)
  end
end
defmodule App.Context do
   ......
   def  list_posts(params) do
    query = Post 

    query = if user_id = params["owner_id"] do
      query |> where([p], p.user_id == ^user_id)
    else
      query
    end

    Repo.all(query)
  end
  .....
end

There is a better way! Dynamic queries (https://hexdocs.pm/ecto/dynamic-queries.html)

defmodule App.Context do
   ......
   def  list_posts(params) do
    Post 
    |> where(^filter_where(params))
    |> Repo.all()
  end

  defp filter_where(params) do
    Enum.reduce(params, dynamic(true), fn
      {"owner_id", user_id}, dynamic ->
        dynamic([p], ^dynamic and p.user_id == ^user_id)
      {_, _}, dynamic ->
        dynamic
    end)
  end
  .....
end

Now, all your where clauses are in one place :)