Skip to content

Commit

Permalink
Allow users to render content in the <head>
Browse files Browse the repository at this point in the history
Relates to: #455
  • Loading branch information
SteffenDE committed Nov 9, 2024
1 parent d1578d8 commit c44ed4d
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 11,914 deletions.
4 changes: 3 additions & 1 deletion assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ let Hooks = {
PhxRememberRefresh: PhxRememberRefresh
}

window.customHooks = window.customHooks || {}

let socketPath = document.querySelector("html").getAttribute("phx-socket") || "/live"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveView.LiveSocket(socketPath, Phoenix.Socket, {
hooks: Hooks,
hooks: { ...Hooks, ...window.customHooks },
params: (liveViewName) => {
return {
_csrf_token: csrfToken,
Expand Down
11,909 changes: 3 additions & 11,906 deletions dist/css/app.css

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions dist/js/app.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/js/app.js.map

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions lib/phoenix/live_dashboard/layout_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,20 @@ defmodule Phoenix.LiveDashboard.LayoutView do
)
end
end

defp custom_head_tags(assigns, key) do
case assigns do
%{^key => components} when is_list(components) ->
assigns = assign(assigns, :components, components)

~H"""
<%= for component <- @components do %>
<%= component.(assigns) %>
<% end %>
"""

_ ->
nil
end
end
end
2 changes: 2 additions & 0 deletions lib/phoenix/live_dashboard/layouts/dash.html.heex
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<!DOCTYPE html>
<html lang="en" phx-socket={live_socket_path(@conn)}>
<head>
<%= custom_head_tags(assigns, :after_opening_head_tag) %>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no, user-scalable=no"/>
<meta name="csrf-token" content={Phoenix.Controller.get_csrf_token()} />
<title><%= assigns[:page_title] || "Phoenix LiveDashboard" %></title>
<link rel="stylesheet" nonce={csp_nonce(@conn, :style)} href={asset_path(@conn, :css)}>
<script nonce={csp_nonce(@conn, :script)} src={asset_path(@conn, :js)} defer></script>
<%= custom_head_tags(assigns, :before_closing_head_tag) %>
</head>
<body>
<div class="d-flex flex-column align-items-stretch layout-wrapper">
Expand Down
94 changes: 93 additions & 1 deletion lib/phoenix/live_dashboard/page_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ defmodule Phoenix.LiveDashboard.PageBuilder do
We currently support `card/1`, `fields_card/1`, `row/1`,
`shared_usage_card/1`, and `usage_card/1`;
and the live components `live_layered_graph/1`, `live_nav_bar/1`,
and the live components `live_layered_graph/1`, `live_nav_bar/1`,
and `live_table/1`.
## Helpers
Expand All @@ -105,6 +105,74 @@ defmodule Phoenix.LiveDashboard.PageBuilder do
helpers are: `live_dashboard_path/2`, `live_dashboard_path/3`,
`encode_app/1`, `encode_ets/1`, `encode_pid/1`, `encode_port/1`,
and `encode_socket/1`.
## Custom Hooks
If your page needs to register custom hooks, you can use the `register_after_opening_head_tag/2`
function. Because the hooks need to be available on the dead render in the layout, before the
LiveView's LiveSocket is configured, your need to do this inside an `on_mount` hook:
```elixir
defmodule MyAppWeb.MyLiveDashboardHooks do
import Phoenix.LiveView
import Phoenix.Component
alias Phoenix.LiveDashboard.PageBuilder
def on_mount(:default, _params, _session, socket) do
{:cont, PageBuilder.register_after_opening_head_tag(socket, &after_opening_head_tag/1)}
end
defp after_opening_head_tag(assigns) do
~H\"\"\"
<script>
window.customHooks = {
...(window.customHooks || {}),
MyHook: {
mounted() {
// do something
}
}
}
</script>
\"\"\"
end
end
defmodule MyAppWeb.MyCustomPage do
...
end
```
And then add it to the list of `on_mount` hooks in the `live_dashboard` router configuration:
```elixir
live_dashboard "/dashboard",
additional_pages: [
route_name: MyAppWeb.MyCustomPage
],
on_mount: [
MyAppWeb.MyLiveDashboardHooks
]
```
The LiveDashboard will merge the `window.customHooks` object into the hooks that are
configured on the LiveSocket.
> #### Warning {: .warning}
>
> If you are building a library that will be used by others, ensure that you are
> not overwriting the `window.customHooks` object instead of extending it.
>
> Instead of `window.customHooks = {...}`,
> use `window.customHooks = {...(window.customHooks || {}), ...}`.
Note that in order to use external libraries, you will either need to include them from
a CDN, or bundle them yourself and include them from your apps static paths.
Also, you are responsible for ensuring that your Content Security Policy (CSP) allows
the hooks to be executed. If you are building a library that will be used by others,
consider including a valid nonce on your script tags.
"""

use Phoenix.Component
Expand Down Expand Up @@ -971,6 +1039,30 @@ defmodule Phoenix.LiveDashboard.PageBuilder do
live_dashboard_path(socket, route, node, old_params, new_params)
end

@doc """
Registers a component to be rendered after the opening head tag in the layout.
"""
def register_after_opening_head_tag(socket, component) do
register_head(socket, component, :after_opening_head_tag)
end

@doc """
Registers a component to be rendered before the closing head tag in the layout.
"""
def register_before_closing_head_tag(socket, component) do
register_head(socket, component, :before_closing_head_tag)
end

defp register_head(socket, component, assign) do
case socket do
%{assigns: %{^assign => [_ | _]}} ->
update(socket, assign, fn existing -> [component | existing] end)

_ ->
assign(socket, assign, [component])
end
end

# TODO: Remove this and the conditional on Phoenix v1.7+
@compile {:no_warn_undefined, Phoenix.VerifiedRoutes}

Expand Down

0 comments on commit c44ed4d

Please sign in to comment.