Right click new tab for phx-click

I have a table where each row is clickable

          <tr phx-click={"[[\"navigate\",{\"href\":\"/battle/#{log.match_id}\",\"replace\":false}]]"}>

However when I right click the row I cannot see the option to open in new tab. How can I modify this?

Hi!
For that feature you need to replace in phx-click with good old <.link> helper:
https://hexdocs.pm/phoenix_live_view/live-navigation.html

1 Like

Don’t use links for table rows, html tables are finicky so it’s best to use on table elements. Instead build a menu with the options you want. Then add a reference to the menu as a contextmenu to what ever you wish to right click. Respond to the contextmenu event.

Edit: this method currently has poor browser support which is a shame. I came across this pattern many years ago and assumed the support would be better now. everything above is supported. The menu’s related <menuitem> element has been removed from the html spec Sorry

2 Likes

Does this work if I want to use it with a whole row?

No, neither the <table>, <tbody>, or <tr> elements accept nested anchors. Note <tr> permitted parents section, it can’t be the child of an anchor either.

You can try this:

<tr><td><span><%= live_redirect "Battle - #{log.match_id}", to: "battle/#{log.match_id}" %></span></td></tr>

This won’t make the whole row clickable just the first cell. There’s already an in-spec way to achieve what you want and you should use that since it’s web standard. If you want a hack however put an anchor in the first cell and apply an absolutely positioned pseudo element to it so that it takes up the whole row. But then you lose the ability to copy/paste or apply any other effects to the row because the first cell anchor now spans the entire row

OK that works and able to set it for one column at least.

New question:
How do I make it so it opens new tab on left click?

For that you need to change live_redirect to link:

<tr><td><span><%= link "Battle - #{log.match_id}", to: "battle/#{log.match_id}" , target: "_blank", rel: "noreferrer noopener" %></span></td></tr>

Both of these functions are deprected in the latest versions of their libraries.

Phoenix.Component — Phoenix LiveView v1.0.0-rc.6 provides a function component, which composes better in heex over those plain functions.

1 Like

tl;dr, Updated my core_components <.table> to include an optional row_link attr. So I can either pass in a row_link function that will wrap the slot in a <.link> (which does what people mentioned above) or use existing row_click for normal functionality. See code below (ignore custom styling), and further explanation below that:

  attr :row_link, :any,
    default: nil,
    doc: "function that given a row returns a URL string"

  attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"

  ...

  def table(assigns) do
    assigns =
      with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
        assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
      end

    ~H"""
    <div class="overflow-y-auto overflow-visible border-y rounded-none bg-white 2xl:border 2xl:rounded-2xl -mx-6 2xl:mx-0">
      <table class="w-[40rem] sm:w-full">
        <thead class="text-sm text-left text-gray-500">
          <tr>
            <th :for={col <- @col} class="px-6 py-3 2xl:py-4 font-semibold">
              <span :if={is_nil(col[:order_by])} class="relative">
                {col[:label]}
              </span>
            </th>
            <th :if={@action != []} class="relative p-0">
              <span class="sr-only">{gettext("Actions")}</span>
            </th>
          </tr>
        </thead>
        <tbody
          id={@id}
          phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
          class="relative divide-y divide-zinc-200 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
        >
          <tr
            :for={row <- @rows}
            id={@row_id && @row_id.(row)}
            class={["group hover:bg-zinc-50", @row_class && @row_class.(row)]}
          >
            <td
              :for={{col, i} <- Enum.with_index(@col)}
              class={["relative px-6 py-4", @row_click && "hover:cursor-pointer"]}
            >
              <%= if @row_link do %>
                <.link
                  navigate={@row_link.(row)}
                  class="block h-full w-full"
                  aria-label={"Go to #{inspect row}"}
                >
                  <span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
                    {render_slot(col, @row_item.(row))}
                  </span>
                </.link>
              <% else %>
                <div class="block" phx-click={@row_click && @row_click.(row)}>
                  <span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
                    {render_slot(col, @row_item.(row))}
                  </span>
                </div>
              <% end %>
            </td>
...

Under the hood, <.link> renders a real <a href="…">, so the browser gives you right-click/open-in-new-tab/copy-link for free. By contrast, phx-click attaches a JS listener that listens for click events and then pushes a LiveView message over the WebSocket—there’s no <a href> for the browser to recognize as a link. The result: you get your LiveView handler, but none of the built-in link behaviors (right-click menu, open-in-new-tab, URL preview, etc.) because to the browser it’s just a <div> with a JS listener.

1 Like